Compare commits
60 Commits
Group_Mana
...
v2.5.5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a811225610 | ||
|
|
1893c3814d | ||
|
|
422c391f96 | ||
|
|
f7f95ffbae | ||
|
|
f408bd7c77 | ||
|
|
ad13ce6cde | ||
|
|
c35179d924 | ||
|
|
cedc7f0fb8 | ||
|
|
64fa0e97a3 | ||
|
|
a45e9de472 | ||
|
|
101e9ebf35 | ||
|
|
17a76d2843 | ||
|
|
a23a5de540 | ||
|
|
a16e83468b | ||
|
|
1c59afe031 | ||
|
|
c49ec9a74c | ||
|
|
a0dd101d97 | ||
|
|
700cf9c10b | ||
|
|
697cd5e6d9 | ||
|
|
c6d27a4463 | ||
|
|
751f564c4a | ||
|
|
6658f648e6 | ||
|
|
d6f9f3f6d3 | ||
|
|
fad6c497eb | ||
|
|
42fa64770b | ||
|
|
0a207b8a2c | ||
|
|
26bf693dbd | ||
|
|
7483fb2ec5 | ||
|
|
2d8cca3a2e | ||
|
|
cf7fec1351 | ||
|
|
361849b9db | ||
|
|
c13db7922e | ||
|
|
d6d05a9b4d | ||
|
|
3caace2cb6 | ||
|
|
99f26be30d | ||
|
|
b0edd24c52 | ||
|
|
f0cfd48f66 | ||
|
|
f5aea03765 | ||
|
|
14cdde371f | ||
|
|
99a23f25d5 | ||
|
|
653ec90451 | ||
|
|
91a84db479 | ||
|
|
0f97eca314 | ||
|
|
fb79081aa1 | ||
|
|
ea19fb8ff6 | ||
|
|
f4cfe9eb63 | ||
|
|
4def70a006 | ||
|
|
e0e9e2681a | ||
|
|
31e1581d6b | ||
|
|
018e98a510 | ||
|
|
21ea673c30 | ||
|
|
08b55da408 | ||
|
|
7a3ee69a7f | ||
|
|
664bd9b596 | ||
|
|
ceb1217121 | ||
|
|
e754523689 | ||
|
|
e84503feec | ||
|
|
1bbf31df9f | ||
|
|
49bfff9fa5 | ||
|
|
d18a1a37ce |
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -83,7 +83,7 @@ jobs:
|
||||
cd x-ui/bin
|
||||
|
||||
# Download dependencies
|
||||
Xray_URL="https://github.com/XTLS/Xray-core/releases/download/v25.1.30/"
|
||||
Xray_URL="https://github.com/XTLS/Xray-core/releases/download/v25.3.6/"
|
||||
if [ "${{ matrix.platform }}" == "amd64" ]; then
|
||||
wget -q ${Xray_URL}Xray-linux-64.zip
|
||||
unzip Xray-linux-64.zip
|
||||
|
||||
@@ -27,7 +27,7 @@ case $1 in
|
||||
esac
|
||||
mkdir -p build/bin
|
||||
cd build/bin
|
||||
wget -q "https://github.com/XTLS/Xray-core/releases/download/v25.1.30/Xray-linux-${ARCH}.zip"
|
||||
wget -q "https://github.com/XTLS/Xray-core/releases/download/v25.3.6/Xray-linux-${ARCH}.zip"
|
||||
unzip "Xray-linux-${ARCH}.zip"
|
||||
rm -f "Xray-linux-${ARCH}.zip" geoip.dat geosite.dat
|
||||
mv xray "xray-linux-${FNAME}"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# ========================================================
|
||||
# Stage: Builder
|
||||
# ========================================================
|
||||
FROM golang:1.23-alpine AS builder
|
||||
FROM golang:1.24-alpine AS builder
|
||||
WORKDIR /app
|
||||
ARG TARGETARCH
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
2.5.1
|
||||
2.5.5
|
||||
@@ -26,35 +26,20 @@ const (
|
||||
)
|
||||
|
||||
func initModels() error {
|
||||
// Order matters: first create tables without dependencies
|
||||
baseModels := []interface{}{
|
||||
models := []interface{}{
|
||||
&model.User{},
|
||||
&model.Setting{},
|
||||
}
|
||||
|
||||
// Migrate base models
|
||||
for _, model := range baseModels {
|
||||
if err := db.AutoMigrate(model); err != nil {
|
||||
log.Printf("Error auto migrating base model: %v", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Then migrate models with dependencies
|
||||
dependentModels := []interface{}{
|
||||
&model.Inbound{},
|
||||
&model.OutboundTraffics{},
|
||||
&model.Setting{},
|
||||
&model.InboundClientIps{},
|
||||
&xray.ClientTraffic{},
|
||||
}
|
||||
|
||||
for _, model := range dependentModels {
|
||||
for _, model := range models {
|
||||
if err := db.AutoMigrate(model); err != nil {
|
||||
log.Printf("Error auto migrating dependent model: %v", err)
|
||||
log.Printf("Error auto migrating model: %v", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -97,31 +82,9 @@ func InitDB(dbPath string) error {
|
||||
}
|
||||
|
||||
c := &gorm.Config{
|
||||
Logger: gormLogger,
|
||||
SkipDefaultTransaction: true,
|
||||
PrepareStmt: true,
|
||||
Logger: gormLogger,
|
||||
}
|
||||
|
||||
dsn := dbPath + "?cache=shared&_journal_mode=WAL&_synchronous=NORMAL"
|
||||
db, err = gorm.Open(sqlite.Open(dsn), c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sqlDB, err := db.DB()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = sqlDB.Exec("PRAGMA cache_size = -64000;")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = sqlDB.Exec("PRAGMA temp_store = MEMORY;")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = sqlDB.Exec("PRAGMA foreign_keys = ON;")
|
||||
db, err = gorm.Open(sqlite.Open(dbPath), c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -138,11 +101,6 @@ func InitDB(dbPath string) error {
|
||||
|
||||
func CloseDB() error {
|
||||
if db != nil {
|
||||
|
||||
if err := Checkpoint(); err != nil {
|
||||
log.Printf("error executing checkpoint: %v", err)
|
||||
}
|
||||
|
||||
sqlDB, err := db.DB()
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -29,14 +29,14 @@ type User struct {
|
||||
|
||||
type Inbound struct {
|
||||
Id int `json:"id" form:"id" gorm:"primaryKey;autoIncrement"`
|
||||
UserId int `json:"-" gorm:"index"`
|
||||
UserId int `json:"-"`
|
||||
Up int64 `json:"up" form:"up"`
|
||||
Down int64 `json:"down" form:"down"`
|
||||
Total int64 `json:"total" form:"total"`
|
||||
Remark string `json:"remark" form:"remark"`
|
||||
Enable bool `json:"enable" form:"enable"`
|
||||
ExpiryTime int64 `json:"expiryTime" form:"expiryTime"`
|
||||
ClientStats []xray.ClientTraffic `gorm:"foreignKey:InboundId;references:Id;constraint:OnDelete:CASCADE" json:"clientStats"`
|
||||
ClientStats []xray.ClientTraffic `gorm:"foreignKey:InboundId;references:Id" json:"clientStats" form:"clientStats"`
|
||||
|
||||
// config part
|
||||
Listen string `json:"listen" form:"listen"`
|
||||
|
||||
64
go.mod
64
go.mod
@@ -1,6 +1,6 @@
|
||||
module x-ui
|
||||
|
||||
go 1.23.5
|
||||
go 1.24.1
|
||||
|
||||
require (
|
||||
github.com/gin-contrib/gzip v1.2.2
|
||||
@@ -12,21 +12,21 @@ require (
|
||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
|
||||
github.com/pelletier/go-toml/v2 v2.2.3
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/shirou/gopsutil/v4 v4.25.1
|
||||
github.com/valyala/fasthttp v1.58.0
|
||||
github.com/xtls/xray-core v1.8.25-0.20250130105737-0a8470cb14eb
|
||||
github.com/shirou/gopsutil/v4 v4.25.2
|
||||
github.com/valyala/fasthttp v1.59.0
|
||||
github.com/xtls/xray-core v1.250306.0
|
||||
go.uber.org/atomic v1.11.0
|
||||
golang.org/x/text v0.21.0
|
||||
google.golang.org/grpc v1.70.0
|
||||
golang.org/x/text v0.23.0
|
||||
google.golang.org/grpc v1.71.0
|
||||
gorm.io/driver/sqlite v1.5.7
|
||||
gorm.io/gorm v1.25.12
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/andybalholm/brotli v1.1.1 // indirect
|
||||
github.com/bytedance/sonic v1.12.8 // indirect
|
||||
github.com/bytedance/sonic/loader v0.2.3 // indirect
|
||||
github.com/cloudflare/circl v1.5.0 // indirect
|
||||
github.com/bytedance/sonic v1.13.1 // indirect
|
||||
github.com/bytedance/sonic/loader v0.2.4 // indirect
|
||||
github.com/cloudflare/circl v1.6.0 // indirect
|
||||
github.com/cloudwego/base64x v0.1.5 // indirect
|
||||
github.com/dgryski/go-metro v0.0.0-20250106013310-edb8663e5e33 // indirect
|
||||
github.com/ebitengine/purego v0.8.2 // indirect
|
||||
@@ -36,10 +36,10 @@ require (
|
||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.24.0 // indirect
|
||||
github.com/go-playground/validator/v10 v10.25.0 // indirect
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
|
||||
github.com/google/btree v1.1.3 // indirect
|
||||
github.com/google/pprof v0.0.0-20250202011525-fc3143867406 // indirect
|
||||
github.com/google/pprof v0.0.0-20250302191652-9094ed2288e7 // indirect
|
||||
github.com/gorilla/context v1.1.2 // indirect
|
||||
github.com/gorilla/securecookie v1.1.2 // indirect
|
||||
github.com/gorilla/sessions v1.4.0 // indirect
|
||||
@@ -48,29 +48,29 @@ require (
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/compress v1.17.11 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.9 // indirect
|
||||
github.com/klauspost/compress v1.18.0 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20250303091104-876f3ea5145d // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.24 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.22.2 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.23.0 // indirect
|
||||
github.com/pires/go-proxyproto v0.8.0 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
|
||||
github.com/quic-go/qpack v0.5.1 // indirect
|
||||
github.com/quic-go/quic-go v0.49.0 // indirect
|
||||
github.com/quic-go/quic-go v0.50.0 // indirect
|
||||
github.com/refraction-networking/utls v1.6.7 // indirect
|
||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
|
||||
github.com/rogpeppe/go-internal v1.13.1 // indirect
|
||||
github.com/sagernet/sing v0.5.1 // indirect
|
||||
github.com/rogpeppe/go-internal v1.14.1 // indirect
|
||||
github.com/sagernet/sing v0.6.3 // indirect
|
||||
github.com/sagernet/sing-shadowsocks v0.2.7 // indirect
|
||||
github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 // indirect
|
||||
github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.14 // indirect
|
||||
github.com/tklauser/numcpus v0.9.0 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.15 // indirect
|
||||
github.com/tklauser/numcpus v0.10.0 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e // indirect
|
||||
@@ -82,20 +82,20 @@ require (
|
||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||
go.uber.org/mock v0.5.0 // indirect
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
|
||||
golang.org/x/arch v0.13.0 // indirect
|
||||
golang.org/x/crypto v0.32.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c // indirect
|
||||
golang.org/x/mod v0.22.0 // indirect
|
||||
golang.org/x/net v0.34.0 // indirect
|
||||
golang.org/x/sync v0.10.0 // indirect
|
||||
golang.org/x/sys v0.29.0 // indirect
|
||||
golang.org/x/time v0.9.0 // indirect
|
||||
golang.org/x/tools v0.29.0 // indirect
|
||||
golang.org/x/arch v0.15.0 // indirect
|
||||
golang.org/x/crypto v0.36.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect
|
||||
golang.org/x/mod v0.24.0 // indirect
|
||||
golang.org/x/net v0.37.0 // indirect
|
||||
golang.org/x/sync v0.12.0 // indirect
|
||||
golang.org/x/sys v0.31.0 // indirect
|
||||
golang.org/x/time v0.11.0 // indirect
|
||||
golang.org/x/tools v0.31.0 // indirect
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
||||
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250127172529-29210b9bc287 // indirect
|
||||
google.golang.org/protobuf v1.36.4 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb // indirect
|
||||
google.golang.org/protobuf v1.36.5 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
gvisor.dev/gvisor v0.0.0-20240320123526-dc6abceb7ff0 // indirect
|
||||
lukechampine.com/blake3 v1.3.0 // indirect
|
||||
lukechampine.com/blake3 v1.4.0 // indirect
|
||||
)
|
||||
|
||||
150
go.sum
150
go.sum
@@ -4,13 +4,13 @@ github.com/OmarTariq612/goech v0.0.0-20240405204721-8e2e1dafd3a0 h1:Wo41lDOevRJS
|
||||
github.com/OmarTariq612/goech v0.0.0-20240405204721-8e2e1dafd3a0/go.mod h1:FVGavL/QEBQDcBpr3fAojoK17xX5k9bicBphrOpP7uM=
|
||||
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
|
||||
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
|
||||
github.com/bytedance/sonic v1.12.8 h1:4xYRVRlXIgvSZ4e8iVTlMF5szgpXd4AfvuWgA8I8lgs=
|
||||
github.com/bytedance/sonic v1.12.8/go.mod h1:uVvFidNmlt9+wa31S1urfwwthTWteBgG0hWuoKAXTx8=
|
||||
github.com/bytedance/sonic v1.13.1 h1:Jyd5CIvdFnkOWuKXr+wm4Nyk2h0yAFsr8ucJgEasO3g=
|
||||
github.com/bytedance/sonic v1.13.1/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4=
|
||||
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||
github.com/bytedance/sonic/loader v0.2.3 h1:yctD0Q3v2NOGfSWPLPvG2ggA2kV6TS6s4wioyEqssH0=
|
||||
github.com/bytedance/sonic/loader v0.2.3/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
||||
github.com/cloudflare/circl v1.5.0 h1:hxIWksrX6XN5a1L2TI/h53AGPhNHoUBo+TD1ms9+pys=
|
||||
github.com/cloudflare/circl v1.5.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
|
||||
github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY=
|
||||
github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
||||
github.com/cloudflare/circl v1.6.0 h1:cr5JKic4HI+LkINy2lg3W2jF8sHCVTBncJr5gIIq7qk=
|
||||
github.com/cloudflare/circl v1.6.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
|
||||
github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
|
||||
github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
||||
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
||||
@@ -50,8 +50,8 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.24.0 h1:KHQckvo8G6hlWnrPX4NJJ+aBfWNAE/HH+qdL2cBpCmg=
|
||||
github.com/go-playground/validator/v10 v10.24.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus=
|
||||
github.com/go-playground/validator/v10 v10.25.0 h1:5Dh7cjvzR7BRZadnsVOzPhWsrwUr0nmsZJxEAnFLNO8=
|
||||
github.com/go-playground/validator/v10 v10.25.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
||||
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||
@@ -62,13 +62,13 @@ github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
|
||||
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/pprof v0.0.0-20250202011525-fc3143867406 h1:wlQI2cYY0BsWmmPPAnxfQ8SDW0S3Jasn+4B8kXFxprg=
|
||||
github.com/google/pprof v0.0.0-20250202011525-fc3143867406/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
|
||||
github.com/google/pprof v0.0.0-20250302191652-9094ed2288e7 h1:+J3r2e8+RsmN3vKfo75g0YSY61ms37qzPglu4p0sGro=
|
||||
github.com/google/pprof v0.0.0-20250302191652-9094ed2288e7/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/context v1.1.2 h1:WRkNAv2uoa03QNIc1A6u4O7DAGMUVoopZhkiXWA2V1o=
|
||||
@@ -87,11 +87,11 @@ github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
|
||||
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
|
||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY=
|
||||
github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8=
|
||||
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
|
||||
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
@@ -99,8 +99,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
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/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 h1:7UMa6KCCMjZEMDtTVdcGu0B1GmmC7QJKiCCjyTAWQy0=
|
||||
github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k=
|
||||
github.com/lufia/plan9stats v0.0.0-20250303091104-876f3ea5145d h1:fjMbDVUGsMQiVZnSQsmouYJvMdwsGiDipOZoN66v844=
|
||||
github.com/lufia/plan9stats v0.0.0-20250303091104-876f3ea5145d/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/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
|
||||
@@ -116,8 +116,8 @@ github.com/mymmrac/telego v0.32.0 h1:4X8C1l3k+opkk86r95+eQE8DxiS2LYlR61L/G7yreDY
|
||||
github.com/mymmrac/telego v0.32.0/go.mod h1:qS6NaRhJgcuEEBEMVCV79S2xCAuHq9O+ixwfLuRW31M=
|
||||
github.com/nicksnyder/go-i18n/v2 v2.5.1 h1:IxtPxYsR9Gp60cGXjfuR/llTqV8aYMsC472zD0D1vHk=
|
||||
github.com/nicksnyder/go-i18n/v2 v2.5.1/go.mod h1:DrhgsSDZxoAfvVrBVLXoxZn/pN5TXqaDbq7ju94viiQ=
|
||||
github.com/onsi/ginkgo/v2 v2.22.2 h1:/3X8Panh8/WwhU/3Ssa6rCKqPLuAkVY2I0RoyDLySlU=
|
||||
github.com/onsi/ginkgo/v2 v2.22.2/go.mod h1:oeMosUL+8LtarXBHu/c0bx2D/K9zyQ6uX3cTyztHwsk=
|
||||
github.com/onsi/ginkgo/v2 v2.23.0 h1:FA1xjp8ieYDzlgS5ABTpdUDB7wtngggONc8a7ku2NqQ=
|
||||
github.com/onsi/ginkgo/v2 v2.23.0/go.mod h1:zXTP6xIp3U8aVuXN8ENK9IXRaTjFnpVB9mGmaSRvxnM=
|
||||
github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8=
|
||||
github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY=
|
||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88=
|
||||
@@ -134,26 +134,26 @@ github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
|
||||
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
|
||||
github.com/quic-go/quic-go v0.49.0 h1:w5iJHXwHxs1QxyBv1EHKuC50GX5to8mJAxvtnttJp94=
|
||||
github.com/quic-go/quic-go v0.49.0/go.mod h1:s2wDnmCdooUQBmQfpUSTCYBl1/D4FcqbULMMkASvR6s=
|
||||
github.com/quic-go/quic-go v0.50.0 h1:3H/ld1pa3CYhkcc20TPIyG1bNsdhn9qZBGN3b9/UyUo=
|
||||
github.com/quic-go/quic-go v0.50.0/go.mod h1:Vim6OmUvlYdwBhXP9ZVrtGmCMWa3wEqhq3NgYrI8b4E=
|
||||
github.com/refraction-networking/utls v1.6.7 h1:zVJ7sP1dJx/WtVuITug3qYUq034cDq9B2MR1K67ULZM=
|
||||
github.com/refraction-networking/utls v1.6.7/go.mod h1:BC3O4vQzye5hqpmDTWUqi4P5DDhzJfkV1tdqtawQIH0=
|
||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
|
||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s=
|
||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||
github.com/sagernet/sing v0.5.1 h1:mhL/MZVq0TjuvHcpYcFtmSD1BFOxZ/+8ofbNZcg1k1Y=
|
||||
github.com/sagernet/sing v0.5.1/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/sagernet/sing v0.6.3 h1:J1spMc6LMlqUvRjWjvNMAcbvACDneqxB9zxfLuS0UTE=
|
||||
github.com/sagernet/sing v0.6.3/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
||||
github.com/sagernet/sing-shadowsocks v0.2.7 h1:zaopR1tbHEw5Nk6FAkM05wCslV6ahVegEZaKMv9ipx8=
|
||||
github.com/sagernet/sing-shadowsocks v0.2.7/go.mod h1:0rIKJZBR65Qi0zwdKezt4s57y/Tl1ofkaq6NlkzVuyE=
|
||||
github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 h1:D0vL7YNisV2yqE55+q0lFuGse6U8lxlg7fYTctlT5Gc=
|
||||
github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38/go.mod h1:sM7Mt7uEoCeFSCBM+qBrqvEo+/9vdmj19wzp3yzUhmg=
|
||||
github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771 h1:emzAzMZ1L9iaKCTxdy3Em8Wv4ChIAGnfiz18Cda70g4=
|
||||
github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg=
|
||||
github.com/shirou/gopsutil/v4 v4.25.1 h1:QSWkTc+fu9LTAWfkZwZ6j8MSUk4A2LV7rbH0ZqmLjXs=
|
||||
github.com/shirou/gopsutil/v4 v4.25.1/go.mod h1:RoUCUpndaJFtT+2zsZzzmhvbfGoDCJ7nFXKJf8GqJbI=
|
||||
github.com/shirou/gopsutil/v4 v4.25.2 h1:NMscG3l2CqtWFS86kj3vP7soOczqrQYIEhO/pMvvQkk=
|
||||
github.com/shirou/gopsutil/v4 v4.25.2/go.mod h1:34gBYJzyqCDT11b6bMHP0XCvWeU3J61XRT7a2EmCRTA=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
@@ -167,10 +167,10 @@ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU=
|
||||
github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY=
|
||||
github.com/tklauser/numcpus v0.9.0 h1:lmyCHtANi8aRUgkckBgoDk1nHCux3n2cgkJLXdQGPDo=
|
||||
github.com/tklauser/numcpus v0.9.0/go.mod h1:SN6Nq1O3VychhC1npsWostA+oW+VOQTxZrS604NSRyI=
|
||||
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/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso=
|
||||
github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
||||
@@ -179,8 +179,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/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasthttp v1.58.0 h1:GGB2dWxSbEprU9j0iMJHgdKYJVDyjrOwF9RE59PbRuE=
|
||||
github.com/valyala/fasthttp v1.58.0/go.mod h1:SYXvHHaFp7QZHGKSHmoMipInhrI5StHrhDTYVEjK/Kw=
|
||||
github.com/valyala/fasthttp v1.59.0 h1:Qu0qYHfXvPk1mSLNqcFtEk6DpxgA26hy6bmydotDpRI=
|
||||
github.com/valyala/fasthttp v1.59.0/go.mod h1:GTxNb9Bc6r2a9D0TWNSPwDz78UxnTGBViY3xZNEqyYU=
|
||||
github.com/valyala/fastjson v1.6.4 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ=
|
||||
github.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
|
||||
github.com/vishvananda/netlink v1.3.0 h1:X7l42GfcV4S6E4vHTsw48qbrV+9PVojNfIhZcwQdrZk=
|
||||
@@ -190,64 +190,66 @@ github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zd
|
||||
github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
|
||||
github.com/xtls/reality v0.0.0-20240909153216-e26ae2305463 h1:g1Cj7d+my6k/HHxLAyxPwyX8i7FGRr6ulBDMkBzg2BM=
|
||||
github.com/xtls/reality v0.0.0-20240909153216-e26ae2305463/go.mod h1:BjIOLmkEEtAgloAiVUcYj0Mt+YU00JARZw8AEU0IwAg=
|
||||
github.com/xtls/xray-core v1.8.25-0.20250130105737-0a8470cb14eb h1:VBzDZ4XHT9FM0S3qvXp6KuTOk7mXQQm0pjOW0Neeog4=
|
||||
github.com/xtls/xray-core v1.8.25-0.20250130105737-0a8470cb14eb/go.mod h1:EKhNEDY/WIB+ylEbVv2pzUaP08W/lt0sYmJQ9FJruko=
|
||||
github.com/xtls/xray-core v1.250306.0 h1:XZyZvSgcpAoVEGnFnxNdoHbSF7Kp77A/0TPk4lhv6rM=
|
||||
github.com/xtls/xray-core v1.250306.0/go.mod h1:clXnUOnX6CKWBGgJY4ePYhb/EtTdSrUC7vPfT6m5p4c=
|
||||
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
||||
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/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
go.opentelemetry.io/otel v1.32.0 h1:WnBN+Xjcteh0zdk01SVqV55d/m62NJLJdIyb4y/WO5U=
|
||||
go.opentelemetry.io/otel v1.32.0/go.mod h1:00DCVSB0RQcnzlwyTfqtxSm+DRr9hpYrHjNGiBHVQIg=
|
||||
go.opentelemetry.io/otel/metric v1.32.0 h1:xV2umtmNcThh2/a/aCP+h64Xx5wsj8qqnkYZktzNa0M=
|
||||
go.opentelemetry.io/otel/metric v1.32.0/go.mod h1:jH7CIbbK6SH2V2wE16W05BHCtIDzauciCRLoc/SyMv8=
|
||||
go.opentelemetry.io/otel/sdk v1.32.0 h1:RNxepc9vK59A8XsgZQouW8ue8Gkb4jpWtJm9ge5lEG4=
|
||||
go.opentelemetry.io/otel/sdk v1.32.0/go.mod h1:LqgegDBjKMmb2GC6/PrTnteJG39I8/vJCAP9LlJXEjU=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.32.0 h1:rZvFnvmvawYb0alrYkjraqJq0Z4ZUJAiyYCU9snn1CU=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.32.0/go.mod h1:PWeZlq0zt9YkYAp3gjKZ0eicRYvOh1Gd+X99x6GHpCQ=
|
||||
go.opentelemetry.io/otel/trace v1.32.0 h1:WIC9mYrXf8TmY/EXuULKc8hR17vE+Hjv2cssQDe03fM=
|
||||
go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8=
|
||||
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/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY=
|
||||
go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI=
|
||||
go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ=
|
||||
go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE=
|
||||
go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A=
|
||||
go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w=
|
||||
go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k=
|
||||
go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE=
|
||||
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/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
|
||||
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
|
||||
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=
|
||||
golang.org/x/arch v0.13.0 h1:KCkqVVV1kGg0X87TFysjCJ8MxtZEIU4Ja/yXGeoECdA=
|
||||
golang.org/x/arch v0.13.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
|
||||
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
|
||||
golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c h1:KL/ZBHXgKGVmuZBZ01Lt57yE5ws8ZPSkkihmEyq7FXc=
|
||||
golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU=
|
||||
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
|
||||
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
||||
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
|
||||
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
|
||||
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/arch v0.15.0 h1:QtOrQd0bTUnhNVNndMpLHNWrDmYzZ2KDqSrEymqInZw=
|
||||
golang.org/x/arch v0.15.0/go.mod h1:JmwW7aLIoRUKgaTzhkiEFxvcEiQGyOg9BMonBJUS7EE=
|
||||
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
|
||||
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
|
||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw=
|
||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM=
|
||||
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
|
||||
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
||||
golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
|
||||
golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
||||
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
|
||||
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=
|
||||
golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE=
|
||||
golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588=
|
||||
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
|
||||
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
|
||||
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
|
||||
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
|
||||
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||
golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU=
|
||||
golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ=
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uIfPMv78iAJGcPKDeqAFnaLBropIC4=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250127172529-29210b9bc287 h1:J1H9f+LEdWAfHcez/4cvaVBox7cOYT+IU6rgqj5x++8=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250127172529-29210b9bc287/go.mod h1:8BS3B93F/U1juMFq9+EDk+qOT5CO1R9IzXxG3PTqiRk=
|
||||
google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ=
|
||||
google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw=
|
||||
google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM=
|
||||
google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb h1:TLPQVbx1GJ8VKZxz52VAxl1EBgKXXbTiU9Fc5fZeLn4=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I=
|
||||
google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg=
|
||||
google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec=
|
||||
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
|
||||
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
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/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
@@ -263,6 +265,6 @@ gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
|
||||
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
|
||||
gvisor.dev/gvisor v0.0.0-20240320123526-dc6abceb7ff0 h1:P+U/06iIKPQ3DLcg+zBfSCia1luZ2msPZrJ8jYDFPs0=
|
||||
gvisor.dev/gvisor v0.0.0-20240320123526-dc6abceb7ff0/go.mod h1:NQHVAzMwvZ+Qe3ElSiHmq9RUm1MdNHpUZ52fiEqvn+0=
|
||||
lukechampine.com/blake3 v1.3.0 h1:sJ3XhFINmHSrYCgl958hscfIa3bw8x4DqMP3u1YvoYE=
|
||||
lukechampine.com/blake3 v1.3.0/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k=
|
||||
lukechampine.com/blake3 v1.4.0 h1:xDbKOZCVbnZsfzM6mHSYcGRHZ3YrLDzqz8XnV4uaD5w=
|
||||
lukechampine.com/blake3 v1.4.0/go.mod h1:MQJNQCTnR+kwOP/JEZSxj3MaQjp80FOFSNMMHXcSeX0=
|
||||
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
||||
|
||||
15
install.sh
15
install.sh
@@ -90,6 +90,10 @@ elif [[ "${release}" == "ol" ]]; then
|
||||
if [[ ${os_version} -lt 8 ]]; then
|
||||
echo -e "${red} Please use Oracle Linux 8 or higher ${plain}\n" && exit 1
|
||||
fi
|
||||
elif [[ "${release}" == "virtuozzo" ]]; then
|
||||
if [[ ${os_version} -lt 8 ]]; then
|
||||
echo -e "${red} Please use Virtuozzo Linux 8 or higher ${plain}\n" && exit 1
|
||||
fi
|
||||
else
|
||||
echo -e "${red}Your operating system is not supported by this script.${plain}\n"
|
||||
echo "Please ensure you are using one of the following supported operating systems:"
|
||||
@@ -107,6 +111,7 @@ else
|
||||
echo "- Oracle Linux 8+"
|
||||
echo "- OpenSUSE Tumbleweed"
|
||||
echo "- Amazon Linux 2023"
|
||||
echo "- Virtuozzo Linux 8+"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -118,7 +123,7 @@ install_base() {
|
||||
centos | almalinux | rocky | ol)
|
||||
yum -y update && yum install -y -q wget curl tar tzdata
|
||||
;;
|
||||
fedora | amzn)
|
||||
fedora | amzn | virtuozzo)
|
||||
dnf -y update && dnf install -y -q wget curl tar tzdata
|
||||
;;
|
||||
arch | manjaro | parch)
|
||||
@@ -209,7 +214,7 @@ install_x-ui() {
|
||||
exit 1
|
||||
fi
|
||||
echo -e "Got x-ui latest version: ${tag_version}, beginning the installation..."
|
||||
wget -N --no-check-certificate -O /usr/local/x-ui-linux-$(arch).tar.gz https://github.com/MHSanaei/3x-ui/releases/download/${tag_version}/x-ui-linux-$(arch).tar.gz
|
||||
wget -N -O /usr/local/x-ui-linux-$(arch).tar.gz https://github.com/MHSanaei/3x-ui/releases/download/${tag_version}/x-ui-linux-$(arch).tar.gz
|
||||
if [[ $? -ne 0 ]]; then
|
||||
echo -e "${red}Downloading x-ui failed, please be sure that your server can access GitHub ${plain}"
|
||||
exit 1
|
||||
@@ -226,7 +231,7 @@ install_x-ui() {
|
||||
|
||||
url="https://github.com/MHSanaei/3x-ui/releases/download/${tag_version}/x-ui-linux-$(arch).tar.gz"
|
||||
echo -e "Beginning to install x-ui $1"
|
||||
wget -N --no-check-certificate -O /usr/local/x-ui-linux-$(arch).tar.gz ${url}
|
||||
wget -N -O /usr/local/x-ui-linux-$(arch).tar.gz ${url}
|
||||
if [[ $? -ne 0 ]]; then
|
||||
echo -e "${red}Download x-ui $1 failed, please check if the version exists ${plain}"
|
||||
exit 1
|
||||
@@ -251,7 +256,7 @@ install_x-ui() {
|
||||
|
||||
chmod +x x-ui bin/xray-linux-$(arch)
|
||||
cp -f x-ui.service /etc/systemd/system/
|
||||
wget --no-check-certificate -O /usr/bin/x-ui https://raw.githubusercontent.com/MHSanaei/3x-ui/main/x-ui.sh
|
||||
wget -O /usr/bin/x-ui https://raw.githubusercontent.com/MHSanaei/3x-ui/main/x-ui.sh
|
||||
chmod +x /usr/local/x-ui/x-ui.sh
|
||||
chmod +x /usr/bin/x-ui
|
||||
config_after_install
|
||||
@@ -283,4 +288,4 @@ install_x-ui() {
|
||||
|
||||
echo -e "${green}Running...${plain}"
|
||||
install_base
|
||||
install_x-ui $1
|
||||
install_x-ui $1
|
||||
|
||||
1
web/assets/base64/base64.min.js
vendored
1
web/assets/base64/base64.min.js
vendored
@@ -1 +0,0 @@
|
||||
(function(global,factory){typeof exports==="object"&&typeof module!=="undefined"?module.exports=factory(global):typeof define==="function"&&define.amd?define(factory):factory(global)})(typeof self!=="undefined"?self:typeof window!=="undefined"?window:typeof global!=="undefined"?global:this,function(global){"use strict";var _Base64=global.Base64;var version="2.5.0";var buffer;if(typeof module!=="undefined"&&module.exports){try{buffer=eval("require('buffer').Buffer")}catch(err){buffer=undefined}}var b64chars="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";var b64tab=function(bin){var t={};for(var i=0,l=bin.length;i<l;i++)t[bin.charAt(i)]=i;return t}(b64chars);var fromCharCode=String.fromCharCode;var cb_utob=function(c){if(c.length<2){var cc=c.charCodeAt(0);return cc<128?c:cc<2048?fromCharCode(192|cc>>>6)+fromCharCode(128|cc&63):fromCharCode(224|cc>>>12&15)+fromCharCode(128|cc>>>6&63)+fromCharCode(128|cc&63)}else{var cc=65536+(c.charCodeAt(0)-55296)*1024+(c.charCodeAt(1)-56320);return fromCharCode(240|cc>>>18&7)+fromCharCode(128|cc>>>12&63)+fromCharCode(128|cc>>>6&63)+fromCharCode(128|cc&63)}};var re_utob=/[\uD800-\uDBFF][\uDC00-\uDFFFF]|[^\x00-\x7F]/g;var utob=function(u){return u.replace(re_utob,cb_utob)};var cb_encode=function(ccc){var padlen=[0,2,1][ccc.length%3],ord=ccc.charCodeAt(0)<<16|(ccc.length>1?ccc.charCodeAt(1):0)<<8|(ccc.length>2?ccc.charCodeAt(2):0),chars=[b64chars.charAt(ord>>>18),b64chars.charAt(ord>>>12&63),padlen>=2?"=":b64chars.charAt(ord>>>6&63),padlen>=1?"=":b64chars.charAt(ord&63)];return chars.join("")};var btoa=global.btoa?function(b){return global.btoa(b)}:function(b){return b.replace(/[\s\S]{1,3}/g,cb_encode)};var _encode=buffer?buffer.from&&Uint8Array&&buffer.from!==Uint8Array.from?function(u){return(u.constructor===buffer.constructor?u:buffer.from(u)).toString("base64")}:function(u){return(u.constructor===buffer.constructor?u:new buffer(u)).toString("base64")}:function(u){return btoa(utob(u))};var encode=function(u,urisafe){return!urisafe?_encode(String(u)):_encode(String(u)).replace(/[+\/]/g,function(m0){return m0=="+"?"-":"_"}).replace(/=/g,"")};var encodeURI=function(u){return encode(u,true)};var re_btou=new RegExp(["[À-ß][-¿]","[à-ï][-¿]{2}","[ð-÷][-¿]{3}"].join("|"),"g");var cb_btou=function(cccc){switch(cccc.length){case 4:var cp=(7&cccc.charCodeAt(0))<<18|(63&cccc.charCodeAt(1))<<12|(63&cccc.charCodeAt(2))<<6|63&cccc.charCodeAt(3),offset=cp-65536;return fromCharCode((offset>>>10)+55296)+fromCharCode((offset&1023)+56320);case 3:return fromCharCode((15&cccc.charCodeAt(0))<<12|(63&cccc.charCodeAt(1))<<6|63&cccc.charCodeAt(2));default:return fromCharCode((31&cccc.charCodeAt(0))<<6|63&cccc.charCodeAt(1))}};var btou=function(b){return b.replace(re_btou,cb_btou)};var cb_decode=function(cccc){var len=cccc.length,padlen=len%4,n=(len>0?b64tab[cccc.charAt(0)]<<18:0)|(len>1?b64tab[cccc.charAt(1)]<<12:0)|(len>2?b64tab[cccc.charAt(2)]<<6:0)|(len>3?b64tab[cccc.charAt(3)]:0),chars=[fromCharCode(n>>>16),fromCharCode(n>>>8&255),fromCharCode(n&255)];chars.length-=[0,0,2,1][padlen];return chars.join("")};var _atob=global.atob?function(a){return global.atob(a)}:function(a){return a.replace(/\S{1,4}/g,cb_decode)};var atob=function(a){return _atob(String(a).replace(/[^A-Za-z0-9\+\/]/g,""))};var _decode=buffer?buffer.from&&Uint8Array&&buffer.from!==Uint8Array.from?function(a){return(a.constructor===buffer.constructor?a:buffer.from(a,"base64")).toString()}:function(a){return(a.constructor===buffer.constructor?a:new buffer(a,"base64")).toString()}:function(a){return btou(_atob(a))};var decode=function(a){return _decode(String(a).replace(/[-_]/g,function(m0){return m0=="-"?"+":"/"}).replace(/[^A-Za-z0-9\+\/]/g,""))};var noConflict=function(){var Base64=global.Base64;global.Base64=_Base64;return Base64};global.Base64={VERSION:version,atob:atob,btoa:btoa,fromBase64:decode,toBase64:encode,utob:utob,encode:encode,encodeURI:encodeURI,btou:btou,decode:decode,noConflict:noConflict,__buffer__:buffer};if(typeof Object.defineProperty==="function"){var noEnum=function(v){return{value:v,enumerable:false,writable:true,configurable:true}};global.Base64.extendString=function(){Object.defineProperty(String.prototype,"fromBase64",noEnum(function(){return decode(this)}));Object.defineProperty(String.prototype,"toBase64",noEnum(function(urisafe){return encode(this,urisafe)}));Object.defineProperty(String.prototype,"toBase64URI",noEnum(function(){return encode(this,true)}))}}if(global["Meteor"]){Base64=global.Base64}if(typeof module!=="undefined"&&module.exports){module.exports.Base64=global.Base64}else if(typeof define==="function"&&define.amd){define([],function(){return global.Base64})}return{Base64:global.Base64}});
|
||||
7
web/assets/clipboard/clipboard.min.js
vendored
7
web/assets/clipboard/clipboard.min.js
vendored
File diff suppressed because one or more lines are too long
@@ -1,103 +0,0 @@
|
||||
const supportLangs = [
|
||||
{
|
||||
name: "English",
|
||||
value: "en-US",
|
||||
icon: "🇺🇸",
|
||||
},
|
||||
{
|
||||
name: "فارسی",
|
||||
value: "fa-IR",
|
||||
icon: "🇮🇷",
|
||||
},
|
||||
{
|
||||
name: "简体中文",
|
||||
value: "zh-CN",
|
||||
icon: "🇨🇳",
|
||||
},
|
||||
{
|
||||
name: "繁體中文",
|
||||
value: "zh-TW",
|
||||
icon: "🇹🇼",
|
||||
},
|
||||
{
|
||||
name: "日本語",
|
||||
value: "ja-JP",
|
||||
icon: "🇯🇵",
|
||||
},
|
||||
{
|
||||
name: "Русский",
|
||||
value: "ru-RU",
|
||||
icon: "🇷🇺",
|
||||
},
|
||||
{
|
||||
name: "Tiếng Việt",
|
||||
value: "vi-VN",
|
||||
icon: "🇻🇳",
|
||||
},
|
||||
{
|
||||
name: "Español",
|
||||
value: "es-ES",
|
||||
icon: "🇪🇸",
|
||||
},
|
||||
{
|
||||
name: "Indonesian",
|
||||
value: "id-ID",
|
||||
icon: "🇮🇩",
|
||||
},
|
||||
{
|
||||
name: "Український",
|
||||
value: "uk-UA",
|
||||
icon: "🇺🇦",
|
||||
},
|
||||
{
|
||||
name: "Türkçe",
|
||||
value: "tr-TR",
|
||||
icon: "🇹🇷",
|
||||
},
|
||||
{
|
||||
name: "Português",
|
||||
value: "pt-BR",
|
||||
icon: "🇧🇷",
|
||||
},
|
||||
];
|
||||
|
||||
function getLang() {
|
||||
let lang = getCookie("lang");
|
||||
|
||||
if (!lang) {
|
||||
if (window.navigator) {
|
||||
lang = window.navigator.language || window.navigator.userLanguage;
|
||||
|
||||
if (isSupportLang(lang)) {
|
||||
setCookie("lang", lang, 150);
|
||||
} else {
|
||||
setCookie("lang", "en-US", 150);
|
||||
window.location.reload();
|
||||
}
|
||||
} else {
|
||||
setCookie("lang", "en-US", 150);
|
||||
window.location.reload();
|
||||
}
|
||||
}
|
||||
|
||||
return lang;
|
||||
}
|
||||
|
||||
function setLang(lang) {
|
||||
if (!isSupportLang(lang)) {
|
||||
lang = "en-US";
|
||||
}
|
||||
|
||||
setCookie("lang", lang, 150);
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
function isSupportLang(lang) {
|
||||
for (l of supportLangs) {
|
||||
if (l.value === lang) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -25,11 +25,11 @@ class DBInbound {
|
||||
}
|
||||
|
||||
get totalGB() {
|
||||
return toFixed(this.total / ONE_GB, 2);
|
||||
return NumberFormatter.toFixed(this.total / SizeFormatter.ONE_GB, 2);
|
||||
}
|
||||
|
||||
set totalGB(gb) {
|
||||
this.total = toFixed(gb * ONE_GB, 0);
|
||||
this.total = NumberFormatter.toFixed(gb * SizeFormatter.ONE_GB, 0);
|
||||
}
|
||||
|
||||
get isVMess() {
|
||||
|
||||
@@ -60,6 +60,7 @@ const UTLS_FINGERPRINT = {
|
||||
UTLS_QQ: "qq",
|
||||
UTLS_RANDOM: "random",
|
||||
UTLS_RANDOMIZED: "randomized",
|
||||
UTLS_RONDOMIZEDNOALPN: "randomizednoalpn",
|
||||
UTLS_UNSAFE: "unsafe",
|
||||
};
|
||||
|
||||
@@ -554,7 +555,7 @@ class TlsStreamSettings extends XrayCommonClass {
|
||||
maxVersion = TLS_VERSION_OPTION.TLS13,
|
||||
cipherSuites = '',
|
||||
rejectUnknownSni = false,
|
||||
serverNameToVerify = 'dns.google',
|
||||
verifyPeerCertInNames = ['dns.google', 'cloudflare-dns.com'],
|
||||
disableSystemRoot = false,
|
||||
enableSessionResumption = false,
|
||||
certificates = [new TlsStreamSettings.Cert()],
|
||||
@@ -567,7 +568,7 @@ class TlsStreamSettings extends XrayCommonClass {
|
||||
this.maxVersion = maxVersion;
|
||||
this.cipherSuites = cipherSuites;
|
||||
this.rejectUnknownSni = rejectUnknownSni;
|
||||
this.serverNameToVerify = serverNameToVerify;
|
||||
this.verifyPeerCertInNames = Array.isArray(verifyPeerCertInNames) ? verifyPeerCertInNames.join(",") : verifyPeerCertInNames;
|
||||
this.disableSystemRoot = disableSystemRoot;
|
||||
this.enableSessionResumption = enableSessionResumption;
|
||||
this.certs = certificates;
|
||||
@@ -599,7 +600,7 @@ class TlsStreamSettings extends XrayCommonClass {
|
||||
json.maxVersion,
|
||||
json.cipherSuites,
|
||||
json.rejectUnknownSni,
|
||||
json.serverNameToVerify,
|
||||
json.verifyPeerCertInNames,
|
||||
json.disableSystemRoot,
|
||||
json.enableSessionResumption,
|
||||
certs,
|
||||
@@ -615,7 +616,7 @@ class TlsStreamSettings extends XrayCommonClass {
|
||||
maxVersion: this.maxVersion,
|
||||
cipherSuites: this.cipherSuites,
|
||||
rejectUnknownSni: this.rejectUnknownSni,
|
||||
serverNameToVerify: this.serverNameToVerify,
|
||||
verifyPeerCertInNames: this.verifyPeerCertInNames.split(","),
|
||||
disableSystemRoot: this.disableSystemRoot,
|
||||
enableSessionResumption: this.enableSessionResumption,
|
||||
certificates: TlsStreamSettings.toJsonArray(this.certs),
|
||||
@@ -1049,7 +1050,7 @@ class Allocate extends XrayCommonClass {
|
||||
|
||||
class Inbound extends XrayCommonClass {
|
||||
constructor(
|
||||
port = RandomUtil.randomIntRange(10000, 60000),
|
||||
port = RandomUtil.randomInteger(10000, 60000),
|
||||
listen = '',
|
||||
protocol = Protocols.VLESS,
|
||||
settings = null,
|
||||
@@ -1225,7 +1226,7 @@ class Inbound extends XrayCommonClass {
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.port = RandomUtil.randomIntRange(10000, 60000);
|
||||
this.port = RandomUtil.randomInteger(10000, 60000);
|
||||
this.listen = '';
|
||||
this.protocol = Protocols.VMESS;
|
||||
this.settings = Inbound.Settings.getSettings(Protocols.VMESS);
|
||||
@@ -1301,7 +1302,7 @@ class Inbound extends XrayCommonClass {
|
||||
}
|
||||
}
|
||||
|
||||
return 'vmess://' + base64(JSON.stringify(obj, null, 2));
|
||||
return 'vmess://' + Base64.encode(JSON.stringify(obj, null, 2));
|
||||
}
|
||||
|
||||
genVLESSLink(address = '', port = this.port, forceTls, remark = '', clientId, flow) {
|
||||
@@ -1473,7 +1474,7 @@ class Inbound extends XrayCommonClass {
|
||||
if (this.isSS2022) password.push(settings.password);
|
||||
if (this.isSSMultiUser) password.push(clientPassword);
|
||||
|
||||
let link = `ss://${safeBase64(settings.method + ':' + password.join(':'))}@${address}:${port}`;
|
||||
let link = `ss://${Base64.encode(`${settings.method}:${password.join(':')}`, true)}@${address}:${port}`;
|
||||
const url = new URL(link);
|
||||
for (const [key, value] of params) {
|
||||
url.searchParams.set(key, value)
|
||||
@@ -1836,11 +1837,11 @@ Inbound.VmessSettings.VMESS = class extends XrayCommonClass {
|
||||
}
|
||||
}
|
||||
get _totalGB() {
|
||||
return toFixed(this.totalGB / ONE_GB, 2);
|
||||
return NumberFormatter.toFixed(this.totalGB / SizeFormatter.ONE_GB, 2);
|
||||
}
|
||||
|
||||
set _totalGB(gb) {
|
||||
this.totalGB = toFixed(gb * ONE_GB, 0);
|
||||
this.totalGB = NumberFormatter.toFixed(gb * SizeFormatter.ONE_GB, 0);
|
||||
}
|
||||
|
||||
};
|
||||
@@ -1946,11 +1947,11 @@ Inbound.VLESSSettings.VLESS = class extends XrayCommonClass {
|
||||
}
|
||||
}
|
||||
get _totalGB() {
|
||||
return toFixed(this.totalGB / ONE_GB, 2);
|
||||
return NumberFormatter.toFixed(this.totalGB / SizeFormatter.ONE_GB, 2);
|
||||
}
|
||||
|
||||
set _totalGB(gb) {
|
||||
this.totalGB = toFixed(gb * ONE_GB, 0);
|
||||
this.totalGB = NumberFormatter.toFixed(gb * SizeFormatter.ONE_GB, 0);
|
||||
}
|
||||
};
|
||||
Inbound.VLESSSettings.Fallback = class extends XrayCommonClass {
|
||||
@@ -2098,11 +2099,11 @@ Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
|
||||
}
|
||||
}
|
||||
get _totalGB() {
|
||||
return toFixed(this.totalGB / ONE_GB, 2);
|
||||
return NumberFormatter.toFixed(this.totalGB / SizeFormatter.ONE_GB, 2);
|
||||
}
|
||||
|
||||
set _totalGB(gb) {
|
||||
this.totalGB = toFixed(gb * ONE_GB, 0);
|
||||
this.totalGB = NumberFormatter.toFixed(gb * SizeFormatter.ONE_GB, 0);
|
||||
}
|
||||
|
||||
};
|
||||
@@ -2262,11 +2263,11 @@ Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass {
|
||||
}
|
||||
}
|
||||
get _totalGB() {
|
||||
return toFixed(this.totalGB / ONE_GB, 2);
|
||||
return NumberFormatter.toFixed(this.totalGB / SizeFormatter.ONE_GB, 2);
|
||||
}
|
||||
|
||||
set _totalGB(gb) {
|
||||
this.totalGB = toFixed(gb * ONE_GB, 0);
|
||||
this.totalGB = NumberFormatter.toFixed(gb * SizeFormatter.ONE_GB, 0);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
@@ -39,6 +39,7 @@ const UTLS_FINGERPRINT = {
|
||||
UTLS_QQ: "qq",
|
||||
UTLS_RANDOM: "random",
|
||||
UTLS_RANDOMIZED: "randomized",
|
||||
UTLS_RONDOMIZEDNOALPN: "randomizednoalpn",
|
||||
UTLS_UNSAFE: "unsafe",
|
||||
};
|
||||
|
||||
@@ -85,6 +86,16 @@ const MODE_OPTION = {
|
||||
STREAM_ONE: "stream-one",
|
||||
};
|
||||
|
||||
const Address_Port_Strategy = {
|
||||
NONE: "none",
|
||||
SrvPortOnly: "srvportonly",
|
||||
SrvAddressOnly: "srvaddressonly",
|
||||
SrvPortAndAddress: "srvportandaddress",
|
||||
TxtPortOnly: "txtportonly",
|
||||
TxtAddressOnly: "txtaddressonly",
|
||||
TxtPortAndAddress: "txtportandaddress"
|
||||
};
|
||||
|
||||
Object.freeze(Protocols);
|
||||
Object.freeze(SSMethods);
|
||||
Object.freeze(TLS_FLOW_CONTROL);
|
||||
@@ -94,7 +105,7 @@ Object.freeze(OutboundDomainStrategies);
|
||||
Object.freeze(WireguardDomainStrategy);
|
||||
Object.freeze(USERS_SECURITY);
|
||||
Object.freeze(MODE_OPTION);
|
||||
|
||||
Object.freeze(Address_Port_Strategy);
|
||||
|
||||
class CommonClass {
|
||||
|
||||
@@ -411,7 +422,8 @@ class SockoptStreamSettings extends CommonClass {
|
||||
tcpFastOpen = false,
|
||||
tcpKeepAliveInterval = 0,
|
||||
tcpMptcp = false,
|
||||
penetrate = false
|
||||
penetrate = false,
|
||||
addressPortStrategy = Address_Port_Strategy.NONE,
|
||||
) {
|
||||
super();
|
||||
this.dialerProxy = dialerProxy;
|
||||
@@ -419,6 +431,7 @@ class SockoptStreamSettings extends CommonClass {
|
||||
this.tcpKeepAliveInterval = tcpKeepAliveInterval;
|
||||
this.tcpMptcp = tcpMptcp;
|
||||
this.penetrate = penetrate;
|
||||
this.addressPortStrategy = addressPortStrategy;
|
||||
}
|
||||
|
||||
static fromJson(json = {}) {
|
||||
@@ -429,6 +442,7 @@ class SockoptStreamSettings extends CommonClass {
|
||||
json.tcpKeepAliveInterval,
|
||||
json.tcpMptcp,
|
||||
json.penetrate,
|
||||
json.addressPortStrategy
|
||||
);
|
||||
}
|
||||
|
||||
@@ -439,6 +453,7 @@ class SockoptStreamSettings extends CommonClass {
|
||||
tcpKeepAliveInterval: this.tcpKeepAliveInterval,
|
||||
tcpMptcp: this.tcpMptcp,
|
||||
penetrate: this.penetrate,
|
||||
addressPortStrategy: this.addressPortStrategy
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,12 +26,13 @@ class AllSetting {
|
||||
this.xrayTemplateConfig = "";
|
||||
this.secretEnable = false;
|
||||
this.subEnable = false;
|
||||
this.subSyncEnable = true;
|
||||
this.subListen = "";
|
||||
this.subPort = 2096;
|
||||
this.subPath = "/sub/";
|
||||
this.subJsonPath = "/json/";
|
||||
this.subDomain = "";
|
||||
this.externalTrafficInformEnable = false;
|
||||
this.externalTrafficInformURI = "";
|
||||
this.subCertFile = "";
|
||||
this.subKeyFile = "";
|
||||
this.subUpdates = 12;
|
||||
|
||||
@@ -1,194 +0,0 @@
|
||||
const ONE_KB = 1024;
|
||||
const ONE_MB = ONE_KB * 1024;
|
||||
const ONE_GB = ONE_MB * 1024;
|
||||
const ONE_TB = ONE_GB * 1024;
|
||||
const ONE_PB = ONE_TB * 1024;
|
||||
|
||||
function sizeFormat(size) {
|
||||
if (size <= 0) return "0 B";
|
||||
|
||||
if (size < ONE_KB) {
|
||||
return size.toFixed(0) + " B";
|
||||
} else if (size < ONE_MB) {
|
||||
return (size / ONE_KB).toFixed(2) + " KB";
|
||||
} else if (size < ONE_GB) {
|
||||
return (size / ONE_MB).toFixed(2) + " MB";
|
||||
} else if (size < ONE_TB) {
|
||||
return (size / ONE_GB).toFixed(2) + " GB";
|
||||
} else if (size < ONE_PB) {
|
||||
return (size / ONE_TB).toFixed(2) + " TB";
|
||||
} else {
|
||||
return (size / ONE_PB).toFixed(2) + " PB";
|
||||
}
|
||||
}
|
||||
|
||||
function cpuSpeedFormat(speed) {
|
||||
if (speed > 1000) {
|
||||
const GHz = speed / 1000;
|
||||
return GHz.toFixed(2) + " GHz";
|
||||
} else {
|
||||
return speed.toFixed(2) + " MHz";
|
||||
}
|
||||
}
|
||||
|
||||
function cpuCoreFormat(cores) {
|
||||
if (cores === 1) {
|
||||
return "1 Core";
|
||||
} else {
|
||||
return cores + " Cores";
|
||||
}
|
||||
}
|
||||
|
||||
function base64(str) {
|
||||
return Base64.encode(str);
|
||||
}
|
||||
|
||||
function safeBase64(str) {
|
||||
return base64(str)
|
||||
.replace(/\+/g, '-')
|
||||
.replace(/=/g, '')
|
||||
.replace(/\//g, '_');
|
||||
}
|
||||
|
||||
function formatSecond(second) {
|
||||
if (second < 60) {
|
||||
return second.toFixed(0) + 's';
|
||||
} else if (second < 3600) {
|
||||
return (second / 60).toFixed(0) + 'm';
|
||||
} else if (second < 3600 * 24) {
|
||||
return (second / 3600).toFixed(0) + 'h';
|
||||
} else {
|
||||
day = Math.floor(second / 3600 / 24);
|
||||
remain = ((second / 3600) - (day * 24)).toFixed(0);
|
||||
return day + 'd' + (remain > 0 ? ' ' + remain + 'h' : '');
|
||||
}
|
||||
}
|
||||
|
||||
function addZero(num) {
|
||||
if (num < 10) {
|
||||
return "0" + num;
|
||||
} else {
|
||||
return num;
|
||||
}
|
||||
}
|
||||
|
||||
function toFixed(num, n) {
|
||||
n = Math.pow(10, n);
|
||||
return Math.floor(num * n) / n;
|
||||
}
|
||||
|
||||
function debounce(fn, delay) {
|
||||
var timeoutID = null;
|
||||
return function () {
|
||||
clearTimeout(timeoutID);
|
||||
var args = arguments;
|
||||
var that = this;
|
||||
timeoutID = setTimeout(function () {
|
||||
fn.apply(that, args);
|
||||
}, delay);
|
||||
};
|
||||
}
|
||||
|
||||
function getCookie(cname) {
|
||||
let name = cname + '=';
|
||||
let ca = document.cookie.split(';');
|
||||
for (let i = 0; i < ca.length; i++) {
|
||||
let c = ca[i];
|
||||
while (c.charAt(0) == ' ') {
|
||||
c = c.substring(1);
|
||||
}
|
||||
if (c.indexOf(name) == 0) {
|
||||
// decode cookie value only
|
||||
return decodeURIComponent(c.substring(name.length, c.length));
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
|
||||
function setCookie(cname, cvalue, exdays) {
|
||||
const d = new Date();
|
||||
d.setTime(d.getTime() + exdays * 24 * 60 * 60 * 1000);
|
||||
let expires = 'expires=' + d.toUTCString();
|
||||
// encode cookie value
|
||||
document.cookie = cname + '=' + encodeURIComponent(cvalue) + ';' + expires + ';path=/';
|
||||
}
|
||||
|
||||
function usageColor(data, threshold, total) {
|
||||
switch (true) {
|
||||
case data === null:
|
||||
return "purple";
|
||||
case total < 0:
|
||||
return "green";
|
||||
case total == 0:
|
||||
return "purple";
|
||||
case data < total - threshold:
|
||||
return "green";
|
||||
case data < total:
|
||||
return "orange";
|
||||
default:
|
||||
return "red";
|
||||
}
|
||||
}
|
||||
|
||||
function clientUsageColor(clientStats, trafficDiff) {
|
||||
switch (true) {
|
||||
case !clientStats || clientStats.total == 0:
|
||||
return "#7a316f"; // purple
|
||||
case clientStats.up + clientStats.down < clientStats.total - trafficDiff:
|
||||
return "#008771"; // Green
|
||||
case clientStats.up + clientStats.down < clientStats.total:
|
||||
return "#f37b24"; // Orange
|
||||
default:
|
||||
return "#cf3c3c"; // Red
|
||||
}
|
||||
}
|
||||
|
||||
function userExpiryColor(threshold, client, isDark = false) {
|
||||
if (!client.enable) {
|
||||
return isDark ? '#2c3950' : '#bcbcbc';
|
||||
}
|
||||
now = new Date().getTime(),
|
||||
expiry = client.expiryTime;
|
||||
switch (true) {
|
||||
case expiry === null:
|
||||
return "#7a316f"; // purple
|
||||
case expiry < 0:
|
||||
return "#008771"; // Green
|
||||
case expiry == 0:
|
||||
return "#7a316f"; // purple
|
||||
case now < expiry - threshold:
|
||||
return "#008771"; // Green
|
||||
case now < expiry:
|
||||
return "#f37b24"; // Orange
|
||||
default:
|
||||
return "#cf3c3c"; // Red
|
||||
}
|
||||
}
|
||||
|
||||
function doAllItemsExist(array1, array2) {
|
||||
for (let i = 0; i < array1.length; i++) {
|
||||
if (!array2.includes(array1[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function buildURL({ host, port, isTLS, base, path }) {
|
||||
if (!host || host.length === 0) host = window.location.hostname;
|
||||
if (!port || port.length === 0) port = window.location.port;
|
||||
|
||||
if (isTLS === undefined) isTLS = window.location.protocol === "https:";
|
||||
|
||||
const protocol = isTLS ? "https:" : "http:";
|
||||
|
||||
port = String(port);
|
||||
if (port === "" || (isTLS && port === "443") || (!isTLS && port === "80")) {
|
||||
port = "";
|
||||
} else {
|
||||
port = `:${port}`;
|
||||
}
|
||||
|
||||
return `${protocol}//${host}${port}${base}${path}`;
|
||||
}
|
||||
@@ -108,14 +108,14 @@ Date.prototype.setMaxTime = function () {
|
||||
* Formatting date
|
||||
*/
|
||||
Date.prototype.formatDate = function () {
|
||||
return this.getFullYear() + "-" + addZero(this.getMonth() + 1) + "-" + addZero(this.getDate());
|
||||
return this.getFullYear() + "-" + NumberFormatter.addZero(this.getMonth() + 1) + "-" + NumberFormatter.addZero(this.getDate());
|
||||
};
|
||||
|
||||
/**
|
||||
* Format time
|
||||
*/
|
||||
Date.prototype.formatTime = function () {
|
||||
return addZero(this.getHours()) + ":" + addZero(this.getMinutes()) + ":" + addZero(this.getSeconds());
|
||||
return NumberFormatter.addZero(this.getHours()) + ":" + NumberFormatter.addZero(this.getMinutes()) + ":" + NumberFormatter.addZero(this.getSeconds());
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -70,41 +70,6 @@ class HttpUtil {
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
|
||||
static async jsonPost(url, data) {
|
||||
let msg;
|
||||
try {
|
||||
const requestOptions = {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(data),
|
||||
};
|
||||
const resp = await fetch(basePath + url.replace(/^\/+|\/+$/g, ''), requestOptions);
|
||||
const response = await resp.json();
|
||||
|
||||
msg = this._respToMsg({data : response});
|
||||
} catch (e) {
|
||||
msg = new Msg(false, e.toString());
|
||||
}
|
||||
this._handleMsg(msg);
|
||||
return msg;
|
||||
}
|
||||
|
||||
static async postWithModalJson(url, data, modal) {
|
||||
if (modal) {
|
||||
modal.loading(true);
|
||||
}
|
||||
const msg = await this.jsonPost(url, data);
|
||||
if (modal) {
|
||||
modal.loading(false);
|
||||
if (msg instanceof Msg && msg.success) {
|
||||
modal.close();
|
||||
}
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
}
|
||||
|
||||
class PromiseUtil {
|
||||
@@ -115,66 +80,68 @@ class PromiseUtil {
|
||||
}
|
||||
}
|
||||
|
||||
const seq = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('');
|
||||
|
||||
class RandomUtil {
|
||||
static randomIntRange(min, max) {
|
||||
return Math.floor(Math.random() * (max - min) + min);
|
||||
}
|
||||
|
||||
static randomInt(n) {
|
||||
return this.randomIntRange(0, n);
|
||||
}
|
||||
|
||||
static randomSeq(count) {
|
||||
let str = '';
|
||||
for (let i = 0; i < count; ++i) {
|
||||
str += seq[this.randomInt(62)];
|
||||
static getSeq({ type = "default", hasNumbers = true, hasLowercase = true, hasUppercase = true } = {}) {
|
||||
let seq = '';
|
||||
|
||||
switch (type) {
|
||||
case "hex":
|
||||
seq += "0123456789abcdef";
|
||||
break;
|
||||
default:
|
||||
if (hasNumbers) seq += "0123456789";
|
||||
if (hasLowercase) seq += "abcdefghijklmnopqrstuvwxyz";
|
||||
if (hasUppercase) seq += "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
break;
|
||||
}
|
||||
return str;
|
||||
|
||||
return seq;
|
||||
}
|
||||
|
||||
static randomInteger(min, max) {
|
||||
const range = max - min + 1;
|
||||
const randomBuffer = new Uint32Array(1);
|
||||
window.crypto.getRandomValues(randomBuffer);
|
||||
return Math.floor((randomBuffer[0] / (0xFFFFFFFF + 1)) * range) + min;
|
||||
}
|
||||
|
||||
static randomSeq(count, options = {}) {
|
||||
const seq = this.getSeq(options);
|
||||
const seqLength = seq.length;
|
||||
const randomValues = new Uint32Array(count);
|
||||
window.crypto.getRandomValues(randomValues);
|
||||
return Array.from(randomValues, v => seq[v % seqLength]).join('');
|
||||
}
|
||||
|
||||
static randomShortIds() {
|
||||
const lengths = [2, 4, 6, 8, 10, 12, 14, 16];
|
||||
for (let i = lengths.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(Math.random() * (i + 1));
|
||||
[lengths[i], lengths[j]] = [lengths[j], lengths[i]];
|
||||
}
|
||||
const lengths = [2, 4, 6, 8, 10, 12, 14, 16].sort(() => Math.random() - 0.5);
|
||||
|
||||
let shortIds = [];
|
||||
for (let length of lengths) {
|
||||
let shortId = '';
|
||||
for (let i = 0; i < length; i++) {
|
||||
shortId += seq[this.randomInt(16)];
|
||||
}
|
||||
shortIds.push(shortId);
|
||||
}
|
||||
return shortIds.join(',');
|
||||
return lengths.map(len => this.randomSeq(len, { type: "hex" })).join(',');
|
||||
}
|
||||
|
||||
static randomLowerAndNum(len) {
|
||||
let str = '';
|
||||
for (let i = 0; i < len; ++i) {
|
||||
str += seq[this.randomInt(36)];
|
||||
}
|
||||
return str;
|
||||
return this.randomSeq(len, { hasUppercase: false });
|
||||
}
|
||||
|
||||
static randomUUID() {
|
||||
const template = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx';
|
||||
return template.replace(/[xy]/g, function (c) {
|
||||
const randomValues = new Uint8Array(1);
|
||||
crypto.getRandomValues(randomValues);
|
||||
let randomValue = randomValues[0] % 16;
|
||||
let calculatedValue = (c === 'x') ? randomValue : (randomValue & 0x3 | 0x8);
|
||||
return calculatedValue.toString(16);
|
||||
});
|
||||
if (window.location.protocol === "https:") {
|
||||
return window.crypto.randomUUID();
|
||||
} else {
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'
|
||||
.replace(/[xy]/g, function (c) {
|
||||
const randomValues = new Uint8Array(1);
|
||||
window.crypto.getRandomValues(randomValues);
|
||||
let randomValue = randomValues[0] % 16;
|
||||
let calculatedValue = (c === 'x') ? randomValue : (randomValue & 0x3 | 0x8);
|
||||
return calculatedValue.toString(16);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
static randomShadowsocksPassword() {
|
||||
let array = new Uint8Array(32);
|
||||
const array = new Uint8Array(32);
|
||||
window.crypto.getRandomValues(array);
|
||||
return btoa(String.fromCharCode.apply(null, array));
|
||||
return Base64.encode(String.fromCharCode(...array));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -513,4 +480,304 @@ class Wireguard {
|
||||
privateKey: secretKey.length > 0 ? secretKey : this.keyToBase64(privateKey)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class ClipboardManager {
|
||||
static copyText(content = "") {
|
||||
// !! here old way of copying is used because not everyone can afford https connection
|
||||
return new Promise((resolve) => {
|
||||
try {
|
||||
const textarea = window.document.createElement('textarea');
|
||||
|
||||
textarea.style.fontSize = '12pt';
|
||||
textarea.style.border = '0';
|
||||
textarea.style.padding = '0';
|
||||
textarea.style.margin = '0';
|
||||
textarea.style.position = 'absolute';
|
||||
textarea.style.left = '-9999px';
|
||||
textarea.style.top = `${window.pageYOffset || document.documentElement.scrollTop}px`;
|
||||
textarea.setAttribute('readonly', '');
|
||||
textarea.value = content;
|
||||
|
||||
window.document.body.appendChild(textarea);
|
||||
|
||||
textarea.select();
|
||||
window.document.execCommand("copy");
|
||||
|
||||
window.document.body.removeChild(textarea);
|
||||
|
||||
resolve(true)
|
||||
} catch {
|
||||
resolve(false)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
class Base64 {
|
||||
static encode(content = "", safe = false) {
|
||||
if (safe) {
|
||||
return Base64.encode(content)
|
||||
.replace(/\+/g, '-')
|
||||
.replace(/=/g, '')
|
||||
.replace(/\//g, '_')
|
||||
}
|
||||
|
||||
return window.btoa(
|
||||
String.fromCharCode(...new TextEncoder().encode(content))
|
||||
)
|
||||
}
|
||||
|
||||
static decode(content = "") {
|
||||
return new TextDecoder()
|
||||
.decode(
|
||||
Uint8Array.from(window.atob(content), c => c.charCodeAt(0))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class SizeFormatter {
|
||||
static ONE_KB = 1024;
|
||||
static ONE_MB = this.ONE_KB * 1024;
|
||||
static ONE_GB = this.ONE_MB * 1024;
|
||||
static ONE_TB = this.ONE_GB * 1024;
|
||||
static ONE_PB = this.ONE_TB * 1024;
|
||||
|
||||
static sizeFormat(size) {
|
||||
if (size <= 0) return "0 B";
|
||||
if (size < this.ONE_KB) return size.toFixed(0) + " B";
|
||||
if (size < this.ONE_MB) return (size / this.ONE_KB).toFixed(2) + " KB";
|
||||
if (size < this.ONE_GB) return (size / this.ONE_MB).toFixed(2) + " MB";
|
||||
if (size < this.ONE_TB) return (size / this.ONE_GB).toFixed(2) + " GB";
|
||||
if (size < this.ONE_PB) return (size / this.ONE_TB).toFixed(2) + " TB";
|
||||
return (size / this.ONE_PB).toFixed(2) + " PB";
|
||||
}
|
||||
}
|
||||
|
||||
class CPUFormatter {
|
||||
static cpuSpeedFormat(speed) {
|
||||
return speed > 1000 ? (speed / 1000).toFixed(2) + " GHz" : speed.toFixed(2) + " MHz";
|
||||
}
|
||||
|
||||
static cpuCoreFormat(cores) {
|
||||
return cores === 1 ? "1 Core" : cores + " Cores";
|
||||
}
|
||||
}
|
||||
|
||||
class TimeFormatter {
|
||||
static formatSecond(second) {
|
||||
if (second < 60) return second.toFixed(0) + 's';
|
||||
if (second < 3600) return (second / 60).toFixed(0) + 'm';
|
||||
if (second < 3600 * 24) return (second / 3600).toFixed(0) + 'h';
|
||||
let day = Math.floor(second / 3600 / 24);
|
||||
let remain = ((second / 3600) - (day * 24)).toFixed(0);
|
||||
return day + 'd' + (remain > 0 ? ' ' + remain + 'h' : '');
|
||||
}
|
||||
}
|
||||
|
||||
class NumberFormatter {
|
||||
static addZero(num) {
|
||||
return num < 10 ? "0" + num : num;
|
||||
}
|
||||
|
||||
static toFixed(num, n) {
|
||||
n = Math.pow(10, n);
|
||||
return Math.floor(num * n) / n;
|
||||
}
|
||||
}
|
||||
|
||||
class Utils {
|
||||
static debounce(fn, delay) {
|
||||
let timeoutID = null;
|
||||
return function () {
|
||||
clearTimeout(timeoutID);
|
||||
let args = arguments;
|
||||
let that = this;
|
||||
timeoutID = setTimeout(() => fn.apply(that, args), delay);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class CookieManager {
|
||||
static getCookie(cname) {
|
||||
let name = cname + '=';
|
||||
let ca = document.cookie.split(';');
|
||||
for (let c of ca) {
|
||||
c = c.trim();
|
||||
if (c.indexOf(name) === 0) {
|
||||
return decodeURIComponent(c.substring(name.length, c.length));
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
static setCookie(cname, cvalue, exdays) {
|
||||
const d = new Date();
|
||||
d.setTime(d.getTime() + exdays * 24 * 60 * 60 * 1000);
|
||||
let expires = 'expires=' + d.toUTCString();
|
||||
document.cookie = cname + '=' + encodeURIComponent(cvalue) + ';' + expires + ';path=/';
|
||||
}
|
||||
}
|
||||
|
||||
class ColorUtils {
|
||||
static usageColor(data, threshold, total) {
|
||||
switch (true) {
|
||||
case data === null: return "purple";
|
||||
case total < 0: return "green";
|
||||
case total == 0: return "purple";
|
||||
case data < total - threshold: return "green";
|
||||
case data < total: return "orange";
|
||||
default: return "red";
|
||||
}
|
||||
}
|
||||
|
||||
static clientUsageColor(clientStats, trafficDiff) {
|
||||
switch (true) {
|
||||
case !clientStats || clientStats.total == 0: return "#7a316f";
|
||||
case clientStats.up + clientStats.down < clientStats.total - trafficDiff: return "#008771";
|
||||
case clientStats.up + clientStats.down < clientStats.total: return "#f37b24";
|
||||
default: return "#cf3c3c";
|
||||
}
|
||||
}
|
||||
|
||||
static userExpiryColor(threshold, client, isDark = false) {
|
||||
if (!client.enable) return isDark ? '#2c3950' : '#bcbcbc';
|
||||
let now = new Date().getTime(), expiry = client.expiryTime;
|
||||
switch (true) {
|
||||
case expiry === null: return "#7a316f";
|
||||
case expiry < 0: return "#008771";
|
||||
case expiry == 0: return "#7a316f";
|
||||
case now < expiry - threshold: return "#008771";
|
||||
case now < expiry: return "#f37b24";
|
||||
default: return "#cf3c3c";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ArrayUtils {
|
||||
static doAllItemsExist(array1, array2) {
|
||||
return array1.every(item => array2.includes(item));
|
||||
}
|
||||
}
|
||||
|
||||
class URLBuilder {
|
||||
static buildURL({ host, port, isTLS, base, path }) {
|
||||
if (!host || host.length === 0) host = window.location.hostname;
|
||||
if (!port || port.length === 0) port = window.location.port;
|
||||
if (isTLS === undefined) isTLS = window.location.protocol === "https:";
|
||||
|
||||
const protocol = isTLS ? "https:" : "http:";
|
||||
port = String(port);
|
||||
if (port === "" || (isTLS && port === "443") || (!isTLS && port === "80")) {
|
||||
port = "";
|
||||
} else {
|
||||
port = `:${port}`;
|
||||
}
|
||||
|
||||
return `${protocol}//${host}${port}${base}${path}`;
|
||||
}
|
||||
}
|
||||
|
||||
class LanguageManager {
|
||||
static supportedLanguages = [
|
||||
{
|
||||
name: "English",
|
||||
value: "en-US",
|
||||
icon: "🇺🇸",
|
||||
},
|
||||
{
|
||||
name: "فارسی",
|
||||
value: "fa-IR",
|
||||
icon: "🇮🇷",
|
||||
},
|
||||
{
|
||||
name: "简体中文",
|
||||
value: "zh-CN",
|
||||
icon: "🇨🇳",
|
||||
},
|
||||
{
|
||||
name: "繁體中文",
|
||||
value: "zh-TW",
|
||||
icon: "🇹🇼",
|
||||
},
|
||||
{
|
||||
name: "日本語",
|
||||
value: "ja-JP",
|
||||
icon: "🇯🇵",
|
||||
},
|
||||
{
|
||||
name: "Русский",
|
||||
value: "ru-RU",
|
||||
icon: "🇷🇺",
|
||||
},
|
||||
{
|
||||
name: "Tiếng Việt",
|
||||
value: "vi-VN",
|
||||
icon: "🇻🇳",
|
||||
},
|
||||
{
|
||||
name: "Español",
|
||||
value: "es-ES",
|
||||
icon: "🇪🇸",
|
||||
},
|
||||
{
|
||||
name: "Indonesian",
|
||||
value: "id-ID",
|
||||
icon: "🇮🇩",
|
||||
},
|
||||
{
|
||||
name: "Український",
|
||||
value: "uk-UA",
|
||||
icon: "🇺🇦",
|
||||
},
|
||||
{
|
||||
name: "Türkçe",
|
||||
value: "tr-TR",
|
||||
icon: "🇹🇷",
|
||||
},
|
||||
{
|
||||
name: "Português",
|
||||
value: "pt-BR",
|
||||
icon: "🇧🇷",
|
||||
}
|
||||
]
|
||||
|
||||
static getLanguage() {
|
||||
let lang = CookieManager.getCookie("lang");
|
||||
|
||||
if (!lang) {
|
||||
if (window.navigator) {
|
||||
lang = window.navigator.language || window.navigator.userLanguage;
|
||||
|
||||
if (LanguageManager.isSupportLanguage(lang)) {
|
||||
CookieManager.setCookie("lang", lang, 150);
|
||||
} else {
|
||||
CookieManager.setCookie("lang", "en-US", 150);
|
||||
window.location.reload();
|
||||
}
|
||||
} else {
|
||||
CookieManager.setCookie("lang", "en-US", 150);
|
||||
window.location.reload();
|
||||
}
|
||||
}
|
||||
|
||||
return lang;
|
||||
}
|
||||
|
||||
static setLanguage(language) {
|
||||
if (!LanguageManager.isSupportLanguage(language)) {
|
||||
language = "en-US";
|
||||
}
|
||||
|
||||
CookieManager.setCookie("lang", language, 150);
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
static isSupportLanguage(language) {
|
||||
const languageFilter = LanguageManager.supportedLanguages.filter((lang) => {
|
||||
return lang.value === language
|
||||
})
|
||||
|
||||
return languageFilter.length > 0;
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"x-ui/database/model"
|
||||
"x-ui/web/service"
|
||||
"x-ui/web/session"
|
||||
@@ -33,13 +33,9 @@ func (a *InboundController) initRouter(g *gin.RouterGroup) {
|
||||
g.POST("/clientIps/:email", a.getClientIps)
|
||||
g.POST("/clearClientIps/:email", a.clearClientIps)
|
||||
g.POST("/addClient", a.addInboundClient)
|
||||
g.POST("/addGroupClient", a.addGroupInboundClient)
|
||||
g.POST("/:id/delClient/:clientId", a.delInboundClient)
|
||||
g.POST("/delGroupClients", a.delGroupClients)
|
||||
g.POST("/updateClient/:clientId", a.updateInboundClient)
|
||||
g.POST("/updateClients", a.updateGroupInboundClient)
|
||||
g.POST("/:id/resetClientTraffic/:email", a.resetClientTraffic)
|
||||
g.POST("/resetGroupClientTraffic", a.resetGroupClientTraffic)
|
||||
g.POST("/resetAllTraffics", a.resetAllTraffics)
|
||||
g.POST("/resetAllClientTraffics/:id", a.resetAllClientTraffics)
|
||||
g.POST("/delDepletedClients/:id", a.delDepletedClients)
|
||||
@@ -194,34 +190,6 @@ func (a *InboundController) addInboundClient(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
func (a *InboundController) addGroupInboundClient(c *gin.Context) {
|
||||
var requestData []model.Inbound
|
||||
|
||||
err := c.ShouldBindJSON(&requestData)
|
||||
|
||||
if err != nil {
|
||||
jsonMsg(c, I18nWeb(c, "pages.inbounds.update"), err)
|
||||
return
|
||||
}
|
||||
|
||||
needRestart := true
|
||||
|
||||
for _, data := range requestData {
|
||||
|
||||
needRestart, err = a.inboundService.AddInboundClient(&data)
|
||||
if err != nil {
|
||||
jsonMsg(c, "Something went wrong!", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
jsonMsg(c, "Client(s) added", nil)
|
||||
if err == nil && needRestart {
|
||||
a.xrayService.SetToNeedRestart()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (a *InboundController) delInboundClient(c *gin.Context) {
|
||||
id, err := strconv.Atoi(c.Param("id"))
|
||||
if err != nil {
|
||||
@@ -243,38 +211,6 @@ func (a *InboundController) delInboundClient(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
func (a *InboundController) delGroupClients(c *gin.Context) {
|
||||
var requestData []struct {
|
||||
InboundID int `json:"inboundId"`
|
||||
ClientID string `json:"clientId"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&requestData); err != nil {
|
||||
jsonMsg(c, "Invalid request data", err)
|
||||
return
|
||||
}
|
||||
|
||||
needRestart := false
|
||||
|
||||
for _, req := range requestData {
|
||||
needRestartTmp, err := a.inboundService.DelInboundClient(req.InboundID, req.ClientID)
|
||||
if err != nil {
|
||||
jsonMsg(c, "Failed to delete client", err)
|
||||
return
|
||||
}
|
||||
|
||||
if needRestartTmp {
|
||||
needRestart = true
|
||||
}
|
||||
}
|
||||
|
||||
jsonMsg(c, "Clients deleted successfully", nil)
|
||||
|
||||
if needRestart {
|
||||
a.xrayService.SetToNeedRestart()
|
||||
}
|
||||
}
|
||||
|
||||
func (a *InboundController) updateInboundClient(c *gin.Context) {
|
||||
clientId := c.Param("clientId")
|
||||
|
||||
@@ -298,56 +234,6 @@ func (a *InboundController) updateInboundClient(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
func (a *InboundController) updateGroupInboundClient(c *gin.Context) {
|
||||
var requestData []map[string]interface{}
|
||||
|
||||
if err := c.ShouldBindJSON(&requestData); err != nil {
|
||||
jsonMsg(c, I18nWeb(c, "pages.inbounds.update"), err)
|
||||
return
|
||||
}
|
||||
|
||||
needRestart := false
|
||||
|
||||
for _, item := range requestData {
|
||||
|
||||
inboundMap, ok := item["inbound"].(map[string]interface{})
|
||||
if !ok {
|
||||
jsonMsg(c, "Something went wrong!", errors.New("Failed to convert 'inbound' to map"))
|
||||
return
|
||||
}
|
||||
|
||||
clientId, ok := item["clientId"].(string)
|
||||
if !ok {
|
||||
jsonMsg(c, "Something went wrong!", errors.New("Failed to convert 'clientId' to string"))
|
||||
return
|
||||
}
|
||||
|
||||
inboundJSON, err := json.Marshal(inboundMap)
|
||||
if err != nil {
|
||||
jsonMsg(c, "Something went wrong!", err)
|
||||
return
|
||||
}
|
||||
|
||||
var inboundModel model.Inbound
|
||||
if err := json.Unmarshal(inboundJSON, &inboundModel); err != nil {
|
||||
jsonMsg(c, "Something went wrong!", err)
|
||||
return
|
||||
}
|
||||
|
||||
if restart, err := a.inboundService.UpdateInboundClient(&inboundModel, clientId); err != nil {
|
||||
jsonMsg(c, "Something went wrong!", err)
|
||||
return
|
||||
} else {
|
||||
needRestart = needRestart || restart
|
||||
}
|
||||
}
|
||||
|
||||
jsonMsg(c, "Client updated", nil)
|
||||
if needRestart {
|
||||
a.xrayService.SetToNeedRestart()
|
||||
}
|
||||
}
|
||||
|
||||
func (a *InboundController) resetClientTraffic(c *gin.Context) {
|
||||
id, err := strconv.Atoi(c.Param("id"))
|
||||
if err != nil {
|
||||
@@ -367,44 +253,6 @@ func (a *InboundController) resetClientTraffic(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
func (a *InboundController) resetGroupClientTraffic(c *gin.Context) {
|
||||
var requestData []struct {
|
||||
InboundID int `json:"inboundId"` // Map JSON "inboundId" to struct field "InboundID"
|
||||
Email string `json:"email"` // Map JSON "email" to struct field "Email"
|
||||
}
|
||||
|
||||
// Parse JSON body directly using ShouldBindJSON
|
||||
if err := c.ShouldBindJSON(&requestData); err != nil {
|
||||
jsonMsg(c, "Invalid request data", err)
|
||||
return
|
||||
}
|
||||
|
||||
needRestart := false
|
||||
|
||||
// Process each request data
|
||||
for _, req := range requestData {
|
||||
needRestartTmp, err := a.inboundService.ResetClientTraffic(req.InboundID, req.Email)
|
||||
if err != nil {
|
||||
jsonMsg(c, "Failed to reset client traffic", err)
|
||||
return
|
||||
}
|
||||
|
||||
// If any request requires a restart, set needRestart to true
|
||||
if needRestartTmp {
|
||||
needRestart = true
|
||||
}
|
||||
}
|
||||
|
||||
// Send response back to the client
|
||||
jsonMsg(c, "Traffic reset for all clients", nil)
|
||||
|
||||
// Restart the service if required
|
||||
if needRestart {
|
||||
a.xrayService.SetToNeedRestart()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func (a *InboundController) resetAllTraffics(c *gin.Context) {
|
||||
err := a.inboundService.ResetAllTraffics()
|
||||
if err != nil {
|
||||
|
||||
@@ -16,48 +16,49 @@ type Msg struct {
|
||||
}
|
||||
|
||||
type AllSetting struct {
|
||||
WebListen string `json:"webListen" form:"webListen"`
|
||||
WebDomain string `json:"webDomain" form:"webDomain"`
|
||||
WebPort int `json:"webPort" form:"webPort"`
|
||||
WebCertFile string `json:"webCertFile" form:"webCertFile"`
|
||||
WebKeyFile string `json:"webKeyFile" form:"webKeyFile"`
|
||||
WebBasePath string `json:"webBasePath" form:"webBasePath"`
|
||||
SessionMaxAge int `json:"sessionMaxAge" form:"sessionMaxAge"`
|
||||
PageSize int `json:"pageSize" form:"pageSize"`
|
||||
ExpireDiff int `json:"expireDiff" form:"expireDiff"`
|
||||
TrafficDiff int `json:"trafficDiff" form:"trafficDiff"`
|
||||
RemarkModel string `json:"remarkModel" form:"remarkModel"`
|
||||
TgBotEnable bool `json:"tgBotEnable" form:"tgBotEnable"`
|
||||
TgBotToken string `json:"tgBotToken" form:"tgBotToken"`
|
||||
TgBotProxy string `json:"tgBotProxy" form:"tgBotProxy"`
|
||||
TgBotAPIServer string `json:"tgBotAPIServer" form:"tgBotAPIServer"`
|
||||
TgBotChatId string `json:"tgBotChatId" form:"tgBotChatId"`
|
||||
TgRunTime string `json:"tgRunTime" form:"tgRunTime"`
|
||||
TgBotBackup bool `json:"tgBotBackup" form:"tgBotBackup"`
|
||||
TgBotLoginNotify bool `json:"tgBotLoginNotify" form:"tgBotLoginNotify"`
|
||||
TgCpu int `json:"tgCpu" form:"tgCpu"`
|
||||
TgLang string `json:"tgLang" form:"tgLang"`
|
||||
TimeLocation string `json:"timeLocation" form:"timeLocation"`
|
||||
SecretEnable bool `json:"secretEnable" form:"secretEnable"`
|
||||
SubEnable bool `json:"subEnable" form:"subEnable"`
|
||||
SubSyncEnable bool `json:"subSyncEnable" form:"subSyncEnable"`
|
||||
SubListen string `json:"subListen" form:"subListen"`
|
||||
SubPort int `json:"subPort" form:"subPort"`
|
||||
SubPath string `json:"subPath" form:"subPath"`
|
||||
SubDomain string `json:"subDomain" form:"subDomain"`
|
||||
SubCertFile string `json:"subCertFile" form:"subCertFile"`
|
||||
SubKeyFile string `json:"subKeyFile" form:"subKeyFile"`
|
||||
SubUpdates int `json:"subUpdates" form:"subUpdates"`
|
||||
SubEncrypt bool `json:"subEncrypt" form:"subEncrypt"`
|
||||
SubShowInfo bool `json:"subShowInfo" form:"subShowInfo"`
|
||||
SubURI string `json:"subURI" form:"subURI"`
|
||||
SubJsonPath string `json:"subJsonPath" form:"subJsonPath"`
|
||||
SubJsonURI string `json:"subJsonURI" form:"subJsonURI"`
|
||||
SubJsonFragment string `json:"subJsonFragment" form:"subJsonFragment"`
|
||||
SubJsonNoises string `json:"subJsonNoises" form:"subJsonNoises"`
|
||||
SubJsonMux string `json:"subJsonMux" form:"subJsonMux"`
|
||||
SubJsonRules string `json:"subJsonRules" form:"subJsonRules"`
|
||||
Datepicker string `json:"datepicker" form:"datepicker"`
|
||||
WebListen string `json:"webListen" form:"webListen"`
|
||||
WebDomain string `json:"webDomain" form:"webDomain"`
|
||||
WebPort int `json:"webPort" form:"webPort"`
|
||||
WebCertFile string `json:"webCertFile" form:"webCertFile"`
|
||||
WebKeyFile string `json:"webKeyFile" form:"webKeyFile"`
|
||||
WebBasePath string `json:"webBasePath" form:"webBasePath"`
|
||||
SessionMaxAge int `json:"sessionMaxAge" form:"sessionMaxAge"`
|
||||
PageSize int `json:"pageSize" form:"pageSize"`
|
||||
ExpireDiff int `json:"expireDiff" form:"expireDiff"`
|
||||
TrafficDiff int `json:"trafficDiff" form:"trafficDiff"`
|
||||
RemarkModel string `json:"remarkModel" form:"remarkModel"`
|
||||
TgBotEnable bool `json:"tgBotEnable" form:"tgBotEnable"`
|
||||
TgBotToken string `json:"tgBotToken" form:"tgBotToken"`
|
||||
TgBotProxy string `json:"tgBotProxy" form:"tgBotProxy"`
|
||||
TgBotAPIServer string `json:"tgBotAPIServer" form:"tgBotAPIServer"`
|
||||
TgBotChatId string `json:"tgBotChatId" form:"tgBotChatId"`
|
||||
TgRunTime string `json:"tgRunTime" form:"tgRunTime"`
|
||||
TgBotBackup bool `json:"tgBotBackup" form:"tgBotBackup"`
|
||||
TgBotLoginNotify bool `json:"tgBotLoginNotify" form:"tgBotLoginNotify"`
|
||||
TgCpu int `json:"tgCpu" form:"tgCpu"`
|
||||
TgLang string `json:"tgLang" form:"tgLang"`
|
||||
TimeLocation string `json:"timeLocation" form:"timeLocation"`
|
||||
SecretEnable bool `json:"secretEnable" form:"secretEnable"`
|
||||
SubEnable bool `json:"subEnable" form:"subEnable"`
|
||||
SubListen string `json:"subListen" form:"subListen"`
|
||||
SubPort int `json:"subPort" form:"subPort"`
|
||||
SubPath string `json:"subPath" form:"subPath"`
|
||||
SubDomain string `json:"subDomain" form:"subDomain"`
|
||||
SubCertFile string `json:"subCertFile" form:"subCertFile"`
|
||||
SubKeyFile string `json:"subKeyFile" form:"subKeyFile"`
|
||||
SubUpdates int `json:"subUpdates" form:"subUpdates"`
|
||||
ExternalTrafficInformEnable bool `json:"externalTrafficInformEnable" form:"externalTrafficInformEnable"`
|
||||
ExternalTrafficInformURI string `json:"externalTrafficInformURI" form:"externalTrafficInformURI"`
|
||||
SubEncrypt bool `json:"subEncrypt" form:"subEncrypt"`
|
||||
SubShowInfo bool `json:"subShowInfo" form:"subShowInfo"`
|
||||
SubURI string `json:"subURI" form:"subURI"`
|
||||
SubJsonPath string `json:"subJsonPath" form:"subJsonPath"`
|
||||
SubJsonURI string `json:"subJsonURI" form:"subJsonURI"`
|
||||
SubJsonFragment string `json:"subJsonFragment" form:"subJsonFragment"`
|
||||
SubJsonNoises string `json:"subJsonNoises" form:"subJsonNoises"`
|
||||
SubJsonMux string `json:"subJsonMux" form:"subJsonMux"`
|
||||
SubJsonRules string `json:"subJsonRules" form:"subJsonRules"`
|
||||
Datepicker string `json:"datepicker" form:"datepicker"`
|
||||
}
|
||||
|
||||
func (s *AllSetting) CheckValid() error {
|
||||
|
||||
@@ -5,10 +5,8 @@
|
||||
<script src="{{ .base_path }}assets/axios/axios.min.js?{{ .cur_ver }}"></script>
|
||||
<script src="{{ .base_path }}assets/qs/qs.min.js"></script>
|
||||
<script src="{{ .base_path }}assets/js/axios-init.js?{{ .cur_ver }}"></script>
|
||||
<script src="{{ .base_path }}assets/js/util/common.js?{{ .cur_ver }}"></script>
|
||||
<script src="{{ .base_path }}assets/js/util/date-util.js?{{ .cur_ver }}"></script>
|
||||
<script src="{{ .base_path }}assets/js/util/utils.js?{{ .cur_ver }}"></script>
|
||||
<script src="{{ .base_path }}assets/js/langs.js"></script>
|
||||
<script src="{{ .base_path }}assets/js/util/index.js?{{ .cur_ver }}"></script>
|
||||
<script>
|
||||
const basePath = '{{ .base_path }}';
|
||||
axios.defaults.baseURL = basePath;
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<a-tag color="purple" class="qr-tag"><span>{{ i18n "pages.settings.subSettings"}}</span></a-tag>
|
||||
<tr-qr-bg class="qr-bg-sub">
|
||||
<tr-qr-bg-inner class="qr-bg-sub-inner">
|
||||
<canvas @click="copyToClipboard('qrCode-sub',genSubLink(qrModal.client.subId))" id="qrCode-sub" class="qr-cv"></canvas>
|
||||
<canvas @click="copy(genSubLink(qrModal.client.subId))" id="qrCode-sub" class="qr-cv"></canvas>
|
||||
</tr-qr-bg-inner>
|
||||
</tr-qr-bg>
|
||||
</tr-qr-box>
|
||||
@@ -18,20 +18,18 @@
|
||||
<a-tag color="purple" class="qr-tag"><span>{{ i18n "pages.settings.subSettings"}} Json</span></a-tag>
|
||||
<tr-qr-bg class="qr-bg-sub">
|
||||
<tr-qr-bg-inner class="qr-bg-sub-inner">
|
||||
<canvas @click="copyToClipboard('qrCode-subJson',genSubJsonLink(qrModal.client.subId))" id="qrCode-subJson" class="qr-cv"></canvas>
|
||||
<canvas @click="copy(genSubJsonLink(qrModal.client.subId))" id="qrCode-subJson" class="qr-cv"></canvas>
|
||||
</tr-qr-bg-inner>
|
||||
</tr-qr-bg>
|
||||
</tr-qr-box>
|
||||
</template>
|
||||
<template v-if="!isJustSub">
|
||||
<template v-for="(row, index) in qrModal.qrcodes">
|
||||
<tr-qr-box class="qr-box">
|
||||
<a-tag color="green" class="qr-tag"><span>[[ row.remark ]]</span></a-tag>
|
||||
<tr-qr-bg class="qr-bg">
|
||||
<canvas @click="copyToClipboard('qrCode-'+index, row.link)" :id="'qrCode-'+index" class="qr-cv"></canvas>
|
||||
</tr-qr-bg>
|
||||
</tr-qr-box>
|
||||
</template>
|
||||
<template v-for="(row, index) in qrModal.qrcodes">
|
||||
<tr-qr-box class="qr-box">
|
||||
<a-tag color="green" class="qr-tag"><span>[[ row.remark ]]</span></a-tag>
|
||||
<tr-qr-bg class="qr-bg">
|
||||
<canvas @click="copy(row.link)" :id="'qrCode-'+index" class="qr-cv"></canvas>
|
||||
</tr-qr-bg>
|
||||
</tr-qr-box>
|
||||
</template>
|
||||
</tr-qr-modal>
|
||||
</a-modal>
|
||||
@@ -43,16 +41,13 @@
|
||||
dbInbound: new DBInbound(),
|
||||
client: null,
|
||||
qrcodes: [],
|
||||
clipboard: null,
|
||||
visible: false,
|
||||
isJustSub: false,
|
||||
subId: '',
|
||||
show: function(title = '', dbInbound, client, isJustSub = false) {
|
||||
show: function(title = '', dbInbound, client) {
|
||||
this.title = title;
|
||||
this.dbInbound = dbInbound;
|
||||
this.inbound = dbInbound.toInbound();
|
||||
this.client = client;
|
||||
this.isJustSub = isJustSub;
|
||||
this.subId = '';
|
||||
this.qrcodes = [];
|
||||
if (this.inbound.protocol == Protocols.WIREGUARD) {
|
||||
@@ -80,19 +75,15 @@
|
||||
delimiters: ['[[', ']]'],
|
||||
el: '#qrcode-modal',
|
||||
data: {
|
||||
qrModal: qrModal,get isJustSub(){
|
||||
return qrModal.isJustSub
|
||||
}
|
||||
qrModal: qrModal,
|
||||
},
|
||||
methods: {
|
||||
copyToClipboard(elementId, content) {
|
||||
this.qrModal.clipboard = new ClipboardJS('#' + elementId, {
|
||||
text: () => content,
|
||||
});
|
||||
this.qrModal.clipboard.on('success', () => {
|
||||
app.$message.success('{{ i18n "copied" }}')
|
||||
this.qrModal.clipboard.destroy();
|
||||
});
|
||||
copy(content) {
|
||||
ClipboardManager
|
||||
.copyText(content)
|
||||
.then(() => {
|
||||
app.$message.success('{{ i18n "copied" }}')
|
||||
})
|
||||
},
|
||||
setQrCode(elementId, content) {
|
||||
new QRious({
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
:href="'data:application/text;charset=utf-8,' + encodeURIComponent(txtModal.content)"
|
||||
:download="txtModal.fileName">[[ txtModal.fileName ]]
|
||||
</a-button>
|
||||
<a-button type="primary" id="copy-btn">{{ i18n "copy" }}</a-button>
|
||||
<a-button type="primary" @click="txtModal.copy(txtModal.content)">{{ i18n "copy" }}</a-button>
|
||||
</template>
|
||||
<a-input style="overflow-y: auto;" type="textarea" v-model="txtModal.content"
|
||||
:autosize="{ minRows: 10, maxRows: 20}"></a-input>
|
||||
@@ -20,24 +20,20 @@
|
||||
content: '',
|
||||
fileName: '',
|
||||
qrcode: null,
|
||||
clipboard: null,
|
||||
visible: false,
|
||||
show: function (title = '', content = '', fileName = '') {
|
||||
this.title = title;
|
||||
this.content = content;
|
||||
this.fileName = fileName;
|
||||
this.visible = true;
|
||||
textModalApp.$nextTick(() => {
|
||||
if (this.clipboard === null) {
|
||||
this.clipboard = new ClipboardJS('#copy-btn', {
|
||||
text: () => this.content,
|
||||
});
|
||||
this.clipboard.on('success', () => {
|
||||
app.$message.success('{{ i18n "copied" }}')
|
||||
this.close();
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
copy: function (content = '') {
|
||||
ClipboardManager
|
||||
.copyText(content)
|
||||
.then(() => {
|
||||
app.$message.success('{{ i18n "copied" }}')
|
||||
this.close();
|
||||
})
|
||||
},
|
||||
close: function () {
|
||||
this.visible = false;
|
||||
|
||||
@@ -422,16 +422,16 @@
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<password-input autocomplete="password" name="password" icon="lock" v-model.trim="user.password"
|
||||
<a-password-input autocomplete="password" name="password" icon="lock" v-model.trim="user.password"
|
||||
placeholder='{{ i18n "password" }}'
|
||||
@keydown.enter.native="login">
|
||||
</password-input>
|
||||
</a-password-input>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="secretEnable">
|
||||
<password-input autocomplete="secret" name="secret" icon="key" v-model.trim="user.loginSecret"
|
||||
<a-password-input autocomplete="secret" name="secret" icon="key" v-model.trim="user.loginSecret"
|
||||
placeholder='{{ i18n "secretToken" }}'
|
||||
@keydown.enter.native="login">
|
||||
</password-input>
|
||||
</a-password-input>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-row justify="center" class="centered">
|
||||
@@ -449,9 +449,9 @@
|
||||
<a-row justify="center" class="centered">
|
||||
<a-col :span="24">
|
||||
<a-select ref="selectLang" v-model="lang"
|
||||
@change="setLang(lang)" style="width: 200px;"
|
||||
@change="LanguageManager.setLanguage(lang)" style="width: 200px;"
|
||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option :value="l.value" label="English" v-for="l in supportLangs">
|
||||
<a-select-option :value="l.value" label="English" v-for="l in LanguageManager.supportedLanguages">
|
||||
<span role="img" aria-label="l.name" v-text="l.icon"></span>
|
||||
<span v-text="l.name"></span>
|
||||
</a-select-option>
|
||||
@@ -461,7 +461,7 @@
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-row justify="center" class="centered">
|
||||
<theme-switch-login></theme-switch-login>
|
||||
<a-theme-switch-login></a-theme-switch-login>
|
||||
</a-row>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
@@ -493,7 +493,7 @@
|
||||
lang: ""
|
||||
},
|
||||
async created() {
|
||||
this.lang = getLang();
|
||||
this.lang = LanguageManager.getLanguage();
|
||||
this.secretEnable = await this.getSecretStatus();
|
||||
},
|
||||
methods: {
|
||||
|
||||
@@ -106,9 +106,9 @@
|
||||
<a-date-picker v-if="datepicker == 'gregorian'" :show-time="{ format: 'HH:mm:ss' }"
|
||||
format="YYYY-MM-DD HH:mm:ss" :dropdown-class-name="themeSwitcher.currentTheme"
|
||||
v-model="clientsBulkModal.expiryTime"></a-date-picker>
|
||||
<persian-datepicker v-else placeholder='{{ i18n "pages.settings.datepickerPlaceholder" }}'
|
||||
<a-persian-datepicker v-else placeholder='{{ i18n "pages.settings.datepickerPlaceholder" }}'
|
||||
value="clientsBulkModal.expiryTime" v-model="clientsBulkModal.expiryTime">
|
||||
</persian-datepicker>
|
||||
</a-persian-datepicker>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="clientsBulkModal.expiryTime != 0">
|
||||
<template slot="label">
|
||||
|
||||
@@ -16,16 +16,7 @@
|
||||
title: '',
|
||||
okText: '',
|
||||
isEdit: false,
|
||||
group: {
|
||||
canGroup: true,
|
||||
isGroup: false,
|
||||
currentClient: null,
|
||||
inbounds: [],
|
||||
clients: [],
|
||||
editIds: []
|
||||
},
|
||||
dbInbound: new DBInbound(),
|
||||
dbInbounds: null,
|
||||
inbound: new Inbound(),
|
||||
clients: [],
|
||||
clientStats: [],
|
||||
@@ -34,126 +25,33 @@
|
||||
clientIps: null,
|
||||
delayedStart: false,
|
||||
ok() {
|
||||
if (app.subSettings.enable && clientModal.group.isGroup && clientModal.group.canGroup) {
|
||||
const currentClient = clientModal.group.currentClient;
|
||||
const { limitIp, comment, totalGB, expiryTime, reset, enable, subId, tgId, flow } = currentClient;
|
||||
const uniqueEmails = clientModalApp.makeGroupEmailsUnique(clientModal.dbInbounds, currentClient.email, clientModal.group.clients);
|
||||
|
||||
clientModal.group.clients.forEach((client, index) => {
|
||||
client.email = uniqueEmails[index];
|
||||
client.limitIp = limitIp;
|
||||
client.comment = comment;
|
||||
client.totalGB = totalGB;
|
||||
client.expiryTime = expiryTime;
|
||||
client.reset = reset;
|
||||
client.enable = enable;
|
||||
|
||||
if (subId) {
|
||||
client.subId = subId;
|
||||
}
|
||||
if (tgId) {
|
||||
client.tgId = tgId;
|
||||
}
|
||||
if (flow) {
|
||||
client.flow = flow;
|
||||
}
|
||||
});
|
||||
|
||||
if (clientModal.isEdit) {
|
||||
ObjectUtil.execute(clientModal.confirm, clientModal.group.clients, clientModal.group.inbounds, clientModal.group.editIds);
|
||||
} else {
|
||||
ObjectUtil.execute(clientModal.confirm, clientModal.group.clients, clientModal.group.inbounds);
|
||||
}
|
||||
if (clientModal.isEdit) {
|
||||
ObjectUtil.execute(clientModal.confirm, clientModalApp.client, clientModal.dbInbound.id, clientModal.oldClientId);
|
||||
} else {
|
||||
if (clientModal.isEdit) {
|
||||
ObjectUtil.execute(clientModal.confirm, clientModalApp.client, clientModal.dbInbound.id, clientModal.oldClientId);
|
||||
} else {
|
||||
ObjectUtil.execute(clientModal.confirm, clientModalApp.client, clientModal.dbInbound.id);
|
||||
}
|
||||
ObjectUtil.execute(clientModal.confirm, clientModalApp.client, clientModal.dbInbound.id);
|
||||
}
|
||||
},
|
||||
show({
|
||||
title = '',
|
||||
okText = '{{ i18n "sure" }}',
|
||||
index = null,
|
||||
dbInbound = null,
|
||||
dbInbounds = null,
|
||||
confirm = () => {
|
||||
},
|
||||
isEdit = false
|
||||
}) {
|
||||
this.group = {
|
||||
canGroup: true,
|
||||
isGroup: false,
|
||||
currentClient: null,
|
||||
inbounds: [],
|
||||
clients: [],
|
||||
editIds: []
|
||||
}
|
||||
this.dbInbounds = dbInbounds;
|
||||
show({ title = '', okText = '{{ i18n "sure" }}', index = null, dbInbound = null, confirm = () => { }, isEdit = false }) {
|
||||
this.visible = true;
|
||||
this.title = title;
|
||||
this.okText = okText;
|
||||
this.isEdit = isEdit;
|
||||
if (app.subSettings.enable && dbInbounds !== null && Array.isArray(dbInbounds)) {
|
||||
if (isEdit) {
|
||||
this.showProcess(dbInbound, index);
|
||||
let processSingleEdit = true
|
||||
if (this.group.canGroup) {
|
||||
this.group.currentClient = this.clients[this.index]
|
||||
const response = app.getSubGroupClients(dbInbounds, this.group.currentClient)
|
||||
if (response.clients.length > 1) {
|
||||
this.group.isGroup = true;
|
||||
this.group.inbounds = response.inbounds
|
||||
this.group.clients = response.clients
|
||||
this.group.editIds = response.editIds
|
||||
if (this.clients[index].expiryTime < 0) {
|
||||
this.delayedStart = true;
|
||||
}
|
||||
processSingleEdit = false
|
||||
}
|
||||
}
|
||||
if (processSingleEdit) {
|
||||
this.singleEditClientProcess(index)
|
||||
}
|
||||
} else {
|
||||
this.group.isGroup = true;
|
||||
dbInbounds.forEach((dbInboundItem) => {
|
||||
this.showProcess(dbInboundItem);
|
||||
if (this.dbInbound.isMultiUser()) {
|
||||
this.addClient(this.inbound.protocol, this.clients);
|
||||
this.group.inbounds.push(dbInboundItem.id)
|
||||
this.group.clients.push(this.clients[this.index])
|
||||
}
|
||||
})
|
||||
this.group.currentClient = this.clients[this.index]
|
||||
this.dbInbound = new DBInbound(dbInbound);
|
||||
this.inbound = dbInbound.toInbound();
|
||||
this.clients = this.inbound.clients;
|
||||
this.index = index === null ? this.clients.length : index;
|
||||
this.delayedStart = false;
|
||||
if (isEdit) {
|
||||
if (this.clients[index].expiryTime < 0) {
|
||||
this.delayedStart = true;
|
||||
}
|
||||
this.oldClientId = this.getClientId(dbInbound.protocol, clients[index]);
|
||||
} else {
|
||||
this.showProcess(dbInbound, index);
|
||||
if (isEdit) {
|
||||
this.singleEditClientProcess(index)
|
||||
} else {
|
||||
this.addClient(this.inbound.protocol, this.clients);
|
||||
}
|
||||
this.addClient(this.inbound.protocol, this.clients);
|
||||
}
|
||||
this.clientStats = this.dbInbound.clientStats.find(row => row.email === this.clients[this.index].email);
|
||||
this.confirm = confirm;
|
||||
},
|
||||
showProcess(dbInbound, index = null) {
|
||||
this.dbInbound = new DBInbound(dbInbound);
|
||||
this.inbound = dbInbound.toInbound();
|
||||
if (this.dbInbound.isMultiUser()) {
|
||||
this.clients = this.inbound.clients;
|
||||
this.index = index === null ? this.clients.length : index;
|
||||
this.delayedStart = false;
|
||||
}
|
||||
},
|
||||
singleEditClientProcess(index) {
|
||||
if (this.clients[index].expiryTime < 0) {
|
||||
this.delayedStart = true;
|
||||
}
|
||||
this.oldClientId = this.getClientId(this.dbInbound.protocol, this.clients[index]);
|
||||
},
|
||||
},
|
||||
getClientId(protocol, client) {
|
||||
switch (protocol) {
|
||||
case Protocols.TROJAN: return client.password;
|
||||
@@ -174,7 +72,7 @@
|
||||
clientModal.visible = false;
|
||||
clientModal.loading(false);
|
||||
},
|
||||
loading(loading = true) {
|
||||
loading(loading=true) {
|
||||
clientModal.confirmLoading = loading;
|
||||
},
|
||||
};
|
||||
@@ -196,18 +94,6 @@
|
||||
get isEdit() {
|
||||
return this.clientModal.isEdit;
|
||||
},
|
||||
get isGroup() {
|
||||
return this.clientModal.group.isGroup;
|
||||
},
|
||||
get isGroupEdit() {
|
||||
return this.clientModal.group.canGroup;
|
||||
},
|
||||
set isGroupEdit(value) {
|
||||
this.clientModal.group.canGroup = value;
|
||||
if (!value) {
|
||||
this.clientModal.singleEditClientProcess(this.clientModal.index)
|
||||
}
|
||||
},
|
||||
get datepicker() {
|
||||
return app.datepicker;
|
||||
},
|
||||
@@ -234,36 +120,6 @@
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
makeGroupEmailsUnique(dbInbounds, baseEmail, groupClients) {
|
||||
// Extract the base part of the email (before the "__" if present)
|
||||
const match = baseEmail.match(/^(.*?)__/);
|
||||
const base = match ? match[1] : baseEmail;
|
||||
|
||||
// Generate initial emails for each client in the group
|
||||
const generatedEmails = groupClients.map((_, index) => `${base}__${index + 1}`);
|
||||
|
||||
// Function to check if an email already exists in dbInbounds but belongs to a different subId
|
||||
const isDuplicate = (emailToCheck, clientSubId) => {
|
||||
return dbInbounds.some((dbInbound) => {
|
||||
const settings = JSON.parse(dbInbound.settings);
|
||||
const clients = settings && settings.clients ? settings.clients : [];
|
||||
return clients.some(client => client.email === emailToCheck && client.subId !== clientSubId);
|
||||
});
|
||||
};
|
||||
|
||||
// Check if any of the generated emails are duplicates
|
||||
const hasDuplicates = generatedEmails.some((email, index) => {
|
||||
return isDuplicate(email, groupClients[index].subId);
|
||||
});
|
||||
|
||||
// If duplicates exist, add a random string to the base email to ensure uniqueness
|
||||
if (hasDuplicates) {
|
||||
const randomString = `-${RandomUtil.randomLowerAndNum(4)}`;
|
||||
return groupClients.map((_, index) => `${base}${randomString}__${index + 1}`);
|
||||
}
|
||||
|
||||
return generatedEmails;
|
||||
},
|
||||
async getDBClientIps(email) {
|
||||
const msg = await HttpUtil.post(`/panel/inbound/clientIps/${email}`);
|
||||
if (!msg.success) {
|
||||
@@ -291,22 +147,7 @@
|
||||
} catch (error) {
|
||||
}
|
||||
},
|
||||
async resetClientTrafficHandler(client, dbInboundId, clients = []) {
|
||||
if (clients.length > 0) {
|
||||
const resetRequests = clients
|
||||
.filter(client => {
|
||||
const inbound = clientModal.dbInbounds.find(inbound => inbound.id === client.inboundId);
|
||||
return inbound && app.hasClientStats(inbound, client.email);
|
||||
}).map(client => ({ inboundId: client.inboundId, email: client.email}));
|
||||
|
||||
return HttpUtil.postWithModalJson('/panel/inbound/resetGroupClientTraffic', resetRequests, null)
|
||||
} else {
|
||||
return HttpUtil.postWithModal('/panel/inbound/' + dbInboundId + '/resetClientTraffic/' + client.email)
|
||||
}
|
||||
},
|
||||
resetClientTraffic(client, dbInboundId, iconElement) {
|
||||
const subGroup = app.subSettings.enable && clientModal.group.isGroup && clientModal.group.canGroup && clientModal.dbInbounds && clientModal.dbInbounds.length > 0 ? app.getSubGroupClients(clientModal.dbInbounds, client) : [];
|
||||
const clients = subGroup && subGroup.clients && subGroup.clients.length > 1 ? subGroup.clients : [];
|
||||
resetClientTraffic(email, dbInboundId, iconElement) {
|
||||
this.$confirm({
|
||||
title: '{{ i18n "pages.inbounds.resetTraffic"}}',
|
||||
content: '{{ i18n "pages.inbounds.resetTrafficContent"}}',
|
||||
@@ -315,8 +156,8 @@
|
||||
cancelText: '{{ i18n "cancel"}}',
|
||||
onOk: async () => {
|
||||
iconElement.disabled = true;
|
||||
const msg = await this.resetClientTrafficHandler(client, dbInboundId, clients);
|
||||
if (msg && msg.success) {
|
||||
const msg = await HttpUtil.postWithModal('/panel/inbound/' + dbInboundId + '/resetClientTraffic/' + email);
|
||||
if (msg.success) {
|
||||
this.clientModal.clientStats.up = 0;
|
||||
this.clientModal.clientStats.down = 0;
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
|
||||
{{define "commonSider"}}
|
||||
<a-layout-sider :theme="themeSwitcher.currentTheme" id="sider" collapsible breakpoint="md">
|
||||
<theme-switch></theme-switch>
|
||||
<a-theme-switch></a-theme-switch>
|
||||
<a-menu :theme="themeSwitcher.currentTheme" mode="inline" :selected-keys="['{{ .request_uri }}']" @click="({key}) => key.startsWith('http') ? window.open(key) : location.href = key">
|
||||
{{template "menuItems" .}}
|
||||
</a-menu>
|
||||
@@ -43,7 +43,7 @@
|
||||
<div class="drawer-handle" @click="siderDrawer.change()" slot="handle">
|
||||
<a-icon :type="siderDrawer.visible ? 'close' : 'menu-fold'"></a-icon>
|
||||
</div>
|
||||
<theme-switch></theme-switch>
|
||||
<a-theme-switch></a-theme-switch>
|
||||
<a-menu :theme="themeSwitcher.currentTheme" mode="inline" :selected-keys="['{{ .request_uri }}']" @click="({key}) => key.startsWith('http') ? window.open(key) : location.href = key">
|
||||
{{template "menuItems" .}}
|
||||
</a-menu>
|
||||
|
||||
@@ -1,26 +1,46 @@
|
||||
{{define "component/passwordInput"}}
|
||||
<template>
|
||||
<a-input :value="value" :type="showPassword ? 'text' : 'password'"
|
||||
:placeholder="placeholder"
|
||||
:autocomplete="autocomplete"
|
||||
:name="name"
|
||||
@input="$emit('input', $event.target.value)">
|
||||
<template v-if="icon" #prefix>
|
||||
<a-icon :type="icon" style="font-size: 16px;" />
|
||||
</template>
|
||||
<template #addonAfter>
|
||||
<a-icon :type="showPassword ? 'eye-invisible' : 'eye'"
|
||||
@click="toggleShowPassword"
|
||||
style="font-size: 16px;" />
|
||||
</template>
|
||||
</a-input>
|
||||
<a-input :value="value" :type="showPassword ? 'text' : 'password'" :placeholder="placeholder"
|
||||
:autocomplete="autocomplete" :name="name" @input="$emit('input', $event.target.value)">
|
||||
<template v-if="icon" #prefix>
|
||||
<a-icon :type="icon" style="font-size: 16px;" />
|
||||
</template>
|
||||
<template #addonAfter>
|
||||
<a-icon :type="showPassword ? 'eye-invisible' : 'eye'" @click="toggleShowPassword" style="font-size: 16px;" />
|
||||
</template>
|
||||
</a-input>
|
||||
</template>
|
||||
{{end}}
|
||||
|
||||
{{define "component/password"}}
|
||||
<script>
|
||||
Vue.component('password-input', {
|
||||
props: ["title", "value", "placeholder", "icon", "autocomplete", "name"],
|
||||
Vue.component('a-password-input', {
|
||||
props: {
|
||||
'title': {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
'value': {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
'placeholder': {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
'autocomplete': {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
'name': {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
'icon': {
|
||||
type: undefined,
|
||||
required: false
|
||||
}
|
||||
},
|
||||
template: `{{template "component/passwordInput"}}`,
|
||||
data() {
|
||||
return {
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-input :value="value" type="text" v-model="date" data-jdp class="persian-datepicker"
|
||||
@input="$emit('input', convertToGregorian($event.target.value)); jalaliDatepicker.hide();"
|
||||
:placeholder="placeholder">
|
||||
@input="$emit('input', convertToGregorian($event.target.value)); jalaliDatepicker.hide();"
|
||||
:placeholder="placeholder">
|
||||
<template #addonAfter>
|
||||
<a-icon type="calendar" style="font-size: 14px; opacity: 0.5;"/>
|
||||
<a-icon type="calendar" style="font-size: 14px; opacity: 0.5;" />
|
||||
</template>
|
||||
</a-input>
|
||||
</div>
|
||||
@@ -13,15 +13,27 @@
|
||||
{{end}}
|
||||
|
||||
{{define "component/persianDatepicker"}}
|
||||
<link rel="stylesheet" href="{{ .base_path }}assets/persian-datepicker/persian-datepicker.min.css?{{ .cur_ver }}"/>
|
||||
<link rel="stylesheet" href="{{ .base_path }}assets/persian-datepicker/persian-datepicker.min.css?{{ .cur_ver }}" />
|
||||
<script src="{{ .base_path }}assets/moment/moment-jalali.min.js?{{ .cur_ver }}"></script>
|
||||
<script src="{{ .base_path }}assets/persian-datepicker/persian-datepicker.min.js?{{ .cur_ver }}"></script>
|
||||
<script>
|
||||
|
||||
const persianDatepicker = {};
|
||||
|
||||
Vue.component('persian-datepicker', {
|
||||
props: ['placeholder', 'format', 'value'],
|
||||
Vue.component('a-persian-datepicker', {
|
||||
props: {
|
||||
'format': {
|
||||
type: undefined,
|
||||
required: false,
|
||||
},
|
||||
'value': {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
'placeholder': {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
},
|
||||
template: `{{template "component/persianDatepickerTemplate"}}`,
|
||||
data() {
|
||||
return {
|
||||
@@ -57,4 +69,4 @@
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{{end}}
|
||||
{{end}}
|
||||
@@ -1,26 +1,18 @@
|
||||
{{define "component/settingListItem"}}
|
||||
<a-list-item style="padding: 20px">
|
||||
<a-row v-if="type === 'textarea'">
|
||||
<a-col>
|
||||
<a-list-item-meta :title="title" :description="desc"/>
|
||||
<a-textarea class="ant-setting-textarea" :value="value" @input="$emit('input', $event.target.value)" :auto-size="{ minRows: 10 }"></a-textarea>
|
||||
<!--a-textarea :value="value" @input="$emit('input', $event.target.value)" :auto-size="{ minRows: 10, maxRows: 30 }"></a-textarea-->
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row v-else>
|
||||
<a-list-item :style="{ padding: padding }">
|
||||
<a-row>
|
||||
<a-col :lg="24" :xl="12">
|
||||
<a-list-item-meta :title="title" :description="desc"/>
|
||||
<a-list-item-meta>
|
||||
<template #title>
|
||||
<slot name="title"></slot>
|
||||
</template>
|
||||
<template #description>
|
||||
<slot name="description"></slot>
|
||||
</template>
|
||||
</a-list-item-meta>
|
||||
</a-col>
|
||||
<a-col :lg="24" :xl="12">
|
||||
<template v-if="type === 'text'">
|
||||
<a-input :value="value" @input="$emit('input', $event.target.value)" :placeholder="placeholder"></a-input>
|
||||
</template>
|
||||
<template v-else-if="type === 'number'">
|
||||
<a-input-number :value="value" :step="step" @change="value => $emit('input', value)" :min="min" :max="max" style="width: 100%;"></a-input-number>
|
||||
</template>
|
||||
<template v-else-if="type === 'switch'">
|
||||
<a-switch :checked="value" @change="value => $emit('input', value)"></a-switch>
|
||||
</template>
|
||||
<slot name="control"></slot>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-list-item>
|
||||
@@ -28,9 +20,38 @@
|
||||
|
||||
{{define "component/setting"}}
|
||||
<script>
|
||||
Vue.component('setting-list-item', {
|
||||
props: ["type", "title", "desc", "value", "min", "max" , "step", "placeholder"],
|
||||
template: `{{template "component/settingListItem"}}`,
|
||||
});
|
||||
Vue.component('a-setting-list-item', {
|
||||
props: {
|
||||
'title': {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
'description': {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
'paddings': {
|
||||
type: String,
|
||||
required: false,
|
||||
defaultValue: "default",
|
||||
validator: function (value) {
|
||||
return ['small', 'default'].includes(value)
|
||||
}
|
||||
}
|
||||
},
|
||||
template: `{{ template "component/settingListItem" }}`,
|
||||
computed: {
|
||||
padding() {
|
||||
switch (this.paddings) {
|
||||
case "small":
|
||||
return "10px 20px !important"
|
||||
break;
|
||||
case "default":
|
||||
return "20px !important"
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
{{end}}
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
{{define "component/sortableTableTrigger"}}
|
||||
<a-icon type="drag"
|
||||
class="sortable-icon"
|
||||
style="cursor: move;"
|
||||
@mouseup="mouseUpHandler"
|
||||
@mousedown="mouseDownHandler"
|
||||
<a-icon type="drag" class="sortable-icon" style="cursor: move;" @mouseup="mouseUpHandler" @mousedown="mouseDownHandler"
|
||||
@click="clickHandler" />
|
||||
{{end}}
|
||||
|
||||
@@ -28,7 +24,16 @@
|
||||
newElementIndex: null,
|
||||
};
|
||||
},
|
||||
props: ['data-source', 'customRow'],
|
||||
props: {
|
||||
'data-source': {
|
||||
type: undefined,
|
||||
required: false,
|
||||
},
|
||||
'customRow': {
|
||||
type: undefined,
|
||||
required: false,
|
||||
}
|
||||
},
|
||||
inheritAttrs: false,
|
||||
provide() {
|
||||
const sortable = {}
|
||||
@@ -44,7 +49,7 @@
|
||||
sortable,
|
||||
}
|
||||
},
|
||||
render: function(createElement) {
|
||||
render: function (createElement) {
|
||||
return createElement('a-table', {
|
||||
class: {
|
||||
'ant-table-is-sorting': this.isDragging(),
|
||||
@@ -59,7 +64,7 @@
|
||||
drop: (e) => this.dropHandler(e),
|
||||
},
|
||||
scopedSlots: this.$scopedSlots,
|
||||
}, this.$slots.default, )
|
||||
}, this.$slots.default,)
|
||||
},
|
||||
created() {
|
||||
this.$memoSort = {};
|
||||
@@ -163,9 +168,14 @@
|
||||
}
|
||||
}
|
||||
});
|
||||
Vue.component('table-sort-trigger', {
|
||||
Vue.component('a-table-sort-trigger', {
|
||||
template: `{{template "component/sortableTableTrigger"}}`,
|
||||
props: ['item-index'],
|
||||
props: {
|
||||
'item-index': {
|
||||
type: undefined,
|
||||
required: false
|
||||
}
|
||||
},
|
||||
inject: ['sortable'],
|
||||
methods: {
|
||||
mouseDownHandler(e) {
|
||||
@@ -190,27 +200,33 @@
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-table-is-sorting .draggable-row td {
|
||||
background-color: #ffffff !important;
|
||||
}
|
||||
|
||||
.dark .ant-table-is-sorting .draggable-row td {
|
||||
background-color: var(--dark-color-surface-100) !important;
|
||||
}
|
||||
|
||||
.ant-table-is-sorting .dragging td {
|
||||
background-color: rgb(232 244 242) !important;
|
||||
color: rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.dark .ant-table-is-sorting .dragging td {
|
||||
background-color: var(--dark-color-table-hover) !important;
|
||||
color: rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
.ant-table-is-sorting .dragging {
|
||||
opacity: 1;
|
||||
box-shadow: 1px -2px 2px #008771;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.ant-table-is-sorting .dragging .ant-table-row-index {
|
||||
opacity: 0.3;
|
||||
}
|
||||
</style>
|
||||
{{end}}
|
||||
{{end}}
|
||||
@@ -6,9 +6,13 @@
|
||||
<a-icon type="bulb" :theme="themeSwitcher.isDarkTheme ? 'filled' : 'outlined'"></a-icon>
|
||||
<span>Theme</span>
|
||||
</span>
|
||||
<a-menu-item id="change-theme" class="ant-menu-theme-switch" @mousedown="themeSwitcher.animationsOff()"> Dark <a-switch style="margin-left: 2px;" size="small" :default-checked="themeSwitcher.isDarkTheme" @change="themeSwitcher.toggleTheme()"></a-switch>
|
||||
<a-menu-item id="change-theme" class="ant-menu-theme-switch" @mousedown="themeSwitcher.animationsOff()"> Dark
|
||||
<a-switch style="margin-left: 2px;" size="small" :default-checked="themeSwitcher.isDarkTheme"
|
||||
@change="themeSwitcher.toggleTheme()"></a-switch>
|
||||
</a-menu-item>
|
||||
<a-menu-item id="change-theme-ultra" v-if="themeSwitcher.isDarkTheme" class="ant-menu-theme-switch" @mousedown="themeSwitcher.animationsOffUltra()"> Ultra <a-checkbox style="margin-left: 2px;" :checked="themeSwitcher.isUltra" @click="themeSwitcher.toggleUltra()"></a-checkbox>
|
||||
<a-menu-item id="change-theme-ultra" v-if="themeSwitcher.isDarkTheme" class="ant-menu-theme-switch"
|
||||
@mousedown="themeSwitcher.animationsOffUltra()"> Ultra <a-checkbox style="margin-left: 2px;"
|
||||
:checked="themeSwitcher.isUltra" @click="themeSwitcher.toggleUltra()"></a-checkbox>
|
||||
</a-menu-item>
|
||||
</a-sub-menu>
|
||||
</a-menu>
|
||||
@@ -17,12 +21,15 @@
|
||||
|
||||
{{define "component/themeSwitchTemplateLogin"}}
|
||||
<template>
|
||||
<a-menu @mousedown="themeSwitcher.animationsOff()" id="change-theme" :theme="themeSwitcher.currentTheme" mode="inline" selected-keys="">
|
||||
<a-menu @mousedown="themeSwitcher.animationsOff()" id="change-theme" :theme="themeSwitcher.currentTheme" mode="inline"
|
||||
selected-keys="">
|
||||
<a-menu-item mode="inline" class="ant-menu-theme-switch">
|
||||
<a-icon type="bulb" :theme="themeSwitcher.isDarkTheme ? 'filled' : 'outlined'"></a-icon>
|
||||
<a-switch size="small" :default-checked="themeSwitcher.isDarkTheme" @change="themeSwitcher.toggleTheme()"></a-switch>
|
||||
<a-switch size="small" :default-checked="themeSwitcher.isDarkTheme"
|
||||
@change="themeSwitcher.toggleTheme()"></a-switch>
|
||||
<template v-if="themeSwitcher.isDarkTheme">
|
||||
<a-checkbox style="margin-left: 1rem; vertical-align: middle;" :checked="themeSwitcher.isUltra" @click="themeSwitcher.toggleUltra()">Ultra</a-checkbox>
|
||||
<a-checkbox style="margin-left: 1rem; vertical-align: middle;" :checked="themeSwitcher.isUltra"
|
||||
@click="themeSwitcher.toggleUltra()">Ultra</a-checkbox>
|
||||
</template>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
@@ -83,8 +90,7 @@
|
||||
};
|
||||
}
|
||||
const themeSwitcher = createThemeSwitcher();
|
||||
Vue.component('theme-switch', {
|
||||
props: [],
|
||||
Vue.component('a-theme-switch', {
|
||||
template: `{{template "component/themeSwitchTemplate"}}`,
|
||||
data: () => ({
|
||||
themeSwitcher
|
||||
@@ -96,8 +102,7 @@
|
||||
document.getElementById('message').className = themeSwitcher.currentTheme;
|
||||
}
|
||||
});
|
||||
Vue.component('theme-switch-login', {
|
||||
props: [],
|
||||
Vue.component('a-theme-switch-login', {
|
||||
template: `{{template "component/themeSwitchTemplateLogin"}}`,
|
||||
data: () => ({
|
||||
themeSwitcher
|
||||
@@ -110,4 +115,4 @@
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{{end}}
|
||||
{{end}}
|
||||
@@ -3,18 +3,6 @@
|
||||
<a-form-item label='{{ i18n "pages.inbounds.enable" }}'>
|
||||
<a-switch v-model="client.enable"></a-switch>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="isEdit && app.subSettings.enable && isGroup">
|
||||
<template slot="label">
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "pages.client.isGroupEditDesc" }}</span>
|
||||
</template>
|
||||
{{ i18n "pages.client.isGroupEdit" }}
|
||||
<a-icon type="question-circle"></a-icon>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<a-switch v-model="isGroupEdit"></a-switch>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<template slot="label">
|
||||
<a-tooltip>
|
||||
@@ -138,15 +126,15 @@
|
||||
<a-input-number v-model.number="client._totalGB" :min="0"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="isEdit && clientStats" label='{{ i18n "usage" }}'>
|
||||
<a-tag :color="clientUsageColor(clientStats, app.trafficDiff)">
|
||||
[[ sizeFormat(clientStats.up) ]] /
|
||||
[[ sizeFormat(clientStats.down) ]]
|
||||
([[ sizeFormat(clientStats.up + clientStats.down) ]])
|
||||
<a-tag :color="ColorUtils.clientUsageColor(clientStats, app.trafficDiff)">
|
||||
[[ SizeFormatter.sizeFormat(clientStats.up) ]] /
|
||||
[[ SizeFormatter.sizeFormat(clientStats.down) ]]
|
||||
([[ SizeFormatter.sizeFormat(clientStats.up + clientStats.down) ]])
|
||||
</a-tag>
|
||||
<a-tooltip>
|
||||
<template slot="title">{{ i18n "pages.inbounds.resetTraffic" }}</template>
|
||||
<a-icon type="retweet"
|
||||
@click="resetClientTraffic(client,clientStats.inboundId,$event.target)"
|
||||
@click="resetClientTraffic(client.email,clientStats.inboundId,$event.target)"
|
||||
v-if="client.email.length > 0"></a-icon>
|
||||
</a-tooltip>
|
||||
</a-form-item>
|
||||
@@ -166,8 +154,8 @@
|
||||
</template>
|
||||
<a-date-picker v-if="datepicker == 'gregorian'" :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
||||
:dropdown-class-name="themeSwitcher.currentTheme" v-model="client._expiryTime"></a-date-picker>
|
||||
<persian-datepicker v-else placeholder='{{ i18n "pages.settings.datepickerPlaceholder" }}'
|
||||
value="client._expiryTime" v-model="client._expiryTime"></persian-datepicker>
|
||||
<a-persian-datepicker v-else placeholder='{{ i18n "pages.settings.datepickerPlaceholder" }}'
|
||||
value="client._expiryTime" v-model="client._expiryTime"></a-persian-datepicker>
|
||||
<a-tag color="red" v-if="isEdit && isExpiry">Expired</a-tag>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="client.expiryTime != 0">
|
||||
|
||||
@@ -57,9 +57,9 @@
|
||||
<a-date-picker style="width: 100%;" v-if="datepicker == 'gregorian'" :show-time="{ format: 'HH:mm:ss' }"
|
||||
format="YYYY-MM-DD HH:mm:ss" :dropdown-class-name="themeSwitcher.currentTheme"
|
||||
v-model="dbInbound._expiryTime"></a-date-picker>
|
||||
<persian-datepicker v-else placeholder='{{ i18n "pages.settings.datepickerPlaceholder" }}'
|
||||
<a-persian-datepicker v-else placeholder='{{ i18n "pages.settings.datepickerPlaceholder" }}'
|
||||
value="dbInbound._expiryTime" v-model="dbInbound._expiryTime">
|
||||
</persian-datepicker>
|
||||
</a-persian-datepicker>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@
|
||||
</a-divider>
|
||||
<a-form-item label='Type'>
|
||||
<a-select v-model="noise.type" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="s in ['rand','base64','str']" :value="s">[[ s ]]</a-select-option>
|
||||
<a-select-option v-for="s in ['rand','base64','str', 'hex']" :value="s">[[ s ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label='Packet'>
|
||||
@@ -465,12 +465,17 @@
|
||||
<a-select-option v-for="tag in ['', ...outModal.tags]" :value="tag">[[ tag ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="TCP Fast Open">
|
||||
<a-switch v-model="outbound.stream.sockopt.tcpFastOpen"></a-switch>
|
||||
<a-form-item label='Address Port Strategy'>
|
||||
<a-select v-model="outbound.stream.sockopt.addressPortStrategy" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="key in Address_Port_Strategy" :value="key">[[ key ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="Keep Alive Interval">
|
||||
<a-input-number v-model.number="outbound.stream.sockopt.tcpKeepAliveInterval" :min="0"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label="TCP Fast Open">
|
||||
<a-switch v-model="outbound.stream.sockopt.tcpFastOpen"></a-switch>
|
||||
</a-form-item>
|
||||
<a-form-item label="Multipath TCP">
|
||||
<a-switch v-model.trim="outbound.stream.sockopt.tcpMptcp"></a-switch>
|
||||
</a-form-item>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<a-input-number v-model.number="inbound.stream.reality.xver" :min="0"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label='uTLS'>
|
||||
<a-select v-model="inbound.stream.reality.settings.fingerprint" style="width: 50%"
|
||||
<a-select v-model="inbound.stream.reality.settings.fingerprint" style="width: 100%"
|
||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
|
||||
</a-select>
|
||||
@@ -43,11 +43,11 @@
|
||||
<a-form-item label='SpiderX'>
|
||||
<a-input v-model.trim="inbound.stream.reality.settings.spiderX"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.inbounds.privatekey" }}'>
|
||||
<a-input v-model.trim="inbound.stream.reality.privateKey"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.inbounds.publicKey" }}'>
|
||||
<a-input v-model.trim="inbound.stream.reality.settings.publicKey"></a-input>
|
||||
<a-input v-model="inbound.stream.reality.settings.publicKey"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.inbounds.privatekey" }}'>
|
||||
<a-input type="password" v-model="inbound.stream.reality.privateKey"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label=" ">
|
||||
<a-button type="primary" icon="import" @click="getNewX25519Cert">Get New Cert</a-button>
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
<a-select-option value="wechat-video">WeChat</a-select-option>
|
||||
<a-select-option value="dtls">DTLS 1.2</a-select-option>
|
||||
<a-select-option value="wireguard">WireGuard</a-select-option>
|
||||
<a-select-option value="dns">DNS</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
</a-input-group>
|
||||
</a-form-item>
|
||||
<a-form-item label="uTLS">
|
||||
<a-select v-model="inbound.stream.tls.settings.fingerprint" style="width: 50%"
|
||||
<a-select v-model="inbound.stream.tls.settings.fingerprint" style="width: 100%"
|
||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option value=''>None</a-select-option>
|
||||
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
|
||||
@@ -57,8 +57,8 @@
|
||||
<a-form-item label="Session Resumption">
|
||||
<a-switch v-model="inbound.stream.tls.enableSessionResumption"></a-switch>
|
||||
</a-form-item>
|
||||
<a-form-item label="Server Name To Verify">
|
||||
<a-input v-model.trim="inbound.stream.tls.serverNameToVerify"></a-input>
|
||||
<a-form-item label="VerifyPeerCertInNames">
|
||||
<a-input v-model.trim="inbound.stream.tls.verifyPeerCertInNames"></a-input>
|
||||
</a-form-item>
|
||||
<template v-for="cert,index in inbound.stream.tls.certs">
|
||||
<a-form-item label='{{ i18n "certificate" }}'>
|
||||
@@ -85,10 +85,10 @@
|
||||
</template>
|
||||
<template v-else>
|
||||
<a-form-item label='{{ i18n "pages.inbounds.publicKey" }}'>
|
||||
<a-input type="textarea" :rows="3" v-model="cert.cert"></a-input>
|
||||
<a-input v-model="cert.cert"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.inbounds.privatekey" }}'>
|
||||
<a-input type="textarea" :rows="3" v-model="cert.key"></a-input>
|
||||
<a-input type="password" v-model="cert.key"></a-input>
|
||||
</a-form-item>
|
||||
</template>
|
||||
<a-form-item label='OCSP stapling'>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<template slot="title">{{ i18n "info" }}</template>
|
||||
<a-icon style="font-size: 24px;" class="normal-icon" type="info-circle" @click="showInfo(record.id,client);"></a-icon>
|
||||
</a-tooltip>
|
||||
<a-tooltip v-if="hasClientStats(record, client.email)">
|
||||
<a-tooltip>
|
||||
<template slot="title">{{ i18n "pages.inbounds.resetTraffic" }}</template>
|
||||
<a-popconfirm @confirm="resetClientTraffic(client,record.id,false)" title='{{ i18n "pages.inbounds.resetTrafficContent"}}' :overlay-class-name="themeSwitcher.currentTheme" ok-text='{{ i18n "reset"}}' cancel-text='{{ i18n "cancel"}}'>
|
||||
<a-icon slot="icon" type="question-circle-o" :style="themeSwitcher.isDarkTheme ? 'color: var(--color-primary-100)' : 'color: var(--color-primary-100)'"></a-icon>
|
||||
@@ -55,18 +55,18 @@
|
||||
<template slot="content" v-if="client.email">
|
||||
<table cellpadding="2" width="100%">
|
||||
<tr>
|
||||
<td>↑[[ sizeFormat(getUpStats(record, client.email)) ]]</td>
|
||||
<td>↓[[ sizeFormat(getDownStats(record, client.email)) ]]</td>
|
||||
<td>↑[[ SizeFormatter.sizeFormat(getUpStats(record, client.email)) ]]</td>
|
||||
<td>↓[[ SizeFormatter.sizeFormat(getDownStats(record, client.email)) ]]</td>
|
||||
</tr>
|
||||
<tr v-if="client.totalGB > 0">
|
||||
<td>{{ i18n "remained" }}</td>
|
||||
<td>[[ sizeFormat(getRemStats(record, client.email)) ]]</td>
|
||||
<td>[[ SizeFormatter.sizeFormat(getRemStats(record, client.email)) ]]</td>
|
||||
</tr>
|
||||
</table>
|
||||
</template>
|
||||
<table>
|
||||
<tr class="tr-table-box">
|
||||
<td class="tr-table-rt"> [[ sizeFormat(getSumStats(record, client.email)) ]] </td>
|
||||
<td class="tr-table-rt"> [[ SizeFormatter.sizeFormat(getSumStats(record, client.email)) ]] </td>
|
||||
<td class="tr-table-bar" v-if="!client.enable">
|
||||
<a-progress :stroke-color="themeSwitcher.isDarkTheme ? 'rgb(72 84 105)' : '#bcbcbc'" :show-info="false" :percent="statsProgress(record, client.email)" />
|
||||
</td>
|
||||
@@ -124,9 +124,9 @@
|
||||
</template>
|
||||
</span>
|
||||
</template>
|
||||
<a-tag style="min-width: 50px; border: none;" :color="userExpiryColor(app.expireDiff, client, themeSwitcher.isDarkTheme)"> [[ remainedDays(client.expiryTime) ]] </a-tag>
|
||||
<a-tag style="min-width: 50px; border: none;" :color="ColorUtils.userExpiryColor(app.expireDiff, client, themeSwitcher.isDarkTheme)"> [[ remainedDays(client.expiryTime) ]] </a-tag>
|
||||
</a-popover>
|
||||
<a-tag v-else :color="userExpiryColor(app.expireDiff, client, themeSwitcher.isDarkTheme)" style="border: none;" class="infinite-tag">
|
||||
<a-tag v-else :color="ColorUtils.userExpiryColor(app.expireDiff, client, themeSwitcher.isDarkTheme)" style="border: none;" class="infinite-tag">
|
||||
<svg height="10px" width="14px" viewBox="0 0 640 512" fill="currentColor">
|
||||
<path d="M484.4 96C407 96 349.2 164.1 320 208.5C290.8 164.1 233 96 155.6 96C69.75 96 0 167.8 0 256s69.75 160 155.6 160C233.1 416 290.8 347.9 320 303.5C349.2 347.9 407 416 484.4 416C570.3 416 640 344.2 640 256S570.3 96 484.4 96zM155.6 368C96.25 368 48 317.8 48 256s48.25-112 107.6-112c67.75 0 120.5 82.25 137.1 112C276 285.8 223.4 368 155.6 368zM484.4 368c-67.75 0-120.5-82.25-137.1-112C364 226.2 416.6 144 484.4 144C543.8 144 592 194.2 592 256S543.8 368 484.4 368z" fill="currentColor"></path>
|
||||
</svg>
|
||||
@@ -172,7 +172,7 @@
|
||||
<td colspan="3" style="text-align: center;">{{ i18n "pages.inbounds.traffic" }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="80px" style="margin:0; text-align: right;font-size: 1em;"> [[ sizeFormat(getUpStats(record, client.email) + getDownStats(record, client.email)) ]] </td>
|
||||
<td width="80px" style="margin:0; text-align: right;font-size: 1em;"> [[ SizeFormatter.sizeFormat(getUpStats(record, client.email) + getDownStats(record, client.email)) ]] </td>
|
||||
<td width="120px" v-if="!client.enable">
|
||||
<a-progress :stroke-color="themeSwitcher.isDarkTheme ? 'rgb(72 84 105)' : '#bcbcbc'" :show-info="false" :percent="statsProgress(record, client.email)" />
|
||||
</td>
|
||||
@@ -181,12 +181,12 @@
|
||||
<template slot="content" v-if="client.email">
|
||||
<table cellpadding="2" width="100%">
|
||||
<tr>
|
||||
<td>↑[[ sizeFormat(getUpStats(record, client.email)) ]]</td>
|
||||
<td>↓[[ sizeFormat(getDownStats(record, client.email)) ]]</td>
|
||||
<td>↑[[ SizeFormatter.sizeFormat(getUpStats(record, client.email)) ]]</td>
|
||||
<td>↓[[ SizeFormatter.sizeFormat(getDownStats(record, client.email)) ]]</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ i18n "remained" }}</td>
|
||||
<td>[[ sizeFormat(getRemStats(record, client.email)) ]]</td>
|
||||
<td>[[ SizeFormatter.sizeFormat(getRemStats(record, client.email)) ]]</td>
|
||||
</tr>
|
||||
</table>
|
||||
</template>
|
||||
@@ -244,7 +244,7 @@
|
||||
</template>
|
||||
</span>
|
||||
</template>
|
||||
<a-tag style="min-width: 50px; border: none;" :color="userExpiryColor(app.expireDiff, client, themeSwitcher.isDarkTheme)"> [[ remainedDays(client.expiryTime) ]] </a-tag>
|
||||
<a-tag style="min-width: 50px; border: none;" :color="ColorUtils.userExpiryColor(app.expireDiff, client, themeSwitcher.isDarkTheme)"> [[ remainedDays(client.expiryTime) ]] </a-tag>
|
||||
</a-popover>
|
||||
<a-tag v-else :color="client.enable ? 'purple' : themeSwitcher.isDarkTheme ? '#2c3950' : '#bcbcbc'" class="infinite-tag">
|
||||
<svg height="10px" width="14px" viewBox="0 0 640 512" fill="currentColor">
|
||||
|
||||
@@ -181,8 +181,8 @@
|
||||
<tr v-if="infoModal.clientStats">
|
||||
<td>{{ i18n "usage" }}</td>
|
||||
<td>
|
||||
<a-tag color="green">[[ sizeFormat(infoModal.clientStats.up + infoModal.clientStats.down) ]]</a-tag>
|
||||
<a-tag>↑ [[ sizeFormat(infoModal.clientStats.up) ]] / [[ sizeFormat(infoModal.clientStats.down) ]] ↓</a-tag>
|
||||
<a-tag color="green">[[ SizeFormatter.sizeFormat(infoModal.clientStats.up + infoModal.clientStats.down) ]]</a-tag>
|
||||
<a-tag>↑ [[ SizeFormatter.sizeFormat(infoModal.clientStats.up) ]] / [[ SizeFormatter.sizeFormat(infoModal.clientStats.down) ]] ↓</a-tag>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="infoModal.clientSettings.comment">
|
||||
@@ -224,7 +224,7 @@
|
||||
<a-tag v-if="infoModal.clientStats && infoModal.clientSettings.totalGB > 0" :color="statsColor(infoModal.clientStats)"> [[ getRemStats() ]] </a-tag>
|
||||
</td>
|
||||
<td>
|
||||
<a-tag v-if="infoModal.clientSettings.totalGB > 0" :color="statsColor(infoModal.clientStats)"> [[ sizeFormat(infoModal.clientSettings.totalGB) ]] </a-tag>
|
||||
<a-tag v-if="infoModal.clientSettings.totalGB > 0" :color="statsColor(infoModal.clientStats)"> [[ SizeFormatter.sizeFormat(infoModal.clientSettings.totalGB) ]] </a-tag>
|
||||
<a-tag v-else color="purple" class="infinite-tag">
|
||||
<svg height="10px" width="14px" viewBox="0 0 640 512" fill="currentColor">
|
||||
<path d="M484.4 96C407 96 349.2 164.1 320 208.5C290.8 164.1 233 96 155.6 96C69.75 96 0 167.8 0 256s69.75 160 155.6 160C233.1 416 290.8 347.9 320 303.5C349.2 347.9 407 416 484.4 416C570.3 416 640 344.2 640 256S570.3 96 484.4 96zM155.6 368C96.25 368 48 317.8 48 256s48.25-112 107.6-112c67.75 0 120.5 82.25 137.1 112C276 285.8 223.4 368 155.6 368zM484.4 368c-67.75 0-120.5-82.25-137.1-112C364 226.2 416.6 144 484.4 144C543.8 144 592 194.2 592 256S543.8 368 484.4 368z" fill="currentColor"></path>
|
||||
@@ -233,7 +233,7 @@
|
||||
</td>
|
||||
<td>
|
||||
<template v-if="infoModal.clientSettings.expiryTime > 0">
|
||||
<a-tag :color="usageColor(new Date().getTime(), app.expireDiff, infoModal.clientSettings.expiryTime)">
|
||||
<a-tag :color="ColorUtils.usageColor(new Date().getTime(), app.expireDiff, infoModal.clientSettings.expiryTime)">
|
||||
<template v-if="app.datepicker === 'gregorian'">
|
||||
[[ DateUtil.formatMillis(infoModal.clientSettings.expiryTime) ]]
|
||||
</template>
|
||||
@@ -258,7 +258,7 @@
|
||||
<tr-info-title class="tr-info-title">
|
||||
<a-tag color="purple">Subscription Link</a-tag>
|
||||
<a-tooltip title='{{ i18n "copy" }}'>
|
||||
<a-button size="small" icon="snippets" id="copy-sub-link" @click="copyToClipboard('copy-sub-link', infoModal.subLink)"></a-button>
|
||||
<a-button size="small" icon="snippets" @click="copy(infoModal.subLink)"></a-button>
|
||||
</a-tooltip>
|
||||
</tr-info-title>
|
||||
<a :href="[[ infoModal.subLink ]]" target="_blank">[[ infoModal.subLink ]]</a>
|
||||
@@ -267,7 +267,7 @@
|
||||
<tr-info-title class="tr-info-title">
|
||||
<a-tag color="purple">Json Link</a-tag>
|
||||
<a-tooltip title='{{ i18n "copy" }}'>
|
||||
<a-button size="small" icon="snippets" id="copy-subJson-link" @click="copyToClipboard('copy-subJson-link', infoModal.subJsonLink)"></a-button>
|
||||
<a-button size="small" icon="snippets" @click="copy(infoModal.subJsonLink)"></a-button>
|
||||
</a-tooltip>
|
||||
</tr-info-title>
|
||||
<a :href="[[ infoModal.subJsonLink ]]" target="_blank">[[ infoModal.subJsonLink ]]</a>
|
||||
@@ -279,7 +279,7 @@
|
||||
<tr-info-title class="tr-info-title">
|
||||
<a-tag color="blue">[[ infoModal.clientSettings.tgId ]]</a-tag>
|
||||
<a-tooltip title='{{ i18n "copy" }}'>
|
||||
<a-button size="small" icon="snippets" id="copy-tg-link" @click="copyToClipboard('copy-tg-link', infoModal.clientSettings.tgId)"></a-button>
|
||||
<a-button size="small" icon="snippets" @click="copy(infoModal.clientSettings.tgId)"></a-button>
|
||||
</a-tooltip>
|
||||
</tr-info-title>
|
||||
</tr-info-row>
|
||||
@@ -290,7 +290,7 @@
|
||||
<tr-info-title class="tr-info-title">
|
||||
<a-tag class="tr-info-tag" color="green">[[ link.remark ]]</a-tag>
|
||||
<a-tooltip title='{{ i18n "copy" }}'>
|
||||
<a-button style="min-width: 24px;" size="small" icon="snippets" :id="'copy-url-link-'+index" @click="copyToClipboard('copy-url-link-'+index, link.link)"></a-button>
|
||||
<a-button style="min-width: 24px;" size="small" icon="snippets" @click="copy(link.link)"></a-button>
|
||||
</a-tooltip>
|
||||
</tr-info-title>
|
||||
<code>[[ link.link ]]</code>
|
||||
@@ -304,7 +304,7 @@
|
||||
<tr-info-title class="tr-info-title">
|
||||
<a-tag class="tr-info-tag" color="green">[[ link.remark ]]</a-tag>
|
||||
<a-tooltip title='{{ i18n "copy" }}'>
|
||||
<a-button style="min-width: 24px;" size="small" icon="snippets" :id="'copy-url-link-'+index" @click="copyToClipboard('copy-url-link-'+index, link.link)"></a-button>
|
||||
<a-button style="min-width: 24px;" size="small" icon="snippets" @click="copy(link.link)"></a-button>
|
||||
</a-tooltip>
|
||||
</tr-info-title>
|
||||
<code>[[ link.link ]]</code>
|
||||
@@ -431,7 +431,7 @@
|
||||
<tr-info-title class="tr-info-title">
|
||||
<a-tag color="blue">Config</a-tag>
|
||||
<a-tooltip title='{{ i18n "copy" }}'>
|
||||
<a-button style="min-width: 24px;" size="small" icon="snippets" :id="'copy-url-link-'+index" @click="copyToClipboard('copy-url-link-'+index, infoModal.links[index])"></a-button>
|
||||
<a-button style="min-width: 24px;" size="small" icon="snippets" @click="copy(infoModal.links[index])"></a-button>
|
||||
</a-tooltip>
|
||||
</tr-info-title>
|
||||
<div v-html="infoModal.links[index].replaceAll(`\n`,`<br />`)" style="border-radius: 1rem; padding: 0.5rem;" class="client-table-odd-row">
|
||||
@@ -464,7 +464,6 @@
|
||||
clientStats: [],
|
||||
upStats: 0,
|
||||
downStats: 0,
|
||||
clipboard: null,
|
||||
links: [],
|
||||
index: null,
|
||||
isExpired: false,
|
||||
@@ -533,21 +532,19 @@
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
copyToClipboard(elementId, content) {
|
||||
this.infoModal.clipboard = new ClipboardJS('#' + elementId, {
|
||||
text: () => content,
|
||||
});
|
||||
this.infoModal.clipboard.on('success', () => {
|
||||
app.$message.success('{{ i18n "copied" }}')
|
||||
this.infoModal.clipboard.destroy();
|
||||
});
|
||||
copy(content) {
|
||||
ClipboardManager
|
||||
.copyText(content)
|
||||
.then(() => {
|
||||
app.$message.success('{{ i18n "copied" }}')
|
||||
})
|
||||
},
|
||||
statsColor(stats) {
|
||||
return usageColor(stats.up + stats.down, app.trafficDiff, stats.total);
|
||||
return ColorUtils.usageColor(stats.up + stats.down, app.trafficDiff, stats.total);
|
||||
},
|
||||
getRemStats() {
|
||||
remained = this.infoModal.clientStats.total - this.infoModal.clientStats.up - this.infoModal.clientStats.down;
|
||||
return remained > 0 ? sizeFormat(remained) : '-';
|
||||
return remained > 0 ? SizeFormatter.sizeFormat(remained) : '-';
|
||||
},
|
||||
refreshIPs() {
|
||||
this.refreshing = true;
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
confirmLoading: false,
|
||||
okText: '{{ i18n "sure" }}',
|
||||
isEdit: false,
|
||||
isGroup: false,
|
||||
confirm: null,
|
||||
inbound: new Inbound(),
|
||||
dbInbound: new DBInbound(),
|
||||
@@ -62,9 +61,6 @@
|
||||
get isEdit() {
|
||||
return inModal.isEdit;
|
||||
},
|
||||
get isGroup() {
|
||||
return inModal.isGroup;
|
||||
},
|
||||
get client() {
|
||||
return inModal.inbound.clients[0];
|
||||
},
|
||||
|
||||
@@ -141,11 +141,11 @@
|
||||
<a-row>
|
||||
<a-col :xs="24" :sm="24" :lg="12">
|
||||
{{ i18n "pages.inbounds.totalDownUp" }}:
|
||||
<a-tag color="green">[[ sizeFormat(total.up) ]] / [[ sizeFormat(total.down) ]]</a-tag>
|
||||
<a-tag color="green">[[ SizeFormatter.sizeFormat(total.up) ]] / [[ SizeFormatter.sizeFormat(total.down) ]]</a-tag>
|
||||
</a-col>
|
||||
<a-col :xs="24" :sm="24" :lg="12">
|
||||
{{ i18n "pages.inbounds.totalUsage" }}:
|
||||
<a-tag color="green">[[ sizeFormat(total.up + total.down) ]]</a-tag>
|
||||
<a-tag color="green">[[ SizeFormatter.sizeFormat(total.up + total.down) ]]</a-tag>
|
||||
</a-col>
|
||||
<a-col :xs="24" :sm="24" :lg="12">
|
||||
{{ i18n "pages.inbounds.inboundCount" }}:
|
||||
@@ -224,10 +224,6 @@
|
||||
<a-icon type="rest"></a-icon>
|
||||
{{ i18n "pages.inbounds.delDepletedClients" }}
|
||||
</a-menu-item>
|
||||
<a-menu-item v-if="subSettings.enable && dbInbounds.length > 0" key="addGroupClient">
|
||||
<a-icon type="usergroup-add"></a-icon>
|
||||
{{ i18n "pages.client.groupAdd"}}
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</a-dropdown>
|
||||
</a-col>
|
||||
@@ -379,19 +375,19 @@
|
||||
<template slot="content">
|
||||
<table cellpadding="2" width="100%">
|
||||
<tr>
|
||||
<td>↑[[ sizeFormat(dbInbound.up) ]]</td>
|
||||
<td>↓[[ sizeFormat(dbInbound.down) ]]</td>
|
||||
<td>↑[[ SizeFormatter.sizeFormat(dbInbound.up) ]]</td>
|
||||
<td>↓[[ SizeFormatter.sizeFormat(dbInbound.down) ]]</td>
|
||||
</tr>
|
||||
<tr v-if="dbInbound.total > 0 && dbInbound.up + dbInbound.down < dbInbound.total">
|
||||
<td>{{ i18n "remained" }}</td>
|
||||
<td>[[ sizeFormat(dbInbound.total - dbInbound.up - dbInbound.down) ]]</td>
|
||||
<td>[[ SizeFormatter.sizeFormat(dbInbound.total - dbInbound.up - dbInbound.down) ]]</td>
|
||||
</tr>
|
||||
</table>
|
||||
</template>
|
||||
<a-tag :color="usageColor(dbInbound.up + dbInbound.down, app.trafficDiff, dbInbound.total)">
|
||||
[[ sizeFormat(dbInbound.up + dbInbound.down) ]] /
|
||||
<a-tag :color="ColorUtils.usageColor(dbInbound.up + dbInbound.down, app.trafficDiff, dbInbound.total)">
|
||||
[[ SizeFormatter.sizeFormat(dbInbound.up + dbInbound.down) ]] /
|
||||
<template v-if="dbInbound.total > 0">
|
||||
[[ sizeFormat(dbInbound.total) ]]
|
||||
[[ SizeFormatter.sizeFormat(dbInbound.total) ]]
|
||||
</template>
|
||||
<template v-else>
|
||||
<svg height="10px" width="14px" viewBox="0 0 640 512" fill="currentColor">
|
||||
@@ -412,7 +408,7 @@
|
||||
<template v-else slot="content">
|
||||
[[ DateUtil.convertToJalalian(moment(dbInbound.expiryTime)) ]]
|
||||
</template>
|
||||
<a-tag style="min-width: 50px;" :color="usageColor(new Date().getTime(), app.expireDiff, dbInbound._expiryTime)">
|
||||
<a-tag style="min-width: 50px;" :color="ColorUtils.usageColor(new Date().getTime(), app.expireDiff, dbInbound._expiryTime)">
|
||||
[[ remainedDays(dbInbound._expiryTime) ]]
|
||||
</a-tag>
|
||||
</a-popover>
|
||||
@@ -478,19 +474,19 @@
|
||||
<template slot="content">
|
||||
<table cellpadding="2" width="100%">
|
||||
<tr>
|
||||
<td>↑[[ sizeFormat(dbInbound.up) ]]</td>
|
||||
<td>↓[[ sizeFormat(dbInbound.down) ]]</td>
|
||||
<td>↑[[ SizeFormatter.sizeFormat(dbInbound.up) ]]</td>
|
||||
<td>↓[[ SizeFormatter.sizeFormat(dbInbound.down) ]]</td>
|
||||
</tr>
|
||||
<tr v-if="dbInbound.total > 0 && dbInbound.up + dbInbound.down < dbInbound.total">
|
||||
<td>{{ i18n "remained" }}</td>
|
||||
<td>[[ sizeFormat(dbInbound.total - dbInbound.up - dbInbound.down) ]]</td>
|
||||
<td>[[ SizeFormatter.sizeFormat(dbInbound.total - dbInbound.up - dbInbound.down) ]]</td>
|
||||
</tr>
|
||||
</table>
|
||||
</template>
|
||||
<a-tag :color="usageColor(dbInbound.up + dbInbound.down, app.trafficDiff, dbInbound.total)">
|
||||
[[ sizeFormat(dbInbound.up + dbInbound.down) ]] /
|
||||
<a-tag :color="ColorUtils.usageColor(dbInbound.up + dbInbound.down, app.trafficDiff, dbInbound.total)">
|
||||
[[ SizeFormatter.sizeFormat(dbInbound.up + dbInbound.down) ]] /
|
||||
<template v-if="dbInbound.total > 0">
|
||||
[[ sizeFormat(dbInbound.total) ]]
|
||||
[[ SizeFormatter.sizeFormat(dbInbound.total) ]]
|
||||
</template>
|
||||
<template v-else>
|
||||
<svg height="10px" width="14px" viewBox="0 0 640 512" fill="currentColor">
|
||||
@@ -548,9 +544,7 @@
|
||||
</a-layout>
|
||||
</a-layout>
|
||||
{{template "js" .}}
|
||||
<script src="{{ .base_path }}assets/base64/base64.min.js"></script>
|
||||
<script src="{{ .base_path }}assets/qrcode/qrious2.min.js?{{ .cur_ver }}"></script>
|
||||
<script src="{{ .base_path }}assets/clipboard/clipboard.min.js?{{ .cur_ver }}"></script>
|
||||
<script src="{{ .base_path }}assets/uri/URI.min.js?{{ .cur_ver }}"></script>
|
||||
<script src="{{ .base_path }}assets/js/model/inbound.js?{{ .cur_ver }}"></script>
|
||||
<script src="{{ .base_path }}assets/js/model/dbinbound.js?{{ .cur_ver }}"></script>
|
||||
@@ -863,9 +857,6 @@
|
||||
case "delDepletedClients":
|
||||
this.delDepletedClients(-1)
|
||||
break;
|
||||
case "addGroupClient":
|
||||
this.openGroupAddClient()
|
||||
break;
|
||||
}
|
||||
},
|
||||
clickAction(action, dbInbound) {
|
||||
@@ -892,7 +883,7 @@
|
||||
this.exportSubs(dbInbound.id);
|
||||
break;
|
||||
case "clipboard":
|
||||
this.copyToClipboard(dbInbound.id);
|
||||
this.copy(dbInbound.id);
|
||||
break;
|
||||
case "resetTraffic":
|
||||
this.resetTraffic(dbInbound.id);
|
||||
@@ -936,7 +927,7 @@
|
||||
expiryTime: dbInbound.expiryTime,
|
||||
|
||||
listen: '',
|
||||
port: RandomUtil.randomIntRange(10000, 60000),
|
||||
port: RandomUtil.randomInteger(10000, 60000),
|
||||
protocol: baseInbound.protocol,
|
||||
settings: Inbound.Settings.getSettings(baseInbound.protocol).toString(),
|
||||
streamSettings: baseInbound.stream.toString(),
|
||||
@@ -1011,21 +1002,6 @@
|
||||
|
||||
await this.submit(`/panel/inbound/update/${dbInbound.id}`, data, inModal);
|
||||
},
|
||||
openGroupAddClient() {
|
||||
clientModal.show({
|
||||
title: '{{ i18n "pages.client.groupAdd"}}',
|
||||
okText: '{{ i18n "pages.client.submitAdd"}}',
|
||||
dbInbounds: this.dbInbounds,
|
||||
confirm: async (clients, dbInboundIds) => {
|
||||
await this.addGroupClient(clients, dbInboundIds, clientModal).then((res) => {
|
||||
if(res){
|
||||
this.showQrcode(dbInboundIds[0],clients[0], true)
|
||||
}
|
||||
});
|
||||
},
|
||||
isEdit: false
|
||||
});
|
||||
},
|
||||
openAddClient(dbInboundId) {
|
||||
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
||||
clientModal.show({
|
||||
@@ -1033,11 +1009,7 @@
|
||||
okText: '{{ i18n "pages.client.submitAdd"}}',
|
||||
dbInbound: dbInbound,
|
||||
confirm: async (clients, dbInboundId) => {
|
||||
await this.addClient(clients, dbInboundId, clientModal).then((res) => {
|
||||
if(res){
|
||||
this.showQrcode(dbInboundId,clients)
|
||||
}
|
||||
});
|
||||
await this.addClient(clients, dbInboundId, clientModal);
|
||||
},
|
||||
isEdit: false
|
||||
});
|
||||
@@ -1060,7 +1032,6 @@
|
||||
clientModal.show({
|
||||
title: '{{ i18n "pages.client.edit"}}',
|
||||
okText: '{{ i18n "pages.client.submitEdit"}}',
|
||||
dbInbounds: this.dbInbounds,
|
||||
dbInbound: dbInbound,
|
||||
index: index,
|
||||
confirm: async (client, dbInboundId, clientId) => {
|
||||
@@ -1086,36 +1057,12 @@
|
||||
};
|
||||
await this.submit(`/panel/inbound/addClient`, data, modal);
|
||||
},
|
||||
async addGroupClient(clients, dbInboundIds, modal) {
|
||||
const data = []
|
||||
dbInboundIds.forEach((dbInboundId, index) => {
|
||||
data.push({
|
||||
id: dbInboundId,
|
||||
settings: '{"clients": [' + clients[index].toString() + ']}',
|
||||
})
|
||||
})
|
||||
return await this.submit(`/panel/inbound/addGroupClient`, data, modal, true)
|
||||
},
|
||||
async updateClient(client, dbInboundId, clientId) {
|
||||
if (Array.isArray(client) && Array.isArray(dbInboundId) && Array.isArray(clientId)){
|
||||
const data = []
|
||||
client.forEach((client, index) => {
|
||||
data.push({
|
||||
clientId: clientId[index],
|
||||
inbound: {
|
||||
id: dbInboundId[index],
|
||||
settings: '{"clients": [' + client.toString() + ']}',
|
||||
}
|
||||
})
|
||||
})
|
||||
await this.submit(`/panel/inbound/updateClients`, data, clientModal, true);
|
||||
}else{
|
||||
const data = {
|
||||
id: dbInboundId,
|
||||
settings: '{"clients": [' + client.toString() + ']}',
|
||||
};
|
||||
await this.submit(`/panel/inbound/updateClient/${clientId}`, data, clientModal);
|
||||
}
|
||||
const data = {
|
||||
id: dbInboundId,
|
||||
settings: '{"clients": [' + client.toString() + ']}',
|
||||
};
|
||||
await this.submit(`/panel/inbound/updateClient/${clientId}`, data, clientModal);
|
||||
},
|
||||
resetTraffic(dbInboundId) {
|
||||
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
||||
@@ -1143,100 +1090,52 @@
|
||||
onOk: () => this.submit('/panel/inbound/del/' + dbInboundId),
|
||||
});
|
||||
},
|
||||
async delClientHandler(dbInboundId, currentClient, clients = []) {
|
||||
if (clients.length > 0) {
|
||||
const deleteRequestData = [];
|
||||
|
||||
for (const client of clients) {
|
||||
const dbInbound = this.dbInbounds.find(inbound => inbound.id === client.inboundId);
|
||||
if (dbInbound) {
|
||||
const inbound = dbInbound.toInbound();
|
||||
if (inbound && inbound.clients && inbound.clients.length === 1) {
|
||||
let newClient = Inbound.Settings.getSettings(inbound.protocol).toString();
|
||||
newClient = JSON.parse(newClient);
|
||||
newClient = newClient && newClient.clients && newClient.clients.length > 0 ? JSON.stringify(newClient.clients[0], null, 2) : null;
|
||||
if (newClient) {
|
||||
const data = {
|
||||
id: client.inboundId,
|
||||
settings: '{"clients": [' + newClient + ']}',
|
||||
};
|
||||
await this.submit(`/panel/inbound/addClient`, data, null);
|
||||
}
|
||||
}
|
||||
|
||||
deleteRequestData.push({
|
||||
inboundId: client.inboundId,
|
||||
clientId: client.clientId,
|
||||
});
|
||||
}
|
||||
}
|
||||
await this.submit('/panel/inbound/delGroupClients', deleteRequestData, null, true);
|
||||
} else {
|
||||
const dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
||||
const clientId = this.getClientId(dbInbound.protocol, currentClient);
|
||||
await this.submit(`/panel/inbound/${dbInboundId}/delClient/${clientId}`);
|
||||
}
|
||||
},
|
||||
delClient(dbInboundId, currentClient, confirmation = true) {
|
||||
const subGroup = this.subSettings.enable ? this.getSubGroupClients(this.dbInbounds, currentClient) : [];
|
||||
const clients = subGroup && subGroup.clients && subGroup.clients.length > 1 ? subGroup.clients : [];
|
||||
delClient(dbInboundId, client,confirmation = true) {
|
||||
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
||||
clientId = this.getClientId(dbInbound.protocol, client);
|
||||
if (confirmation){
|
||||
const clientEmails = clients.length > 0 ? clients.map(item => item.email) : currentClient.email
|
||||
this.$confirm({
|
||||
title: '{{ i18n "pages.inbounds.deleteClient"}}' + ' ' + clientEmails,
|
||||
title: '{{ i18n "pages.inbounds.deleteClient"}}' + ' ' + client.email,
|
||||
content: '{{ i18n "pages.inbounds.deleteClientContent"}}',
|
||||
class: themeSwitcher.currentTheme,
|
||||
okText: '{{ i18n "delete"}}',
|
||||
cancelText: '{{ i18n "cancel"}}',
|
||||
onOk: () => this.delClientHandler(dbInboundId, currentClient, clients),
|
||||
onOk: () => this.submit(`/panel/inbound/${dbInboundId}/delClient/${clientId}`),
|
||||
});
|
||||
} else {
|
||||
this.delClientHandler(dbInboundId, currentClient, clients)
|
||||
this.submit(`/panel/inbound/${dbInboundId}/delClient/${clientId}`);
|
||||
}
|
||||
},
|
||||
getSubGroupClients(dbInbounds, currentClient) {
|
||||
const response = {
|
||||
inbounds: [],
|
||||
clients: [],
|
||||
editIds: [],
|
||||
};
|
||||
|
||||
if (!Array.isArray(dbInbounds) || dbInbounds.length === 0) {
|
||||
return response;
|
||||
}
|
||||
if (!currentClient || !currentClient.subId) {
|
||||
return response;
|
||||
}
|
||||
|
||||
dbInbounds.forEach((dbInboundItem) => {
|
||||
try {
|
||||
const dbInbound = new DBInbound(dbInboundItem);
|
||||
if (!dbInbound) {
|
||||
return;
|
||||
const response = {
|
||||
inbounds: [],
|
||||
clients: [],
|
||||
editIds: []
|
||||
}
|
||||
|
||||
const inbound = dbInbound.toInbound();
|
||||
if (!inbound || !Array.isArray(inbound.clients)) {
|
||||
return;
|
||||
if (dbInbounds && dbInbounds.length > 0 && currentClient) {
|
||||
dbInbounds.forEach((dbInboundItem) => {
|
||||
const dbInbound = new DBInbound(dbInboundItem);
|
||||
if (dbInbound) {
|
||||
const inbound = dbInbound.toInbound();
|
||||
if (inbound) {
|
||||
const clients = inbound.clients;
|
||||
if (clients.length > 0) {
|
||||
clients.forEach((client) => {
|
||||
if (client['subId'] === currentClient['subId']) {
|
||||
client['inboundId'] = dbInboundItem.id
|
||||
client['clientId'] = this.getClientId(dbInbound.protocol, client)
|
||||
response.inbounds.push(dbInboundItem.id)
|
||||
response.clients.push(client)
|
||||
response.editIds.push(client['clientId'])
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
inbound.clients.forEach((client) => {
|
||||
if (client.subId === currentClient.subId) {
|
||||
client.inboundId = dbInboundItem.id;
|
||||
client.clientId = this.getClientId(dbInbound.protocol, client);
|
||||
|
||||
response.inbounds.push(dbInboundItem.id);
|
||||
response.clients.push(client);
|
||||
response.editIds.push(client.clientId);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error processing dbInboundItem:", dbInboundItem, error);
|
||||
}
|
||||
});
|
||||
|
||||
return response;
|
||||
},
|
||||
return response;
|
||||
},
|
||||
getClientId(protocol, client) {
|
||||
switch (protocol) {
|
||||
case Protocols.TROJAN: return client.password;
|
||||
@@ -1264,10 +1163,10 @@
|
||||
}
|
||||
return newDbInbound;
|
||||
},
|
||||
showQrcode(dbInboundId, client, isJustSub = false) {
|
||||
showQrcode(dbInboundId, client) {
|
||||
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
||||
newDbInbound = this.checkFallback(dbInbound);
|
||||
qrModal.show('{{ i18n "qrCode"}}', newDbInbound, client, isJustSub);
|
||||
qrModal.show('{{ i18n "qrCode"}}', newDbInbound, client);
|
||||
},
|
||||
showInfo(dbInboundId, client) {
|
||||
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
||||
@@ -1287,61 +1186,36 @@
|
||||
},
|
||||
async switchEnableClient(dbInboundId, client) {
|
||||
this.loading()
|
||||
const subGroup = this.subSettings.enable ? this.getSubGroupClients(this.dbInbounds, client) : [];
|
||||
if (subGroup && subGroup.clients && subGroup.clients.length > 0){
|
||||
await this.updateClient(subGroup.clients.map(item => {
|
||||
item.enable = !item.enable
|
||||
return item
|
||||
}), subGroup.inbounds, subGroup.editIds);
|
||||
}else{
|
||||
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
||||
inbound = dbInbound.toInbound();
|
||||
clients = inbound.clients;
|
||||
index = this.findIndexOfClient(dbInbound.protocol, clients, client);
|
||||
clients[index].enable = !clients[index].enable;
|
||||
clientId = this.getClientId(dbInbound.protocol, clients[index]);
|
||||
await this.updateClient(clients[index], dbInboundId, clientId);
|
||||
}
|
||||
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
||||
inbound = dbInbound.toInbound();
|
||||
clients = inbound.clients;
|
||||
index = this.findIndexOfClient(dbInbound.protocol, clients, client);
|
||||
clients[index].enable = !clients[index].enable;
|
||||
clientId = this.getClientId(dbInbound.protocol, clients[index]);
|
||||
await this.updateClient(clients[index], dbInboundId, clientId);
|
||||
this.loading(false);
|
||||
},
|
||||
async submit(url, data, model, isJson = false) {
|
||||
const msg = isJson ? await HttpUtil.postWithModalJson(url, data, model) : await HttpUtil.postWithModal(url, data, model);
|
||||
async submit(url, data, modal) {
|
||||
const msg = await HttpUtil.postWithModal(url, data, modal);
|
||||
if (msg.success) {
|
||||
await this.getDBInbounds();
|
||||
}
|
||||
return msg
|
||||
},
|
||||
getInboundClients(dbInbound) {
|
||||
return dbInbound.toInbound().clients;
|
||||
},
|
||||
resetClientTrafficHandler(client, dbInboundId, clients = []) {
|
||||
if (clients.length > 0){
|
||||
const resetRequests = clients
|
||||
.filter(client => {
|
||||
const inbound = this.dbInbounds.find(inbound => inbound.id === client.inboundId);
|
||||
return inbound && this.hasClientStats(inbound, client.email);
|
||||
}).map(client => ({ inboundId: client.inboundId, email: client.email}));
|
||||
|
||||
this.submit('/panel/inbound/resetGroupClientTraffic', resetRequests, null, true)
|
||||
}else {
|
||||
this.submit('/panel/inbound/' + dbInboundId + '/resetClientTraffic/' + client.email);
|
||||
}
|
||||
},
|
||||
resetClientTraffic(client, dbInboundId, confirmation = true) {
|
||||
const subGroup = this.subSettings.enable ? this.getSubGroupClients(this.dbInbounds, client) : [];
|
||||
const clients = subGroup && subGroup.clients && subGroup.clients.length > 1 ? subGroup.clients : [];
|
||||
if (confirmation){
|
||||
const clientEmails = clients.length > 0 ? clients.map(item => item.email) : client.email
|
||||
this.$confirm({
|
||||
title: '{{ i18n "pages.inbounds.resetTraffic"}}' + ' ' + clientEmails,
|
||||
title: '{{ i18n "pages.inbounds.resetTraffic"}}' + ' ' + client.email,
|
||||
content: '{{ i18n "pages.inbounds.resetTrafficContent"}}',
|
||||
class: themeSwitcher.currentTheme,
|
||||
okText: '{{ i18n "reset"}}',
|
||||
cancelText: '{{ i18n "cancel"}}',
|
||||
onOk: () => this.resetClientTrafficHandler(client, dbInboundId, clients),
|
||||
onOk: () => this.submit('/panel/inbound/' + dbInboundId + '/resetClientTraffic/' + client.email),
|
||||
})
|
||||
} else {
|
||||
this.resetClientTrafficHandler(client, dbInboundId, clients);
|
||||
this.submit('/panel/inbound/' + dbInboundId + '/resetClientTraffic/' + client.email);
|
||||
}
|
||||
},
|
||||
resetAllTraffic() {
|
||||
@@ -1377,10 +1251,6 @@
|
||||
isExpiry(dbInbound, index) {
|
||||
return dbInbound.toInbound().isExpiry(index);
|
||||
},
|
||||
hasClientStats(dbInbound, email) {
|
||||
if (email.length == 0) return 0;
|
||||
return !!dbInbound.clientStats.find(stats => stats.email === email);
|
||||
},
|
||||
getUpStats(dbInbound, email) {
|
||||
if (email.length == 0) return 0;
|
||||
clientStats = dbInbound.clientStats.find(stats => stats.email === email);
|
||||
@@ -1404,9 +1274,9 @@
|
||||
return remained>0 ? remained : 0;
|
||||
},
|
||||
clientStatsColor(dbInbound, email) {
|
||||
if (email.length == 0) return clientUsageColor();
|
||||
if (email.length == 0) return ColorUtils.clientUsageColor();
|
||||
clientStats = dbInbound.clientStats.find(stats => stats.email === email);
|
||||
return clientUsageColor(clientStats, app.trafficDiff)
|
||||
return ColorUtils.clientUsageColor(clientStats, app.trafficDiff)
|
||||
},
|
||||
statsProgress(dbInbound, email) {
|
||||
if (email.length == 0) return 100;
|
||||
@@ -1424,17 +1294,17 @@
|
||||
},
|
||||
remainedDays(expTime){
|
||||
if (expTime == 0) return null;
|
||||
if (expTime < 0) return formatSecond(expTime/-1000);
|
||||
if (expTime < 0) return TimeFormatter.formatSecond(expTime/-1000);
|
||||
now = new Date().getTime();
|
||||
if (expTime < now) return '{{ i18n "depleted" }}';
|
||||
return formatSecond((expTime-now)/1000);
|
||||
return TimeFormatter.formatSecond((expTime-now)/1000);
|
||||
},
|
||||
statsExpColor(dbInbound, email){
|
||||
if (email.length == 0) return '#7a316f';
|
||||
clientStats = dbInbound.clientStats.find(stats => stats.email === email);
|
||||
if (!clientStats) return '#7a316f';
|
||||
statsColor = usageColor(clientStats.down + clientStats.up, this.trafficDiff, clientStats.total);
|
||||
expColor = usageColor(new Date().getTime(), this.expireDiff, clientStats.expiryTime);
|
||||
statsColor = ColorUtils.usageColor(clientStats.down + clientStats.up, this.trafficDiff, clientStats.total);
|
||||
expColor = ColorUtils.usageColor(new Date().getTime(), this.expireDiff, clientStats.expiryTime);
|
||||
switch (true) {
|
||||
case statsColor == "red" || expColor == "red":
|
||||
return "#cf3c3c"; // Red
|
||||
@@ -1512,9 +1382,9 @@
|
||||
}
|
||||
txtModal.show('{{ i18n "pages.inbounds.export"}}', copyText.join('\r\n'), 'All-Inbounds');
|
||||
},
|
||||
copyToClipboard(dbInboundId) {
|
||||
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
||||
txtModal.show('{{ i18n "pages.inbounds.inboundData" }}', JSON.stringify(dbInbound, null, 2));
|
||||
copy(dbInboundId) {
|
||||
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
||||
txtModal.show('{{ i18n "pages.inbounds.inboundData" }}', JSON.stringify(dbInbound, null, 2));
|
||||
},
|
||||
async startDataRefreshLoop() {
|
||||
while (this.isRefreshEnabled) {
|
||||
@@ -1568,7 +1438,7 @@
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
searchKey: debounce(function (newVal) {
|
||||
searchKey: Utils.debounce(function (newVal) {
|
||||
this.searchInbounds(newVal);
|
||||
}, 500)
|
||||
},
|
||||
|
||||
@@ -19,6 +19,18 @@
|
||||
.ant-card-dark h2 {
|
||||
color: var(--dark-color-text-primary);
|
||||
}
|
||||
.ant-backup-list-item {
|
||||
gap: 10px;
|
||||
user-select: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
.dark .ant-backup-list-item svg {
|
||||
color: var(--dark-color-text-primary);
|
||||
}
|
||||
.dark .ant-backup-list,
|
||||
.dark .ant-xray-version-list {
|
||||
border-color: var(--dark-color-stroke);
|
||||
}
|
||||
</style>
|
||||
|
||||
<body>
|
||||
@@ -45,11 +57,11 @@
|
||||
<a-progress type="dashboard" status="normal"
|
||||
:stroke-color="status.cpu.color"
|
||||
:percent="status.cpu.percent"></a-progress>
|
||||
<div><b>CPU:</b> [[ cpuCoreFormat(status.cpuCores) ]] <a-tooltip>
|
||||
<div><b>CPU:</b> [[ CPUFormatter.cpuCoreFormat(status.cpuCores) ]] <a-tooltip>
|
||||
<a-icon type="area-chart"></a-icon>
|
||||
<template slot="title">
|
||||
<div><b>Logical Processors:</b> [[ (status.logicalPro) ]]</div>
|
||||
<div><b>Speed:</b> [[ cpuSpeedFormat(status.cpuSpeedMhz) ]]</div>
|
||||
<div><b>Speed:</b> [[ CPUFormatter.cpuSpeedFormat(status.cpuSpeedMhz) ]]</div>
|
||||
</template>
|
||||
</a-tooltip></div>
|
||||
</a-col>
|
||||
@@ -58,7 +70,7 @@
|
||||
:stroke-color="status.mem.color"
|
||||
:percent="status.mem.percent"></a-progress>
|
||||
<div>
|
||||
<b>{{ i18n "pages.index.memory"}}:</b> [[ sizeFormat(status.mem.current) ]] / [[ sizeFormat(status.mem.total) ]]
|
||||
<b>{{ i18n "pages.index.memory"}}:</b> [[ SizeFormatter.sizeFormat(status.mem.current) ]] / [[ SizeFormatter.sizeFormat(status.mem.total) ]]
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
@@ -70,7 +82,7 @@
|
||||
:stroke-color="status.swap.color"
|
||||
:percent="status.swap.percent"></a-progress>
|
||||
<div>
|
||||
<b>Swap:</b> [[ sizeFormat(status.swap.current) ]] / [[ sizeFormat(status.swap.total) ]]
|
||||
<b>Swap:</b> [[ SizeFormatter.sizeFormat(status.swap.current) ]] / [[ SizeFormatter.sizeFormat(status.swap.total) ]]
|
||||
</div>
|
||||
</a-col>
|
||||
<a-col :span="12" style="text-align: center">
|
||||
@@ -78,7 +90,7 @@
|
||||
:stroke-color="status.disk.color"
|
||||
:percent="status.disk.percent"></a-progress>
|
||||
<div>
|
||||
<b>{{ i18n "pages.index.hard"}}:</b> [[ sizeFormat(status.disk.current) ]] / [[ sizeFormat(status.disk.total) ]]
|
||||
<b>{{ i18n "pages.index.hard"}}:</b> [[ SizeFormatter.sizeFormat(status.disk.current) ]] / [[ SizeFormatter.sizeFormat(status.disk.total) ]]
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
@@ -99,8 +111,8 @@
|
||||
<a-col :sm="24" :lg="12">
|
||||
<a-card hoverable>
|
||||
<b>{{ i18n "pages.index.operationHours" }}:</b>
|
||||
<a-tag :color="status.xray.color">Xray: [[ formatSecond(status.appStats.uptime) ]]</a-tag>
|
||||
<a-tag color="green">OS: [[ formatSecond(status.uptime) ]]</a-tag>
|
||||
<a-tag :color="status.xray.color">Xray: [[ TimeFormatter.formatSecond(status.appStats.uptime) ]]</a-tag>
|
||||
<a-tag color="green">OS: [[ TimeFormatter.formatSecond(status.uptime) ]]</a-tag>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :sm="24" :lg="12">
|
||||
@@ -145,7 +157,7 @@
|
||||
<a-col :sm="24" :lg="12">
|
||||
<a-card hoverable>
|
||||
<b>{{ i18n "usage"}}:</b>
|
||||
<a-tag color="green"> RAM: [[ sizeFormat(status.appStats.mem) ]] </a-tag>
|
||||
<a-tag color="green"> RAM: [[ SizeFormatter.sizeFormat(status.appStats.mem) ]] </a-tag>
|
||||
<a-tag color="green"> Threads: [[ status.appStats.threads ]] </a-tag>
|
||||
</a-card>
|
||||
</a-col>
|
||||
@@ -207,7 +219,7 @@
|
||||
<a-col :span="12">
|
||||
<a-tag>
|
||||
<a-tooltip>
|
||||
<a-icon type="arrow-up"></a-icon> Up: [[ sizeFormat(status.netIO.up) ]]/s
|
||||
<a-icon type="arrow-up"></a-icon> Up: [[ SizeFormatter.sizeFormat(status.netIO.up) ]]/s
|
||||
<template slot="title">
|
||||
{{ i18n "pages.index.upSpeed" }}
|
||||
</template>
|
||||
@@ -217,7 +229,7 @@
|
||||
<a-col :span="12">
|
||||
<a-tag>
|
||||
<a-tooltip>
|
||||
<a-icon type="arrow-down"></a-icon> Down: [[ sizeFormat(status.netIO.down) ]]/s
|
||||
<a-icon type="arrow-down"></a-icon> Down: [[ SizeFormatter.sizeFormat(status.netIO.down) ]]/s
|
||||
<template slot="title">
|
||||
{{ i18n "pages.index.downSpeed" }}
|
||||
</template>
|
||||
@@ -236,7 +248,7 @@
|
||||
<a-icon type="cloud-upload"></a-icon>
|
||||
<template slot="title">
|
||||
{{ i18n "pages.index.totalSent" }}
|
||||
</template> Out: [[ sizeFormat(status.netTraffic.sent) ]]
|
||||
</template> Out: [[ SizeFormatter.sizeFormat(status.netTraffic.sent) ]]
|
||||
</a-tooltip>
|
||||
</a-tag>
|
||||
</a-col>
|
||||
@@ -246,7 +258,7 @@
|
||||
<a-icon type="cloud-download"></a-icon>
|
||||
<template slot="title">
|
||||
{{ i18n "pages.index.totalReceive" }}
|
||||
</template> In: [[ sizeFormat(status.netTraffic.recv) ]]
|
||||
</template> In: [[ SizeFormatter.sizeFormat(status.netTraffic.recv) ]]
|
||||
</a-tooltip>
|
||||
</a-tag>
|
||||
</a-col>
|
||||
@@ -260,14 +272,16 @@
|
||||
</a-layout>
|
||||
<a-modal id="version-modal" v-model="versionModal.visible" title='{{ i18n "pages.index.xraySwitch" }}' :closable="true"
|
||||
@ok="() => versionModal.visible = false" :class="themeSwitcher.currentTheme" footer="">
|
||||
<a-alert type="warning" style="margin-bottom: 12px; width: fit-content"
|
||||
<a-alert type="warning" style="margin-bottom: 12px; width: 100%;"
|
||||
message='{{ i18n "pages.index.xraySwitchClickDesk" }}' show-icon></a-alert>
|
||||
<template v-for="version, index in versionModal.versions">
|
||||
<a-tag :color="index % 2 == 0 ? 'purple' : 'green'" style="margin-right: 12px; margin-bottom: 12px"
|
||||
@click="switchV2rayVersion(version)">
|
||||
[[ version ]]
|
||||
</a-tag>
|
||||
</template>
|
||||
<a-list class="ant-xray-version-list" bordered style="width: 100%;">
|
||||
<a-list-item class="ant-xray-version-list-item" v-for="version in versionModal.versions">
|
||||
<a-list-item-meta>
|
||||
<template #title>[[ version ]]</template>
|
||||
</a-list-item-meta>
|
||||
<a-radio :checked="version === `v${status.xray.version}`" @click="switchV2rayVersion(version)"></a-radio>
|
||||
</a-list-item>
|
||||
</a-list>
|
||||
</a-modal>
|
||||
<a-modal id="log-modal" v-model="logModal.visible"
|
||||
:closable="true" @cancel="() => logModal.visible = false"
|
||||
@@ -314,25 +328,34 @@
|
||||
</a-form>
|
||||
<div class="ant-input" style="height: auto; max-height: 500px; overflow: auto; margin-top: 0.5rem;" v-html="logModal.formattedLogs"></div>
|
||||
</a-modal>
|
||||
<a-modal id="backup-modal" v-model="backupModal.visible" :title="backupModal.title"
|
||||
:closable="true" footer=""
|
||||
<a-modal id="backup-modal"
|
||||
v-model="backupModal.visible"
|
||||
title='{{ i18n "pages.index.backupTitle" }}'
|
||||
:closable="true"
|
||||
footer=""
|
||||
:class="themeSwitcher.currentTheme">
|
||||
<a-alert type="warning" style="margin-bottom: 10px; width: fit-content"
|
||||
:message="backupModal.description"
|
||||
show-icon>
|
||||
</a-alert>
|
||||
<a-space direction="horizontal" style="text-align: center; margin-bottom: 10px;">
|
||||
<a-button type="primary" @click="exportDatabase()">
|
||||
[[ backupModal.exportText ]]
|
||||
</a-button>
|
||||
<a-button type="primary" @click="importDatabase()">
|
||||
[[ backupModal.importText ]]
|
||||
</a-button>
|
||||
</a-space>
|
||||
<a-list class="ant-backup-list" bordered style="width: 100%;">
|
||||
<a-list-item class="ant-backup-list-item" @click="exportDatabase()">
|
||||
<a-list-item-meta>
|
||||
<template #title>{{ i18n "pages.index.exportDatabase" }}</template>
|
||||
<template #description>{{ i18n "pages.index.exportDatabaseDesc" }}</template>
|
||||
</a-list-item-meta>
|
||||
<a-icon type="right" />
|
||||
</a-list-item>
|
||||
<a-list-item class="ant-backup-list-item" @click="importDatabase()">
|
||||
<a-list-item-meta>
|
||||
<template #title>{{ i18n "pages.index.importDatabase" }}</template>
|
||||
<template #description>{{ i18n "pages.index.importDatabaseDesc" }}</template>
|
||||
<templaet #avatar>
|
||||
<a-icon type="import" />
|
||||
</templaet>
|
||||
</a-list-item-meta>
|
||||
<a-icon type="right" />
|
||||
</a-list-item>
|
||||
</a-list>
|
||||
</a-modal>
|
||||
</a-layout>
|
||||
{{template "js" .}}
|
||||
<script src="{{ .base_path }}assets/clipboard/clipboard.min.js?{{ .cur_ver }}"></script>
|
||||
{{template "component/themeSwitcher" .}}
|
||||
{{template "textModal"}}
|
||||
<script>
|
||||
@@ -354,7 +377,7 @@
|
||||
if (this.total === 0) {
|
||||
return 0;
|
||||
}
|
||||
return toFixed(this.current / this.total * 100, 2);
|
||||
return NumberFormatter.toFixed(this.current / this.total * 100, 2);
|
||||
}
|
||||
|
||||
get color() {
|
||||
@@ -397,7 +420,7 @@
|
||||
this.logicalPro = data.logicalPro;
|
||||
this.cpuSpeedMhz = data.cpuSpeedMhz;
|
||||
this.disk = new CurTotal(data.disk.current, data.disk.total);
|
||||
this.loads = data.loads.map(load => toFixed(load, 2));
|
||||
this.loads = data.loads.map(load => NumberFormatter.toFixed(load, 2));
|
||||
this.mem = new CurTotal(data.mem.current, data.mem.total);
|
||||
this.netIO = data.netIO;
|
||||
this.netTraffic = data.netTraffic;
|
||||
@@ -492,24 +515,11 @@
|
||||
|
||||
const backupModal = {
|
||||
visible: false,
|
||||
title: '',
|
||||
description: '',
|
||||
exportText: '',
|
||||
importText: '',
|
||||
show({
|
||||
title = '{{ i18n "pages.index.backupTitle" }}',
|
||||
description = '{{ i18n "pages.index.backupDescription" }}',
|
||||
exportText = '{{ i18n "pages.index.exportDatabase" }}',
|
||||
importText = '{{ i18n "pages.index.importDatabase" }}',
|
||||
}) {
|
||||
this.title = title;
|
||||
this.description = description;
|
||||
this.exportText = exportText;
|
||||
this.importText = importText;
|
||||
this.visible = true;
|
||||
show() {
|
||||
this.visible = true;
|
||||
},
|
||||
hide() {
|
||||
this.visible = false;
|
||||
this.visible = false;
|
||||
},
|
||||
};
|
||||
|
||||
@@ -605,12 +615,7 @@
|
||||
txtModal.show('config.json', JSON.stringify(msg.obj, null, 2), 'config.json');
|
||||
},
|
||||
openBackup() {
|
||||
backupModal.show({
|
||||
title: '{{ i18n "pages.index.backupTitle" }}',
|
||||
description: '{{ i18n "pages.index.backupDescription" }}',
|
||||
exportText: '{{ i18n "pages.index.exportDatabase" }}',
|
||||
importText: '{{ i18n "pages.index.importDatabase" }}',
|
||||
});
|
||||
backupModal.show();
|
||||
},
|
||||
exportDatabase() {
|
||||
window.location = basePath + 'server/getDb';
|
||||
|
||||
@@ -108,16 +108,17 @@
|
||||
</a-row>
|
||||
</a-card>
|
||||
<a-tabs default-active-key="1">
|
||||
<a-tab-pane key="1" tab='{{ i18n "pages.settings.panelSettings"}}'>
|
||||
<a-list item-layout="horizontal">
|
||||
<a-list-item>
|
||||
<a-row style="padding: 20px">
|
||||
<a-col :lg="24" :xl="12">
|
||||
<a-list-item-meta title='{{ i18n "pages.settings.remarkModel"}}'>
|
||||
<template slot="description">{{ i18n "pages.settings.sampleRemark"}}: <i>#[[ remarkSample ]]</i></template>
|
||||
</a-list-item-meta>
|
||||
</a-col>
|
||||
<a-col :lg="24" :xl="12">
|
||||
<a-tab-pane key="1" tab='{{ i18n "pages.settings.panelSettings" }}' style="padding-top: 20px;">
|
||||
<a-collapse>
|
||||
<a-collapse-panel header='{{ i18n "pages.xray.generalConfigs"}}'>
|
||||
<a-setting-list-item paddings="small">
|
||||
<template #title>
|
||||
{{ i18n "pages.settings.remarkModel"}}
|
||||
</template>
|
||||
<template #description>
|
||||
{{ i18n "pages.settings.sampleRemark"}}: <i>#[[ remarkSample ]]</i>
|
||||
</template>
|
||||
<template #control>
|
||||
<a-input-group style="width: 100%;">
|
||||
<a-select style="padding-right: .5rem; min-width: 80%; width: auto;"
|
||||
mode="multiple"
|
||||
@@ -129,298 +130,523 @@
|
||||
<a-select-option v-for="key in remarkSeparators" :value="key">[[ key ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-input-group>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-list-item>
|
||||
<setting-list-item type="text" title='{{ i18n "pages.settings.panelListeningIP"}}' desc='{{ i18n "pages.settings.panelListeningIPDesc"}}' v-model="allSetting.webListen"></setting-list-item>
|
||||
<setting-list-item type="text" title='{{ i18n "pages.settings.panelListeningDomain"}}' desc='{{ i18n "pages.settings.panelListeningDomainDesc"}}' v-model="allSetting.webDomain"></setting-list-item>
|
||||
<setting-list-item type="number" title='{{ i18n "pages.settings.panelPort"}}' desc='{{ i18n "pages.settings.panelPortDesc"}}' v-model="allSetting.webPort" :min="1" :max="65531"></setting-list-item>
|
||||
<setting-list-item type="text" title='{{ i18n "pages.settings.publicKeyPath"}}' desc='{{ i18n "pages.settings.publicKeyPathDesc"}}' v-model="allSetting.webCertFile"></setting-list-item>
|
||||
<setting-list-item type="text" title='{{ i18n "pages.settings.privateKeyPath"}}' desc='{{ i18n "pages.settings.privateKeyPathDesc"}}' v-model="allSetting.webKeyFile"></setting-list-item>
|
||||
<setting-list-item type="text" title='{{ i18n "pages.settings.panelUrlPath"}}' desc='{{ i18n "pages.settings.panelUrlPathDesc"}}' v-model="allSetting.webBasePath"></setting-list-item>
|
||||
<setting-list-item type="number" title='{{ i18n "pages.settings.sessionMaxAge" }}' desc='{{ i18n "pages.settings.sessionMaxAgeDesc" }}' v-model="allSetting.sessionMaxAge" :min="60"></setting-list-item>
|
||||
<setting-list-item type="number" title='{{ i18n "pages.settings.pageSize" }}' desc='{{ i18n "pages.settings.pageSizeDesc" }}' v-model="allSetting.pageSize" :min="0" :step="5"></setting-list-item>
|
||||
<setting-list-item type="number" title='{{ i18n "pages.settings.expireTimeDiff" }}' desc='{{ i18n "pages.settings.expireTimeDiffDesc" }}' v-model="allSetting.expireDiff" :min="0"></setting-list-item>
|
||||
<setting-list-item type="number" title='{{ i18n "pages.settings.trafficDiff" }}' desc='{{ i18n "pages.settings.trafficDiffDesc" }}' v-model="allSetting.trafficDiff" :min="0"></setting-list-item>
|
||||
<setting-list-item type="text" title='{{ i18n "pages.settings.timeZone"}}' desc='{{ i18n "pages.settings.timeZoneDesc"}}' v-model="allSetting.timeLocation"></setting-list-item>
|
||||
<a-list-item>
|
||||
<a-row style="padding: 20px">
|
||||
<a-col :lg="24" :xl="12">
|
||||
<a-list-item-meta title='{{ i18n "pages.settings.datepicker"}}'>
|
||||
<template slot="description">{{ i18n "pages.settings.datepickerDescription"}}</template>
|
||||
</a-list-item-meta>
|
||||
</a-col>
|
||||
<a-col :lg="24" :xl="12">
|
||||
<template>
|
||||
<a-select style="width: 100%" :dropdown-class-name="themeSwitcher.currentTheme" v-model="datepicker">
|
||||
<a-select-option v-for="item in datepickerList" :value="item.value">
|
||||
<span v-text="item.name"></span>
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</template>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-list-item>
|
||||
<a-list-item>
|
||||
<a-row style="padding: 20px">
|
||||
<a-col :lg="24" :xl="12">
|
||||
<a-list-item-meta title="Language"></a-list-item-meta>
|
||||
</a-col>
|
||||
<a-col :lg="24" :xl="12">
|
||||
<template>
|
||||
<a-select ref="selectLang"
|
||||
v-model="lang"
|
||||
@change="setLang(lang)"
|
||||
:dropdown-class-name="themeSwitcher.currentTheme"
|
||||
style="width: 100%">
|
||||
<a-select-option :value="l.value" :label="l.value" v-for="l in supportLangs">
|
||||
<span role="img" :aria-label="l.name" v-text="l.icon"></span> <span v-text="l.name"></span>
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</template>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-list-item>
|
||||
</a-list>
|
||||
</template>
|
||||
</a-setting-list-item>
|
||||
<a-setting-list-item paddings="small">
|
||||
<template #title>{{ i18n "pages.settings.panelListeningIP"}}</template>
|
||||
<template #description>{{ i18n "pages.settings.panelListeningIPDesc"}}</template>
|
||||
<template #control>
|
||||
<a-input type="text" v-model="allSetting.webListen"></a-input>
|
||||
</template>
|
||||
</a-setting-list-item>
|
||||
<a-setting-list-item paddings="small">
|
||||
<template #title>{{ i18n "pages.settings.panelListeningDomain"}}</template>
|
||||
<template #description>{{ i18n "pages.settings.panelListeningDomainDesc"}}</template>
|
||||
<template #control>
|
||||
<a-input type="text" v-model="allSetting.webDomain"></a-input>
|
||||
</template>
|
||||
</a-setting-list-item>
|
||||
<a-setting-list-item paddings="small">
|
||||
<template #title>{{ i18n "pages.settings.panelPort"}}</template>
|
||||
<template #description>{{ i18n "pages.settings.panelPortDesc"}}</template>
|
||||
<template #control>
|
||||
<a-input-number :min="1" :min="65531" v-model="allSetting.webPort" style="width: 100%;"></a-input>
|
||||
</template>
|
||||
</a-setting-list-item>
|
||||
<a-setting-list-item paddings="small">
|
||||
<template #title>{{ i18n "pages.settings.panelUrlPath"}}</template>
|
||||
<template #description>{{ i18n "pages.settings.panelUrlPathDesc"}}</template>
|
||||
<template #control>
|
||||
<a-input type="text" v-model="allSetting.webBasePath"></a-input>
|
||||
</template>
|
||||
</a-setting-list-item>
|
||||
<a-setting-list-item paddings="small">
|
||||
<template #title>{{ i18n "pages.settings.sessionMaxAge" }}</template>
|
||||
<template #description>{{ i18n "pages.settings.sessionMaxAgeDesc" }}</template>
|
||||
<template #control>
|
||||
<a-input-number :min="60" v-model="allSetting.sessionMaxAge" style="width: 100%;"></a-input>
|
||||
</template>
|
||||
</a-setting-list-item>
|
||||
<a-setting-list-item paddings="small">
|
||||
<template #title>{{ i18n "pages.settings.pageSize" }}</template>
|
||||
<template #description>{{ i18n "pages.settings.pageSizeDesc" }}</template>
|
||||
<template #control>
|
||||
<a-input-number :min="0" step="5" v-model="allSetting.pageSize" style="width: 100%;"></a-input>
|
||||
</template>
|
||||
</a-setting-list-item>
|
||||
<a-setting-list-item paddings="small">
|
||||
<template #title>{{ i18n "pages.settings.language"}}</template>
|
||||
<template #control>
|
||||
<a-select ref="selectLang"
|
||||
v-model="lang"
|
||||
@change="LanguageManager.setLanguage(lang)"
|
||||
:dropdown-class-name="themeSwitcher.currentTheme"
|
||||
style="width: 100%">
|
||||
<a-select-option :value="l.value" :label="l.value" v-for="l in LanguageManager.supportedLanguages">
|
||||
<span role="img" :aria-label="l.name" v-text="l.icon"></span> <span v-text="l.name"></span>
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</template>
|
||||
</a-setting-list-item>
|
||||
</a-collapse-panel>
|
||||
<a-collapse-panel header='{{ i18n "pages.settings.notifications" }}'>
|
||||
<a-setting-list-item paddings="small">
|
||||
<template #title>{{ i18n "pages.settings.expireTimeDiff" }}</template>
|
||||
<template #description>{{ i18n "pages.settings.expireTimeDiffDesc" }}</template>
|
||||
<template #control>
|
||||
<a-input-number :min="0" v-model="allSetting.expireDiff" style="width: 100%;"></a-input>
|
||||
</template>
|
||||
</a-setting-list-item>
|
||||
<a-setting-list-item paddings="small">
|
||||
<template #title>{{ i18n "pages.settings.trafficDiff" }}</template>
|
||||
<template #description>{{ i18n "pages.settings.trafficDiffDesc" }}</template>
|
||||
<template #control>
|
||||
<a-input-number :min="0" v-model="allSetting.trafficDiff" style="width: 100%;"></a-input>
|
||||
</template>
|
||||
</a-setting-list-item>
|
||||
</a-collapse-panel>
|
||||
<a-collapse-panel header='{{ i18n "pages.settings.certs" }}'>
|
||||
<a-setting-list-item paddings="small">
|
||||
<template #title>{{ i18n "pages.settings.publicKeyPath"}}</template>
|
||||
<template #description>{{ i18n "pages.settings.publicKeyPathDesc"}}</template>
|
||||
<template #control>
|
||||
<a-input type="text" v-model="allSetting.webCertFile"></a-input>
|
||||
</template>
|
||||
</a-setting-list-item>
|
||||
<a-setting-list-item paddings="small">
|
||||
<template #title>{{ i18n "pages.settings.privateKeyPath"}}</template>
|
||||
<template #description>{{ i18n "pages.settings.privateKeyPathDesc"}}</template>
|
||||
<template #control>
|
||||
<a-input type="text" v-model="allSetting.webKeyFile"></a-input>
|
||||
</template>
|
||||
</a-setting-list-item>
|
||||
</a-collapse-panel>
|
||||
<a-collapse-panel header='{{ i18n "pages.settings.externalTraffic" }}'>
|
||||
<a-setting-list-item paddings="small">
|
||||
<template #title>{{ i18n "pages.settings.externalTrafficInformEnable"}}</template>
|
||||
<template #description>{{ i18n "pages.settings.externalTrafficInformEnableDesc"}}</template>
|
||||
<template #control>
|
||||
<a-switch v-model="allSetting.externalTrafficInformEnable"></a-switch>
|
||||
</template>
|
||||
</a-setting-list-item>
|
||||
<a-setting-list-item paddings="small">
|
||||
<template #title>{{ i18n "pages.settings.externalTrafficInformURI"}}</template>
|
||||
<template #description>{{ i18n "pages.settings.externalTrafficInformURIDesc"}}</template>
|
||||
<template #control>
|
||||
<a-input type="text" placeholder="(http|https)://domain[:port]/path/" v-model="allSetting.externalTrafficInformURI"></a-input>
|
||||
</template>
|
||||
</a-setting-list-item>
|
||||
</a-collapse-panel>
|
||||
<a-collapse-panel header='{{ i18n "pages.settings.dateAndTime" }}'>
|
||||
<a-setting-list-item paddings="small">
|
||||
<template #title>{{ i18n "pages.settings.timeZone"}}</template>
|
||||
<template #description>{{ i18n "pages.settings.timeZoneDesc"}}</template>
|
||||
<template #control>
|
||||
<a-input type="text" v-model="allSetting.timeLocation"></a-input>
|
||||
</template>
|
||||
</a-setting-list-item>
|
||||
<a-setting-list-item paddings="small">
|
||||
<template #title>{{ i18n "pages.settings.datepicker"}}</template>
|
||||
<template #description>{{ i18n "pages.settings.datepickerDescription"}}</template>
|
||||
<template #control>
|
||||
<a-select style="width: 100%" :dropdown-class-name="themeSwitcher.currentTheme" v-model="datepicker">
|
||||
<a-select-option v-for="item in datepickerList" :value="item.value">
|
||||
<span v-text="item.name"></span>
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</template>
|
||||
</a-setting-list-item>
|
||||
</a-collapse-panel>
|
||||
</a-collapse>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="2" tab='{{ i18n "pages.settings.securitySettings"}}' style="padding: 20px;">
|
||||
<a-divider>{{ i18n "pages.settings.security.admin"}}</a-divider>
|
||||
<a-form layout="horizontal" :colon="false" style="float: left; margin-bottom: 2rem;" :label-col="{ md: {span:10} }" :wrapper-col="{ md: {span:14} }">
|
||||
<a-form-item label='{{ i18n "pages.settings.oldUsername"}}'>
|
||||
<a-input autocomplete="username" v-model="user.oldUsername"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.settings.currentPassword"}}'>
|
||||
<password-input autocomplete="current-password" v-model="user.oldPassword"></password-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.settings.newUsername"}}'>
|
||||
<a-input v-model="user.newUsername"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.settings.newPassword"}}'>
|
||||
<password-input autocomplete="new-password" v-model="user.newPassword"></password-input>
|
||||
</a-form-item>
|
||||
<a-form-item label=" ">
|
||||
<a-button type="primary" @click="updateUser">{{ i18n "confirm" }}</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<a-divider>{{ i18n "pages.settings.security.secret"}}</a-divider>
|
||||
<a-form style="padding: 0 20px;">
|
||||
<a-list-item>
|
||||
<a-row>
|
||||
<a-col :lg="24" :xl="12">
|
||||
<a-list-item-meta title='{{ i18n "pages.settings.security.loginSecurity" }}'
|
||||
description='{{ i18n "pages.settings.security.loginSecurityDesc" }}'>
|
||||
</a-list-item-meta>
|
||||
</a-col>
|
||||
<a-col :lg="24" :xl="12">
|
||||
<template>
|
||||
<a-switch @change="toggleToken(allSetting.secretEnable)" v-model="allSetting.secretEnable"></a-switch>
|
||||
<a-icon style="margin-left: 1rem;" v-if="allSetting.secretEnable" :spin="this.changeSecret" type="sync" @click="getNewSecret"></a-icon>
|
||||
</template>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-list-item>
|
||||
<a-list-item>
|
||||
<a-row>
|
||||
<a-col :lg="24" :xl="12">
|
||||
<a-list-item-meta title='{{ i18n "pages.settings.security.secretToken" }}'
|
||||
description='{{ i18n "pages.settings.security.secretTokenDesc" }}'>
|
||||
</a-list-item-meta>
|
||||
</a-col>
|
||||
<a-col :lg="24" :xl="12">
|
||||
<template>
|
||||
<a-textarea type="text" :disabled="!allSetting.secretEnable" v-model="user.loginSecret"></a-textarea>
|
||||
</template>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-list-item>
|
||||
<a-button type="primary" :loading="this.changeSecret" @click="updateSecret">{{ i18n "confirm" }}</a-button>
|
||||
</a-form>
|
||||
<a-tab-pane key="2" tab='{{ i18n "pages.settings.securitySettings" }}' style="padding-top: 20px;">
|
||||
<a-collapse>
|
||||
<a-collapse-panel header='{{ i18n "pages.settings.security.admin"}}'>
|
||||
<a-setting-list-item paddings="small">
|
||||
<template #title>{{ i18n "pages.settings.oldUsername"}}</template>
|
||||
<template #control>
|
||||
<a-input autocomplete="username" v-model="user.oldUsername"></a-input>
|
||||
</template>
|
||||
</a-setting-list-item>
|
||||
<a-setting-list-item paddings="small">
|
||||
<template #title>{{ i18n "pages.settings.currentPassword"}}</template>
|
||||
<template #control>
|
||||
<a-password-input autocomplete="current-password" v-model="user.oldPassword"></a-password-input>
|
||||
</template>
|
||||
</a-setting-list-item>
|
||||
<a-setting-list-item paddings="small">
|
||||
<template #title>{{ i18n "pages.settings.newUsername"}}</template>
|
||||
<template #control>
|
||||
<a-input v-model="user.newUsername"></a-input>
|
||||
</template>
|
||||
</a-setting-list-item>
|
||||
<a-setting-list-item paddings="small">
|
||||
<template #title>{{ i18n "pages.settings.newPassword"}}</template>
|
||||
<template #control>
|
||||
<a-password-input autocomplete="new-password" v-model="user.newPassword"></a-password-input>
|
||||
</template>
|
||||
</a-setting-list-item>
|
||||
<a-list-item>
|
||||
<a-space direction="horizontal" style="padding: 0 20px;">
|
||||
<a-button type="primary" @click="updateUser">{{ i18n "confirm" }}</a-button>
|
||||
</a-space>
|
||||
</a-list-item>
|
||||
</a-collapse-panel>
|
||||
<a-collapse-panel header='{{ i18n "pages.settings.security.secret"}}'>
|
||||
<a-setting-list-item paddings="small">
|
||||
<template #title>{{ i18n "pages.settings.security.loginSecurity" }}</template>
|
||||
<template #description>{{ i18n "pages.settings.security.loginSecurityDesc" }}</template>
|
||||
<template #control>
|
||||
<a-switch @change="toggleToken(allSetting.secretEnable)" v-model="allSetting.secretEnable"></a-switch>
|
||||
<a-icon style="margin-left: 1rem;" v-if="allSetting.secretEnable" :spin="this.changeSecret" type="sync" @click="getNewSecret"></a-icon>
|
||||
</template>
|
||||
</a-setting-list-item>
|
||||
<a-setting-list-item paddings="small">
|
||||
<template #title>{{ i18n "pages.settings.security.secretToken" }}</template>
|
||||
<template #description>{{ i18n "pages.settings.security.secretTokenDesc" }}</template>
|
||||
<template #control>
|
||||
<a-textarea type="text" :disabled="!allSetting.secretEnable" v-model="user.loginSecret"></a-textarea>
|
||||
</template>
|
||||
</a-setting-list-item>
|
||||
<a-list-item>
|
||||
<a-space direction="horizontal" style="padding: 0 20px;">
|
||||
<a-button type="primary" :loading="this.changeSecret" @click="updateSecret">{{ i18n "confirm" }}</a-button>
|
||||
</a-space>
|
||||
</a-list-item>
|
||||
</a-collapse-panel>
|
||||
</a-collapse>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="3" tab='{{ i18n "pages.settings.TGBotSettings"}}'>
|
||||
<a-list item-layout="horizontal">
|
||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.telegramBotEnable" }}' desc='{{ i18n "pages.settings.telegramBotEnableDesc" }}' v-model="allSetting.tgBotEnable"></setting-list-item>
|
||||
<setting-list-item type="text" title='{{ i18n "pages.settings.telegramToken"}}' desc='{{ i18n "pages.settings.telegramTokenDesc"}}' v-model="allSetting.tgBotToken"></setting-list-item>
|
||||
<setting-list-item type="text" title='{{ i18n "pages.settings.telegramChatId"}}' desc='{{ i18n "pages.settings.telegramChatIdDesc"}}' v-model="allSetting.tgBotChatId"></setting-list-item>
|
||||
<setting-list-item type="text" title='{{ i18n "pages.settings.telegramNotifyTime"}}' desc='{{ i18n "pages.settings.telegramNotifyTimeDesc"}}' v-model="allSetting.tgRunTime"></setting-list-item>
|
||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.tgNotifyBackup" }}' desc='{{ i18n "pages.settings.tgNotifyBackupDesc" }}' v-model="allSetting.tgBotBackup"></setting-list-item>
|
||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.tgNotifyLogin" }}' desc='{{ i18n "pages.settings.tgNotifyLoginDesc" }}' v-model="allSetting.tgBotLoginNotify"></setting-list-item>
|
||||
<setting-list-item type="number" title='{{ i18n "pages.settings.tgNotifyCpu" }}' desc='{{ i18n "pages.settings.tgNotifyCpuDesc" }}' v-model="allSetting.tgCpu" :min="0" :max="100"></setting-list-item>
|
||||
<setting-list-item type="text" title='{{ i18n "pages.settings.telegramProxy"}}' desc='{{ i18n "pages.settings.telegramProxyDesc"}}' v-model="allSetting.tgBotProxy" placeholder="socks5://user:pass@host:port"></setting-list-item>
|
||||
<setting-list-item type="text" title='{{ i18n "pages.settings.telegramAPIServer"}}' desc='{{ i18n "pages.settings.telegramAPIServerDesc"}}' v-model="allSetting.tgBotAPIServer" placeholder="https://api.example.com"></setting-list-item>
|
||||
<a-list-item>
|
||||
<a-row style="padding: 20px">
|
||||
<a-col :lg="24" :xl="12">
|
||||
<a-list-item-meta title="Telegram Bot Language" />
|
||||
</a-col>
|
||||
<a-col :lg="24" :xl="12">
|
||||
<template>
|
||||
<a-select ref="selectBotLang" v-model="allSetting.tgLang" :dropdown-class-name="themeSwitcher.currentTheme" style="width: 100%">
|
||||
<a-select-option :value="l.value" :label="l.value" v-for="l in supportLangs">
|
||||
<span role="img" :aria-label="l.name" v-text="l.icon"></span> <span v-text="l.name"></span>
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</template>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-list-item>
|
||||
</a-list>
|
||||
<a-tab-pane key="3" tab='{{ i18n "pages.settings.TGBotSettings" }}' style="padding-top: 20px;">
|
||||
<a-collapse>
|
||||
<a-collapse-panel header='{{ i18n "pages.xray.generalConfigs"}}'>
|
||||
<a-setting-list-item paddings="small">
|
||||
<template #title>{{ i18n "pages.settings.telegramBotEnable" }}</template>
|
||||
<template #description>{{ i18n "pages.settings.telegramBotEnableDesc" }}</template>
|
||||
<template #control>
|
||||
<a-switch v-model="allSetting.tgBotEnable"></a-switch>
|
||||
</template>
|
||||
</a-setting-list-item>
|
||||
<a-setting-list-item paddings="small">
|
||||
<template #title>{{ i18n "pages.settings.telegramToken"}}</template>
|
||||
<template #description>{{ i18n "pages.settings.telegramTokenDesc"}}</template>
|
||||
<template #control>
|
||||
<a-input type="text" v-model="allSetting.tgBotToken"></a-input>
|
||||
</template>
|
||||
</a-setting-list-item>
|
||||
<a-setting-list-item paddings="small">
|
||||
<template #title>{{ i18n "pages.settings.telegramChatId"}}</template>
|
||||
<template #description>{{ i18n "pages.settings.telegramChatIdDesc"}}</template>
|
||||
<template #control>
|
||||
<a-input type="text" v-model="allSetting.tgBotChatId"></a-input>
|
||||
</template>
|
||||
</a-setting-list-item>
|
||||
<a-setting-list-item paddings="small">
|
||||
<template #title>{{ i18n "pages.settings.telegramBotLanguage"}}</template>
|
||||
<template #control>
|
||||
<a-select ref="selectBotLang" v-model="allSetting.tgLang" :dropdown-class-name="themeSwitcher.currentTheme" style="width: 100%">
|
||||
<a-select-option :value="l.value" :label="l.value" v-for="l in LanguageManager.supportedLanguages">
|
||||
<span role="img" :aria-label="l.name" v-text="l.icon"></span> <span v-text="l.name"></span>
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</template>
|
||||
</a-setting-list-item>
|
||||
</a-collapse-panel>
|
||||
<a-collapse-panel header='{{ i18n "pages.settings.notifications" }}'>
|
||||
<a-setting-list-item paddings="small">
|
||||
<template #title>{{ i18n "pages.settings.telegramNotifyTime"}}</template>
|
||||
<template #description>{{ i18n "pages.settings.telegramNotifyTimeDesc"}}</template>
|
||||
<template #control>
|
||||
<a-input type="text" v-model="allSetting.tgRunTime"></a-input>
|
||||
</template>
|
||||
</a-setting-list-item>
|
||||
<a-setting-list-item paddings="small">
|
||||
<template #title>{{ i18n "pages.settings.tgNotifyBackup" }}</template>
|
||||
<template #description>{{ i18n "pages.settings.tgNotifyBackupDesc" }}</template>
|
||||
<template #control>
|
||||
<a-switch v-model="allSetting.tgBotBackup"></a-switch>
|
||||
</template>
|
||||
</a-setting-list-item>
|
||||
<a-setting-list-item paddings="small">
|
||||
<template #title>{{ i18n "pages.settings.tgNotifyLogin" }}</template>
|
||||
<template #description>{{ i18n "pages.settings.tgNotifyLoginDesc" }}</template>
|
||||
<template #control>
|
||||
<a-switch v-model="allSetting.tgBotLoginNotify"></a-switch>
|
||||
</template>
|
||||
</a-setting-list-item>
|
||||
<a-setting-list-item paddings="small">
|
||||
<template #title>{{ i18n "pages.settings.tgNotifyCpu" }}</template>
|
||||
<template #description>{{ i18n "pages.settings.tgNotifyCpuDesc" }}</template>
|
||||
<template #control>
|
||||
<a-input-number :min="0" :min="100" v-model="allSetting.tgCpu"></a-switch>
|
||||
</template>
|
||||
</a-setting-list-item>
|
||||
</a-collapse-panel>
|
||||
<a-collapse-panel header='{{ i18n "pages.settings.proxyAndServer" }}'>
|
||||
<a-setting-list-item paddings="small">
|
||||
<template #title>{{ i18n "pages.settings.telegramProxy"}}</template>
|
||||
<template #description>{{ i18n "pages.settings.telegramProxyDesc"}}</template>
|
||||
<template #control>
|
||||
<a-input type="text" placeholder="socks5://user:pass@host:port" v-model="allSetting.tgBotProxy"></a-input>
|
||||
</template>
|
||||
</a-setting-list-item>
|
||||
<a-setting-list-item paddings="small">
|
||||
<template #title>{{ i18n "pages.settings.telegramAPIServer"}}</template>
|
||||
<template #description>{{ i18n "pages.settings.telegramAPIServerDesc"}}</template>
|
||||
<template #control>
|
||||
<a-input type="text" placeholder="https://api.example.com" v-model="allSetting.tgBotAPIServer"></a-input>
|
||||
</template>
|
||||
</a-setting-list-item>
|
||||
</a-collapse-panel>
|
||||
</a-collapse>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="4" tab='{{ i18n "pages.settings.subSettings" }}'>
|
||||
<a-list item-layout="horizontal">
|
||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.subEnable"}}' desc='{{ i18n "pages.settings.subEnableDesc"}}' v-model="allSetting.subEnable"></setting-list-item>
|
||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.subSyncEnable"}}' desc='{{ i18n "pages.settings.subSyncEnableDesc"}}' v-model="allSetting.subSyncEnable"></setting-list-item>
|
||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.subEncrypt"}}' desc='{{ i18n "pages.settings.subEncryptDesc"}}' v-model="allSetting.subEncrypt"></setting-list-item>
|
||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.subShowInfo"}}' desc='{{ i18n "pages.settings.subShowInfoDesc"}}' v-model="allSetting.subShowInfo"></setting-list-item>
|
||||
<setting-list-item type="text" title='{{ i18n "pages.settings.subListen"}}' desc='{{ i18n "pages.settings.subListenDesc"}}' v-model="allSetting.subListen"></setting-list-item>
|
||||
<setting-list-item type="text" title='{{ i18n "pages.settings.subDomain"}}' desc='{{ i18n "pages.settings.subDomainDesc"}}' v-model="allSetting.subDomain"></setting-list-item>
|
||||
<setting-list-item type="number" title='{{ i18n "pages.settings.subPort"}}' desc='{{ i18n "pages.settings.subPortDesc"}}' v-model.number="allSetting.subPort" :min="1" :max="65531"></setting-list-item>
|
||||
<setting-list-item type="text" title='{{ i18n "pages.settings.subPath"}}' desc='{{ i18n "pages.settings.subPathDesc"}}' v-model="allSetting.subPath"></setting-list-item>
|
||||
<setting-list-item type="text" title='{{ i18n "pages.settings.subCertPath"}}' desc='{{ i18n "pages.settings.subCertPathDesc"}}' v-model="allSetting.subCertFile"></setting-list-item>
|
||||
<setting-list-item type="text" title='{{ i18n "pages.settings.subKeyPath"}}' desc='{{ i18n "pages.settings.subKeyPathDesc"}}' v-model="allSetting.subKeyFile"></setting-list-item>
|
||||
<setting-list-item type="text" title='{{ i18n "pages.settings.subURI"}}' desc='{{ i18n "pages.settings.subURIDesc"}}' v-model="allSetting.subURI" placeholder="(http|https)://domain[:port]/path/"></setting-list-item>
|
||||
<setting-list-item type="number" title='{{ i18n "pages.settings.subUpdates"}}' desc='{{ i18n "pages.settings.subUpdatesDesc"}}' v-model="allSetting.subUpdates" :min="1"></setting-list-item>
|
||||
</a-list>
|
||||
<a-tab-pane key="4" tab='{{ i18n "pages.settings.subSettings" }}' style="padding-top: 20px;">
|
||||
<a-collapse>
|
||||
<a-collapse-panel header='{{ i18n "pages.xray.generalConfigs"}}'>
|
||||
<a-setting-list-item paddings="small">
|
||||
<template #title>{{ i18n "pages.settings.subEnable"}}</template>
|
||||
<template #description>{{ i18n "pages.settings.subEnableDesc"}}</template>
|
||||
<template #control>
|
||||
<a-switch v-model="allSetting.subEnable"></a-switch>
|
||||
</template>
|
||||
</a-setting-list-item>
|
||||
<a-setting-list-item paddings="small">
|
||||
<template #title>{{ i18n "pages.settings.subListen"}}</template>
|
||||
<template #description>{{ i18n "pages.settings.subListenDesc"}}</template>
|
||||
<template #control>
|
||||
<a-input type="text" v-model="allSetting.subListen"></a-input>
|
||||
</template>
|
||||
</a-setting-list-item>
|
||||
<a-setting-list-item paddings="small">
|
||||
<template #title>{{ i18n "pages.settings.subDomain"}}</template>
|
||||
<template #description>{{ i18n "pages.settings.subDomainDesc"}}</template>
|
||||
<template #control>
|
||||
<a-input type="text" v-model="allSetting.subDomain"></a-input>
|
||||
</template>
|
||||
</a-setting-list-item>
|
||||
<a-setting-list-item paddings="small">
|
||||
<template #title>{{ i18n "pages.settings.subPort"}}</template>
|
||||
<template #description>{{ i18n "pages.settings.subPortDesc"}}</template>
|
||||
<template #control>
|
||||
<a-input-number v-model="allSetting.subPort" :min="1" :min="65531" style="width: 100%;"></a-input-number>
|
||||
</template>
|
||||
</a-setting-list-item>
|
||||
<a-setting-list-item paddings="small">
|
||||
<template #title>{{ i18n "pages.settings.subPath"}}</template>
|
||||
<template #description>{{ i18n "pages.settings.subPathDesc"}}</template>
|
||||
<template #control>
|
||||
<a-input type="text" v-model="allSetting.subPath"></a-input>
|
||||
</template>
|
||||
</a-setting-list-item>
|
||||
<a-setting-list-item paddings="small">
|
||||
<template #title>{{ i18n "pages.settings.subURI"}}</template>
|
||||
<template #description>{{ i18n "pages.settings.subURIDesc"}}</template>
|
||||
<template #control>
|
||||
<a-input type="text" placeholder="(http|https)://domain[:port]/path/" v-model="allSetting.subURI"></a-input>
|
||||
</template>
|
||||
</a-setting-list-item>
|
||||
</a-collapse-panel>
|
||||
<a-collapse-panel header='{{ i18n "pages.settings.information" }}'>
|
||||
<a-setting-list-item paddings="small">
|
||||
<template #title>{{ i18n "pages.settings.subEncrypt"}}</template>
|
||||
<template #description>{{ i18n "pages.settings.subEncryptDesc"}}</template>
|
||||
<template #control>
|
||||
<a-switch v-model="allSetting.subEncrypt"></a-switch>
|
||||
</template>
|
||||
</a-setting-list-item>
|
||||
<a-setting-list-item paddings="small">
|
||||
<template #title>{{ i18n "pages.settings.subShowInfo"}}</template>
|
||||
<template #description>{{ i18n "pages.settings.subShowInfoDesc"}}</template>
|
||||
<template #control>
|
||||
<a-switch v-model="allSetting.subShowInfo"></a-switch>
|
||||
</template>
|
||||
</a-setting-list-item>
|
||||
</a-collapse-panel>
|
||||
<a-collapse-panel header='{{ i18n "pages.settings.certs" }}'>
|
||||
<a-setting-list-item paddings="small">
|
||||
<template #title>{{ i18n "pages.settings.subCertPath"}}</template>
|
||||
<template #description>{{ i18n "pages.settings.subCertPathDesc"}}</template>
|
||||
<template #control>
|
||||
<a-input type="text" v-model="allSetting.subCertFile"></a-input>
|
||||
</template>
|
||||
</a-setting-list-item>
|
||||
<a-setting-list-item paddings="small">
|
||||
<template #title>{{ i18n "pages.settings.subKeyPath"}}</template>
|
||||
<template #description>{{ i18n "pages.settings.subKeyPathDesc"}}</template>
|
||||
<template #control>
|
||||
<a-input type="text" v-model="allSetting.subKeyFile"></a-input>
|
||||
</template>
|
||||
</a-setting-list-item>
|
||||
</a-collapse-panel>
|
||||
<a-collapse-panel header='{{ i18n "pages.settings.intervals"}}'>
|
||||
<a-setting-list-item paddings="small">
|
||||
<template #title>{{ i18n "pages.settings.subUpdates"}}</template>
|
||||
<template #description>{{ i18n "pages.settings.subUpdatesDesc"}}</template>
|
||||
<template #control>
|
||||
<a-input-number :min="1" v-model="allSetting.subUpdates" style="width: 100%;"></a-input-number>
|
||||
</template>
|
||||
</a-setting-list-item>
|
||||
</a-collapse-panel>
|
||||
</a-collapse>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="5" tab='{{ i18n "pages.settings.subSettings" }} Json' v-if="allSetting.subEnable">
|
||||
<a-list item-layout="horizontal">
|
||||
<setting-list-item type="text" title='{{ i18n "pages.settings.subPath"}}' desc='{{ i18n "pages.settings.subPathDesc"}}' v-model="allSetting.subJsonPath"></setting-list-item>
|
||||
<setting-list-item type="text" title='{{ i18n "pages.settings.subURI"}}' desc='{{ i18n "pages.settings.subURIDesc"}}' v-model="allSetting.subJsonURI" placeholder="(http|https)://domain[:port]/path/"></setting-list-item>
|
||||
<a-list-item style="padding: 20px">
|
||||
<a-row>
|
||||
<a-col :lg="24" :xl="12">
|
||||
<a-list-item-meta title='{{ i18n "pages.settings.fragment"}}'>
|
||||
<template slot="description">{{ i18n "pages.settings.fragmentDesc"}}</template>
|
||||
</a-list-item-meta>
|
||||
</a-col>
|
||||
<a-col :lg="24" :xl="12">
|
||||
<a-tab-pane key="5" tab='{{ i18n "pages.settings.subSettings" }} Json' v-if="allSetting.subEnable" style="padding-top: 20px;">
|
||||
<a-collapse>
|
||||
<a-collapse-panel header='{{ i18n "pages.xray.generalConfigs"}}'>
|
||||
<a-setting-list-item paddings="small">
|
||||
<template #title>{{ i18n "pages.settings.subPath"}}</template>
|
||||
<template #description>{{ i18n "pages.settings.subPathDesc"}}</template>
|
||||
<template #control>
|
||||
<a-input type="text" v-model="allSetting.subJsonPath"></a-input>
|
||||
</template>
|
||||
</a-setting-list-item>
|
||||
<a-setting-list-item paddings="small">
|
||||
<template #title>{{ i18n "pages.settings.subURI"}}</template>
|
||||
<template #description>{{ i18n "pages.settings.subURIDesc"}}</template>
|
||||
<template #control>
|
||||
<a-input type="text" placeholder="(http|https)://domain[:port]/path/" v-model="allSetting.subJsonURI"></a-input>
|
||||
</template>
|
||||
</a-setting-list-item>
|
||||
</a-collapse-panel>
|
||||
<a-collapse-panel header='{{ i18n "pages.settings.fragment"}}'>
|
||||
<a-setting-list-item paddings="small">
|
||||
<template #title>{{ i18n "pages.settings.fragment"}}</template>
|
||||
<template #description>{{ i18n "pages.settings.fragmentDesc"}}</template>
|
||||
<template #control>
|
||||
<a-switch v-model="fragment"></a-switch>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-collapse v-if="fragment" style="margin-top: 14px;">
|
||||
<a-collapse-panel header='{{ i18n "pages.settings.fragmentSett"}}' v-if="fragment">
|
||||
<setting-list-item style="padding: 10px 20px" type="text" title='Packets' v-model="fragmentPackets" placeholder="1-1 | 1-3 | tlshello | ..."></setting-list-item>
|
||||
<setting-list-item style="padding: 10px 20px" type="text" title='Length' v-model="fragmentLength" placeholder="100-200"></setting-list-item>
|
||||
<setting-list-item style="padding: 10px 20px" type="text" title='Interval' v-model="fragmentInterval" placeholder="10-20"></setting-list-item>
|
||||
</a-collapse-panel>
|
||||
</a-collapse>
|
||||
</a-list-item>
|
||||
<a-list-item style="padding: 20px">
|
||||
<a-row>
|
||||
<a-col :lg="24" :xl="12">
|
||||
<a-list-item-meta title='Noises'>
|
||||
<template slot="description">{{ i18n "pages.settings.noisesDesc"}}</template>
|
||||
</a-list-item-meta>
|
||||
</a-col>
|
||||
<a-col :lg="24" :xl="12">
|
||||
</template>
|
||||
</a-setting-list-item>
|
||||
<a-list-item v-if="fragment" style="padding: 10px 20px;">
|
||||
<a-collapse>
|
||||
<a-collapse-panel header='{{ i18n "pages.settings.fragmentSett"}}' v-if="fragment">
|
||||
<a-setting-list-item paddings="small">
|
||||
<template #title>Packets</template>
|
||||
<template #control>
|
||||
<a-input type="text" v-model="fragmentPackets" placeholder="1-1 | 1-3 | tlshello | ..."></a-input>
|
||||
</template>
|
||||
</a-setting-list-item>
|
||||
<a-setting-list-item paddings="small">
|
||||
<template #title>Length</template>
|
||||
<template #control>
|
||||
<a-input type="text" v-model="fragmentLength" placeholder="100-200"></a-input>
|
||||
</template>
|
||||
</a-setting-list-item>
|
||||
<a-setting-list-item paddings="small">
|
||||
<template #title>Interval</template>
|
||||
<template #control>
|
||||
<a-input type="text" v-model="fragmentInterval" placeholder="10-20"></a-input>
|
||||
</template>
|
||||
</a-setting-list-item>
|
||||
</a-collapse-panel>
|
||||
</a-collapse>
|
||||
</a-list-item>
|
||||
</a-collapse-panel>
|
||||
<a-collapse-panel header="Noises">
|
||||
<a-setting-list-item paddings="small">
|
||||
<template #title>Noises</template>
|
||||
<template #description>{{ i18n "pages.settings.noisesDesc"}}</template>
|
||||
<template #control>
|
||||
<a-switch v-model="noises"></a-switch>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-collapse v-if="noises" style="margin-top: 14px;">
|
||||
<a-collapse-panel v-for="(noise, index) in noisesArray" :key="index" :header="`Noise ${index + 1}`">
|
||||
<a-list-item style="padding: 10px 20px">
|
||||
<a-row>
|
||||
<a-col :lg="24" :xl="12">
|
||||
<a-list-item-meta title='Type'></a-list-item-meta>
|
||||
</a-col>
|
||||
<a-col :lg="24" :xl="12">
|
||||
</template>
|
||||
</a-setting-list-item>
|
||||
<a-list-item v-if="noises" style="padding: 10px 20px;">
|
||||
<a-collapse>
|
||||
<a-collapse-panel v-for="(noise, index) in noisesArray" :key="index" :header="`Noise №${index + 1}`">
|
||||
<a-setting-list-item paddings="small">
|
||||
<template #title>Type</template>
|
||||
<template #control>
|
||||
<a-select :value="noise.type" style="width: 100%" :dropdown-class-name="themeSwitcher.currentTheme"
|
||||
@change="(value) => updateNoiseType(index, value)">
|
||||
<a-select-option :value="p" :label="p" v-for="p in ['rand', 'base64', 'str']" :key="p">
|
||||
<a-select-option :value="p" :label="p" v-for="p in ['rand', 'base64', 'str', 'hex']" :key="p">
|
||||
[[ p ]] </a-select-option>
|
||||
</a-select>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-list-item>
|
||||
<setting-list-item style="padding: 10px 20px" type="text" title='Packet' :value="noise.packet"
|
||||
@input="(value) => updateNoisePacket(index, value)" placeholder="5-10"></setting-list-item>
|
||||
<setting-list-item style="padding: 10px 20px" type="text" title='Delay (ms)' :value="noise.delay"
|
||||
@input="(value) => updateNoiseDelay(index, value)" placeholder="10-20"></setting-list-item>
|
||||
<a-button v-if="noisesArray.length > 1" type="danger" @click="removeNoise(index)">Remove</a-button>
|
||||
</a-collapse-panel>
|
||||
</a-collapse>
|
||||
<a-button v-if="noises" type="primary" @click="addNoise" style="margin-top: 10px">Add Noise</a-button>
|
||||
</a-list-item>
|
||||
<a-list-item style="padding: 20px">
|
||||
<a-row>
|
||||
<a-col :lg="24" :xl="12">
|
||||
<a-list-item-meta title='{{ i18n "pages.settings.mux"}}'>
|
||||
<template slot="description">{{ i18n "pages.settings.muxDesc"}}</template>
|
||||
</a-list-item-meta>
|
||||
</a-col>
|
||||
<a-col :lg="24" :xl="12">
|
||||
</template>
|
||||
</a-setting-list-item>
|
||||
<a-setting-list-item paddings="small">
|
||||
<template #title>Packet</template>
|
||||
<template #control>
|
||||
<a-input type="text" :value="noise.packet" @input="(value) => updateNoisePacket(index, event.target.value)" placeholder="5-10"></a-input>
|
||||
</template>
|
||||
</a-setting-list-item>
|
||||
<a-setting-list-item paddings="small">
|
||||
<template #title>Delay (ms)</template>
|
||||
<template #control>
|
||||
<a-input type="text" :value="noise.delay" @input="(value) => updateNoiseDelay(index, event.target.value)" placeholder="10-20"></a-input>
|
||||
</template>
|
||||
</a-setting-list-item>
|
||||
<a-space direction="horizontal" style="padding: 10px 20px;">
|
||||
<a-button v-if="noisesArray.length > 1" type="danger" @click="removeNoise(index)">Remove</a-button>
|
||||
</a-space>
|
||||
</a-collapse-panel>
|
||||
</a-collapse>
|
||||
<a-button v-if="noises" type="primary" @click="addNoise" style="margin-top: 10px">Add Noise</a-button>
|
||||
</a-list-item>
|
||||
</a-collapse-panel>
|
||||
<a-collapse-panel header='{{ i18n "pages.settings.mux"}}'>
|
||||
<a-setting-list-item paddings="small">
|
||||
<template #title>{{ i18n "pages.settings.mux"}}</template>
|
||||
<template #description>{{ i18n "pages.settings.muxDesc"}}</template>
|
||||
<template #control>
|
||||
<a-switch v-model="enableMux"></a-switch>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-collapse v-if="enableMux" style="margin-top: 14px;">
|
||||
<a-collapse-panel header='{{ i18n "pages.settings.muxSett"}}'>
|
||||
<setting-list-item style="padding: 10px 20px" type="number" title='Concurrency' v-model="muxConcurrency" :min="-1" :max="1024"></setting-list-item>
|
||||
<setting-list-item style="padding: 10px 20px" type="number" title='xudp Concurrency' v-model="muxXudpConcurrency" :min="-1" :max="1024"></setting-list-item>
|
||||
<a-list-item style="padding: 10px 20px">
|
||||
<a-row>
|
||||
<a-col :lg="24" :xl="12">
|
||||
<a-list-item-meta title='xudp UDP 443'></a-list-item-meta>
|
||||
</a-col>
|
||||
<a-col :lg="24" :xl="12">
|
||||
</template>
|
||||
</a-setting-list-item>
|
||||
<a-list-item v-if="enableMux" style="padding: 10px 20px;">
|
||||
<a-collapse>
|
||||
<a-collapse-panel header='{{ i18n "pages.settings.muxSett"}}'>
|
||||
<a-setting-list-item paddings="small">
|
||||
<template #title>Concurrency</template>
|
||||
<template #control>
|
||||
<a-input-number v-model="muxConcurrency" :min="-1" :max="1024" style="width: 100%;"></a-input-number>
|
||||
</template>
|
||||
</a-setting-list-item>
|
||||
<a-setting-list-item paddings="small">
|
||||
<template #title>xudp Concurrency</template>
|
||||
<template #control>
|
||||
<a-input-number v-model="muxXudpConcurrency" :min="-1" :max="1024" style="width: 100%;"></a-input-number>
|
||||
</template>
|
||||
</a-setting-list-item>
|
||||
<a-setting-list-item paddings="small">
|
||||
<template #title>xudp UDP 443</template>
|
||||
<template #control>
|
||||
<a-select v-model="muxXudpProxyUDP443" style="width: 100%" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option :value="p" :label="p" v-for="p in ['reject', 'allow', 'skip']"> [[ p ]] </a-select-option>
|
||||
</a-select>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-list-item>
|
||||
</a-collapse-panel>
|
||||
</a-collapse>
|
||||
</a-list-item>
|
||||
<a-list-item style="padding: 20px">
|
||||
<a-row>
|
||||
<a-col :lg="24" :xl="12">
|
||||
<a-list-item-meta title='{{ i18n "pages.settings.direct"}}'>
|
||||
<template slot="description">{{ i18n "pages.settings.directDesc"}}</template>
|
||||
</a-list-item-meta>
|
||||
</a-col>
|
||||
<a-col :lg="24" :xl="12">
|
||||
</template>
|
||||
</a-setting-list-item>
|
||||
</a-collapse-panel>
|
||||
</a-collapse>
|
||||
</a-list-item>
|
||||
</a-collapse-panel>
|
||||
<a-collapse-panel header='{{ i18n "pages.settings.direct" }}'>
|
||||
<a-setting-list-item paddings="small">
|
||||
<template #title>{{ i18n "pages.settings.direct"}}</template>
|
||||
<template #description>{{ i18n "pages.settings.directDesc"}}</template>
|
||||
<template #control>
|
||||
<a-switch v-model="enableDirect"></a-switch>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-collapse v-if="enableDirect" style="margin-top: 14px;">
|
||||
<a-collapse-panel header='{{ i18n "pages.settings.direct"}}'>
|
||||
<a-list-item>
|
||||
<a-row style="padding: 0 20px">
|
||||
<a-col :lg="24" :xl="12">
|
||||
<a-list-item-meta
|
||||
title='{{ i18n "pages.xray.directips" }}'/>
|
||||
</a-col>
|
||||
<a-col :lg="24" :xl="12">
|
||||
<a-select mode="tags" style="width: 100%"
|
||||
v-model="directIPs"
|
||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option :value="p.value" :label="p.label"
|
||||
v-for="p in directIPsOptions"> [[ p.label ]]
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-list-item>
|
||||
<a-list-item>
|
||||
<a-row style="padding: 0 20px">
|
||||
<a-col :lg="24" :xl="12">
|
||||
<a-list-item-meta
|
||||
title='{{ i18n "pages.xray.directdomains" }}'/>
|
||||
</a-col>
|
||||
<a-col :lg="24" :xl="12">
|
||||
<a-select mode="tags" style="width: 100%"
|
||||
v-model="directDomains"
|
||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option :value="p.value" :label="p.label"
|
||||
v-for="p in diretDomainsOptions"> [[ p.label ]]
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-list-item>
|
||||
</a-collapse-panel>
|
||||
</a-collapse>
|
||||
</a-list-item>
|
||||
</a-list>
|
||||
</template>
|
||||
</a-setting-list-item>
|
||||
<a-list-item v-if="enableDirect" style="padding: 10px 20px;">
|
||||
<a-collapse>
|
||||
<a-collapse-panel header='{{ i18n "pages.settings.direct"}}'>
|
||||
<a-setting-list-item paddings="small">
|
||||
<template #title>{{ i18n "pages.xray.directips" }}</template>
|
||||
<template #control>
|
||||
<a-select mode="tags" style="width: 100%" v-model="directIPs" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option :value="p.value" :label="p.label" v-for="p in directIPsOptions">[[ p.label ]]</a-select-option>
|
||||
</a-select>
|
||||
</template>
|
||||
</a-setting-list-item>
|
||||
<a-setting-list-item paddings="small">
|
||||
<template #title>{{ i18n "pages.xray.directdomains" }}</template>
|
||||
<template #control>
|
||||
<a-select mode="tags" style="width: 100%" v-model="directDomains" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option :value="p.value" :label="p.label" v-for="p in diretDomainsOptions">[[ p.label ]]</a-select-option>
|
||||
</a-select>
|
||||
</template>
|
||||
</a-setting-list-item>
|
||||
</a-collapse-panel>
|
||||
</a-collapse>
|
||||
</a-list-item>
|
||||
</a-collapse-panel>
|
||||
</a-collapse>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</a-space>
|
||||
@@ -446,7 +672,7 @@
|
||||
allSetting: new AllSetting(),
|
||||
saveBtnDisable: true,
|
||||
user: {},
|
||||
lang: getLang(),
|
||||
lang: LanguageManager.getLanguage(),
|
||||
remarkModels: { i: 'Inbound', e: 'Email', o: 'Other' },
|
||||
remarkSeparators: [' ', '-', '_', '@', ':', '~', '|', ',', '.', '/'],
|
||||
datepickerList: [{ name: 'Gregorian (Standard)', value: 'gregorian' }, { name: 'Jalalian (شمسی)', value: 'jalalian' }],
|
||||
@@ -606,7 +832,7 @@
|
||||
if (host == this.oldAllSetting.webDomain) host = null;
|
||||
if (port == this.oldAllSetting.webPort) port = null;
|
||||
const isTLS = webCertFile !== "" || webKeyFile !== "";
|
||||
const url = buildURL({ host, port, isTLS, base, path: "panel/settings" });
|
||||
const url = URLBuilder.buildURL({ host, port, isTLS, base, path: "panel/settings" });
|
||||
window.location.replace(url);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -65,15 +65,15 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<td>WARP+ Data</td>
|
||||
<td>[[ sizeFormat(warpModal.warpConfig.account.premium_data) ]]</td>
|
||||
<td>[[ SizeFormatter.sizeFormat(warpModal.warpConfig.account.premium_data) ]]</td>
|
||||
</tr>
|
||||
<tr class="client-table-odd-row">
|
||||
<td>Quota</td>
|
||||
<td>[[ sizeFormat(warpModal.warpConfig.account.quota) ]]</td>
|
||||
<td>[[ SizeFormatter.sizeFormat(warpModal.warpConfig.account.quota) ]]</td>
|
||||
</tr>
|
||||
<tr v-if="!ObjectUtil.isEmpty(warpModal.warpConfig.account.usage)">
|
||||
<td>Usage</td>
|
||||
<td>[[ sizeFormat(warpModal.warpConfig.account.usage) ]]</td>
|
||||
<td>[[ SizeFormatter.sizeFormat(warpModal.warpConfig.account.usage) ]]</td>
|
||||
</tr>
|
||||
</template>
|
||||
</table>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -33,7 +33,7 @@
|
||||
</a-form-item>
|
||||
<a-form-item label='Protocol'>
|
||||
<a-select v-model="ruleModal.rule.protocol" mode="multiple" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="x in ['http','tls','bittorrent']" :value="x">[[ x ]]</a-select-option>
|
||||
<a-select-option v-for="x in ['http','tls','bittorrent','quic']" :value="x">[[ x ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label='Attributes'>
|
||||
|
||||
@@ -23,7 +23,7 @@ func (j *CheckCpuJob) Run() {
|
||||
threshold, _ := j.settingService.GetTgCpu()
|
||||
|
||||
// get latest status of server
|
||||
percent, err := cpu.Percent(1*time.Second, false)
|
||||
percent, err := cpu.Percent(1*time.Minute, false)
|
||||
if err == nil && percent[0] > float64(threshold) {
|
||||
msg := j.tgbotService.I18nBot("tgbot.messages.cpuThreshold",
|
||||
"Percent=="+strconv.FormatFloat(percent[0], 'f', 2, 64),
|
||||
|
||||
@@ -1,178 +0,0 @@
|
||||
package job
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"x-ui/database"
|
||||
"x-ui/database/model"
|
||||
"x-ui/logger"
|
||||
"x-ui/xray"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type SyncClientTrafficJob struct {
|
||||
subClientsCollection map[string][]string
|
||||
}
|
||||
|
||||
func NewClientTrafficSyncJob() *SyncClientTrafficJob {
|
||||
return new(SyncClientTrafficJob)
|
||||
}
|
||||
func (j *SyncClientTrafficJob) Run() {
|
||||
// Step 1: Group clients by SubID
|
||||
subClientsCollection := j.collectClientsGroupedBySubId()
|
||||
|
||||
// Step 2: Sync client traffics for each SubID group
|
||||
for subId, emails := range subClientsCollection {
|
||||
err := j.syncClientTraffics(map[string][]string{subId: emails})
|
||||
if err != nil {
|
||||
logger.Error("Failed to sync traffics for SubID ", subId, ": ", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// collectClientsGroupedBySubId groups clients by their SubIDs
|
||||
func (j *SyncClientTrafficJob) collectClientsGroupedBySubId() map[string][]string {
|
||||
db := database.GetDB()
|
||||
result := make(map[string][]string)
|
||||
|
||||
// Fetch all inbounds
|
||||
var inbounds []*model.Inbound
|
||||
if err := db.Model(&model.Inbound{}).Find(&inbounds).Error; err != nil {
|
||||
logger.Error("Error fetching inbounds: ", err)
|
||||
return result // Return empty map on error
|
||||
}
|
||||
|
||||
// Process each inbound
|
||||
for _, inbound := range inbounds {
|
||||
if inbound.Settings == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
settingsMap, err := parseSettings(inbound.Settings, uint(inbound.Id))
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
continue
|
||||
}
|
||||
|
||||
clients, ok := settingsMap["clients"].([]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
processClients(clients, result)
|
||||
}
|
||||
|
||||
// Remove SubIDs with one or fewer emails
|
||||
filterSingleEmailSubIDs(result)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// parseSettings unmarshals the JSON settings and returns it as a map
|
||||
func parseSettings(settings string, inboundID uint) (map[string]interface{}, error) {
|
||||
if !json.Valid([]byte(settings)) {
|
||||
return nil, fmt.Errorf("Invalid JSON format in Settings for inbound ID %d", inboundID)
|
||||
}
|
||||
|
||||
var tempData map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(settings), &tempData); err != nil {
|
||||
return nil, fmt.Errorf("Error unmarshalling settings for inbound ID %d: %v", inboundID, err)
|
||||
}
|
||||
|
||||
return tempData, nil
|
||||
}
|
||||
|
||||
// processClients extracts SubID and email from the clients and populates the result map
|
||||
func processClients(clients []interface{}, result map[string][]string) {
|
||||
for _, client := range clients {
|
||||
clientMap, ok := client.(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
subId, ok := clientMap["subId"].(string)
|
||||
if !ok || subId == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
email, ok := clientMap["email"].(string)
|
||||
if !ok || email == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
result[subId] = append(result[subId], email)
|
||||
}
|
||||
}
|
||||
|
||||
// filterSingleEmailSubIDs removes SubIDs with one or fewer emails from the result map
|
||||
func filterSingleEmailSubIDs(result map[string][]string) {
|
||||
for subId, emails := range result {
|
||||
if len(emails) <= 1 {
|
||||
delete(result, subId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// syncClientTraffics synchronizes traffic data for each SubID group
|
||||
func (j *SyncClientTrafficJob) syncClientTraffics(result map[string][]string) error {
|
||||
for subId, emails := range result {
|
||||
db := database.GetDB()
|
||||
|
||||
// Step 1: Calculate maxUp and maxDown (outside transaction)
|
||||
var maxUp, maxDown int64
|
||||
err := calculateMaxTraffic(db, emails, &maxUp, &maxDown)
|
||||
if err != nil {
|
||||
logger.Error("Failed to calculate max traffic for SubID ", subId, ": ", err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Step 2: Update traffic data with retry mechanism
|
||||
err = retryOperation(func() error {
|
||||
return updateTraffic(db, emails, maxUp, maxDown)
|
||||
}, 5, 100*time.Millisecond)
|
||||
|
||||
if err != nil {
|
||||
logger.Error("Failed to update client traffics for SubID ", subId, ": ", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// calculateMaxTraffic calculates max up and down traffic for a group of emails
|
||||
func calculateMaxTraffic(db *gorm.DB, emails []string, maxUp, maxDown *int64) error {
|
||||
return db.Model(&xray.ClientTraffic{}).
|
||||
Where("email IN ?", emails).
|
||||
Select("MAX(up) AS max_up, MAX(down) AS max_down").
|
||||
Row().
|
||||
Scan(maxUp, maxDown)
|
||||
}
|
||||
|
||||
// updateTraffic updates the traffic data in the database within a transaction
|
||||
func updateTraffic(db *gorm.DB, emails []string, maxUp, maxDown int64) error {
|
||||
return db.Transaction(func(tx *gorm.DB) error {
|
||||
return tx.Model(&xray.ClientTraffic{}).
|
||||
Where("email IN ?", emails).
|
||||
Updates(map[string]interface{}{
|
||||
"up": maxUp,
|
||||
"down": maxDown,
|
||||
}).Error
|
||||
})
|
||||
}
|
||||
|
||||
// retryOperation retries an operation multiple times with a delay
|
||||
func retryOperation(operation func() error, maxRetries int, delay time.Duration) error {
|
||||
var err error
|
||||
for i := 0; i < maxRetries; i++ {
|
||||
err = operation()
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
logger.Info(fmt.Sprintf("Retry %d/%d failed: %v", i+1, maxRetries, err))
|
||||
time.Sleep(delay)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
package job
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"x-ui/logger"
|
||||
"x-ui/web/service"
|
||||
"x-ui/xray"
|
||||
|
||||
"github.com/valyala/fasthttp"
|
||||
)
|
||||
|
||||
type XrayTrafficJob struct {
|
||||
settingService service.SettingService
|
||||
xrayService service.XrayService
|
||||
inboundService service.InboundService
|
||||
outboundService service.OutboundService
|
||||
@@ -31,7 +36,36 @@ func (j *XrayTrafficJob) Run() {
|
||||
if err != nil {
|
||||
logger.Warning("add outbound traffic failed:", err)
|
||||
}
|
||||
if ExternalTrafficInformEnable, err := j.settingService.GetExternalTrafficInformEnable(); ExternalTrafficInformEnable {
|
||||
j.informTrafficToExternalAPI(traffics, clientTraffics)
|
||||
} else if err != nil {
|
||||
logger.Warning("get ExternalTrafficInformEnable failed:", err)
|
||||
}
|
||||
if needRestart0 || needRestart1 {
|
||||
j.xrayService.SetToNeedRestart()
|
||||
}
|
||||
}
|
||||
|
||||
func (j *XrayTrafficJob) informTrafficToExternalAPI(inboundTraffics []*xray.Traffic, clientTraffics []*xray.ClientTraffic) {
|
||||
informURL, err := j.settingService.GetExternalTrafficInformURI()
|
||||
if err != nil {
|
||||
logger.Warning("get ExternalTrafficInformURI failed:", err)
|
||||
return
|
||||
}
|
||||
requestBody, err := json.Marshal(map[string]interface{}{"clientTraffics": clientTraffics, "inboundTraffics": inboundTraffics})
|
||||
if err != nil {
|
||||
logger.Warning("parse client/inbound traffic failed:", err)
|
||||
return
|
||||
}
|
||||
request := fasthttp.AcquireRequest()
|
||||
defer fasthttp.ReleaseRequest(request)
|
||||
request.Header.SetMethod("POST")
|
||||
request.Header.SetContentType("application/json; charset=UTF-8")
|
||||
request.SetBody([]byte(requestBody))
|
||||
request.SetRequestURI(informURL)
|
||||
response := fasthttp.AcquireResponse()
|
||||
defer fasthttp.ReleaseResponse(response)
|
||||
if err := fasthttp.Do(request, response); err != nil {
|
||||
logger.Warning("POST ExternalTrafficInformURI failed:", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -285,8 +285,7 @@ func (s *ServerService) GetXrayVersions() ([]string, error) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (major == 1 && minor == 8 && patch == 24) ||
|
||||
(major >= 25) {
|
||||
if major > 25 || (major == 25 && minor > 3) || (major == 25 && minor == 3 && patch >= 3) {
|
||||
versions = append(versions, release.TagName)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,51 +24,52 @@ import (
|
||||
var xrayTemplateConfig string
|
||||
|
||||
var defaultValueMap = map[string]string{
|
||||
"xrayTemplateConfig": xrayTemplateConfig,
|
||||
"webListen": "",
|
||||
"webDomain": "",
|
||||
"webPort": "2053",
|
||||
"webCertFile": "",
|
||||
"webKeyFile": "",
|
||||
"secret": random.Seq(32),
|
||||
"webBasePath": "/",
|
||||
"sessionMaxAge": "60",
|
||||
"pageSize": "50",
|
||||
"expireDiff": "0",
|
||||
"trafficDiff": "0",
|
||||
"remarkModel": "-ieo",
|
||||
"timeLocation": "Local",
|
||||
"tgBotEnable": "false",
|
||||
"tgBotToken": "",
|
||||
"tgBotProxy": "",
|
||||
"tgBotAPIServer": "",
|
||||
"tgBotChatId": "",
|
||||
"tgRunTime": "@daily",
|
||||
"tgBotBackup": "false",
|
||||
"tgBotLoginNotify": "true",
|
||||
"tgCpu": "80",
|
||||
"tgLang": "en-US",
|
||||
"secretEnable": "false",
|
||||
"subEnable": "false",
|
||||
"subSyncEnable": "true",
|
||||
"subListen": "",
|
||||
"subPort": "2096",
|
||||
"subPath": "/sub/",
|
||||
"subDomain": "",
|
||||
"subCertFile": "",
|
||||
"subKeyFile": "",
|
||||
"subUpdates": "12",
|
||||
"subEncrypt": "true",
|
||||
"subShowInfo": "true",
|
||||
"subURI": "",
|
||||
"subJsonPath": "/json/",
|
||||
"subJsonURI": "",
|
||||
"subJsonFragment": "",
|
||||
"subJsonNoises": "",
|
||||
"subJsonMux": "",
|
||||
"subJsonRules": "",
|
||||
"datepicker": "gregorian",
|
||||
"warp": "",
|
||||
"xrayTemplateConfig": xrayTemplateConfig,
|
||||
"webListen": "",
|
||||
"webDomain": "",
|
||||
"webPort": "2053",
|
||||
"webCertFile": "",
|
||||
"webKeyFile": "",
|
||||
"secret": random.Seq(32),
|
||||
"webBasePath": "/",
|
||||
"sessionMaxAge": "60",
|
||||
"pageSize": "50",
|
||||
"expireDiff": "0",
|
||||
"trafficDiff": "0",
|
||||
"remarkModel": "-ieo",
|
||||
"timeLocation": "Local",
|
||||
"tgBotEnable": "false",
|
||||
"tgBotToken": "",
|
||||
"tgBotProxy": "",
|
||||
"tgBotAPIServer": "",
|
||||
"tgBotChatId": "",
|
||||
"tgRunTime": "@daily",
|
||||
"tgBotBackup": "false",
|
||||
"tgBotLoginNotify": "true",
|
||||
"tgCpu": "80",
|
||||
"tgLang": "en-US",
|
||||
"secretEnable": "false",
|
||||
"subEnable": "false",
|
||||
"subListen": "",
|
||||
"subPort": "2096",
|
||||
"subPath": "/sub/",
|
||||
"subDomain": "",
|
||||
"subCertFile": "",
|
||||
"subKeyFile": "",
|
||||
"subUpdates": "12",
|
||||
"subEncrypt": "true",
|
||||
"subShowInfo": "true",
|
||||
"subURI": "",
|
||||
"subJsonPath": "/json/",
|
||||
"subJsonURI": "",
|
||||
"subJsonFragment": "",
|
||||
"subJsonNoises": "",
|
||||
"subJsonMux": "",
|
||||
"subJsonRules": "",
|
||||
"datepicker": "gregorian",
|
||||
"warp": "",
|
||||
"externalTrafficInformEnable": "false",
|
||||
"externalTrafficInformURI": "",
|
||||
}
|
||||
|
||||
type SettingService struct{}
|
||||
@@ -417,14 +418,6 @@ func (s *SettingService) GetSubEnable() (bool, error) {
|
||||
return s.getBool("subEnable")
|
||||
}
|
||||
|
||||
func (s *SettingService) GetSubSyncEnable() (bool, error) {
|
||||
return s.getBool("subSyncEnable")
|
||||
}
|
||||
|
||||
func (s *SettingService) SetSubSyncEnable(value bool) error {
|
||||
return s.setBool("subSyncEnable", value)
|
||||
}
|
||||
|
||||
func (s *SettingService) GetSubListen() (string, error) {
|
||||
return s.getString("subListen")
|
||||
}
|
||||
@@ -505,6 +498,22 @@ func (s *SettingService) SetWarp(data string) error {
|
||||
return s.setString("warp", data)
|
||||
}
|
||||
|
||||
func (s *SettingService) GetExternalTrafficInformEnable() (bool, error) {
|
||||
return s.getBool("externalTrafficInformEnable")
|
||||
}
|
||||
|
||||
func (s *SettingService) SetExternalTrafficInformEnable(value bool) error {
|
||||
return s.setBool("externalTrafficInformEnable", value)
|
||||
}
|
||||
|
||||
func (s *SettingService) GetExternalTrafficInformURI() (string, error) {
|
||||
return s.getString("externalTrafficInformURI")
|
||||
}
|
||||
|
||||
func (s *SettingService) SetExternalTrafficInformURI(InformURI string) error {
|
||||
return s.setString("externalTrafficInformURI", InformURI)
|
||||
}
|
||||
|
||||
func (s *SettingService) GetIpLimitEnable() (bool, error) {
|
||||
accessLogPath, err := xray.GetAccessLogPath()
|
||||
if err != nil {
|
||||
@@ -553,7 +562,6 @@ func (s *SettingService) GetDefaultSettings(host string) (interface{}, error) {
|
||||
"defaultKey": func() (interface{}, error) { return s.GetKeyFile() },
|
||||
"tgBotEnable": func() (interface{}, error) { return s.GetTgbotEnabled() },
|
||||
"subEnable": func() (interface{}, error) { return s.GetSubEnable() },
|
||||
"subSyncEnable": func() (interface{}, error) { return s.GetSubSyncEnable() },
|
||||
"subURI": func() (interface{}, error) { return s.GetSubURI() },
|
||||
"subJsonURI": func() (interface{}, error) { return s.GetSubJsonURI() },
|
||||
"remarkModel": func() (interface{}, error) { return s.GetRemarkModel() },
|
||||
|
||||
@@ -307,8 +307,6 @@ func (t *Tgbot) answerCommand(message *telego.Message, chatId int64, isAdmin boo
|
||||
onlyMessage = true
|
||||
if isAdmin {
|
||||
if len(commandArgs) == 0 {
|
||||
msg += t.I18nBot("tgbot.commands.restartUsage")
|
||||
} else if strings.ToLower(commandArgs[0]) == "force" {
|
||||
if t.xrayService.IsXrayRunning() {
|
||||
err := t.xrayService.RestartXray(true)
|
||||
if err != nil {
|
||||
|
||||
@@ -61,6 +61,10 @@
|
||||
"secAlertPanelURI" = "Panel default URI path is insecure. Please configure a complex URI path."
|
||||
"secAlertSubURI" = "Subscription default URI path is insecure. Please configure a complex URI path."
|
||||
"secAlertSubJsonURI" = "Subscription JSON default URI path is insecure. Please configure a complex URI path."
|
||||
"emptyDnsDesc" = "No added DNS servers."
|
||||
"emptyFakeDnsDesc" = "No added Fake DNS servers."
|
||||
"emptyBalancersDesc" = "No added balancers."
|
||||
"emptyReverseDesc" = "No added reverse proxies."
|
||||
|
||||
[menu]
|
||||
"dashboard" = "Overview"
|
||||
@@ -109,9 +113,10 @@
|
||||
"config" = "Config"
|
||||
"backup" = "Backup & Restore"
|
||||
"backupTitle" = "Database Backup & Restore"
|
||||
"backupDescription" = "It is recommended to make a backup before restoring a database."
|
||||
"exportDatabase" = "Back Up"
|
||||
"exportDatabaseDesc" = "Click to download a .db file containing a backup of your current database to your device."
|
||||
"importDatabase" = "Restore"
|
||||
"importDatabaseDesc" = "Click to select and upload a .db file from your device to restore your database from a backup."
|
||||
|
||||
[pages.inbounds]
|
||||
"title" = "Inbounds"
|
||||
@@ -190,9 +195,6 @@
|
||||
|
||||
[pages.client]
|
||||
"add" = "Add Client"
|
||||
"groupAdd" = "Add subscription user"
|
||||
"isGroupEdit" = "Group editing"
|
||||
"isGroupEditDesc" = "All clients with the same subscription are edited"
|
||||
"edit" = "Edit Client"
|
||||
"submitAdd" = "Add Client"
|
||||
"submitEdit" = "Save Changes"
|
||||
@@ -290,8 +292,6 @@
|
||||
"subSettings" = "Subscription"
|
||||
"subEnable" = "Enable Subscription Service"
|
||||
"subEnableDesc" = "Enables the subscription service."
|
||||
"subSyncEnable" = "Enable Subscription Sync"
|
||||
"subSyncEnableDesc" = "Traffic from clients with the same subscription will be synchronized every 10 seconds."
|
||||
"subListen" = "Listen IP"
|
||||
"subListenDesc" = "The IP address for the subscription service. (leave blank to listen on all IPs)"
|
||||
"subPort" = "Listen Port"
|
||||
@@ -312,6 +312,10 @@
|
||||
"subShowInfoDesc" = "The remaining traffic and date will be displayed in the client apps."
|
||||
"subURI" = "Reverse Proxy URI"
|
||||
"subURIDesc" = "The URI path of the subscription URL for use behind proxies."
|
||||
"externalTrafficInformEnable" = "External Traffic Inform"
|
||||
"externalTrafficInformEnableDesc" = "Inform external API on every traffic update."
|
||||
"externalTrafficInformURI" = "External Traffic Inform URI"
|
||||
"externalTrafficInformURIDesc" = "Traffic updates are sent to this URI."
|
||||
"fragment" = "Fragmentation"
|
||||
"fragmentDesc" = "Enable fragmentation for TLS hello packet."
|
||||
"fragmentSett" = "Fragmentation Settings"
|
||||
@@ -322,7 +326,15 @@
|
||||
"muxSett" = "Mux Settings"
|
||||
"direct" = "Direct Connection"
|
||||
"directDesc" = "Directly establishes connections with domains or IP ranges of a specific country."
|
||||
|
||||
"notifications" = "Notifications"
|
||||
"certs" = "Certificaties"
|
||||
"externalTraffic" = "External Traffic"
|
||||
"dateAndTime" = "Date and Time"
|
||||
"proxyAndServer" = "Proxy and Server"
|
||||
"intervals" = "Intervals"
|
||||
"information" = "Information"
|
||||
"language" = "Language"
|
||||
"telegramBotLanguage" = "Telegram Bot Language"
|
||||
|
||||
[pages.xray]
|
||||
"title" = "Xray Configs"
|
||||
@@ -373,10 +385,17 @@
|
||||
"errorLogDesc" = "The file path for the error log. The special value 'none' disabled error logs"
|
||||
"dnsLog" = "DNS Log"
|
||||
"dnsLogDesc" = "Whether to enable DNS query logs"
|
||||
"outboundTraffic" = "Outbounds Traffic"
|
||||
"outboundTrafficDesc" = "Whether to enable outbound traffic"
|
||||
"maskAddress" = "Mask Address"
|
||||
"maskAddressDesc" = "IP address mask, when enabled, will automatically replace the IP address that appears in the log."
|
||||
"statistics" = "Statistics"
|
||||
"statsInboundUplink" = "Inbound Upload Statistics"
|
||||
"statsInboundUplinkDesc" = "Enables the statistics collection for upstream traffic of all inbound proxies."
|
||||
"statsInboundDownlink" = "Inbound Download Statistics"
|
||||
"statsInboundDownlinkDesc" = "Enables the statistics collection for downstream traffic of all inbound proxies."
|
||||
"statsOutboundUplink" = "Outbound Upload Statistics"
|
||||
"statsOutboundUplinkDesc" = "Enables the statistics collection for upstream traffic of all outbound proxies."
|
||||
"statsOutboundDownlink" = "Outbound Download Statistics"
|
||||
"statsOutboundDownlinkDesc" = "Enables the statistics collection for downstream traffic of all outbound proxies."
|
||||
|
||||
[pages.xray.rules]
|
||||
"first" = "First"
|
||||
@@ -434,6 +453,14 @@
|
||||
"enableDesc" = "Enable built-in DNS server"
|
||||
"tag" = "DNS Inbound Tag"
|
||||
"tagDesc" = "This tag will be available as an Inbound tag in routing rules."
|
||||
"clientIp" = "Client IP"
|
||||
"clientIpDesc" = "Used to notify the server of the specified IP location during DNS queries"
|
||||
"disableCache" = "Disable cache"
|
||||
"disableCacheDesc" = "Disables DNS caching"
|
||||
"disableFallback" = "Disable Fallback"
|
||||
"disableFallbackDesc" = "Disables fallback DNS queries"
|
||||
"disableFallbackIfMatch" = "Disable Fallback If Match"
|
||||
"disableFallbackIfMatchDesc" = "Disables fallback DNS queries when the matching domain list of the DNS server is hit"
|
||||
"strategy" = "Query Strategy"
|
||||
"strategyDesc" = "Overall strategy to resolve domain names"
|
||||
"add" = "Add Server"
|
||||
@@ -448,7 +475,7 @@
|
||||
"poolSize" = "Pool Size"
|
||||
|
||||
[pages.settings.security]
|
||||
"admin" = "Admin"
|
||||
"admin" = "Admin credentials"
|
||||
"secret" = "Secret Token"
|
||||
"loginSecurity" = "Secure Login"
|
||||
"loginSecurityDesc" = "Adds an additional layer of authentication to provide more security."
|
||||
@@ -491,9 +518,9 @@
|
||||
"status" = "✅ Bot is OK!"
|
||||
"usage" = "❗ Please provide a text to search!"
|
||||
"getID" = "🆔 Your ID: <code>{{ .ID }}</code>"
|
||||
"helpAdminCommands" = "To restart Xray Core:\r\n<code>/restart force</code>\r\n\r\nTo search for a client email:\r\n<code>/usage [Email]</code>\r\n\r\nTo search for inbounds (with client stats):\r\n<code>/inbound [Remark]</code>\r\n\r\nTelegram Chat ID:\r\n<code>/id</code>"
|
||||
"helpAdminCommands" = "To restart Xray Core:\r\n<code>/restart</code>\r\n\r\nTo search for a client email:\r\n<code>/usage [Email]</code>\r\n\r\nTo search for inbounds (with client stats):\r\n<code>/inbound [Remark]</code>\r\n\r\nTelegram Chat ID:\r\n<code>/id</code>"
|
||||
"helpClientCommands" = "To search for statistics, use the following command:\r\n\r\n<code>/usage [Email]</code>\r\n\r\nTelegram Chat ID:\r\n<code>/id</code>"
|
||||
"restartUsage" = "\r\n\r\n<code>/restart force</code>"
|
||||
"restartUsage" = "\r\n\r\n<code>/restart</code>"
|
||||
"restartSuccess" = "✅ Operation successful!"
|
||||
"restartFailed" = "❗ Error in operation.\r\n\r\n<code>Error: {{ .Error }}</code>."
|
||||
"xrayNotRunning" = "❗ Xray Core is not running."
|
||||
|
||||
@@ -61,6 +61,10 @@
|
||||
"secAlertPanelURI" = "La ruta URI predeterminada del panel no es segura. Por favor, configure una ruta URI compleja."
|
||||
"secAlertSubURI" = "La ruta URI predeterminada de la suscripción no es segura. Por favor, configure una ruta URI compleja."
|
||||
"secAlertSubJsonURI" = "La ruta URI JSON predeterminada de la suscripción no es segura. Por favor, configure una ruta URI compleja."
|
||||
"emptyDnsDesc" = "No hay servidores DNS añadidos."
|
||||
"emptyFakeDnsDesc" = "No hay servidores Fake DNS añadidos."
|
||||
"emptyBalancersDesc" = "No hay balanceadores añadidos."
|
||||
"emptyReverseDesc" = "No hay proxies inversos añadidos."
|
||||
|
||||
[menu]
|
||||
"dashboard" = "Estado del Sistema"
|
||||
@@ -109,9 +113,10 @@
|
||||
"config" = "Configuración"
|
||||
"backup" = "Copia de Seguridad y Restauración"
|
||||
"backupTitle" = "Copia de Seguridad y Restauración de la Base de Datos"
|
||||
"backupDescription" = "Recuerda hacer una copia de seguridad antes de importar una nueva base de datos."
|
||||
"exportDatabase" = "Descargar Base de Datos"
|
||||
"importDatabase" = "Cargar Base de Datos"
|
||||
"exportDatabase" = "Copia de seguridad"
|
||||
"exportDatabaseDesc" = "Haz clic para descargar un archivo .db que contiene una copia de seguridad de tu base de datos actual en tu dispositivo."
|
||||
"importDatabase" = "Restaurar"
|
||||
"importDatabaseDesc" = "Haz clic para seleccionar y cargar un archivo .db desde tu dispositivo para restaurar tu base de datos desde una copia de seguridad."
|
||||
|
||||
[pages.inbounds]
|
||||
"title" = "Entradas"
|
||||
@@ -190,7 +195,6 @@
|
||||
|
||||
[pages.client]
|
||||
"add" = "Agregar Cliente"
|
||||
"groupAdd" = "Agregar usuario de suscripción"
|
||||
"edit" = "Editar Cliente"
|
||||
"submitAdd" = "Agregar Cliente"
|
||||
"submitEdit" = "Guardar Cambios"
|
||||
@@ -288,8 +292,6 @@
|
||||
"subSettings" = "Suscripción"
|
||||
"subEnable" = "Habilitar Servicio"
|
||||
"subEnableDesc" = "Función de suscripción con configuración separada."
|
||||
"subSyncEnable" = "Habilitar sincronización de suscripciones"
|
||||
"subSyncEnableDesc" = "El tráfico de los clientes con la misma suscripción se sincronizará cada 10 segundos."
|
||||
"subListen" = "Listening IP"
|
||||
"subListenDesc" = "Dejar en blanco por defecto para monitorear todas las IPs."
|
||||
"subPort" = "Puerto de Suscripción"
|
||||
@@ -309,6 +311,10 @@
|
||||
"subShowInfo" = "Mostrar información de uso"
|
||||
"subShowInfoDesc" = "Mostrar tráfico restante y fecha después del nombre de configuración."
|
||||
"subURI" = "URI de proxy inverso"
|
||||
"externalTrafficInformEnable" = "Informe de tráfico externo"
|
||||
"externalTrafficInformEnableDesc" = "Informar a la API externa sobre cada actualización de tráfico."
|
||||
"externalTrafficInformURI" = "URI de información de tráfico externo"
|
||||
"externalTrafficInformURIDesc" = "Las actualizaciones de tráfico se envían a este URI."
|
||||
"subURIDesc" = "Cambiar el URI base de la URL de suscripción para usar detrás de los servidores proxy"
|
||||
"fragment" = "Fragmentación"
|
||||
"fragmentDesc" = "Habilitar la fragmentación para el paquete de saludo de TLS"
|
||||
@@ -320,7 +326,15 @@
|
||||
"muxSett" = "Configuración Mux"
|
||||
"direct" = "Conexión Directa"
|
||||
"directDesc" = "Establece conexiones directas con dominios o rangos de IP de un país específico."
|
||||
|
||||
"notifications" = "Notificaciones"
|
||||
"certs" = "Certificados"
|
||||
"externalTraffic" = "Tráfico Externo"
|
||||
"dateAndTime" = "Fecha y Hora"
|
||||
"proxyAndServer" = "Proxy y Servidor"
|
||||
"intervals" = "Intervalos"
|
||||
"information" = "Información"
|
||||
"language" = "Idioma"
|
||||
"telegramBotLanguage" = "Idioma del Bot de Telegram"
|
||||
|
||||
[pages.xray]
|
||||
"title" = "Xray Configuración"
|
||||
@@ -371,10 +385,17 @@
|
||||
"errorLogDesc" = "La ruta del archivo para el registro de errores. El valor especial 'none' desactiva los registros de errores."
|
||||
"dnsLog" = "Registro DNS"
|
||||
"dnsLogDesc" = "Si habilitar los registros de consulta DNS"
|
||||
"outboundTraffic" = "Tráfico saliente"
|
||||
"outboundTrafficDesc" = "Si se debe habilitar el tráfico saliente"
|
||||
"maskAddress" = "Enmascarar Dirección"
|
||||
"maskAddressDesc" = "Máscara de dirección IP, cuando se habilita, reemplazará automáticamente la dirección IP que aparece en el registro."
|
||||
"statistics" = "Estadísticas"
|
||||
"statsInboundUplink" = "Estadísticas de Subida de Entrada"
|
||||
"statsInboundUplinkDesc" = "Habilita la recopilación de estadísticas para el tráfico ascendente de todos los proxies de entrada."
|
||||
"statsInboundDownlink" = "Estadísticas de Bajada de Entrada"
|
||||
"statsInboundDownlinkDesc" = "Habilita la recopilación de estadísticas para el tráfico descendente de todos los proxies de entrada."
|
||||
"statsOutboundUplink" = "Estadísticas de Subida de Salida"
|
||||
"statsOutboundUplinkDesc" = "Habilita la recopilación de estadísticas para el tráfico ascendente de todos los proxies de salida."
|
||||
"statsOutboundDownlink" = "Estadísticas de Bajada de Salida"
|
||||
"statsOutboundDownlinkDesc" = "Habilita la recopilación de estadísticas para el tráfico descendente de todos los proxies de salida."
|
||||
|
||||
[pages.xray.rules]
|
||||
"first" = "Primero"
|
||||
@@ -432,6 +453,14 @@
|
||||
"enableDesc" = "Habilitar servidor DNS incorporado"
|
||||
"tag" = "Etiqueta de Entrada DNS"
|
||||
"tagDesc" = "Esta etiqueta estará disponible como una etiqueta de entrada en las reglas de enrutamiento."
|
||||
"clientIp" = "IP del cliente"
|
||||
"clientIpDesc" = "Se utiliza para notificar al servidor la ubicación IP especificada durante las consultas DNS"
|
||||
"disableCache" = "Desactivar caché"
|
||||
"disableCacheDesc" = "Desactiva el almacenamiento en caché de DNS"
|
||||
"disableFallback" = "Desactivar respaldo"
|
||||
"disableFallbackDesc" = "Desactiva las consultas DNS de respaldo"
|
||||
"disableFallbackIfMatch" = "Desactivar respaldo si coincide"
|
||||
"disableFallbackIfMatchDesc" = "Desactiva las consultas DNS de respaldo cuando se acierta en la lista de dominios coincidentes del servidor DNS"
|
||||
"strategy" = "Estrategia de Consulta"
|
||||
"strategyDesc" = "Estrategia general para resolver nombres de dominio"
|
||||
"add" = "Agregar Servidor"
|
||||
@@ -446,7 +475,7 @@
|
||||
"poolSize" = "Tamaño del grupo"
|
||||
|
||||
[pages.settings.security]
|
||||
"admin" = "Administrador"
|
||||
"admin" = "Credenciales de administrador"
|
||||
"secret" = "Token Secreto"
|
||||
"loginSecurity" = "Seguridad de Inicio de Sesión"
|
||||
"loginSecurityDesc" = "Habilitar un paso adicional de seguridad para el inicio de sesión de usuarios."
|
||||
@@ -489,9 +518,9 @@
|
||||
"status" = "✅ ¡El bot está bien!"
|
||||
"usage" = "❗ ¡Por favor proporciona un texto para buscar!"
|
||||
"getID" = "🆔 Tu ID: <code>{{ .ID }}</code>"
|
||||
"helpAdminCommands" = "Para reiniciar Xray Core:\r\n<code>/restart force</code>\r\n\r\nPara buscar un correo electrónico de cliente:\r\n<code>/usage [Correo electrónico]</code>\r\n\r\nPara buscar entradas (con estadísticas de cliente):\r\n<code>/inbound [Observación]</code>\r\n\r\nID de Chat de Telegram:\r\n<code>/id</code>"
|
||||
"helpAdminCommands" = "Para reiniciar Xray Core:\r\n<code>/restart</code>\r\n\r\nPara buscar un correo electrónico de cliente:\r\n<code>/usage [Correo electrónico]</code>\r\n\r\nPara buscar entradas (con estadísticas de cliente):\r\n<code>/inbound [Observación]</code>\r\n\r\nID de Chat de Telegram:\r\n<code>/id</code>"
|
||||
"helpClientCommands" = "Para buscar estadísticas, utiliza el siguiente comando:\r\n<code>/usage [Correo electrónico]</code>\r\n\r\nID de Chat de Telegram:\r\n<code>/id</code>"
|
||||
"restartUsage" = "\r\n\r\n<code>/restart force</code>"
|
||||
"restartUsage" = "\r\n\r\n<code>/restart</code>"
|
||||
"restartSuccess" = "✅ ¡Operación exitosa!"
|
||||
"restartFailed" = "❗ Error en la operación.\r\n\r\n<code>Error: {{ .Error }}</code>."
|
||||
"xrayNotRunning" = "❗ Xray Core no está en ejecución."
|
||||
|
||||
@@ -61,6 +61,10 @@
|
||||
"secAlertPanelURI" = "مسیر پیشفرض لینک پنل ناامن است. لطفاً یک مسیر پیچیده تنظیم کنید"
|
||||
"secAlertSubURI" = "مسیر پیشفرض لینک سابسکریپشن ناامن است. لطفاً یک مسیر پیچیده تنظیم کنید"
|
||||
"secAlertSubJsonURI" = "مسیر پیشفرض لینک سابسکریپشن جیسون ناامن است. لطفاً یک مسیر پیچیده تنظیم کنید"
|
||||
"emptyDnsDesc" = "هیچ سرور DNS اضافه نشده است."
|
||||
"emptyFakeDnsDesc" = "هیچ سرور Fake DNS اضافه نشده است."
|
||||
"emptyBalancersDesc" = "هیچ بالانسر اضافه نشده است."
|
||||
"emptyReverseDesc" = "هیچ پروکسی معکوس اضافه نشده است."
|
||||
|
||||
[menu]
|
||||
"dashboard" = "نمای کلی"
|
||||
@@ -109,9 +113,10 @@
|
||||
"config" = "پیکربندی"
|
||||
"backup" = "پشتیبانگیری"
|
||||
"backupTitle" = "پشتیبانگیری دیتابیس"
|
||||
"backupDescription" = "توصیهمیشود قبلاز واردکردن یک دیتابیس جدید، نسخه پشتیبان تهیه کنید"
|
||||
"exportDatabase" = "پشتیبانگیری"
|
||||
"importDatabase" = "بازگرداندن"
|
||||
"exportDatabaseDesc" = "برای دانلود یک فایل .db حاوی پشتیبان از پایگاه داده فعلی خود به دستگاهتان کلیک کنید."
|
||||
"importDatabase" = "بازیابی"
|
||||
"importDatabaseDesc" = "برای انتخاب و آپلود یک فایل .db از دستگاهتان و بازیابی پایگاه داده از یک پشتیبان کلیک کنید."
|
||||
|
||||
[pages.inbounds]
|
||||
"title" = "کاربران"
|
||||
@@ -190,9 +195,6 @@
|
||||
|
||||
[pages.client]
|
||||
"add" = "کاربر جدید"
|
||||
"groupAdd" = "افزودن کاربر سابسکریپشن"
|
||||
"isGroupEdit" = "ویرایش گروهی"
|
||||
"isGroupEditDesc" = "همه کاربران با سابسکریپشن یکسان ویرایش می شوند"
|
||||
"edit" = "ویرایش کاربر"
|
||||
"submitAdd" = "اضافه کردن"
|
||||
"submitEdit" = "ذخیره تغییرات"
|
||||
@@ -290,8 +292,6 @@
|
||||
"subSettings" = "سابسکریپشن"
|
||||
"subEnable" = "فعالسازی سرویس سابسکریپشن"
|
||||
"subEnableDesc" = "سرویس سابسکریپشن را فعالمیکند"
|
||||
"subSyncEnable" = "فعالسازی همگام سازی سابسکریپشن"
|
||||
"subSyncEnableDesc" = "ترافیک کلاینت هایی که سابسکریپشن یکسان دارند هر ۱۰ ثانیه همگام میشوند."
|
||||
"subListen" = "آدرس آیپی"
|
||||
"subListenDesc" = "آدرس آیپی برای سرویس سابسکریپشن. برای گوش دادن بهتمام آیپیها خالیبگذارید"
|
||||
"subPort" = "پورت"
|
||||
@@ -306,6 +306,10 @@
|
||||
"subDomainDesc" = "آدرس دامنه برای سرویس سابسکریپشن. برای گوش دادن به تمام دامنهها و آیپیها خالیبگذارید"
|
||||
"subUpdates" = "فاصله بروزرسانی سابسکریپشن"
|
||||
"subUpdatesDesc" = "(فاصله مابین بروزرسانی در برنامههای کاربری. (واحد: ساعت"
|
||||
"externalTrafficInformEnable" = "اطلاع رسانی خارجی مصرف ترافیک"
|
||||
"externalTrafficInformEnableDesc" = "مصرف ترافیک به سرویس خارجی ارسال می شود"
|
||||
"externalTrafficInformURI" = "لینک اطلاع رسانی خارجی مصرف ترافیک"
|
||||
"externalTrafficInformURIDesc" = "ترافیک های مصرفی به این لینک هم ارسال می شود"
|
||||
"subEncrypt" = "کدگذاری"
|
||||
"subEncryptDesc" = "کدگذاری خواهدشد Base64 محتوای برگشتی سرویس سابسکریپشن برپایه"
|
||||
"subShowInfo" = "نمایش اطلاعات مصرف"
|
||||
@@ -322,7 +326,15 @@
|
||||
"muxSett" = "تنظیمات ماکس"
|
||||
"direct" = "اتصال مستقیم"
|
||||
"directDesc" = "به طور مستقیم با دامنه ها یا محدوده آیپی یک کشور خاص ارتباط برقرار می کند"
|
||||
|
||||
"notifications" = "اعلانها"
|
||||
"certs" = "گواهیها"
|
||||
"externalTraffic" = "ترافیک خارجی"
|
||||
"dateAndTime" = "تاریخ و زمان"
|
||||
"proxyAndServer" = "پراکسی و سرور"
|
||||
"intervals" = "فواصل"
|
||||
"information" = "اطلاعات"
|
||||
"language" = "زبان"
|
||||
"telegramBotLanguage" = "زبان ربات تلگرام"
|
||||
|
||||
[pages.xray]
|
||||
"title" = "پیکربندی ایکسری"
|
||||
@@ -373,10 +385,17 @@
|
||||
"errorLogDesc" = "مسیر فایل برای ورود به سیستم خطا. مقدار ویژه «هیچ» گزارش های خطا را غیرفعال میکند"
|
||||
"dnsLog" = "گزارش DNS"
|
||||
"dnsLogDesc" = "آیا ثبتهای درخواست DNS را فعال کنید"
|
||||
"outboundTraffic" = "ترافیک خروجی"
|
||||
"outboundTrafficDesc" = "فعال کردن ترافیک خروجی"
|
||||
"maskAddress" = "پنهان کردن آدرس"
|
||||
"maskAddressDesc" = "پوشش آدرس IP، هنگامی که فعال میشود، به طور خودکار آدرس IP که در لاگ ظاهر میشود را جایگزین میکند."
|
||||
"statistics" = "آمار"
|
||||
"statsInboundUplink" = "آمار آپلود ورودی"
|
||||
"statsInboundUplinkDesc" = "جمعآوری آمار برای ترافیک بالارو (آپلود) تمام پروکسیهای ورودی را فعال میکند."
|
||||
"statsInboundDownlink" = "آمار دانلود ورودی"
|
||||
"statsInboundDownlinkDesc" = "جمعآوری آمار برای ترافیک پایینرو (دانلود) تمام پروکسیهای ورودی را فعال میکند."
|
||||
"statsOutboundUplink" = "آمار آپلود خروجی"
|
||||
"statsOutboundUplinkDesc" = "جمعآوری آمار برای ترافیک بالارو (آپلود) تمام پروکسیهای خروجی را فعال میکند."
|
||||
"statsOutboundDownlink" = "آمار دانلود خروجی"
|
||||
"statsOutboundDownlinkDesc" = "جمعآوری آمار برای ترافیک پایینرو (دانلود) تمام پروکسیهای خروجی را فعال میکند."
|
||||
|
||||
[pages.xray.rules]
|
||||
"first" = "اولین"
|
||||
@@ -434,6 +453,14 @@
|
||||
"enableDesc" = "سرور حل دامنه داخلی را فعال کنید"
|
||||
"tag" = "برچسب"
|
||||
"tagDesc" = "این برچسب در قوانین مسیریابی به عنوان یک برچسب ورودی قابل استفاده خواهد بود"
|
||||
"clientIp" = "آیپی کلاینت"
|
||||
"clientIpDesc" = "برای اطلاعرسانی به سرور درباره مکان IP مشخصشده در طول درخواستهای DNS استفاده میشود"
|
||||
"disableCache" = "غیرفعالسازی کش"
|
||||
"disableCacheDesc" = "کش DNS را غیرفعال میکند"
|
||||
"disableFallback" = "غیرفعالسازی Fallback"
|
||||
"disableFallbackDesc" = "درخواستهای DNS Fallback را غیرفعال میکند"
|
||||
"disableFallbackIfMatch" = "غیرفعالسازی Fallback در صورت تطابق"
|
||||
"disableFallbackIfMatchDesc" = "درخواستهای DNS Fallback را زمانی که لیست دامنههای مطابقتیافته سرور DNS فعال است، غیرفعال میکند"
|
||||
"strategy" = "استراتژی پرسوجو"
|
||||
"strategyDesc" = "استراتژی کلی برای حل نام دامنه"
|
||||
"add" = "افزودن سرور"
|
||||
@@ -448,7 +475,7 @@
|
||||
"poolSize" = "اندازه استخر"
|
||||
|
||||
[pages.settings.security]
|
||||
"admin" = "مدیر"
|
||||
"admin" = "اعتبارنامههای ادمین"
|
||||
"secret" = "توکن مخفی"
|
||||
"loginSecurity" = "ورود ایمن"
|
||||
"loginSecurityDesc" = "یک لایه اضافی از احراز هویت برای ایجاد امنیت بیشتر اضافه می کند"
|
||||
@@ -491,9 +518,9 @@
|
||||
"status" = "✅ ربات در حالت عادی است!"
|
||||
"usage" = "❗ لطفاً یک متن برای جستجو وارد کنید!"
|
||||
"getID" = "🆔 شناسه شما: <code>{{ .ID }}</code>"
|
||||
"helpAdminCommands" = "برای راهاندازی مجدد Xray Core:\r\n<code>/restart force</code>\r\n\r\nبرای جستجوی ایمیل مشتری:\r\n<code>/usage [ایمیل]</code>\r\n\r\nبرای جستجوی ورودیها (با آمار مشتری):\r\n<code>/inbound [توضیحات]</code>\r\n\r\nشناسه گفتگوی تلگرام:\r\n<code>/id</code>"
|
||||
"helpAdminCommands" = "برای راهاندازی مجدد Xray Core:\r\n<code>/restart</code>\r\n\r\nبرای جستجوی ایمیل مشتری:\r\n<code>/usage [ایمیل]</code>\r\n\r\nبرای جستجوی ورودیها (با آمار مشتری):\r\n<code>/inbound [توضیحات]</code>\r\n\r\nشناسه گفتگوی تلگرام:\r\n<code>/id</code>"
|
||||
"helpClientCommands" = "برای جستجوی آمار، از دستور زیر استفاده کنید:\r\n<code>/usage [ایمیل]</code>\r\n\r\nشناسه گفتگوی تلگرام:\r\n<code>/id</code>"
|
||||
"restartUsage" = "\r\n\r\n<code>/restart force</code>"
|
||||
"restartUsage" = "\r\n\r\n<code>/restart</code>"
|
||||
"restartSuccess" = "✅ عملیات با موفقیت انجام شد!"
|
||||
"restartFailed" = "❗ خطا در عملیات.\r\n\r\n<code>خطا: {{ .Error }}</code>."
|
||||
"xrayNotRunning" = "❗ Xray Core در حال اجرا نیست."
|
||||
|
||||
@@ -61,6 +61,10 @@
|
||||
"secAlertPanelURI" = "Jalur URI default panel tidak aman. Harap konfigurasi jalur URI kompleks."
|
||||
"secAlertSubURI" = "Jalur URI default langganan tidak aman. Harap konfigurasi jalur URI kompleks."
|
||||
"secAlertSubJsonURI" = "Jalur URI default JSON langganan tidak aman. Harap konfigurasikan jalur URI kompleks."
|
||||
"emptyDnsDesc" = "Tidak ada server DNS yang ditambahkan."
|
||||
"emptyFakeDnsDesc" = "Tidak ada server Fake DNS yang ditambahkan."
|
||||
"emptyBalancersDesc" = "Tidak ada penyeimbang yang ditambahkan."
|
||||
"emptyReverseDesc" = "Tidak ada proxy terbalik yang ditambahkan."
|
||||
|
||||
[menu]
|
||||
"dashboard" = "Ikhtisar"
|
||||
@@ -109,9 +113,10 @@
|
||||
"config" = "Konfigurasi"
|
||||
"backup" = "Cadangan & Pulihkan"
|
||||
"backupTitle" = "Cadangan & Pulihkan Database"
|
||||
"backupDescription" = "Disarankan untuk membuat cadangan sebelum memulihkan database."
|
||||
"exportDatabase" = "Cadangkan"
|
||||
"exportDatabaseDesc" = "Klik untuk mengunduh file .db yang berisi cadangan dari database Anda saat ini ke perangkat Anda."
|
||||
"importDatabase" = "Pulihkan"
|
||||
"importDatabaseDesc" = "Klik untuk memilih dan mengunggah file .db dari perangkat Anda untuk memulihkan database dari cadangan."
|
||||
|
||||
[pages.inbounds]
|
||||
"title" = "Masuk"
|
||||
@@ -190,9 +195,6 @@
|
||||
|
||||
[pages.client]
|
||||
"add" = "Tambah Klien"
|
||||
"groupAdd" = "Tambahkan pengguna langganan"
|
||||
"isGroupEdit" = "Pengeditan grup"
|
||||
"isGroupEditDesc" = "Semua klien dengan langganan yang sama akan diedit"
|
||||
"edit" = "Edit Klien"
|
||||
"submitAdd" = "Tambah Klien"
|
||||
"submitEdit" = "Simpan Perubahan"
|
||||
@@ -290,8 +292,6 @@
|
||||
"subSettings" = "Langganan"
|
||||
"subEnable" = "Aktifkan Layanan Langganan"
|
||||
"subEnableDesc" = "Mengaktifkan layanan langganan."
|
||||
"subSyncEnable" = "Aktifkan Sinkronisasi Langganan"
|
||||
"subSyncEnableDesc" = "Lalu lintas dari klien dengan langganan yang sama akan disinkronkan setiap 10 detik."
|
||||
"subListen" = "IP Pendengar"
|
||||
"subListenDesc" = "Alamat IP untuk layanan langganan. (biarkan kosong untuk mendengarkan semua IP)"
|
||||
"subPort" = "Port Pendengar"
|
||||
@@ -311,7 +311,10 @@
|
||||
"subShowInfo" = "Tampilkan Info Penggunaan"
|
||||
"subShowInfoDesc" = "Sisa traffic dan tanggal akan ditampilkan di aplikasi klien."
|
||||
"subURI" = "URI Proxy Terbalik"
|
||||
"subURIDesc" = "URI path URL langganan untuk penggunaan di belakang proxy."
|
||||
"externalTrafficInformEnable" = "Informasikan API eksternal pada setiap pembaruan lalu lintas."
|
||||
"externalTrafficInformEnableDesc" = "Inform external API on every traffic update."
|
||||
"externalTrafficInformURI" = "Lalu Lintas Eksternal Menginformasikan URI"
|
||||
"externalTrafficInformURIDesc" = "Pembaruan lalu lintas dikirim ke URI ini."
|
||||
"fragment" = "Fragmentasi"
|
||||
"fragmentDesc" = "Aktifkan fragmentasi untuk paket hello TLS"
|
||||
"fragmentSett" = "Pengaturan Fragmentasi"
|
||||
@@ -322,7 +325,15 @@
|
||||
"muxSett" = "Pengaturan Mux"
|
||||
"direct" = "Koneksi langsung"
|
||||
"directDesc" = "Secara langsung membuat koneksi dengan domain atau rentang IP negara tertentu."
|
||||
|
||||
"notifications" = "Notifikasi"
|
||||
"certs" = "Sertifikat"
|
||||
"externalTraffic" = "Lalu Lintas Eksternal"
|
||||
"dateAndTime" = "Tanggal dan Waktu"
|
||||
"proxyAndServer" = "Proxy dan Server"
|
||||
"intervals" = "Interval"
|
||||
"information" = "Informasi"
|
||||
"language" = "Bahasa"
|
||||
"telegramBotLanguage" = "Bahasa Bot Telegram"
|
||||
|
||||
[pages.xray]
|
||||
"title" = "Konfigurasi Xray"
|
||||
@@ -373,10 +384,17 @@
|
||||
"errorLogDesc" = "Jalur file untuk log kesalahan. Nilai khusus 'tidak ada' menonaktifkan log kesalahan"
|
||||
"dnsLog" = "Log DNS"
|
||||
"dnsLogDesc" = "Apakah akan mengaktifkan log kueri DNS"
|
||||
"outboundTraffic" = "Lalu Lintas Keluar"
|
||||
"outboundTrafficDesc" = "Apakah mengaktifkan lalu lintas keluar"
|
||||
"maskAddress" = "Alamat Masker"
|
||||
"maskAddressDesc" = "Masker alamat IP, ketika diaktifkan, akan secara otomatis mengganti alamat IP yang muncul di log."
|
||||
"statistics" = "Statistik"
|
||||
"statsInboundUplink" = "Statistik Unggah Masuk"
|
||||
"statsInboundUplinkDesc" = "Mengaktifkan pengumpulan statistik untuk lalu lintas unggah dari semua proxy masuk."
|
||||
"statsInboundDownlink" = "Statistik Unduh Masuk"
|
||||
"statsInboundDownlinkDesc" = "Mengaktifkan pengumpulan statistik untuk lalu lintas unduh dari semua proxy masuk."
|
||||
"statsOutboundUplink" = "Statistik Unggah Keluar"
|
||||
"statsOutboundUplinkDesc" = "Mengaktifkan pengumpulan statistik untuk lalu lintas unggah dari semua proxy keluar."
|
||||
"statsOutboundDownlink" = "Statistik Unduh Keluar"
|
||||
"statsOutboundDownlinkDesc" = "Mengaktifkan pengumpulan statistik untuk lalu lintas unduh dari semua proxy keluar."
|
||||
|
||||
[pages.xray.rules]
|
||||
"first" = "Pertama"
|
||||
@@ -434,6 +452,14 @@
|
||||
"enableDesc" = "Aktifkan server DNS bawaan"
|
||||
"tag" = "Tanda DNS Masuk"
|
||||
"tagDesc" = "Tanda ini akan tersedia sebagai tanda masuk dalam aturan penataan."
|
||||
"clientIp" = "IP Klien"
|
||||
"clientIpDesc" = "Digunakan untuk memberi tahu server tentang lokasi IP yang ditentukan selama kueri DNS"
|
||||
"disableCache" = "Nonaktifkan cache"
|
||||
"disableCacheDesc" = "Menonaktifkan caching DNS"
|
||||
"disableFallback" = "Nonaktifkan Fallback"
|
||||
"disableFallbackDesc" = "Menonaktifkan kueri DNS fallback"
|
||||
"disableFallbackIfMatch" = "Nonaktifkan Fallback Jika Cocok"
|
||||
"disableFallbackIfMatchDesc" = "Menonaktifkan kueri DNS fallback ketika daftar domain yang cocok dari server DNS terpenuhi"
|
||||
"strategy" = "Strategi Kueri"
|
||||
"strategyDesc" = "Strategi keseluruhan untuk menyelesaikan nama domain"
|
||||
"add" = "Tambahkan Server"
|
||||
@@ -448,7 +474,7 @@
|
||||
"poolSize" = "Ukuran Kolam"
|
||||
|
||||
[pages.settings.security]
|
||||
"admin" = "Admin"
|
||||
"admin" = "Kredensial admin"
|
||||
"secret" = "Token Rahasia"
|
||||
"loginSecurity" = "Login Aman"
|
||||
"loginSecurityDesc" = "Menambahkan lapisan otentikasi tambahan untuk memberikan keamanan lebih."
|
||||
@@ -491,9 +517,9 @@
|
||||
"status" = "✅ Bot dalam keadaan baik!"
|
||||
"usage" = "❗ Harap berikan teks untuk mencari!"
|
||||
"getID" = "🆔 ID Anda: <code>{{ .ID }}</code>"
|
||||
"helpAdminCommands" = "Untuk memulai ulang Xray Core:\r\n<code>/restart force</code>\r\n\r\nUntuk mencari email klien:\r\n<code>/usage [Email]</code>\r\n\r\nUntuk mencari inbound (dengan statistik klien):\r\n<code>/inbound [Catatan]</code>\r\n\r\nID Obrolan Telegram:\r\n<code>/id</code>"
|
||||
"helpAdminCommands" = "Untuk memulai ulang Xray Core:\r\n<code>/restart</code>\r\n\r\nUntuk mencari email klien:\r\n<code>/usage [Email]</code>\r\n\r\nUntuk mencari inbound (dengan statistik klien):\r\n<code>/inbound [Catatan]</code>\r\n\r\nID Obrolan Telegram:\r\n<code>/id</code>"
|
||||
"helpClientCommands" = "Untuk mencari statistik, gunakan perintah berikut:\r\n<code>/usage [Email]</code>\r\n\r\nID Obrolan Telegram:\r\n<code>/id</code>"
|
||||
"restartUsage" = "\r\n\r\n<code>/restart force</code>"
|
||||
"restartUsage" = "\r\n\r\n<code>/restart</code>"
|
||||
"restartSuccess" = "✅ Operasi berhasil!"
|
||||
"restartFailed" = "❗ Kesalahan dalam operasi.\r\n\r\n<code>Error: {{ .Error }}</code>."
|
||||
"xrayNotRunning" = "❗ Xray Core tidak berjalan."
|
||||
|
||||
@@ -61,6 +61,10 @@
|
||||
"secAlertPanelURI" = "デフォルトのURIパスは安全ではありません。複雑なURIパスを設定してください。"
|
||||
"secAlertSubURI" = "サブスクリプションのデフォルトURIパスは安全ではありません。複雑なURIパスを設定してください。"
|
||||
"secAlertSubJsonURI" = "JSONサブスクリプションのデフォルトURIパスは安全ではありません。複雑なURIパスを設定してください。"
|
||||
"emptyDnsDesc" = "追加されたDNSサーバーはありません。"
|
||||
"emptyFakeDnsDesc" = "追加されたFake DNSサーバーはありません。"
|
||||
"emptyBalancersDesc" = "追加されたバランサーはありません。"
|
||||
"emptyReverseDesc" = "追加されたリバースプロキシはありません。"
|
||||
|
||||
[menu]
|
||||
"dashboard" = "ダッシュボード"
|
||||
@@ -109,9 +113,10 @@
|
||||
"config" = "設定"
|
||||
"backup" = "バックアップと復元"
|
||||
"backupTitle" = "データベースのバックアップと復元"
|
||||
"backupDescription" = "データベースを復元する前にバックアップすることをお勧めします"
|
||||
"exportDatabase" = "バックアップ"
|
||||
"exportDatabaseDesc" = "クリックして、現在のデータベースのバックアップを含む .db ファイルをデバイスにダウンロードします。"
|
||||
"importDatabase" = "復元"
|
||||
"importDatabaseDesc" = "クリックして、デバイスから .db ファイルを選択し、アップロードしてバックアップからデータベースを復元します。"
|
||||
|
||||
[pages.inbounds]
|
||||
"title" = "インバウンド一覧"
|
||||
@@ -190,9 +195,6 @@
|
||||
|
||||
[pages.client]
|
||||
"add" = "クライアント追加"
|
||||
"groupAdd" = "サブスクリプション ユーザーの追加"
|
||||
"isGroupEdit" = "グループの編集"
|
||||
"isGroupEditDesc" = "同じサブスクリプションを持つすべてのクライアントが編集されます"
|
||||
"edit" = "クライアント編集"
|
||||
"submitAdd" = "クライアント追加"
|
||||
"submitEdit" = "変更を保存"
|
||||
@@ -290,8 +292,6 @@
|
||||
"subSettings" = "サブスクリプション設定"
|
||||
"subEnable" = "サブスクリプションサービスを有効にする"
|
||||
"subEnableDesc" = "サブスクリプションサービス機能を有効にする"
|
||||
"subSyncEnable" = "サブスクリプション同期を有効にする"
|
||||
"subSyncEnableDesc" = "同じサブスクリプションを持つクライアントからのトラフィックは 10 秒ごとに同期されます。"
|
||||
"subListen" = "監視IP"
|
||||
"subListenDesc" = "サブスクリプションサービスが監視するIPアドレス(空白にするとすべてのIPを監視)"
|
||||
"subPort" = "監視ポート"
|
||||
@@ -312,6 +312,10 @@
|
||||
"subShowInfoDesc" = "クライアントアプリで残りのトラフィックと日付情報を表示する"
|
||||
"subURI" = "リバースプロキシURI"
|
||||
"subURIDesc" = "プロキシ後ろのサブスクリプションURLのURIパスに使用する"
|
||||
"externalTrafficInformEnable" = "外部トラフィック情報"
|
||||
"externalTrafficInformEnableDesc" = "トラフィックの更新ごとに外部 API に通知します。"
|
||||
"externalTrafficInformURI" = "外部トラフィック通知 URI"
|
||||
"externalTrafficInformURIDesc" = "トラフィックの更新ごとに外部 API に通知します。"
|
||||
"fragment" = "フラグメント"
|
||||
"fragmentDesc" = "TLS helloパケットのフラグメントを有効にする"
|
||||
"fragmentSett" = "設定"
|
||||
@@ -322,7 +326,15 @@
|
||||
"muxSett" = "マルチプレクサ設定"
|
||||
"direct" = "直接接続"
|
||||
"directDesc" = "特定の国のドメインまたはIP範囲に直接接続する"
|
||||
|
||||
"notifications" = "通知"
|
||||
"certs" = "証明書"
|
||||
"externalTraffic" = "外部トラフィック"
|
||||
"dateAndTime" = "日付と時刻"
|
||||
"proxyAndServer" = "プロキシとサーバー"
|
||||
"intervals" = "間隔"
|
||||
"information" = "情報"
|
||||
"language" = "言語"
|
||||
"telegramBotLanguage" = "Telegram Botの言語"
|
||||
|
||||
[pages.xray]
|
||||
"title" = "Xray 設定"
|
||||
@@ -373,10 +385,17 @@
|
||||
"errorLogDesc" = "エラーログのファイルパス。特殊値 'none' はエラーログを無効にします"
|
||||
"dnsLog" = "DNS ログ"
|
||||
"dnsLogDesc" = "DNSクエリのログを有効にするかどうか"
|
||||
"outboundTraffic" = "アウトバウンドトラフィック"
|
||||
"outboundTrafficDesc" = "アウトバウンドトラフィックを有効にするかどうか"
|
||||
"maskAddress" = "アドレスをマスク"
|
||||
"maskAddressDesc" = "IPアドレスをマスクし、有効にするとログに表示されるIPアドレスを自動的に置き換えます"
|
||||
"statistics" = "統計"
|
||||
"statsInboundUplink" = "インバウンドアップロード統計"
|
||||
"statsInboundUplinkDesc" = "すべてのインバウンドプロキシのアップストリームトラフィックの統計収集を有効にします。"
|
||||
"statsInboundDownlink" = "インバウンドダウンロード統計"
|
||||
"statsInboundDownlinkDesc" = "すべてのインバウンドプロキシのダウンストリームトラフィックの統計収集を有効にします。"
|
||||
"statsOutboundUplink" = "アウトバウンドアップロード統計"
|
||||
"statsOutboundUplinkDesc" = "すべてのアウトバウンドプロキシのアップストリームトラフィックの統計収集を有効にします。"
|
||||
"statsOutboundDownlink" = "アウトバウンドダウンロード統計"
|
||||
"statsOutboundDownlinkDesc" = "すべてのアウトバウンドプロキシのダウンストリームトラフィックの統計収集を有効にします。"
|
||||
|
||||
[pages.xray.rules]
|
||||
"first" = "最初"
|
||||
@@ -434,6 +453,14 @@
|
||||
"enableDesc" = "組み込みDNSサーバーを有効にする"
|
||||
"tag" = "DNSインバウンドタグ"
|
||||
"tagDesc" = "このタグはルーティングルールでインバウンドタグとして使用できます"
|
||||
"clientIp" = "クライアントIP"
|
||||
"clientIpDesc" = "DNSクエリ中に指定されたIPの位置をサーバーに通知するために使用されます"
|
||||
"disableCache" = "キャッシュを無効にする"
|
||||
"disableCacheDesc" = "DNSキャッシュを無効にします"
|
||||
"disableFallback" = "フォールバックを無効にする"
|
||||
"disableFallbackDesc" = "フォールバックDNSクエリを無効にします"
|
||||
"disableFallbackIfMatch" = "一致した場合にフォールバックを無効にする"
|
||||
"disableFallbackIfMatchDesc" = "DNSサーバーの一致するドメインリストにヒットした場合、フォールバックDNSクエリを無効にします"
|
||||
"strategy" = "クエリ戦略"
|
||||
"strategyDesc" = "ドメイン名解決の全体的な戦略"
|
||||
"add" = "サーバー追加"
|
||||
@@ -448,7 +475,7 @@
|
||||
"poolSize" = "プールサイズ"
|
||||
|
||||
[pages.settings.security]
|
||||
"admin" = "管理者"
|
||||
"admin" = "管理者の資格情報"
|
||||
"secret" = "セキュリティトークン"
|
||||
"loginSecurity" = "ログインセキュリティ"
|
||||
"loginSecurityDesc" = "追加の認証を追加してセキュリティを向上させる"
|
||||
@@ -491,9 +518,9 @@
|
||||
"status" = "✅ ボットは正常に動作しています!"
|
||||
"usage" = "❗ 検索するテキストを入力してください!"
|
||||
"getID" = "🆔 あなたのIDは:<code>{{ .ID }}</code>"
|
||||
"helpAdminCommands" = "Xray Coreを再起動するには:\r\n<code>/restart force</code>\r\n\r\nクライアントの電子メールを検索するには:\r\n<code>/usage [電子メール]</code>\r\n\r\nインバウンド(クライアントの統計情報を含む)を検索するには:\r\n<code>/inbound [備考]</code>\r\n\r\nTelegramチャットID:\r\n<code>/id</code>"
|
||||
"helpAdminCommands" = "Xray Coreを再起動するには:\r\n<code>/restart</code>\r\n\r\nクライアントの電子メールを検索するには:\r\n<code>/usage [電子メール]</code>\r\n\r\nインバウンド(クライアントの統計情報を含む)を検索するには:\r\n<code>/inbound [備考]</code>\r\n\r\nTelegramチャットID:\r\n<code>/id</code>"
|
||||
"helpClientCommands" = "統計情報を検索するには、次のコマンドを使用してください:\r\n<code>/usage [電子メール]</code>\r\n\r\nTelegramチャットID:\r\n<code>/id</code>"
|
||||
"restartUsage" = "\r\n\r\n<code>/restart force</code>"
|
||||
"restartUsage" = "\r\n\r\n<code>/restart</code>"
|
||||
"restartSuccess" = "✅ 操作成功!"
|
||||
"restartFailed" = "❗ 操作エラー。\r\n\r\n<code>エラー: {{ .Error }}</code>"
|
||||
"xrayNotRunning" = "❗ Xray Core は動作していません。"
|
||||
|
||||
@@ -61,6 +61,10 @@
|
||||
"secAlertPanelURI" = "O caminho URI padrão do painel não é seguro. Configure um caminho URI complexo."
|
||||
"secAlertSubURI" = "O caminho URI padrão de inscrição não é seguro. Configure um caminho URI complexo."
|
||||
"secAlertSubJsonURI" = "O caminho URI JSON de inscrição padrão não é seguro. Configure um caminho URI complexo."
|
||||
"emptyDnsDesc" = "Nenhum servidor DNS adicionado."
|
||||
"emptyFakeDnsDesc" = "Nenhum servidor Fake DNS adicionado."
|
||||
"emptyBalancersDesc" = "Nenhum balanceador adicionado."
|
||||
"emptyReverseDesc" = "Nenhum proxy reverso adicionado."
|
||||
|
||||
[menu]
|
||||
"dashboard" = "Visão Geral"
|
||||
@@ -109,9 +113,10 @@
|
||||
"config" = "Configuração"
|
||||
"backup" = "Backup e Restauração"
|
||||
"backupTitle" = "Backup e Restauração do Banco de Dados"
|
||||
"backupDescription" = "É recomendado fazer um backup antes de restaurar o banco de dados."
|
||||
"exportDatabase" = "Fazer Backup"
|
||||
"exportDatabase" = "Backup"
|
||||
"exportDatabaseDesc" = "Clique para baixar um arquivo .db contendo um backup do seu banco de dados atual para o seu dispositivo."
|
||||
"importDatabase" = "Restaurar"
|
||||
"importDatabaseDesc" = "Clique para selecionar e enviar um arquivo .db do seu dispositivo para restaurar seu banco de dados a partir de um backup."
|
||||
|
||||
[pages.inbounds]
|
||||
"title" = "Inbounds"
|
||||
@@ -190,9 +195,6 @@
|
||||
|
||||
[pages.client]
|
||||
"add" = "Adicionar Cliente"
|
||||
"groupAdd" = "Adicionar usuário de assinatura"
|
||||
"isGroupEdit" = "Edição de grupo"
|
||||
"isGroupEditDesc" = "Todos os clientes com a mesma assinatura são editados"
|
||||
"edit" = "Editar Cliente"
|
||||
"submitAdd" = "Adicionar Cliente"
|
||||
"submitEdit" = "Salvar Alterações"
|
||||
@@ -290,8 +292,6 @@
|
||||
"subSettings" = "Assinatura"
|
||||
"subEnable" = "Ativar Serviço de Assinatura"
|
||||
"subEnableDesc" = "Ativa o serviço de assinatura."
|
||||
"subSyncEnable" = "Habilitar sincronização de assinatura"
|
||||
"subSyncEnableDesc" = "O tráfego de clientes com a mesma assinatura será sincronizado a cada 10 segundos."
|
||||
"subListen" = "IP de Escuta"
|
||||
"subListenDesc" = "O endereço IP para o serviço de assinatura. (deixe em branco para escutar em todos os IPs)"
|
||||
"subPort" = "Porta de Escuta"
|
||||
@@ -312,6 +312,10 @@
|
||||
"subShowInfoDesc" = "O tráfego restante e a data serão exibidos nos aplicativos de cliente."
|
||||
"subURI" = "URI de Proxy Reverso"
|
||||
"subURIDesc" = "O caminho URI da URL de assinatura para uso por trás de proxies."
|
||||
"externalTrafficInformEnable" = "Informações de tráfego externo"
|
||||
"externalTrafficInformEnableDesc" = "Informar a API externa sobre cada atualização de tráfego."
|
||||
"externalTrafficInformURI" = "URI de informação de tráfego externo"
|
||||
"externalTrafficInformURIDesc" = "As atualizações de tráfego são enviadas para este URI."
|
||||
"fragment" = "Fragmentação"
|
||||
"fragmentDesc" = "Ativa a fragmentação para o pacote TLS hello."
|
||||
"fragmentSett" = "Configurações de Fragmentação"
|
||||
@@ -322,7 +326,15 @@
|
||||
"muxSett" = "Configurações de Mux"
|
||||
"direct" = "Conexão Direta"
|
||||
"directDesc" = "Estabelece conexões diretamente com domínios ou intervalos de IP de um país específico."
|
||||
|
||||
"notifications" = "Notificações"
|
||||
"certs" = "Certificados"
|
||||
"externalTraffic" = "Tráfego Externo"
|
||||
"dateAndTime" = "Data e Hora"
|
||||
"proxyAndServer" = "Proxy e Servidor"
|
||||
"intervals" = "Intervalos"
|
||||
"information" = "Informação"
|
||||
"language" = "Idioma"
|
||||
"telegramBotLanguage" = "Idioma do Bot do Telegram"
|
||||
|
||||
[pages.xray]
|
||||
"title" = "Configurações Xray"
|
||||
@@ -373,10 +385,17 @@
|
||||
"errorLogDesc" = "O caminho do arquivo para o log de erros. O valor especial 'none' desativa os logs de erro."
|
||||
"dnsLog" = "Log DNS"
|
||||
"dnsLogDesc" = "Se ativar logs de consulta DNS"
|
||||
"outboundTraffic" = "Tráfego de saída"
|
||||
"outboundTrafficDesc" = "Se deve habilitar o tráfego de saída"
|
||||
"maskAddress" = "Mascarar Endereço"
|
||||
"maskAddressDesc" = "Máscara de endereço IP, quando ativado, substitui automaticamente o endereço IP que aparece no log."
|
||||
"statistics" = "Estatísticas"
|
||||
"statsInboundUplink" = "Estatísticas de Upload de Entrada"
|
||||
"statsInboundUplinkDesc" = "Habilita a coleta de estatísticas para o tráfego de upload de todos os proxies de entrada."
|
||||
"statsInboundDownlink" = "Estatísticas de Download de Entrada"
|
||||
"statsInboundDownlinkDesc" = "Habilita a coleta de estatísticas para o tráfego de download de todos os proxies de entrada."
|
||||
"statsOutboundUplink" = "Estatísticas de Upload de Saída"
|
||||
"statsOutboundUplinkDesc" = "Habilita a coleta de estatísticas para o tráfego de upload de todos os proxies de saída."
|
||||
"statsOutboundDownlink" = "Estatísticas de Download de Saída"
|
||||
"statsOutboundDownlinkDesc" = "Habilita a coleta de estatísticas para o tráfego de download de todos os proxies de saída."
|
||||
|
||||
[pages.xray.rules]
|
||||
"first" = "Primeiro"
|
||||
@@ -434,6 +453,14 @@
|
||||
"enableDesc" = "Ativar o servidor DNS integrado"
|
||||
"tag" = "Tag de Entrada DNS"
|
||||
"tagDesc" = "Esta tag estará disponível como uma tag de Entrada nas regras de roteamento."
|
||||
"clientIp" = "IP do Cliente"
|
||||
"clientIpDesc" = "Usado para notificar o servidor sobre a localização IP especificada durante consultas DNS"
|
||||
"disableCache" = "Desativar cache"
|
||||
"disableCacheDesc" = "Desativa o cache de DNS"
|
||||
"disableFallback" = "Desativar Fallback"
|
||||
"disableFallbackDesc" = "Desativa consultas DNS de fallback"
|
||||
"disableFallbackIfMatch" = "Desativar Fallback Se Corresponder"
|
||||
"disableFallbackIfMatchDesc" = "Desativa consultas DNS de fallback quando a lista de domínios correspondentes do servidor DNS é atingida"
|
||||
"strategy" = "Estratégia de Consulta"
|
||||
"strategyDesc" = "Estratégia geral para resolver nomes de domínio"
|
||||
"add" = "Adicionar Servidor"
|
||||
@@ -448,7 +475,7 @@
|
||||
"poolSize" = "Tamanho do Pool"
|
||||
|
||||
[pages.settings.security]
|
||||
"admin" = "Admin"
|
||||
"admin" = "Credenciais de administrador"
|
||||
"secret" = "Token Secreto"
|
||||
"loginSecurity" = "Login Seguro"
|
||||
"loginSecurityDesc" = "Adiciona uma camada extra de autenticação para fornecer mais segurança."
|
||||
@@ -491,9 +518,9 @@
|
||||
"status" = "✅ Bot está OK!"
|
||||
"usage" = "❗ Por favor, forneça um texto para pesquisar!"
|
||||
"getID" = "🆔 Seu ID: <code>{{ .ID }}</code>"
|
||||
"helpAdminCommands" = "Para reiniciar o Xray Core:\r\n<code>/restart force</code>\r\n\r\nPara pesquisar por um email de cliente:\r\n<code>/usage [Email]</code>\r\n\r\nPara pesquisar por inbounds (com estatísticas do cliente):\r\n<code>/inbound [Remark]</code>\r\n\r\nTelegram Chat ID:\r\n<code>/id</code>"
|
||||
"helpAdminCommands" = "Para reiniciar o Xray Core:\r\n<code>/restart</code>\r\n\r\nPara pesquisar por um email de cliente:\r\n<code>/usage [Email]</code>\r\n\r\nPara pesquisar por inbounds (com estatísticas do cliente):\r\n<code>/inbound [Remark]</code>\r\n\r\nTelegram Chat ID:\r\n<code>/id</code>"
|
||||
"helpClientCommands" = "Para pesquisar por estatísticas, use o seguinte comando:\r\n\r\n<code>/usage [Email]</code>\r\n\r\nTelegram Chat ID:\r\n<code>/id</code>"
|
||||
"restartUsage" = "\r\n\r\n<code>/restart force</code>"
|
||||
"restartUsage" = "\r\n\r\n<code>/restart</code>"
|
||||
"restartSuccess" = "✅ Operação bem-sucedida!"
|
||||
"restartFailed" = "❗ Erro na operação.\r\n\r\n<code>Erro: {{ .Error }}</code>."
|
||||
"xrayNotRunning" = "❗ Xray Core não está em execução."
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
"domainName" = "Домен"
|
||||
"monitor" = "Слушать IP"
|
||||
"certificate" = "Цифровой сертификат"
|
||||
"fail" = "Неудачно"
|
||||
"fail" = "Ошибка"
|
||||
"comment" = "Комментарий"
|
||||
"success" = "Успешно"
|
||||
"getVersion" = "Узнать версию"
|
||||
@@ -61,6 +61,10 @@
|
||||
"secAlertPanelURI" = "URI-путь по умолчанию панели небезопасен. Пожалуйста, настройте сложный URI-путь."
|
||||
"secAlertSubURI" = "URI-путь по умолчанию подписки небезопасен. Пожалуйста, настройте сложный URI-путь."
|
||||
"secAlertSubJsonURI" = "URI-путь по умолчанию для JSON подписки небезопасен. Пожалуйста, настройте сложный URI-путь."
|
||||
"emptyDnsDesc" = "Нет добавленных DNS-серверов."
|
||||
"emptyFakeDnsDesc" = "Нет добавленных Fake DNS-серверов."
|
||||
"emptyBalancersDesc" = "Нет добавленных балансировщиков."
|
||||
"emptyReverseDesc" = "Нет добавленных обратных прокси."
|
||||
|
||||
[menu]
|
||||
"dashboard" = "Статус системы"
|
||||
@@ -89,33 +93,34 @@
|
||||
"xrayStatus" = "Xray"
|
||||
"stopXray" = "Остановить"
|
||||
"restartXray" = "Перезапустить"
|
||||
"xraySwitch" = "Версия"
|
||||
"xraySwitch" = "Выбор версии"
|
||||
"xraySwitchClick" = "Выберите желаемую версию"
|
||||
"xraySwitchClickDesk" = "Выбирайте внимательно, так как старые версии могут быть несовместимы с текущими конфигурациями"
|
||||
"xraySwitchClickDesk" = "Обратите внимание: старые версии могут не поддерживать текущие настройки"
|
||||
"operationHours" = "Время работы системы"
|
||||
"systemLoad" = "Системная нагрузка"
|
||||
"systemLoad" = "Нагрузка на систему"
|
||||
"systemLoadDesc" = "Средняя загрузка системы за последние 1, 5 и 15 минут"
|
||||
"connectionTcpCountDesc" = "Общее количество подключений TCP по всем сетевым картам."
|
||||
"connectionUdpCountDesc" = "Общее количество подключений UDP по всем сетевым картам."
|
||||
"connectionCount" = "Количество соединений"
|
||||
"upSpeed" = "Общая скорость upload для всех сетей"
|
||||
"downSpeed" = "Общая скорость download для всех сетей"
|
||||
"totalSent" = "Общий объем загруженных данных для всех сетей с момента запуска системы"
|
||||
"upSpeed" = "Общая скорость отправки для всех сетей"
|
||||
"downSpeed" = "Общая скорость загрузки для всех сетей"
|
||||
"totalSent" = "Общий объем отправляемых данных с момента запуска системы"
|
||||
"totalReceive" = "Общий объем полученных данных для всех сетей с момента запуска системы."
|
||||
"xraySwitchVersionDialog" = "Переключить версию Xray"
|
||||
"xraySwitchVersionDialogDesc" = "Вы точно хотите сменить версию Xray?"
|
||||
"dontRefresh" = "Идёт установка. Пожалуйста, не обновляйте эту страницу"
|
||||
"dontRefresh" = "Установка в процессе. Не обновляйте страницу"
|
||||
"logs" = "Логи"
|
||||
"config" = "Конфигурация"
|
||||
"backup" = "Бэкап и восстановление"
|
||||
"backupTitle" = "База данных бэкапа и восстановления"
|
||||
"backupDescription" = "Рекомендуется сделать резервную копию перед восстановлением базы данных."
|
||||
"backup" = "Резервное копирование и восстановление"
|
||||
"backupTitle" = "База данных резервных копий"
|
||||
"exportDatabase" = "Экспорт базы данных"
|
||||
"exportDatabaseDesc" = "Нажмите, чтобы скачать файл .db, содержащий резервную копию вашей текущей базы данных на ваше устройство."
|
||||
"importDatabase" = "Импорт базы данных"
|
||||
"importDatabaseDesc" = "Нажмите, чтобы выбрать и загрузить файл .db с вашего устройства для восстановления базы данных из резервной копии."
|
||||
|
||||
[pages.inbounds]
|
||||
"title" = "Подключения"
|
||||
"totalDownUp" = "Всего uploads/downloads"
|
||||
"totalDownUp" = "Общий объем отправленного/полученного трафика"
|
||||
"totalUsage" = "Всего использовано"
|
||||
"inboundCount" = "Количество подключений"
|
||||
"operate" = "Меню"
|
||||
@@ -151,13 +156,13 @@
|
||||
"certificatePath" = "Путь к файлу"
|
||||
"certificateContent" = "Содержимое файла"
|
||||
"publicKey" = "Публичный ключ"
|
||||
"privatekey" = "Приватный ключ"
|
||||
"privatekey" = "Закрытый ключ"
|
||||
"clickOnQRcode" = "Нажмите на QR-код, чтобы скопировать"
|
||||
"client" = "Клиент"
|
||||
"export" = "Экспорт ключей"
|
||||
"clone" = "Клонировать"
|
||||
"cloneInbound" = "Клонировать"
|
||||
"cloneInboundContent" = "Все настройки этого подключения, кроме порта, IP-адреса прослушки и клиентов, будут клонированы"
|
||||
"cloneInboundContent" = "Будут клонированы все настройки подключений, за исключением списка клиентов, порта и IP-адреса прослушивания"
|
||||
"cloneInboundOk" = "Клонировано"
|
||||
"resetAllTraffic" = "Сбросить трафик всех подключений"
|
||||
"resetAllTrafficTitle" = "Сброс трафика всех подключений"
|
||||
@@ -173,8 +178,8 @@
|
||||
"delDepletedClientsContent" = "Вы уверены, что хотите удалить всех отключенных пользователей?"
|
||||
"email" = "Email"
|
||||
"emailDesc" = "Пожалуйста, укажите уникальный Email"
|
||||
"IPLimit" = "Ограничение по IP"
|
||||
"IPLimitDesc" = "Сбросить подключение, если подключено больше введенного значения (введите 0, чтобы отключить ограничение IP-адресов)"
|
||||
"IPLimit" = "Лимит по IP"
|
||||
"IPLimitDesc" = "Ограничение количества подключений с одного IP (0 – отключить)"
|
||||
"IPLimitlog" = "IP лог"
|
||||
"IPLimitlogDesc" = "Лог IP-адресов (перед включением лога IP-адресов, вы должны очистить список)"
|
||||
"IPLimitlogclear" = "Очистить лог"
|
||||
@@ -190,9 +195,6 @@
|
||||
|
||||
[pages.client]
|
||||
"add" = "Добавить пользователя"
|
||||
"groupAdd" = "Добавить пользователя подписки"
|
||||
"isGroupEdit" = "Групповое редактирование"
|
||||
"isGroupEditDesc" = "Все клиенты с одинаковой подпиской редактируются"
|
||||
"edit" = "Редактировать пользователя"
|
||||
"submitAdd" = "Добавить пользователя"
|
||||
"submitEdit" = "Сохранить изменения"
|
||||
@@ -232,18 +234,18 @@
|
||||
"save" = "Сохранить"
|
||||
"infoDesc" = "Каждое выполненное изменение необходимо сохранить. Пожалуйста, перезапустите панель, чтобы изменения вступили в силу"
|
||||
"restartPanel" = "Перезапуск панели"
|
||||
"restartPanelDesc" = "Вы уверены, что хотите перезапустить панель? Нажмите ОК для перезапуска панели через 3 сек. Если вы не можете пользоваться панелью после перезапуска, пожалуйста, посмотрите лог панели на сервере"
|
||||
"restartPanelDesc" = "Вы уверены, что хотите перезапустить панель? Подтвердите, и перезапуск произойдёт через 3 секунды. Если панель станет недоступной, проверьте лог сервера"
|
||||
"actions" = "Действия"
|
||||
"resetDefaultConfig" = "Сбросить на конфигурацию по умолчанию"
|
||||
"resetDefaultConfig" = "Восстановить настройки по умолчанию"
|
||||
"panelSettings" = "Настройки панели"
|
||||
"securitySettings" = "Настройки безопасности"
|
||||
"TGBotSettings" = "Настройки Telegram бота"
|
||||
"panelListeningIP" = "IP-адрес панели"
|
||||
"panelListeningIPDesc" = "Оставьте пустым для подключения с любого IP"
|
||||
"panelListeningDomain" = "Домен прослушивания панели"
|
||||
"panelListeningDomain" = "Домен панели"
|
||||
"panelListeningDomainDesc" = "По умолчанию оставьте пустым, чтобы отслеживать все домены и IP-адреса"
|
||||
"panelPort" = "Порт панели"
|
||||
"panelPortDesc" = "Порт, используемый для отображения этой панели"
|
||||
"panelPortDesc" = "Порт, на котором работает панель"
|
||||
"publicKeyPath" = "Путь к файлу публичного ключа сертификата панели"
|
||||
"publicKeyPathDesc" = "Введите полный путь, начинающийся с '/'"
|
||||
"privateKeyPath" = "Путь к файлу приватного ключа сертификата панели"
|
||||
@@ -255,14 +257,14 @@
|
||||
"remarkModel" = "Модель примечания и символ разделения"
|
||||
"datepicker" = "Выбор даты"
|
||||
"datepickerPlaceholder" = "Выберите дату"
|
||||
"datepickerDescription" = "Запланированные задачи выполняются в соответствии с данным календарём"
|
||||
"datepickerDescription" = "Запланированные задачи будут выполняться в выбранное время"
|
||||
"sampleRemark" = "Пример замечания"
|
||||
"oldUsername" = "Текущий логин"
|
||||
"currentPassword" = "Текущий пароль"
|
||||
"newUsername" = "Новый логин"
|
||||
"newPassword" = "Новый пароль"
|
||||
"telegramBotEnable" = "Включить Telegram бота"
|
||||
"telegramBotEnableDesc" = "Подключайтесь к функциям этой панели через Telegram бота"
|
||||
"telegramBotEnableDesc" = "Получайте доступ к функциям панели через Telegram-бота"
|
||||
"telegramToken" = "Токен Telegram бота"
|
||||
"telegramTokenDesc" = "Необходимо получить токен у менеджера ботов Telegram @botfather"
|
||||
"telegramProxy" = "Прокси Socks5"
|
||||
@@ -272,14 +274,14 @@
|
||||
"telegramChatId" = "Идентификатор Telegram администратора бота"
|
||||
"telegramChatIdDesc" = "Один или несколько идентификаторов администратора бота. Чтобы получить идентификатор, используйте @userinfobot или команду '/id' в боте."
|
||||
"telegramNotifyTime" = "Частота уведомлений бота Telegram"
|
||||
"telegramNotifyTimeDesc" = "Используйте формат времени Crontab"
|
||||
"telegramNotifyTimeDesc" = "Укажите интервал уведомлений в формате Crontab"
|
||||
"tgNotifyBackup" = "Резервное копирование базы данных"
|
||||
"tgNotifyBackupDesc" = "Включать файл резервной копии базы данных с уведомлением об отчете"
|
||||
"tgNotifyBackupDesc" = "Отправлять уведомление с файлом резервной копии базы данных"
|
||||
"tgNotifyLogin" = "Уведомление о входе"
|
||||
"tgNotifyLoginDesc" = "Отображает имя пользователя, IP-адрес и время, когда кто-то пытается войти в вашу панель."
|
||||
"sessionMaxAge" = "Продолжительность сессии"
|
||||
"sessionMaxAgeDesc" = "Продолжительность сессии в системе (значение: минута)"
|
||||
"expireTimeDiff" = "Порог истечения срока сессии для уведомления"
|
||||
"expireTimeDiff" = "Задержка уведомления об истечении сессии"
|
||||
"expireTimeDiffDesc" = "Получение уведомления об истечении срока действия сессии до достижения порогового значения (значение: день)"
|
||||
"trafficDiff" = "Порог трафика для уведомления"
|
||||
"trafficDiffDesc" = "Получение уведомления об исчерпании трафика до достижения порога (значение: ГБ)"
|
||||
@@ -290,8 +292,6 @@
|
||||
"subSettings" = "Подписка"
|
||||
"subEnable" = "Включить службу"
|
||||
"subEnableDesc" = "Функция подписки с отдельной конфигурацией"
|
||||
"subSyncEnable" = "Включить синхронизацию подписки"
|
||||
"subSyncEnableDesc" = "Трафик от клиентов с одинаковой подпиской будет синхронизироваться каждые 10 секунд."
|
||||
"subListen" = "Прослушивание IP"
|
||||
"subListenDesc" = "Оставьте пустым по умолчанию, чтобы отслеживать все IP-адреса"
|
||||
"subPort" = "Порт подписки"
|
||||
@@ -309,20 +309,32 @@
|
||||
"subEncrypt" = "Шифровать конфиги"
|
||||
"subEncryptDesc" = "Шифровать возвращенные конфиги в подписке"
|
||||
"subShowInfo" = "Показать информацию об использовании"
|
||||
"subShowInfoDesc" = "Показывать оставшиеся трафик и дату после имени конфигурации"
|
||||
"subShowInfoDesc" = "Отображать остаток трафика и дату окончания после имени конфигурации"
|
||||
"subURI" = "URI обратного прокси"
|
||||
"subURIDesc" = "Изменить базовый URI URL-адреса подписки для использования за прокси-серверами"
|
||||
"externalTrafficInformEnable" = "Информация о внешнем трафике"
|
||||
"externalTrafficInformEnableDesc" = "Информировать внешний API о каждом обновлении трафика"
|
||||
"externalTrafficInformURI" = "URI информации о внешнем трафике"
|
||||
"externalTrafficInformURIDesc" = "Обновления трафика отправляются на этот URI"
|
||||
"fragment" = "Фрагментация"
|
||||
"fragmentDesc" = "Включить фрагментацию для пакета приветствия TLS"
|
||||
"fragmentDesc" = "Включить фрагментацию TLS-приветствия"
|
||||
"fragmentSett" = "Настройки фрагментации"
|
||||
"noisesDesc" = "Включить Noises."
|
||||
"noisesSett" = "Настройки Noises"
|
||||
"mux" = "Mux"
|
||||
"muxDesc" = "Передача нескольких независимых потоков данных в рамках установленного потока данных."
|
||||
"muxDesc" = "Передача нескольких независимых потоков данных в одном соединении."
|
||||
"muxSett" = "Mux Настройки"
|
||||
"direct" = "Прямая связь"
|
||||
"directDesc" = "Напрямую устанавливает соединения с доменами или диапазонами IP конкретной страны."
|
||||
|
||||
"directDesc" = "Устанавливает прямые соединения с доменами или IP-адресами определённой страны."
|
||||
"notifications" = "Уведомления"
|
||||
"certs" = "Сертификаты"
|
||||
"externalTraffic" = "Внешний трафик"
|
||||
"dateAndTime" = "Дата и время"
|
||||
"proxyAndServer" = "Прокси и сервер"
|
||||
"intervals" = "Интервалы"
|
||||
"information" = "Информация"
|
||||
"language" = "Язык"
|
||||
"telegramBotLanguage" = "Язык Telegram-бота"
|
||||
|
||||
[pages.xray]
|
||||
"title" = "Настройки Xray"
|
||||
@@ -333,7 +345,7 @@
|
||||
"generalConfigs" = "Основные настройки"
|
||||
"generalConfigsDesc" = "Эти параметры описывают общие настройки"
|
||||
"logConfigs" = "Журнал"
|
||||
"logConfigsDesc" = "Журналы могут повлиять на эффективность вашего сервера. Рекомендуется включать их только в случае необходимости!"
|
||||
"logConfigsDesc" = "Логи могут замедлять работу сервера. Включайте только нужные вам виды логов при необходимости!"
|
||||
"blockConfigs" = "Блокировка конфигураций"
|
||||
"blockConfigsDesc" = "Эти параметры не позволят пользователям подключаться к определенным протоколам и веб-сайтам"
|
||||
"basicRouting" = "Базовые соединения"
|
||||
@@ -373,10 +385,17 @@
|
||||
"errorLogDesc" = "Путь к файлу журнала ошибок. Специальное значение «none» отключает журналы ошибок."
|
||||
"dnsLog" = "DNS Журнал"
|
||||
"dnsLogDesc" = "Включить логи запросов DNS"
|
||||
"outboundTraffic" = "Исходящий трафик"
|
||||
"outboundTrafficDesc" = "Включить исходящий трафик"
|
||||
"maskAddress" = "Маскировать Адрес"
|
||||
"maskAddressDesc" = "Маска IP-адреса, при активации автоматически заменяет IP-адрес, который появляется в логе."
|
||||
"maskAddressDesc" = "При активации реальный IP-адрес заменяется на маскировочный в логах."
|
||||
"statistics" = "Статистика"
|
||||
"statsInboundUplink" = "Статистика входящего аплинка"
|
||||
"statsInboundUplinkDesc" = "Включает сбор статистики для исходящего трафика всех входящих прокси."
|
||||
"statsInboundDownlink" = "Статистика входящего даунлинка"
|
||||
"statsInboundDownlinkDesc" = "Включает сбор статистики для входящего трафика всех входящих прокси."
|
||||
"statsOutboundUplink" = "Статистика исходящего аплинка"
|
||||
"statsOutboundUplinkDesc" = "Включает сбор статистики для исходящего трафика всех исходящих прокси."
|
||||
"statsOutboundDownlink" = "Статистика исходящего даунлинка"
|
||||
"statsOutboundDownlinkDesc" = "Включает сбор статистики для входящего трафика всех исходящих прокси."
|
||||
|
||||
[pages.xray.rules]
|
||||
"first" = "Первый"
|
||||
@@ -422,7 +441,7 @@
|
||||
"balancerDesc" = "Невозможно одновременно использовать balancerTag и outboundTag. При одновременном использовании будет работать только outboundTag."
|
||||
|
||||
[pages.xray.wireguard]
|
||||
"secretKey" = "Приватный ключ"
|
||||
"secretKey" = "Закрытый ключ"
|
||||
"publicKey" = "Публичный ключ"
|
||||
"allowedIPs" = "Разрешенные IP-адреса"
|
||||
"endpoint" = "Конечная точка"
|
||||
@@ -434,6 +453,14 @@
|
||||
"enableDesc" = "Включить встроенный DNS-сервер"
|
||||
"tag" = "Входящий тег DNS"
|
||||
"tagDesc" = "Этот тег будет доступен как входящий тег в правилах маршрутизации."
|
||||
"clientIp" = "IP клиента"
|
||||
"clientIpDesc" = "Используется для уведомления сервера о указанном местоположении IP во время DNS-запросов"
|
||||
"disableCache" = "Отключить кэш"
|
||||
"disableCacheDesc" = "Отключает кэширование DNS"
|
||||
"disableFallback" = "Отключить резервный DNS"
|
||||
"disableFallbackDesc" = "Отключает резервные DNS-запросы"
|
||||
"disableFallbackIfMatch" = "Отключить резервный DNS при совпадении"
|
||||
"disableFallbackIfMatchDesc" = "Отключает резервные DNS-запросы при совпадении списка доменов DNS-сервера"
|
||||
"strategy" = "Стратегия запроса"
|
||||
"strategyDesc" = "Общая стратегия разрешения доменных имен"
|
||||
"add" = "Добавить сервер"
|
||||
@@ -448,7 +475,7 @@
|
||||
"poolSize" = "Размер пула"
|
||||
|
||||
[pages.settings.security]
|
||||
"admin" = "Админ"
|
||||
"admin" = "Учетные данные администратора"
|
||||
"secret" = "Секретный токен"
|
||||
"loginSecurity" = "Безопасность входа"
|
||||
"loginSecurityDesc" = "Включить дополнительные меры безопасности входа пользователя"
|
||||
@@ -456,7 +483,7 @@
|
||||
"secretTokenDesc" = "Пожалуйста, скопируйте и сохраните этот токен в безопасном месте. Этот токен необходим для входа в систему и не может быть восстановлен с помощью инструмента x-ui"
|
||||
|
||||
[pages.settings.toasts]
|
||||
"modifySettings" = "Изменение настроек"
|
||||
"modifySettings" = "Настройки изменены"
|
||||
"getSettings" = "Просмотр настроек"
|
||||
"modifyUser" = "Изменение пользователя"
|
||||
"originalUserPassIncorrect" = "Неверное имя пользователя или пароль"
|
||||
@@ -491,9 +518,9 @@
|
||||
"status" = "✅ Бот работает нормально!"
|
||||
"usage" = "❗ Пожалуйста, укажите текст для поиска!"
|
||||
"getID" = "🆔 Ваш ID: <code>{{ .ID }}</code>"
|
||||
"helpAdminCommands" = "Для перезапуска Xray Core:\r\n<code>/restart force</code>\r\n\r\nДля поиска электронной почты клиента:\r\n<code>/usage [Email]</code>\r\n\r\nДля поиска входящих (со статистикой клиента):\r\n<code>/inbound [Примечание]</code>\r\n\r\nID чата Telegram:\r\n<code>/id</code>"
|
||||
"helpAdminCommands" = "Для перезапуска Xray Core:\r\n<code>/restart</code>\r\n\r\nДля поиска электронной почты клиента:\r\n<code>/usage [Email]</code>\r\n\r\nДля поиска входящих (со статистикой клиента):\r\n<code>/inbound [Примечание]</code>\r\n\r\nID чата Telegram:\r\n<code>/id</code>"
|
||||
"helpClientCommands" = "Для поиска статистики используйте следующую команду:\r\n<code>/usage [Email]</code>\r\n\r\nID чата Telegram:\r\n<code>/id</code>"
|
||||
"restartUsage" = "\r\n\r\n<code>/restart force</code>"
|
||||
"restartUsage" = "\r\n\r\n<code>/restart</code>"
|
||||
"restartSuccess" = "✅ Операция успешно завершена!"
|
||||
"restartFailed" = "❗ Ошибка в операции.\r\n\r\n<code>Ошибка: {{ .Error }}</code>."
|
||||
"xrayNotRunning" = "❗ Xray Core не запущен."
|
||||
@@ -580,7 +607,7 @@
|
||||
"allClients" = "Все клиенты"
|
||||
|
||||
[tgbot.answers]
|
||||
"successfulOperation" = "✅ Успешный!"
|
||||
"successfulOperation" = "✅ Успешно!"
|
||||
"errorOperation" = "❗ Ошибка в операции."
|
||||
"getInboundsFailed" = "❌ Не удалось получить входящие потоки."
|
||||
"getClientsFailed" = "❌ Не удалось получить клиентов."
|
||||
@@ -589,8 +616,8 @@
|
||||
"IpRefreshSuccess" = "✅ {{ .Email }}: IP-адреса успешно обновлены."
|
||||
"TGIdRefreshSuccess" = "✅ {{ .Email }}: Пользователь Telegram клиента успешно обновлен."
|
||||
"resetTrafficSuccess" = "✅ {{ .Email }}: Трафик успешно сброшен."
|
||||
"setTrafficLimitSuccess" = "✅ {{ .Email }}: Лимит Трафик успешно сохранен."
|
||||
"expireResetSuccess" = "✅ {{ .Email }}: Дни истечения успешно сброшены."
|
||||
"setTrafficLimitSuccess" = "✅ {{ .Email }}: Лимит трафика успешно установлен."
|
||||
"expireResetSuccess" = "✅ {{ .Email }}: Срок действия успешно сброшен."
|
||||
"resetIpSuccess" = "✅ {{ .Email }}: Лимит IP ({{ .Count }}) успешно сохранен."
|
||||
"clearIpSuccess" = "✅ {{ .Email }}: IP-адреса успешно очищены."
|
||||
"getIpLog" = "✅ {{ .Email }}: Получен лог IP."
|
||||
|
||||
@@ -61,6 +61,10 @@
|
||||
"secAlertPanelURI" = "Panel varsayılan URI yolu güvensiz. Karmaşık bir URI yolu yapılandırın."
|
||||
"secAlertSubURI" = "Abonelik varsayılan URI yolu güvensiz. Karmaşık bir URI yolu yapılandırın."
|
||||
"secAlertSubJsonURI" = "Abonelik JSON varsayılan URI yolu güvensiz. Karmaşık bir URI yolu yapılandırın."
|
||||
"emptyDnsDesc" = "Eklenmiş DNS sunucusu yok."
|
||||
"emptyFakeDnsDesc" = "Eklenmiş Fake DNS sunucusu yok."
|
||||
"emptyBalancersDesc" = "Eklenmiş dengeleyici yok."
|
||||
"emptyReverseDesc" = "Eklenmiş ters proxy yok."
|
||||
|
||||
[menu]
|
||||
"dashboard" = "Genel Bakış"
|
||||
@@ -109,9 +113,10 @@
|
||||
"config" = "Yapılandırma"
|
||||
"backup" = "Yedekle & Geri Yükle"
|
||||
"backupTitle" = "Veritabanı Yedekleme & Geri Yükleme"
|
||||
"backupDescription" = "Veritabanını geri yüklemeden önce yedek almanız önerilir."
|
||||
"exportDatabase" = "Yedekle"
|
||||
"exportDatabaseDesc" = "Mevcut veritabanınızın yedeğini içeren bir .db dosyasını cihazınıza indirmek için tıklayın."
|
||||
"importDatabase" = "Geri Yükle"
|
||||
"importDatabaseDesc" = "Cihazınızdan bir .db dosyası seçip yükleyerek veritabanınızı yedekten geri yüklemek için tıklayın."
|
||||
|
||||
[pages.inbounds]
|
||||
"title" = "Gelenler"
|
||||
@@ -190,9 +195,6 @@
|
||||
|
||||
[pages.client]
|
||||
"add" = "Müşteri Ekle"
|
||||
"groupAdd" = "Abonelik kullanıcısı ekle"
|
||||
"isGroupEdit" = "Grup düzenleme"
|
||||
"isGroupEditDesc" = "Aynı aboneliğe sahip tüm istemciler düzenlendi"
|
||||
"edit" = "Müşteriyi Düzenle"
|
||||
"submitAdd" = "Müşteri Ekle"
|
||||
"submitEdit" = "Değişiklikleri Kaydet"
|
||||
@@ -290,8 +292,6 @@
|
||||
"subSettings" = "Abonelik"
|
||||
"subEnable" = "Abonelik Hizmetini Etkinleştir"
|
||||
"subEnableDesc" = "Abonelik hizmetini etkinleştirir."
|
||||
"subSyncEnable" = "Abonelik Senkronizasyonunu Etkinleştir"
|
||||
"subSyncEnableDesc" = "Aynı aboneliğe sahip istemcilerden gelen trafik her 10 saniyede bir senkronize edilecektir."
|
||||
"subListen" = "Dinleme IP"
|
||||
"subListenDesc" = "Abonelik hizmeti için IP adresi. (tüm IP'leri dinlemek için boş bırakın)"
|
||||
"subPort" = "Dinleme Portu"
|
||||
@@ -312,6 +312,10 @@
|
||||
"subShowInfoDesc" = "Kalan trafik ve tarih müşteri uygulamalarında görüntülenir."
|
||||
"subURI" = "Ters Proxy URI"
|
||||
"subURIDesc" = "Proxy arkasında kullanılacak abonelik URL'sinin URI yolu."
|
||||
"externalTrafficInformEnable" = "Harici Trafik Bilgisi"
|
||||
"externalTrafficInformEnableDesc" = "Her trafik güncellemesinde harici API'yi bilgilendirin."
|
||||
"externalTrafficInformURI" = "Harici Trafik Bilgisi URI'si"
|
||||
"externalTrafficInformURIDesc" = "Trafik güncellemeleri bu URI'ye gönderildi."
|
||||
"fragment" = "Parçalama"
|
||||
"fragmentDesc" = "TLS merhaba paketinin parçalanmasını etkinleştir."
|
||||
"fragmentSett" = "Parçalama Ayarları"
|
||||
@@ -322,7 +326,15 @@
|
||||
"muxSett" = "Mux Ayarları"
|
||||
"direct" = "Doğrudan Bağlantı"
|
||||
"directDesc" = "Belirli bir ülkenin alan adları veya IP aralıkları ile doğrudan bağlantı kurar."
|
||||
|
||||
"notifications" = "Bildirimler"
|
||||
"certs" = "Sertifikalar"
|
||||
"externalTraffic" = "Harici Trafik"
|
||||
"dateAndTime" = "Tarih ve Saat"
|
||||
"proxyAndServer" = "Proxy ve Sunucu"
|
||||
"intervals" = "Aralıklar"
|
||||
"information" = "Bilgi"
|
||||
"language" = "Dil"
|
||||
"telegramBotLanguage" = "Telegram Bot Dili"
|
||||
|
||||
[pages.xray]
|
||||
"title" = "Xray Yapılandırmaları"
|
||||
@@ -373,10 +385,17 @@
|
||||
"errorLogDesc" = "Hata günlüğü için dosya yolu. 'none' özel değeri hata günlüklerini devre dışı bırakır"
|
||||
"dnsLog" = "DNS Günlüğü"
|
||||
"dnsLogDesc" = "DNS sorgu günlüklerini etkinleştirin"
|
||||
"outboundTraffic" = "Gidenler trafiği"
|
||||
"outboundTrafficDesc" = "Çıkış trafiğini etkinleştirip etkinleştirmeyeceğiniz"
|
||||
"maskAddress" = "Adres Maskesi"
|
||||
"maskAddressDesc" = "IP adresi maskesi, etkinleştirildiğinde, günlükte görünen IP adresini otomatik olarak değiştirecektir."
|
||||
"statistics" = "İstatistikler"
|
||||
"statsInboundUplink" = "Gelen Yükleme İstatistikleri"
|
||||
"statsInboundUplinkDesc" = "Tüm gelen proxy'lerin yükleme trafiği için istatistik toplamayı etkinleştirir."
|
||||
"statsInboundDownlink" = "Gelen İndirme İstatistikleri"
|
||||
"statsInboundDownlinkDesc" = "Tüm gelen proxy'lerin indirme trafiği için istatistik toplamayı etkinleştirir."
|
||||
"statsOutboundUplink" = "Giden Yükleme İstatistikleri"
|
||||
"statsOutboundUplinkDesc" = "Tüm giden proxy'lerin yükleme trafiği için istatistik toplamayı etkinleştirir."
|
||||
"statsOutboundDownlink" = "Giden İndirme İstatistikleri"
|
||||
"statsOutboundDownlinkDesc" = "Tüm giden proxy'lerin indirme trafiği için istatistik toplamayı etkinleştirir."
|
||||
|
||||
[pages.xray.rules]
|
||||
"first" = "İlk"
|
||||
@@ -434,6 +453,14 @@
|
||||
"enableDesc" = "Dahili DNS sunucusunu etkinleştir"
|
||||
"tag" = "DNS Gelen Etiketi"
|
||||
"tagDesc" = "Bu etiket, yönlendirme kurallarında Gelen etiketi olarak kullanılabilir."
|
||||
"clientIp" = "İstemci IP"
|
||||
"clientIpDesc" = "DNS sorguları sırasında belirtilen IP konumunu sunucuya bildirmek için kullanılır"
|
||||
"disableCache" = "Önbelleği devre dışı bırak"
|
||||
"disableCacheDesc" = "DNS önbelleğini devre dışı bırakır"
|
||||
"disableFallback" = "Yedeklemeyi devre dışı bırak"
|
||||
"disableFallbackDesc" = "Yedek DNS sorgularını devre dışı bırakır"
|
||||
"disableFallbackIfMatch" = "Eşleşirse Yedeklemeyi Devre Dışı Bırak"
|
||||
"disableFallbackIfMatchDesc" = "DNS sunucusunun eşleşen alan adı listesi vurulduğunda yedek DNS sorgularını devre dışı bırakır"
|
||||
"strategy" = "Sorgu Stratejisi"
|
||||
"strategyDesc" = "Alan adlarını çözmek için genel strateji"
|
||||
"add" = "Sunucu Ekle"
|
||||
@@ -448,7 +475,7 @@
|
||||
"poolSize" = "Havuz Boyutu"
|
||||
|
||||
[pages.settings.security]
|
||||
"admin" = "Yönetici"
|
||||
"admin" = "Yönetici kimlik bilgileri"
|
||||
"secret" = "Gizli Anahtar"
|
||||
"loginSecurity" = "Güvenli Giriş"
|
||||
"loginSecurityDesc" = "Daha fazla güvenlik sağlamak için ek bir kimlik doğrulama katmanı ekler."
|
||||
@@ -491,9 +518,9 @@
|
||||
"status" = "✅ Bot çalışıyor!"
|
||||
"usage" = "❗ Lütfen aramak için bir metin sağlayın!"
|
||||
"getID" = "🆔 Kimliğiniz: <code>{{ .ID }}</code>"
|
||||
"helpAdminCommands" = "Xray Core'u yeniden başlatmak için:\r\n<code>/restart force</code>\r\n\r\nBir müşteri e-postasını aramak için:\r\n<code>/usage [E-posta]</code>\r\n\r\nGelenleri aramak için (müşteri istatistikleri ile):\r\n<code>/inbound [Açıklama]</code>\r\n\r\nTelegram Sohbet Kimliği:\r\n<code>/id</code>"
|
||||
"helpAdminCommands" = "Xray Core'u yeniden başlatmak için:\r\n<code>/restart</code>\r\n\r\nBir müşteri e-postasını aramak için:\r\n<code>/usage [E-posta]</code>\r\n\r\nGelenleri aramak için (müşteri istatistikleri ile):\r\n<code>/inbound [Açıklama]</code>\r\n\r\nTelegram Sohbet Kimliği:\r\n<code>/id</code>"
|
||||
"helpClientCommands" = "İstatistikleri aramak için şu komutu kullanın:\r\n\r\n<code>/usage [E-posta]</code>\r\n\r\nTelegram Sohbet Kimliği:\r\n<code>/id</code>"
|
||||
"restartUsage" = "\r\n\r\n<code>/restart force</code>"
|
||||
"restartUsage" = "\r\n\r\n<code>/restart</code>"
|
||||
"restartSuccess" = "✅ İşlem başarılı!"
|
||||
"restartFailed" = "❗ İşlem hatası.\r\n\r\n<code>Hata: {{ .Error }}</code>."
|
||||
"xrayNotRunning" = "❗ Xray Core çalışmıyor."
|
||||
|
||||
@@ -61,6 +61,10 @@
|
||||
"secAlertPanelURI" = "Стандартний URI-шлях панелі небезпечний. Будь ласка, сконфігуруйте складний URI-шлях."
|
||||
"secAlertSubURI" = "Стандартний URI-шлях підписки небезпечний. Будь ласка, сконфігуруйте складний URI-шлях."
|
||||
"secAlertSubJsonURI" = "Стандартний URI-шлях JSON підписки небезпечний. Будь ласка, сконфігуруйте складний URI-шлях."
|
||||
"emptyDnsDesc" = "Немає доданих DNS-серверів."
|
||||
"emptyFakeDnsDesc" = "Немає доданих Fake DNS-серверів."
|
||||
"emptyBalancersDesc" = "Немає доданих балансувальників."
|
||||
"emptyReverseDesc" = "Немає доданих зворотних проксі."
|
||||
|
||||
[menu]
|
||||
"dashboard" = "Огляд"
|
||||
@@ -109,9 +113,10 @@
|
||||
"config" = "Конфігурація"
|
||||
"backup" = "Резервне копіювання та відновлення"
|
||||
"backupTitle" = "Резервне копіювання та відновлення бази даних"
|
||||
"backupDescription" = "Рекомендується зробити резервну копію перед відновленням бази даних."
|
||||
"exportDatabase" = "Резервне копіювання"
|
||||
"exportDatabase" = "Резервна копія"
|
||||
"exportDatabaseDesc" = "Натисніть, щоб завантажити файл .db, що містить резервну копію вашої поточної бази даних на ваш пристрій."
|
||||
"importDatabase" = "Відновити"
|
||||
"importDatabaseDesc" = "Натисніть, щоб вибрати та завантажити файл .db з вашого пристрою для відновлення бази даних з резервної копії."
|
||||
|
||||
[pages.inbounds]
|
||||
"title" = "Вхідні"
|
||||
@@ -190,9 +195,6 @@
|
||||
|
||||
[pages.client]
|
||||
"add" = "Додати клієнта"
|
||||
"groupAdd" = "Додати підписаного користувача"
|
||||
"isGroupEdit" = "Редагування групи"
|
||||
"isGroupEditDesc" = "Всі клієнти з однаковою підпискою редагуються"
|
||||
"edit" = "Редагувати клієнта"
|
||||
"submitAdd" = "Додати клієнта"
|
||||
"submitEdit" = "Зберегти зміни"
|
||||
@@ -290,8 +292,6 @@
|
||||
"subSettings" = "Підписка"
|
||||
"subEnable" = "Увімкнути службу підписки"
|
||||
"subEnableDesc" = "Вмикає службу підписки."
|
||||
"subSyncEnable" = "Увімкнути синхронізацію підписки"
|
||||
"subSyncEnableDesc" = "Трафік від клієнтів з однаковою підпискою буде синхронізовано кожні 10 секунд."
|
||||
"subListen" = "Слухати IP"
|
||||
"subListenDesc" = "IP-адреса для служби підписки. (залиште порожнім, щоб слухати всі IP-адреси)"
|
||||
"subPort" = "Слухати порт"
|
||||
@@ -312,6 +312,10 @@
|
||||
"subShowInfoDesc" = "Залишок трафіку та дата відображатимуться в клієнтських програмах."
|
||||
"subURI" = "URI зворотного проксі"
|
||||
"subURIDesc" = "URI до URL-адреси підписки для використання за проксі."
|
||||
"externalTrafficInformEnable" = "Інформація про зовнішній трафік"
|
||||
"externalTrafficInformEnableDesc" = "Інформувати зовнішній API про кожне оновлення трафіку."
|
||||
"externalTrafficInformURI" = "Інформаційний URI зовнішнього трафіку"
|
||||
"externalTrafficInformURIDesc" = "Оновлення трафіку надсилаються на цей URI."
|
||||
"fragment" = "Фрагментація"
|
||||
"fragmentDesc" = "Увімкнути фрагментацію для пакету привітання TLS"
|
||||
"fragmentSett" = "Параметри фрагментації"
|
||||
@@ -322,7 +326,15 @@
|
||||
"muxSett" = "Налаштування Mux"
|
||||
"direct" = "Пряме підключення"
|
||||
"directDesc" = "Безпосередньо встановлює з’єднання з доменами або діапазонами IP певної країни."
|
||||
|
||||
"notifications" = "Сповіщення"
|
||||
"certs" = "Сертифікати"
|
||||
"externalTraffic" = "Зовнішній трафік"
|
||||
"dateAndTime" = "Дата та час"
|
||||
"proxyAndServer" = "Проксі та сервер"
|
||||
"intervals" = "Інтервали"
|
||||
"information" = "Інформація"
|
||||
"language" = "Мова"
|
||||
"telegramBotLanguage" = "Мова Telegram-бота"
|
||||
|
||||
[pages.xray]
|
||||
"title" = "Xray конфігурації"
|
||||
@@ -373,10 +385,17 @@
|
||||
"errorLogDesc" = "Шлях до файлу журналу помилок. Спеціальне значення 'none' вимикає журнали помилок"
|
||||
"dnsLog" = "Журнал DNS"
|
||||
"dnsLogDesc" = "Чи включити журнали запитів DNS"
|
||||
"outboundTraffic" = "Вихідний трафік"
|
||||
"outboundTrafficDesc" = "Чи потрібно увімкнути вихідний трафік"
|
||||
"maskAddress" = "Маскувати Адресу"
|
||||
"maskAddressDesc" = "Маска IP-адреси, при активації автоматично замінює IP-адресу, яка з'являється у журналі."
|
||||
"statistics" = "Статистика"
|
||||
"statsInboundUplink" = "Статистика вхідного аплінку"
|
||||
"statsInboundUplinkDesc" = "Увімкнення збору статистики для вхідного трафіку всіх вхідних проксі."
|
||||
"statsInboundDownlink" = "Статистика вхідного даунлінку"
|
||||
"statsInboundDownlinkDesc" = "Увімкнення збору статистики для вихідного трафіку всіх вхідних проксі."
|
||||
"statsOutboundUplink" = "Статистика вихідного аплінку"
|
||||
"statsOutboundUplinkDesc" = "Увімкнення збору статистики для вхідного трафіку всіх вихідних проксі."
|
||||
"statsOutboundDownlink" = "Статистика вихідного даунлінку"
|
||||
"statsOutboundDownlinkDesc" = "Увімкнення збору статистики для вихідного трафіку всіх вихідних проксі."
|
||||
|
||||
[pages.xray.rules]
|
||||
"first" = "Перший"
|
||||
@@ -434,6 +453,14 @@
|
||||
"enableDesc" = "Увімкнути вбудований DNS-сервер"
|
||||
"tag" = "Мітка вхідного DNS"
|
||||
"tagDesc" = "Ця мітка буде доступна як вхідна мітка в правилах маршрутизації."
|
||||
"clientIp" = "IP клієнта"
|
||||
"clientIpDesc" = "Використовується для повідомлення серверу про вказане місцезнаходження IP під час DNS-запитів"
|
||||
"disableCache" = "Вимкнути кеш"
|
||||
"disableCacheDesc" = "Вимкнути кешування DNS"
|
||||
"disableFallback" = "Вимкнути резервний DNS"
|
||||
"disableFallbackDesc" = "Вимкнути резервні DNS-запити"
|
||||
"disableFallbackIfMatch" = "Вимкнути резервний DNS при збігу"
|
||||
"disableFallbackIfMatchDesc" = "Вимкнути резервні DNS-запити при збігу списку доменів DNS-сервера"
|
||||
"strategy" = "Стратегія запиту"
|
||||
"strategyDesc" = "Загальна стратегія вирішення доменних імен"
|
||||
"add" = "Додати сервер"
|
||||
@@ -448,7 +475,7 @@
|
||||
"poolSize" = "Розмір пулу"
|
||||
|
||||
[pages.settings.security]
|
||||
"admin" = "Адміністратор"
|
||||
"admin" = "Облікові дані адміністратора"
|
||||
"secret" = "Секретний маркер"
|
||||
"loginSecurity" = "Безпечний вхід"
|
||||
"loginSecurityDesc" = "Додає додатковий рівень автентифікації для забезпечення більшої безпеки."
|
||||
@@ -491,9 +518,9 @@
|
||||
"status" = "✅ Бот в порядку!"
|
||||
"usage" = "❗ Введіть текст для пошуку!"
|
||||
"getID" = "🆔 Ваш ідентифікатор: <code>{{ .ID }}</code>"
|
||||
"helpAdminCommands" = "Для перезапуску Xray Core:\r\n<code>/restart force</code>\r\n\r\nДля пошуку електронної пошти клієнта:\r\n<code>/usage [Електронна пошта]</code>\r\n\r\nДля пошуку вхідних (зі статистикою клієнта):\r\n<code>/inbound [Примітка]</code>\r\n\r\nID чату Telegram:\r\n<code>/id</code>"
|
||||
"helpAdminCommands" = "Для перезапуску Xray Core:\r\n<code>/restart</code>\r\n\r\nДля пошуку електронної пошти клієнта:\r\n<code>/usage [Електронна пошта]</code>\r\n\r\nДля пошуку вхідних (зі статистикою клієнта):\r\n<code>/inbound [Примітка]</code>\r\n\r\nID чату Telegram:\r\n<code>/id</code>"
|
||||
"helpClientCommands" = "Для пошуку статистики використовуйте наступну команду:\r\n<code>/usage [Електронна пошта]</code>\r\n\r\nID чату Telegram:\r\n<code>/id</code>"
|
||||
"restartUsage" = "\r\n\r\n<code>/restart force</code>"
|
||||
"restartUsage" = "\r\n\r\n<code>/restart</code>"
|
||||
"restartSuccess" = "✅ Операція успішна!"
|
||||
"restartFailed" = "❗ Помилка в операції.\r\n\r\n<code>Помилка: {{ .Error }}</code>."
|
||||
"xrayNotRunning" = "❗ Xray Core не запущений."
|
||||
|
||||
@@ -61,6 +61,10 @@
|
||||
"secAlertPanelURI" = "Đường dẫn URI mặc định của bảng điều khiển không an toàn. Vui lòng cấu hình một đường dẫn URI phức tạp."
|
||||
"secAlertSubURI" = "Đường dẫn URI mặc định của đăng ký không an toàn. Vui lòng cấu hình một đường dẫn URI phức tạp."
|
||||
"secAlertSubJsonURI" = "Đường dẫn URI JSON mặc định của đăng ký không an toàn. Vui lòng cấu hình một đường dẫn URI phức tạp."
|
||||
"emptyDnsDesc" = "Không có máy chủ DNS nào được thêm."
|
||||
"emptyFakeDnsDesc" = "Không có máy chủ Fake DNS nào được thêm."
|
||||
"emptyBalancersDesc" = "Không có bộ cân bằng tải nào được thêm."
|
||||
"emptyReverseDesc" = "Không có proxy ngược nào được thêm."
|
||||
|
||||
[menu]
|
||||
"dashboard" = "Trạng thái hệ thống"
|
||||
@@ -109,9 +113,10 @@
|
||||
"config" = "Cấu hình"
|
||||
"backup" = "Sao lưu & Khôi phục"
|
||||
"backupTitle" = "Sao lưu & Khôi phục Cơ sở dữ liệu"
|
||||
"backupDescription" = "Hãy nhớ sao lưu trước khi nhập cơ sở dữ liệu mới."
|
||||
"exportDatabase" = "Tải về Cơ sở dữ liệu"
|
||||
"importDatabase" = "Tải lên Cơ sở dữ liệu"
|
||||
"exportDatabase" = "Sao lưu"
|
||||
"exportDatabaseDesc" = "Nhấp để tải xuống tệp .db chứa bản sao lưu cơ sở dữ liệu hiện tại của bạn vào thiết bị."
|
||||
"importDatabase" = "Khôi phục"
|
||||
"importDatabaseDesc" = "Nhấp để chọn và tải lên tệp .db từ thiết bị của bạn để khôi phục cơ sở dữ liệu từ bản sao lưu."
|
||||
|
||||
[pages.inbounds]
|
||||
"title" = "Điểm vào (Inbounds)"
|
||||
@@ -190,9 +195,6 @@
|
||||
|
||||
[pages.client]
|
||||
"add" = "Thêm người dùng"
|
||||
"groupAdd" = "Thêm người dùng đăng ký"
|
||||
"isGroupEdit" = "Chỉnh sửa nhóm"
|
||||
"isGroupEditDesc" = "Tất cả người dùng có cùng đăng ký đều có thể được chỉnh sửa"
|
||||
"edit" = "Chỉnh sửa người dùng"
|
||||
"submitAdd" = "Thêm"
|
||||
"submitEdit" = "Lưu thay đổi"
|
||||
@@ -290,8 +292,6 @@
|
||||
"subSettings" = "Gói đăng ký"
|
||||
"subEnable" = "Bật dịch vụ"
|
||||
"subEnableDesc" = "Tính năng gói đăng ký với cấu hình riêng"
|
||||
"subSyncEnable" = "Bật đồng bộ đăng ký"
|
||||
"subSyncEnableDesc" = "Lưu lượng truy cập từ các máy khách có cùng đăng ký sẽ được đồng bộ sau mỗi 10 giây."
|
||||
"subListen" = "Listening IP"
|
||||
"subListenDesc" = "Mặc định để trống để nghe tất cả các IP"
|
||||
"subPort" = "Cổng gói đăng ký"
|
||||
@@ -312,6 +312,10 @@
|
||||
"subShowInfoDesc" = "Hiển thị lưu lượng truy cập còn lại và ngày sau tên cấu hình"
|
||||
"subURI" = "URI proxy trung gian"
|
||||
"subURIDesc" = "Thay đổi URI cơ sở của URL gói đăng ký để sử dụng cho proxy trung gian"
|
||||
"externalTrafficInformEnable" = "Thông báo giao thông bên ngoài"
|
||||
"externalTrafficInformEnableDesc" = "Thông báo cho API bên ngoài về mọi cập nhật lưu lượng truy cập."
|
||||
"externalTrafficInformURI" = "URI thông báo lưu lượng truy cập bên ngoài"
|
||||
"externalTrafficInformURIDesc" = "Cập nhật lưu lượng truy cập được gửi tới URI này."
|
||||
"fragment" = "Sự phân mảnh"
|
||||
"fragmentDesc" = "Kích hoạt phân mảnh cho gói TLS hello"
|
||||
"fragmentSett" = "Cài đặt phân mảnh"
|
||||
@@ -322,7 +326,15 @@
|
||||
"muxSett" = "Mux Cài đặt"
|
||||
"direct" = "Kết nối trực tiếp"
|
||||
"directDesc" = "Trực tiếp thiết lập kết nối với tên miền hoặc dải IP của một quốc gia cụ thể."
|
||||
|
||||
"notifications" = "Thông báo"
|
||||
"certs" = "Chứng chỉ"
|
||||
"externalTraffic" = "Lưu lượng bên ngoài"
|
||||
"dateAndTime" = "Ngày và giờ"
|
||||
"proxyAndServer" = "Proxy và máy chủ"
|
||||
"intervals" = "Khoảng thời gian"
|
||||
"information" = "Thông tin"
|
||||
"language" = "Ngôn ngữ"
|
||||
"telegramBotLanguage" = "Ngôn ngữ của Bot Telegram"
|
||||
|
||||
[pages.xray]
|
||||
"title" = "Cài đặt Xray"
|
||||
@@ -373,10 +385,17 @@
|
||||
"errorLogDesc" = "Đường dẫn tệp cho nhật ký lỗi. Nhật ký lỗi bị vô hiệu hóa có giá trị đặc biệt 'không'"
|
||||
"dnsLog" = "Nhật ký DNS"
|
||||
"dnsLogDesc" = "Có bật nhật ký truy vấn DNS không"
|
||||
"outboundTraffic" = "Lưu lượng đi ra"
|
||||
"outboundTrafficDesc" = "Có nên bật lưu lượng ra không"
|
||||
"maskAddress" = "Ẩn Địa Chỉ"
|
||||
"maskAddressDesc" = "Mặt nạ địa chỉ IP, khi được bật, sẽ tự động thay thế địa chỉ IP xuất hiện trong nhật ký."
|
||||
"statistics" = "Thống kê"
|
||||
"statsInboundUplink" = "Thống kê tải lên đầu vào"
|
||||
"statsInboundUplinkDesc" = "Kích hoạt thu thập thống kê cho lưu lượng tải lên của tất cả các proxy đầu vào."
|
||||
"statsInboundDownlink" = "Thống kê tải xuống đầu vào"
|
||||
"statsInboundDownlinkDesc" = "Kích hoạt thu thập thống kê cho lưu lượng tải xuống của tất cả các proxy đầu vào."
|
||||
"statsOutboundUplink" = "Thống kê tải lên đầu ra"
|
||||
"statsOutboundUplinkDesc" = "Kích hoạt thu thập thống kê cho lưu lượng tải lên của tất cả các proxy đầu ra."
|
||||
"statsOutboundDownlink" = "Thống kê tải xuống đầu ra"
|
||||
"statsOutboundDownlinkDesc" = "Kích hoạt thu thập thống kê cho lưu lượng tải xuống của tất cả các proxy đầu ra."
|
||||
|
||||
[pages.xray.rules]
|
||||
"first" = "Đầu tiên"
|
||||
@@ -434,6 +453,14 @@
|
||||
"enableDesc" = "Kích hoạt máy chủ DNS tích hợp"
|
||||
"tag" = "Thẻ gửi đến DNS"
|
||||
"tagDesc" = "Thẻ này sẽ có sẵn dưới dạng thẻ Gửi đến trong quy tắc định tuyến."
|
||||
"clientIp" = "IP khách hàng"
|
||||
"clientIpDesc" = "Được sử dụng để thông báo cho máy chủ về vị trí IP được chỉ định trong các truy vấn DNS"
|
||||
"disableCache" = "Tắt bộ nhớ đệm"
|
||||
"disableCacheDesc" = "Tắt bộ nhớ đệm DNS"
|
||||
"disableFallback" = "Tắt Fallback"
|
||||
"disableFallbackDesc" = "Tắt các truy vấn DNS Fallback"
|
||||
"disableFallbackIfMatch" = "Tắt Fallback Nếu Khớp"
|
||||
"disableFallbackIfMatchDesc" = "Tắt các truy vấn DNS Fallback khi danh sách tên miền khớp của máy chủ DNS được kích hoạt"
|
||||
"strategy" = "Chiến lược truy vấn"
|
||||
"strategyDesc" = "Chiến lược tổng thể để phân giải tên miền"
|
||||
"add" = "Thêm máy chủ"
|
||||
@@ -448,7 +475,7 @@
|
||||
"poolSize" = "Kích thước bể bơi"
|
||||
|
||||
[pages.settings.security]
|
||||
"admin" = "Quản trị viên"
|
||||
"admin" = "Thông tin đăng nhập quản trị viên"
|
||||
"secret" = "Mã thông báo bí mật"
|
||||
"loginSecurity" = "Bảo mật đăng nhập"
|
||||
"loginSecurityDesc" = "Bật bước bảo mật đăng nhập bổ sung cho người dùng"
|
||||
@@ -491,9 +518,9 @@
|
||||
"status" = "✅ Bot hoạt động bình thường!"
|
||||
"usage" = "❗ Vui lòng cung cấp văn bản để tìm kiếm!"
|
||||
"getID" = "🆔 ID của bạn: <code>{{ .ID }}</code>"
|
||||
"helpAdminCommands" = "Để khởi động lại Xray Core:\r\n<code>/restart force</code>\r\n\r\nĐể tìm kiếm email của khách hàng:\r\n<code>/usage [Email]</code>\r\n\r\nĐể tìm kiếm các nhập (với số liệu thống kê của khách hàng):\r\n<code>/inbound [Ghi chú]</code>\r\n\r\nID Trò chuyện Telegram:\r\n<code>/id</code>"
|
||||
"helpAdminCommands" = "Để khởi động lại Xray Core:\r\n<code>/restart</code>\r\n\r\nĐể tìm kiếm email của khách hàng:\r\n<code>/usage [Email]</code>\r\n\r\nĐể tìm kiếm các nhập (với số liệu thống kê của khách hàng):\r\n<code>/inbound [Ghi chú]</code>\r\n\r\nID Trò chuyện Telegram:\r\n<code>/id</code>"
|
||||
"helpClientCommands" = "Để tìm kiếm thống kê, sử dụng lệnh sau:\r\n<code>/usage [Email]</code>\r\n\r\nID Trò chuyện Telegram:\r\n<code>/id</code>"
|
||||
"restartUsage" = "\r\n\r\n<code>/restart force</code>"
|
||||
"restartUsage" = "\r\n\r\n<code>/restart</code>"
|
||||
"restartSuccess" = "✅ Hoạt động thành công!"
|
||||
"restartFailed" = "❗ Lỗi trong quá trình hoạt động.\r\n\r\n<code>Lỗi: {{ .Error }}</code>."
|
||||
"xrayNotRunning" = "❗ Xray Core không chạy."
|
||||
|
||||
@@ -61,6 +61,10 @@
|
||||
"secAlertPanelURI" = "面板默认 URI 路径不安全。请配置复杂的 URI 路径。"
|
||||
"secAlertSubURI" = "订阅默认 URI 路径不安全。请配置复杂的 URI 路径。"
|
||||
"secAlertSubJsonURI" = "订阅 JSON 默认 URI 路径不安全。请配置复杂的 URI 路径。"
|
||||
"emptyDnsDesc" = "未添加DNS服务器。"
|
||||
"emptyFakeDnsDesc" = "未添加Fake DNS服务器。"
|
||||
"emptyBalancersDesc" = "未添加负载均衡器。"
|
||||
"emptyReverseDesc" = "未添加反向代理。"
|
||||
|
||||
[menu]
|
||||
"dashboard" = "系统状态"
|
||||
@@ -109,9 +113,10 @@
|
||||
"config" = "配置"
|
||||
"backup" = "备份和恢复"
|
||||
"backupTitle" = "备份和恢复数据库"
|
||||
"backupDescription" = "恢复数据库之前建议进行备份"
|
||||
"exportDatabase" = "备份"
|
||||
"exportDatabaseDesc" = "点击下载包含当前数据库备份的 .db 文件到您的设备。"
|
||||
"importDatabase" = "恢复"
|
||||
"importDatabaseDesc" = "点击选择并上传设备中的 .db 文件以从备份恢复数据库。"
|
||||
|
||||
[pages.inbounds]
|
||||
"title" = "入站列表"
|
||||
@@ -190,9 +195,6 @@
|
||||
|
||||
[pages.client]
|
||||
"add" = "添加客户端"
|
||||
"groupAdd" = "添加订阅用户"
|
||||
"isGroupEdit" = "群组编辑"
|
||||
"isGroupEditDesc" = "所有具有相同订阅的客户端均被编辑"
|
||||
"edit" = "编辑客户端"
|
||||
"submitAdd" = "添加客户端"
|
||||
"submitEdit" = "保存修改"
|
||||
@@ -290,8 +292,6 @@
|
||||
"subSettings" = "订阅设置"
|
||||
"subEnable" = "启用订阅服务"
|
||||
"subEnableDesc" = "启用订阅服务功能"
|
||||
"subSyncEnable" = "启用订阅同步"
|
||||
"subSyncEnableDesc" = "具有相同订阅的客户端的流量将每 10 秒同步一次。"
|
||||
"subListen" = "监听 IP"
|
||||
"subListenDesc" = "订阅服务监听的 IP 地址(留空表示监听所有 IP)"
|
||||
"subPort" = "监听端口"
|
||||
@@ -312,6 +312,10 @@
|
||||
"subShowInfoDesc" = "客户端应用中将显示剩余流量和日期信息"
|
||||
"subURI" = "反向代理 URI"
|
||||
"subURIDesc" = "用于代理后面的订阅 URL 的 URI 路径"
|
||||
"externalTrafficInformEnable" = "外部交通通知"
|
||||
"externalTrafficInformEnableDesc" = "每次流量更新时通知外部 API"
|
||||
"externalTrafficInformURI" = "外部流量通知 URI"
|
||||
"externalTrafficInformURIDesc" = "流量更新将发送到此 URI"
|
||||
"fragment" = "分片"
|
||||
"fragmentDesc" = "启用 TLS hello 数据包分片"
|
||||
"fragmentSett" = "设置"
|
||||
@@ -322,7 +326,15 @@
|
||||
"muxSett" = "复用器设置"
|
||||
"direct" = "直接连接"
|
||||
"directDesc" = "直接与特定国家的域或IP范围建立连接"
|
||||
|
||||
"notifications" = "通知"
|
||||
"certs" = "证书"
|
||||
"externalTraffic" = "外部流量"
|
||||
"dateAndTime" = "日期和时间"
|
||||
"proxyAndServer" = "代理和服务器"
|
||||
"intervals" = "间隔"
|
||||
"information" = "信息"
|
||||
"language" = "语言"
|
||||
"telegramBotLanguage" = "Telegram 机器人语言"
|
||||
|
||||
[pages.xray]
|
||||
"title" = "Xray 配置"
|
||||
@@ -373,10 +385,17 @@
|
||||
"errorLogDesc" = "错误日志的文件路径。特殊值 'none' 禁用错误日志"
|
||||
"dnsLog" = "DNS 日志"
|
||||
"dnsLogDesc" = "是否启用 DNS 查询日志"
|
||||
"outboundTraffic" = "出站流量"
|
||||
"outboundTrafficDesc" = "是否启用出站流量"
|
||||
"maskAddress" = "隐藏地址"
|
||||
"maskAddressDesc" = "IP 地址掩码,启用时会自动替换日志中出现的 IP 地址。"
|
||||
"statistics" = "统计"
|
||||
"statsInboundUplink" = "入站上传统计"
|
||||
"statsInboundUplinkDesc" = "启用所有入站代理的上行流量统计收集。"
|
||||
"statsInboundDownlink" = "入站下载统计"
|
||||
"statsInboundDownlinkDesc" = "启用所有入站代理的下行流量统计收集。"
|
||||
"statsOutboundUplink" = "出站上传统计"
|
||||
"statsOutboundUplinkDesc" = "启用所有出站代理的上行流量统计收集。"
|
||||
"statsOutboundDownlink" = "出站下载统计"
|
||||
"statsOutboundDownlinkDesc" = "启用所有出站代理的下行流量统计收集。"
|
||||
|
||||
[pages.xray.rules]
|
||||
"first" = "置顶"
|
||||
@@ -434,6 +453,14 @@
|
||||
"enableDesc" = "启用内置 DNS 服务器"
|
||||
"tag" = "DNS 入站标签"
|
||||
"tagDesc" = "此标签将在路由规则中可用作入站标签"
|
||||
"clientIp" = "客户端IP"
|
||||
"clientIpDesc" = "用于在DNS查询期间通知服务器指定的IP位置"
|
||||
"disableCache" = "禁用缓存"
|
||||
"disableCacheDesc" = "禁用DNS缓存"
|
||||
"disableFallback" = "禁用回退"
|
||||
"disableFallbackDesc" = "禁用回退DNS查询"
|
||||
"disableFallbackIfMatch" = "匹配时禁用回退"
|
||||
"disableFallbackIfMatchDesc" = "当DNS服务器的匹配域名列表命中时,禁用回退DNS查询"
|
||||
"strategy" = "查询策略"
|
||||
"strategyDesc" = "解析域名的总体策略"
|
||||
"add" = "添加服务器"
|
||||
@@ -448,7 +475,7 @@
|
||||
"poolSize" = "池大小"
|
||||
|
||||
[pages.settings.security]
|
||||
"admin" = "管理员"
|
||||
"admin" = "管理员凭据"
|
||||
"secret" = "安全令牌"
|
||||
"loginSecurity" = "登录安全"
|
||||
"loginSecurityDesc" = "添加额外的身份验证以提高安全性"
|
||||
@@ -491,9 +518,9 @@
|
||||
"status" = "✅ 机器人正常运行!"
|
||||
"usage" = "❗ 请输入要搜索的文本!"
|
||||
"getID" = "🆔 您的 ID 为:<code>{{ .ID }}</code>"
|
||||
"helpAdminCommands" = "要重新启动 Xray Core:\r\n<code>/restart force</code>\r\n\r\n要搜索客户电子邮件:\r\n<code>/usage [电子邮件]</code>\r\n\r\n要搜索入站(带有客户统计数据):\r\n<code>/inbound [备注]</code>\r\n\r\nTelegram聊天ID:\r\n<code>/id</code>"
|
||||
"helpAdminCommands" = "要重新启动 Xray Core:\r\n<code>/restart</code>\r\n\r\n要搜索客户电子邮件:\r\n<code>/usage [电子邮件]</code>\r\n\r\n要搜索入站(带有客户统计数据):\r\n<code>/inbound [备注]</code>\r\n\r\nTelegram聊天ID:\r\n<code>/id</code>"
|
||||
"helpClientCommands" = "要搜索统计数据,请使用以下命令:\r\n<code>/usage [电子邮件]</code>\r\n\r\nTelegram聊天ID:\r\n<code>/id</code>"
|
||||
"restartUsage" = "\r\n\r\n<code>/restart force</code>"
|
||||
"restartUsage" = "\r\n\r\n<code>/restart</code>"
|
||||
"restartSuccess" = "✅ 操作成功!"
|
||||
"restartFailed" = "❗ 操作错误。\r\n\r\n<code>错误: {{ .Error }}</code>."
|
||||
"xrayNotRunning" = "❗ Xray Core 未运行。"
|
||||
|
||||
@@ -61,6 +61,10 @@
|
||||
"secAlertPanelURI" = "面板預設 URI 路徑不安全。請配置複雜的 URI 路徑。"
|
||||
"secAlertSubURI" = "訂閱預設 URI 路徑不安全。請配置複雜的 URI 路徑。"
|
||||
"secAlertSubJsonURI" = "訂閱 JSON 預設 URI 路徑不安全。請配置複雜的 URI 路徑。"
|
||||
"emptyDnsDesc" = "未添加DNS伺服器。"
|
||||
"emptyFakeDnsDesc" = "未添加Fake DNS伺服器。"
|
||||
"emptyBalancersDesc" = "未添加負載平衡器。"
|
||||
"emptyReverseDesc" = "未添加反向代理。"
|
||||
|
||||
[menu]
|
||||
"dashboard" = "系統狀態"
|
||||
@@ -109,9 +113,10 @@
|
||||
"config" = "配置"
|
||||
"backup" = "備份和恢復"
|
||||
"backupTitle" = "備份和恢復資料庫"
|
||||
"backupDescription" = "恢復資料庫之前建議進行備份"
|
||||
"exportDatabase" = "備份"
|
||||
"exportDatabaseDesc" = "點擊下載包含當前資料庫備份的 .db 文件到您的設備。"
|
||||
"importDatabase" = "恢復"
|
||||
"importDatabaseDesc" = "點擊選擇並上傳設備中的 .db 文件以從備份恢復資料庫。"
|
||||
|
||||
[pages.inbounds]
|
||||
"title" = "入站列表"
|
||||
@@ -190,9 +195,6 @@
|
||||
|
||||
[pages.client]
|
||||
"add" = "新增客戶端"
|
||||
"groupAdd" = "新增訂閱使用者"
|
||||
"isGroupEdit" = "群組編輯"
|
||||
"isGroupEditDesc" = "所有具有相同訂閱的用戶端都被編輯"
|
||||
"edit" = "編輯客戶端"
|
||||
"submitAdd" = "新增客戶端"
|
||||
"submitEdit" = "儲存修改"
|
||||
@@ -290,8 +292,6 @@
|
||||
"subSettings" = "訂閱設定"
|
||||
"subEnable" = "啟用訂閱服務"
|
||||
"subEnableDesc" = "啟用訂閱服務功能"
|
||||
"subSyncEnable" = "啟用訂閱同步"
|
||||
"subSyncEnableDesc" = "來自具有相同訂閱的客戶端的流量將每 10 秒同步一次。"
|
||||
"subListen" = "監聽 IP"
|
||||
"subListenDesc" = "訂閱服務監聽的 IP 地址(留空表示監聽所有 IP)"
|
||||
"subPort" = "監聽埠"
|
||||
@@ -312,6 +312,10 @@
|
||||
"subShowInfoDesc" = "客戶端應用中將顯示剩餘流量和日期資訊"
|
||||
"subURI" = "反向代理 URI"
|
||||
"subURIDesc" = "用於代理後面的訂閱 URL 的 URI 路徑"
|
||||
"externalTrafficInformEnable" = "外部交通通知"
|
||||
"externalTrafficInformEnableDesc" = "每次流量更新時通知外部 API"
|
||||
"externalTrafficInformURI" = "外部流量通知 URI"
|
||||
"externalTrafficInformURIDesc" = "流量更新將會傳送到此 URI"
|
||||
"fragment" = "分片"
|
||||
"fragmentDesc" = "啟用 TLS hello 資料包分片"
|
||||
"fragmentSett" = "設定"
|
||||
@@ -322,7 +326,15 @@
|
||||
"muxSett" = "複用器設定"
|
||||
"direct" = "直接連線"
|
||||
"directDesc" = "直接與特定國家的域或IP範圍建立連線"
|
||||
|
||||
"notifications" = "通知"
|
||||
"certs" = "證書"
|
||||
"externalTraffic" = "外部流量"
|
||||
"dateAndTime" = "日期和時間"
|
||||
"proxyAndServer" = "代理和伺服器"
|
||||
"intervals" = "間隔"
|
||||
"information" = "資訊"
|
||||
"language" = "語言"
|
||||
"telegramBotLanguage" = "Telegram 機器人語言"
|
||||
|
||||
[pages.xray]
|
||||
"title" = "Xray 配置"
|
||||
@@ -373,10 +385,17 @@
|
||||
"errorLogDesc" = "錯誤日誌的檔案路徑。特殊值 'none' 禁用錯誤日誌"
|
||||
"dnsLog" = "DNS 日誌"
|
||||
"dnsLogDesc" = "是否啟用 DNS 查詢日誌"
|
||||
"outboundTraffic" = "出站流量"
|
||||
"outboundTrafficDesc" = "是否啟用出站流量"
|
||||
"maskAddress" = "隱藏地址"
|
||||
"maskAddressDesc" = "IP 地址掩碼,啟用時會自動替換日誌中出現的 IP 地址。"
|
||||
"statistics" = "統計"
|
||||
"statsInboundUplink" = "入站上傳統計"
|
||||
"statsInboundUplinkDesc" = "啟用所有入站代理的上行流量統計收集。"
|
||||
"statsInboundDownlink" = "入站下載統計"
|
||||
"statsInboundDownlinkDesc" = "啟用所有入站代理的下行流量統計收集。"
|
||||
"statsOutboundUplink" = "出站上傳統計"
|
||||
"statsOutboundUplinkDesc" = "啟用所有出站代理的上行流量統計收集。"
|
||||
"statsOutboundDownlink" = "出站下載統計"
|
||||
"statsOutboundDownlinkDesc" = "啟用所有出站代理的下行流量統計收集。"
|
||||
|
||||
[pages.xray.rules]
|
||||
"first" = "置頂"
|
||||
@@ -434,6 +453,14 @@
|
||||
"enableDesc" = "啟用內建 DNS 伺服器"
|
||||
"tag" = "DNS 入站標籤"
|
||||
"tagDesc" = "此標籤將在路由規則中可用作入站標籤"
|
||||
"clientIp" = "客戶端IP"
|
||||
"clientIpDesc" = "用於在DNS查詢期間通知伺服器指定的IP位置"
|
||||
"disableCache" = "禁用快取"
|
||||
"disableCacheDesc" = "禁用DNS快取"
|
||||
"disableFallback" = "禁用回退"
|
||||
"disableFallbackDesc" = "禁用回退DNS查詢"
|
||||
"disableFallbackIfMatch" = "匹配時禁用回退"
|
||||
"disableFallbackIfMatchDesc" = "當DNS伺服器的匹配域名列表命中時,禁用回退DNS查詢"
|
||||
"strategy" = "查詢策略"
|
||||
"strategyDesc" = "解析域名的總體策略"
|
||||
"add" = "新增伺服器"
|
||||
@@ -448,7 +475,7 @@
|
||||
"poolSize" = "池大小"
|
||||
|
||||
[pages.settings.security]
|
||||
"admin" = "管理員"
|
||||
"admin" = "管理員憑證"
|
||||
"secret" = "安全令牌"
|
||||
"loginSecurity" = "登入安全"
|
||||
"loginSecurityDesc" = "新增額外的身份驗證以提高安全性"
|
||||
@@ -491,9 +518,9 @@
|
||||
"status" = "✅ 機器人正常執行!"
|
||||
"usage" = "❗ 請輸入要搜尋的文字!"
|
||||
"getID" = "🆔 您的 ID 為:<code>{{ .ID }}</code>"
|
||||
"helpAdminCommands" = "要重新啟動 Xray Core:\r\n<code>/restart force</code>\r\n\r\n要搜尋客戶電子郵件:\r\n<code>/usage [電子郵件]</code>\r\n\r\n要搜尋入站(帶有客戶統計資料):\r\n<code>/inbound [備註]</code>\r\n\r\nTelegram聊天ID:\r\n<code>/id</code>"
|
||||
"helpAdminCommands" = "要重新啟動 Xray Core:\r\n<code>/restart</code>\r\n\r\n要搜尋客戶電子郵件:\r\n<code>/usage [電子郵件]</code>\r\n\r\n要搜尋入站(帶有客戶統計資料):\r\n<code>/inbound [備註]</code>\r\n\r\nTelegram聊天ID:\r\n<code>/id</code>"
|
||||
"helpClientCommands" = "要搜尋統計資料,請使用以下命令:\r\n<code>/usage [電子郵件]</code>\r\n\r\nTelegram聊天ID:\r\n<code>/id</code>"
|
||||
"restartUsage" = "\r\n\r\n<code>/restart force</code>"
|
||||
"restartUsage" = "\r\n\r\n<code>/restart</code>"
|
||||
"restartSuccess" = "✅ 操作成功!"
|
||||
"restartFailed" = "❗ 操作錯誤。\r\n\r\n<code>錯誤: {{ .Error }}</code>."
|
||||
"xrayNotRunning" = "❗ Xray Core 未運行。"
|
||||
|
||||
@@ -260,13 +260,6 @@ func (s *Server) startTask() {
|
||||
s.cron.AddJob("@every 10s", job.NewXrayTrafficJob())
|
||||
}()
|
||||
|
||||
isSubEnable, err1 := s.settingService.GetSubEnable()
|
||||
isSubSyncEnable, err2 := s.settingService.GetSubSyncEnable()
|
||||
if err1 == nil && err2 == nil && isSubEnable && isSubSyncEnable {
|
||||
// Sync the client traffic with the same SubId every 10 seconds
|
||||
s.cron.AddJob("@every 10s", job.NewClientTrafficSyncJob())
|
||||
}
|
||||
|
||||
// check client ips from log file every 10 sec
|
||||
s.cron.AddJob("@every 10s", job.NewCheckClientIpJob())
|
||||
|
||||
|
||||
34
x-ui.sh
34
x-ui.sh
@@ -87,6 +87,10 @@ elif [[ "${release}" == "ol" ]]; then
|
||||
if [[ ${os_version} -lt 8 ]]; then
|
||||
echo -e "${red} Please use Oracle Linux 8 or higher ${plain}\n" && exit 1
|
||||
fi
|
||||
elif [[ "${release}" == "virtuozzo" ]]; then
|
||||
if [[ ${os_version} -lt 8 ]]; then
|
||||
echo -e "${red} Please use Virtuozzo Linux 8 or higher ${plain}\n" && exit 1
|
||||
fi
|
||||
else
|
||||
echo -e "${red}Your operating system is not supported by this script.${plain}\n"
|
||||
echo "Please ensure you are using one of the following supported operating systems:"
|
||||
@@ -104,6 +108,7 @@ else
|
||||
echo "- Oracle Linux 8+"
|
||||
echo "- OpenSUSE Tumbleweed"
|
||||
echo "- Amazon Linux 2023"
|
||||
echo "- Virtuozzo Linux 8+"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -180,7 +185,7 @@ update_menu() {
|
||||
return 0
|
||||
fi
|
||||
|
||||
wget --no-check-certificate -O /usr/bin/x-ui https://raw.githubusercontent.com/MHSanaei/3x-ui/main/x-ui.sh
|
||||
wget -O /usr/bin/x-ui https://raw.githubusercontent.com/MHSanaei/3x-ui/main/x-ui.sh
|
||||
chmod +x /usr/local/x-ui/x-ui.sh
|
||||
chmod +x /usr/bin/x-ui
|
||||
|
||||
@@ -280,7 +285,7 @@ reset_webbasepath() {
|
||||
|
||||
# Apply the new web base path setting
|
||||
/usr/local/x-ui/x-ui setting -webBasePath "${config_webBasePath}" >/dev/null 2>&1
|
||||
|
||||
|
||||
echo -e "Web base path has been reset to: ${green}${config_webBasePath}${plain}"
|
||||
echo -e "${green}Please use the new web base path to access the panel.${plain}"
|
||||
restart
|
||||
@@ -440,7 +445,7 @@ show_log() {
|
||||
1)
|
||||
journalctl -u x-ui -e --no-pager -f -p debug
|
||||
if [[ $# == 0 ]]; then
|
||||
before_show_menu
|
||||
before_show_menu
|
||||
fi
|
||||
;;
|
||||
2)
|
||||
@@ -458,9 +463,9 @@ show_log() {
|
||||
|
||||
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
|
||||
@@ -504,7 +509,7 @@ bbr_menu() {
|
||||
disable_bbr
|
||||
bbr_menu
|
||||
;;
|
||||
*)
|
||||
*)
|
||||
echo -e "${red}Invalid option. Please select a valid number.${plain}\n"
|
||||
bbr_menu
|
||||
;;
|
||||
@@ -547,7 +552,7 @@ enable_bbr() {
|
||||
centos | almalinux | rocky | ol)
|
||||
yum -y update && yum -y install ca-certificates
|
||||
;;
|
||||
fedora | amzn)
|
||||
fedora | amzn | virtuozzo)
|
||||
dnf -y update && dnf -y install ca-certificates
|
||||
;;
|
||||
arch | manjaro | parch)
|
||||
@@ -575,14 +580,14 @@ enable_bbr() {
|
||||
}
|
||||
|
||||
update_shell() {
|
||||
wget -O /usr/bin/x-ui -N --no-check-certificate https://github.com/MHSanaei/3x-ui/raw/main/x-ui.sh
|
||||
wget -O /usr/bin/x-ui -N https://github.com/MHSanaei/3x-ui/raw/main/x-ui.sh
|
||||
if [[ $? != 0 ]]; then
|
||||
echo ""
|
||||
LOGE "Failed to download script, Please check whether the machine can connect Github"
|
||||
before_show_menu
|
||||
else
|
||||
chmod +x /usr/bin/x-ui
|
||||
LOGI "Upgrade script succeeded, Please rerun the script"
|
||||
LOGI "Upgrade script succeeded, Please rerun the script"
|
||||
before_show_menu
|
||||
fi
|
||||
}
|
||||
@@ -724,7 +729,7 @@ firewall_menu() {
|
||||
ufw status verbose
|
||||
firewall_menu
|
||||
;;
|
||||
*)
|
||||
*)
|
||||
echo -e "${red}Invalid option. Please select a valid number.${plain}\n"
|
||||
firewall_menu
|
||||
;;
|
||||
@@ -873,7 +878,6 @@ delete_ports() {
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
update_geo() {
|
||||
echo -e "${green}\t1.${plain} Loyalsoldier (geoip.dat, geosite.dat)"
|
||||
echo -e "${green}\t2.${plain} chocolate4u (geoip_IR.dat, geosite_IR.dat)"
|
||||
@@ -1069,7 +1073,7 @@ ssl_cert_issue() {
|
||||
centos | almalinux | rocky | ol)
|
||||
yum -y update && yum -y install socat
|
||||
;;
|
||||
fedora | amzn)
|
||||
fedora | amzn | virtuozzo)
|
||||
dnf -y update && dnf -y install socat
|
||||
;;
|
||||
arch | manjaro | parch)
|
||||
@@ -1537,7 +1541,7 @@ install_iplimit() {
|
||||
yum update -y && yum install epel-release -y
|
||||
yum -y install fail2ban
|
||||
;;
|
||||
fedora | amzn)
|
||||
fedora | amzn | virtuozzo)
|
||||
dnf -y update && dnf -y install fail2ban
|
||||
;;
|
||||
arch | manjaro | parch)
|
||||
@@ -1618,7 +1622,7 @@ remove_iplimit() {
|
||||
yum remove fail2ban -y
|
||||
yum autoremove -y
|
||||
;;
|
||||
fedora | amzn)
|
||||
fedora | amzn | virtuozzo)
|
||||
dnf remove fail2ban -y
|
||||
dnf autoremove -y
|
||||
;;
|
||||
@@ -1912,4 +1916,4 @@ if [[ $# > 0 ]]; then
|
||||
esac
|
||||
else
|
||||
show_menu
|
||||
fi
|
||||
fi
|
||||
|
||||
@@ -2,9 +2,9 @@ package xray
|
||||
|
||||
type ClientTraffic struct {
|
||||
Id int `json:"id" form:"id" gorm:"primaryKey;autoIncrement"`
|
||||
InboundId int `json:"inboundId" form:"inboundId" gorm:"index;not null"`
|
||||
InboundId int `json:"inboundId" form:"inboundId"`
|
||||
Enable bool `json:"enable" form:"enable"`
|
||||
Email string `json:"email" form:"email" gorm:"uniqueIndex"`
|
||||
Email string `json:"email" form:"email" gorm:"unique"`
|
||||
Up int64 `json:"up" form:"up"`
|
||||
Down int64 `json:"down" form:"down"`
|
||||
ExpiryTime int64 `json:"expiryTime" form:"expiryTime"`
|
||||
|
||||
@@ -32,32 +32,39 @@ func (lw *LogWriter) Write(m []byte) (n int, err error) {
|
||||
return len(m), nil
|
||||
}
|
||||
|
||||
regex := regexp.MustCompile(`^(\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2}) \[([^\]]+)\] (.+)$`)
|
||||
messages := strings.Split(message, "\n")
|
||||
regex := regexp.MustCompile(`^(\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2}\.\d{6}) \[([^\]]+)\] (.+)$`)
|
||||
messages := strings.SplitSeq(message, "\n")
|
||||
|
||||
for _, msg := range messages {
|
||||
for msg := range messages {
|
||||
matches := regex.FindStringSubmatch(msg)
|
||||
|
||||
if len(matches) > 3 {
|
||||
level := matches[2]
|
||||
msgBody := matches[3]
|
||||
|
||||
// Map the level to the appropriate logger function
|
||||
switch level {
|
||||
case "Debug":
|
||||
logger.Debug("XRAY: " + msgBody)
|
||||
case "Info":
|
||||
logger.Info("XRAY: " + msgBody)
|
||||
case "Warning":
|
||||
logger.Warning("XRAY: " + msgBody)
|
||||
case "Error":
|
||||
if strings.Contains(strings.ToLower(msgBody), "failed") {
|
||||
logger.Error("XRAY: " + msgBody)
|
||||
default:
|
||||
logger.Debug("XRAY: " + msg)
|
||||
} else {
|
||||
switch level {
|
||||
case "Debug":
|
||||
logger.Debug("XRAY: " + msgBody)
|
||||
case "Info":
|
||||
logger.Info("XRAY: " + msgBody)
|
||||
case "Warning":
|
||||
logger.Warning("XRAY: " + msgBody)
|
||||
case "Error":
|
||||
logger.Error("XRAY: " + msgBody)
|
||||
default:
|
||||
logger.Debug("XRAY: " + msg)
|
||||
}
|
||||
}
|
||||
lw.lastLine = ""
|
||||
} else if msg != "" {
|
||||
logger.Debug("XRAY: " + msg)
|
||||
if strings.Contains(strings.ToLower(msg), "failed") {
|
||||
logger.Error("XRAY: " + msg)
|
||||
} else {
|
||||
logger.Debug("XRAY: " + msg)
|
||||
}
|
||||
lw.lastLine = msg
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user