Compare commits

...

60 Commits

Author SHA1 Message Date
Shishkevich D.
a811225610 fix: protocol checking during random uuidv4 generation
fixes the https://github.com/MHSanaei/3x-ui/issues/2750 issue
2025-03-10 22:09:51 +07:00
mhsanaei
1893c3814d v2.5.5 2025-03-10 14:33:58 +01:00
mhsanaei
422c391f96 Xray log: show failed on error log level 2025-03-10 14:31:06 +01:00
mhsanaei
f7f95ffbae bug fix - xray log 2025-03-10 13:46:46 +01:00
mhsanaei
f408bd7c77 Xray core v2.3.6 + update dependencies 2025-03-10 09:51:31 +01:00
Shishkevich D.
ad13ce6cde fix: generating shortIds for vless reality (#2745) 2025-03-09 19:37:53 +07:00
Shishkevich D.
c35179d924 chore: remove unused variable 2025-03-09 06:38:45 +00:00
Shishkevich D.
cedc7f0fb8 chore: refactoring RandomUtil class
now we use window.crypto.getRandomValues to generate random values.
2025-03-09 06:37:05 +00:00
Shishkevich D.
64fa0e97a3 chore: use crypto.randomUUID() for generating UUIDv4 2025-03-09 06:09:42 +00:00
Shishkevich D.
a45e9de472 chore: use Base64 library for generating SS password 2025-03-09 06:06:27 +00:00
Shishkevich D.
101e9ebf35 fix: modals style 2025-03-09 06:01:27 +00:00
Shishkevich D.
17a76d2843 Revert "chore: add missing params for grpc stream settings (outbound)"
This reverts commit 1c59afe031.
2025-03-09 05:38:34 +00:00
Shishkevich D.
a23a5de540 Revert "chore: add new grpc params for outbound (#2744)"
This reverts commit c49ec9a74c.
2025-03-09 05:37:50 +00:00
Shishkevich D.
a16e83468b chore: add dns type for kcp protocol
see https://xtls.github.io/config/transports/mkcp.html#headerobject
2025-03-09 12:23:35 +07:00
Shishkevich D.
1c59afe031 chore: add missing params for grpc stream settings (outbound) 2025-03-09 04:49:17 +00:00
Shishkevich D.
c49ec9a74c chore: add new grpc params for outbound (#2744) 2025-03-09 11:28:12 +07:00
mhsanaei
a0dd101d97 tgbot - restart
change restart force to restart
2025-03-08 23:08:04 +01:00
mhsanaei
700cf9c10b minor changes 2025-03-08 18:14:48 +01:00
Shishkevich D.
697cd5e6d9 Code refactoring (#2739)
* refactor: switching to the use of typed props

* refactor: `password-input` -> `a-password-input`

* fix: qr modal copy error
2025-03-08 22:41:27 +07:00
Shishkevich D.
c6d27a4463 chore: pretty backup and xray version modal (#2737)
* chore: pretty `backup & restore` modal

* chore: pretty `xray version` modal

* fix: new `xray version` modal style
2025-03-08 20:41:23 +07:00
Shishkevich D.
751f564c4a fix: base64 encoding on vmess/shadowsocks inbounds (#2736) 2025-03-08 12:33:34 +07:00
Shishkevich D.
6658f648e6 refactor: move language manager to utils (#2735) 2025-03-08 09:54:41 +07:00
Shishkevich D.
d6f9f3f6d3 chore: add empty screens for empty data (balancers, reverses, dns) 2025-03-07 22:17:14 +07:00
Shishkevich D.
fad6c497eb chore: add empty screens for empty data (balancers, reverses, dns)
cleaned up some of the margins & paddings
2025-03-07 13:56:03 +00:00
Sanaei
42fa64770b Merge pull request #2732 from shishkevichd/refactor/refactor-1
Code refactoring
2025-03-07 12:40:35 +01:00
Shishkevich D.
0a207b8a2c refactor: merging all util functions into classes 2025-03-07 09:07:23 +00:00
Shishkevich D.
26bf693dbd refactor: move copy function to utils.js 2025-03-07 07:27:33 +00:00
Shishkevich D.
7483fb2ec5 refactor: delete base64js
instead of base64 library you can use built-in JS functions `btoa()` and `atob()`
2025-03-07 07:11:03 +00:00
Shishkevich D.
2d8cca3a2e refactor: delete clipboardjs (#2727)
text copying can be done without using additional libraries
2025-03-06 20:43:46 +01:00
UltraMegaPotato
cf7fec1351 Change the cpu usage interval from second to minute (#2729)
feature(check_cpu_usage): Change the usage interval from second to minute
2025-03-06 20:38:58 +01:00
mhsanaei
361849b9db Balancer fallbackTag #2724 2025-03-06 20:35:17 +01:00
Shishkevich D.
c13db7922e Pretty Panel and Xray settings (#2726)
* chore: refactor `setting-list-item` component

* chore: remove padding

* chore: replace settings list with settings collapse panels

* chore: add missing translations

* chore: fix translation
2025-03-06 11:17:25 +01:00
mhsanaei
d6d05a9b4d v2.5.4 2025-03-05 15:42:51 +01:00
mhsanaei
3caace2cb6 update dependencies 2025-03-05 15:40:27 +01:00
Shishkevich D.
99f26be30d feat: add statistics section (#2718)
the "Outbounds Traffic" parameter, which was misleading, was also renamed and moved
2025-03-05 13:27:25 +01:00
mhsanaei
b0edd24c52 Noise: Add hex 2025-03-05 13:22:07 +01:00
mhsanaei
f0cfd48f66 Sockopt: Add addressPortStrategy 2025-03-05 13:14:17 +01:00
Shishkevich D.
f5aea03765 chore: add global params for DNS (#2713)
parameter list: `disableCache`, `disableFallback`, `disableFallbackIfMatch`, `clientIp`
2025-03-04 14:18:51 +01:00
mhsanaei
14cdde371f better view for uTLS 2025-03-04 12:50:49 +01:00
mhsanaei
99a23f25d5 TLS-REALITY : hide value of private key 2025-03-04 10:36:02 +01:00
Shishkevich D.
653ec90451 chore: pretty dns section in xray settings (#2700) 2025-03-04 09:55:13 +01:00
Shishkevich D.
91a84db479 chore: pretty auth tab in panel settings (#2701) 2025-03-04 09:54:59 +01:00
mhsanaei
0f97eca314 Xray core v25.3.3 2025-03-04 09:54:10 +01:00
mhsanaei
fb79081aa1 TLS fingerprints: randomizednoalpn 2025-03-03 10:20:52 +01:00
mhsanaei
ea19fb8ff6 fix mistake 2025-03-01 10:19:52 +01:00
atarwn
f4cfe9eb63 Improved ru_RU translation (#2677)
* Improved ru_RU translation

Some words have been changed to sound nicer. I made some words less complicated to understand. I also fixed some warnings and messages so they are easier to read.

* Apply suggestions from code review
2025-02-28 10:42:27 +01:00
mhsanaei
4def70a006 Xray core buggy version removed 2025-02-27 13:40:56 +01:00
mhsanaei
e0e9e2681a docker: go 1.24 2025-02-25 18:56:35 +01:00
mhsanaei
31e1581d6b v2.5.3 2025-02-25 18:43:29 +01:00
mhsanaei
018e98a510 Go v1.24.0 2025-02-25 18:43:15 +01:00
mhsanaei
21ea673c30 Make wget verify certificates part2 #2661
Co-Authored-By: İrem Kuyucu <siren@kernal.eu>
2025-02-24 13:15:18 +01:00
Shishkevich D.
08b55da408 feat: add quic protocol in xray rule modal (#2666) 2025-02-24 09:23:59 +01:00
atarwn
7a3ee69a7f Virtuozzo linux support (#2668) 2025-02-24 09:22:34 +01:00
mhsanaei
664bd9b596 bug fix #2660 2025-02-22 14:31:08 +01:00
mhsanaei
ceb1217121 serverNameToVerify to verifyPeerCertInNames #2662 2025-02-22 14:09:52 +01:00
mhsanaei
e754523689 Xray core v25.2.21 2025-02-22 13:46:15 +01:00
İrem Kuyucu
e84503feec Make wget verify certificates (#2661) 2025-02-22 11:53:36 +01:00
AAA
1bbf31df9f feat(externalTrafficJob): External Traffic Inform (#2660)
* Add Setting entity + GUI field in panel settings

* Add a missing 'Traffic' in InformEnabale field

* Add ExternalTrafficURL Post request call

* Add translation + cleanup

* Move options to General tab

---------

Co-authored-by: root <root@vm3562019.stark-industries.solutions>
Co-authored-by: root <root@vm3688062.stark-industries.solutions>
2025-02-22 10:45:14 +01:00
mhsanaei
49bfff9fa5 v2.5.2 2025-02-04 11:38:57 +01:00
Sanaei
d18a1a37ce revert group management (#2656)
* Revert "json post base path bug fixed (#2647)"

This reverts commit 04cf250a54.

* Revert "Group Management of Subscription Clients"

* Revert "fix getSubGroupClients for enable/disable and edit clients."

* Revert "Enhance database initialization in db.go (#2645)"

This reverts commit 66fe84181b.

* Revert "Add checkpoint handling in CloseDB function (#2646)"

This reverts commit 4dd40f6f19.

* Revert "Improved database model migration and added indexing (#2655)"

This reverts commit b922d986d6.
2025-02-04 11:27:58 +01:00
70 changed files with 2818 additions and 2726 deletions

View File

@@ -83,7 +83,7 @@ jobs:
cd x-ui/bin cd x-ui/bin
# Download dependencies # Download dependencies
Xray_URL="https://github.com/XTLS/Xray-core/releases/download/v25.1.30/" Xray_URL="https://github.com/XTLS/Xray-core/releases/download/v25.3.6/"
if [ "${{ matrix.platform }}" == "amd64" ]; then if [ "${{ matrix.platform }}" == "amd64" ]; then
wget -q ${Xray_URL}Xray-linux-64.zip wget -q ${Xray_URL}Xray-linux-64.zip
unzip Xray-linux-64.zip unzip Xray-linux-64.zip

View File

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

View File

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

View File

@@ -1 +1 @@
2.5.1 2.5.5

View File

@@ -26,35 +26,20 @@ const (
) )
func initModels() error { func initModels() error {
// Order matters: first create tables without dependencies models := []interface{}{
baseModels := []interface{}{
&model.User{}, &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.Inbound{},
&model.OutboundTraffics{}, &model.OutboundTraffics{},
&model.Setting{},
&model.InboundClientIps{}, &model.InboundClientIps{},
&xray.ClientTraffic{}, &xray.ClientTraffic{},
} }
for _, model := range models {
for _, model := range dependentModels {
if err := db.AutoMigrate(model); err != nil { 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 err
} }
} }
return nil return nil
} }
@@ -97,31 +82,9 @@ func InitDB(dbPath string) error {
} }
c := &gorm.Config{ c := &gorm.Config{
Logger: gormLogger, Logger: gormLogger,
SkipDefaultTransaction: true,
PrepareStmt: true,
} }
db, err = gorm.Open(sqlite.Open(dbPath), c)
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;")
if err != nil { if err != nil {
return err return err
} }
@@ -138,11 +101,6 @@ func InitDB(dbPath string) error {
func CloseDB() error { func CloseDB() error {
if db != nil { if db != nil {
if err := Checkpoint(); err != nil {
log.Printf("error executing checkpoint: %v", err)
}
sqlDB, err := db.DB() sqlDB, err := db.DB()
if err != nil { if err != nil {
return err return err

View File

@@ -29,14 +29,14 @@ type User struct {
type Inbound struct { type Inbound struct {
Id int `json:"id" form:"id" gorm:"primaryKey;autoIncrement"` Id int `json:"id" form:"id" gorm:"primaryKey;autoIncrement"`
UserId int `json:"-" gorm:"index"` UserId int `json:"-"`
Up int64 `json:"up" form:"up"` Up int64 `json:"up" form:"up"`
Down int64 `json:"down" form:"down"` Down int64 `json:"down" form:"down"`
Total int64 `json:"total" form:"total"` Total int64 `json:"total" form:"total"`
Remark string `json:"remark" form:"remark"` Remark string `json:"remark" form:"remark"`
Enable bool `json:"enable" form:"enable"` Enable bool `json:"enable" form:"enable"`
ExpiryTime int64 `json:"expiryTime" form:"expiryTime"` ExpiryTime int64 `json:"expiryTime" form:"expiryTime"`
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 // config part
Listen string `json:"listen" form:"listen"` Listen string `json:"listen" form:"listen"`

64
go.mod
View File

@@ -1,6 +1,6 @@
module x-ui module x-ui
go 1.23.5 go 1.24.1
require ( require (
github.com/gin-contrib/gzip v1.2.2 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/op/go-logging v0.0.0-20160315200505-970db520ece7
github.com/pelletier/go-toml/v2 v2.2.3 github.com/pelletier/go-toml/v2 v2.2.3
github.com/robfig/cron/v3 v3.0.1 github.com/robfig/cron/v3 v3.0.1
github.com/shirou/gopsutil/v4 v4.25.1 github.com/shirou/gopsutil/v4 v4.25.2
github.com/valyala/fasthttp v1.58.0 github.com/valyala/fasthttp v1.59.0
github.com/xtls/xray-core v1.8.25-0.20250130105737-0a8470cb14eb github.com/xtls/xray-core v1.250306.0
go.uber.org/atomic v1.11.0 go.uber.org/atomic v1.11.0
golang.org/x/text v0.21.0 golang.org/x/text v0.23.0
google.golang.org/grpc v1.70.0 google.golang.org/grpc v1.71.0
gorm.io/driver/sqlite v1.5.7 gorm.io/driver/sqlite v1.5.7
gorm.io/gorm v1.25.12 gorm.io/gorm v1.25.12
) )
require ( require (
github.com/andybalholm/brotli v1.1.1 // indirect github.com/andybalholm/brotli v1.1.1 // indirect
github.com/bytedance/sonic v1.12.8 // indirect github.com/bytedance/sonic v1.13.1 // indirect
github.com/bytedance/sonic/loader v0.2.3 // indirect github.com/bytedance/sonic/loader v0.2.4 // indirect
github.com/cloudflare/circl v1.5.0 // indirect github.com/cloudflare/circl v1.6.0 // indirect
github.com/cloudwego/base64x v0.1.5 // indirect github.com/cloudwego/base64x v0.1.5 // indirect
github.com/dgryski/go-metro v0.0.0-20250106013310-edb8663e5e33 // indirect github.com/dgryski/go-metro v0.0.0-20250106013310-edb8663e5e33 // indirect
github.com/ebitengine/purego v0.8.2 // 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-ole/go-ole v1.3.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.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/go-task/slim-sprig/v3 v3.0.0 // indirect
github.com/google/btree v1.1.3 // 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/context v1.1.2 // indirect
github.com/gorilla/securecookie v1.1.2 // indirect github.com/gorilla/securecookie v1.1.2 // indirect
github.com/gorilla/sessions v1.4.0 // 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/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect github.com/jinzhu/now v1.1.5 // indirect
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.17.11 // indirect github.com/klauspost/compress v1.18.0 // indirect
github.com/klauspost/cpuid/v2 v2.2.9 // indirect github.com/klauspost/cpuid/v2 v2.2.10 // indirect
github.com/kr/text v0.2.0 // indirect github.com/kr/text v0.2.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect
github.com/lufia/plan9stats v0.0.0-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-isatty v0.0.20 // indirect
github.com/mattn/go-sqlite3 v1.14.24 // 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/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
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/pires/go-proxyproto v0.8.0 // indirect
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // 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/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/refraction-networking/utls v1.6.7 // indirect
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
github.com/rogpeppe/go-internal v1.13.1 // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/sagernet/sing v0.5.1 // indirect github.com/sagernet/sing v0.6.3 // indirect
github.com/sagernet/sing-shadowsocks v0.2.7 // indirect github.com/sagernet/sing-shadowsocks v0.2.7 // indirect
github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 // indirect github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 // indirect
github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771 // indirect github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771 // indirect
github.com/tklauser/go-sysconf v0.3.14 // indirect github.com/tklauser/go-sysconf v0.3.15 // indirect
github.com/tklauser/numcpus v0.9.0 // indirect github.com/tklauser/numcpus v0.10.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect github.com/ugorji/go/codec v1.2.12 // indirect
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e // 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 github.com/yusufpapurcu/wmi v1.2.4 // indirect
go.uber.org/mock v0.5.0 // indirect go.uber.org/mock v0.5.0 // indirect
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
golang.org/x/arch v0.13.0 // indirect golang.org/x/arch v0.15.0 // indirect
golang.org/x/crypto v0.32.0 // indirect golang.org/x/crypto v0.36.0 // indirect
golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c // indirect golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect
golang.org/x/mod v0.22.0 // indirect golang.org/x/mod v0.24.0 // indirect
golang.org/x/net v0.34.0 // indirect golang.org/x/net v0.37.0 // indirect
golang.org/x/sync v0.10.0 // indirect golang.org/x/sync v0.12.0 // indirect
golang.org/x/sys v0.29.0 // indirect golang.org/x/sys v0.31.0 // indirect
golang.org/x/time v0.9.0 // indirect golang.org/x/time v0.11.0 // indirect
golang.org/x/tools v0.29.0 // indirect golang.org/x/tools v0.31.0 // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 // 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/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb // indirect
google.golang.org/protobuf v1.36.4 // indirect google.golang.org/protobuf v1.36.5 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
gvisor.dev/gvisor v0.0.0-20240320123526-dc6abceb7ff0 // 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
View File

@@ -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/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 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= 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.13.1 h1:Jyd5CIvdFnkOWuKXr+wm4Nyk2h0yAFsr8ucJgEasO3g=
github.com/bytedance/sonic v1.12.8/go.mod h1:uVvFidNmlt9+wa31S1urfwwthTWteBgG0hWuoKAXTx8= 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.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/bytedance/sonic/loader v0.2.3 h1:yctD0Q3v2NOGfSWPLPvG2ggA2kV6TS6s4wioyEqssH0= github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY=
github.com/bytedance/sonic/loader v0.2.3/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
github.com/cloudflare/circl v1.5.0 h1:hxIWksrX6XN5a1L2TI/h53AGPhNHoUBo+TD1ms9+pys= github.com/cloudflare/circl v1.6.0 h1:cr5JKic4HI+LkINy2lg3W2jF8sHCVTBncJr5gIIq7qk=
github.com/cloudflare/circl v1.5.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= 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 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= 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= 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/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 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= 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.25.0 h1:5Dh7cjvzR7BRZadnsVOzPhWsrwUr0nmsZJxEAnFLNO8=
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/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 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= 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= 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/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 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= 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.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 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.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 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 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-20250302191652-9094ed2288e7 h1:+J3r2e8+RsmN3vKfo75g0YSY61ms37qzPglu4p0sGro=
github.com/google/pprof v0.0.0-20250202011525-fc3143867406/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= 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 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/context v1.1.2 h1:WRkNAv2uoa03QNIc1A6u4O7DAGMUVoopZhkiXWA2V1o= 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/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 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 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.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= 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.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.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8= 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/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 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 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/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 h1:7UMa6KCCMjZEMDtTVdcGu0B1GmmC7QJKiCCjyTAWQy0= github.com/lufia/plan9stats v0.0.0-20250303091104-876f3ea5145d h1:fjMbDVUGsMQiVZnSQsmouYJvMdwsGiDipOZoN66v844=
github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k= 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 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM= 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/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 h1:IxtPxYsR9Gp60cGXjfuR/llTqV8aYMsC472zD0D1vHk=
github.com/nicksnyder/go-i18n/v2 v2.5.1/go.mod h1:DrhgsSDZxoAfvVrBVLXoxZn/pN5TXqaDbq7ju94viiQ= 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.23.0 h1:FA1xjp8ieYDzlgS5ABTpdUDB7wtngggONc8a7ku2NqQ=
github.com/onsi/ginkgo/v2 v2.22.2/go.mod h1:oeMosUL+8LtarXBHu/c0bx2D/K9zyQ6uX3cTyztHwsk= 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 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8=
github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY= github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY=
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88= 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/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 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= 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.50.0 h1:3H/ld1pa3CYhkcc20TPIyG1bNsdhn9qZBGN3b9/UyUo=
github.com/quic-go/quic-go v0.49.0/go.mod h1:s2wDnmCdooUQBmQfpUSTCYBl1/D4FcqbULMMkASvR6s= 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 h1:zVJ7sP1dJx/WtVuITug3qYUq034cDq9B2MR1K67ULZM=
github.com/refraction-networking/utls v1.6.7/go.mod h1:BC3O4vQzye5hqpmDTWUqi4P5DDhzJfkV1tdqtawQIH0= 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 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s= 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 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= 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.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/sagernet/sing v0.5.1 h1:mhL/MZVq0TjuvHcpYcFtmSD1BFOxZ/+8ofbNZcg1k1Y= github.com/sagernet/sing v0.6.3 h1:J1spMc6LMlqUvRjWjvNMAcbvACDneqxB9zxfLuS0UTE=
github.com/sagernet/sing v0.5.1/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= 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 h1:zaopR1tbHEw5Nk6FAkM05wCslV6ahVegEZaKMv9ipx8=
github.com/sagernet/sing-shadowsocks v0.2.7/go.mod h1:0rIKJZBR65Qi0zwdKezt4s57y/Tl1ofkaq6NlkzVuyE= 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 h1:D0vL7YNisV2yqE55+q0lFuGse6U8lxlg7fYTctlT5Gc=
github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38/go.mod h1:sM7Mt7uEoCeFSCBM+qBrqvEo+/9vdmj19wzp3yzUhmg= 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 h1:emzAzMZ1L9iaKCTxdy3Em8Wv4ChIAGnfiz18Cda70g4=
github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg= 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.2 h1:NMscG3l2CqtWFS86kj3vP7soOczqrQYIEhO/pMvvQkk=
github.com/shirou/gopsutil/v4 v4.25.1/go.mod h1:RoUCUpndaJFtT+2zsZzzmhvbfGoDCJ7nFXKJf8GqJbI= 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.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.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 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.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 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 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.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4=
github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY= github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4=
github.com/tklauser/numcpus v0.9.0 h1:lmyCHtANi8aRUgkckBgoDk1nHCux3n2cgkJLXdQGPDo= github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso=
github.com/tklauser/numcpus v0.9.0/go.mod h1:SN6Nq1O3VychhC1npsWostA+oW+VOQTxZrS604NSRyI= 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 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= 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= 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/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e/go.mod h1:5t19P9LBIrNamL6AcMQOncg/r10y3Pc01AbHeMhwlpU=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.58.0 h1:GGB2dWxSbEprU9j0iMJHgdKYJVDyjrOwF9RE59PbRuE= github.com/valyala/fasthttp v1.59.0 h1:Qu0qYHfXvPk1mSLNqcFtEk6DpxgA26hy6bmydotDpRI=
github.com/valyala/fasthttp v1.58.0/go.mod h1:SYXvHHaFp7QZHGKSHmoMipInhrI5StHrhDTYVEjK/Kw= 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 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ=
github.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY= github.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
github.com/vishvananda/netlink v1.3.0 h1:X7l42GfcV4S6E4vHTsw48qbrV+9PVojNfIhZcwQdrZk= 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/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 h1:g1Cj7d+my6k/HHxLAyxPwyX8i7FGRr6ulBDMkBzg2BM=
github.com/xtls/reality v0.0.0-20240909153216-e26ae2305463/go.mod h1:BjIOLmkEEtAgloAiVUcYj0Mt+YU00JARZw8AEU0IwAg= 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.250306.0 h1:XZyZvSgcpAoVEGnFnxNdoHbSF7Kp77A/0TPk4lhv6rM=
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/go.mod h1:clXnUOnX6CKWBGgJY4ePYhb/EtTdSrUC7vPfT6m5p4c=
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.opentelemetry.io/otel v1.32.0 h1:WnBN+Xjcteh0zdk01SVqV55d/m62NJLJdIyb4y/WO5U= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/otel v1.32.0/go.mod h1:00DCVSB0RQcnzlwyTfqtxSm+DRr9hpYrHjNGiBHVQIg= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/otel/metric v1.32.0 h1:xV2umtmNcThh2/a/aCP+h64Xx5wsj8qqnkYZktzNa0M= go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY=
go.opentelemetry.io/otel/metric v1.32.0/go.mod h1:jH7CIbbK6SH2V2wE16W05BHCtIDzauciCRLoc/SyMv8= go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI=
go.opentelemetry.io/otel/sdk v1.32.0 h1:RNxepc9vK59A8XsgZQouW8ue8Gkb4jpWtJm9ge5lEG4= go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ=
go.opentelemetry.io/otel/sdk v1.32.0/go.mod h1:LqgegDBjKMmb2GC6/PrTnteJG39I8/vJCAP9LlJXEjU= go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE=
go.opentelemetry.io/otel/sdk/metric v1.32.0 h1:rZvFnvmvawYb0alrYkjraqJq0Z4ZUJAiyYCU9snn1CU= go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A=
go.opentelemetry.io/otel/sdk/metric v1.32.0/go.mod h1:PWeZlq0zt9YkYAp3gjKZ0eicRYvOh1Gd+X99x6GHpCQ= go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU=
go.opentelemetry.io/otel/trace v1.32.0 h1:WIC9mYrXf8TmY/EXuULKc8hR17vE+Hjv2cssQDe03fM= go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk=
go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8= 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 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= 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 h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
golang.org/x/arch v0.13.0 h1:KCkqVVV1kGg0X87TFysjCJ8MxtZEIU4Ja/yXGeoECdA= golang.org/x/arch v0.15.0 h1:QtOrQd0bTUnhNVNndMpLHNWrDmYzZ2KDqSrEymqInZw=
golang.org/x/arch v0.13.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= golang.org/x/arch v0.15.0/go.mod h1:JmwW7aLIoRUKgaTzhkiEFxvcEiQGyOg9BMonBJUS7EE=
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c h1:KL/ZBHXgKGVmuZBZ01Lt57yE5ws8ZPSkkihmEyq7FXc= golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw=
golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU= golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM=
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 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-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/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.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.10.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.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE= golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU=
golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588= 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 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uIfPMv78iAJGcPKDeqAFnaLBropIC4= 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= 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-20250303144028-a0af3efb3deb h1:TLPQVbx1GJ8VKZxz52VAxl1EBgKXXbTiU9Fc5fZeLn4=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250127172529-29210b9bc287/go.mod h1:8BS3B93F/U1juMFq9+EDk+qOT5CO1R9IzXxG3PTqiRk= google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I=
google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ= google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg=
google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw= google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec=
google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM= google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= 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 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
@@ -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= 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 h1:P+U/06iIKPQ3DLcg+zBfSCia1luZ2msPZrJ8jYDFPs0=
gvisor.dev/gvisor v0.0.0-20240320123526-dc6abceb7ff0/go.mod h1:NQHVAzMwvZ+Qe3ElSiHmq9RUm1MdNHpUZ52fiEqvn+0= 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.4.0 h1:xDbKOZCVbnZsfzM6mHSYcGRHZ3YrLDzqz8XnV4uaD5w=
lukechampine.com/blake3 v1.3.0/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k= lukechampine.com/blake3 v1.4.0/go.mod h1:MQJNQCTnR+kwOP/JEZSxj3MaQjp80FOFSNMMHXcSeX0=
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=

View File

@@ -90,6 +90,10 @@ elif [[ "${release}" == "ol" ]]; then
if [[ ${os_version} -lt 8 ]]; then if [[ ${os_version} -lt 8 ]]; then
echo -e "${red} Please use Oracle Linux 8 or higher ${plain}\n" && exit 1 echo -e "${red} Please use Oracle Linux 8 or higher ${plain}\n" && exit 1
fi 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 else
echo -e "${red}Your operating system is not supported by this script.${plain}\n" 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:" echo "Please ensure you are using one of the following supported operating systems:"
@@ -107,6 +111,7 @@ else
echo "- Oracle Linux 8+" echo "- Oracle Linux 8+"
echo "- OpenSUSE Tumbleweed" echo "- OpenSUSE Tumbleweed"
echo "- Amazon Linux 2023" echo "- Amazon Linux 2023"
echo "- Virtuozzo Linux 8+"
exit 1 exit 1
fi fi
@@ -118,7 +123,7 @@ install_base() {
centos | almalinux | rocky | ol) centos | almalinux | rocky | ol)
yum -y update && yum install -y -q wget curl tar tzdata 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 dnf -y update && dnf install -y -q wget curl tar tzdata
;; ;;
arch | manjaro | parch) arch | manjaro | parch)
@@ -209,7 +214,7 @@ install_x-ui() {
exit 1 exit 1
fi fi
echo -e "Got x-ui latest version: ${tag_version}, beginning the installation..." 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 if [[ $? -ne 0 ]]; then
echo -e "${red}Downloading x-ui failed, please be sure that your server can access GitHub ${plain}" echo -e "${red}Downloading x-ui failed, please be sure that your server can access GitHub ${plain}"
exit 1 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" 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" 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 if [[ $? -ne 0 ]]; then
echo -e "${red}Download x-ui $1 failed, please check if the version exists ${plain}" echo -e "${red}Download x-ui $1 failed, please check if the version exists ${plain}"
exit 1 exit 1
@@ -251,7 +256,7 @@ install_x-ui() {
chmod +x x-ui bin/xray-linux-$(arch) chmod +x x-ui bin/xray-linux-$(arch)
cp -f x-ui.service /etc/systemd/system/ 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/local/x-ui/x-ui.sh
chmod +x /usr/bin/x-ui chmod +x /usr/bin/x-ui
config_after_install config_after_install
@@ -283,4 +288,4 @@ install_x-ui() {
echo -e "${green}Running...${plain}" echo -e "${green}Running...${plain}"
install_base install_base
install_x-ui $1 install_x-ui $1

View File

@@ -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}});

File diff suppressed because one or more lines are too long

View File

@@ -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;
}

View File

@@ -25,11 +25,11 @@ class DBInbound {
} }
get totalGB() { get totalGB() {
return toFixed(this.total / ONE_GB, 2); return NumberFormatter.toFixed(this.total / SizeFormatter.ONE_GB, 2);
} }
set totalGB(gb) { set totalGB(gb) {
this.total = toFixed(gb * ONE_GB, 0); this.total = NumberFormatter.toFixed(gb * SizeFormatter.ONE_GB, 0);
} }
get isVMess() { get isVMess() {

View File

@@ -60,6 +60,7 @@ const UTLS_FINGERPRINT = {
UTLS_QQ: "qq", UTLS_QQ: "qq",
UTLS_RANDOM: "random", UTLS_RANDOM: "random",
UTLS_RANDOMIZED: "randomized", UTLS_RANDOMIZED: "randomized",
UTLS_RONDOMIZEDNOALPN: "randomizednoalpn",
UTLS_UNSAFE: "unsafe", UTLS_UNSAFE: "unsafe",
}; };
@@ -554,7 +555,7 @@ class TlsStreamSettings extends XrayCommonClass {
maxVersion = TLS_VERSION_OPTION.TLS13, maxVersion = TLS_VERSION_OPTION.TLS13,
cipherSuites = '', cipherSuites = '',
rejectUnknownSni = false, rejectUnknownSni = false,
serverNameToVerify = 'dns.google', verifyPeerCertInNames = ['dns.google', 'cloudflare-dns.com'],
disableSystemRoot = false, disableSystemRoot = false,
enableSessionResumption = false, enableSessionResumption = false,
certificates = [new TlsStreamSettings.Cert()], certificates = [new TlsStreamSettings.Cert()],
@@ -567,7 +568,7 @@ class TlsStreamSettings extends XrayCommonClass {
this.maxVersion = maxVersion; this.maxVersion = maxVersion;
this.cipherSuites = cipherSuites; this.cipherSuites = cipherSuites;
this.rejectUnknownSni = rejectUnknownSni; this.rejectUnknownSni = rejectUnknownSni;
this.serverNameToVerify = serverNameToVerify; this.verifyPeerCertInNames = Array.isArray(verifyPeerCertInNames) ? verifyPeerCertInNames.join(",") : verifyPeerCertInNames;
this.disableSystemRoot = disableSystemRoot; this.disableSystemRoot = disableSystemRoot;
this.enableSessionResumption = enableSessionResumption; this.enableSessionResumption = enableSessionResumption;
this.certs = certificates; this.certs = certificates;
@@ -599,7 +600,7 @@ class TlsStreamSettings extends XrayCommonClass {
json.maxVersion, json.maxVersion,
json.cipherSuites, json.cipherSuites,
json.rejectUnknownSni, json.rejectUnknownSni,
json.serverNameToVerify, json.verifyPeerCertInNames,
json.disableSystemRoot, json.disableSystemRoot,
json.enableSessionResumption, json.enableSessionResumption,
certs, certs,
@@ -615,7 +616,7 @@ class TlsStreamSettings extends XrayCommonClass {
maxVersion: this.maxVersion, maxVersion: this.maxVersion,
cipherSuites: this.cipherSuites, cipherSuites: this.cipherSuites,
rejectUnknownSni: this.rejectUnknownSni, rejectUnknownSni: this.rejectUnknownSni,
serverNameToVerify: this.serverNameToVerify, verifyPeerCertInNames: this.verifyPeerCertInNames.split(","),
disableSystemRoot: this.disableSystemRoot, disableSystemRoot: this.disableSystemRoot,
enableSessionResumption: this.enableSessionResumption, enableSessionResumption: this.enableSessionResumption,
certificates: TlsStreamSettings.toJsonArray(this.certs), certificates: TlsStreamSettings.toJsonArray(this.certs),
@@ -1049,7 +1050,7 @@ class Allocate extends XrayCommonClass {
class Inbound extends XrayCommonClass { class Inbound extends XrayCommonClass {
constructor( constructor(
port = RandomUtil.randomIntRange(10000, 60000), port = RandomUtil.randomInteger(10000, 60000),
listen = '', listen = '',
protocol = Protocols.VLESS, protocol = Protocols.VLESS,
settings = null, settings = null,
@@ -1225,7 +1226,7 @@ class Inbound extends XrayCommonClass {
} }
reset() { reset() {
this.port = RandomUtil.randomIntRange(10000, 60000); this.port = RandomUtil.randomInteger(10000, 60000);
this.listen = ''; this.listen = '';
this.protocol = Protocols.VMESS; this.protocol = Protocols.VMESS;
this.settings = Inbound.Settings.getSettings(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) { 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.isSS2022) password.push(settings.password);
if (this.isSSMultiUser) password.push(clientPassword); 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); const url = new URL(link);
for (const [key, value] of params) { for (const [key, value] of params) {
url.searchParams.set(key, value) url.searchParams.set(key, value)
@@ -1836,11 +1837,11 @@ Inbound.VmessSettings.VMESS = class extends XrayCommonClass {
} }
} }
get _totalGB() { get _totalGB() {
return toFixed(this.totalGB / ONE_GB, 2); return NumberFormatter.toFixed(this.totalGB / SizeFormatter.ONE_GB, 2);
} }
set _totalGB(gb) { 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() { get _totalGB() {
return toFixed(this.totalGB / ONE_GB, 2); return NumberFormatter.toFixed(this.totalGB / SizeFormatter.ONE_GB, 2);
} }
set _totalGB(gb) { 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 { Inbound.VLESSSettings.Fallback = class extends XrayCommonClass {
@@ -2098,11 +2099,11 @@ Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
} }
} }
get _totalGB() { get _totalGB() {
return toFixed(this.totalGB / ONE_GB, 2); return NumberFormatter.toFixed(this.totalGB / SizeFormatter.ONE_GB, 2);
} }
set _totalGB(gb) { 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() { get _totalGB() {
return toFixed(this.totalGB / ONE_GB, 2); return NumberFormatter.toFixed(this.totalGB / SizeFormatter.ONE_GB, 2);
} }
set _totalGB(gb) { set _totalGB(gb) {
this.totalGB = toFixed(gb * ONE_GB, 0); this.totalGB = NumberFormatter.toFixed(gb * SizeFormatter.ONE_GB, 0);
} }
}; };

View File

@@ -39,6 +39,7 @@ const UTLS_FINGERPRINT = {
UTLS_QQ: "qq", UTLS_QQ: "qq",
UTLS_RANDOM: "random", UTLS_RANDOM: "random",
UTLS_RANDOMIZED: "randomized", UTLS_RANDOMIZED: "randomized",
UTLS_RONDOMIZEDNOALPN: "randomizednoalpn",
UTLS_UNSAFE: "unsafe", UTLS_UNSAFE: "unsafe",
}; };
@@ -85,6 +86,16 @@ const MODE_OPTION = {
STREAM_ONE: "stream-one", 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(Protocols);
Object.freeze(SSMethods); Object.freeze(SSMethods);
Object.freeze(TLS_FLOW_CONTROL); Object.freeze(TLS_FLOW_CONTROL);
@@ -94,7 +105,7 @@ Object.freeze(OutboundDomainStrategies);
Object.freeze(WireguardDomainStrategy); Object.freeze(WireguardDomainStrategy);
Object.freeze(USERS_SECURITY); Object.freeze(USERS_SECURITY);
Object.freeze(MODE_OPTION); Object.freeze(MODE_OPTION);
Object.freeze(Address_Port_Strategy);
class CommonClass { class CommonClass {
@@ -411,7 +422,8 @@ class SockoptStreamSettings extends CommonClass {
tcpFastOpen = false, tcpFastOpen = false,
tcpKeepAliveInterval = 0, tcpKeepAliveInterval = 0,
tcpMptcp = false, tcpMptcp = false,
penetrate = false penetrate = false,
addressPortStrategy = Address_Port_Strategy.NONE,
) { ) {
super(); super();
this.dialerProxy = dialerProxy; this.dialerProxy = dialerProxy;
@@ -419,6 +431,7 @@ class SockoptStreamSettings extends CommonClass {
this.tcpKeepAliveInterval = tcpKeepAliveInterval; this.tcpKeepAliveInterval = tcpKeepAliveInterval;
this.tcpMptcp = tcpMptcp; this.tcpMptcp = tcpMptcp;
this.penetrate = penetrate; this.penetrate = penetrate;
this.addressPortStrategy = addressPortStrategy;
} }
static fromJson(json = {}) { static fromJson(json = {}) {
@@ -429,6 +442,7 @@ class SockoptStreamSettings extends CommonClass {
json.tcpKeepAliveInterval, json.tcpKeepAliveInterval,
json.tcpMptcp, json.tcpMptcp,
json.penetrate, json.penetrate,
json.addressPortStrategy
); );
} }
@@ -439,6 +453,7 @@ class SockoptStreamSettings extends CommonClass {
tcpKeepAliveInterval: this.tcpKeepAliveInterval, tcpKeepAliveInterval: this.tcpKeepAliveInterval,
tcpMptcp: this.tcpMptcp, tcpMptcp: this.tcpMptcp,
penetrate: this.penetrate, penetrate: this.penetrate,
addressPortStrategy: this.addressPortStrategy
}; };
} }
} }

View File

@@ -26,12 +26,13 @@ class AllSetting {
this.xrayTemplateConfig = ""; this.xrayTemplateConfig = "";
this.secretEnable = false; this.secretEnable = false;
this.subEnable = false; this.subEnable = false;
this.subSyncEnable = true;
this.subListen = ""; this.subListen = "";
this.subPort = 2096; this.subPort = 2096;
this.subPath = "/sub/"; this.subPath = "/sub/";
this.subJsonPath = "/json/"; this.subJsonPath = "/json/";
this.subDomain = ""; this.subDomain = "";
this.externalTrafficInformEnable = false;
this.externalTrafficInformURI = "";
this.subCertFile = ""; this.subCertFile = "";
this.subKeyFile = ""; this.subKeyFile = "";
this.subUpdates = 12; this.subUpdates = 12;

View File

@@ -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}`;
}

View File

@@ -108,14 +108,14 @@ Date.prototype.setMaxTime = function () {
* Formatting date * Formatting date
*/ */
Date.prototype.formatDate = function () { 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 * Format time
*/ */
Date.prototype.formatTime = function () { 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());
}; };
/** /**

View File

@@ -70,41 +70,6 @@ class HttpUtil {
} }
return msg; 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 { class PromiseUtil {
@@ -115,66 +80,68 @@ class PromiseUtil {
} }
} }
const seq = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('');
class RandomUtil { class RandomUtil {
static randomIntRange(min, max) { static getSeq({ type = "default", hasNumbers = true, hasLowercase = true, hasUppercase = true } = {}) {
return Math.floor(Math.random() * (max - min) + min); let seq = '';
}
switch (type) {
static randomInt(n) { case "hex":
return this.randomIntRange(0, n); seq += "0123456789abcdef";
} break;
default:
static randomSeq(count) { if (hasNumbers) seq += "0123456789";
let str = ''; if (hasLowercase) seq += "abcdefghijklmnopqrstuvwxyz";
for (let i = 0; i < count; ++i) { if (hasUppercase) seq += "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
str += seq[this.randomInt(62)]; 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() { static randomShortIds() {
const lengths = [2, 4, 6, 8, 10, 12, 14, 16]; const lengths = [2, 4, 6, 8, 10, 12, 14, 16].sort(() => Math.random() - 0.5);
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]];
}
let shortIds = []; return lengths.map(len => this.randomSeq(len, { type: "hex" })).join(',');
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(',');
} }
static randomLowerAndNum(len) { static randomLowerAndNum(len) {
let str = ''; return this.randomSeq(len, { hasUppercase: false });
for (let i = 0; i < len; ++i) {
str += seq[this.randomInt(36)];
}
return str;
} }
static randomUUID() { static randomUUID() {
const template = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'; if (window.location.protocol === "https:") {
return template.replace(/[xy]/g, function (c) { return window.crypto.randomUUID();
const randomValues = new Uint8Array(1); } else {
crypto.getRandomValues(randomValues); return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'
let randomValue = randomValues[0] % 16; .replace(/[xy]/g, function (c) {
let calculatedValue = (c === 'x') ? randomValue : (randomValue & 0x3 | 0x8); const randomValues = new Uint8Array(1);
return calculatedValue.toString(16); window.crypto.getRandomValues(randomValues);
}); let randomValue = randomValues[0] % 16;
let calculatedValue = (c === 'x') ? randomValue : (randomValue & 0x3 | 0x8);
return calculatedValue.toString(16);
});
}
} }
static randomShadowsocksPassword() { static randomShadowsocksPassword() {
let array = new Uint8Array(32); const array = new Uint8Array(32);
window.crypto.getRandomValues(array); 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) 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;
}
} }

View File

@@ -1,10 +1,10 @@
package controller package controller
import ( import (
"errors"
"encoding/json" "encoding/json"
"fmt" "fmt"
"strconv" "strconv"
"x-ui/database/model" "x-ui/database/model"
"x-ui/web/service" "x-ui/web/service"
"x-ui/web/session" "x-ui/web/session"
@@ -33,13 +33,9 @@ func (a *InboundController) initRouter(g *gin.RouterGroup) {
g.POST("/clientIps/:email", a.getClientIps) g.POST("/clientIps/:email", a.getClientIps)
g.POST("/clearClientIps/:email", a.clearClientIps) g.POST("/clearClientIps/:email", a.clearClientIps)
g.POST("/addClient", a.addInboundClient) g.POST("/addClient", a.addInboundClient)
g.POST("/addGroupClient", a.addGroupInboundClient)
g.POST("/:id/delClient/:clientId", a.delInboundClient) g.POST("/:id/delClient/:clientId", a.delInboundClient)
g.POST("/delGroupClients", a.delGroupClients)
g.POST("/updateClient/:clientId", a.updateInboundClient) g.POST("/updateClient/:clientId", a.updateInboundClient)
g.POST("/updateClients", a.updateGroupInboundClient)
g.POST("/:id/resetClientTraffic/:email", a.resetClientTraffic) g.POST("/:id/resetClientTraffic/:email", a.resetClientTraffic)
g.POST("/resetGroupClientTraffic", a.resetGroupClientTraffic)
g.POST("/resetAllTraffics", a.resetAllTraffics) g.POST("/resetAllTraffics", a.resetAllTraffics)
g.POST("/resetAllClientTraffics/:id", a.resetAllClientTraffics) g.POST("/resetAllClientTraffics/:id", a.resetAllClientTraffics)
g.POST("/delDepletedClients/:id", a.delDepletedClients) 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) { func (a *InboundController) delInboundClient(c *gin.Context) {
id, err := strconv.Atoi(c.Param("id")) id, err := strconv.Atoi(c.Param("id"))
if err != nil { 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) { func (a *InboundController) updateInboundClient(c *gin.Context) {
clientId := c.Param("clientId") 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) { func (a *InboundController) resetClientTraffic(c *gin.Context) {
id, err := strconv.Atoi(c.Param("id")) id, err := strconv.Atoi(c.Param("id"))
if err != nil { 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) { func (a *InboundController) resetAllTraffics(c *gin.Context) {
err := a.inboundService.ResetAllTraffics() err := a.inboundService.ResetAllTraffics()
if err != nil { if err != nil {

View File

@@ -16,48 +16,49 @@ type Msg struct {
} }
type AllSetting struct { type AllSetting struct {
WebListen string `json:"webListen" form:"webListen"` WebListen string `json:"webListen" form:"webListen"`
WebDomain string `json:"webDomain" form:"webDomain"` WebDomain string `json:"webDomain" form:"webDomain"`
WebPort int `json:"webPort" form:"webPort"` WebPort int `json:"webPort" form:"webPort"`
WebCertFile string `json:"webCertFile" form:"webCertFile"` WebCertFile string `json:"webCertFile" form:"webCertFile"`
WebKeyFile string `json:"webKeyFile" form:"webKeyFile"` WebKeyFile string `json:"webKeyFile" form:"webKeyFile"`
WebBasePath string `json:"webBasePath" form:"webBasePath"` WebBasePath string `json:"webBasePath" form:"webBasePath"`
SessionMaxAge int `json:"sessionMaxAge" form:"sessionMaxAge"` SessionMaxAge int `json:"sessionMaxAge" form:"sessionMaxAge"`
PageSize int `json:"pageSize" form:"pageSize"` PageSize int `json:"pageSize" form:"pageSize"`
ExpireDiff int `json:"expireDiff" form:"expireDiff"` ExpireDiff int `json:"expireDiff" form:"expireDiff"`
TrafficDiff int `json:"trafficDiff" form:"trafficDiff"` TrafficDiff int `json:"trafficDiff" form:"trafficDiff"`
RemarkModel string `json:"remarkModel" form:"remarkModel"` RemarkModel string `json:"remarkModel" form:"remarkModel"`
TgBotEnable bool `json:"tgBotEnable" form:"tgBotEnable"` TgBotEnable bool `json:"tgBotEnable" form:"tgBotEnable"`
TgBotToken string `json:"tgBotToken" form:"tgBotToken"` TgBotToken string `json:"tgBotToken" form:"tgBotToken"`
TgBotProxy string `json:"tgBotProxy" form:"tgBotProxy"` TgBotProxy string `json:"tgBotProxy" form:"tgBotProxy"`
TgBotAPIServer string `json:"tgBotAPIServer" form:"tgBotAPIServer"` TgBotAPIServer string `json:"tgBotAPIServer" form:"tgBotAPIServer"`
TgBotChatId string `json:"tgBotChatId" form:"tgBotChatId"` TgBotChatId string `json:"tgBotChatId" form:"tgBotChatId"`
TgRunTime string `json:"tgRunTime" form:"tgRunTime"` TgRunTime string `json:"tgRunTime" form:"tgRunTime"`
TgBotBackup bool `json:"tgBotBackup" form:"tgBotBackup"` TgBotBackup bool `json:"tgBotBackup" form:"tgBotBackup"`
TgBotLoginNotify bool `json:"tgBotLoginNotify" form:"tgBotLoginNotify"` TgBotLoginNotify bool `json:"tgBotLoginNotify" form:"tgBotLoginNotify"`
TgCpu int `json:"tgCpu" form:"tgCpu"` TgCpu int `json:"tgCpu" form:"tgCpu"`
TgLang string `json:"tgLang" form:"tgLang"` TgLang string `json:"tgLang" form:"tgLang"`
TimeLocation string `json:"timeLocation" form:"timeLocation"` TimeLocation string `json:"timeLocation" form:"timeLocation"`
SecretEnable bool `json:"secretEnable" form:"secretEnable"` SecretEnable bool `json:"secretEnable" form:"secretEnable"`
SubEnable bool `json:"subEnable" form:"subEnable"` SubEnable bool `json:"subEnable" form:"subEnable"`
SubSyncEnable bool `json:"subSyncEnable" form:"subSyncEnable"` SubListen string `json:"subListen" form:"subListen"`
SubListen string `json:"subListen" form:"subListen"` SubPort int `json:"subPort" form:"subPort"`
SubPort int `json:"subPort" form:"subPort"` SubPath string `json:"subPath" form:"subPath"`
SubPath string `json:"subPath" form:"subPath"` SubDomain string `json:"subDomain" form:"subDomain"`
SubDomain string `json:"subDomain" form:"subDomain"` SubCertFile string `json:"subCertFile" form:"subCertFile"`
SubCertFile string `json:"subCertFile" form:"subCertFile"` SubKeyFile string `json:"subKeyFile" form:"subKeyFile"`
SubKeyFile string `json:"subKeyFile" form:"subKeyFile"` SubUpdates int `json:"subUpdates" form:"subUpdates"`
SubUpdates int `json:"subUpdates" form:"subUpdates"` ExternalTrafficInformEnable bool `json:"externalTrafficInformEnable" form:"externalTrafficInformEnable"`
SubEncrypt bool `json:"subEncrypt" form:"subEncrypt"` ExternalTrafficInformURI string `json:"externalTrafficInformURI" form:"externalTrafficInformURI"`
SubShowInfo bool `json:"subShowInfo" form:"subShowInfo"` SubEncrypt bool `json:"subEncrypt" form:"subEncrypt"`
SubURI string `json:"subURI" form:"subURI"` SubShowInfo bool `json:"subShowInfo" form:"subShowInfo"`
SubJsonPath string `json:"subJsonPath" form:"subJsonPath"` SubURI string `json:"subURI" form:"subURI"`
SubJsonURI string `json:"subJsonURI" form:"subJsonURI"` SubJsonPath string `json:"subJsonPath" form:"subJsonPath"`
SubJsonFragment string `json:"subJsonFragment" form:"subJsonFragment"` SubJsonURI string `json:"subJsonURI" form:"subJsonURI"`
SubJsonNoises string `json:"subJsonNoises" form:"subJsonNoises"` SubJsonFragment string `json:"subJsonFragment" form:"subJsonFragment"`
SubJsonMux string `json:"subJsonMux" form:"subJsonMux"` SubJsonNoises string `json:"subJsonNoises" form:"subJsonNoises"`
SubJsonRules string `json:"subJsonRules" form:"subJsonRules"` SubJsonMux string `json:"subJsonMux" form:"subJsonMux"`
Datepicker string `json:"datepicker" form:"datepicker"` SubJsonRules string `json:"subJsonRules" form:"subJsonRules"`
Datepicker string `json:"datepicker" form:"datepicker"`
} }
func (s *AllSetting) CheckValid() error { func (s *AllSetting) CheckValid() error {

View File

@@ -5,10 +5,8 @@
<script src="{{ .base_path }}assets/axios/axios.min.js?{{ .cur_ver }}"></script> <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/qs/qs.min.js"></script>
<script src="{{ .base_path }}assets/js/axios-init.js?{{ .cur_ver }}"></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/date-util.js?{{ .cur_ver }}"></script>
<script src="{{ .base_path }}assets/js/util/utils.js?{{ .cur_ver }}"></script> <script src="{{ .base_path }}assets/js/util/index.js?{{ .cur_ver }}"></script>
<script src="{{ .base_path }}assets/js/langs.js"></script>
<script> <script>
const basePath = '{{ .base_path }}'; const basePath = '{{ .base_path }}';
axios.defaults.baseURL = basePath; axios.defaults.baseURL = basePath;

View File

@@ -10,7 +10,7 @@
<a-tag color="purple" class="qr-tag"><span>{{ i18n "pages.settings.subSettings"}}</span></a-tag> <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 class="qr-bg-sub">
<tr-qr-bg-inner class="qr-bg-sub-inner"> <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-inner>
</tr-qr-bg> </tr-qr-bg>
</tr-qr-box> </tr-qr-box>
@@ -18,20 +18,18 @@
<a-tag color="purple" class="qr-tag"><span>{{ i18n "pages.settings.subSettings"}} Json</span></a-tag> <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 class="qr-bg-sub">
<tr-qr-bg-inner class="qr-bg-sub-inner"> <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-inner>
</tr-qr-bg> </tr-qr-bg>
</tr-qr-box> </tr-qr-box>
</template> </template>
<template v-if="!isJustSub"> <template v-for="(row, index) in qrModal.qrcodes">
<template v-for="(row, index) in qrModal.qrcodes"> <tr-qr-box class="qr-box">
<tr-qr-box class="qr-box"> <a-tag color="green" class="qr-tag"><span>[[ row.remark ]]</span></a-tag>
<a-tag color="green" class="qr-tag"><span>[[ row.remark ]]</span></a-tag> <tr-qr-bg class="qr-bg">
<tr-qr-bg class="qr-bg"> <canvas @click="copy(row.link)" :id="'qrCode-'+index" class="qr-cv"></canvas>
<canvas @click="copyToClipboard('qrCode-'+index, row.link)" :id="'qrCode-'+index" class="qr-cv"></canvas> </tr-qr-bg>
</tr-qr-bg> </tr-qr-box>
</tr-qr-box>
</template>
</template> </template>
</tr-qr-modal> </tr-qr-modal>
</a-modal> </a-modal>
@@ -43,16 +41,13 @@
dbInbound: new DBInbound(), dbInbound: new DBInbound(),
client: null, client: null,
qrcodes: [], qrcodes: [],
clipboard: null,
visible: false, visible: false,
isJustSub: false,
subId: '', subId: '',
show: function(title = '', dbInbound, client, isJustSub = false) { show: function(title = '', dbInbound, client) {
this.title = title; this.title = title;
this.dbInbound = dbInbound; this.dbInbound = dbInbound;
this.inbound = dbInbound.toInbound(); this.inbound = dbInbound.toInbound();
this.client = client; this.client = client;
this.isJustSub = isJustSub;
this.subId = ''; this.subId = '';
this.qrcodes = []; this.qrcodes = [];
if (this.inbound.protocol == Protocols.WIREGUARD) { if (this.inbound.protocol == Protocols.WIREGUARD) {
@@ -80,19 +75,15 @@
delimiters: ['[[', ']]'], delimiters: ['[[', ']]'],
el: '#qrcode-modal', el: '#qrcode-modal',
data: { data: {
qrModal: qrModal,get isJustSub(){ qrModal: qrModal,
return qrModal.isJustSub
}
}, },
methods: { methods: {
copyToClipboard(elementId, content) { copy(content) {
this.qrModal.clipboard = new ClipboardJS('#' + elementId, { ClipboardManager
text: () => content, .copyText(content)
}); .then(() => {
this.qrModal.clipboard.on('success', () => { app.$message.success('{{ i18n "copied" }}')
app.$message.success('{{ i18n "copied" }}') })
this.qrModal.clipboard.destroy();
});
}, },
setQrCode(elementId, content) { setQrCode(elementId, content) {
new QRious({ new QRious({

View File

@@ -7,7 +7,7 @@
:href="'data:application/text;charset=utf-8,' + encodeURIComponent(txtModal.content)" :href="'data:application/text;charset=utf-8,' + encodeURIComponent(txtModal.content)"
:download="txtModal.fileName">[[ txtModal.fileName ]] :download="txtModal.fileName">[[ txtModal.fileName ]]
</a-button> </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> </template>
<a-input style="overflow-y: auto;" type="textarea" v-model="txtModal.content" <a-input style="overflow-y: auto;" type="textarea" v-model="txtModal.content"
:autosize="{ minRows: 10, maxRows: 20}"></a-input> :autosize="{ minRows: 10, maxRows: 20}"></a-input>
@@ -20,24 +20,20 @@
content: '', content: '',
fileName: '', fileName: '',
qrcode: null, qrcode: null,
clipboard: null,
visible: false, visible: false,
show: function (title = '', content = '', fileName = '') { show: function (title = '', content = '', fileName = '') {
this.title = title; this.title = title;
this.content = content; this.content = content;
this.fileName = fileName; this.fileName = fileName;
this.visible = true; this.visible = true;
textModalApp.$nextTick(() => { },
if (this.clipboard === null) { copy: function (content = '') {
this.clipboard = new ClipboardJS('#copy-btn', { ClipboardManager
text: () => this.content, .copyText(content)
}); .then(() => {
this.clipboard.on('success', () => { app.$message.success('{{ i18n "copied" }}')
app.$message.success('{{ i18n "copied" }}') this.close();
this.close(); })
});
}
});
}, },
close: function () { close: function () {
this.visible = false; this.visible = false;

View File

@@ -422,16 +422,16 @@
</a-input> </a-input>
</a-form-item> </a-form-item>
<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" }}' placeholder='{{ i18n "password" }}'
@keydown.enter.native="login"> @keydown.enter.native="login">
</password-input> </a-password-input>
</a-form-item> </a-form-item>
<a-form-item v-if="secretEnable"> <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" }}' placeholder='{{ i18n "secretToken" }}'
@keydown.enter.native="login"> @keydown.enter.native="login">
</password-input> </a-password-input>
</a-form-item> </a-form-item>
<a-form-item> <a-form-item>
<a-row justify="center" class="centered"> <a-row justify="center" class="centered">
@@ -449,9 +449,9 @@
<a-row justify="center" class="centered"> <a-row justify="center" class="centered">
<a-col :span="24"> <a-col :span="24">
<a-select ref="selectLang" v-model="lang" <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"> :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 role="img" aria-label="l.name" v-text="l.icon"></span>
&nbsp;&nbsp;<span v-text="l.name"></span> &nbsp;&nbsp;<span v-text="l.name"></span>
</a-select-option> </a-select-option>
@@ -461,7 +461,7 @@
</a-form-item> </a-form-item>
<a-form-item> <a-form-item>
<a-row justify="center" class="centered"> <a-row justify="center" class="centered">
<theme-switch-login></theme-switch-login> <a-theme-switch-login></a-theme-switch-login>
</a-row> </a-row>
</a-form-item> </a-form-item>
</a-form> </a-form>
@@ -493,7 +493,7 @@
lang: "" lang: ""
}, },
async created() { async created() {
this.lang = getLang(); this.lang = LanguageManager.getLanguage();
this.secretEnable = await this.getSecretStatus(); this.secretEnable = await this.getSecretStatus();
}, },
methods: { methods: {

View File

@@ -106,9 +106,9 @@
<a-date-picker v-if="datepicker == 'gregorian'" :show-time="{ format: 'HH:mm:ss' }" <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" format="YYYY-MM-DD HH:mm:ss" :dropdown-class-name="themeSwitcher.currentTheme"
v-model="clientsBulkModal.expiryTime"></a-date-picker> 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"> value="clientsBulkModal.expiryTime" v-model="clientsBulkModal.expiryTime">
</persian-datepicker> </a-persian-datepicker>
</a-form-item> </a-form-item>
<a-form-item v-if="clientsBulkModal.expiryTime != 0"> <a-form-item v-if="clientsBulkModal.expiryTime != 0">
<template slot="label"> <template slot="label">

View File

@@ -16,16 +16,7 @@
title: '', title: '',
okText: '', okText: '',
isEdit: false, isEdit: false,
group: {
canGroup: true,
isGroup: false,
currentClient: null,
inbounds: [],
clients: [],
editIds: []
},
dbInbound: new DBInbound(), dbInbound: new DBInbound(),
dbInbounds: null,
inbound: new Inbound(), inbound: new Inbound(),
clients: [], clients: [],
clientStats: [], clientStats: [],
@@ -34,126 +25,33 @@
clientIps: null, clientIps: null,
delayedStart: false, delayedStart: false,
ok() { ok() {
if (app.subSettings.enable && clientModal.group.isGroup && clientModal.group.canGroup) { if (clientModal.isEdit) {
const currentClient = clientModal.group.currentClient; ObjectUtil.execute(clientModal.confirm, clientModalApp.client, clientModal.dbInbound.id, clientModal.oldClientId);
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);
}
} else { } else {
if (clientModal.isEdit) { ObjectUtil.execute(clientModal.confirm, clientModalApp.client, clientModal.dbInbound.id);
ObjectUtil.execute(clientModal.confirm, clientModalApp.client, clientModal.dbInbound.id, clientModal.oldClientId);
} else {
ObjectUtil.execute(clientModal.confirm, clientModalApp.client, clientModal.dbInbound.id);
}
} }
}, },
show({ show({ title = '', okText = '{{ i18n "sure" }}', index = null, dbInbound = null, confirm = () => { }, isEdit = false }) {
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;
this.visible = true; this.visible = true;
this.title = title; this.title = title;
this.okText = okText; this.okText = okText;
this.isEdit = isEdit; this.isEdit = isEdit;
if (app.subSettings.enable && dbInbounds !== null && Array.isArray(dbInbounds)) { this.dbInbound = new DBInbound(dbInbound);
if (isEdit) { this.inbound = dbInbound.toInbound();
this.showProcess(dbInbound, index); this.clients = this.inbound.clients;
let processSingleEdit = true this.index = index === null ? this.clients.length : index;
if (this.group.canGroup) { this.delayedStart = false;
this.group.currentClient = this.clients[this.index] if (isEdit) {
const response = app.getSubGroupClients(dbInbounds, this.group.currentClient) if (this.clients[index].expiryTime < 0) {
if (response.clients.length > 1) { this.delayedStart = true;
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.oldClientId = this.getClientId(dbInbound.protocol, clients[index]);
} else { } else {
this.showProcess(dbInbound, index); this.addClient(this.inbound.protocol, this.clients);
if (isEdit) {
this.singleEditClientProcess(index)
} else {
this.addClient(this.inbound.protocol, this.clients);
}
} }
this.clientStats = this.dbInbound.clientStats.find(row => row.email === this.clients[this.index].email); this.clientStats = this.dbInbound.clientStats.find(row => row.email === this.clients[this.index].email);
this.confirm = confirm; 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) { getClientId(protocol, client) {
switch (protocol) { switch (protocol) {
case Protocols.TROJAN: return client.password; case Protocols.TROJAN: return client.password;
@@ -174,7 +72,7 @@
clientModal.visible = false; clientModal.visible = false;
clientModal.loading(false); clientModal.loading(false);
}, },
loading(loading = true) { loading(loading=true) {
clientModal.confirmLoading = loading; clientModal.confirmLoading = loading;
}, },
}; };
@@ -196,18 +94,6 @@
get isEdit() { get isEdit() {
return this.clientModal.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() { get datepicker() {
return app.datepicker; return app.datepicker;
}, },
@@ -234,36 +120,6 @@
}, },
}, },
methods: { 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) { async getDBClientIps(email) {
const msg = await HttpUtil.post(`/panel/inbound/clientIps/${email}`); const msg = await HttpUtil.post(`/panel/inbound/clientIps/${email}`);
if (!msg.success) { if (!msg.success) {
@@ -291,22 +147,7 @@
} catch (error) { } catch (error) {
} }
}, },
async resetClientTrafficHandler(client, dbInboundId, clients = []) { resetClientTraffic(email, dbInboundId, iconElement) {
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 : [];
this.$confirm({ this.$confirm({
title: '{{ i18n "pages.inbounds.resetTraffic"}}', title: '{{ i18n "pages.inbounds.resetTraffic"}}',
content: '{{ i18n "pages.inbounds.resetTrafficContent"}}', content: '{{ i18n "pages.inbounds.resetTrafficContent"}}',
@@ -315,8 +156,8 @@
cancelText: '{{ i18n "cancel"}}', cancelText: '{{ i18n "cancel"}}',
onOk: async () => { onOk: async () => {
iconElement.disabled = true; iconElement.disabled = true;
const msg = await this.resetClientTrafficHandler(client, dbInboundId, clients); const msg = await HttpUtil.postWithModal('/panel/inbound/' + dbInboundId + '/resetClientTraffic/' + email);
if (msg && msg.success) { if (msg.success) {
this.clientModal.clientStats.up = 0; this.clientModal.clientStats.up = 0;
this.clientModal.clientStats.down = 0; this.clientModal.clientStats.down = 0;
} }

View File

@@ -34,7 +34,7 @@
{{define "commonSider"}} {{define "commonSider"}}
<a-layout-sider :theme="themeSwitcher.currentTheme" id="sider" collapsible breakpoint="md"> <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"> <a-menu :theme="themeSwitcher.currentTheme" mode="inline" :selected-keys="['{{ .request_uri }}']" @click="({key}) => key.startsWith('http') ? window.open(key) : location.href = key">
{{template "menuItems" .}} {{template "menuItems" .}}
</a-menu> </a-menu>
@@ -43,7 +43,7 @@
<div class="drawer-handle" @click="siderDrawer.change()" slot="handle"> <div class="drawer-handle" @click="siderDrawer.change()" slot="handle">
<a-icon :type="siderDrawer.visible ? 'close' : 'menu-fold'"></a-icon> <a-icon :type="siderDrawer.visible ? 'close' : 'menu-fold'"></a-icon>
</div> </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"> <a-menu :theme="themeSwitcher.currentTheme" mode="inline" :selected-keys="['{{ .request_uri }}']" @click="({key}) => key.startsWith('http') ? window.open(key) : location.href = key">
{{template "menuItems" .}} {{template "menuItems" .}}
</a-menu> </a-menu>

View File

@@ -1,26 +1,46 @@
{{define "component/passwordInput"}} {{define "component/passwordInput"}}
<template> <template>
<a-input :value="value" :type="showPassword ? 'text' : 'password'" <a-input :value="value" :type="showPassword ? 'text' : 'password'" :placeholder="placeholder"
:placeholder="placeholder" :autocomplete="autocomplete" :name="name" @input="$emit('input', $event.target.value)">
:autocomplete="autocomplete" <template v-if="icon" #prefix>
:name="name" <a-icon :type="icon" style="font-size: 16px;" />
@input="$emit('input', $event.target.value)"> </template>
<template v-if="icon" #prefix> <template #addonAfter>
<a-icon :type="icon" style="font-size: 16px;" /> <a-icon :type="showPassword ? 'eye-invisible' : 'eye'" @click="toggleShowPassword" style="font-size: 16px;" />
</template> </template>
<template #addonAfter> </a-input>
<a-icon :type="showPassword ? 'eye-invisible' : 'eye'"
@click="toggleShowPassword"
style="font-size: 16px;" />
</template>
</a-input>
</template> </template>
{{end}} {{end}}
{{define "component/password"}} {{define "component/password"}}
<script> <script>
Vue.component('password-input', { Vue.component('a-password-input', {
props: ["title", "value", "placeholder", "icon", "autocomplete", "name"], 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"}}`, template: `{{template "component/passwordInput"}}`,
data() { data() {
return { return {

View File

@@ -2,10 +2,10 @@
<template> <template>
<div> <div>
<a-input :value="value" type="text" v-model="date" data-jdp class="persian-datepicker" <a-input :value="value" type="text" v-model="date" data-jdp class="persian-datepicker"
@input="$emit('input', convertToGregorian($event.target.value)); jalaliDatepicker.hide();" @input="$emit('input', convertToGregorian($event.target.value)); jalaliDatepicker.hide();"
:placeholder="placeholder"> :placeholder="placeholder">
<template #addonAfter> <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> </template>
</a-input> </a-input>
</div> </div>
@@ -13,15 +13,27 @@
{{end}} {{end}}
{{define "component/persianDatepicker"}} {{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/moment/moment-jalali.min.js?{{ .cur_ver }}"></script>
<script src="{{ .base_path }}assets/persian-datepicker/persian-datepicker.min.js?{{ .cur_ver }}"></script> <script src="{{ .base_path }}assets/persian-datepicker/persian-datepicker.min.js?{{ .cur_ver }}"></script>
<script> <script>
const persianDatepicker = {}; const persianDatepicker = {};
Vue.component('persian-datepicker', { Vue.component('a-persian-datepicker', {
props: ['placeholder', 'format', 'value'], props: {
'format': {
type: undefined,
required: false,
},
'value': {
type: String,
required: false,
},
'placeholder': {
type: String,
required: false,
},
},
template: `{{template "component/persianDatepickerTemplate"}}`, template: `{{template "component/persianDatepickerTemplate"}}`,
data() { data() {
return { return {
@@ -57,4 +69,4 @@
} }
}); });
</script> </script>
{{end}} {{end}}

View File

@@ -1,26 +1,18 @@
{{define "component/settingListItem"}} {{define "component/settingListItem"}}
<a-list-item style="padding: 20px"> <a-list-item :style="{ padding: padding }">
<a-row v-if="type === 'textarea'"> <a-row>
<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-col :lg="24" :xl="12"> <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>
<a-col :lg="24" :xl="12"> <a-col :lg="24" :xl="12">
<template v-if="type === 'text'"> <slot name="control"></slot>
<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>
</a-col> </a-col>
</a-row> </a-row>
</a-list-item> </a-list-item>
@@ -28,9 +20,38 @@
{{define "component/setting"}} {{define "component/setting"}}
<script> <script>
Vue.component('setting-list-item', { Vue.component('a-setting-list-item', {
props: ["type", "title", "desc", "value", "min", "max" , "step", "placeholder"], props: {
template: `{{template "component/settingListItem"}}`, '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> </script>
{{end}} {{end}}

View File

@@ -1,9 +1,5 @@
{{define "component/sortableTableTrigger"}} {{define "component/sortableTableTrigger"}}
<a-icon type="drag" <a-icon type="drag" class="sortable-icon" style="cursor: move;" @mouseup="mouseUpHandler" @mousedown="mouseDownHandler"
class="sortable-icon"
style="cursor: move;"
@mouseup="mouseUpHandler"
@mousedown="mouseDownHandler"
@click="clickHandler" /> @click="clickHandler" />
{{end}} {{end}}
@@ -28,7 +24,16 @@
newElementIndex: null, newElementIndex: null,
}; };
}, },
props: ['data-source', 'customRow'], props: {
'data-source': {
type: undefined,
required: false,
},
'customRow': {
type: undefined,
required: false,
}
},
inheritAttrs: false, inheritAttrs: false,
provide() { provide() {
const sortable = {} const sortable = {}
@@ -44,7 +49,7 @@
sortable, sortable,
} }
}, },
render: function(createElement) { render: function (createElement) {
return createElement('a-table', { return createElement('a-table', {
class: { class: {
'ant-table-is-sorting': this.isDragging(), 'ant-table-is-sorting': this.isDragging(),
@@ -59,7 +64,7 @@
drop: (e) => this.dropHandler(e), drop: (e) => this.dropHandler(e),
}, },
scopedSlots: this.$scopedSlots, scopedSlots: this.$scopedSlots,
}, this.$slots.default, ) }, this.$slots.default,)
}, },
created() { created() {
this.$memoSort = {}; this.$memoSort = {};
@@ -163,9 +168,14 @@
} }
} }
}); });
Vue.component('table-sort-trigger', { Vue.component('a-table-sort-trigger', {
template: `{{template "component/sortableTableTrigger"}}`, template: `{{template "component/sortableTableTrigger"}}`,
props: ['item-index'], props: {
'item-index': {
type: undefined,
required: false
}
},
inject: ['sortable'], inject: ['sortable'],
methods: { methods: {
mouseDownHandler(e) { mouseDownHandler(e) {
@@ -190,27 +200,33 @@
display: none; display: none;
} }
} }
.ant-table-is-sorting .draggable-row td { .ant-table-is-sorting .draggable-row td {
background-color: #ffffff !important; background-color: #ffffff !important;
} }
.dark .ant-table-is-sorting .draggable-row td { .dark .ant-table-is-sorting .draggable-row td {
background-color: var(--dark-color-surface-100) !important; background-color: var(--dark-color-surface-100) !important;
} }
.ant-table-is-sorting .dragging td { .ant-table-is-sorting .dragging td {
background-color: rgb(232 244 242) !important; background-color: rgb(232 244 242) !important;
color: rgba(0, 0, 0, 0.3); color: rgba(0, 0, 0, 0.3);
} }
.dark .ant-table-is-sorting .dragging td { .dark .ant-table-is-sorting .dragging td {
background-color: var(--dark-color-table-hover) !important; background-color: var(--dark-color-table-hover) !important;
color: rgba(255, 255, 255, 0.3); color: rgba(255, 255, 255, 0.3);
} }
.ant-table-is-sorting .dragging { .ant-table-is-sorting .dragging {
opacity: 1; opacity: 1;
box-shadow: 1px -2px 2px #008771; box-shadow: 1px -2px 2px #008771;
transition: all 0.2s; transition: all 0.2s;
} }
.ant-table-is-sorting .dragging .ant-table-row-index { .ant-table-is-sorting .dragging .ant-table-row-index {
opacity: 0.3; opacity: 0.3;
} }
</style> </style>
{{end}} {{end}}

View File

@@ -6,9 +6,13 @@
<a-icon type="bulb" :theme="themeSwitcher.isDarkTheme ? 'filled' : 'outlined'"></a-icon> <a-icon type="bulb" :theme="themeSwitcher.isDarkTheme ? 'filled' : 'outlined'"></a-icon>
<span>Theme</span> <span>Theme</span>
</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>
<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-menu-item>
</a-sub-menu> </a-sub-menu>
</a-menu> </a-menu>
@@ -17,12 +21,15 @@
{{define "component/themeSwitchTemplateLogin"}} {{define "component/themeSwitchTemplateLogin"}}
<template> <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-menu-item mode="inline" class="ant-menu-theme-switch">
<a-icon type="bulb" :theme="themeSwitcher.isDarkTheme ? 'filled' : 'outlined'"></a-icon> <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"> <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> </template>
</a-menu-item> </a-menu-item>
</a-menu> </a-menu>
@@ -83,8 +90,7 @@
}; };
} }
const themeSwitcher = createThemeSwitcher(); const themeSwitcher = createThemeSwitcher();
Vue.component('theme-switch', { Vue.component('a-theme-switch', {
props: [],
template: `{{template "component/themeSwitchTemplate"}}`, template: `{{template "component/themeSwitchTemplate"}}`,
data: () => ({ data: () => ({
themeSwitcher themeSwitcher
@@ -96,8 +102,7 @@
document.getElementById('message').className = themeSwitcher.currentTheme; document.getElementById('message').className = themeSwitcher.currentTheme;
} }
}); });
Vue.component('theme-switch-login', { Vue.component('a-theme-switch-login', {
props: [],
template: `{{template "component/themeSwitchTemplateLogin"}}`, template: `{{template "component/themeSwitchTemplateLogin"}}`,
data: () => ({ data: () => ({
themeSwitcher themeSwitcher
@@ -110,4 +115,4 @@
} }
}); });
</script> </script>
{{end}} {{end}}

View File

@@ -3,18 +3,6 @@
<a-form-item label='{{ i18n "pages.inbounds.enable" }}'> <a-form-item label='{{ i18n "pages.inbounds.enable" }}'>
<a-switch v-model="client.enable"></a-switch> <a-switch v-model="client.enable"></a-switch>
</a-form-item> </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> <a-form-item>
<template slot="label"> <template slot="label">
<a-tooltip> <a-tooltip>
@@ -138,15 +126,15 @@
<a-input-number v-model.number="client._totalGB" :min="0"></a-input-number> <a-input-number v-model.number="client._totalGB" :min="0"></a-input-number>
</a-form-item> </a-form-item>
<a-form-item v-if="isEdit && clientStats" label='{{ i18n "usage" }}'> <a-form-item v-if="isEdit && clientStats" label='{{ i18n "usage" }}'>
<a-tag :color="clientUsageColor(clientStats, app.trafficDiff)"> <a-tag :color="ColorUtils.clientUsageColor(clientStats, app.trafficDiff)">
[[ sizeFormat(clientStats.up) ]] / [[ SizeFormatter.sizeFormat(clientStats.up) ]] /
[[ sizeFormat(clientStats.down) ]] [[ SizeFormatter.sizeFormat(clientStats.down) ]]
([[ sizeFormat(clientStats.up + clientStats.down) ]]) ([[ SizeFormatter.sizeFormat(clientStats.up + clientStats.down) ]])
</a-tag> </a-tag>
<a-tooltip> <a-tooltip>
<template slot="title">{{ i18n "pages.inbounds.resetTraffic" }}</template> <template slot="title">{{ i18n "pages.inbounds.resetTraffic" }}</template>
<a-icon type="retweet" <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> v-if="client.email.length > 0"></a-icon>
</a-tooltip> </a-tooltip>
</a-form-item> </a-form-item>
@@ -166,8 +154,8 @@
</template> </template>
<a-date-picker v-if="datepicker == 'gregorian'" :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss" <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> :dropdown-class-name="themeSwitcher.currentTheme" v-model="client._expiryTime"></a-date-picker>
<persian-datepicker v-else placeholder='{{ i18n "pages.settings.datepickerPlaceholder" }}' <a-persian-datepicker v-else placeholder='{{ i18n "pages.settings.datepickerPlaceholder" }}'
value="client._expiryTime" v-model="client._expiryTime"></persian-datepicker> value="client._expiryTime" v-model="client._expiryTime"></a-persian-datepicker>
<a-tag color="red" v-if="isEdit && isExpiry">Expired</a-tag> <a-tag color="red" v-if="isEdit && isExpiry">Expired</a-tag>
</a-form-item> </a-form-item>
<a-form-item v-if="client.expiryTime != 0"> <a-form-item v-if="client.expiryTime != 0">

View File

@@ -57,9 +57,9 @@
<a-date-picker style="width: 100%;" v-if="datepicker == 'gregorian'" :show-time="{ format: 'HH:mm:ss' }" <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" format="YYYY-MM-DD HH:mm:ss" :dropdown-class-name="themeSwitcher.currentTheme"
v-model="dbInbound._expiryTime"></a-date-picker> 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"> value="dbInbound._expiryTime" v-model="dbInbound._expiryTime">
</persian-datepicker> </a-persian-datepicker>
</a-form-item> </a-form-item>
</a-form> </a-form>

View File

@@ -66,7 +66,7 @@
</a-divider> </a-divider>
<a-form-item label='Type'> <a-form-item label='Type'>
<a-select v-model="noise.type" :dropdown-class-name="themeSwitcher.currentTheme"> <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-select>
</a-form-item> </a-form-item>
<a-form-item label='Packet'> <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-option v-for="tag in ['', ...outModal.tags]" :value="tag">[[ tag ]]</a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
<a-form-item label="TCP Fast Open"> <a-form-item label='Address Port Strategy'>
<a-switch v-model="outbound.stream.sockopt.tcpFastOpen"></a-switch> <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>
<a-form-item label="Keep Alive Interval"> <a-form-item label="Keep Alive Interval">
<a-input-number v-model.number="outbound.stream.sockopt.tcpKeepAliveInterval" :min="0"></a-input-number> <a-input-number v-model.number="outbound.stream.sockopt.tcpKeepAliveInterval" :min="0"></a-input-number>
</a-form-item> </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-form-item label="Multipath TCP">
<a-switch v-model.trim="outbound.stream.sockopt.tcpMptcp"></a-switch> <a-switch v-model.trim="outbound.stream.sockopt.tcpMptcp"></a-switch>
</a-form-item> </a-form-item>

View File

@@ -7,7 +7,7 @@
<a-input-number v-model.number="inbound.stream.reality.xver" :min="0"></a-input-number> <a-input-number v-model.number="inbound.stream.reality.xver" :min="0"></a-input-number>
</a-form-item> </a-form-item>
<a-form-item label='uTLS'> <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"> :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option> <a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
</a-select> </a-select>
@@ -43,11 +43,11 @@
<a-form-item label='SpiderX'> <a-form-item label='SpiderX'>
<a-input v-model.trim="inbound.stream.reality.settings.spiderX"></a-input> <a-input v-model.trim="inbound.stream.reality.settings.spiderX"></a-input>
</a-form-item> </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-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>
<a-form-item label=" "> <a-form-item label=" ">
<a-button type="primary" icon="import" @click="getNewX25519Cert">Get New Cert</a-button> <a-button type="primary" icon="import" @click="getNewX25519Cert">Get New Cert</a-button>

View File

@@ -8,6 +8,7 @@
<a-select-option value="wechat-video">WeChat</a-select-option> <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="dtls">DTLS 1.2</a-select-option>
<a-select-option value="wireguard">WireGuard</a-select-option> <a-select-option value="wireguard">WireGuard</a-select-option>
<a-select-option value="dns">DNS</a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
<a-form-item> <a-form-item>

View File

@@ -34,7 +34,7 @@
</a-input-group> </a-input-group>
</a-form-item> </a-form-item>
<a-form-item label="uTLS"> <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"> :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value=''>None</a-select-option> <a-select-option value=''>None</a-select-option>
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option> <a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
@@ -57,8 +57,8 @@
<a-form-item label="Session Resumption"> <a-form-item label="Session Resumption">
<a-switch v-model="inbound.stream.tls.enableSessionResumption"></a-switch> <a-switch v-model="inbound.stream.tls.enableSessionResumption"></a-switch>
</a-form-item> </a-form-item>
<a-form-item label="Server Name To Verify"> <a-form-item label="VerifyPeerCertInNames">
<a-input v-model.trim="inbound.stream.tls.serverNameToVerify"></a-input> <a-input v-model.trim="inbound.stream.tls.verifyPeerCertInNames"></a-input>
</a-form-item> </a-form-item>
<template v-for="cert,index in inbound.stream.tls.certs"> <template v-for="cert,index in inbound.stream.tls.certs">
<a-form-item label='{{ i18n "certificate" }}'> <a-form-item label='{{ i18n "certificate" }}'>
@@ -85,10 +85,10 @@
</template> </template>
<template v-else> <template v-else>
<a-form-item label='{{ i18n "pages.inbounds.publicKey" }}'> <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>
<a-form-item label='{{ i18n "pages.inbounds.privatekey" }}'> <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> </a-form-item>
</template> </template>
<a-form-item label='OCSP stapling'> <a-form-item label='OCSP stapling'>

View File

@@ -12,7 +12,7 @@
<template slot="title">{{ i18n "info" }}</template> <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-icon style="font-size: 24px;" class="normal-icon" type="info-circle" @click="showInfo(record.id,client);"></a-icon>
</a-tooltip> </a-tooltip>
<a-tooltip v-if="hasClientStats(record, client.email)"> <a-tooltip>
<template slot="title">{{ i18n "pages.inbounds.resetTraffic" }}</template> <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-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> <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"> <template slot="content" v-if="client.email">
<table cellpadding="2" width="100%"> <table cellpadding="2" width="100%">
<tr> <tr>
<td>↑[[ sizeFormat(getUpStats(record, client.email)) ]]</td> <td>↑[[ SizeFormatter.sizeFormat(getUpStats(record, client.email)) ]]</td>
<td>↓[[ sizeFormat(getDownStats(record, client.email)) ]]</td> <td>↓[[ SizeFormatter.sizeFormat(getDownStats(record, client.email)) ]]</td>
</tr> </tr>
<tr v-if="client.totalGB > 0"> <tr v-if="client.totalGB > 0">
<td>{{ i18n "remained" }}</td> <td>{{ i18n "remained" }}</td>
<td>[[ sizeFormat(getRemStats(record, client.email)) ]]</td> <td>[[ SizeFormatter.sizeFormat(getRemStats(record, client.email)) ]]</td>
</tr> </tr>
</table> </table>
</template> </template>
<table> <table>
<tr class="tr-table-box"> <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"> <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)" /> <a-progress :stroke-color="themeSwitcher.isDarkTheme ? 'rgb(72 84 105)' : '#bcbcbc'" :show-info="false" :percent="statsProgress(record, client.email)" />
</td> </td>
@@ -124,9 +124,9 @@
</template> </template>
</span> </span>
</template> </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-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"> <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> <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> </svg>
@@ -172,7 +172,7 @@
<td colspan="3" style="text-align: center;">{{ i18n "pages.inbounds.traffic" }}</td> <td colspan="3" style="text-align: center;">{{ i18n "pages.inbounds.traffic" }}</td>
</tr> </tr>
<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"> <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)" /> <a-progress :stroke-color="themeSwitcher.isDarkTheme ? 'rgb(72 84 105)' : '#bcbcbc'" :show-info="false" :percent="statsProgress(record, client.email)" />
</td> </td>
@@ -181,12 +181,12 @@
<template slot="content" v-if="client.email"> <template slot="content" v-if="client.email">
<table cellpadding="2" width="100%"> <table cellpadding="2" width="100%">
<tr> <tr>
<td>↑[[ sizeFormat(getUpStats(record, client.email)) ]]</td> <td>↑[[ SizeFormatter.sizeFormat(getUpStats(record, client.email)) ]]</td>
<td>↓[[ sizeFormat(getDownStats(record, client.email)) ]]</td> <td>↓[[ SizeFormatter.sizeFormat(getDownStats(record, client.email)) ]]</td>
</tr> </tr>
<tr> <tr>
<td>{{ i18n "remained" }}</td> <td>{{ i18n "remained" }}</td>
<td>[[ sizeFormat(getRemStats(record, client.email)) ]]</td> <td>[[ SizeFormatter.sizeFormat(getRemStats(record, client.email)) ]]</td>
</tr> </tr>
</table> </table>
</template> </template>
@@ -244,7 +244,7 @@
</template> </template>
</span> </span>
</template> </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-popover>
<a-tag v-else :color="client.enable ? 'purple' : themeSwitcher.isDarkTheme ? '#2c3950' : '#bcbcbc'" class="infinite-tag"> <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"> <svg height="10px" width="14px" viewBox="0 0 640 512" fill="currentColor">

View File

@@ -181,8 +181,8 @@
<tr v-if="infoModal.clientStats"> <tr v-if="infoModal.clientStats">
<td>{{ i18n "usage" }}</td> <td>{{ i18n "usage" }}</td>
<td> <td>
<a-tag color="green">[[ sizeFormat(infoModal.clientStats.up + infoModal.clientStats.down) ]]</a-tag> <a-tag color="green">[[ SizeFormatter.sizeFormat(infoModal.clientStats.up + infoModal.clientStats.down) ]]</a-tag>
<a-tag>↑ [[ sizeFormat(infoModal.clientStats.up) ]] / [[ sizeFormat(infoModal.clientStats.down) ]] ↓</a-tag> <a-tag>↑ [[ SizeFormatter.sizeFormat(infoModal.clientStats.up) ]] / [[ SizeFormatter.sizeFormat(infoModal.clientStats.down) ]] ↓</a-tag>
</td> </td>
</tr> </tr>
<tr v-if="infoModal.clientSettings.comment"> <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> <a-tag v-if="infoModal.clientStats && infoModal.clientSettings.totalGB > 0" :color="statsColor(infoModal.clientStats)"> [[ getRemStats() ]] </a-tag>
</td> </td>
<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"> <a-tag v-else color="purple" class="infinite-tag">
<svg height="10px" width="14px" viewBox="0 0 640 512" fill="currentColor"> <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> <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>
<td> <td>
<template v-if="infoModal.clientSettings.expiryTime > 0"> <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'"> <template v-if="app.datepicker === 'gregorian'">
[[ DateUtil.formatMillis(infoModal.clientSettings.expiryTime) ]] [[ DateUtil.formatMillis(infoModal.clientSettings.expiryTime) ]]
</template> </template>
@@ -258,7 +258,7 @@
<tr-info-title class="tr-info-title"> <tr-info-title class="tr-info-title">
<a-tag color="purple">Subscription Link</a-tag> <a-tag color="purple">Subscription Link</a-tag>
<a-tooltip title='{{ i18n "copy" }}'> <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> </a-tooltip>
</tr-info-title> </tr-info-title>
<a :href="[[ infoModal.subLink ]]" target="_blank">[[ infoModal.subLink ]]</a> <a :href="[[ infoModal.subLink ]]" target="_blank">[[ infoModal.subLink ]]</a>
@@ -267,7 +267,7 @@
<tr-info-title class="tr-info-title"> <tr-info-title class="tr-info-title">
<a-tag color="purple">Json Link</a-tag> <a-tag color="purple">Json Link</a-tag>
<a-tooltip title='{{ i18n "copy" }}'> <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> </a-tooltip>
</tr-info-title> </tr-info-title>
<a :href="[[ infoModal.subJsonLink ]]" target="_blank">[[ infoModal.subJsonLink ]]</a> <a :href="[[ infoModal.subJsonLink ]]" target="_blank">[[ infoModal.subJsonLink ]]</a>
@@ -279,7 +279,7 @@
<tr-info-title class="tr-info-title"> <tr-info-title class="tr-info-title">
<a-tag color="blue">[[ infoModal.clientSettings.tgId ]]</a-tag> <a-tag color="blue">[[ infoModal.clientSettings.tgId ]]</a-tag>
<a-tooltip title='{{ i18n "copy" }}'> <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> </a-tooltip>
</tr-info-title> </tr-info-title>
</tr-info-row> </tr-info-row>
@@ -290,7 +290,7 @@
<tr-info-title class="tr-info-title"> <tr-info-title class="tr-info-title">
<a-tag class="tr-info-tag" color="green">[[ link.remark ]]</a-tag> <a-tag class="tr-info-tag" color="green">[[ link.remark ]]</a-tag>
<a-tooltip title='{{ i18n "copy" }}'> <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> </a-tooltip>
</tr-info-title> </tr-info-title>
<code>[[ link.link ]]</code> <code>[[ link.link ]]</code>
@@ -304,7 +304,7 @@
<tr-info-title class="tr-info-title"> <tr-info-title class="tr-info-title">
<a-tag class="tr-info-tag" color="green">[[ link.remark ]]</a-tag> <a-tag class="tr-info-tag" color="green">[[ link.remark ]]</a-tag>
<a-tooltip title='{{ i18n "copy" }}'> <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> </a-tooltip>
</tr-info-title> </tr-info-title>
<code>[[ link.link ]]</code> <code>[[ link.link ]]</code>
@@ -431,7 +431,7 @@
<tr-info-title class="tr-info-title"> <tr-info-title class="tr-info-title">
<a-tag color="blue">Config</a-tag> <a-tag color="blue">Config</a-tag>
<a-tooltip title='{{ i18n "copy" }}'> <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> </a-tooltip>
</tr-info-title> </tr-info-title>
<div v-html="infoModal.links[index].replaceAll(`\n`,`<br />`)" style="border-radius: 1rem; padding: 0.5rem;" class="client-table-odd-row"> <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: [], clientStats: [],
upStats: 0, upStats: 0,
downStats: 0, downStats: 0,
clipboard: null,
links: [], links: [],
index: null, index: null,
isExpired: false, isExpired: false,
@@ -533,21 +532,19 @@
}, },
}, },
methods: { methods: {
copyToClipboard(elementId, content) { copy(content) {
this.infoModal.clipboard = new ClipboardJS('#' + elementId, { ClipboardManager
text: () => content, .copyText(content)
}); .then(() => {
this.infoModal.clipboard.on('success', () => { app.$message.success('{{ i18n "copied" }}')
app.$message.success('{{ i18n "copied" }}') })
this.infoModal.clipboard.destroy();
});
}, },
statsColor(stats) { statsColor(stats) {
return usageColor(stats.up + stats.down, app.trafficDiff, stats.total); return ColorUtils.usageColor(stats.up + stats.down, app.trafficDiff, stats.total);
}, },
getRemStats() { getRemStats() {
remained = this.infoModal.clientStats.total - this.infoModal.clientStats.up - this.infoModal.clientStats.down; 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() { refreshIPs() {
this.refreshing = true; this.refreshing = true;

View File

@@ -14,7 +14,6 @@
confirmLoading: false, confirmLoading: false,
okText: '{{ i18n "sure" }}', okText: '{{ i18n "sure" }}',
isEdit: false, isEdit: false,
isGroup: false,
confirm: null, confirm: null,
inbound: new Inbound(), inbound: new Inbound(),
dbInbound: new DBInbound(), dbInbound: new DBInbound(),
@@ -62,9 +61,6 @@
get isEdit() { get isEdit() {
return inModal.isEdit; return inModal.isEdit;
}, },
get isGroup() {
return inModal.isGroup;
},
get client() { get client() {
return inModal.inbound.clients[0]; return inModal.inbound.clients[0];
}, },

View File

@@ -141,11 +141,11 @@
<a-row> <a-row>
<a-col :xs="24" :sm="24" :lg="12"> <a-col :xs="24" :sm="24" :lg="12">
{{ i18n "pages.inbounds.totalDownUp" }}: {{ 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>
<a-col :xs="24" :sm="24" :lg="12"> <a-col :xs="24" :sm="24" :lg="12">
{{ i18n "pages.inbounds.totalUsage" }}: {{ 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>
<a-col :xs="24" :sm="24" :lg="12"> <a-col :xs="24" :sm="24" :lg="12">
{{ i18n "pages.inbounds.inboundCount" }}: {{ i18n "pages.inbounds.inboundCount" }}:
@@ -224,10 +224,6 @@
<a-icon type="rest"></a-icon> <a-icon type="rest"></a-icon>
{{ i18n "pages.inbounds.delDepletedClients" }} {{ i18n "pages.inbounds.delDepletedClients" }}
</a-menu-item> </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-menu>
</a-dropdown> </a-dropdown>
</a-col> </a-col>
@@ -379,19 +375,19 @@
<template slot="content"> <template slot="content">
<table cellpadding="2" width="100%"> <table cellpadding="2" width="100%">
<tr> <tr>
<td>↑[[ sizeFormat(dbInbound.up) ]]</td> <td>↑[[ SizeFormatter.sizeFormat(dbInbound.up) ]]</td>
<td>↓[[ sizeFormat(dbInbound.down) ]]</td> <td>↓[[ SizeFormatter.sizeFormat(dbInbound.down) ]]</td>
</tr> </tr>
<tr v-if="dbInbound.total > 0 && dbInbound.up + dbInbound.down < dbInbound.total"> <tr v-if="dbInbound.total > 0 && dbInbound.up + dbInbound.down < dbInbound.total">
<td>{{ i18n "remained" }}</td> <td>{{ i18n "remained" }}</td>
<td>[[ sizeFormat(dbInbound.total - dbInbound.up - dbInbound.down) ]]</td> <td>[[ SizeFormatter.sizeFormat(dbInbound.total - dbInbound.up - dbInbound.down) ]]</td>
</tr> </tr>
</table> </table>
</template> </template>
<a-tag :color="usageColor(dbInbound.up + dbInbound.down, app.trafficDiff, dbInbound.total)"> <a-tag :color="ColorUtils.usageColor(dbInbound.up + dbInbound.down, app.trafficDiff, dbInbound.total)">
[[ sizeFormat(dbInbound.up + dbInbound.down) ]] / [[ SizeFormatter.sizeFormat(dbInbound.up + dbInbound.down) ]] /
<template v-if="dbInbound.total > 0"> <template v-if="dbInbound.total > 0">
[[ sizeFormat(dbInbound.total) ]] [[ SizeFormatter.sizeFormat(dbInbound.total) ]]
</template> </template>
<template v-else> <template v-else>
<svg height="10px" width="14px" viewBox="0 0 640 512" fill="currentColor"> <svg height="10px" width="14px" viewBox="0 0 640 512" fill="currentColor">
@@ -412,7 +408,7 @@
<template v-else slot="content"> <template v-else slot="content">
[[ DateUtil.convertToJalalian(moment(dbInbound.expiryTime)) ]] [[ DateUtil.convertToJalalian(moment(dbInbound.expiryTime)) ]]
</template> </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) ]] [[ remainedDays(dbInbound._expiryTime) ]]
</a-tag> </a-tag>
</a-popover> </a-popover>
@@ -478,19 +474,19 @@
<template slot="content"> <template slot="content">
<table cellpadding="2" width="100%"> <table cellpadding="2" width="100%">
<tr> <tr>
<td>↑[[ sizeFormat(dbInbound.up) ]]</td> <td>↑[[ SizeFormatter.sizeFormat(dbInbound.up) ]]</td>
<td>↓[[ sizeFormat(dbInbound.down) ]]</td> <td>↓[[ SizeFormatter.sizeFormat(dbInbound.down) ]]</td>
</tr> </tr>
<tr v-if="dbInbound.total > 0 && dbInbound.up + dbInbound.down < dbInbound.total"> <tr v-if="dbInbound.total > 0 && dbInbound.up + dbInbound.down < dbInbound.total">
<td>{{ i18n "remained" }}</td> <td>{{ i18n "remained" }}</td>
<td>[[ sizeFormat(dbInbound.total - dbInbound.up - dbInbound.down) ]]</td> <td>[[ SizeFormatter.sizeFormat(dbInbound.total - dbInbound.up - dbInbound.down) ]]</td>
</tr> </tr>
</table> </table>
</template> </template>
<a-tag :color="usageColor(dbInbound.up + dbInbound.down, app.trafficDiff, dbInbound.total)"> <a-tag :color="ColorUtils.usageColor(dbInbound.up + dbInbound.down, app.trafficDiff, dbInbound.total)">
[[ sizeFormat(dbInbound.up + dbInbound.down) ]] / [[ SizeFormatter.sizeFormat(dbInbound.up + dbInbound.down) ]] /
<template v-if="dbInbound.total > 0"> <template v-if="dbInbound.total > 0">
[[ sizeFormat(dbInbound.total) ]] [[ SizeFormatter.sizeFormat(dbInbound.total) ]]
</template> </template>
<template v-else> <template v-else>
<svg height="10px" width="14px" viewBox="0 0 640 512" fill="currentColor"> <svg height="10px" width="14px" viewBox="0 0 640 512" fill="currentColor">
@@ -548,9 +544,7 @@
</a-layout> </a-layout>
</a-layout> </a-layout>
{{template "js" .}} {{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/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/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/inbound.js?{{ .cur_ver }}"></script>
<script src="{{ .base_path }}assets/js/model/dbinbound.js?{{ .cur_ver }}"></script> <script src="{{ .base_path }}assets/js/model/dbinbound.js?{{ .cur_ver }}"></script>
@@ -863,9 +857,6 @@
case "delDepletedClients": case "delDepletedClients":
this.delDepletedClients(-1) this.delDepletedClients(-1)
break; break;
case "addGroupClient":
this.openGroupAddClient()
break;
} }
}, },
clickAction(action, dbInbound) { clickAction(action, dbInbound) {
@@ -892,7 +883,7 @@
this.exportSubs(dbInbound.id); this.exportSubs(dbInbound.id);
break; break;
case "clipboard": case "clipboard":
this.copyToClipboard(dbInbound.id); this.copy(dbInbound.id);
break; break;
case "resetTraffic": case "resetTraffic":
this.resetTraffic(dbInbound.id); this.resetTraffic(dbInbound.id);
@@ -936,7 +927,7 @@
expiryTime: dbInbound.expiryTime, expiryTime: dbInbound.expiryTime,
listen: '', listen: '',
port: RandomUtil.randomIntRange(10000, 60000), port: RandomUtil.randomInteger(10000, 60000),
protocol: baseInbound.protocol, protocol: baseInbound.protocol,
settings: Inbound.Settings.getSettings(baseInbound.protocol).toString(), settings: Inbound.Settings.getSettings(baseInbound.protocol).toString(),
streamSettings: baseInbound.stream.toString(), streamSettings: baseInbound.stream.toString(),
@@ -1011,21 +1002,6 @@
await this.submit(`/panel/inbound/update/${dbInbound.id}`, data, inModal); 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) { openAddClient(dbInboundId) {
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId); dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
clientModal.show({ clientModal.show({
@@ -1033,11 +1009,7 @@
okText: '{{ i18n "pages.client.submitAdd"}}', okText: '{{ i18n "pages.client.submitAdd"}}',
dbInbound: dbInbound, dbInbound: dbInbound,
confirm: async (clients, dbInboundId) => { confirm: async (clients, dbInboundId) => {
await this.addClient(clients, dbInboundId, clientModal).then((res) => { await this.addClient(clients, dbInboundId, clientModal);
if(res){
this.showQrcode(dbInboundId,clients)
}
});
}, },
isEdit: false isEdit: false
}); });
@@ -1060,7 +1032,6 @@
clientModal.show({ clientModal.show({
title: '{{ i18n "pages.client.edit"}}', title: '{{ i18n "pages.client.edit"}}',
okText: '{{ i18n "pages.client.submitEdit"}}', okText: '{{ i18n "pages.client.submitEdit"}}',
dbInbounds: this.dbInbounds,
dbInbound: dbInbound, dbInbound: dbInbound,
index: index, index: index,
confirm: async (client, dbInboundId, clientId) => { confirm: async (client, dbInboundId, clientId) => {
@@ -1086,36 +1057,12 @@
}; };
await this.submit(`/panel/inbound/addClient`, data, modal); 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) { async updateClient(client, dbInboundId, clientId) {
if (Array.isArray(client) && Array.isArray(dbInboundId) && Array.isArray(clientId)){ const data = {
const data = [] id: dbInboundId,
client.forEach((client, index) => { settings: '{"clients": [' + client.toString() + ']}',
data.push({ };
clientId: clientId[index], await this.submit(`/panel/inbound/updateClient/${clientId}`, data, clientModal);
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);
}
}, },
resetTraffic(dbInboundId) { resetTraffic(dbInboundId) {
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId); dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
@@ -1143,100 +1090,52 @@
onOk: () => this.submit('/panel/inbound/del/' + dbInboundId), onOk: () => this.submit('/panel/inbound/del/' + dbInboundId),
}); });
}, },
async delClientHandler(dbInboundId, currentClient, clients = []) { delClient(dbInboundId, client,confirmation = true) {
if (clients.length > 0) { dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
const deleteRequestData = []; clientId = this.getClientId(dbInbound.protocol, client);
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 : [];
if (confirmation){ if (confirmation){
const clientEmails = clients.length > 0 ? clients.map(item => item.email) : currentClient.email
this.$confirm({ this.$confirm({
title: '{{ i18n "pages.inbounds.deleteClient"}}' + ' ' + clientEmails, title: '{{ i18n "pages.inbounds.deleteClient"}}' + ' ' + client.email,
content: '{{ i18n "pages.inbounds.deleteClientContent"}}', content: '{{ i18n "pages.inbounds.deleteClientContent"}}',
class: themeSwitcher.currentTheme, class: themeSwitcher.currentTheme,
okText: '{{ i18n "delete"}}', okText: '{{ i18n "delete"}}',
cancelText: '{{ i18n "cancel"}}', cancelText: '{{ i18n "cancel"}}',
onOk: () => this.delClientHandler(dbInboundId, currentClient, clients), onOk: () => this.submit(`/panel/inbound/${dbInboundId}/delClient/${clientId}`),
}); });
} else { } else {
this.delClientHandler(dbInboundId, currentClient, clients) this.submit(`/panel/inbound/${dbInboundId}/delClient/${clientId}`);
} }
}, },
getSubGroupClients(dbInbounds, currentClient) { getSubGroupClients(dbInbounds, currentClient) {
const response = { const response = {
inbounds: [], inbounds: [],
clients: [], clients: [],
editIds: [], 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;
} }
if (dbInbounds && dbInbounds.length > 0 && currentClient) {
const inbound = dbInbound.toInbound(); dbInbounds.forEach((dbInboundItem) => {
if (!inbound || !Array.isArray(inbound.clients)) { const dbInbound = new DBInbound(dbInboundItem);
return; 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'])
}
})
}
}
}
})
} }
return response;
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;
},
getClientId(protocol, client) { getClientId(protocol, client) {
switch (protocol) { switch (protocol) {
case Protocols.TROJAN: return client.password; case Protocols.TROJAN: return client.password;
@@ -1264,10 +1163,10 @@
} }
return newDbInbound; return newDbInbound;
}, },
showQrcode(dbInboundId, client, isJustSub = false) { showQrcode(dbInboundId, client) {
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId); dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
newDbInbound = this.checkFallback(dbInbound); newDbInbound = this.checkFallback(dbInbound);
qrModal.show('{{ i18n "qrCode"}}', newDbInbound, client, isJustSub); qrModal.show('{{ i18n "qrCode"}}', newDbInbound, client);
}, },
showInfo(dbInboundId, client) { showInfo(dbInboundId, client) {
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId); dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
@@ -1287,61 +1186,36 @@
}, },
async switchEnableClient(dbInboundId, client) { async switchEnableClient(dbInboundId, client) {
this.loading() this.loading()
const subGroup = this.subSettings.enable ? this.getSubGroupClients(this.dbInbounds, client) : []; dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
if (subGroup && subGroup.clients && subGroup.clients.length > 0){ inbound = dbInbound.toInbound();
await this.updateClient(subGroup.clients.map(item => { clients = inbound.clients;
item.enable = !item.enable index = this.findIndexOfClient(dbInbound.protocol, clients, client);
return item clients[index].enable = !clients[index].enable;
}), subGroup.inbounds, subGroup.editIds); clientId = this.getClientId(dbInbound.protocol, clients[index]);
}else{ 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); this.loading(false);
}, },
async submit(url, data, model, isJson = false) { async submit(url, data, modal) {
const msg = isJson ? await HttpUtil.postWithModalJson(url, data, model) : await HttpUtil.postWithModal(url, data, model); const msg = await HttpUtil.postWithModal(url, data, modal);
if (msg.success) { if (msg.success) {
await this.getDBInbounds(); await this.getDBInbounds();
} }
return msg
}, },
getInboundClients(dbInbound) { getInboundClients(dbInbound) {
return dbInbound.toInbound().clients; 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) { 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){ if (confirmation){
const clientEmails = clients.length > 0 ? clients.map(item => item.email) : client.email
this.$confirm({ this.$confirm({
title: '{{ i18n "pages.inbounds.resetTraffic"}}' + ' ' + clientEmails, title: '{{ i18n "pages.inbounds.resetTraffic"}}' + ' ' + client.email,
content: '{{ i18n "pages.inbounds.resetTrafficContent"}}', content: '{{ i18n "pages.inbounds.resetTrafficContent"}}',
class: themeSwitcher.currentTheme, class: themeSwitcher.currentTheme,
okText: '{{ i18n "reset"}}', okText: '{{ i18n "reset"}}',
cancelText: '{{ i18n "cancel"}}', cancelText: '{{ i18n "cancel"}}',
onOk: () => this.resetClientTrafficHandler(client, dbInboundId, clients), onOk: () => this.submit('/panel/inbound/' + dbInboundId + '/resetClientTraffic/' + client.email),
}) })
} else { } else {
this.resetClientTrafficHandler(client, dbInboundId, clients); this.submit('/panel/inbound/' + dbInboundId + '/resetClientTraffic/' + client.email);
} }
}, },
resetAllTraffic() { resetAllTraffic() {
@@ -1377,10 +1251,6 @@
isExpiry(dbInbound, index) { isExpiry(dbInbound, index) {
return dbInbound.toInbound().isExpiry(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) { getUpStats(dbInbound, email) {
if (email.length == 0) return 0; if (email.length == 0) return 0;
clientStats = dbInbound.clientStats.find(stats => stats.email === email); clientStats = dbInbound.clientStats.find(stats => stats.email === email);
@@ -1404,9 +1274,9 @@
return remained>0 ? remained : 0; return remained>0 ? remained : 0;
}, },
clientStatsColor(dbInbound, email) { clientStatsColor(dbInbound, email) {
if (email.length == 0) return clientUsageColor(); if (email.length == 0) return ColorUtils.clientUsageColor();
clientStats = dbInbound.clientStats.find(stats => stats.email === email); clientStats = dbInbound.clientStats.find(stats => stats.email === email);
return clientUsageColor(clientStats, app.trafficDiff) return ColorUtils.clientUsageColor(clientStats, app.trafficDiff)
}, },
statsProgress(dbInbound, email) { statsProgress(dbInbound, email) {
if (email.length == 0) return 100; if (email.length == 0) return 100;
@@ -1424,17 +1294,17 @@
}, },
remainedDays(expTime){ remainedDays(expTime){
if (expTime == 0) return null; if (expTime == 0) return null;
if (expTime < 0) return formatSecond(expTime/-1000); if (expTime < 0) return TimeFormatter.formatSecond(expTime/-1000);
now = new Date().getTime(); now = new Date().getTime();
if (expTime < now) return '{{ i18n "depleted" }}'; if (expTime < now) return '{{ i18n "depleted" }}';
return formatSecond((expTime-now)/1000); return TimeFormatter.formatSecond((expTime-now)/1000);
}, },
statsExpColor(dbInbound, email){ statsExpColor(dbInbound, email){
if (email.length == 0) return '#7a316f'; if (email.length == 0) return '#7a316f';
clientStats = dbInbound.clientStats.find(stats => stats.email === email); clientStats = dbInbound.clientStats.find(stats => stats.email === email);
if (!clientStats) return '#7a316f'; if (!clientStats) return '#7a316f';
statsColor = usageColor(clientStats.down + clientStats.up, this.trafficDiff, clientStats.total); statsColor = ColorUtils.usageColor(clientStats.down + clientStats.up, this.trafficDiff, clientStats.total);
expColor = usageColor(new Date().getTime(), this.expireDiff, clientStats.expiryTime); expColor = ColorUtils.usageColor(new Date().getTime(), this.expireDiff, clientStats.expiryTime);
switch (true) { switch (true) {
case statsColor == "red" || expColor == "red": case statsColor == "red" || expColor == "red":
return "#cf3c3c"; // Red return "#cf3c3c"; // Red
@@ -1512,9 +1382,9 @@
} }
txtModal.show('{{ i18n "pages.inbounds.export"}}', copyText.join('\r\n'), 'All-Inbounds'); txtModal.show('{{ i18n "pages.inbounds.export"}}', copyText.join('\r\n'), 'All-Inbounds');
}, },
copyToClipboard(dbInboundId) { copy(dbInboundId) {
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId); dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
txtModal.show('{{ i18n "pages.inbounds.inboundData" }}', JSON.stringify(dbInbound, null, 2)); txtModal.show('{{ i18n "pages.inbounds.inboundData" }}', JSON.stringify(dbInbound, null, 2));
}, },
async startDataRefreshLoop() { async startDataRefreshLoop() {
while (this.isRefreshEnabled) { while (this.isRefreshEnabled) {
@@ -1568,7 +1438,7 @@
} }
}, },
watch: { watch: {
searchKey: debounce(function (newVal) { searchKey: Utils.debounce(function (newVal) {
this.searchInbounds(newVal); this.searchInbounds(newVal);
}, 500) }, 500)
}, },

View File

@@ -19,6 +19,18 @@
.ant-card-dark h2 { .ant-card-dark h2 {
color: var(--dark-color-text-primary); 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> </style>
<body> <body>
@@ -45,11 +57,11 @@
<a-progress type="dashboard" status="normal" <a-progress type="dashboard" status="normal"
:stroke-color="status.cpu.color" :stroke-color="status.cpu.color"
:percent="status.cpu.percent"></a-progress> :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> <a-icon type="area-chart"></a-icon>
<template slot="title"> <template slot="title">
<div><b>Logical Processors:</b> [[ (status.logicalPro) ]]</div> <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> </template>
</a-tooltip></div> </a-tooltip></div>
</a-col> </a-col>
@@ -58,7 +70,7 @@
:stroke-color="status.mem.color" :stroke-color="status.mem.color"
:percent="status.mem.percent"></a-progress> :percent="status.mem.percent"></a-progress>
<div> <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> </div>
</a-col> </a-col>
</a-row> </a-row>
@@ -70,7 +82,7 @@
:stroke-color="status.swap.color" :stroke-color="status.swap.color"
:percent="status.swap.percent"></a-progress> :percent="status.swap.percent"></a-progress>
<div> <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> </div>
</a-col> </a-col>
<a-col :span="12" style="text-align: center"> <a-col :span="12" style="text-align: center">
@@ -78,7 +90,7 @@
:stroke-color="status.disk.color" :stroke-color="status.disk.color"
:percent="status.disk.percent"></a-progress> :percent="status.disk.percent"></a-progress>
<div> <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> </div>
</a-col> </a-col>
</a-row> </a-row>
@@ -99,8 +111,8 @@
<a-col :sm="24" :lg="12"> <a-col :sm="24" :lg="12">
<a-card hoverable> <a-card hoverable>
<b>{{ i18n "pages.index.operationHours" }}:</b> <b>{{ i18n "pages.index.operationHours" }}:</b>
<a-tag :color="status.xray.color">Xray: [[ formatSecond(status.appStats.uptime) ]]</a-tag> <a-tag :color="status.xray.color">Xray: [[ TimeFormatter.formatSecond(status.appStats.uptime) ]]</a-tag>
<a-tag color="green">OS: [[ formatSecond(status.uptime) ]]</a-tag> <a-tag color="green">OS: [[ TimeFormatter.formatSecond(status.uptime) ]]</a-tag>
</a-card> </a-card>
</a-col> </a-col>
<a-col :sm="24" :lg="12"> <a-col :sm="24" :lg="12">
@@ -145,7 +157,7 @@
<a-col :sm="24" :lg="12"> <a-col :sm="24" :lg="12">
<a-card hoverable> <a-card hoverable>
<b>{{ i18n "usage"}}:</b> <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-tag color="green"> Threads: [[ status.appStats.threads ]] </a-tag>
</a-card> </a-card>
</a-col> </a-col>
@@ -207,7 +219,7 @@
<a-col :span="12"> <a-col :span="12">
<a-tag> <a-tag>
<a-tooltip> <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"> <template slot="title">
{{ i18n "pages.index.upSpeed" }} {{ i18n "pages.index.upSpeed" }}
</template> </template>
@@ -217,7 +229,7 @@
<a-col :span="12"> <a-col :span="12">
<a-tag> <a-tag>
<a-tooltip> <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"> <template slot="title">
{{ i18n "pages.index.downSpeed" }} {{ i18n "pages.index.downSpeed" }}
</template> </template>
@@ -236,7 +248,7 @@
<a-icon type="cloud-upload"></a-icon> <a-icon type="cloud-upload"></a-icon>
<template slot="title"> <template slot="title">
{{ i18n "pages.index.totalSent" }} {{ i18n "pages.index.totalSent" }}
</template> Out: [[ sizeFormat(status.netTraffic.sent) ]] </template> Out: [[ SizeFormatter.sizeFormat(status.netTraffic.sent) ]]
</a-tooltip> </a-tooltip>
</a-tag> </a-tag>
</a-col> </a-col>
@@ -246,7 +258,7 @@
<a-icon type="cloud-download"></a-icon> <a-icon type="cloud-download"></a-icon>
<template slot="title"> <template slot="title">
{{ i18n "pages.index.totalReceive" }} {{ i18n "pages.index.totalReceive" }}
</template> In: [[ sizeFormat(status.netTraffic.recv) ]] </template> In: [[ SizeFormatter.sizeFormat(status.netTraffic.recv) ]]
</a-tooltip> </a-tooltip>
</a-tag> </a-tag>
</a-col> </a-col>
@@ -260,14 +272,16 @@
</a-layout> </a-layout>
<a-modal id="version-modal" v-model="versionModal.visible" title='{{ i18n "pages.index.xraySwitch" }}' :closable="true" <a-modal id="version-modal" v-model="versionModal.visible" title='{{ i18n "pages.index.xraySwitch" }}' :closable="true"
@ok="() => versionModal.visible = false" :class="themeSwitcher.currentTheme" footer=""> @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> message='{{ i18n "pages.index.xraySwitchClickDesk" }}' show-icon></a-alert>
<template v-for="version, index in versionModal.versions"> <a-list class="ant-xray-version-list" bordered style="width: 100%;">
<a-tag :color="index % 2 == 0 ? 'purple' : 'green'" style="margin-right: 12px; margin-bottom: 12px" <a-list-item class="ant-xray-version-list-item" v-for="version in versionModal.versions">
@click="switchV2rayVersion(version)"> <a-list-item-meta>
[[ version ]] <template #title>[[ version ]]</template>
</a-tag> </a-list-item-meta>
</template> <a-radio :checked="version === `v${status.xray.version}`" @click="switchV2rayVersion(version)"></a-radio>
</a-list-item>
</a-list>
</a-modal> </a-modal>
<a-modal id="log-modal" v-model="logModal.visible" <a-modal id="log-modal" v-model="logModal.visible"
:closable="true" @cancel="() => logModal.visible = false" :closable="true" @cancel="() => logModal.visible = false"
@@ -314,25 +328,34 @@
</a-form> </a-form>
<div class="ant-input" style="height: auto; max-height: 500px; overflow: auto; margin-top: 0.5rem;" v-html="logModal.formattedLogs"></div> <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>
<a-modal id="backup-modal" v-model="backupModal.visible" :title="backupModal.title" <a-modal id="backup-modal"
:closable="true" footer="" v-model="backupModal.visible"
title='{{ i18n "pages.index.backupTitle" }}'
:closable="true"
footer=""
:class="themeSwitcher.currentTheme"> :class="themeSwitcher.currentTheme">
<a-alert type="warning" style="margin-bottom: 10px; width: fit-content" <a-list class="ant-backup-list" bordered style="width: 100%;">
:message="backupModal.description" <a-list-item class="ant-backup-list-item" @click="exportDatabase()">
show-icon> <a-list-item-meta>
</a-alert> <template #title>{{ i18n "pages.index.exportDatabase" }}</template>
<a-space direction="horizontal" style="text-align: center; margin-bottom: 10px;"> <template #description>{{ i18n "pages.index.exportDatabaseDesc" }}</template>
<a-button type="primary" @click="exportDatabase()"> </a-list-item-meta>
[[ backupModal.exportText ]] <a-icon type="right" />
</a-button> </a-list-item>
<a-button type="primary" @click="importDatabase()"> <a-list-item class="ant-backup-list-item" @click="importDatabase()">
[[ backupModal.importText ]] <a-list-item-meta>
</a-button> <template #title>{{ i18n "pages.index.importDatabase" }}</template>
</a-space> <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-modal>
</a-layout> </a-layout>
{{template "js" .}} {{template "js" .}}
<script src="{{ .base_path }}assets/clipboard/clipboard.min.js?{{ .cur_ver }}"></script>
{{template "component/themeSwitcher" .}} {{template "component/themeSwitcher" .}}
{{template "textModal"}} {{template "textModal"}}
<script> <script>
@@ -354,7 +377,7 @@
if (this.total === 0) { if (this.total === 0) {
return 0; return 0;
} }
return toFixed(this.current / this.total * 100, 2); return NumberFormatter.toFixed(this.current / this.total * 100, 2);
} }
get color() { get color() {
@@ -397,7 +420,7 @@
this.logicalPro = data.logicalPro; this.logicalPro = data.logicalPro;
this.cpuSpeedMhz = data.cpuSpeedMhz; this.cpuSpeedMhz = data.cpuSpeedMhz;
this.disk = new CurTotal(data.disk.current, data.disk.total); 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.mem = new CurTotal(data.mem.current, data.mem.total);
this.netIO = data.netIO; this.netIO = data.netIO;
this.netTraffic = data.netTraffic; this.netTraffic = data.netTraffic;
@@ -492,24 +515,11 @@
const backupModal = { const backupModal = {
visible: false, visible: false,
title: '', show() {
description: '', this.visible = true;
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;
}, },
hide() { hide() {
this.visible = false; this.visible = false;
}, },
}; };
@@ -605,12 +615,7 @@
txtModal.show('config.json', JSON.stringify(msg.obj, null, 2), 'config.json'); txtModal.show('config.json', JSON.stringify(msg.obj, null, 2), 'config.json');
}, },
openBackup() { openBackup() {
backupModal.show({ backupModal.show();
title: '{{ i18n "pages.index.backupTitle" }}',
description: '{{ i18n "pages.index.backupDescription" }}',
exportText: '{{ i18n "pages.index.exportDatabase" }}',
importText: '{{ i18n "pages.index.importDatabase" }}',
});
}, },
exportDatabase() { exportDatabase() {
window.location = basePath + 'server/getDb'; window.location = basePath + 'server/getDb';

View File

@@ -108,16 +108,17 @@
</a-row> </a-row>
</a-card> </a-card>
<a-tabs default-active-key="1"> <a-tabs default-active-key="1">
<a-tab-pane key="1" tab='{{ i18n "pages.settings.panelSettings"}}'> <a-tab-pane key="1" tab='{{ i18n "pages.settings.panelSettings" }}' style="padding-top: 20px;">
<a-list item-layout="horizontal"> <a-collapse>
<a-list-item> <a-collapse-panel header='{{ i18n "pages.xray.generalConfigs"}}'>
<a-row style="padding: 20px"> <a-setting-list-item paddings="small">
<a-col :lg="24" :xl="12"> <template #title>
<a-list-item-meta title='{{ i18n "pages.settings.remarkModel"}}'> {{ i18n "pages.settings.remarkModel"}}
<template slot="description">{{ i18n "pages.settings.sampleRemark"}}: <i>#[[ remarkSample ]]</i></template> </template>
</a-list-item-meta> <template #description>
</a-col> {{ i18n "pages.settings.sampleRemark"}}: <i>#[[ remarkSample ]]</i>
<a-col :lg="24" :xl="12"> </template>
<template #control>
<a-input-group style="width: 100%;"> <a-input-group style="width: 100%;">
<a-select style="padding-right: .5rem; min-width: 80%; width: auto;" <a-select style="padding-right: .5rem; min-width: 80%; width: auto;"
mode="multiple" mode="multiple"
@@ -129,298 +130,523 @@
<a-select-option v-for="key in remarkSeparators" :value="key">[[ key ]]</a-select-option> <a-select-option v-for="key in remarkSeparators" :value="key">[[ key ]]</a-select-option>
</a-select> </a-select>
</a-input-group> </a-input-group>
</a-col> </template>
</a-row> </a-setting-list-item>
</a-list-item> <a-setting-list-item paddings="small">
<setting-list-item type="text" title='{{ i18n "pages.settings.panelListeningIP"}}' desc='{{ i18n "pages.settings.panelListeningIPDesc"}}' v-model="allSetting.webListen"></setting-list-item> <template #title>{{ i18n "pages.settings.panelListeningIP"}}</template>
<setting-list-item type="text" title='{{ i18n "pages.settings.panelListeningDomain"}}' desc='{{ i18n "pages.settings.panelListeningDomainDesc"}}' v-model="allSetting.webDomain"></setting-list-item> <template #description>{{ i18n "pages.settings.panelListeningIPDesc"}}</template>
<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> <template #control>
<setting-list-item type="text" title='{{ i18n "pages.settings.publicKeyPath"}}' desc='{{ i18n "pages.settings.publicKeyPathDesc"}}' v-model="allSetting.webCertFile"></setting-list-item> <a-input type="text" v-model="allSetting.webListen"></a-input>
<setting-list-item type="text" title='{{ i18n "pages.settings.privateKeyPath"}}' desc='{{ i18n "pages.settings.privateKeyPathDesc"}}' v-model="allSetting.webKeyFile"></setting-list-item> </template>
<setting-list-item type="text" title='{{ i18n "pages.settings.panelUrlPath"}}' desc='{{ i18n "pages.settings.panelUrlPathDesc"}}' v-model="allSetting.webBasePath"></setting-list-item> </a-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> <a-setting-list-item paddings="small">
<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> <template #title>{{ i18n "pages.settings.panelListeningDomain"}}</template>
<setting-list-item type="number" title='{{ i18n "pages.settings.expireTimeDiff" }}' desc='{{ i18n "pages.settings.expireTimeDiffDesc" }}' v-model="allSetting.expireDiff" :min="0"></setting-list-item> <template #description>{{ i18n "pages.settings.panelListeningDomainDesc"}}</template>
<setting-list-item type="number" title='{{ i18n "pages.settings.trafficDiff" }}' desc='{{ i18n "pages.settings.trafficDiffDesc" }}' v-model="allSetting.trafficDiff" :min="0"></setting-list-item> <template #control>
<setting-list-item type="text" title='{{ i18n "pages.settings.timeZone"}}' desc='{{ i18n "pages.settings.timeZoneDesc"}}' v-model="allSetting.timeLocation"></setting-list-item> <a-input type="text" v-model="allSetting.webDomain"></a-input>
<a-list-item> </template>
<a-row style="padding: 20px"> </a-setting-list-item>
<a-col :lg="24" :xl="12"> <a-setting-list-item paddings="small">
<a-list-item-meta title='{{ i18n "pages.settings.datepicker"}}'> <template #title>{{ i18n "pages.settings.panelPort"}}</template>
<template slot="description">{{ i18n "pages.settings.datepickerDescription"}}</template> <template #description>{{ i18n "pages.settings.panelPortDesc"}}</template>
</a-list-item-meta> <template #control>
</a-col> <a-input-number :min="1" :min="65531" v-model="allSetting.webPort" style="width: 100%;"></a-input>
<a-col :lg="24" :xl="12"> </template>
<template> </a-setting-list-item>
<a-select style="width: 100%" :dropdown-class-name="themeSwitcher.currentTheme" v-model="datepicker"> <a-setting-list-item paddings="small">
<a-select-option v-for="item in datepickerList" :value="item.value"> <template #title>{{ i18n "pages.settings.panelUrlPath"}}</template>
<span v-text="item.name"></span> <template #description>{{ i18n "pages.settings.panelUrlPathDesc"}}</template>
</a-select-option> <template #control>
</a-select> <a-input type="text" v-model="allSetting.webBasePath"></a-input>
</template> </template>
</a-col> </a-setting-list-item>
</a-row> <a-setting-list-item paddings="small">
</a-list-item> <template #title>{{ i18n "pages.settings.sessionMaxAge" }}</template>
<a-list-item> <template #description>{{ i18n "pages.settings.sessionMaxAgeDesc" }}</template>
<a-row style="padding: 20px"> <template #control>
<a-col :lg="24" :xl="12"> <a-input-number :min="60" v-model="allSetting.sessionMaxAge" style="width: 100%;"></a-input>
<a-list-item-meta title="Language"></a-list-item-meta> </template>
</a-col> </a-setting-list-item>
<a-col :lg="24" :xl="12"> <a-setting-list-item paddings="small">
<template> <template #title>{{ i18n "pages.settings.pageSize" }}</template>
<a-select ref="selectLang" <template #description>{{ i18n "pages.settings.pageSizeDesc" }}</template>
v-model="lang" <template #control>
@change="setLang(lang)" <a-input-number :min="0" step="5" v-model="allSetting.pageSize" style="width: 100%;"></a-input>
:dropdown-class-name="themeSwitcher.currentTheme" </template>
style="width: 100%"> </a-setting-list-item>
<a-select-option :value="l.value" :label="l.value" v-for="l in supportLangs"> <a-setting-list-item paddings="small">
<span role="img" :aria-label="l.name" v-text="l.icon"></span> &nbsp;&nbsp; <span v-text="l.name"></span> <template #title>{{ i18n "pages.settings.language"}}</template>
</a-select-option> <template #control>
</a-select> <a-select ref="selectLang"
</template> v-model="lang"
</a-col> @change="LanguageManager.setLanguage(lang)"
</a-row> :dropdown-class-name="themeSwitcher.currentTheme"
</a-list-item> style="width: 100%">
</a-list> <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> &nbsp;&nbsp; <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>
<a-tab-pane key="2" tab='{{ i18n "pages.settings.securitySettings"}}' style="padding: 20px;"> <a-tab-pane key="2" tab='{{ i18n "pages.settings.securitySettings" }}' style="padding-top: 20px;">
<a-divider>{{ i18n "pages.settings.security.admin"}}</a-divider> <a-collapse>
<a-form layout="horizontal" :colon="false" style="float: left; margin-bottom: 2rem;" :label-col="{ md: {span:10} }" :wrapper-col="{ md: {span:14} }"> <a-collapse-panel header='{{ i18n "pages.settings.security.admin"}}'>
<a-form-item label='{{ i18n "pages.settings.oldUsername"}}'> <a-setting-list-item paddings="small">
<a-input autocomplete="username" v-model="user.oldUsername"></a-input> <template #title>{{ i18n "pages.settings.oldUsername"}}</template>
</a-form-item> <template #control>
<a-form-item label='{{ i18n "pages.settings.currentPassword"}}'> <a-input autocomplete="username" v-model="user.oldUsername"></a-input>
<password-input autocomplete="current-password" v-model="user.oldPassword"></password-input> </template>
</a-form-item> </a-setting-list-item>
<a-form-item label='{{ i18n "pages.settings.newUsername"}}'> <a-setting-list-item paddings="small">
<a-input v-model="user.newUsername"></a-input> <template #title>{{ i18n "pages.settings.currentPassword"}}</template>
</a-form-item> <template #control>
<a-form-item label='{{ i18n "pages.settings.newPassword"}}'> <a-password-input autocomplete="current-password" v-model="user.oldPassword"></a-password-input>
<password-input autocomplete="new-password" v-model="user.newPassword"></password-input> </template>
</a-form-item> </a-setting-list-item>
<a-form-item label=" "> <a-setting-list-item paddings="small">
<a-button type="primary" @click="updateUser">{{ i18n "confirm" }}</a-button> <template #title>{{ i18n "pages.settings.newUsername"}}</template>
</a-form-item> <template #control>
</a-form> <a-input v-model="user.newUsername"></a-input>
<a-divider>{{ i18n "pages.settings.security.secret"}}</a-divider> </template>
<a-form style="padding: 0 20px;"> </a-setting-list-item>
<a-list-item> <a-setting-list-item paddings="small">
<a-row> <template #title>{{ i18n "pages.settings.newPassword"}}</template>
<a-col :lg="24" :xl="12"> <template #control>
<a-list-item-meta title='{{ i18n "pages.settings.security.loginSecurity" }}' <a-password-input autocomplete="new-password" v-model="user.newPassword"></a-password-input>
description='{{ i18n "pages.settings.security.loginSecurityDesc" }}'> </template>
</a-list-item-meta> </a-setting-list-item>
</a-col> <a-list-item>
<a-col :lg="24" :xl="12"> <a-space direction="horizontal" style="padding: 0 20px;">
<template> <a-button type="primary" @click="updateUser">{{ i18n "confirm" }}</a-button>
<a-switch @change="toggleToken(allSetting.secretEnable)" v-model="allSetting.secretEnable"></a-switch> </a-space>
<a-icon style="margin-left: 1rem;" v-if="allSetting.secretEnable" :spin="this.changeSecret" type="sync" @click="getNewSecret"></a-icon> </a-list-item>
</template> </a-collapse-panel>
</a-col> <a-collapse-panel header='{{ i18n "pages.settings.security.secret"}}'>
</a-row> <a-setting-list-item paddings="small">
</a-list-item> <template #title>{{ i18n "pages.settings.security.loginSecurity" }}</template>
<a-list-item> <template #description>{{ i18n "pages.settings.security.loginSecurityDesc" }}</template>
<a-row> <template #control>
<a-col :lg="24" :xl="12"> <a-switch @change="toggleToken(allSetting.secretEnable)" v-model="allSetting.secretEnable"></a-switch>
<a-list-item-meta title='{{ i18n "pages.settings.security.secretToken" }}' <a-icon style="margin-left: 1rem;" v-if="allSetting.secretEnable" :spin="this.changeSecret" type="sync" @click="getNewSecret"></a-icon>
description='{{ i18n "pages.settings.security.secretTokenDesc" }}'> </template>
</a-list-item-meta> </a-setting-list-item>
</a-col> <a-setting-list-item paddings="small">
<a-col :lg="24" :xl="12"> <template #title>{{ i18n "pages.settings.security.secretToken" }}</template>
<template> <template #description>{{ i18n "pages.settings.security.secretTokenDesc" }}</template>
<a-textarea type="text" :disabled="!allSetting.secretEnable" v-model="user.loginSecret"></a-textarea> <template #control>
</template> <a-textarea type="text" :disabled="!allSetting.secretEnable" v-model="user.loginSecret"></a-textarea>
</a-col> </template>
</a-row> </a-setting-list-item>
</a-list-item> <a-list-item>
<a-button type="primary" :loading="this.changeSecret" @click="updateSecret">{{ i18n "confirm" }}</a-button> <a-space direction="horizontal" style="padding: 0 20px;">
</a-form> <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>
<a-tab-pane key="3" tab='{{ i18n "pages.settings.TGBotSettings"}}'> <a-tab-pane key="3" tab='{{ i18n "pages.settings.TGBotSettings" }}' style="padding-top: 20px;">
<a-list item-layout="horizontal"> <a-collapse>
<setting-list-item type="switch" title='{{ i18n "pages.settings.telegramBotEnable" }}' desc='{{ i18n "pages.settings.telegramBotEnableDesc" }}' v-model="allSetting.tgBotEnable"></setting-list-item> <a-collapse-panel header='{{ i18n "pages.xray.generalConfigs"}}'>
<setting-list-item type="text" title='{{ i18n "pages.settings.telegramToken"}}' desc='{{ i18n "pages.settings.telegramTokenDesc"}}' v-model="allSetting.tgBotToken"></setting-list-item> <a-setting-list-item paddings="small">
<setting-list-item type="text" title='{{ i18n "pages.settings.telegramChatId"}}' desc='{{ i18n "pages.settings.telegramChatIdDesc"}}' v-model="allSetting.tgBotChatId"></setting-list-item> <template #title>{{ i18n "pages.settings.telegramBotEnable" }}</template>
<setting-list-item type="text" title='{{ i18n "pages.settings.telegramNotifyTime"}}' desc='{{ i18n "pages.settings.telegramNotifyTimeDesc"}}' v-model="allSetting.tgRunTime"></setting-list-item> <template #description>{{ i18n "pages.settings.telegramBotEnableDesc" }}</template>
<setting-list-item type="switch" title='{{ i18n "pages.settings.tgNotifyBackup" }}' desc='{{ i18n "pages.settings.tgNotifyBackupDesc" }}' v-model="allSetting.tgBotBackup"></setting-list-item> <template #control>
<setting-list-item type="switch" title='{{ i18n "pages.settings.tgNotifyLogin" }}' desc='{{ i18n "pages.settings.tgNotifyLoginDesc" }}' v-model="allSetting.tgBotLoginNotify"></setting-list-item> <a-switch v-model="allSetting.tgBotEnable"></a-switch>
<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> </template>
<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> </a-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-setting-list-item paddings="small">
<a-list-item> <template #title>{{ i18n "pages.settings.telegramToken"}}</template>
<a-row style="padding: 20px"> <template #description>{{ i18n "pages.settings.telegramTokenDesc"}}</template>
<a-col :lg="24" :xl="12"> <template #control>
<a-list-item-meta title="Telegram Bot Language" /> <a-input type="text" v-model="allSetting.tgBotToken"></a-input>
</a-col> </template>
<a-col :lg="24" :xl="12"> </a-setting-list-item>
<template> <a-setting-list-item paddings="small">
<a-select ref="selectBotLang" v-model="allSetting.tgLang" :dropdown-class-name="themeSwitcher.currentTheme" style="width: 100%"> <template #title>{{ i18n "pages.settings.telegramChatId"}}</template>
<a-select-option :value="l.value" :label="l.value" v-for="l in supportLangs"> <template #description>{{ i18n "pages.settings.telegramChatIdDesc"}}</template>
<span role="img" :aria-label="l.name" v-text="l.icon"></span> &nbsp;&nbsp; <span v-text="l.name"></span> <template #control>
</a-select-option> <a-input type="text" v-model="allSetting.tgBotChatId"></a-input>
</a-select> </template>
</template> </a-setting-list-item>
</a-col> <a-setting-list-item paddings="small">
</a-row> <template #title>{{ i18n "pages.settings.telegramBotLanguage"}}</template>
</a-list-item> <template #control>
</a-list> <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> &nbsp;&nbsp; <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>
<a-tab-pane key="4" tab='{{ i18n "pages.settings.subSettings" }}'> <a-tab-pane key="4" tab='{{ i18n "pages.settings.subSettings" }}' style="padding-top: 20px;">
<a-list item-layout="horizontal"> <a-collapse>
<setting-list-item type="switch" title='{{ i18n "pages.settings.subEnable"}}' desc='{{ i18n "pages.settings.subEnableDesc"}}' v-model="allSetting.subEnable"></setting-list-item> <a-collapse-panel header='{{ i18n "pages.xray.generalConfigs"}}'>
<setting-list-item type="switch" title='{{ i18n "pages.settings.subSyncEnable"}}' desc='{{ i18n "pages.settings.subSyncEnableDesc"}}' v-model="allSetting.subSyncEnable"></setting-list-item> <a-setting-list-item paddings="small">
<setting-list-item type="switch" title='{{ i18n "pages.settings.subEncrypt"}}' desc='{{ i18n "pages.settings.subEncryptDesc"}}' v-model="allSetting.subEncrypt"></setting-list-item> <template #title>{{ i18n "pages.settings.subEnable"}}</template>
<setting-list-item type="switch" title='{{ i18n "pages.settings.subShowInfo"}}' desc='{{ i18n "pages.settings.subShowInfoDesc"}}' v-model="allSetting.subShowInfo"></setting-list-item> <template #description>{{ i18n "pages.settings.subEnableDesc"}}</template>
<setting-list-item type="text" title='{{ i18n "pages.settings.subListen"}}' desc='{{ i18n "pages.settings.subListenDesc"}}' v-model="allSetting.subListen"></setting-list-item> <template #control>
<setting-list-item type="text" title='{{ i18n "pages.settings.subDomain"}}' desc='{{ i18n "pages.settings.subDomainDesc"}}' v-model="allSetting.subDomain"></setting-list-item> <a-switch v-model="allSetting.subEnable"></a-switch>
<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> </template>
<setting-list-item type="text" title='{{ i18n "pages.settings.subPath"}}' desc='{{ i18n "pages.settings.subPathDesc"}}' v-model="allSetting.subPath"></setting-list-item> </a-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> <a-setting-list-item paddings="small">
<setting-list-item type="text" title='{{ i18n "pages.settings.subKeyPath"}}' desc='{{ i18n "pages.settings.subKeyPathDesc"}}' v-model="allSetting.subKeyFile"></setting-list-item> <template #title>{{ i18n "pages.settings.subListen"}}</template>
<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> <template #description>{{ i18n "pages.settings.subListenDesc"}}</template>
<setting-list-item type="number" title='{{ i18n "pages.settings.subUpdates"}}' desc='{{ i18n "pages.settings.subUpdatesDesc"}}' v-model="allSetting.subUpdates" :min="1"></setting-list-item> <template #control>
</a-list> <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>
<a-tab-pane key="5" tab='{{ i18n "pages.settings.subSettings" }} Json' v-if="allSetting.subEnable"> <a-tab-pane key="5" tab='{{ i18n "pages.settings.subSettings" }} Json' v-if="allSetting.subEnable" style="padding-top: 20px;">
<a-list item-layout="horizontal"> <a-collapse>
<setting-list-item type="text" title='{{ i18n "pages.settings.subPath"}}' desc='{{ i18n "pages.settings.subPathDesc"}}' v-model="allSetting.subJsonPath"></setting-list-item> <a-collapse-panel header='{{ i18n "pages.xray.generalConfigs"}}'>
<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-setting-list-item paddings="small">
<a-list-item style="padding: 20px"> <template #title>{{ i18n "pages.settings.subPath"}}</template>
<a-row> <template #description>{{ i18n "pages.settings.subPathDesc"}}</template>
<a-col :lg="24" :xl="12"> <template #control>
<a-list-item-meta title='{{ i18n "pages.settings.fragment"}}'> <a-input type="text" v-model="allSetting.subJsonPath"></a-input>
<template slot="description">{{ i18n "pages.settings.fragmentDesc"}}</template> </template>
</a-list-item-meta> </a-setting-list-item>
</a-col> <a-setting-list-item paddings="small">
<a-col :lg="24" :xl="12"> <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-switch v-model="fragment"></a-switch>
</a-col> </template>
</a-row> </a-setting-list-item>
<a-collapse v-if="fragment" style="margin-top: 14px;"> <a-list-item v-if="fragment" style="padding: 10px 20px;">
<a-collapse-panel header='{{ i18n "pages.settings.fragmentSett"}}' v-if="fragment"> <a-collapse>
<setting-list-item style="padding: 10px 20px" type="text" title='Packets' v-model="fragmentPackets" placeholder="1-1 | 1-3 | tlshello | ..."></setting-list-item> <a-collapse-panel header='{{ i18n "pages.settings.fragmentSett"}}' v-if="fragment">
<setting-list-item style="padding: 10px 20px" type="text" title='Length' v-model="fragmentLength" placeholder="100-200"></setting-list-item> <a-setting-list-item paddings="small">
<setting-list-item style="padding: 10px 20px" type="text" title='Interval' v-model="fragmentInterval" placeholder="10-20"></setting-list-item> <template #title>Packets</template>
</a-collapse-panel> <template #control>
</a-collapse> <a-input type="text" v-model="fragmentPackets" placeholder="1-1 | 1-3 | tlshello | ..."></a-input>
</a-list-item> </template>
<a-list-item style="padding: 20px"> </a-setting-list-item>
<a-row> <a-setting-list-item paddings="small">
<a-col :lg="24" :xl="12"> <template #title>Length</template>
<a-list-item-meta title='Noises'> <template #control>
<template slot="description">{{ i18n "pages.settings.noisesDesc"}}</template> <a-input type="text" v-model="fragmentLength" placeholder="100-200"></a-input>
</a-list-item-meta> </template>
</a-col> </a-setting-list-item>
<a-col :lg="24" :xl="12"> <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-switch v-model="noises"></a-switch>
</a-col> </template>
</a-row> </a-setting-list-item>
<a-collapse v-if="noises" style="margin-top: 14px;"> <a-list-item v-if="noises" style="padding: 10px 20px;">
<a-collapse-panel v-for="(noise, index) in noisesArray" :key="index" :header="`Noise ${index + 1}`"> <a-collapse>
<a-list-item style="padding: 10px 20px"> <a-collapse-panel v-for="(noise, index) in noisesArray" :key="index" :header="`Noise №${index + 1}`">
<a-row> <a-setting-list-item paddings="small">
<a-col :lg="24" :xl="12"> <template #title>Type</template>
<a-list-item-meta title='Type'></a-list-item-meta> <template #control>
</a-col>
<a-col :lg="24" :xl="12">
<a-select :value="noise.type" style="width: 100%" :dropdown-class-name="themeSwitcher.currentTheme" <a-select :value="noise.type" style="width: 100%" :dropdown-class-name="themeSwitcher.currentTheme"
@change="(value) => updateNoiseType(index, value)"> @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> [[ p ]] </a-select-option>
</a-select> </a-select>
</a-col> </template>
</a-row> </a-setting-list-item>
</a-list-item> <a-setting-list-item paddings="small">
<setting-list-item style="padding: 10px 20px" type="text" title='Packet' :value="noise.packet" <template #title>Packet</template>
@input="(value) => updateNoisePacket(index, value)" placeholder="5-10"></setting-list-item> <template #control>
<setting-list-item style="padding: 10px 20px" type="text" title='Delay (ms)' :value="noise.delay" <a-input type="text" :value="noise.packet" @input="(value) => updateNoisePacket(index, event.target.value)" placeholder="5-10"></a-input>
@input="(value) => updateNoiseDelay(index, value)" placeholder="10-20"></setting-list-item> </template>
<a-button v-if="noisesArray.length > 1" type="danger" @click="removeNoise(index)">Remove</a-button> </a-setting-list-item>
</a-collapse-panel> <a-setting-list-item paddings="small">
</a-collapse> <template #title>Delay (ms)</template>
<a-button v-if="noises" type="primary" @click="addNoise" style="margin-top: 10px">Add Noise</a-button> <template #control>
</a-list-item> <a-input type="text" :value="noise.delay" @input="(value) => updateNoiseDelay(index, event.target.value)" placeholder="10-20"></a-input>
<a-list-item style="padding: 20px"> </template>
<a-row> </a-setting-list-item>
<a-col :lg="24" :xl="12"> <a-space direction="horizontal" style="padding: 10px 20px;">
<a-list-item-meta title='{{ i18n "pages.settings.mux"}}'> <a-button v-if="noisesArray.length > 1" type="danger" @click="removeNoise(index)">Remove</a-button>
<template slot="description">{{ i18n "pages.settings.muxDesc"}}</template> </a-space>
</a-list-item-meta> </a-collapse-panel>
</a-col> </a-collapse>
<a-col :lg="24" :xl="12"> <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-switch v-model="enableMux"></a-switch>
</a-col> </template>
</a-row> </a-setting-list-item>
<a-collapse v-if="enableMux" style="margin-top: 14px;"> <a-list-item v-if="enableMux" style="padding: 10px 20px;">
<a-collapse-panel header='{{ i18n "pages.settings.muxSett"}}'> <a-collapse>
<setting-list-item style="padding: 10px 20px" type="number" title='Concurrency' v-model="muxConcurrency" :min="-1" :max="1024"></setting-list-item> <a-collapse-panel header='{{ i18n "pages.settings.muxSett"}}'>
<setting-list-item style="padding: 10px 20px" type="number" title='xudp Concurrency' v-model="muxXudpConcurrency" :min="-1" :max="1024"></setting-list-item> <a-setting-list-item paddings="small">
<a-list-item style="padding: 10px 20px"> <template #title>Concurrency</template>
<a-row> <template #control>
<a-col :lg="24" :xl="12"> <a-input-number v-model="muxConcurrency" :min="-1" :max="1024" style="width: 100%;"></a-input-number>
<a-list-item-meta title='xudp UDP 443'></a-list-item-meta> </template>
</a-col> </a-setting-list-item>
<a-col :lg="24" :xl="12"> <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 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-option :value="p" :label="p" v-for="p in ['reject', 'allow', 'skip']"> [[ p ]] </a-select-option>
</a-select> </a-select>
</a-col> </template>
</a-row> </a-setting-list-item>
</a-list-item> </a-collapse-panel>
</a-collapse-panel> </a-collapse>
</a-collapse> </a-list-item>
</a-list-item> </a-collapse-panel>
<a-list-item style="padding: 20px"> <a-collapse-panel header='{{ i18n "pages.settings.direct" }}'>
<a-row> <a-setting-list-item paddings="small">
<a-col :lg="24" :xl="12"> <template #title>{{ i18n "pages.settings.direct"}}</template>
<a-list-item-meta title='{{ i18n "pages.settings.direct"}}'> <template #description>{{ i18n "pages.settings.directDesc"}}</template>
<template slot="description">{{ i18n "pages.settings.directDesc"}}</template> <template #control>
</a-list-item-meta>
</a-col>
<a-col :lg="24" :xl="12">
<a-switch v-model="enableDirect"></a-switch> <a-switch v-model="enableDirect"></a-switch>
</a-col> </template>
</a-row> </a-setting-list-item>
<a-collapse v-if="enableDirect" style="margin-top: 14px;"> <a-list-item v-if="enableDirect" style="padding: 10px 20px;">
<a-collapse-panel header='{{ i18n "pages.settings.direct"}}'> <a-collapse>
<a-list-item> <a-collapse-panel header='{{ i18n "pages.settings.direct"}}'>
<a-row style="padding: 0 20px"> <a-setting-list-item paddings="small">
<a-col :lg="24" :xl="12"> <template #title>{{ i18n "pages.xray.directips" }}</template>
<a-list-item-meta <template #control>
title='{{ i18n "pages.xray.directips" }}'/> <a-select mode="tags" style="width: 100%" v-model="directIPs" :dropdown-class-name="themeSwitcher.currentTheme">
</a-col> <a-select-option :value="p.value" :label="p.label" v-for="p in directIPsOptions">[[ p.label ]]</a-select-option>
<a-col :lg="24" :xl="12"> </a-select>
<a-select mode="tags" style="width: 100%" </template>
v-model="directIPs" </a-setting-list-item>
:dropdown-class-name="themeSwitcher.currentTheme"> <a-setting-list-item paddings="small">
<a-select-option :value="p.value" :label="p.label" <template #title>{{ i18n "pages.xray.directdomains" }}</template>
v-for="p in directIPsOptions"> [[ p.label ]] <template #control>
</a-select-option> <a-select mode="tags" style="width: 100%" v-model="directDomains" :dropdown-class-name="themeSwitcher.currentTheme">
</a-select> <a-select-option :value="p.value" :label="p.label" v-for="p in diretDomainsOptions">[[ p.label ]]</a-select-option>
</a-col> </a-select>
</a-row> </template>
</a-list-item> </a-setting-list-item>
<a-list-item> </a-collapse-panel>
<a-row style="padding: 0 20px"> </a-collapse>
<a-col :lg="24" :xl="12"> </a-list-item>
<a-list-item-meta </a-collapse-panel>
title='{{ i18n "pages.xray.directdomains" }}'/> </a-collapse>
</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>
</a-tab-pane> </a-tab-pane>
</a-tabs> </a-tabs>
</a-space> </a-space>
@@ -446,7 +672,7 @@
allSetting: new AllSetting(), allSetting: new AllSetting(),
saveBtnDisable: true, saveBtnDisable: true,
user: {}, user: {},
lang: getLang(), lang: LanguageManager.getLanguage(),
remarkModels: { i: 'Inbound', e: 'Email', o: 'Other' }, remarkModels: { i: 'Inbound', e: 'Email', o: 'Other' },
remarkSeparators: [' ', '-', '_', '@', ':', '~', '|', ',', '.', '/'], remarkSeparators: [' ', '-', '_', '@', ':', '~', '|', ',', '.', '/'],
datepickerList: [{ name: 'Gregorian (Standard)', value: 'gregorian' }, { name: 'Jalalian (شمسی)', value: 'jalalian' }], datepickerList: [{ name: 'Gregorian (Standard)', value: 'gregorian' }, { name: 'Jalalian (شمسی)', value: 'jalalian' }],
@@ -606,7 +832,7 @@
if (host == this.oldAllSetting.webDomain) host = null; if (host == this.oldAllSetting.webDomain) host = null;
if (port == this.oldAllSetting.webPort) port = null; if (port == this.oldAllSetting.webPort) port = null;
const isTLS = webCertFile !== "" || webKeyFile !== ""; 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); window.location.replace(url);
} }
}, },

View File

@@ -65,15 +65,15 @@
</tr> </tr>
<tr> <tr>
<td>WARP+ Data</td> <td>WARP+ Data</td>
<td>[[ sizeFormat(warpModal.warpConfig.account.premium_data) ]]</td> <td>[[ SizeFormatter.sizeFormat(warpModal.warpConfig.account.premium_data) ]]</td>
</tr> </tr>
<tr class="client-table-odd-row"> <tr class="client-table-odd-row">
<td>Quota</td> <td>Quota</td>
<td>[[ sizeFormat(warpModal.warpConfig.account.quota) ]]</td> <td>[[ SizeFormatter.sizeFormat(warpModal.warpConfig.account.quota) ]]</td>
</tr> </tr>
<tr v-if="!ObjectUtil.isEmpty(warpModal.warpConfig.account.usage)"> <tr v-if="!ObjectUtil.isEmpty(warpModal.warpConfig.account.usage)">
<td>Usage</td> <td>Usage</td>
<td>[[ sizeFormat(warpModal.warpConfig.account.usage) ]]</td> <td>[[ SizeFormatter.sizeFormat(warpModal.warpConfig.account.usage) ]]</td>
</tr> </tr>
</template> </template>
</table> </table>

File diff suppressed because it is too large Load Diff

View File

@@ -33,7 +33,7 @@
</a-form-item> </a-form-item>
<a-form-item label='Protocol'> <a-form-item label='Protocol'>
<a-select v-model="ruleModal.rule.protocol" mode="multiple" :dropdown-class-name="themeSwitcher.currentTheme"> <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-select>
</a-form-item> </a-form-item>
<a-form-item label='Attributes'> <a-form-item label='Attributes'>

View File

@@ -23,7 +23,7 @@ func (j *CheckCpuJob) Run() {
threshold, _ := j.settingService.GetTgCpu() threshold, _ := j.settingService.GetTgCpu()
// get latest status of server // 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) { if err == nil && percent[0] > float64(threshold) {
msg := j.tgbotService.I18nBot("tgbot.messages.cpuThreshold", msg := j.tgbotService.I18nBot("tgbot.messages.cpuThreshold",
"Percent=="+strconv.FormatFloat(percent[0], 'f', 2, 64), "Percent=="+strconv.FormatFloat(percent[0], 'f', 2, 64),

View File

@@ -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
}

View File

@@ -1,11 +1,16 @@
package job package job
import ( import (
"encoding/json"
"x-ui/logger" "x-ui/logger"
"x-ui/web/service" "x-ui/web/service"
"x-ui/xray"
"github.com/valyala/fasthttp"
) )
type XrayTrafficJob struct { type XrayTrafficJob struct {
settingService service.SettingService
xrayService service.XrayService xrayService service.XrayService
inboundService service.InboundService inboundService service.InboundService
outboundService service.OutboundService outboundService service.OutboundService
@@ -31,7 +36,36 @@ func (j *XrayTrafficJob) Run() {
if err != nil { if err != nil {
logger.Warning("add outbound traffic failed:", err) 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 { if needRestart0 || needRestart1 {
j.xrayService.SetToNeedRestart() 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)
}
}

View File

@@ -285,8 +285,7 @@ func (s *ServerService) GetXrayVersions() ([]string, error) {
continue continue
} }
if (major == 1 && minor == 8 && patch == 24) || if major > 25 || (major == 25 && minor > 3) || (major == 25 && minor == 3 && patch >= 3) {
(major >= 25) {
versions = append(versions, release.TagName) versions = append(versions, release.TagName)
} }
} }

View File

@@ -24,51 +24,52 @@ import (
var xrayTemplateConfig string var xrayTemplateConfig string
var defaultValueMap = map[string]string{ var defaultValueMap = map[string]string{
"xrayTemplateConfig": xrayTemplateConfig, "xrayTemplateConfig": xrayTemplateConfig,
"webListen": "", "webListen": "",
"webDomain": "", "webDomain": "",
"webPort": "2053", "webPort": "2053",
"webCertFile": "", "webCertFile": "",
"webKeyFile": "", "webKeyFile": "",
"secret": random.Seq(32), "secret": random.Seq(32),
"webBasePath": "/", "webBasePath": "/",
"sessionMaxAge": "60", "sessionMaxAge": "60",
"pageSize": "50", "pageSize": "50",
"expireDiff": "0", "expireDiff": "0",
"trafficDiff": "0", "trafficDiff": "0",
"remarkModel": "-ieo", "remarkModel": "-ieo",
"timeLocation": "Local", "timeLocation": "Local",
"tgBotEnable": "false", "tgBotEnable": "false",
"tgBotToken": "", "tgBotToken": "",
"tgBotProxy": "", "tgBotProxy": "",
"tgBotAPIServer": "", "tgBotAPIServer": "",
"tgBotChatId": "", "tgBotChatId": "",
"tgRunTime": "@daily", "tgRunTime": "@daily",
"tgBotBackup": "false", "tgBotBackup": "false",
"tgBotLoginNotify": "true", "tgBotLoginNotify": "true",
"tgCpu": "80", "tgCpu": "80",
"tgLang": "en-US", "tgLang": "en-US",
"secretEnable": "false", "secretEnable": "false",
"subEnable": "false", "subEnable": "false",
"subSyncEnable": "true", "subListen": "",
"subListen": "", "subPort": "2096",
"subPort": "2096", "subPath": "/sub/",
"subPath": "/sub/", "subDomain": "",
"subDomain": "", "subCertFile": "",
"subCertFile": "", "subKeyFile": "",
"subKeyFile": "", "subUpdates": "12",
"subUpdates": "12", "subEncrypt": "true",
"subEncrypt": "true", "subShowInfo": "true",
"subShowInfo": "true", "subURI": "",
"subURI": "", "subJsonPath": "/json/",
"subJsonPath": "/json/", "subJsonURI": "",
"subJsonURI": "", "subJsonFragment": "",
"subJsonFragment": "", "subJsonNoises": "",
"subJsonNoises": "", "subJsonMux": "",
"subJsonMux": "", "subJsonRules": "",
"subJsonRules": "", "datepicker": "gregorian",
"datepicker": "gregorian", "warp": "",
"warp": "", "externalTrafficInformEnable": "false",
"externalTrafficInformURI": "",
} }
type SettingService struct{} type SettingService struct{}
@@ -417,14 +418,6 @@ func (s *SettingService) GetSubEnable() (bool, error) {
return s.getBool("subEnable") 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) { func (s *SettingService) GetSubListen() (string, error) {
return s.getString("subListen") return s.getString("subListen")
} }
@@ -505,6 +498,22 @@ func (s *SettingService) SetWarp(data string) error {
return s.setString("warp", data) 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) { func (s *SettingService) GetIpLimitEnable() (bool, error) {
accessLogPath, err := xray.GetAccessLogPath() accessLogPath, err := xray.GetAccessLogPath()
if err != nil { if err != nil {
@@ -553,7 +562,6 @@ func (s *SettingService) GetDefaultSettings(host string) (interface{}, error) {
"defaultKey": func() (interface{}, error) { return s.GetKeyFile() }, "defaultKey": func() (interface{}, error) { return s.GetKeyFile() },
"tgBotEnable": func() (interface{}, error) { return s.GetTgbotEnabled() }, "tgBotEnable": func() (interface{}, error) { return s.GetTgbotEnabled() },
"subEnable": func() (interface{}, error) { return s.GetSubEnable() }, "subEnable": func() (interface{}, error) { return s.GetSubEnable() },
"subSyncEnable": func() (interface{}, error) { return s.GetSubSyncEnable() },
"subURI": func() (interface{}, error) { return s.GetSubURI() }, "subURI": func() (interface{}, error) { return s.GetSubURI() },
"subJsonURI": func() (interface{}, error) { return s.GetSubJsonURI() }, "subJsonURI": func() (interface{}, error) { return s.GetSubJsonURI() },
"remarkModel": func() (interface{}, error) { return s.GetRemarkModel() }, "remarkModel": func() (interface{}, error) { return s.GetRemarkModel() },

View File

@@ -307,8 +307,6 @@ func (t *Tgbot) answerCommand(message *telego.Message, chatId int64, isAdmin boo
onlyMessage = true onlyMessage = true
if isAdmin { if isAdmin {
if len(commandArgs) == 0 { if len(commandArgs) == 0 {
msg += t.I18nBot("tgbot.commands.restartUsage")
} else if strings.ToLower(commandArgs[0]) == "force" {
if t.xrayService.IsXrayRunning() { if t.xrayService.IsXrayRunning() {
err := t.xrayService.RestartXray(true) err := t.xrayService.RestartXray(true)
if err != nil { if err != nil {

View File

@@ -61,6 +61,10 @@
"secAlertPanelURI" = "Panel default URI path is insecure. Please configure a complex URI path." "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." "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." "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] [menu]
"dashboard" = "Overview" "dashboard" = "Overview"
@@ -109,9 +113,10 @@
"config" = "Config" "config" = "Config"
"backup" = "Backup & Restore" "backup" = "Backup & Restore"
"backupTitle" = "Database Backup & Restore" "backupTitle" = "Database Backup & Restore"
"backupDescription" = "It is recommended to make a backup before restoring a database."
"exportDatabase" = "Back Up" "exportDatabase" = "Back Up"
"exportDatabaseDesc" = "Click to download a .db file containing a backup of your current database to your device."
"importDatabase" = "Restore" "importDatabase" = "Restore"
"importDatabaseDesc" = "Click to select and upload a .db file from your device to restore your database from a backup."
[pages.inbounds] [pages.inbounds]
"title" = "Inbounds" "title" = "Inbounds"
@@ -190,9 +195,6 @@
[pages.client] [pages.client]
"add" = "Add Client" "add" = "Add Client"
"groupAdd" = "Add subscription user"
"isGroupEdit" = "Group editing"
"isGroupEditDesc" = "All clients with the same subscription are edited"
"edit" = "Edit Client" "edit" = "Edit Client"
"submitAdd" = "Add Client" "submitAdd" = "Add Client"
"submitEdit" = "Save Changes" "submitEdit" = "Save Changes"
@@ -290,8 +292,6 @@
"subSettings" = "Subscription" "subSettings" = "Subscription"
"subEnable" = "Enable Subscription Service" "subEnable" = "Enable Subscription Service"
"subEnableDesc" = "Enables the 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" "subListen" = "Listen IP"
"subListenDesc" = "The IP address for the subscription service. (leave blank to listen on all IPs)" "subListenDesc" = "The IP address for the subscription service. (leave blank to listen on all IPs)"
"subPort" = "Listen Port" "subPort" = "Listen Port"
@@ -312,6 +312,10 @@
"subShowInfoDesc" = "The remaining traffic and date will be displayed in the client apps." "subShowInfoDesc" = "The remaining traffic and date will be displayed in the client apps."
"subURI" = "Reverse Proxy URI" "subURI" = "Reverse Proxy URI"
"subURIDesc" = "The URI path of the subscription URL for use behind proxies." "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" "fragment" = "Fragmentation"
"fragmentDesc" = "Enable fragmentation for TLS hello packet." "fragmentDesc" = "Enable fragmentation for TLS hello packet."
"fragmentSett" = "Fragmentation Settings" "fragmentSett" = "Fragmentation Settings"
@@ -322,7 +326,15 @@
"muxSett" = "Mux Settings" "muxSett" = "Mux Settings"
"direct" = "Direct Connection" "direct" = "Direct Connection"
"directDesc" = "Directly establishes connections with domains or IP ranges of a specific country." "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] [pages.xray]
"title" = "Xray Configs" "title" = "Xray Configs"
@@ -373,10 +385,17 @@
"errorLogDesc" = "The file path for the error log. The special value 'none' disabled error logs" "errorLogDesc" = "The file path for the error log. The special value 'none' disabled error logs"
"dnsLog" = "DNS Log" "dnsLog" = "DNS Log"
"dnsLogDesc" = "Whether to enable DNS query logs" "dnsLogDesc" = "Whether to enable DNS query logs"
"outboundTraffic" = "Outbounds Traffic"
"outboundTrafficDesc" = "Whether to enable outbound traffic"
"maskAddress" = "Mask Address" "maskAddress" = "Mask Address"
"maskAddressDesc" = "IP address mask, when enabled, will automatically replace the IP address that appears in the log." "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] [pages.xray.rules]
"first" = "First" "first" = "First"
@@ -434,6 +453,14 @@
"enableDesc" = "Enable built-in DNS server" "enableDesc" = "Enable built-in DNS server"
"tag" = "DNS Inbound Tag" "tag" = "DNS Inbound Tag"
"tagDesc" = "This tag will be available as an Inbound tag in routing rules." "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" "strategy" = "Query Strategy"
"strategyDesc" = "Overall strategy to resolve domain names" "strategyDesc" = "Overall strategy to resolve domain names"
"add" = "Add Server" "add" = "Add Server"
@@ -448,7 +475,7 @@
"poolSize" = "Pool Size" "poolSize" = "Pool Size"
[pages.settings.security] [pages.settings.security]
"admin" = "Admin" "admin" = "Admin credentials"
"secret" = "Secret Token" "secret" = "Secret Token"
"loginSecurity" = "Secure Login" "loginSecurity" = "Secure Login"
"loginSecurityDesc" = "Adds an additional layer of authentication to provide more security." "loginSecurityDesc" = "Adds an additional layer of authentication to provide more security."
@@ -491,9 +518,9 @@
"status" = "✅ Bot is OK!" "status" = "✅ Bot is OK!"
"usage" = "❗ Please provide a text to search!" "usage" = "❗ Please provide a text to search!"
"getID" = "🆔 Your ID: <code>{{ .ID }}</code>" "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>" "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!" "restartSuccess" = "✅ Operation successful!"
"restartFailed" = "❗ Error in operation.\r\n\r\n<code>Error: {{ .Error }}</code>." "restartFailed" = "❗ Error in operation.\r\n\r\n<code>Error: {{ .Error }}</code>."
"xrayNotRunning" = "❗ Xray Core is not running." "xrayNotRunning" = "❗ Xray Core is not running."

View File

@@ -61,6 +61,10 @@
"secAlertPanelURI" = "La ruta URI predeterminada del panel no es segura. Por favor, configure una ruta URI compleja." "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." "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." "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] [menu]
"dashboard" = "Estado del Sistema" "dashboard" = "Estado del Sistema"
@@ -109,9 +113,10 @@
"config" = "Configuración" "config" = "Configuración"
"backup" = "Copia de Seguridad y Restauración" "backup" = "Copia de Seguridad y Restauración"
"backupTitle" = "Copia de Seguridad y Restauración de la Base de Datos" "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" = "Copia de seguridad"
"exportDatabase" = "Descargar Base de Datos" "exportDatabaseDesc" = "Haz clic para descargar un archivo .db que contiene una copia de seguridad de tu base de datos actual en tu dispositivo."
"importDatabase" = "Cargar Base de Datos" "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] [pages.inbounds]
"title" = "Entradas" "title" = "Entradas"
@@ -190,7 +195,6 @@
[pages.client] [pages.client]
"add" = "Agregar Cliente" "add" = "Agregar Cliente"
"groupAdd" = "Agregar usuario de suscripción"
"edit" = "Editar Cliente" "edit" = "Editar Cliente"
"submitAdd" = "Agregar Cliente" "submitAdd" = "Agregar Cliente"
"submitEdit" = "Guardar Cambios" "submitEdit" = "Guardar Cambios"
@@ -288,8 +292,6 @@
"subSettings" = "Suscripción" "subSettings" = "Suscripción"
"subEnable" = "Habilitar Servicio" "subEnable" = "Habilitar Servicio"
"subEnableDesc" = "Función de suscripción con configuración separada." "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" "subListen" = "Listening IP"
"subListenDesc" = "Dejar en blanco por defecto para monitorear todas las IPs." "subListenDesc" = "Dejar en blanco por defecto para monitorear todas las IPs."
"subPort" = "Puerto de Suscripción" "subPort" = "Puerto de Suscripción"
@@ -309,6 +311,10 @@
"subShowInfo" = "Mostrar información de uso" "subShowInfo" = "Mostrar información de uso"
"subShowInfoDesc" = "Mostrar tráfico restante y fecha después del nombre de configuración." "subShowInfoDesc" = "Mostrar tráfico restante y fecha después del nombre de configuración."
"subURI" = "URI de proxy inverso" "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" "subURIDesc" = "Cambiar el URI base de la URL de suscripción para usar detrás de los servidores proxy"
"fragment" = "Fragmentación" "fragment" = "Fragmentación"
"fragmentDesc" = "Habilitar la fragmentación para el paquete de saludo de TLS" "fragmentDesc" = "Habilitar la fragmentación para el paquete de saludo de TLS"
@@ -320,7 +326,15 @@
"muxSett" = "Configuración Mux" "muxSett" = "Configuración Mux"
"direct" = "Conexión Directa" "direct" = "Conexión Directa"
"directDesc" = "Establece conexiones directas con dominios o rangos de IP de un país específico." "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] [pages.xray]
"title" = "Xray Configuración" "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." "errorLogDesc" = "La ruta del archivo para el registro de errores. El valor especial 'none' desactiva los registros de errores."
"dnsLog" = "Registro DNS" "dnsLog" = "Registro DNS"
"dnsLogDesc" = "Si habilitar los registros de consulta 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" "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." "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] [pages.xray.rules]
"first" = "Primero" "first" = "Primero"
@@ -432,6 +453,14 @@
"enableDesc" = "Habilitar servidor DNS incorporado" "enableDesc" = "Habilitar servidor DNS incorporado"
"tag" = "Etiqueta de Entrada DNS" "tag" = "Etiqueta de Entrada DNS"
"tagDesc" = "Esta etiqueta estará disponible como una etiqueta de entrada en las reglas de enrutamiento." "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" "strategy" = "Estrategia de Consulta"
"strategyDesc" = "Estrategia general para resolver nombres de dominio" "strategyDesc" = "Estrategia general para resolver nombres de dominio"
"add" = "Agregar Servidor" "add" = "Agregar Servidor"
@@ -446,7 +475,7 @@
"poolSize" = "Tamaño del grupo" "poolSize" = "Tamaño del grupo"
[pages.settings.security] [pages.settings.security]
"admin" = "Administrador" "admin" = "Credenciales de administrador"
"secret" = "Token Secreto" "secret" = "Token Secreto"
"loginSecurity" = "Seguridad de Inicio de Sesión" "loginSecurity" = "Seguridad de Inicio de Sesión"
"loginSecurityDesc" = "Habilitar un paso adicional de seguridad para el inicio de sesión de usuarios." "loginSecurityDesc" = "Habilitar un paso adicional de seguridad para el inicio de sesión de usuarios."
@@ -489,9 +518,9 @@
"status" = "✅ ¡El bot está bien!" "status" = "✅ ¡El bot está bien!"
"usage" = "❗ ¡Por favor proporciona un texto para buscar!" "usage" = "❗ ¡Por favor proporciona un texto para buscar!"
"getID" = "🆔 Tu ID: <code>{{ .ID }}</code>" "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>" "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!" "restartSuccess" = "✅ ¡Operación exitosa!"
"restartFailed" = "❗ Error en la operación.\r\n\r\n<code>Error: {{ .Error }}</code>." "restartFailed" = "❗ Error en la operación.\r\n\r\n<code>Error: {{ .Error }}</code>."
"xrayNotRunning" = "❗ Xray Core no está en ejecución." "xrayNotRunning" = "❗ Xray Core no está en ejecución."

View File

@@ -61,6 +61,10 @@
"secAlertPanelURI" = "مسیر پیش‌فرض لینک پنل ناامن است. لطفاً یک مسیر پیچیده تنظیم کنید" "secAlertPanelURI" = "مسیر پیش‌فرض لینک پنل ناامن است. لطفاً یک مسیر پیچیده تنظیم کنید"
"secAlertSubURI" = "مسیر پیش‌فرض لینک سابسکریپشن ناامن است. لطفاً یک مسیر پیچیده تنظیم کنید" "secAlertSubURI" = "مسیر پیش‌فرض لینک سابسکریپشن ناامن است. لطفاً یک مسیر پیچیده تنظیم کنید"
"secAlertSubJsonURI" = "مسیر پیش‌فرض لینک سابسکریپشن جیسون ناامن است. لطفاً یک مسیر پیچیده تنظیم کنید" "secAlertSubJsonURI" = "مسیر پیش‌فرض لینک سابسکریپشن جیسون ناامن است. لطفاً یک مسیر پیچیده تنظیم کنید"
"emptyDnsDesc" = "هیچ سرور DNS اضافه نشده است."
"emptyFakeDnsDesc" = "هیچ سرور Fake DNS اضافه نشده است."
"emptyBalancersDesc" = "هیچ بالانسر اضافه نشده است."
"emptyReverseDesc" = "هیچ پروکسی معکوس اضافه نشده است."
[menu] [menu]
"dashboard" = "نمای کلی" "dashboard" = "نمای کلی"
@@ -109,9 +113,10 @@
"config" = "پیکربندی" "config" = "پیکربندی"
"backup" = "پشتیبان‌گیری" "backup" = "پشتیبان‌گیری"
"backupTitle" = "پشتیبان‌گیری دیتابیس" "backupTitle" = "پشتیبان‌گیری دیتابیس"
"backupDescription" = "توصیه‌می‌شود قبل‌از واردکردن یک دیتابیس جدید، نسخه پشتیبان تهیه ‌کنید"
"exportDatabase" = "پشتیبان‌گیری" "exportDatabase" = "پشتیبان‌گیری"
"importDatabase" = ازگرداندن" "exportDatabaseDesc" = رای دانلود یک فایل .db حاوی پشتیبان از پایگاه داده فعلی خود به دستگاهتان کلیک کنید."
"importDatabase" = "بازیابی"
"importDatabaseDesc" = "برای انتخاب و آپلود یک فایل .db از دستگاهتان و بازیابی پایگاه داده از یک پشتیبان کلیک کنید."
[pages.inbounds] [pages.inbounds]
"title" = "کاربران" "title" = "کاربران"
@@ -190,9 +195,6 @@
[pages.client] [pages.client]
"add" = "کاربر جدید" "add" = "کاربر جدید"
"groupAdd" = "افزودن کاربر سابسکریپشن"
"isGroupEdit" = "ویرایش گروهی"
"isGroupEditDesc" = "همه کاربران با سابسکریپشن یکسان ویرایش می شوند"
"edit" = "ویرایش کاربر" "edit" = "ویرایش کاربر"
"submitAdd" = "اضافه کردن" "submitAdd" = "اضافه کردن"
"submitEdit" = "ذخیره تغییرات" "submitEdit" = "ذخیره تغییرات"
@@ -290,8 +292,6 @@
"subSettings" = "سابسکریپشن" "subSettings" = "سابسکریپشن"
"subEnable" = "فعال‌سازی سرویس سابسکریپشن" "subEnable" = "فعال‌سازی سرویس سابسکریپشن"
"subEnableDesc" = "سرویس سابسکریپشن‌ را فعال‌می‌کند" "subEnableDesc" = "سرویس سابسکریپشن‌ را فعال‌می‌کند"
"subSyncEnable" = "فعال‌سازی همگام سازی سابسکریپشن"
"subSyncEnableDesc" = "ترافیک کلاینت هایی که سابسکریپشن یکسان دارند هر ۱۰ ثانیه همگام می‌شوند."
"subListen" = "آدرس آی‌پی" "subListen" = "آدرس آی‌پی"
"subListenDesc" = "آدرس آی‌پی برای سرویس سابسکریپشن. برای گوش دادن به‌تمام آی‌پی‌ها خالی‌بگذارید" "subListenDesc" = "آدرس آی‌پی برای سرویس سابسکریپشن. برای گوش دادن به‌تمام آی‌پی‌ها خالی‌بگذارید"
"subPort" = "پورت" "subPort" = "پورت"
@@ -306,6 +306,10 @@
"subDomainDesc" = "آدرس دامنه برای سرویس سابسکریپشن. برای گوش دادن به تمام دامنه‌ها و آی‌پی‌ها خالی‌بگذارید‌" "subDomainDesc" = "آدرس دامنه برای سرویس سابسکریپشن. برای گوش دادن به تمام دامنه‌ها و آی‌پی‌ها خالی‌بگذارید‌"
"subUpdates" = "فاصله بروزرسانی‌ سابسکریپشن" "subUpdates" = "فاصله بروزرسانی‌ سابسکریپشن"
"subUpdatesDesc" = "(فاصله مابین بروزرسانی در برنامه‌های کاربری. (واحد: ساعت" "subUpdatesDesc" = "(فاصله مابین بروزرسانی در برنامه‌های کاربری. (واحد: ساعت"
"externalTrafficInformEnable" = "اطلاع رسانی خارجی مصرف ترافیک"
"externalTrafficInformEnableDesc" = "مصرف ترافیک به سرویس خارجی ارسال می شود"
"externalTrafficInformURI" = "لینک اطلاع رسانی خارجی مصرف ترافیک"
"externalTrafficInformURIDesc" = "ترافیک های مصرفی به این لینک هم ارسال می شود"
"subEncrypt" = "کدگذاری" "subEncrypt" = "کدگذاری"
"subEncryptDesc" = "کدگذاری خواهدشد Base64 محتوای برگشتی سرویس سابسکریپشن برپایه" "subEncryptDesc" = "کدگذاری خواهدشد Base64 محتوای برگشتی سرویس سابسکریپشن برپایه"
"subShowInfo" = "نمایش اطلاعات مصرف" "subShowInfo" = "نمایش اطلاعات مصرف"
@@ -322,7 +326,15 @@
"muxSett" = "تنظیمات ماکس" "muxSett" = "تنظیمات ماکس"
"direct" = "اتصال مستقیم" "direct" = "اتصال مستقیم"
"directDesc" = "به طور مستقیم با دامنه ها یا محدوده آی‌پی یک کشور خاص ارتباط برقرار می کند" "directDesc" = "به طور مستقیم با دامنه ها یا محدوده آی‌پی یک کشور خاص ارتباط برقرار می کند"
"notifications" = "اعلان‌ها"
"certs" = "گواهی‌ها"
"externalTraffic" = "ترافیک خارجی"
"dateAndTime" = "تاریخ و زمان"
"proxyAndServer" = "پراکسی و سرور"
"intervals" = "فواصل"
"information" = "اطلاعات"
"language" = "زبان"
"telegramBotLanguage" = "زبان ربات تلگرام"
[pages.xray] [pages.xray]
"title" = "پیکربندی ایکس‌ری" "title" = "پیکربندی ایکس‌ری"
@@ -373,10 +385,17 @@
"errorLogDesc" = "مسیر فایل برای ورود به سیستم خطا. مقدار ویژه «هیچ» گزارش های خطا را غیرفعال میکند" "errorLogDesc" = "مسیر فایل برای ورود به سیستم خطا. مقدار ویژه «هیچ» گزارش های خطا را غیرفعال میکند"
"dnsLog" = "گزارش DNS" "dnsLog" = "گزارش DNS"
"dnsLogDesc" = "آیا ثبت‌های درخواست DNS را فعال کنید" "dnsLogDesc" = "آیا ثبت‌های درخواست DNS را فعال کنید"
"outboundTraffic" = "ترافیک خروجی"
"outboundTrafficDesc" = "فعال کردن ترافیک خروجی"
"maskAddress" = "پنهان کردن آدرس" "maskAddress" = "پنهان کردن آدرس"
"maskAddressDesc" = "پوشش آدرس IP، هنگامی که فعال می‌شود، به طور خودکار آدرس IP که در لاگ ظاهر می‌شود را جایگزین می‌کند." "maskAddressDesc" = "پوشش آدرس IP، هنگامی که فعال می‌شود، به طور خودکار آدرس IP که در لاگ ظاهر می‌شود را جایگزین می‌کند."
"statistics" = "آمار"
"statsInboundUplink" = "آمار آپلود ورودی"
"statsInboundUplinkDesc" = "جمع‌آوری آمار برای ترافیک بالارو (آپلود) تمام پروکسی‌های ورودی را فعال می‌کند."
"statsInboundDownlink" = "آمار دانلود ورودی"
"statsInboundDownlinkDesc" = "جمع‌آوری آمار برای ترافیک پایین‌رو (دانلود) تمام پروکسی‌های ورودی را فعال می‌کند."
"statsOutboundUplink" = "آمار آپلود خروجی"
"statsOutboundUplinkDesc" = "جمع‌آوری آمار برای ترافیک بالارو (آپلود) تمام پروکسی‌های خروجی را فعال می‌کند."
"statsOutboundDownlink" = "آمار دانلود خروجی"
"statsOutboundDownlinkDesc" = "جمع‌آوری آمار برای ترافیک پایین‌رو (دانلود) تمام پروکسی‌های خروجی را فعال می‌کند."
[pages.xray.rules] [pages.xray.rules]
"first" = "اولین" "first" = "اولین"
@@ -434,6 +453,14 @@
"enableDesc" = "سرور حل دامنه داخلی را فعال کنید" "enableDesc" = "سرور حل دامنه داخلی را فعال کنید"
"tag" = "برچسب" "tag" = "برچسب"
"tagDesc" = "این برچسب در قوانین مسیریابی به عنوان یک برچسب ورودی قابل استفاده خواهد بود" "tagDesc" = "این برچسب در قوانین مسیریابی به عنوان یک برچسب ورودی قابل استفاده خواهد بود"
"clientIp" = "آی‌پی کلاینت"
"clientIpDesc" = "برای اطلاع‌رسانی به سرور درباره مکان IP مشخص‌شده در طول درخواست‌های DNS استفاده می‌شود"
"disableCache" = "غیرفعال‌سازی کش"
"disableCacheDesc" = "کش DNS را غیرفعال می‌کند"
"disableFallback" = "غیرفعال‌سازی Fallback"
"disableFallbackDesc" = "درخواست‌های DNS Fallback را غیرفعال می‌کند"
"disableFallbackIfMatch" = "غیرفعال‌سازی Fallback در صورت تطابق"
"disableFallbackIfMatchDesc" = "درخواست‌های DNS Fallback را زمانی که لیست دامنه‌های مطابقت‌یافته سرور DNS فعال است، غیرفعال می‌کند"
"strategy" = "استراتژی پرس‌وجو" "strategy" = "استراتژی پرس‌وجو"
"strategyDesc" = "استراتژی کلی برای حل نام دامنه" "strategyDesc" = "استراتژی کلی برای حل نام دامنه"
"add" = "افزودن سرور" "add" = "افزودن سرور"
@@ -448,7 +475,7 @@
"poolSize" = "اندازه استخر" "poolSize" = "اندازه استخر"
[pages.settings.security] [pages.settings.security]
"admin" = "مدیر" "admin" = "اعتبارنامه‌های ادمین"
"secret" = "توکن مخفی" "secret" = "توکن مخفی"
"loginSecurity" = "ورود ایمن" "loginSecurity" = "ورود ایمن"
"loginSecurityDesc" = "یک لایه اضافی از احراز هویت برای ایجاد امنیت بیشتر اضافه می کند" "loginSecurityDesc" = "یک لایه اضافی از احراز هویت برای ایجاد امنیت بیشتر اضافه می کند"
@@ -491,9 +518,9 @@
"status" = "✅ ربات در حالت عادی است!" "status" = "✅ ربات در حالت عادی است!"
"usage" = "❗ لطفاً یک متن برای جستجو وارد کنید!" "usage" = "❗ لطفاً یک متن برای جستجو وارد کنید!"
"getID" = "🆔 شناسه شما: <code>{{ .ID }}</code>" "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>" "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" = "✅ عملیات با موفقیت انجام شد!" "restartSuccess" = "✅ عملیات با موفقیت انجام شد!"
"restartFailed" = "❗ خطا در عملیات.\r\n\r\n<code>خطا: {{ .Error }}</code>." "restartFailed" = "❗ خطا در عملیات.\r\n\r\n<code>خطا: {{ .Error }}</code>."
"xrayNotRunning" = "❗ Xray Core در حال اجرا نیست." "xrayNotRunning" = "❗ Xray Core در حال اجرا نیست."

View File

@@ -61,6 +61,10 @@
"secAlertPanelURI" = "Jalur URI default panel tidak aman. Harap konfigurasi jalur URI kompleks." "secAlertPanelURI" = "Jalur URI default panel tidak aman. Harap konfigurasi jalur URI kompleks."
"secAlertSubURI" = "Jalur URI default langganan 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." "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] [menu]
"dashboard" = "Ikhtisar" "dashboard" = "Ikhtisar"
@@ -109,9 +113,10 @@
"config" = "Konfigurasi" "config" = "Konfigurasi"
"backup" = "Cadangan & Pulihkan" "backup" = "Cadangan & Pulihkan"
"backupTitle" = "Cadangan & Pulihkan Database" "backupTitle" = "Cadangan & Pulihkan Database"
"backupDescription" = "Disarankan untuk membuat cadangan sebelum memulihkan database."
"exportDatabase" = "Cadangkan" "exportDatabase" = "Cadangkan"
"exportDatabaseDesc" = "Klik untuk mengunduh file .db yang berisi cadangan dari database Anda saat ini ke perangkat Anda."
"importDatabase" = "Pulihkan" "importDatabase" = "Pulihkan"
"importDatabaseDesc" = "Klik untuk memilih dan mengunggah file .db dari perangkat Anda untuk memulihkan database dari cadangan."
[pages.inbounds] [pages.inbounds]
"title" = "Masuk" "title" = "Masuk"
@@ -190,9 +195,6 @@
[pages.client] [pages.client]
"add" = "Tambah Klien" "add" = "Tambah Klien"
"groupAdd" = "Tambahkan pengguna langganan"
"isGroupEdit" = "Pengeditan grup"
"isGroupEditDesc" = "Semua klien dengan langganan yang sama akan diedit"
"edit" = "Edit Klien" "edit" = "Edit Klien"
"submitAdd" = "Tambah Klien" "submitAdd" = "Tambah Klien"
"submitEdit" = "Simpan Perubahan" "submitEdit" = "Simpan Perubahan"
@@ -290,8 +292,6 @@
"subSettings" = "Langganan" "subSettings" = "Langganan"
"subEnable" = "Aktifkan Layanan Langganan" "subEnable" = "Aktifkan Layanan Langganan"
"subEnableDesc" = "Mengaktifkan 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" "subListen" = "IP Pendengar"
"subListenDesc" = "Alamat IP untuk layanan langganan. (biarkan kosong untuk mendengarkan semua IP)" "subListenDesc" = "Alamat IP untuk layanan langganan. (biarkan kosong untuk mendengarkan semua IP)"
"subPort" = "Port Pendengar" "subPort" = "Port Pendengar"
@@ -311,7 +311,10 @@
"subShowInfo" = "Tampilkan Info Penggunaan" "subShowInfo" = "Tampilkan Info Penggunaan"
"subShowInfoDesc" = "Sisa traffic dan tanggal akan ditampilkan di aplikasi klien." "subShowInfoDesc" = "Sisa traffic dan tanggal akan ditampilkan di aplikasi klien."
"subURI" = "URI Proxy Terbalik" "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" "fragment" = "Fragmentasi"
"fragmentDesc" = "Aktifkan fragmentasi untuk paket hello TLS" "fragmentDesc" = "Aktifkan fragmentasi untuk paket hello TLS"
"fragmentSett" = "Pengaturan Fragmentasi" "fragmentSett" = "Pengaturan Fragmentasi"
@@ -322,7 +325,15 @@
"muxSett" = "Pengaturan Mux" "muxSett" = "Pengaturan Mux"
"direct" = "Koneksi langsung" "direct" = "Koneksi langsung"
"directDesc" = "Secara langsung membuat koneksi dengan domain atau rentang IP negara tertentu." "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] [pages.xray]
"title" = "Konfigurasi Xray" "title" = "Konfigurasi Xray"
@@ -373,10 +384,17 @@
"errorLogDesc" = "Jalur file untuk log kesalahan. Nilai khusus 'tidak ada' menonaktifkan log kesalahan" "errorLogDesc" = "Jalur file untuk log kesalahan. Nilai khusus 'tidak ada' menonaktifkan log kesalahan"
"dnsLog" = "Log DNS" "dnsLog" = "Log DNS"
"dnsLogDesc" = "Apakah akan mengaktifkan log kueri DNS" "dnsLogDesc" = "Apakah akan mengaktifkan log kueri DNS"
"outboundTraffic" = "Lalu Lintas Keluar"
"outboundTrafficDesc" = "Apakah mengaktifkan lalu lintas keluar"
"maskAddress" = "Alamat Masker" "maskAddress" = "Alamat Masker"
"maskAddressDesc" = "Masker alamat IP, ketika diaktifkan, akan secara otomatis mengganti alamat IP yang muncul di log." "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] [pages.xray.rules]
"first" = "Pertama" "first" = "Pertama"
@@ -434,6 +452,14 @@
"enableDesc" = "Aktifkan server DNS bawaan" "enableDesc" = "Aktifkan server DNS bawaan"
"tag" = "Tanda DNS Masuk" "tag" = "Tanda DNS Masuk"
"tagDesc" = "Tanda ini akan tersedia sebagai tanda masuk dalam aturan penataan." "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" "strategy" = "Strategi Kueri"
"strategyDesc" = "Strategi keseluruhan untuk menyelesaikan nama domain" "strategyDesc" = "Strategi keseluruhan untuk menyelesaikan nama domain"
"add" = "Tambahkan Server" "add" = "Tambahkan Server"
@@ -448,7 +474,7 @@
"poolSize" = "Ukuran Kolam" "poolSize" = "Ukuran Kolam"
[pages.settings.security] [pages.settings.security]
"admin" = "Admin" "admin" = "Kredensial admin"
"secret" = "Token Rahasia" "secret" = "Token Rahasia"
"loginSecurity" = "Login Aman" "loginSecurity" = "Login Aman"
"loginSecurityDesc" = "Menambahkan lapisan otentikasi tambahan untuk memberikan keamanan lebih." "loginSecurityDesc" = "Menambahkan lapisan otentikasi tambahan untuk memberikan keamanan lebih."
@@ -491,9 +517,9 @@
"status" = "✅ Bot dalam keadaan baik!" "status" = "✅ Bot dalam keadaan baik!"
"usage" = "❗ Harap berikan teks untuk mencari!" "usage" = "❗ Harap berikan teks untuk mencari!"
"getID" = "🆔 ID Anda: <code>{{ .ID }}</code>" "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>" "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!" "restartSuccess" = "✅ Operasi berhasil!"
"restartFailed" = "❗ Kesalahan dalam operasi.\r\n\r\n<code>Error: {{ .Error }}</code>." "restartFailed" = "❗ Kesalahan dalam operasi.\r\n\r\n<code>Error: {{ .Error }}</code>."
"xrayNotRunning" = "❗ Xray Core tidak berjalan." "xrayNotRunning" = "❗ Xray Core tidak berjalan."

View File

@@ -61,6 +61,10 @@
"secAlertPanelURI" = "デフォルトのURIパスは安全ではありません。複雑なURIパスを設定してください。" "secAlertPanelURI" = "デフォルトのURIパスは安全ではありません。複雑なURIパスを設定してください。"
"secAlertSubURI" = "サブスクリプションのデフォルトURIパスは安全ではありません。複雑なURIパスを設定してください。" "secAlertSubURI" = "サブスクリプションのデフォルトURIパスは安全ではありません。複雑なURIパスを設定してください。"
"secAlertSubJsonURI" = "JSONサブスクリプションのデフォルトURIパスは安全ではありません。複雑なURIパスを設定してください。" "secAlertSubJsonURI" = "JSONサブスクリプションのデフォルトURIパスは安全ではありません。複雑なURIパスを設定してください。"
"emptyDnsDesc" = "追加されたDNSサーバーはありません。"
"emptyFakeDnsDesc" = "追加されたFake DNSサーバーはありません。"
"emptyBalancersDesc" = "追加されたバランサーはありません。"
"emptyReverseDesc" = "追加されたリバースプロキシはありません。"
[menu] [menu]
"dashboard" = "ダッシュボード" "dashboard" = "ダッシュボード"
@@ -109,9 +113,10 @@
"config" = "設定" "config" = "設定"
"backup" = "バックアップと復元" "backup" = "バックアップと復元"
"backupTitle" = "データベースのバックアップと復元" "backupTitle" = "データベースのバックアップと復元"
"backupDescription" = "データベースを復元する前にバックアップすることをお勧めします"
"exportDatabase" = "バックアップ" "exportDatabase" = "バックアップ"
"exportDatabaseDesc" = "クリックして、現在のデータベースのバックアップを含む .db ファイルをデバイスにダウンロードします。"
"importDatabase" = "復元" "importDatabase" = "復元"
"importDatabaseDesc" = "クリックして、デバイスから .db ファイルを選択し、アップロードしてバックアップからデータベースを復元します。"
[pages.inbounds] [pages.inbounds]
"title" = "インバウンド一覧" "title" = "インバウンド一覧"
@@ -190,9 +195,6 @@
[pages.client] [pages.client]
"add" = "クライアント追加" "add" = "クライアント追加"
"groupAdd" = "サブスクリプション ユーザーの追加"
"isGroupEdit" = "グループの編集"
"isGroupEditDesc" = "同じサブスクリプションを持つすべてのクライアントが編集されます"
"edit" = "クライアント編集" "edit" = "クライアント編集"
"submitAdd" = "クライアント追加" "submitAdd" = "クライアント追加"
"submitEdit" = "変更を保存" "submitEdit" = "変更を保存"
@@ -290,8 +292,6 @@
"subSettings" = "サブスクリプション設定" "subSettings" = "サブスクリプション設定"
"subEnable" = "サブスクリプションサービスを有効にする" "subEnable" = "サブスクリプションサービスを有効にする"
"subEnableDesc" = "サブスクリプションサービス機能を有効にする" "subEnableDesc" = "サブスクリプションサービス機能を有効にする"
"subSyncEnable" = "サブスクリプション同期を有効にする"
"subSyncEnableDesc" = "同じサブスクリプションを持つクライアントからのトラフィックは 10 秒ごとに同期されます。"
"subListen" = "監視IP" "subListen" = "監視IP"
"subListenDesc" = "サブスクリプションサービスが監視するIPアドレス空白にするとすべてのIPを監視" "subListenDesc" = "サブスクリプションサービスが監視するIPアドレス空白にするとすべてのIPを監視"
"subPort" = "監視ポート" "subPort" = "監視ポート"
@@ -312,6 +312,10 @@
"subShowInfoDesc" = "クライアントアプリで残りのトラフィックと日付情報を表示する" "subShowInfoDesc" = "クライアントアプリで残りのトラフィックと日付情報を表示する"
"subURI" = "リバースプロキシURI" "subURI" = "リバースプロキシURI"
"subURIDesc" = "プロキシ後ろのサブスクリプションURLのURIパスに使用する" "subURIDesc" = "プロキシ後ろのサブスクリプションURLのURIパスに使用する"
"externalTrafficInformEnable" = "外部トラフィック情報"
"externalTrafficInformEnableDesc" = "トラフィックの更新ごとに外部 API に通知します。"
"externalTrafficInformURI" = "外部トラフィック通知 URI"
"externalTrafficInformURIDesc" = "トラフィックの更新ごとに外部 API に通知します。"
"fragment" = "フラグメント" "fragment" = "フラグメント"
"fragmentDesc" = "TLS helloパケットのフラグメントを有効にする" "fragmentDesc" = "TLS helloパケットのフラグメントを有効にする"
"fragmentSett" = "設定" "fragmentSett" = "設定"
@@ -322,7 +326,15 @@
"muxSett" = "マルチプレクサ設定" "muxSett" = "マルチプレクサ設定"
"direct" = "直接接続" "direct" = "直接接続"
"directDesc" = "特定の国のドメインまたはIP範囲に直接接続する" "directDesc" = "特定の国のドメインまたはIP範囲に直接接続する"
"notifications" = "通知"
"certs" = "証明書"
"externalTraffic" = "外部トラフィック"
"dateAndTime" = "日付と時刻"
"proxyAndServer" = "プロキシとサーバー"
"intervals" = "間隔"
"information" = "情報"
"language" = "言語"
"telegramBotLanguage" = "Telegram Botの言語"
[pages.xray] [pages.xray]
"title" = "Xray 設定" "title" = "Xray 設定"
@@ -373,10 +385,17 @@
"errorLogDesc" = "エラーログのファイルパス。特殊値 'none' はエラーログを無効にします" "errorLogDesc" = "エラーログのファイルパス。特殊値 'none' はエラーログを無効にします"
"dnsLog" = "DNS ログ" "dnsLog" = "DNS ログ"
"dnsLogDesc" = "DNSクエリのログを有効にするかどうか" "dnsLogDesc" = "DNSクエリのログを有効にするかどうか"
"outboundTraffic" = "アウトバウンドトラフィック"
"outboundTrafficDesc" = "アウトバウンドトラフィックを有効にするかどうか"
"maskAddress" = "アドレスをマスク" "maskAddress" = "アドレスをマスク"
"maskAddressDesc" = "IPアドレスをマスクし、有効にするとログに表示されるIPアドレスを自動的に置き換えます" "maskAddressDesc" = "IPアドレスをマスクし、有効にするとログに表示されるIPアドレスを自動的に置き換えます"
"statistics" = "統計"
"statsInboundUplink" = "インバウンドアップロード統計"
"statsInboundUplinkDesc" = "すべてのインバウンドプロキシのアップストリームトラフィックの統計収集を有効にします。"
"statsInboundDownlink" = "インバウンドダウンロード統計"
"statsInboundDownlinkDesc" = "すべてのインバウンドプロキシのダウンストリームトラフィックの統計収集を有効にします。"
"statsOutboundUplink" = "アウトバウンドアップロード統計"
"statsOutboundUplinkDesc" = "すべてのアウトバウンドプロキシのアップストリームトラフィックの統計収集を有効にします。"
"statsOutboundDownlink" = "アウトバウンドダウンロード統計"
"statsOutboundDownlinkDesc" = "すべてのアウトバウンドプロキシのダウンストリームトラフィックの統計収集を有効にします。"
[pages.xray.rules] [pages.xray.rules]
"first" = "最初" "first" = "最初"
@@ -434,6 +453,14 @@
"enableDesc" = "組み込みDNSサーバーを有効にする" "enableDesc" = "組み込みDNSサーバーを有効にする"
"tag" = "DNSインバウンドタグ" "tag" = "DNSインバウンドタグ"
"tagDesc" = "このタグはルーティングルールでインバウンドタグとして使用できます" "tagDesc" = "このタグはルーティングルールでインバウンドタグとして使用できます"
"clientIp" = "クライアントIP"
"clientIpDesc" = "DNSクエリ中に指定されたIPの位置をサーバーに通知するために使用されます"
"disableCache" = "キャッシュを無効にする"
"disableCacheDesc" = "DNSキャッシュを無効にします"
"disableFallback" = "フォールバックを無効にする"
"disableFallbackDesc" = "フォールバックDNSクエリを無効にします"
"disableFallbackIfMatch" = "一致した場合にフォールバックを無効にする"
"disableFallbackIfMatchDesc" = "DNSサーバーの一致するドメインリストにヒットした場合、フォールバックDNSクエリを無効にします"
"strategy" = "クエリ戦略" "strategy" = "クエリ戦略"
"strategyDesc" = "ドメイン名解決の全体的な戦略" "strategyDesc" = "ドメイン名解決の全体的な戦略"
"add" = "サーバー追加" "add" = "サーバー追加"
@@ -448,7 +475,7 @@
"poolSize" = "プールサイズ" "poolSize" = "プールサイズ"
[pages.settings.security] [pages.settings.security]
"admin" = "管理者" "admin" = "管理者の資格情報"
"secret" = "セキュリティトークン" "secret" = "セキュリティトークン"
"loginSecurity" = "ログインセキュリティ" "loginSecurity" = "ログインセキュリティ"
"loginSecurityDesc" = "追加の認証を追加してセキュリティを向上させる" "loginSecurityDesc" = "追加の認証を追加してセキュリティを向上させる"
@@ -491,9 +518,9 @@
"status" = "✅ ボットは正常に動作しています!" "status" = "✅ ボットは正常に動作しています!"
"usage" = "❗ 検索するテキストを入力してください!" "usage" = "❗ 検索するテキストを入力してください!"
"getID" = "🆔 あなたのIDは<code>{{ .ID }}</code>" "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>" "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" = "✅ 操作成功!" "restartSuccess" = "✅ 操作成功!"
"restartFailed" = "❗ 操作エラー。\r\n\r\n<code>エラー: {{ .Error }}</code>" "restartFailed" = "❗ 操作エラー。\r\n\r\n<code>エラー: {{ .Error }}</code>"
"xrayNotRunning" = "❗ Xray Core は動作していません。" "xrayNotRunning" = "❗ Xray Core は動作していません。"

View File

@@ -61,6 +61,10 @@
"secAlertPanelURI" = "O caminho URI padrão do painel não é seguro. Configure um caminho URI complexo." "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." "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." "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] [menu]
"dashboard" = "Visão Geral" "dashboard" = "Visão Geral"
@@ -109,9 +113,10 @@
"config" = "Configuração" "config" = "Configuração"
"backup" = "Backup e Restauração" "backup" = "Backup e Restauração"
"backupTitle" = "Backup e Restauração do Banco de Dados" "backupTitle" = "Backup e Restauração do Banco de Dados"
"backupDescription" = "É recomendado fazer um backup antes de restaurar o banco de dados." "exportDatabase" = "Backup"
"exportDatabase" = "Fazer Backup" "exportDatabaseDesc" = "Clique para baixar um arquivo .db contendo um backup do seu banco de dados atual para o seu dispositivo."
"importDatabase" = "Restaurar" "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] [pages.inbounds]
"title" = "Inbounds" "title" = "Inbounds"
@@ -190,9 +195,6 @@
[pages.client] [pages.client]
"add" = "Adicionar Cliente" "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" "edit" = "Editar Cliente"
"submitAdd" = "Adicionar Cliente" "submitAdd" = "Adicionar Cliente"
"submitEdit" = "Salvar Alterações" "submitEdit" = "Salvar Alterações"
@@ -290,8 +292,6 @@
"subSettings" = "Assinatura" "subSettings" = "Assinatura"
"subEnable" = "Ativar Serviço de Assinatura" "subEnable" = "Ativar Serviço de Assinatura"
"subEnableDesc" = "Ativa o 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" "subListen" = "IP de Escuta"
"subListenDesc" = "O endereço IP para o serviço de assinatura. (deixe em branco para escutar em todos os IPs)" "subListenDesc" = "O endereço IP para o serviço de assinatura. (deixe em branco para escutar em todos os IPs)"
"subPort" = "Porta de Escuta" "subPort" = "Porta de Escuta"
@@ -312,6 +312,10 @@
"subShowInfoDesc" = "O tráfego restante e a data serão exibidos nos aplicativos de cliente." "subShowInfoDesc" = "O tráfego restante e a data serão exibidos nos aplicativos de cliente."
"subURI" = "URI de Proxy Reverso" "subURI" = "URI de Proxy Reverso"
"subURIDesc" = "O caminho URI da URL de assinatura para uso por trás de proxies." "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" "fragment" = "Fragmentação"
"fragmentDesc" = "Ativa a fragmentação para o pacote TLS hello." "fragmentDesc" = "Ativa a fragmentação para o pacote TLS hello."
"fragmentSett" = "Configurações de Fragmentação" "fragmentSett" = "Configurações de Fragmentação"
@@ -322,7 +326,15 @@
"muxSett" = "Configurações de Mux" "muxSett" = "Configurações de Mux"
"direct" = "Conexão Direta" "direct" = "Conexão Direta"
"directDesc" = "Estabelece conexões diretamente com domínios ou intervalos de IP de um país específico." "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] [pages.xray]
"title" = "Configurações 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." "errorLogDesc" = "O caminho do arquivo para o log de erros. O valor especial 'none' desativa os logs de erro."
"dnsLog" = "Log DNS" "dnsLog" = "Log DNS"
"dnsLogDesc" = "Se ativar logs de consulta 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" "maskAddress" = "Mascarar Endereço"
"maskAddressDesc" = "Máscara de endereço IP, quando ativado, substitui automaticamente o endereço IP que aparece no log." "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] [pages.xray.rules]
"first" = "Primeiro" "first" = "Primeiro"
@@ -434,6 +453,14 @@
"enableDesc" = "Ativar o servidor DNS integrado" "enableDesc" = "Ativar o servidor DNS integrado"
"tag" = "Tag de Entrada DNS" "tag" = "Tag de Entrada DNS"
"tagDesc" = "Esta tag estará disponível como uma tag de Entrada nas regras de roteamento." "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" "strategy" = "Estratégia de Consulta"
"strategyDesc" = "Estratégia geral para resolver nomes de domínio" "strategyDesc" = "Estratégia geral para resolver nomes de domínio"
"add" = "Adicionar Servidor" "add" = "Adicionar Servidor"
@@ -448,7 +475,7 @@
"poolSize" = "Tamanho do Pool" "poolSize" = "Tamanho do Pool"
[pages.settings.security] [pages.settings.security]
"admin" = "Admin" "admin" = "Credenciais de administrador"
"secret" = "Token Secreto" "secret" = "Token Secreto"
"loginSecurity" = "Login Seguro" "loginSecurity" = "Login Seguro"
"loginSecurityDesc" = "Adiciona uma camada extra de autenticação para fornecer mais segurança." "loginSecurityDesc" = "Adiciona uma camada extra de autenticação para fornecer mais segurança."
@@ -491,9 +518,9 @@
"status" = "✅ Bot está OK!" "status" = "✅ Bot está OK!"
"usage" = "❗ Por favor, forneça um texto para pesquisar!" "usage" = "❗ Por favor, forneça um texto para pesquisar!"
"getID" = "🆔 Seu ID: <code>{{ .ID }}</code>" "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>" "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!" "restartSuccess" = "✅ Operação bem-sucedida!"
"restartFailed" = "❗ Erro na operação.\r\n\r\n<code>Erro: {{ .Error }}</code>." "restartFailed" = "❗ Erro na operação.\r\n\r\n<code>Erro: {{ .Error }}</code>."
"xrayNotRunning" = "❗ Xray Core não está em execução." "xrayNotRunning" = "❗ Xray Core não está em execução."

View File

@@ -43,7 +43,7 @@
"domainName" = "Домен" "domainName" = "Домен"
"monitor" = "Слушать IP" "monitor" = "Слушать IP"
"certificate" = "Цифровой сертификат" "certificate" = "Цифровой сертификат"
"fail" = "Неудачно" "fail" = "Ошибка"
"comment" = "Комментарий" "comment" = "Комментарий"
"success" = "Успешно" "success" = "Успешно"
"getVersion" = "Узнать версию" "getVersion" = "Узнать версию"
@@ -61,6 +61,10 @@
"secAlertPanelURI" = "URI-путь по умолчанию панели небезопасен. Пожалуйста, настройте сложный URI-путь." "secAlertPanelURI" = "URI-путь по умолчанию панели небезопасен. Пожалуйста, настройте сложный URI-путь."
"secAlertSubURI" = "URI-путь по умолчанию подписки небезопасен. Пожалуйста, настройте сложный URI-путь." "secAlertSubURI" = "URI-путь по умолчанию подписки небезопасен. Пожалуйста, настройте сложный URI-путь."
"secAlertSubJsonURI" = "URI-путь по умолчанию для JSON подписки небезопасен. Пожалуйста, настройте сложный URI-путь." "secAlertSubJsonURI" = "URI-путь по умолчанию для JSON подписки небезопасен. Пожалуйста, настройте сложный URI-путь."
"emptyDnsDesc" = "Нет добавленных DNS-серверов."
"emptyFakeDnsDesc" = "Нет добавленных Fake DNS-серверов."
"emptyBalancersDesc" = "Нет добавленных балансировщиков."
"emptyReverseDesc" = "Нет добавленных обратных прокси."
[menu] [menu]
"dashboard" = "Статус системы" "dashboard" = "Статус системы"
@@ -89,33 +93,34 @@
"xrayStatus" = "Xray" "xrayStatus" = "Xray"
"stopXray" = "Остановить" "stopXray" = "Остановить"
"restartXray" = "Перезапустить" "restartXray" = "Перезапустить"
"xraySwitch" = "Версия" "xraySwitch" = "Выбор версии"
"xraySwitchClick" = "Выберите желаемую версию" "xraySwitchClick" = "Выберите желаемую версию"
"xraySwitchClickDesk" = "Выбирайте внимательно, так как старые версии могут быть несовместимы с текущими конфигурациями" "xraySwitchClickDesk" = "Обратите внимание: старые версии могут не поддерживать текущие настройки"
"operationHours" = "Время работы системы" "operationHours" = "Время работы системы"
"systemLoad" = "Системная нагрузка" "systemLoad" = "Нагрузка на систему"
"systemLoadDesc" = "Средняя загрузка системы за последние 1, 5 и 15 минут" "systemLoadDesc" = "Средняя загрузка системы за последние 1, 5 и 15 минут"
"connectionTcpCountDesc" = "Общее количество подключений TCP по всем сетевым картам." "connectionTcpCountDesc" = "Общее количество подключений TCP по всем сетевым картам."
"connectionUdpCountDesc" = "Общее количество подключений UDP по всем сетевым картам." "connectionUdpCountDesc" = "Общее количество подключений UDP по всем сетевым картам."
"connectionCount" = "Количество соединений" "connectionCount" = "Количество соединений"
"upSpeed" = "Общая скорость upload для всех сетей" "upSpeed" = "Общая скорость отправки для всех сетей"
"downSpeed" = "Общая скорость download для всех сетей" "downSpeed" = "Общая скорость загрузки для всех сетей"
"totalSent" = "Общий объем загруженных данных для всех сетей с момента запуска системы" "totalSent" = "Общий объем отправляемых данных с момента запуска системы"
"totalReceive" = "Общий объем полученных данных для всех сетей с момента запуска системы." "totalReceive" = "Общий объем полученных данных для всех сетей с момента запуска системы."
"xraySwitchVersionDialog" = "Переключить версию Xray" "xraySwitchVersionDialog" = "Переключить версию Xray"
"xraySwitchVersionDialogDesc" = "Вы точно хотите сменить версию Xray?" "xraySwitchVersionDialogDesc" = "Вы точно хотите сменить версию Xray?"
"dontRefresh" = "Идёт установка. Пожалуйста, не обновляйте эту страницу" "dontRefresh" = "Установка в процессе. Не обновляйте страницу"
"logs" = "Логи" "logs" = "Логи"
"config" = "Конфигурация" "config" = "Конфигурация"
"backup" = "Бэкап и восстановление" "backup" = "Резервное копирование и восстановление"
"backupTitle" = "База данных бэкапа и восстановления" "backupTitle" = "База данных резервных копий"
"backupDescription" = "Рекомендуется сделать резервную копию перед восстановлением базы данных."
"exportDatabase" = "Экспорт базы данных" "exportDatabase" = "Экспорт базы данных"
"exportDatabaseDesc" = "Нажмите, чтобы скачать файл .db, содержащий резервную копию вашей текущей базы данных на ваше устройство."
"importDatabase" = "Импорт базы данных" "importDatabase" = "Импорт базы данных"
"importDatabaseDesc" = "Нажмите, чтобы выбрать и загрузить файл .db с вашего устройства для восстановления базы данных из резервной копии."
[pages.inbounds] [pages.inbounds]
"title" = "Подключения" "title" = "Подключения"
"totalDownUp" = "Всего uploads/downloads" "totalDownUp" = "Общий объем отправленного/полученного трафика"
"totalUsage" = "Всего использовано" "totalUsage" = "Всего использовано"
"inboundCount" = "Количество подключений" "inboundCount" = "Количество подключений"
"operate" = "Меню" "operate" = "Меню"
@@ -151,13 +156,13 @@
"certificatePath" = "Путь к файлу" "certificatePath" = "Путь к файлу"
"certificateContent" = "Содержимое файла" "certificateContent" = "Содержимое файла"
"publicKey" = "Публичный ключ" "publicKey" = "Публичный ключ"
"privatekey" = "Приватный ключ" "privatekey" = "Закрытый ключ"
"clickOnQRcode" = "Нажмите на QR-код, чтобы скопировать" "clickOnQRcode" = "Нажмите на QR-код, чтобы скопировать"
"client" = "Клиент" "client" = "Клиент"
"export" = "Экспорт ключей" "export" = "Экспорт ключей"
"clone" = "Клонировать" "clone" = "Клонировать"
"cloneInbound" = "Клонировать" "cloneInbound" = "Клонировать"
"cloneInboundContent" = "Все настройки этого подключения, кроме порта, IP-адреса прослушки и клиентов, будут клонированы" "cloneInboundContent" = "Будут клонированы все настройки подключений, за исключением списка клиентов, порта и IP-адреса прослушивания"
"cloneInboundOk" = "Клонировано" "cloneInboundOk" = "Клонировано"
"resetAllTraffic" = "Сбросить трафик всех подключений" "resetAllTraffic" = "Сбросить трафик всех подключений"
"resetAllTrafficTitle" = "Сброс трафика всех подключений" "resetAllTrafficTitle" = "Сброс трафика всех подключений"
@@ -173,8 +178,8 @@
"delDepletedClientsContent" = "Вы уверены, что хотите удалить всех отключенных пользователей?" "delDepletedClientsContent" = "Вы уверены, что хотите удалить всех отключенных пользователей?"
"email" = "Email" "email" = "Email"
"emailDesc" = "Пожалуйста, укажите уникальный Email" "emailDesc" = "Пожалуйста, укажите уникальный Email"
"IPLimit" = "Ограничение по IP" "IPLimit" = "Лимит по IP"
"IPLimitDesc" = "Сбросить подключение, если подключено больше введенного значения (введите 0, чтобы отключить ограничение IP-адресов)" "IPLimitDesc" = "Ограничение количества подключений с одного IP (0 отключить)"
"IPLimitlog" = "IP лог" "IPLimitlog" = "IP лог"
"IPLimitlogDesc" = "Лог IP-адресов (перед включением лога IP-адресов, вы должны очистить список)" "IPLimitlogDesc" = "Лог IP-адресов (перед включением лога IP-адресов, вы должны очистить список)"
"IPLimitlogclear" = "Очистить лог" "IPLimitlogclear" = "Очистить лог"
@@ -190,9 +195,6 @@
[pages.client] [pages.client]
"add" = "Добавить пользователя" "add" = "Добавить пользователя"
"groupAdd" = "Добавить пользователя подписки"
"isGroupEdit" = "Групповое редактирование"
"isGroupEditDesc" = "Все клиенты с одинаковой подпиской редактируются"
"edit" = "Редактировать пользователя" "edit" = "Редактировать пользователя"
"submitAdd" = "Добавить пользователя" "submitAdd" = "Добавить пользователя"
"submitEdit" = "Сохранить изменения" "submitEdit" = "Сохранить изменения"
@@ -232,18 +234,18 @@
"save" = "Сохранить" "save" = "Сохранить"
"infoDesc" = "Каждое выполненное изменение необходимо сохранить. Пожалуйста, перезапустите панель, чтобы изменения вступили в силу" "infoDesc" = "Каждое выполненное изменение необходимо сохранить. Пожалуйста, перезапустите панель, чтобы изменения вступили в силу"
"restartPanel" = "Перезапуск панели" "restartPanel" = "Перезапуск панели"
"restartPanelDesc" = "Вы уверены, что хотите перезапустить панель? Нажмите ОК для перезапуска панели через 3 сек. Если вы не можете пользоваться панелью после перезапуска, пожалуйста, посмотрите лог панели на сервере" "restartPanelDesc" = "Вы уверены, что хотите перезапустить панель? Подтвердите, и перезапуск произойдёт через 3 секунды. Если панель станет недоступной, проверьте лог сервера"
"actions" = "Действия" "actions" = "Действия"
"resetDefaultConfig" = "Сбросить на конфигурацию по умолчанию" "resetDefaultConfig" = "Восстановить настройки по умолчанию"
"panelSettings" = "Настройки панели" "panelSettings" = "Настройки панели"
"securitySettings" = "Настройки безопасности" "securitySettings" = "Настройки безопасности"
"TGBotSettings" = "Настройки Telegram бота" "TGBotSettings" = "Настройки Telegram бота"
"panelListeningIP" = "IP-адрес панели" "panelListeningIP" = "IP-адрес панели"
"panelListeningIPDesc" = "Оставьте пустым для подключения с любого IP" "panelListeningIPDesc" = "Оставьте пустым для подключения с любого IP"
"panelListeningDomain" = "Домен прослушивания панели" "panelListeningDomain" = "Домен панели"
"panelListeningDomainDesc" = "По умолчанию оставьте пустым, чтобы отслеживать все домены и IP-адреса" "panelListeningDomainDesc" = "По умолчанию оставьте пустым, чтобы отслеживать все домены и IP-адреса"
"panelPort" = "Порт панели" "panelPort" = "Порт панели"
"panelPortDesc" = "Порт, используемый для отображения этой панели" "panelPortDesc" = "Порт, на котором работает панель"
"publicKeyPath" = "Путь к файлу публичного ключа сертификата панели" "publicKeyPath" = "Путь к файлу публичного ключа сертификата панели"
"publicKeyPathDesc" = "Введите полный путь, начинающийся с '/'" "publicKeyPathDesc" = "Введите полный путь, начинающийся с '/'"
"privateKeyPath" = "Путь к файлу приватного ключа сертификата панели" "privateKeyPath" = "Путь к файлу приватного ключа сертификата панели"
@@ -255,14 +257,14 @@
"remarkModel" = "Модель примечания и символ разделения" "remarkModel" = "Модель примечания и символ разделения"
"datepicker" = "Выбор даты" "datepicker" = "Выбор даты"
"datepickerPlaceholder" = "Выберите дату" "datepickerPlaceholder" = "Выберите дату"
"datepickerDescription" = "Запланированные задачи выполняются в соответствии с данным календарём" "datepickerDescription" = "Запланированные задачи будут выполняться в выбранное время"
"sampleRemark" = "Пример замечания" "sampleRemark" = "Пример замечания"
"oldUsername" = "Текущий логин" "oldUsername" = "Текущий логин"
"currentPassword" = "Текущий пароль" "currentPassword" = "Текущий пароль"
"newUsername" = "Новый логин" "newUsername" = "Новый логин"
"newPassword" = "Новый пароль" "newPassword" = "Новый пароль"
"telegramBotEnable" = "Включить Telegram бота" "telegramBotEnable" = "Включить Telegram бота"
"telegramBotEnableDesc" = "Подключайтесь к функциям этой панели через Telegram бота" "telegramBotEnableDesc" = "Получайте доступ к функциям панели через Telegram-бота"
"telegramToken" = "Токен Telegram бота" "telegramToken" = "Токен Telegram бота"
"telegramTokenDesc" = "Необходимо получить токен у менеджера ботов Telegram @botfather" "telegramTokenDesc" = "Необходимо получить токен у менеджера ботов Telegram @botfather"
"telegramProxy" = "Прокси Socks5" "telegramProxy" = "Прокси Socks5"
@@ -272,14 +274,14 @@
"telegramChatId" = "Идентификатор Telegram администратора бота" "telegramChatId" = "Идентификатор Telegram администратора бота"
"telegramChatIdDesc" = "Один или несколько идентификаторов администратора бота. Чтобы получить идентификатор, используйте @userinfobot или команду '/id' в боте." "telegramChatIdDesc" = "Один или несколько идентификаторов администратора бота. Чтобы получить идентификатор, используйте @userinfobot или команду '/id' в боте."
"telegramNotifyTime" = "Частота уведомлений бота Telegram" "telegramNotifyTime" = "Частота уведомлений бота Telegram"
"telegramNotifyTimeDesc" = "Используйте формат времени Crontab" "telegramNotifyTimeDesc" = "Укажите интервал уведомлений в формате Crontab"
"tgNotifyBackup" = "Резервное копирование базы данных" "tgNotifyBackup" = "Резервное копирование базы данных"
"tgNotifyBackupDesc" = "Включать файл резервной копии базы данных с уведомлением об отчете" "tgNotifyBackupDesc" = "Отправлять уведомление с файлом резервной копии базы данных"
"tgNotifyLogin" = "Уведомление о входе" "tgNotifyLogin" = "Уведомление о входе"
"tgNotifyLoginDesc" = "Отображает имя пользователя, IP-адрес и время, когда кто-то пытается войти в вашу панель." "tgNotifyLoginDesc" = "Отображает имя пользователя, IP-адрес и время, когда кто-то пытается войти в вашу панель."
"sessionMaxAge" = "Продолжительность сессии" "sessionMaxAge" = "Продолжительность сессии"
"sessionMaxAgeDesc" = "Продолжительность сессии в системе (значение: минута)" "sessionMaxAgeDesc" = "Продолжительность сессии в системе (значение: минута)"
"expireTimeDiff" = "Порог истечения срока сессии для уведомления" "expireTimeDiff" = "Задержка уведомления об истечении сессии"
"expireTimeDiffDesc" = "Получение уведомления об истечении срока действия сессии до достижения порогового значения (значение: день)" "expireTimeDiffDesc" = "Получение уведомления об истечении срока действия сессии до достижения порогового значения (значение: день)"
"trafficDiff" = "Порог трафика для уведомления" "trafficDiff" = "Порог трафика для уведомления"
"trafficDiffDesc" = "Получение уведомления об исчерпании трафика до достижения порога (значение: ГБ)" "trafficDiffDesc" = "Получение уведомления об исчерпании трафика до достижения порога (значение: ГБ)"
@@ -290,8 +292,6 @@
"subSettings" = "Подписка" "subSettings" = "Подписка"
"subEnable" = "Включить службу" "subEnable" = "Включить службу"
"subEnableDesc" = "Функция подписки с отдельной конфигурацией" "subEnableDesc" = "Функция подписки с отдельной конфигурацией"
"subSyncEnable" = "Включить синхронизацию подписки"
"subSyncEnableDesc" = "Трафик от клиентов с одинаковой подпиской будет синхронизироваться каждые 10 секунд."
"subListen" = "Прослушивание IP" "subListen" = "Прослушивание IP"
"subListenDesc" = "Оставьте пустым по умолчанию, чтобы отслеживать все IP-адреса" "subListenDesc" = "Оставьте пустым по умолчанию, чтобы отслеживать все IP-адреса"
"subPort" = "Порт подписки" "subPort" = "Порт подписки"
@@ -309,20 +309,32 @@
"subEncrypt" = "Шифровать конфиги" "subEncrypt" = "Шифровать конфиги"
"subEncryptDesc" = "Шифровать возвращенные конфиги в подписке" "subEncryptDesc" = "Шифровать возвращенные конфиги в подписке"
"subShowInfo" = "Показать информацию об использовании" "subShowInfo" = "Показать информацию об использовании"
"subShowInfoDesc" = "Показывать оставшиеся трафик и дату после имени конфигурации" "subShowInfoDesc" = "Отображать остаток трафика и дату окончания после имени конфигурации"
"subURI" = "URI обратного прокси" "subURI" = "URI обратного прокси"
"subURIDesc" = "Изменить базовый URI URL-адреса подписки для использования за прокси-серверами" "subURIDesc" = "Изменить базовый URI URL-адреса подписки для использования за прокси-серверами"
"externalTrafficInformEnable" = "Информация о внешнем трафике"
"externalTrafficInformEnableDesc" = "Информировать внешний API о каждом обновлении трафика"
"externalTrafficInformURI" = "URI информации о внешнем трафике"
"externalTrafficInformURIDesc" = "Обновления трафика отправляются на этот URI"
"fragment" = "Фрагментация" "fragment" = "Фрагментация"
"fragmentDesc" = "Включить фрагментацию для пакета приветствия TLS" "fragmentDesc" = "Включить фрагментацию TLS-приветствия"
"fragmentSett" = "Настройки фрагментации" "fragmentSett" = "Настройки фрагментации"
"noisesDesc" = "Включить Noises." "noisesDesc" = "Включить Noises."
"noisesSett" = "Настройки Noises" "noisesSett" = "Настройки Noises"
"mux" = "Mux" "mux" = "Mux"
"muxDesc" = "Передача нескольких независимых потоков данных в рамках установленного потока данных." "muxDesc" = "Передача нескольких независимых потоков данных в одном соединении."
"muxSett" = "Mux Настройки" "muxSett" = "Mux Настройки"
"direct" = "Прямая связь" "direct" = "Прямая связь"
"directDesc" = "Напрямую устанавливает соединения с доменами или диапазонами IP конкретной страны." "directDesc" = "Устанавливает прямые соединения с доменами или IP-адресами определённой страны."
"notifications" = "Уведомления"
"certs" = "Сертификаты"
"externalTraffic" = "Внешний трафик"
"dateAndTime" = "Дата и время"
"proxyAndServer" = "Прокси и сервер"
"intervals" = "Интервалы"
"information" = "Информация"
"language" = "Язык"
"telegramBotLanguage" = "Язык Telegram-бота"
[pages.xray] [pages.xray]
"title" = "Настройки Xray" "title" = "Настройки Xray"
@@ -333,7 +345,7 @@
"generalConfigs" = "Основные настройки" "generalConfigs" = "Основные настройки"
"generalConfigsDesc" = "Эти параметры описывают общие настройки" "generalConfigsDesc" = "Эти параметры описывают общие настройки"
"logConfigs" = "Журнал" "logConfigs" = "Журнал"
"logConfigsDesc" = "Журналы могут повлиять на эффективность вашего сервера. Рекомендуется включать их только в случае необходимости!" "logConfigsDesc" = "Логи могут замедлять работу сервера. Включайте только нужные вам виды логов при необходимости!"
"blockConfigs" = "Блокировка конфигураций" "blockConfigs" = "Блокировка конфигураций"
"blockConfigsDesc" = "Эти параметры не позволят пользователям подключаться к определенным протоколам и веб-сайтам" "blockConfigsDesc" = "Эти параметры не позволят пользователям подключаться к определенным протоколам и веб-сайтам"
"basicRouting" = "Базовые соединения" "basicRouting" = "Базовые соединения"
@@ -373,10 +385,17 @@
"errorLogDesc" = "Путь к файлу журнала ошибок. Специальное значение «none» отключает журналы ошибок." "errorLogDesc" = "Путь к файлу журнала ошибок. Специальное значение «none» отключает журналы ошибок."
"dnsLog" = "DNS Журнал" "dnsLog" = "DNS Журнал"
"dnsLogDesc" = "Включить логи запросов DNS" "dnsLogDesc" = "Включить логи запросов DNS"
"outboundTraffic" = "Исходящий трафик"
"outboundTrafficDesc" = "Включить исходящий трафик"
"maskAddress" = "Маскировать Адрес" "maskAddress" = "Маскировать Адрес"
"maskAddressDesc" = "Маска IP-адреса, при активации автоматически заменяет IP-адрес, который появляется в логе." "maskAddressDesc" = "При активации реальный IP-адрес заменяется на маскировочный в логах."
"statistics" = "Статистика"
"statsInboundUplink" = "Статистика входящего аплинка"
"statsInboundUplinkDesc" = "Включает сбор статистики для исходящего трафика всех входящих прокси."
"statsInboundDownlink" = "Статистика входящего даунлинка"
"statsInboundDownlinkDesc" = "Включает сбор статистики для входящего трафика всех входящих прокси."
"statsOutboundUplink" = "Статистика исходящего аплинка"
"statsOutboundUplinkDesc" = "Включает сбор статистики для исходящего трафика всех исходящих прокси."
"statsOutboundDownlink" = "Статистика исходящего даунлинка"
"statsOutboundDownlinkDesc" = "Включает сбор статистики для входящего трафика всех исходящих прокси."
[pages.xray.rules] [pages.xray.rules]
"first" = "Первый" "first" = "Первый"
@@ -422,7 +441,7 @@
"balancerDesc" = "Невозможно одновременно использовать balancerTag и outboundTag. При одновременном использовании будет работать только outboundTag." "balancerDesc" = "Невозможно одновременно использовать balancerTag и outboundTag. При одновременном использовании будет работать только outboundTag."
[pages.xray.wireguard] [pages.xray.wireguard]
"secretKey" = "Приватный ключ" "secretKey" = "Закрытый ключ"
"publicKey" = "Публичный ключ" "publicKey" = "Публичный ключ"
"allowedIPs" = "Разрешенные IP-адреса" "allowedIPs" = "Разрешенные IP-адреса"
"endpoint" = "Конечная точка" "endpoint" = "Конечная точка"
@@ -434,6 +453,14 @@
"enableDesc" = "Включить встроенный DNS-сервер" "enableDesc" = "Включить встроенный DNS-сервер"
"tag" = "Входящий тег DNS" "tag" = "Входящий тег DNS"
"tagDesc" = "Этот тег будет доступен как входящий тег в правилах маршрутизации." "tagDesc" = "Этот тег будет доступен как входящий тег в правилах маршрутизации."
"clientIp" = "IP клиента"
"clientIpDesc" = "Используется для уведомления сервера о указанном местоположении IP во время DNS-запросов"
"disableCache" = "Отключить кэш"
"disableCacheDesc" = "Отключает кэширование DNS"
"disableFallback" = "Отключить резервный DNS"
"disableFallbackDesc" = "Отключает резервные DNS-запросы"
"disableFallbackIfMatch" = "Отключить резервный DNS при совпадении"
"disableFallbackIfMatchDesc" = "Отключает резервные DNS-запросы при совпадении списка доменов DNS-сервера"
"strategy" = "Стратегия запроса" "strategy" = "Стратегия запроса"
"strategyDesc" = "Общая стратегия разрешения доменных имен" "strategyDesc" = "Общая стратегия разрешения доменных имен"
"add" = "Добавить сервер" "add" = "Добавить сервер"
@@ -448,7 +475,7 @@
"poolSize" = "Размер пула" "poolSize" = "Размер пула"
[pages.settings.security] [pages.settings.security]
"admin" = "Админ" "admin" = "Учетные данные администратора"
"secret" = "Секретный токен" "secret" = "Секретный токен"
"loginSecurity" = "Безопасность входа" "loginSecurity" = "Безопасность входа"
"loginSecurityDesc" = "Включить дополнительные меры безопасности входа пользователя" "loginSecurityDesc" = "Включить дополнительные меры безопасности входа пользователя"
@@ -456,7 +483,7 @@
"secretTokenDesc" = "Пожалуйста, скопируйте и сохраните этот токен в безопасном месте. Этот токен необходим для входа в систему и не может быть восстановлен с помощью инструмента x-ui" "secretTokenDesc" = "Пожалуйста, скопируйте и сохраните этот токен в безопасном месте. Этот токен необходим для входа в систему и не может быть восстановлен с помощью инструмента x-ui"
[pages.settings.toasts] [pages.settings.toasts]
"modifySettings" = "Изменение настроек" "modifySettings" = "Настройки изменены"
"getSettings" = "Просмотр настроек" "getSettings" = "Просмотр настроек"
"modifyUser" = "Изменение пользователя" "modifyUser" = "Изменение пользователя"
"originalUserPassIncorrect" = "Неверное имя пользователя или пароль" "originalUserPassIncorrect" = "Неверное имя пользователя или пароль"
@@ -491,9 +518,9 @@
"status" = "✅ Бот работает нормально!" "status" = "✅ Бот работает нормально!"
"usage" = "❗ Пожалуйста, укажите текст для поиска!" "usage" = "❗ Пожалуйста, укажите текст для поиска!"
"getID" = "🆔 Ваш ID: <code>{{ .ID }}</code>" "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>" "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" = "✅ Операция успешно завершена!" "restartSuccess" = "✅ Операция успешно завершена!"
"restartFailed" = "❗ Ошибка в операции.\r\n\r\n<code>Ошибка: {{ .Error }}</code>." "restartFailed" = "❗ Ошибка в операции.\r\n\r\n<code>Ошибка: {{ .Error }}</code>."
"xrayNotRunning" = "❗ Xray Core не запущен." "xrayNotRunning" = "❗ Xray Core не запущен."
@@ -580,7 +607,7 @@
"allClients" = "Все клиенты" "allClients" = "Все клиенты"
[tgbot.answers] [tgbot.answers]
"successfulOperation" = "✅ Успешный!" "successfulOperation" = "✅ Успешно!"
"errorOperation" = "❗ Ошибка в операции." "errorOperation" = "❗ Ошибка в операции."
"getInboundsFailed" = "❌ Не удалось получить входящие потоки." "getInboundsFailed" = "❌ Не удалось получить входящие потоки."
"getClientsFailed" = "❌ Не удалось получить клиентов." "getClientsFailed" = "❌ Не удалось получить клиентов."
@@ -589,8 +616,8 @@
"IpRefreshSuccess" = "✅ {{ .Email }}: IP-адреса успешно обновлены." "IpRefreshSuccess" = "✅ {{ .Email }}: IP-адреса успешно обновлены."
"TGIdRefreshSuccess" = "✅ {{ .Email }}: Пользователь Telegram клиента успешно обновлен." "TGIdRefreshSuccess" = "✅ {{ .Email }}: Пользователь Telegram клиента успешно обновлен."
"resetTrafficSuccess" = "✅ {{ .Email }}: Трафик успешно сброшен." "resetTrafficSuccess" = "✅ {{ .Email }}: Трафик успешно сброшен."
"setTrafficLimitSuccess" = "✅ {{ .Email }}: Лимит Трафик успешно сохранен." "setTrafficLimitSuccess" = "✅ {{ .Email }}: Лимит трафика успешно установлен."
"expireResetSuccess" = "✅ {{ .Email }}: Дни истечения успешно сброшены." "expireResetSuccess" = "✅ {{ .Email }}: Срок действия успешно сброшен."
"resetIpSuccess" = "✅ {{ .Email }}: Лимит IP ({{ .Count }}) успешно сохранен." "resetIpSuccess" = "✅ {{ .Email }}: Лимит IP ({{ .Count }}) успешно сохранен."
"clearIpSuccess" = "✅ {{ .Email }}: IP-адреса успешно очищены." "clearIpSuccess" = "✅ {{ .Email }}: IP-адреса успешно очищены."
"getIpLog" = "✅ {{ .Email }}: Получен лог IP." "getIpLog" = "✅ {{ .Email }}: Получен лог IP."

View File

@@ -61,6 +61,10 @@
"secAlertPanelURI" = "Panel varsayılan URI yolu güvensiz. Karmaşık bir URI yolu yapılandırın." "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." "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." "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] [menu]
"dashboard" = "Genel Bakış" "dashboard" = "Genel Bakış"
@@ -109,9 +113,10 @@
"config" = "Yapılandırma" "config" = "Yapılandırma"
"backup" = "Yedekle & Geri Yükle" "backup" = "Yedekle & Geri Yükle"
"backupTitle" = "Veritabanı Yedekleme & Geri Yükleme" "backupTitle" = "Veritabanı Yedekleme & Geri Yükleme"
"backupDescription" = "Veritabanını geri yüklemeden önce yedek almanız önerilir."
"exportDatabase" = "Yedekle" "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" "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] [pages.inbounds]
"title" = "Gelenler" "title" = "Gelenler"
@@ -190,9 +195,6 @@
[pages.client] [pages.client]
"add" = "Müşteri Ekle" "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" "edit" = "Müşteriyi Düzenle"
"submitAdd" = "Müşteri Ekle" "submitAdd" = "Müşteri Ekle"
"submitEdit" = "Değişiklikleri Kaydet" "submitEdit" = "Değişiklikleri Kaydet"
@@ -290,8 +292,6 @@
"subSettings" = "Abonelik" "subSettings" = "Abonelik"
"subEnable" = "Abonelik Hizmetini Etkinleştir" "subEnable" = "Abonelik Hizmetini Etkinleştir"
"subEnableDesc" = "Abonelik hizmetini etkinleştirir." "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" "subListen" = "Dinleme IP"
"subListenDesc" = "Abonelik hizmeti için IP adresi. (tüm IP'leri dinlemek için boş bırakın)" "subListenDesc" = "Abonelik hizmeti için IP adresi. (tüm IP'leri dinlemek için boş bırakın)"
"subPort" = "Dinleme Portu" "subPort" = "Dinleme Portu"
@@ -312,6 +312,10 @@
"subShowInfoDesc" = "Kalan trafik ve tarih müşteri uygulamalarında görüntülenir." "subShowInfoDesc" = "Kalan trafik ve tarih müşteri uygulamalarında görüntülenir."
"subURI" = "Ters Proxy URI" "subURI" = "Ters Proxy URI"
"subURIDesc" = "Proxy arkasında kullanılacak abonelik URL'sinin URI yolu." "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" "fragment" = "Parçalama"
"fragmentDesc" = "TLS merhaba paketinin parçalanmasını etkinleştir." "fragmentDesc" = "TLS merhaba paketinin parçalanmasını etkinleştir."
"fragmentSett" = "Parçalama Ayarları" "fragmentSett" = "Parçalama Ayarları"
@@ -322,7 +326,15 @@
"muxSett" = "Mux Ayarları" "muxSett" = "Mux Ayarları"
"direct" = "Doğrudan Bağlantı" "direct" = "Doğrudan Bağlantı"
"directDesc" = "Belirli bir ülkenin alan adları veya IP aralıkları ile doğrudan bağlantı kurar." "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] [pages.xray]
"title" = "Xray Yapılandırmaları" "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" "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üğü" "dnsLog" = "DNS Günlüğü"
"dnsLogDesc" = "DNS sorgu günlüklerini etkinleştirin" "dnsLogDesc" = "DNS sorgu günlüklerini etkinleştirin"
"outboundTraffic" = "Gidenler trafiği"
"outboundTrafficDesc" = ıkış trafiğini etkinleştirip etkinleştirmeyeceğiniz"
"maskAddress" = "Adres Maskesi" "maskAddress" = "Adres Maskesi"
"maskAddressDesc" = "IP adresi maskesi, etkinleştirildiğinde, günlükte görünen IP adresini otomatik olarak değiştirecektir." "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] [pages.xray.rules]
"first" = "İlk" "first" = "İlk"
@@ -434,6 +453,14 @@
"enableDesc" = "Dahili DNS sunucusunu etkinleştir" "enableDesc" = "Dahili DNS sunucusunu etkinleştir"
"tag" = "DNS Gelen Etiketi" "tag" = "DNS Gelen Etiketi"
"tagDesc" = "Bu etiket, yönlendirme kurallarında Gelen etiketi olarak kullanılabilir." "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" "strategy" = "Sorgu Stratejisi"
"strategyDesc" = "Alan adlarını çözmek için genel strateji" "strategyDesc" = "Alan adlarını çözmek için genel strateji"
"add" = "Sunucu Ekle" "add" = "Sunucu Ekle"
@@ -448,7 +475,7 @@
"poolSize" = "Havuz Boyutu" "poolSize" = "Havuz Boyutu"
[pages.settings.security] [pages.settings.security]
"admin" = "Yönetici" "admin" = "Yönetici kimlik bilgileri"
"secret" = "Gizli Anahtar" "secret" = "Gizli Anahtar"
"loginSecurity" = "Güvenli Giriş" "loginSecurity" = "Güvenli Giriş"
"loginSecurityDesc" = "Daha fazla güvenlik sağlamak için ek bir kimlik doğrulama katmanı ekler." "loginSecurityDesc" = "Daha fazla güvenlik sağlamak için ek bir kimlik doğrulama katmanı ekler."
@@ -491,9 +518,9 @@
"status" = "✅ Bot çalışıyor!" "status" = "✅ Bot çalışıyor!"
"usage" = "❗ Lütfen aramak için bir metin sağlayın!" "usage" = "❗ Lütfen aramak için bir metin sağlayın!"
"getID" = "🆔 Kimliğiniz: <code>{{ .ID }}</code>" "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>" "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ı!" "restartSuccess" = "✅ İşlem başarılı!"
"restartFailed" = "❗ İşlem hatası.\r\n\r\n<code>Hata: {{ .Error }}</code>." "restartFailed" = "❗ İşlem hatası.\r\n\r\n<code>Hata: {{ .Error }}</code>."
"xrayNotRunning" = "❗ Xray Core çalışmıyor." "xrayNotRunning" = "❗ Xray Core çalışmıyor."

View File

@@ -61,6 +61,10 @@
"secAlertPanelURI" = "Стандартний URI-шлях панелі небезпечний. Будь ласка, сконфігуруйте складний URI-шлях." "secAlertPanelURI" = "Стандартний URI-шлях панелі небезпечний. Будь ласка, сконфігуруйте складний URI-шлях."
"secAlertSubURI" = "Стандартний URI-шлях підписки небезпечний. Будь ласка, сконфігуруйте складний URI-шлях." "secAlertSubURI" = "Стандартний URI-шлях підписки небезпечний. Будь ласка, сконфігуруйте складний URI-шлях."
"secAlertSubJsonURI" = "Стандартний URI-шлях JSON підписки небезпечний. Будь ласка, сконфігуруйте складний URI-шлях." "secAlertSubJsonURI" = "Стандартний URI-шлях JSON підписки небезпечний. Будь ласка, сконфігуруйте складний URI-шлях."
"emptyDnsDesc" = "Немає доданих DNS-серверів."
"emptyFakeDnsDesc" = "Немає доданих Fake DNS-серверів."
"emptyBalancersDesc" = "Немає доданих балансувальників."
"emptyReverseDesc" = "Немає доданих зворотних проксі."
[menu] [menu]
"dashboard" = "Огляд" "dashboard" = "Огляд"
@@ -109,9 +113,10 @@
"config" = "Конфігурація" "config" = "Конфігурація"
"backup" = "Резервне копіювання та відновлення" "backup" = "Резервне копіювання та відновлення"
"backupTitle" = "Резервне копіювання та відновлення бази даних" "backupTitle" = "Резервне копіювання та відновлення бази даних"
"backupDescription" = "Рекомендується зробити резервну копію перед відновленням бази даних." "exportDatabase" = "Резервна копія"
"exportDatabase" = "Резервне копіювання" "exportDatabaseDesc" = "Натисніть, щоб завантажити файл .db, що містить резервну копію вашої поточної бази даних на ваш пристрій."
"importDatabase" = "Відновити" "importDatabase" = "Відновити"
"importDatabaseDesc" = "Натисніть, щоб вибрати та завантажити файл .db з вашого пристрою для відновлення бази даних з резервної копії."
[pages.inbounds] [pages.inbounds]
"title" = "Вхідні" "title" = "Вхідні"
@@ -190,9 +195,6 @@
[pages.client] [pages.client]
"add" = "Додати клієнта" "add" = "Додати клієнта"
"groupAdd" = "Додати підписаного користувача"
"isGroupEdit" = "Редагування групи"
"isGroupEditDesc" = "Всі клієнти з однаковою підпискою редагуються"
"edit" = "Редагувати клієнта" "edit" = "Редагувати клієнта"
"submitAdd" = "Додати клієнта" "submitAdd" = "Додати клієнта"
"submitEdit" = "Зберегти зміни" "submitEdit" = "Зберегти зміни"
@@ -290,8 +292,6 @@
"subSettings" = "Підписка" "subSettings" = "Підписка"
"subEnable" = "Увімкнути службу підписки" "subEnable" = "Увімкнути службу підписки"
"subEnableDesc" = "Вмикає службу підписки." "subEnableDesc" = "Вмикає службу підписки."
"subSyncEnable" = "Увімкнути синхронізацію підписки"
"subSyncEnableDesc" = "Трафік від клієнтів з однаковою підпискою буде синхронізовано кожні 10 секунд."
"subListen" = "Слухати IP" "subListen" = "Слухати IP"
"subListenDesc" = "IP-адреса для служби підписки. (залиште порожнім, щоб слухати всі IP-адреси)" "subListenDesc" = "IP-адреса для служби підписки. (залиште порожнім, щоб слухати всі IP-адреси)"
"subPort" = "Слухати порт" "subPort" = "Слухати порт"
@@ -312,6 +312,10 @@
"subShowInfoDesc" = "Залишок трафіку та дата відображатимуться в клієнтських програмах." "subShowInfoDesc" = "Залишок трафіку та дата відображатимуться в клієнтських програмах."
"subURI" = "URI зворотного проксі" "subURI" = "URI зворотного проксі"
"subURIDesc" = "URI до URL-адреси підписки для використання за проксі." "subURIDesc" = "URI до URL-адреси підписки для використання за проксі."
"externalTrafficInformEnable" = "Інформація про зовнішній трафік"
"externalTrafficInformEnableDesc" = "Інформувати зовнішній API про кожне оновлення трафіку."
"externalTrafficInformURI" = "Інформаційний URI зовнішнього трафіку"
"externalTrafficInformURIDesc" = "Оновлення трафіку надсилаються на цей URI."
"fragment" = "Фрагментація" "fragment" = "Фрагментація"
"fragmentDesc" = "Увімкнути фрагментацію для пакету привітання TLS" "fragmentDesc" = "Увімкнути фрагментацію для пакету привітання TLS"
"fragmentSett" = "Параметри фрагментації" "fragmentSett" = "Параметри фрагментації"
@@ -322,7 +326,15 @@
"muxSett" = "Налаштування Mux" "muxSett" = "Налаштування Mux"
"direct" = "Пряме підключення" "direct" = "Пряме підключення"
"directDesc" = "Безпосередньо встановлює з’єднання з доменами або діапазонами IP певної країни." "directDesc" = "Безпосередньо встановлює з’єднання з доменами або діапазонами IP певної країни."
"notifications" = "Сповіщення"
"certs" = "Сертифікати"
"externalTraffic" = "Зовнішній трафік"
"dateAndTime" = "Дата та час"
"proxyAndServer" = "Проксі та сервер"
"intervals" = "Інтервали"
"information" = "Інформація"
"language" = "Мова"
"telegramBotLanguage" = "Мова Telegram-бота"
[pages.xray] [pages.xray]
"title" = "Xray конфігурації" "title" = "Xray конфігурації"
@@ -373,10 +385,17 @@
"errorLogDesc" = "Шлях до файлу журналу помилок. Спеціальне значення 'none' вимикає журнали помилок" "errorLogDesc" = "Шлях до файлу журналу помилок. Спеціальне значення 'none' вимикає журнали помилок"
"dnsLog" = "Журнал DNS" "dnsLog" = "Журнал DNS"
"dnsLogDesc" = "Чи включити журнали запитів DNS" "dnsLogDesc" = "Чи включити журнали запитів DNS"
"outboundTraffic" = "Вихідний трафік"
"outboundTrafficDesc" = "Чи потрібно увімкнути вихідний трафік"
"maskAddress" = "Маскувати Адресу" "maskAddress" = "Маскувати Адресу"
"maskAddressDesc" = "Маска IP-адреси, при активації автоматично замінює IP-адресу, яка з'являється у журналі." "maskAddressDesc" = "Маска IP-адреси, при активації автоматично замінює IP-адресу, яка з'являється у журналі."
"statistics" = "Статистика"
"statsInboundUplink" = "Статистика вхідного аплінку"
"statsInboundUplinkDesc" = "Увімкнення збору статистики для вхідного трафіку всіх вхідних проксі."
"statsInboundDownlink" = "Статистика вхідного даунлінку"
"statsInboundDownlinkDesc" = "Увімкнення збору статистики для вихідного трафіку всіх вхідних проксі."
"statsOutboundUplink" = "Статистика вихідного аплінку"
"statsOutboundUplinkDesc" = "Увімкнення збору статистики для вхідного трафіку всіх вихідних проксі."
"statsOutboundDownlink" = "Статистика вихідного даунлінку"
"statsOutboundDownlinkDesc" = "Увімкнення збору статистики для вихідного трафіку всіх вихідних проксі."
[pages.xray.rules] [pages.xray.rules]
"first" = "Перший" "first" = "Перший"
@@ -434,6 +453,14 @@
"enableDesc" = "Увімкнути вбудований DNS-сервер" "enableDesc" = "Увімкнути вбудований DNS-сервер"
"tag" = "Мітка вхідного DNS" "tag" = "Мітка вхідного DNS"
"tagDesc" = "Ця мітка буде доступна як вхідна мітка в правилах маршрутизації." "tagDesc" = "Ця мітка буде доступна як вхідна мітка в правилах маршрутизації."
"clientIp" = "IP клієнта"
"clientIpDesc" = "Використовується для повідомлення серверу про вказане місцезнаходження IP під час DNS-запитів"
"disableCache" = "Вимкнути кеш"
"disableCacheDesc" = "Вимкнути кешування DNS"
"disableFallback" = "Вимкнути резервний DNS"
"disableFallbackDesc" = "Вимкнути резервні DNS-запити"
"disableFallbackIfMatch" = "Вимкнути резервний DNS при збігу"
"disableFallbackIfMatchDesc" = "Вимкнути резервні DNS-запити при збігу списку доменів DNS-сервера"
"strategy" = "Стратегія запиту" "strategy" = "Стратегія запиту"
"strategyDesc" = "Загальна стратегія вирішення доменних імен" "strategyDesc" = "Загальна стратегія вирішення доменних імен"
"add" = "Додати сервер" "add" = "Додати сервер"
@@ -448,7 +475,7 @@
"poolSize" = "Розмір пулу" "poolSize" = "Розмір пулу"
[pages.settings.security] [pages.settings.security]
"admin" = "Адміністратор" "admin" = "Облікові дані адміністратора"
"secret" = "Секретний маркер" "secret" = "Секретний маркер"
"loginSecurity" = "Безпечний вхід" "loginSecurity" = "Безпечний вхід"
"loginSecurityDesc" = "Додає додатковий рівень автентифікації для забезпечення більшої безпеки." "loginSecurityDesc" = "Додає додатковий рівень автентифікації для забезпечення більшої безпеки."
@@ -491,9 +518,9 @@
"status" = "✅ Бот в порядку!" "status" = "✅ Бот в порядку!"
"usage" = "❗ Введіть текст для пошуку!" "usage" = "❗ Введіть текст для пошуку!"
"getID" = "🆔 Ваш ідентифікатор: <code>{{ .ID }}</code>" "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>" "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" = "✅ Операція успішна!" "restartSuccess" = "✅ Операція успішна!"
"restartFailed" = "❗ Помилка в операції.\r\n\r\n<code>Помилка: {{ .Error }}</code>." "restartFailed" = "❗ Помилка в операції.\r\n\r\n<code>Помилка: {{ .Error }}</code>."
"xrayNotRunning" = "❗ Xray Core не запущений." "xrayNotRunning" = "❗ Xray Core не запущений."

View File

@@ -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." "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." "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." "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] [menu]
"dashboard" = "Trạng thái hệ thống" "dashboard" = "Trạng thái hệ thống"
@@ -109,9 +113,10 @@
"config" = "Cấu hình" "config" = "Cấu hình"
"backup" = "Sao lưu & Khôi phục" "backup" = "Sao lưu & Khôi phục"
"backupTitle" = "Sao lưu & Khôi phục Cơ sở dữ liệu" "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" = "Sao lưu"
"exportDatabase" = "Tải về Cơ sở dữ liệ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" = "Tải lên Cơ sở dữ liệu" "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] [pages.inbounds]
"title" = "Điểm vào (Inbounds)" "title" = "Điểm vào (Inbounds)"
@@ -190,9 +195,6 @@
[pages.client] [pages.client]
"add" = "Thêm người dùng" "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" "edit" = "Chỉnh sửa người dùng"
"submitAdd" = "Thêm" "submitAdd" = "Thêm"
"submitEdit" = "Lưu thay đổi" "submitEdit" = "Lưu thay đổi"
@@ -290,8 +292,6 @@
"subSettings" = "Gói đăng ký" "subSettings" = "Gói đăng ký"
"subEnable" = "Bật dịch vụ" "subEnable" = "Bật dịch vụ"
"subEnableDesc" = "Tính năng gói đăng ký với cấu hình riêng" "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" "subListen" = "Listening IP"
"subListenDesc" = "Mặc định để trống để nghe tất cả các IP" "subListenDesc" = "Mặc định để trống để nghe tất cả các IP"
"subPort" = "Cổng gói đăng ký" "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" "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" "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" "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" "fragment" = "Sự phân mảnh"
"fragmentDesc" = "Kích hoạt phân mảnh cho gói TLS hello" "fragmentDesc" = "Kích hoạt phân mảnh cho gói TLS hello"
"fragmentSett" = "Cài đặt phân mảnh" "fragmentSett" = "Cài đặt phân mảnh"
@@ -322,7 +326,15 @@
"muxSett" = "Mux Cài đặt" "muxSett" = "Mux Cài đặt"
"direct" = "Kết nối trực tiếp" "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ể." "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] [pages.xray]
"title" = "Cài đặt 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'" "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" "dnsLog" = "Nhật ký DNS"
"dnsLogDesc" = "Có bật nhật ký truy vấn DNS không" "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ỉ" "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ý." "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] [pages.xray.rules]
"first" = "Đầu tiên" "first" = "Đầu tiên"
@@ -434,6 +453,14 @@
"enableDesc" = "Kích hoạt máy chủ DNS tích hợp" "enableDesc" = "Kích hoạt máy chủ DNS tích hợp"
"tag" = "Thẻ gửi đến DNS" "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." "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" "strategy" = "Chiến lược truy vấn"
"strategyDesc" = "Chiến lược tổng thể để phân giải tên miền" "strategyDesc" = "Chiến lược tổng thể để phân giải tên miền"
"add" = "Thêm máy chủ" "add" = "Thêm máy chủ"
@@ -448,7 +475,7 @@
"poolSize" = "Kích thước bể bơi" "poolSize" = "Kích thước bể bơi"
[pages.settings.security] [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" "secret" = "Mã thông báo bí mật"
"loginSecurity" = "Bảo mật đăng nhập" "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" "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!" "status" = "✅ Bot hoạt động bình thường!"
"usage" = "❗ Vui lòng cung cấp văn bản để tìm kiếm!" "usage" = "❗ Vui lòng cung cấp văn bản để tìm kiếm!"
"getID" = "🆔 ID của bạn: <code>{{ .ID }}</code>" "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>" "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!" "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>." "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." "xrayNotRunning" = "❗ Xray Core không chạy."

View File

@@ -61,6 +61,10 @@
"secAlertPanelURI" = "面板默认 URI 路径不安全。请配置复杂的 URI 路径。" "secAlertPanelURI" = "面板默认 URI 路径不安全。请配置复杂的 URI 路径。"
"secAlertSubURI" = "订阅默认 URI 路径不安全。请配置复杂的 URI 路径。" "secAlertSubURI" = "订阅默认 URI 路径不安全。请配置复杂的 URI 路径。"
"secAlertSubJsonURI" = "订阅 JSON 默认 URI 路径不安全。请配置复杂的 URI 路径。" "secAlertSubJsonURI" = "订阅 JSON 默认 URI 路径不安全。请配置复杂的 URI 路径。"
"emptyDnsDesc" = "未添加DNS服务器。"
"emptyFakeDnsDesc" = "未添加Fake DNS服务器。"
"emptyBalancersDesc" = "未添加负载均衡器。"
"emptyReverseDesc" = "未添加反向代理。"
[menu] [menu]
"dashboard" = "系统状态" "dashboard" = "系统状态"
@@ -109,9 +113,10 @@
"config" = "配置" "config" = "配置"
"backup" = "备份和恢复" "backup" = "备份和恢复"
"backupTitle" = "备份和恢复数据库" "backupTitle" = "备份和恢复数据库"
"backupDescription" = "恢复数据库之前建议进行备份"
"exportDatabase" = "备份" "exportDatabase" = "备份"
"exportDatabaseDesc" = "点击下载包含当前数据库备份的 .db 文件到您的设备。"
"importDatabase" = "恢复" "importDatabase" = "恢复"
"importDatabaseDesc" = "点击选择并上传设备中的 .db 文件以从备份恢复数据库。"
[pages.inbounds] [pages.inbounds]
"title" = "入站列表" "title" = "入站列表"
@@ -190,9 +195,6 @@
[pages.client] [pages.client]
"add" = "添加客户端" "add" = "添加客户端"
"groupAdd" = "添加订阅用户"
"isGroupEdit" = "群组编辑"
"isGroupEditDesc" = "所有具有相同订阅的客户端均被编辑"
"edit" = "编辑客户端" "edit" = "编辑客户端"
"submitAdd" = "添加客户端" "submitAdd" = "添加客户端"
"submitEdit" = "保存修改" "submitEdit" = "保存修改"
@@ -290,8 +292,6 @@
"subSettings" = "订阅设置" "subSettings" = "订阅设置"
"subEnable" = "启用订阅服务" "subEnable" = "启用订阅服务"
"subEnableDesc" = "启用订阅服务功能" "subEnableDesc" = "启用订阅服务功能"
"subSyncEnable" = "启用订阅同步"
"subSyncEnableDesc" = "具有相同订阅的客户端的流量将每 10 秒同步一次。"
"subListen" = "监听 IP" "subListen" = "监听 IP"
"subListenDesc" = "订阅服务监听的 IP 地址(留空表示监听所有 IP" "subListenDesc" = "订阅服务监听的 IP 地址(留空表示监听所有 IP"
"subPort" = "监听端口" "subPort" = "监听端口"
@@ -312,6 +312,10 @@
"subShowInfoDesc" = "客户端应用中将显示剩余流量和日期信息" "subShowInfoDesc" = "客户端应用中将显示剩余流量和日期信息"
"subURI" = "反向代理 URI" "subURI" = "反向代理 URI"
"subURIDesc" = "用于代理后面的订阅 URL 的 URI 路径" "subURIDesc" = "用于代理后面的订阅 URL 的 URI 路径"
"externalTrafficInformEnable" = "外部交通通知"
"externalTrafficInformEnableDesc" = "每次流量更新时通知外部 API"
"externalTrafficInformURI" = "外部流量通知 URI"
"externalTrafficInformURIDesc" = "流量更新将发送到此 URI"
"fragment" = "分片" "fragment" = "分片"
"fragmentDesc" = "启用 TLS hello 数据包分片" "fragmentDesc" = "启用 TLS hello 数据包分片"
"fragmentSett" = "设置" "fragmentSett" = "设置"
@@ -322,7 +326,15 @@
"muxSett" = "复用器设置" "muxSett" = "复用器设置"
"direct" = "直接连接" "direct" = "直接连接"
"directDesc" = "直接与特定国家的域或IP范围建立连接" "directDesc" = "直接与特定国家的域或IP范围建立连接"
"notifications" = "通知"
"certs" = "证书"
"externalTraffic" = "外部流量"
"dateAndTime" = "日期和时间"
"proxyAndServer" = "代理和服务器"
"intervals" = "间隔"
"information" = "信息"
"language" = "语言"
"telegramBotLanguage" = "Telegram 机器人语言"
[pages.xray] [pages.xray]
"title" = "Xray 配置" "title" = "Xray 配置"
@@ -373,10 +385,17 @@
"errorLogDesc" = "错误日志的文件路径。特殊值 'none' 禁用错误日志" "errorLogDesc" = "错误日志的文件路径。特殊值 'none' 禁用错误日志"
"dnsLog" = "DNS 日志" "dnsLog" = "DNS 日志"
"dnsLogDesc" = "是否启用 DNS 查询日志" "dnsLogDesc" = "是否启用 DNS 查询日志"
"outboundTraffic" = "出站流量"
"outboundTrafficDesc" = "是否启用出站流量"
"maskAddress" = "隐藏地址" "maskAddress" = "隐藏地址"
"maskAddressDesc" = "IP 地址掩码,启用时会自动替换日志中出现的 IP 地址。" "maskAddressDesc" = "IP 地址掩码,启用时会自动替换日志中出现的 IP 地址。"
"statistics" = "统计"
"statsInboundUplink" = "入站上传统计"
"statsInboundUplinkDesc" = "启用所有入站代理的上行流量统计收集。"
"statsInboundDownlink" = "入站下载统计"
"statsInboundDownlinkDesc" = "启用所有入站代理的下行流量统计收集。"
"statsOutboundUplink" = "出站上传统计"
"statsOutboundUplinkDesc" = "启用所有出站代理的上行流量统计收集。"
"statsOutboundDownlink" = "出站下载统计"
"statsOutboundDownlinkDesc" = "启用所有出站代理的下行流量统计收集。"
[pages.xray.rules] [pages.xray.rules]
"first" = "置顶" "first" = "置顶"
@@ -434,6 +453,14 @@
"enableDesc" = "启用内置 DNS 服务器" "enableDesc" = "启用内置 DNS 服务器"
"tag" = "DNS 入站标签" "tag" = "DNS 入站标签"
"tagDesc" = "此标签将在路由规则中可用作入站标签" "tagDesc" = "此标签将在路由规则中可用作入站标签"
"clientIp" = "客户端IP"
"clientIpDesc" = "用于在DNS查询期间通知服务器指定的IP位置"
"disableCache" = "禁用缓存"
"disableCacheDesc" = "禁用DNS缓存"
"disableFallback" = "禁用回退"
"disableFallbackDesc" = "禁用回退DNS查询"
"disableFallbackIfMatch" = "匹配时禁用回退"
"disableFallbackIfMatchDesc" = "当DNS服务器的匹配域名列表命中时禁用回退DNS查询"
"strategy" = "查询策略" "strategy" = "查询策略"
"strategyDesc" = "解析域名的总体策略" "strategyDesc" = "解析域名的总体策略"
"add" = "添加服务器" "add" = "添加服务器"
@@ -448,7 +475,7 @@
"poolSize" = "池大小" "poolSize" = "池大小"
[pages.settings.security] [pages.settings.security]
"admin" = "管理员" "admin" = "管理员凭据"
"secret" = "安全令牌" "secret" = "安全令牌"
"loginSecurity" = "登录安全" "loginSecurity" = "登录安全"
"loginSecurityDesc" = "添加额外的身份验证以提高安全性" "loginSecurityDesc" = "添加额外的身份验证以提高安全性"
@@ -491,9 +518,9 @@
"status" = "✅ 机器人正常运行!" "status" = "✅ 机器人正常运行!"
"usage" = "❗ 请输入要搜索的文本!" "usage" = "❗ 请输入要搜索的文本!"
"getID" = "🆔 您的 ID 为:<code>{{ .ID }}</code>" "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>" "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" = "✅ 操作成功!" "restartSuccess" = "✅ 操作成功!"
"restartFailed" = "❗ 操作错误。\r\n\r\n<code>错误: {{ .Error }}</code>." "restartFailed" = "❗ 操作错误。\r\n\r\n<code>错误: {{ .Error }}</code>."
"xrayNotRunning" = "❗ Xray Core 未运行。" "xrayNotRunning" = "❗ Xray Core 未运行。"

View File

@@ -61,6 +61,10 @@
"secAlertPanelURI" = "面板預設 URI 路徑不安全。請配置複雜的 URI 路徑。" "secAlertPanelURI" = "面板預設 URI 路徑不安全。請配置複雜的 URI 路徑。"
"secAlertSubURI" = "訂閱預設 URI 路徑不安全。請配置複雜的 URI 路徑。" "secAlertSubURI" = "訂閱預設 URI 路徑不安全。請配置複雜的 URI 路徑。"
"secAlertSubJsonURI" = "訂閱 JSON 預設 URI 路徑不安全。請配置複雜的 URI 路徑。" "secAlertSubJsonURI" = "訂閱 JSON 預設 URI 路徑不安全。請配置複雜的 URI 路徑。"
"emptyDnsDesc" = "未添加DNS伺服器。"
"emptyFakeDnsDesc" = "未添加Fake DNS伺服器。"
"emptyBalancersDesc" = "未添加負載平衡器。"
"emptyReverseDesc" = "未添加反向代理。"
[menu] [menu]
"dashboard" = "系統狀態" "dashboard" = "系統狀態"
@@ -109,9 +113,10 @@
"config" = "配置" "config" = "配置"
"backup" = "備份和恢復" "backup" = "備份和恢復"
"backupTitle" = "備份和恢復資料庫" "backupTitle" = "備份和恢復資料庫"
"backupDescription" = "恢復資料庫之前建議進行備份"
"exportDatabase" = "備份" "exportDatabase" = "備份"
"exportDatabaseDesc" = "點擊下載包含當前資料庫備份的 .db 文件到您的設備。"
"importDatabase" = "恢復" "importDatabase" = "恢復"
"importDatabaseDesc" = "點擊選擇並上傳設備中的 .db 文件以從備份恢復資料庫。"
[pages.inbounds] [pages.inbounds]
"title" = "入站列表" "title" = "入站列表"
@@ -190,9 +195,6 @@
[pages.client] [pages.client]
"add" = "新增客戶端" "add" = "新增客戶端"
"groupAdd" = "新增訂閱使用者"
"isGroupEdit" = "群組編輯"
"isGroupEditDesc" = "所有具有相同訂閱的用戶端都被編輯"
"edit" = "編輯客戶端" "edit" = "編輯客戶端"
"submitAdd" = "新增客戶端" "submitAdd" = "新增客戶端"
"submitEdit" = "儲存修改" "submitEdit" = "儲存修改"
@@ -290,8 +292,6 @@
"subSettings" = "訂閱設定" "subSettings" = "訂閱設定"
"subEnable" = "啟用訂閱服務" "subEnable" = "啟用訂閱服務"
"subEnableDesc" = "啟用訂閱服務功能" "subEnableDesc" = "啟用訂閱服務功能"
"subSyncEnable" = "啟用訂閱同步"
"subSyncEnableDesc" = "來自具有相同訂閱的客戶端的流量將每 10 秒同步一次。"
"subListen" = "監聽 IP" "subListen" = "監聽 IP"
"subListenDesc" = "訂閱服務監聽的 IP 地址(留空表示監聽所有 IP" "subListenDesc" = "訂閱服務監聽的 IP 地址(留空表示監聽所有 IP"
"subPort" = "監聽埠" "subPort" = "監聽埠"
@@ -312,6 +312,10 @@
"subShowInfoDesc" = "客戶端應用中將顯示剩餘流量和日期資訊" "subShowInfoDesc" = "客戶端應用中將顯示剩餘流量和日期資訊"
"subURI" = "反向代理 URI" "subURI" = "反向代理 URI"
"subURIDesc" = "用於代理後面的訂閱 URL 的 URI 路徑" "subURIDesc" = "用於代理後面的訂閱 URL 的 URI 路徑"
"externalTrafficInformEnable" = "外部交通通知"
"externalTrafficInformEnableDesc" = "每次流量更新時通知外部 API"
"externalTrafficInformURI" = "外部流量通知 URI"
"externalTrafficInformURIDesc" = "流量更新將會傳送到此 URI"
"fragment" = "分片" "fragment" = "分片"
"fragmentDesc" = "啟用 TLS hello 資料包分片" "fragmentDesc" = "啟用 TLS hello 資料包分片"
"fragmentSett" = "設定" "fragmentSett" = "設定"
@@ -322,7 +326,15 @@
"muxSett" = "複用器設定" "muxSett" = "複用器設定"
"direct" = "直接連線" "direct" = "直接連線"
"directDesc" = "直接與特定國家的域或IP範圍建立連線" "directDesc" = "直接與特定國家的域或IP範圍建立連線"
"notifications" = "通知"
"certs" = "證書"
"externalTraffic" = "外部流量"
"dateAndTime" = "日期和時間"
"proxyAndServer" = "代理和伺服器"
"intervals" = "間隔"
"information" = "資訊"
"language" = "語言"
"telegramBotLanguage" = "Telegram 機器人語言"
[pages.xray] [pages.xray]
"title" = "Xray 配置" "title" = "Xray 配置"
@@ -373,10 +385,17 @@
"errorLogDesc" = "錯誤日誌的檔案路徑。特殊值 'none' 禁用錯誤日誌" "errorLogDesc" = "錯誤日誌的檔案路徑。特殊值 'none' 禁用錯誤日誌"
"dnsLog" = "DNS 日誌" "dnsLog" = "DNS 日誌"
"dnsLogDesc" = "是否啟用 DNS 查詢日誌" "dnsLogDesc" = "是否啟用 DNS 查詢日誌"
"outboundTraffic" = "出站流量"
"outboundTrafficDesc" = "是否啟用出站流量"
"maskAddress" = "隱藏地址" "maskAddress" = "隱藏地址"
"maskAddressDesc" = "IP 地址掩碼,啟用時會自動替換日誌中出現的 IP 地址。" "maskAddressDesc" = "IP 地址掩碼,啟用時會自動替換日誌中出現的 IP 地址。"
"statistics" = "統計"
"statsInboundUplink" = "入站上傳統計"
"statsInboundUplinkDesc" = "啟用所有入站代理的上行流量統計收集。"
"statsInboundDownlink" = "入站下載統計"
"statsInboundDownlinkDesc" = "啟用所有入站代理的下行流量統計收集。"
"statsOutboundUplink" = "出站上傳統計"
"statsOutboundUplinkDesc" = "啟用所有出站代理的上行流量統計收集。"
"statsOutboundDownlink" = "出站下載統計"
"statsOutboundDownlinkDesc" = "啟用所有出站代理的下行流量統計收集。"
[pages.xray.rules] [pages.xray.rules]
"first" = "置頂" "first" = "置頂"
@@ -434,6 +453,14 @@
"enableDesc" = "啟用內建 DNS 伺服器" "enableDesc" = "啟用內建 DNS 伺服器"
"tag" = "DNS 入站標籤" "tag" = "DNS 入站標籤"
"tagDesc" = "此標籤將在路由規則中可用作入站標籤" "tagDesc" = "此標籤將在路由規則中可用作入站標籤"
"clientIp" = "客戶端IP"
"clientIpDesc" = "用於在DNS查詢期間通知伺服器指定的IP位置"
"disableCache" = "禁用快取"
"disableCacheDesc" = "禁用DNS快取"
"disableFallback" = "禁用回退"
"disableFallbackDesc" = "禁用回退DNS查詢"
"disableFallbackIfMatch" = "匹配時禁用回退"
"disableFallbackIfMatchDesc" = "當DNS伺服器的匹配域名列表命中時禁用回退DNS查詢"
"strategy" = "查詢策略" "strategy" = "查詢策略"
"strategyDesc" = "解析域名的總體策略" "strategyDesc" = "解析域名的總體策略"
"add" = "新增伺服器" "add" = "新增伺服器"
@@ -448,7 +475,7 @@
"poolSize" = "池大小" "poolSize" = "池大小"
[pages.settings.security] [pages.settings.security]
"admin" = "管理員" "admin" = "管理員憑證"
"secret" = "安全令牌" "secret" = "安全令牌"
"loginSecurity" = "登入安全" "loginSecurity" = "登入安全"
"loginSecurityDesc" = "新增額外的身份驗證以提高安全性" "loginSecurityDesc" = "新增額外的身份驗證以提高安全性"
@@ -491,9 +518,9 @@
"status" = "✅ 機器人正常執行!" "status" = "✅ 機器人正常執行!"
"usage" = "❗ 請輸入要搜尋的文字!" "usage" = "❗ 請輸入要搜尋的文字!"
"getID" = "🆔 您的 ID 為:<code>{{ .ID }}</code>" "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>" "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" = "✅ 操作成功!" "restartSuccess" = "✅ 操作成功!"
"restartFailed" = "❗ 操作錯誤。\r\n\r\n<code>錯誤: {{ .Error }}</code>." "restartFailed" = "❗ 操作錯誤。\r\n\r\n<code>錯誤: {{ .Error }}</code>."
"xrayNotRunning" = "❗ Xray Core 未運行。" "xrayNotRunning" = "❗ Xray Core 未運行。"

View File

@@ -260,13 +260,6 @@ func (s *Server) startTask() {
s.cron.AddJob("@every 10s", job.NewXrayTrafficJob()) 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 // check client ips from log file every 10 sec
s.cron.AddJob("@every 10s", job.NewCheckClientIpJob()) s.cron.AddJob("@every 10s", job.NewCheckClientIpJob())

34
x-ui.sh
View File

@@ -87,6 +87,10 @@ elif [[ "${release}" == "ol" ]]; then
if [[ ${os_version} -lt 8 ]]; then if [[ ${os_version} -lt 8 ]]; then
echo -e "${red} Please use Oracle Linux 8 or higher ${plain}\n" && exit 1 echo -e "${red} Please use Oracle Linux 8 or higher ${plain}\n" && exit 1
fi 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 else
echo -e "${red}Your operating system is not supported by this script.${plain}\n" 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:" echo "Please ensure you are using one of the following supported operating systems:"
@@ -104,6 +108,7 @@ else
echo "- Oracle Linux 8+" echo "- Oracle Linux 8+"
echo "- OpenSUSE Tumbleweed" echo "- OpenSUSE Tumbleweed"
echo "- Amazon Linux 2023" echo "- Amazon Linux 2023"
echo "- Virtuozzo Linux 8+"
exit 1 exit 1
fi fi
@@ -180,7 +185,7 @@ update_menu() {
return 0 return 0
fi 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/local/x-ui/x-ui.sh
chmod +x /usr/bin/x-ui chmod +x /usr/bin/x-ui
@@ -280,7 +285,7 @@ reset_webbasepath() {
# Apply the new web base path setting # Apply the new web base path setting
/usr/local/x-ui/x-ui setting -webBasePath "${config_webBasePath}" >/dev/null 2>&1 /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 "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}" echo -e "${green}Please use the new web base path to access the panel.${plain}"
restart restart
@@ -440,7 +445,7 @@ show_log() {
1) 1)
journalctl -u x-ui -e --no-pager -f -p debug journalctl -u x-ui -e --no-pager -f -p debug
if [[ $# == 0 ]]; then if [[ $# == 0 ]]; then
before_show_menu before_show_menu
fi fi
;; ;;
2) 2)
@@ -458,9 +463,9 @@ show_log() {
show_banlog() { show_banlog() {
local system_log="/var/log/fail2ban.log" local system_log="/var/log/fail2ban.log"
echo -e "${green}Checking ban logs...${plain}\n" echo -e "${green}Checking ban logs...${plain}\n"
if ! systemctl is-active --quiet fail2ban; then if ! systemctl is-active --quiet fail2ban; then
echo -e "${red}Fail2ban service is not running!${plain}\n" echo -e "${red}Fail2ban service is not running!${plain}\n"
return 1 return 1
@@ -504,7 +509,7 @@ bbr_menu() {
disable_bbr disable_bbr
bbr_menu bbr_menu
;; ;;
*) *)
echo -e "${red}Invalid option. Please select a valid number.${plain}\n" echo -e "${red}Invalid option. Please select a valid number.${plain}\n"
bbr_menu bbr_menu
;; ;;
@@ -547,7 +552,7 @@ enable_bbr() {
centos | almalinux | rocky | ol) centos | almalinux | rocky | ol)
yum -y update && yum -y install ca-certificates yum -y update && yum -y install ca-certificates
;; ;;
fedora | amzn) fedora | amzn | virtuozzo)
dnf -y update && dnf -y install ca-certificates dnf -y update && dnf -y install ca-certificates
;; ;;
arch | manjaro | parch) arch | manjaro | parch)
@@ -575,14 +580,14 @@ enable_bbr() {
} }
update_shell() { 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 if [[ $? != 0 ]]; then
echo "" echo ""
LOGE "Failed to download script, Please check whether the machine can connect Github" LOGE "Failed to download script, Please check whether the machine can connect Github"
before_show_menu before_show_menu
else else
chmod +x /usr/bin/x-ui 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 before_show_menu
fi fi
} }
@@ -724,7 +729,7 @@ firewall_menu() {
ufw status verbose ufw status verbose
firewall_menu firewall_menu
;; ;;
*) *)
echo -e "${red}Invalid option. Please select a valid number.${plain}\n" echo -e "${red}Invalid option. Please select a valid number.${plain}\n"
firewall_menu firewall_menu
;; ;;
@@ -873,7 +878,6 @@ delete_ports() {
fi fi
} }
update_geo() { update_geo() {
echo -e "${green}\t1.${plain} Loyalsoldier (geoip.dat, geosite.dat)" echo -e "${green}\t1.${plain} Loyalsoldier (geoip.dat, geosite.dat)"
echo -e "${green}\t2.${plain} chocolate4u (geoip_IR.dat, geosite_IR.dat)" echo -e "${green}\t2.${plain} chocolate4u (geoip_IR.dat, geosite_IR.dat)"
@@ -1069,7 +1073,7 @@ ssl_cert_issue() {
centos | almalinux | rocky | ol) centos | almalinux | rocky | ol)
yum -y update && yum -y install socat yum -y update && yum -y install socat
;; ;;
fedora | amzn) fedora | amzn | virtuozzo)
dnf -y update && dnf -y install socat dnf -y update && dnf -y install socat
;; ;;
arch | manjaro | parch) arch | manjaro | parch)
@@ -1537,7 +1541,7 @@ install_iplimit() {
yum update -y && yum install epel-release -y yum update -y && yum install epel-release -y
yum -y install fail2ban yum -y install fail2ban
;; ;;
fedora | amzn) fedora | amzn | virtuozzo)
dnf -y update && dnf -y install fail2ban dnf -y update && dnf -y install fail2ban
;; ;;
arch | manjaro | parch) arch | manjaro | parch)
@@ -1618,7 +1622,7 @@ remove_iplimit() {
yum remove fail2ban -y yum remove fail2ban -y
yum autoremove -y yum autoremove -y
;; ;;
fedora | amzn) fedora | amzn | virtuozzo)
dnf remove fail2ban -y dnf remove fail2ban -y
dnf autoremove -y dnf autoremove -y
;; ;;
@@ -1912,4 +1916,4 @@ if [[ $# > 0 ]]; then
esac esac
else else
show_menu show_menu
fi fi

View File

@@ -2,9 +2,9 @@ package xray
type ClientTraffic struct { type ClientTraffic struct {
Id int `json:"id" form:"id" gorm:"primaryKey;autoIncrement"` 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"` 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"` Up int64 `json:"up" form:"up"`
Down int64 `json:"down" form:"down"` Down int64 `json:"down" form:"down"`
ExpiryTime int64 `json:"expiryTime" form:"expiryTime"` ExpiryTime int64 `json:"expiryTime" form:"expiryTime"`

View File

@@ -32,32 +32,39 @@ func (lw *LogWriter) Write(m []byte) (n int, err error) {
return len(m), nil return len(m), nil
} }
regex := regexp.MustCompile(`^(\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2}) \[([^\]]+)\] (.+)$`) regex := regexp.MustCompile(`^(\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2}\.\d{6}) \[([^\]]+)\] (.+)$`)
messages := strings.Split(message, "\n") messages := strings.SplitSeq(message, "\n")
for _, msg := range messages { for msg := range messages {
matches := regex.FindStringSubmatch(msg) matches := regex.FindStringSubmatch(msg)
if len(matches) > 3 { if len(matches) > 3 {
level := matches[2] level := matches[2]
msgBody := matches[3] msgBody := matches[3]
// Map the level to the appropriate logger function if strings.Contains(strings.ToLower(msgBody), "failed") {
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) logger.Error("XRAY: " + msgBody)
default: } else {
logger.Debug("XRAY: " + msg) 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 = "" lw.lastLine = ""
} else if msg != "" { } 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 lw.lastLine = msg
} }
} }