mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-03-16 07:52:31 +00:00
Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2ffde55f8f | ||
|
|
6e5ed881f2 | ||
|
|
d52c50fd9e | ||
|
|
fa5fb927c1 | ||
|
|
f7198c4c2f | ||
|
|
21e7d45b54 | ||
|
|
db62a07fb8 | ||
|
|
e3120c4028 | ||
|
|
7ae855e7c9 | ||
|
|
b9307c6c9c | ||
|
|
d30cdbf49a | ||
|
|
cac00224db | ||
|
|
b68f0a206c | ||
|
|
0bde51b91e | ||
|
|
280a22b57d | ||
|
|
315d852087 | ||
|
|
6a0d2e0a29 |
2
.github/workflows/docker.yml
vendored
2
.github/workflows/docker.yml
vendored
@@ -7,7 +7,7 @@ on:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
||||
- 386
|
||||
- armv5
|
||||
- s390x
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
@@ -244,7 +244,7 @@ location /sub {
|
||||
|
||||
## SO Recomendados
|
||||
|
||||
- Ubuntu 20.04+
|
||||
- Ubuntu 22.04+
|
||||
- Debian 11+
|
||||
- CentOS 8+
|
||||
- OpenEuler 22.03+
|
||||
@@ -258,6 +258,7 @@ location /sub {
|
||||
- Oracle Linux 8+
|
||||
- OpenSUSE Tubleweed
|
||||
- Amazon Linux 2023
|
||||
- Virtuozzo Linux 8+
|
||||
- Windows x64
|
||||
|
||||
## Arquitecturas y Dispositivos Compatibles
|
||||
|
||||
@@ -245,7 +245,7 @@ location /sub {
|
||||
|
||||
## سیستمعاملهای توصیه شده
|
||||
|
||||
- Ubuntu 20.04+
|
||||
- Ubuntu 22.04+
|
||||
- Debian 11+
|
||||
- CentOS 8+
|
||||
- OpenEuler 22.03+
|
||||
@@ -259,6 +259,7 @@ location /sub {
|
||||
- Oracle Linux 8+
|
||||
- OpenSUSE Tubleweed
|
||||
- Amazon Linux 2023
|
||||
- Virtuozzo Linux 8+
|
||||
- Windows x64
|
||||
|
||||
## معماریها و دستگاههای پشتیبانی شده
|
||||
|
||||
@@ -249,7 +249,7 @@ location /sub {
|
||||
|
||||
## Recommended OS
|
||||
|
||||
- Ubuntu 20.04+
|
||||
- Ubuntu 22.04+
|
||||
- Debian 11+
|
||||
- CentOS 8+
|
||||
- OpenEuler 22.03+
|
||||
@@ -263,6 +263,7 @@ location /sub {
|
||||
- Oracle Linux 8+
|
||||
- OpenSUSE Tubleweed
|
||||
- Amazon Linux 2023
|
||||
- Virtuozzo Linux 8+
|
||||
- Windows x64
|
||||
|
||||
## Supported Architectures and Devices
|
||||
|
||||
@@ -248,7 +248,7 @@ location /sub {
|
||||
|
||||
## Рекомендуемые ОС
|
||||
|
||||
- Ubuntu 20.04+
|
||||
- Ubuntu 22.04+
|
||||
- Debian 11+
|
||||
- CentOS 8+
|
||||
- OpenEuler 22.03+
|
||||
@@ -262,6 +262,7 @@ location /sub {
|
||||
- Oracle Linux 8+
|
||||
- OpenSUSE Tubleweed
|
||||
- Amazon Linux 2023
|
||||
- Virtuozzo Linux 8+
|
||||
- Windows x64
|
||||
|
||||
## Поддерживаемые архитектуры и устройства
|
||||
|
||||
@@ -245,7 +245,7 @@ location /sub {
|
||||
|
||||
## 建议使用的操作系统
|
||||
|
||||
- Ubuntu 20.04+
|
||||
- Ubuntu 22.04+
|
||||
- Debian 11+
|
||||
- CentOS 8+
|
||||
- OpenEuler 22.03+
|
||||
@@ -259,6 +259,7 @@ location /sub {
|
||||
- Oracle Linux 8+
|
||||
- OpenSUSE Tubleweed
|
||||
- Amazon Linux 2023
|
||||
- Virtuozzo Linux 8+
|
||||
- Windows x64
|
||||
|
||||
## 支持的架构和设备
|
||||
|
||||
@@ -1 +1 @@
|
||||
2.5.5
|
||||
2.5.6
|
||||
@@ -26,7 +26,7 @@ const (
|
||||
)
|
||||
|
||||
func initModels() error {
|
||||
models := []interface{}{
|
||||
models := []any{
|
||||
&model.User{},
|
||||
&model.Inbound{},
|
||||
&model.OutboundTraffics{},
|
||||
|
||||
8
go.mod
8
go.mod
@@ -39,7 +39,7 @@ require (
|
||||
github.com/go-playground/validator/v10 v10.25.0 // indirect
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
|
||||
github.com/google/btree v1.1.3 // indirect
|
||||
github.com/google/pprof v0.0.0-20250302191652-9094ed2288e7 // indirect
|
||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e // indirect
|
||||
github.com/gorilla/context v1.1.2 // indirect
|
||||
github.com/gorilla/securecookie v1.1.2 // indirect
|
||||
github.com/gorilla/sessions v1.4.0 // indirect
|
||||
@@ -52,12 +52,12 @@ require (
|
||||
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20250303091104-876f3ea5145d // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.24 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.23.0 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.23.1 // indirect
|
||||
github.com/pires/go-proxyproto v0.8.0 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
|
||||
github.com/quic-go/qpack v0.5.1 // indirect
|
||||
@@ -93,7 +93,7 @@ require (
|
||||
golang.org/x/tools v0.31.0 // indirect
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
||||
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4 // indirect
|
||||
google.golang.org/protobuf v1.36.5 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
gvisor.dev/gvisor v0.0.0-20240320123526-dc6abceb7ff0 // indirect
|
||||
|
||||
16
go.sum
16
go.sum
@@ -67,8 +67,8 @@ github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/pprof v0.0.0-20250302191652-9094ed2288e7 h1:+J3r2e8+RsmN3vKfo75g0YSY61ms37qzPglu4p0sGro=
|
||||
github.com/google/pprof v0.0.0-20250302191652-9094ed2288e7/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
|
||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
|
||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/context v1.1.2 h1:WRkNAv2uoa03QNIc1A6u4O7DAGMUVoopZhkiXWA2V1o=
|
||||
@@ -99,8 +99,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
github.com/lufia/plan9stats v0.0.0-20250303091104-876f3ea5145d h1:fjMbDVUGsMQiVZnSQsmouYJvMdwsGiDipOZoN66v844=
|
||||
github.com/lufia/plan9stats v0.0.0-20250303091104-876f3ea5145d/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
|
||||
github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35 h1:PpXWgLPs+Fqr325bN2FD2ISlRRztXibcX6e8f5FR5Dc=
|
||||
github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
|
||||
@@ -116,8 +116,8 @@ github.com/mymmrac/telego v0.32.0 h1:4X8C1l3k+opkk86r95+eQE8DxiS2LYlR61L/G7yreDY
|
||||
github.com/mymmrac/telego v0.32.0/go.mod h1:qS6NaRhJgcuEEBEMVCV79S2xCAuHq9O+ixwfLuRW31M=
|
||||
github.com/nicksnyder/go-i18n/v2 v2.5.1 h1:IxtPxYsR9Gp60cGXjfuR/llTqV8aYMsC472zD0D1vHk=
|
||||
github.com/nicksnyder/go-i18n/v2 v2.5.1/go.mod h1:DrhgsSDZxoAfvVrBVLXoxZn/pN5TXqaDbq7ju94viiQ=
|
||||
github.com/onsi/ginkgo/v2 v2.23.0 h1:FA1xjp8ieYDzlgS5ABTpdUDB7wtngggONc8a7ku2NqQ=
|
||||
github.com/onsi/ginkgo/v2 v2.23.0/go.mod h1:zXTP6xIp3U8aVuXN8ENK9IXRaTjFnpVB9mGmaSRvxnM=
|
||||
github.com/onsi/ginkgo/v2 v2.23.1 h1:Ox0cOPv/t8RzKJUfDo9ZKtRvBOJY369sFJnl00CjqwY=
|
||||
github.com/onsi/ginkgo/v2 v2.23.1/go.mod h1:zXTP6xIp3U8aVuXN8ENK9IXRaTjFnpVB9mGmaSRvxnM=
|
||||
github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8=
|
||||
github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY=
|
||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88=
|
||||
@@ -244,8 +244,8 @@ golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeu
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uIfPMv78iAJGcPKDeqAFnaLBropIC4=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb h1:TLPQVbx1GJ8VKZxz52VAxl1EBgKXXbTiU9Fc5fZeLn4=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4 h1:iK2jbkWL86DXjEx0qiHcRE9dE4/Ahua5k6V8OWFb//c=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I=
|
||||
google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg=
|
||||
google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec=
|
||||
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
|
||||
|
||||
@@ -63,8 +63,8 @@ elif [[ "${release}" == "centos" ]]; then
|
||||
echo -e "${red} Please use CentOS 8 or higher ${plain}\n" && exit 1
|
||||
fi
|
||||
elif [[ "${release}" == "ubuntu" ]]; then
|
||||
if [[ ${os_version} -lt 2004 ]]; then
|
||||
echo -e "${red} Please use Ubuntu 20 or higher version!${plain}\n" && exit 1
|
||||
if [[ ${os_version} -lt 2204 ]]; then
|
||||
echo -e "${red} Please use Ubuntu 22 or higher version!${plain}\n" && exit 1
|
||||
fi
|
||||
elif [[ "${release}" == "fedora" ]]; then
|
||||
if [[ ${os_version} -lt 36 ]]; then
|
||||
@@ -97,7 +97,7 @@ elif [[ "${release}" == "virtuozzo" ]]; then
|
||||
else
|
||||
echo -e "${red}Your operating system is not supported by this script.${plain}\n"
|
||||
echo "Please ensure you are using one of the following supported operating systems:"
|
||||
echo "- Ubuntu 20.04+"
|
||||
echo "- Ubuntu 22.04+"
|
||||
echo "- Debian 11+"
|
||||
echo "- CentOS 8+"
|
||||
echo "- OpenEuler 22.03+"
|
||||
|
||||
@@ -47,52 +47,52 @@ func InitLogger(level logging.Level) {
|
||||
logger = newLogger
|
||||
}
|
||||
|
||||
func Debug(args ...interface{}) {
|
||||
func Debug(args ...any) {
|
||||
logger.Debug(args...)
|
||||
addToBuffer("DEBUG", fmt.Sprint(args...))
|
||||
}
|
||||
|
||||
func Debugf(format string, args ...interface{}) {
|
||||
func Debugf(format string, args ...any) {
|
||||
logger.Debugf(format, args...)
|
||||
addToBuffer("DEBUG", fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
func Info(args ...interface{}) {
|
||||
func Info(args ...any) {
|
||||
logger.Info(args...)
|
||||
addToBuffer("INFO", fmt.Sprint(args...))
|
||||
}
|
||||
|
||||
func Infof(format string, args ...interface{}) {
|
||||
func Infof(format string, args ...any) {
|
||||
logger.Infof(format, args...)
|
||||
addToBuffer("INFO", fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
func Notice(args ...interface{}) {
|
||||
func Notice(args ...any) {
|
||||
logger.Notice(args...)
|
||||
addToBuffer("NOTICE", fmt.Sprint(args...))
|
||||
}
|
||||
|
||||
func Noticef(format string, args ...interface{}) {
|
||||
func Noticef(format string, args ...any) {
|
||||
logger.Noticef(format, args...)
|
||||
addToBuffer("NOTICE", fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
func Warning(args ...interface{}) {
|
||||
func Warning(args ...any) {
|
||||
logger.Warning(args...)
|
||||
addToBuffer("WARNING", fmt.Sprint(args...))
|
||||
}
|
||||
|
||||
func Warningf(format string, args ...interface{}) {
|
||||
func Warningf(format string, args ...any) {
|
||||
logger.Warningf(format, args...)
|
||||
addToBuffer("WARNING", fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
func Error(args ...interface{}) {
|
||||
func Error(args ...any) {
|
||||
logger.Error(args...)
|
||||
addToBuffer("ERROR", fmt.Sprint(args...))
|
||||
}
|
||||
|
||||
func Errorf(format string, args ...interface{}) {
|
||||
func Errorf(format string, args ...any) {
|
||||
logger.Errorf(format, args...)
|
||||
addToBuffer("ERROR", fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
@@ -107,11 +107,16 @@ func (s *Server) initRouter() (*gin.Engine, error) {
|
||||
SubJsonRules = ""
|
||||
}
|
||||
|
||||
SubTitle, err := s.settingService.GetSubTitle()
|
||||
if err != nil {
|
||||
SubTitle = ""
|
||||
}
|
||||
|
||||
g := engine.Group("/")
|
||||
|
||||
s.sub = NewSUBController(
|
||||
g, LinksPath, JsonPath, Encrypt, ShowInfo, RemarkModel, SubUpdates,
|
||||
SubJsonFragment, SubJsonNoises, SubJsonMux, SubJsonRules)
|
||||
SubJsonFragment, SubJsonNoises, SubJsonMux, SubJsonRules, SubTitle)
|
||||
|
||||
return engine, nil
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
)
|
||||
|
||||
type SUBController struct {
|
||||
subTitle string
|
||||
subPath string
|
||||
subJsonPath string
|
||||
subEncrypt bool
|
||||
@@ -30,9 +31,11 @@ func NewSUBController(
|
||||
jsonNoise string,
|
||||
jsonMux string,
|
||||
jsonRules string,
|
||||
subTitle string,
|
||||
) *SUBController {
|
||||
sub := NewSubService(showInfo, rModel)
|
||||
a := &SUBController{
|
||||
subTitle: subTitle,
|
||||
subPath: subPath,
|
||||
subJsonPath: jsonPath,
|
||||
subEncrypt: encrypt,
|
||||
@@ -82,7 +85,7 @@ func (a *SUBController) subs(c *gin.Context) {
|
||||
// Add headers
|
||||
c.Writer.Header().Set("Subscription-Userinfo", header)
|
||||
c.Writer.Header().Set("Profile-Update-Interval", a.updateInterval)
|
||||
c.Writer.Header().Set("Profile-Title", subId)
|
||||
c.Writer.Header().Set("Profile-Title", a.subTitle)
|
||||
|
||||
if a.subEncrypt {
|
||||
c.String(200, base64.StdEncoding.EncodeToString([]byte(result)))
|
||||
@@ -116,7 +119,7 @@ func (a *SUBController) subJsons(c *gin.Context) {
|
||||
// Add headers
|
||||
c.Writer.Header().Set("Subscription-Userinfo", header)
|
||||
c.Writer.Header().Set("Profile-Update-Interval", a.updateInterval)
|
||||
c.Writer.Header().Set("Profile-Title", subId)
|
||||
c.Writer.Header().Set("Profile-Title", a.subTitle)
|
||||
|
||||
c.String(200, jsonSub)
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ import (
|
||||
var defaultJson string
|
||||
|
||||
type SubJsonService struct {
|
||||
configJson map[string]interface{}
|
||||
configJson map[string]any
|
||||
defaultOutbounds []json_util.RawMessage
|
||||
fragment string
|
||||
noises string
|
||||
@@ -29,10 +29,10 @@ type SubJsonService struct {
|
||||
}
|
||||
|
||||
func NewSubJsonService(fragment string, noises string, mux string, rules string, subService *SubService) *SubJsonService {
|
||||
var configJson map[string]interface{}
|
||||
var configJson map[string]any
|
||||
var defaultOutbounds []json_util.RawMessage
|
||||
json.Unmarshal([]byte(defaultJson), &configJson)
|
||||
if outboundSlices, ok := configJson["outbounds"].([]interface{}); ok {
|
||||
if outboundSlices, ok := configJson["outbounds"].([]any); ok {
|
||||
for _, defaultOutbound := range outboundSlices {
|
||||
jsonBytes, _ := json.Marshal(defaultOutbound)
|
||||
defaultOutbounds = append(defaultOutbounds, jsonBytes)
|
||||
@@ -40,9 +40,9 @@ func NewSubJsonService(fragment string, noises string, mux string, rules string,
|
||||
}
|
||||
|
||||
if rules != "" {
|
||||
var newRules []interface{}
|
||||
routing, _ := configJson["routing"].(map[string]interface{})
|
||||
defaultRules, _ := routing["rules"].([]interface{})
|
||||
var newRules []any
|
||||
routing, _ := configJson["routing"].(map[string]any)
|
||||
defaultRules, _ := routing["rules"].([]any)
|
||||
json.Unmarshal([]byte(rules), &newRules)
|
||||
defaultRules = append(newRules, defaultRules...)
|
||||
routing["rules"] = defaultRules
|
||||
@@ -148,10 +148,10 @@ func (s *SubJsonService) getConfig(inbound *model.Inbound, client model.Client,
|
||||
var newJsonArray []json_util.RawMessage
|
||||
stream := s.streamData(inbound.StreamSettings)
|
||||
|
||||
externalProxies, ok := stream["externalProxy"].([]interface{})
|
||||
externalProxies, ok := stream["externalProxy"].([]any)
|
||||
if !ok || len(externalProxies) == 0 {
|
||||
externalProxies = []interface{}{
|
||||
map[string]interface{}{
|
||||
externalProxies = []any{
|
||||
map[string]any{
|
||||
"forceTls": "same",
|
||||
"dest": host,
|
||||
"port": float64(inbound.Port),
|
||||
@@ -163,7 +163,7 @@ func (s *SubJsonService) getConfig(inbound *model.Inbound, client model.Client,
|
||||
delete(stream, "externalProxy")
|
||||
|
||||
for _, ep := range externalProxies {
|
||||
extPrxy := ep.(map[string]interface{})
|
||||
extPrxy := ep.(map[string]any)
|
||||
inbound.Listen = extPrxy["dest"].(string)
|
||||
inbound.Port = int(extPrxy["port"].(float64))
|
||||
newStream := stream
|
||||
@@ -171,7 +171,7 @@ func (s *SubJsonService) getConfig(inbound *model.Inbound, client model.Client,
|
||||
case "tls":
|
||||
if newStream["security"] != "tls" {
|
||||
newStream["security"] = "tls"
|
||||
newStream["tslSettings"] = map[string]interface{}{}
|
||||
newStream["tslSettings"] = map[string]any{}
|
||||
}
|
||||
case "none":
|
||||
if newStream["security"] != "none" {
|
||||
@@ -191,7 +191,7 @@ func (s *SubJsonService) getConfig(inbound *model.Inbound, client model.Client,
|
||||
}
|
||||
|
||||
newOutbounds = append(newOutbounds, s.defaultOutbounds...)
|
||||
newConfigJson := make(map[string]interface{})
|
||||
newConfigJson := make(map[string]any)
|
||||
for key, value := range s.configJson {
|
||||
newConfigJson[key] = value
|
||||
}
|
||||
@@ -205,14 +205,14 @@ func (s *SubJsonService) getConfig(inbound *model.Inbound, client model.Client,
|
||||
return newJsonArray
|
||||
}
|
||||
|
||||
func (s *SubJsonService) streamData(stream string) map[string]interface{} {
|
||||
var streamSettings map[string]interface{}
|
||||
func (s *SubJsonService) streamData(stream string) map[string]any {
|
||||
var streamSettings map[string]any
|
||||
json.Unmarshal([]byte(stream), &streamSettings)
|
||||
security, _ := streamSettings["security"].(string)
|
||||
if security == "tls" {
|
||||
streamSettings["tlsSettings"] = s.tlsData(streamSettings["tlsSettings"].(map[string]interface{}))
|
||||
streamSettings["tlsSettings"] = s.tlsData(streamSettings["tlsSettings"].(map[string]any))
|
||||
} else if security == "reality" {
|
||||
streamSettings["realitySettings"] = s.realityData(streamSettings["realitySettings"].(map[string]interface{}))
|
||||
streamSettings["realitySettings"] = s.realityData(streamSettings["realitySettings"].(map[string]any))
|
||||
}
|
||||
delete(streamSettings, "sockopt")
|
||||
|
||||
@@ -233,17 +233,17 @@ func (s *SubJsonService) streamData(stream string) map[string]interface{} {
|
||||
return streamSettings
|
||||
}
|
||||
|
||||
func (s *SubJsonService) removeAcceptProxy(setting interface{}) map[string]interface{} {
|
||||
netSettings, ok := setting.(map[string]interface{})
|
||||
func (s *SubJsonService) removeAcceptProxy(setting any) map[string]any {
|
||||
netSettings, ok := setting.(map[string]any)
|
||||
if ok {
|
||||
delete(netSettings, "acceptProxyProtocol")
|
||||
}
|
||||
return netSettings
|
||||
}
|
||||
|
||||
func (s *SubJsonService) tlsData(tData map[string]interface{}) map[string]interface{} {
|
||||
tlsData := make(map[string]interface{}, 1)
|
||||
tlsClientSettings, _ := tData["settings"].(map[string]interface{})
|
||||
func (s *SubJsonService) tlsData(tData map[string]any) map[string]any {
|
||||
tlsData := make(map[string]any, 1)
|
||||
tlsClientSettings, _ := tData["settings"].(map[string]any)
|
||||
|
||||
tlsData["serverName"] = tData["serverName"]
|
||||
tlsData["alpn"] = tData["alpn"]
|
||||
@@ -256,9 +256,9 @@ func (s *SubJsonService) tlsData(tData map[string]interface{}) map[string]interf
|
||||
return tlsData
|
||||
}
|
||||
|
||||
func (s *SubJsonService) realityData(rData map[string]interface{}) map[string]interface{} {
|
||||
rltyData := make(map[string]interface{}, 1)
|
||||
rltyClientSettings, _ := rData["settings"].(map[string]interface{})
|
||||
func (s *SubJsonService) realityData(rData map[string]any) map[string]any {
|
||||
rltyData := make(map[string]any, 1)
|
||||
rltyClientSettings, _ := rData["settings"].(map[string]any)
|
||||
|
||||
rltyData["show"] = false
|
||||
rltyData["publicKey"] = rltyClientSettings["publicKey"]
|
||||
@@ -266,13 +266,13 @@ func (s *SubJsonService) realityData(rData map[string]interface{}) map[string]in
|
||||
|
||||
// Set random data
|
||||
rltyData["spiderX"] = "/" + random.Seq(15)
|
||||
shortIds, ok := rData["shortIds"].([]interface{})
|
||||
shortIds, ok := rData["shortIds"].([]any)
|
||||
if ok && len(shortIds) > 0 {
|
||||
rltyData["shortId"] = shortIds[random.Num(len(shortIds))].(string)
|
||||
} else {
|
||||
rltyData["shortId"] = ""
|
||||
}
|
||||
serverNames, ok := rData["serverNames"].([]interface{})
|
||||
serverNames, ok := rData["serverNames"].([]any)
|
||||
if ok && len(serverNames) > 0 {
|
||||
rltyData["serverName"] = serverNames[random.Num(len(serverNames))].(string)
|
||||
} else {
|
||||
@@ -329,7 +329,7 @@ func (s *SubJsonService) genServer(inbound *model.Inbound, streamSettings json_u
|
||||
}
|
||||
|
||||
if inbound.Protocol == model.Shadowsocks {
|
||||
var inboundSettings map[string]interface{}
|
||||
var inboundSettings map[string]any
|
||||
json.Unmarshal([]byte(inbound.Settings), &inboundSettings)
|
||||
method, _ := inboundSettings["method"].(string)
|
||||
serverData[0].Method = method
|
||||
@@ -357,12 +357,12 @@ func (s *SubJsonService) genServer(inbound *model.Inbound, streamSettings json_u
|
||||
}
|
||||
|
||||
type Outbound struct {
|
||||
Protocol string `json:"protocol"`
|
||||
Tag string `json:"tag"`
|
||||
StreamSettings json_util.RawMessage `json:"streamSettings"`
|
||||
Mux json_util.RawMessage `json:"mux,omitempty"`
|
||||
ProxySettings map[string]interface{} `json:"proxySettings,omitempty"`
|
||||
Settings OutboundSettings `json:"settings,omitempty"`
|
||||
Protocol string `json:"protocol"`
|
||||
Tag string `json:"tag"`
|
||||
StreamSettings json_util.RawMessage `json:"streamSettings"`
|
||||
Mux json_util.RawMessage `json:"mux,omitempty"`
|
||||
ProxySettings map[string]any `json:"proxySettings,omitempty"`
|
||||
Settings OutboundSettings `json:"settings,omitempty"`
|
||||
}
|
||||
|
||||
type OutboundSettings struct {
|
||||
|
||||
@@ -141,9 +141,9 @@ func (s *SubService) getFallbackMaster(dest string, streamSettings string) (stri
|
||||
return "", 0, "", err
|
||||
}
|
||||
|
||||
var stream map[string]interface{}
|
||||
var stream map[string]any
|
||||
json.Unmarshal([]byte(streamSettings), &stream)
|
||||
var masterStream map[string]interface{}
|
||||
var masterStream map[string]any
|
||||
json.Unmarshal([]byte(inbound.StreamSettings), &masterStream)
|
||||
stream["security"] = masterStream["security"]
|
||||
stream["tlsSettings"] = masterStream["tlsSettings"]
|
||||
@@ -171,66 +171,66 @@ func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
|
||||
if inbound.Protocol != model.VMESS {
|
||||
return ""
|
||||
}
|
||||
obj := map[string]interface{}{
|
||||
obj := map[string]any{
|
||||
"v": "2",
|
||||
"add": s.address,
|
||||
"port": inbound.Port,
|
||||
"type": "none",
|
||||
}
|
||||
var stream map[string]interface{}
|
||||
var stream map[string]any
|
||||
json.Unmarshal([]byte(inbound.StreamSettings), &stream)
|
||||
network, _ := stream["network"].(string)
|
||||
obj["net"] = network
|
||||
switch network {
|
||||
case "tcp":
|
||||
tcp, _ := stream["tcpSettings"].(map[string]interface{})
|
||||
header, _ := tcp["header"].(map[string]interface{})
|
||||
tcp, _ := stream["tcpSettings"].(map[string]any)
|
||||
header, _ := tcp["header"].(map[string]any)
|
||||
typeStr, _ := header["type"].(string)
|
||||
obj["type"] = typeStr
|
||||
if typeStr == "http" {
|
||||
request := header["request"].(map[string]interface{})
|
||||
requestPath, _ := request["path"].([]interface{})
|
||||
request := header["request"].(map[string]any)
|
||||
requestPath, _ := request["path"].([]any)
|
||||
obj["path"] = requestPath[0].(string)
|
||||
headers, _ := request["headers"].(map[string]interface{})
|
||||
headers, _ := request["headers"].(map[string]any)
|
||||
obj["host"] = searchHost(headers)
|
||||
}
|
||||
case "kcp":
|
||||
kcp, _ := stream["kcpSettings"].(map[string]interface{})
|
||||
header, _ := kcp["header"].(map[string]interface{})
|
||||
kcp, _ := stream["kcpSettings"].(map[string]any)
|
||||
header, _ := kcp["header"].(map[string]any)
|
||||
obj["type"], _ = header["type"].(string)
|
||||
obj["path"], _ = kcp["seed"].(string)
|
||||
case "ws":
|
||||
ws, _ := stream["wsSettings"].(map[string]interface{})
|
||||
ws, _ := stream["wsSettings"].(map[string]any)
|
||||
obj["path"] = ws["path"].(string)
|
||||
if host, ok := ws["host"].(string); ok && len(host) > 0 {
|
||||
obj["host"] = host
|
||||
} else {
|
||||
headers, _ := ws["headers"].(map[string]interface{})
|
||||
headers, _ := ws["headers"].(map[string]any)
|
||||
obj["host"] = searchHost(headers)
|
||||
}
|
||||
case "grpc":
|
||||
grpc, _ := stream["grpcSettings"].(map[string]interface{})
|
||||
grpc, _ := stream["grpcSettings"].(map[string]any)
|
||||
obj["path"] = grpc["serviceName"].(string)
|
||||
obj["authority"] = grpc["authority"].(string)
|
||||
if grpc["multiMode"].(bool) {
|
||||
obj["type"] = "multi"
|
||||
}
|
||||
case "httpupgrade":
|
||||
httpupgrade, _ := stream["httpupgradeSettings"].(map[string]interface{})
|
||||
httpupgrade, _ := stream["httpupgradeSettings"].(map[string]any)
|
||||
obj["path"] = httpupgrade["path"].(string)
|
||||
if host, ok := httpupgrade["host"].(string); ok && len(host) > 0 {
|
||||
obj["host"] = host
|
||||
} else {
|
||||
headers, _ := httpupgrade["headers"].(map[string]interface{})
|
||||
headers, _ := httpupgrade["headers"].(map[string]any)
|
||||
obj["host"] = searchHost(headers)
|
||||
}
|
||||
case "xhttp":
|
||||
xhttp, _ := stream["xhttpSettings"].(map[string]interface{})
|
||||
xhttp, _ := stream["xhttpSettings"].(map[string]any)
|
||||
obj["path"] = xhttp["path"].(string)
|
||||
if host, ok := xhttp["host"].(string); ok && len(host) > 0 {
|
||||
obj["host"] = host
|
||||
} else {
|
||||
headers, _ := xhttp["headers"].(map[string]interface{})
|
||||
headers, _ := xhttp["headers"].(map[string]any)
|
||||
obj["host"] = searchHost(headers)
|
||||
}
|
||||
obj["mode"] = xhttp["mode"].(string)
|
||||
@@ -238,8 +238,8 @@ func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
|
||||
security, _ := stream["security"].(string)
|
||||
obj["tls"] = security
|
||||
if security == "tls" {
|
||||
tlsSetting, _ := stream["tlsSettings"].(map[string]interface{})
|
||||
alpns, _ := tlsSetting["alpn"].([]interface{})
|
||||
tlsSetting, _ := stream["tlsSettings"].(map[string]any)
|
||||
alpns, _ := tlsSetting["alpn"].([]any)
|
||||
if len(alpns) > 0 {
|
||||
var alpn []string
|
||||
for _, a := range alpns {
|
||||
@@ -273,14 +273,14 @@ func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
|
||||
obj["id"] = clients[clientIndex].ID
|
||||
obj["scy"] = clients[clientIndex].Security
|
||||
|
||||
externalProxies, _ := stream["externalProxy"].([]interface{})
|
||||
externalProxies, _ := stream["externalProxy"].([]any)
|
||||
|
||||
if len(externalProxies) > 0 {
|
||||
links := ""
|
||||
for index, externalProxy := range externalProxies {
|
||||
ep, _ := externalProxy.(map[string]interface{})
|
||||
ep, _ := externalProxy.(map[string]any)
|
||||
newSecurity, _ := ep["forceTls"].(string)
|
||||
newObj := map[string]interface{}{}
|
||||
newObj := map[string]any{}
|
||||
for key, value := range obj {
|
||||
if !(newSecurity == "none" && (key == "alpn" || key == "sni" || key == "fp" || key == "allowInsecure")) {
|
||||
newObj[key] = value
|
||||
@@ -313,7 +313,7 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
||||
if inbound.Protocol != model.VLESS {
|
||||
return ""
|
||||
}
|
||||
var stream map[string]interface{}
|
||||
var stream map[string]any
|
||||
json.Unmarshal([]byte(inbound.StreamSettings), &stream)
|
||||
clients, _ := s.inboundService.GetClients(inbound)
|
||||
clientIndex := -1
|
||||
@@ -331,54 +331,54 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
||||
|
||||
switch streamNetwork {
|
||||
case "tcp":
|
||||
tcp, _ := stream["tcpSettings"].(map[string]interface{})
|
||||
header, _ := tcp["header"].(map[string]interface{})
|
||||
tcp, _ := stream["tcpSettings"].(map[string]any)
|
||||
header, _ := tcp["header"].(map[string]any)
|
||||
typeStr, _ := header["type"].(string)
|
||||
if typeStr == "http" {
|
||||
request := header["request"].(map[string]interface{})
|
||||
requestPath, _ := request["path"].([]interface{})
|
||||
request := header["request"].(map[string]any)
|
||||
requestPath, _ := request["path"].([]any)
|
||||
params["path"] = requestPath[0].(string)
|
||||
headers, _ := request["headers"].(map[string]interface{})
|
||||
headers, _ := request["headers"].(map[string]any)
|
||||
params["host"] = searchHost(headers)
|
||||
params["headerType"] = "http"
|
||||
}
|
||||
case "kcp":
|
||||
kcp, _ := stream["kcpSettings"].(map[string]interface{})
|
||||
header, _ := kcp["header"].(map[string]interface{})
|
||||
kcp, _ := stream["kcpSettings"].(map[string]any)
|
||||
header, _ := kcp["header"].(map[string]any)
|
||||
params["headerType"] = header["type"].(string)
|
||||
params["seed"] = kcp["seed"].(string)
|
||||
case "ws":
|
||||
ws, _ := stream["wsSettings"].(map[string]interface{})
|
||||
ws, _ := stream["wsSettings"].(map[string]any)
|
||||
params["path"] = ws["path"].(string)
|
||||
if host, ok := ws["host"].(string); ok && len(host) > 0 {
|
||||
params["host"] = host
|
||||
} else {
|
||||
headers, _ := ws["headers"].(map[string]interface{})
|
||||
headers, _ := ws["headers"].(map[string]any)
|
||||
params["host"] = searchHost(headers)
|
||||
}
|
||||
case "grpc":
|
||||
grpc, _ := stream["grpcSettings"].(map[string]interface{})
|
||||
grpc, _ := stream["grpcSettings"].(map[string]any)
|
||||
params["serviceName"] = grpc["serviceName"].(string)
|
||||
params["authority"], _ = grpc["authority"].(string)
|
||||
if grpc["multiMode"].(bool) {
|
||||
params["mode"] = "multi"
|
||||
}
|
||||
case "httpupgrade":
|
||||
httpupgrade, _ := stream["httpupgradeSettings"].(map[string]interface{})
|
||||
httpupgrade, _ := stream["httpupgradeSettings"].(map[string]any)
|
||||
params["path"] = httpupgrade["path"].(string)
|
||||
if host, ok := httpupgrade["host"].(string); ok && len(host) > 0 {
|
||||
params["host"] = host
|
||||
} else {
|
||||
headers, _ := httpupgrade["headers"].(map[string]interface{})
|
||||
headers, _ := httpupgrade["headers"].(map[string]any)
|
||||
params["host"] = searchHost(headers)
|
||||
}
|
||||
case "xhttp":
|
||||
xhttp, _ := stream["xhttpSettings"].(map[string]interface{})
|
||||
xhttp, _ := stream["xhttpSettings"].(map[string]any)
|
||||
params["path"] = xhttp["path"].(string)
|
||||
if host, ok := xhttp["host"].(string); ok && len(host) > 0 {
|
||||
params["host"] = host
|
||||
} else {
|
||||
headers, _ := xhttp["headers"].(map[string]interface{})
|
||||
headers, _ := xhttp["headers"].(map[string]any)
|
||||
params["host"] = searchHost(headers)
|
||||
}
|
||||
params["mode"] = xhttp["mode"].(string)
|
||||
@@ -386,8 +386,8 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
||||
security, _ := stream["security"].(string)
|
||||
if security == "tls" {
|
||||
params["security"] = "tls"
|
||||
tlsSetting, _ := stream["tlsSettings"].(map[string]interface{})
|
||||
alpns, _ := tlsSetting["alpn"].([]interface{})
|
||||
tlsSetting, _ := stream["tlsSettings"].(map[string]any)
|
||||
alpns, _ := tlsSetting["alpn"].([]any)
|
||||
var alpn []string
|
||||
for _, a := range alpns {
|
||||
alpn = append(alpn, a.(string))
|
||||
@@ -418,18 +418,18 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
||||
|
||||
if security == "reality" {
|
||||
params["security"] = "reality"
|
||||
realitySetting, _ := stream["realitySettings"].(map[string]interface{})
|
||||
realitySetting, _ := stream["realitySettings"].(map[string]any)
|
||||
realitySettings, _ := searchKey(realitySetting, "settings")
|
||||
if realitySetting != nil {
|
||||
if sniValue, ok := searchKey(realitySetting, "serverNames"); ok {
|
||||
sNames, _ := sniValue.([]interface{})
|
||||
sNames, _ := sniValue.([]any)
|
||||
params["sni"] = sNames[random.Num(len(sNames))].(string)
|
||||
}
|
||||
if pbkValue, ok := searchKey(realitySettings, "publicKey"); ok {
|
||||
params["pbk"], _ = pbkValue.(string)
|
||||
}
|
||||
if sidValue, ok := searchKey(realitySetting, "shortIds"); ok {
|
||||
shortIds, _ := sidValue.([]interface{})
|
||||
shortIds, _ := sidValue.([]any)
|
||||
params["sid"] = shortIds[random.Num(len(shortIds))].(string)
|
||||
}
|
||||
if fpValue, ok := searchKey(realitySettings, "fingerprint"); ok {
|
||||
@@ -449,12 +449,12 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
||||
params["security"] = "none"
|
||||
}
|
||||
|
||||
externalProxies, _ := stream["externalProxy"].([]interface{})
|
||||
externalProxies, _ := stream["externalProxy"].([]any)
|
||||
|
||||
if len(externalProxies) > 0 {
|
||||
links := ""
|
||||
for index, externalProxy := range externalProxies {
|
||||
ep, _ := externalProxy.(map[string]interface{})
|
||||
ep, _ := externalProxy.(map[string]any)
|
||||
newSecurity, _ := ep["forceTls"].(string)
|
||||
dest, _ := ep["dest"].(string)
|
||||
port := int(ep["port"].(float64))
|
||||
@@ -507,7 +507,7 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
||||
if inbound.Protocol != model.Trojan {
|
||||
return ""
|
||||
}
|
||||
var stream map[string]interface{}
|
||||
var stream map[string]any
|
||||
json.Unmarshal([]byte(inbound.StreamSettings), &stream)
|
||||
clients, _ := s.inboundService.GetClients(inbound)
|
||||
clientIndex := -1
|
||||
@@ -525,54 +525,54 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
||||
|
||||
switch streamNetwork {
|
||||
case "tcp":
|
||||
tcp, _ := stream["tcpSettings"].(map[string]interface{})
|
||||
header, _ := tcp["header"].(map[string]interface{})
|
||||
tcp, _ := stream["tcpSettings"].(map[string]any)
|
||||
header, _ := tcp["header"].(map[string]any)
|
||||
typeStr, _ := header["type"].(string)
|
||||
if typeStr == "http" {
|
||||
request := header["request"].(map[string]interface{})
|
||||
requestPath, _ := request["path"].([]interface{})
|
||||
request := header["request"].(map[string]any)
|
||||
requestPath, _ := request["path"].([]any)
|
||||
params["path"] = requestPath[0].(string)
|
||||
headers, _ := request["headers"].(map[string]interface{})
|
||||
headers, _ := request["headers"].(map[string]any)
|
||||
params["host"] = searchHost(headers)
|
||||
params["headerType"] = "http"
|
||||
}
|
||||
case "kcp":
|
||||
kcp, _ := stream["kcpSettings"].(map[string]interface{})
|
||||
header, _ := kcp["header"].(map[string]interface{})
|
||||
kcp, _ := stream["kcpSettings"].(map[string]any)
|
||||
header, _ := kcp["header"].(map[string]any)
|
||||
params["headerType"] = header["type"].(string)
|
||||
params["seed"] = kcp["seed"].(string)
|
||||
case "ws":
|
||||
ws, _ := stream["wsSettings"].(map[string]interface{})
|
||||
ws, _ := stream["wsSettings"].(map[string]any)
|
||||
params["path"] = ws["path"].(string)
|
||||
if host, ok := ws["host"].(string); ok && len(host) > 0 {
|
||||
params["host"] = host
|
||||
} else {
|
||||
headers, _ := ws["headers"].(map[string]interface{})
|
||||
headers, _ := ws["headers"].(map[string]any)
|
||||
params["host"] = searchHost(headers)
|
||||
}
|
||||
case "grpc":
|
||||
grpc, _ := stream["grpcSettings"].(map[string]interface{})
|
||||
grpc, _ := stream["grpcSettings"].(map[string]any)
|
||||
params["serviceName"] = grpc["serviceName"].(string)
|
||||
params["authority"], _ = grpc["authority"].(string)
|
||||
if grpc["multiMode"].(bool) {
|
||||
params["mode"] = "multi"
|
||||
}
|
||||
case "httpupgrade":
|
||||
httpupgrade, _ := stream["httpupgradeSettings"].(map[string]interface{})
|
||||
httpupgrade, _ := stream["httpupgradeSettings"].(map[string]any)
|
||||
params["path"] = httpupgrade["path"].(string)
|
||||
if host, ok := httpupgrade["host"].(string); ok && len(host) > 0 {
|
||||
params["host"] = host
|
||||
} else {
|
||||
headers, _ := httpupgrade["headers"].(map[string]interface{})
|
||||
headers, _ := httpupgrade["headers"].(map[string]any)
|
||||
params["host"] = searchHost(headers)
|
||||
}
|
||||
case "xhttp":
|
||||
xhttp, _ := stream["xhttpSettings"].(map[string]interface{})
|
||||
xhttp, _ := stream["xhttpSettings"].(map[string]any)
|
||||
params["path"] = xhttp["path"].(string)
|
||||
if host, ok := xhttp["host"].(string); ok && len(host) > 0 {
|
||||
params["host"] = host
|
||||
} else {
|
||||
headers, _ := xhttp["headers"].(map[string]interface{})
|
||||
headers, _ := xhttp["headers"].(map[string]any)
|
||||
params["host"] = searchHost(headers)
|
||||
}
|
||||
params["mode"] = xhttp["mode"].(string)
|
||||
@@ -580,8 +580,8 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
||||
security, _ := stream["security"].(string)
|
||||
if security == "tls" {
|
||||
params["security"] = "tls"
|
||||
tlsSetting, _ := stream["tlsSettings"].(map[string]interface{})
|
||||
alpns, _ := tlsSetting["alpn"].([]interface{})
|
||||
tlsSetting, _ := stream["tlsSettings"].(map[string]any)
|
||||
alpns, _ := tlsSetting["alpn"].([]any)
|
||||
var alpn []string
|
||||
for _, a := range alpns {
|
||||
alpn = append(alpn, a.(string))
|
||||
@@ -608,18 +608,18 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
||||
|
||||
if security == "reality" {
|
||||
params["security"] = "reality"
|
||||
realitySetting, _ := stream["realitySettings"].(map[string]interface{})
|
||||
realitySetting, _ := stream["realitySettings"].(map[string]any)
|
||||
realitySettings, _ := searchKey(realitySetting, "settings")
|
||||
if realitySetting != nil {
|
||||
if sniValue, ok := searchKey(realitySetting, "serverNames"); ok {
|
||||
sNames, _ := sniValue.([]interface{})
|
||||
sNames, _ := sniValue.([]any)
|
||||
params["sni"] = sNames[random.Num(len(sNames))].(string)
|
||||
}
|
||||
if pbkValue, ok := searchKey(realitySettings, "publicKey"); ok {
|
||||
params["pbk"], _ = pbkValue.(string)
|
||||
}
|
||||
if sidValue, ok := searchKey(realitySetting, "shortIds"); ok {
|
||||
shortIds, _ := sidValue.([]interface{})
|
||||
shortIds, _ := sidValue.([]any)
|
||||
params["sid"] = shortIds[random.Num(len(shortIds))].(string)
|
||||
}
|
||||
if fpValue, ok := searchKey(realitySettings, "fingerprint"); ok {
|
||||
@@ -639,12 +639,12 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
||||
params["security"] = "none"
|
||||
}
|
||||
|
||||
externalProxies, _ := stream["externalProxy"].([]interface{})
|
||||
externalProxies, _ := stream["externalProxy"].([]any)
|
||||
|
||||
if len(externalProxies) > 0 {
|
||||
links := ""
|
||||
for index, externalProxy := range externalProxies {
|
||||
ep, _ := externalProxy.(map[string]interface{})
|
||||
ep, _ := externalProxy.(map[string]any)
|
||||
newSecurity, _ := ep["forceTls"].(string)
|
||||
dest, _ := ep["dest"].(string)
|
||||
port := int(ep["port"].(float64))
|
||||
@@ -698,11 +698,11 @@ func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) st
|
||||
if inbound.Protocol != model.Shadowsocks {
|
||||
return ""
|
||||
}
|
||||
var stream map[string]interface{}
|
||||
var stream map[string]any
|
||||
json.Unmarshal([]byte(inbound.StreamSettings), &stream)
|
||||
clients, _ := s.inboundService.GetClients(inbound)
|
||||
|
||||
var settings map[string]interface{}
|
||||
var settings map[string]any
|
||||
json.Unmarshal([]byte(inbound.Settings), &settings)
|
||||
inboundPassword := settings["password"].(string)
|
||||
method := settings["method"].(string)
|
||||
@@ -719,54 +719,54 @@ func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) st
|
||||
|
||||
switch streamNetwork {
|
||||
case "tcp":
|
||||
tcp, _ := stream["tcpSettings"].(map[string]interface{})
|
||||
header, _ := tcp["header"].(map[string]interface{})
|
||||
tcp, _ := stream["tcpSettings"].(map[string]any)
|
||||
header, _ := tcp["header"].(map[string]any)
|
||||
typeStr, _ := header["type"].(string)
|
||||
if typeStr == "http" {
|
||||
request := header["request"].(map[string]interface{})
|
||||
requestPath, _ := request["path"].([]interface{})
|
||||
request := header["request"].(map[string]any)
|
||||
requestPath, _ := request["path"].([]any)
|
||||
params["path"] = requestPath[0].(string)
|
||||
headers, _ := request["headers"].(map[string]interface{})
|
||||
headers, _ := request["headers"].(map[string]any)
|
||||
params["host"] = searchHost(headers)
|
||||
params["headerType"] = "http"
|
||||
}
|
||||
case "kcp":
|
||||
kcp, _ := stream["kcpSettings"].(map[string]interface{})
|
||||
header, _ := kcp["header"].(map[string]interface{})
|
||||
kcp, _ := stream["kcpSettings"].(map[string]any)
|
||||
header, _ := kcp["header"].(map[string]any)
|
||||
params["headerType"] = header["type"].(string)
|
||||
params["seed"] = kcp["seed"].(string)
|
||||
case "ws":
|
||||
ws, _ := stream["wsSettings"].(map[string]interface{})
|
||||
ws, _ := stream["wsSettings"].(map[string]any)
|
||||
params["path"] = ws["path"].(string)
|
||||
if host, ok := ws["host"].(string); ok && len(host) > 0 {
|
||||
params["host"] = host
|
||||
} else {
|
||||
headers, _ := ws["headers"].(map[string]interface{})
|
||||
headers, _ := ws["headers"].(map[string]any)
|
||||
params["host"] = searchHost(headers)
|
||||
}
|
||||
case "grpc":
|
||||
grpc, _ := stream["grpcSettings"].(map[string]interface{})
|
||||
grpc, _ := stream["grpcSettings"].(map[string]any)
|
||||
params["serviceName"] = grpc["serviceName"].(string)
|
||||
params["authority"], _ = grpc["authority"].(string)
|
||||
if grpc["multiMode"].(bool) {
|
||||
params["mode"] = "multi"
|
||||
}
|
||||
case "httpupgrade":
|
||||
httpupgrade, _ := stream["httpupgradeSettings"].(map[string]interface{})
|
||||
httpupgrade, _ := stream["httpupgradeSettings"].(map[string]any)
|
||||
params["path"] = httpupgrade["path"].(string)
|
||||
if host, ok := httpupgrade["host"].(string); ok && len(host) > 0 {
|
||||
params["host"] = host
|
||||
} else {
|
||||
headers, _ := httpupgrade["headers"].(map[string]interface{})
|
||||
headers, _ := httpupgrade["headers"].(map[string]any)
|
||||
params["host"] = searchHost(headers)
|
||||
}
|
||||
case "xhttp":
|
||||
xhttp, _ := stream["xhttpSettings"].(map[string]interface{})
|
||||
xhttp, _ := stream["xhttpSettings"].(map[string]any)
|
||||
params["path"] = xhttp["path"].(string)
|
||||
if host, ok := xhttp["host"].(string); ok && len(host) > 0 {
|
||||
params["host"] = host
|
||||
} else {
|
||||
headers, _ := xhttp["headers"].(map[string]interface{})
|
||||
headers, _ := xhttp["headers"].(map[string]any)
|
||||
params["host"] = searchHost(headers)
|
||||
}
|
||||
params["mode"] = xhttp["mode"].(string)
|
||||
@@ -775,8 +775,8 @@ func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) st
|
||||
security, _ := stream["security"].(string)
|
||||
if security == "tls" {
|
||||
params["security"] = "tls"
|
||||
tlsSetting, _ := stream["tlsSettings"].(map[string]interface{})
|
||||
alpns, _ := tlsSetting["alpn"].([]interface{})
|
||||
tlsSetting, _ := stream["tlsSettings"].(map[string]any)
|
||||
alpns, _ := tlsSetting["alpn"].([]any)
|
||||
var alpn []string
|
||||
for _, a := range alpns {
|
||||
alpn = append(alpn, a.(string))
|
||||
@@ -806,12 +806,12 @@ func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) st
|
||||
encPart = fmt.Sprintf("%s:%s:%s", method, inboundPassword, clients[clientIndex].Password)
|
||||
}
|
||||
|
||||
externalProxies, _ := stream["externalProxy"].([]interface{})
|
||||
externalProxies, _ := stream["externalProxy"].([]any)
|
||||
|
||||
if len(externalProxies) > 0 {
|
||||
links := ""
|
||||
for index, externalProxy := range externalProxies {
|
||||
ep, _ := externalProxy.(map[string]interface{})
|
||||
ep, _ := externalProxy.(map[string]any)
|
||||
newSecurity, _ := ep["forceTls"].(string)
|
||||
dest, _ := ep["dest"].(string)
|
||||
port := int(ep["port"].(float64))
|
||||
@@ -944,9 +944,9 @@ func (s *SubService) genRemark(inbound *model.Inbound, email string, extra strin
|
||||
return strings.Join(remark, separationChar)
|
||||
}
|
||||
|
||||
func searchKey(data interface{}, key string) (interface{}, bool) {
|
||||
func searchKey(data any, key string) (any, bool) {
|
||||
switch val := data.(type) {
|
||||
case map[string]interface{}:
|
||||
case map[string]any:
|
||||
for k, v := range val {
|
||||
if k == key {
|
||||
return v, true
|
||||
@@ -955,7 +955,7 @@ func searchKey(data interface{}, key string) (interface{}, bool) {
|
||||
return result, true
|
||||
}
|
||||
}
|
||||
case []interface{}:
|
||||
case []any:
|
||||
for _, v := range val {
|
||||
if result, ok := searchKey(v, key); ok {
|
||||
return result, true
|
||||
@@ -965,19 +965,19 @@ func searchKey(data interface{}, key string) (interface{}, bool) {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func searchHost(headers interface{}) string {
|
||||
data, _ := headers.(map[string]interface{})
|
||||
func searchHost(headers any) string {
|
||||
data, _ := headers.(map[string]any)
|
||||
for k, v := range data {
|
||||
if strings.EqualFold(k, "host") {
|
||||
switch v.(type) {
|
||||
case []interface{}:
|
||||
hosts, _ := v.([]interface{})
|
||||
case []any:
|
||||
hosts, _ := v.([]any)
|
||||
if len(hosts) > 0 {
|
||||
return hosts[0].(string)
|
||||
} else {
|
||||
return ""
|
||||
}
|
||||
case interface{}:
|
||||
case any:
|
||||
return v.(string)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,17 +7,17 @@ import (
|
||||
"x-ui/logger"
|
||||
)
|
||||
|
||||
func NewErrorf(format string, a ...interface{}) error {
|
||||
func NewErrorf(format string, a ...any) error {
|
||||
msg := fmt.Sprintf(format, a...)
|
||||
return errors.New(msg)
|
||||
}
|
||||
|
||||
func NewError(a ...interface{}) error {
|
||||
func NewError(a ...any) error {
|
||||
msg := fmt.Sprintln(a...)
|
||||
return errors.New(msg)
|
||||
}
|
||||
|
||||
func Recover(msg string) interface{} {
|
||||
func Recover(msg string) any {
|
||||
panicErr := recover()
|
||||
if panicErr != nil {
|
||||
if msg != "" {
|
||||
|
||||
3
web/assets/axios/axios.min.js
vendored
3
web/assets/axios/axios.min.js
vendored
File diff suppressed because one or more lines are too long
@@ -26,6 +26,7 @@ class AllSetting {
|
||||
this.xrayTemplateConfig = "";
|
||||
this.secretEnable = false;
|
||||
this.subEnable = false;
|
||||
this.subTitle = "";
|
||||
this.subListen = "";
|
||||
this.subPort = 2096;
|
||||
this.subPath = "/sub/";
|
||||
|
||||
@@ -31,11 +31,11 @@ func jsonMsg(c *gin.Context, msg string, err error) {
|
||||
jsonMsgObj(c, msg, nil, err)
|
||||
}
|
||||
|
||||
func jsonObj(c *gin.Context, obj interface{}, err error) {
|
||||
func jsonObj(c *gin.Context, obj any, err error) {
|
||||
jsonMsgObj(c, "", obj, err)
|
||||
}
|
||||
|
||||
func jsonMsgObj(c *gin.Context, msg string, obj interface{}, err error) {
|
||||
func jsonMsgObj(c *gin.Context, msg string, obj any, err error) {
|
||||
m := entity.Msg{
|
||||
Obj: obj,
|
||||
}
|
||||
|
||||
@@ -10,9 +10,9 @@ import (
|
||||
)
|
||||
|
||||
type Msg struct {
|
||||
Success bool `json:"success"`
|
||||
Msg string `json:"msg"`
|
||||
Obj interface{} `json:"obj"`
|
||||
Success bool `json:"success"`
|
||||
Msg string `json:"msg"`
|
||||
Obj any `json:"obj"`
|
||||
}
|
||||
|
||||
type AllSetting struct {
|
||||
@@ -40,6 +40,7 @@ type AllSetting struct {
|
||||
TimeLocation string `json:"timeLocation" form:"timeLocation"`
|
||||
SecretEnable bool `json:"secretEnable" form:"secretEnable"`
|
||||
SubEnable bool `json:"subEnable" form:"subEnable"`
|
||||
SubTitle string `json:"subTitle" form:"subTitle"`
|
||||
SubListen string `json:"subListen" form:"subListen"`
|
||||
SubPort int `json:"subPort" form:"subPort"`
|
||||
SubPath string `json:"subPath" form:"subPath"`
|
||||
|
||||
@@ -473,8 +473,8 @@
|
||||
</transition>
|
||||
</a-layout>
|
||||
{{template "js" .}}
|
||||
{{template "component/themeSwitcher" .}}
|
||||
{{template "component/password" .}}
|
||||
{{template "component/aThemeSwitch" .}}
|
||||
{{template "component/aPasswordInput" .}}
|
||||
<script>
|
||||
class User {
|
||||
constructor() {
|
||||
|
||||
@@ -1,33 +1,23 @@
|
||||
{{define "menuItems"}}
|
||||
<a-menu-item key="{{ .base_path }}panel/">
|
||||
<a-icon type="dashboard"></a-icon>
|
||||
<span>
|
||||
<b>{{ i18n "menu.dashboard"}}</b>
|
||||
</span>
|
||||
<span>{{ i18n "menu.dashboard"}}</span>
|
||||
</a-menu-item>
|
||||
<a-menu-item key="{{ .base_path }}panel/inbounds">
|
||||
<a-icon type="user"></a-icon>
|
||||
<span>
|
||||
<b>{{ i18n "menu.inbounds"}}</b>
|
||||
</span>
|
||||
<span>{{ i18n "menu.inbounds"}}</span>
|
||||
</a-menu-item>
|
||||
<a-menu-item key="{{ .base_path }}panel/settings">
|
||||
<a-icon type="setting"></a-icon>
|
||||
<span>
|
||||
<b>{{ i18n "menu.settings"}}</b>
|
||||
</span>
|
||||
<span>{{ i18n "menu.settings"}}</span>
|
||||
</a-menu-item>
|
||||
<a-menu-item key="{{ .base_path }}panel/xray">
|
||||
<a-icon type="tool"></a-icon>
|
||||
<span>
|
||||
<b>{{ i18n "menu.xray"}}</b>
|
||||
</span>
|
||||
<span>{{ i18n "menu.xray"}}</span>
|
||||
</a-menu-item>
|
||||
<a-menu-item key="{{ .base_path }}logout">
|
||||
<a-icon type="logout"></a-icon>
|
||||
<span>
|
||||
<b>{{ i18n "menu.logout"}}</b>
|
||||
</span>
|
||||
<span>{{ i18n "menu.logout"}}</span>
|
||||
</a-menu-item>
|
||||
{{end}}
|
||||
|
||||
|
||||
42
web/html/xui/component/aCustomStatistic.html
Normal file
42
web/html/xui/component/aCustomStatistic.html
Normal file
@@ -0,0 +1,42 @@
|
||||
{{define "component/customStatistic"}}
|
||||
<template>
|
||||
<a-statistic :title="title" :value="value">
|
||||
<template #prefix>
|
||||
<slot name="prefix"></slot>
|
||||
</template>
|
||||
<template #suffix>
|
||||
<slot name="suffix"></slot>
|
||||
</template>
|
||||
</a-statistic>
|
||||
</template>
|
||||
{{end}}
|
||||
|
||||
{{define "component/aCustomStatistic"}}
|
||||
<style>
|
||||
.dark .ant-statistic-content {
|
||||
color: var(--dark-color-text-primary)
|
||||
}
|
||||
.dark .ant-statistic-title {
|
||||
color: rgba(255, 255, 255, 0.55)
|
||||
}
|
||||
.ant-statistic-content {
|
||||
font-size: 16px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
Vue.component('a-custom-statistic', {
|
||||
props: {
|
||||
'title': {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
'value': {
|
||||
type: String,
|
||||
required: false
|
||||
}
|
||||
},
|
||||
template: `{{template "component/customStatistic"}}`,
|
||||
});
|
||||
</script>
|
||||
{{end}}
|
||||
@@ -12,7 +12,7 @@
|
||||
</template>
|
||||
{{end}}
|
||||
|
||||
{{define "component/password"}}
|
||||
{{define "component/aPasswordInput"}}
|
||||
<script>
|
||||
Vue.component('a-password-input', {
|
||||
props: {
|
||||
@@ -12,7 +12,7 @@
|
||||
</template>
|
||||
{{end}}
|
||||
|
||||
{{define "component/persianDatepicker"}}
|
||||
{{define "component/aPersianDatepicker"}}
|
||||
<link rel="stylesheet" href="{{ .base_path }}assets/persian-datepicker/persian-datepicker.min.css?{{ .cur_ver }}" />
|
||||
<script src="{{ .base_path }}assets/moment/moment-jalali.min.js?{{ .cur_ver }}"></script>
|
||||
<script src="{{ .base_path }}assets/persian-datepicker/persian-datepicker.min.js?{{ .cur_ver }}"></script>
|
||||
@@ -18,18 +18,10 @@
|
||||
</a-list-item>
|
||||
{{end}}
|
||||
|
||||
{{define "component/setting"}}
|
||||
{{define "component/aSettingListItem"}}
|
||||
<script>
|
||||
Vue.component('a-setting-list-item', {
|
||||
props: {
|
||||
'title': {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
'description': {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
'paddings': {
|
||||
type: String,
|
||||
required: false,
|
||||
@@ -3,7 +3,7 @@
|
||||
@click="clickHandler" />
|
||||
{{end}}
|
||||
|
||||
{{define "component/sortableTable"}}
|
||||
{{define "component/aTableSortable"}}
|
||||
<script>
|
||||
const DRAGGABLE_ROW_CLASS = 'draggable-row';
|
||||
const findParentRowElement = (el) => {
|
||||
@@ -4,15 +4,15 @@
|
||||
<a-sub-menu>
|
||||
<span slot="title">
|
||||
<a-icon type="bulb" :theme="themeSwitcher.isDarkTheme ? 'filled' : 'outlined'"></a-icon>
|
||||
<span>Theme</span>
|
||||
<span>{{ i18n "menu.theme" }}</span>
|
||||
</span>
|
||||
<a-menu-item id="change-theme" class="ant-menu-theme-switch" @mousedown="themeSwitcher.animationsOff()"> Dark
|
||||
<a-switch style="margin-left: 2px;" size="small" :default-checked="themeSwitcher.isDarkTheme"
|
||||
@change="themeSwitcher.toggleTheme()"></a-switch>
|
||||
<a-menu-item id="change-theme" class="ant-menu-theme-switch" @mousedown="themeSwitcher.animationsOff()">
|
||||
<span>{{ i18n "menu.dark" }}</span>
|
||||
<a-switch style="margin-left: 2px;" size="small" :default-checked="themeSwitcher.isDarkTheme" @change="themeSwitcher.toggleTheme()"></a-switch>
|
||||
</a-menu-item>
|
||||
<a-menu-item id="change-theme-ultra" v-if="themeSwitcher.isDarkTheme" class="ant-menu-theme-switch"
|
||||
@mousedown="themeSwitcher.animationsOffUltra()"> Ultra <a-checkbox style="margin-left: 2px;"
|
||||
:checked="themeSwitcher.isUltra" @click="themeSwitcher.toggleUltra()"></a-checkbox>
|
||||
<a-menu-item id="change-theme-ultra" v-if="themeSwitcher.isDarkTheme" class="ant-menu-theme-switch" @mousedown="themeSwitcher.animationsOffUltra()">
|
||||
<span>{{ i18n "menu.ultraDark" }}</span>
|
||||
<a-checkbox style="margin-left: 2px;" :checked="themeSwitcher.isUltra" @click="themeSwitcher.toggleUltra()"></a-checkbox>
|
||||
</a-menu-item>
|
||||
</a-sub-menu>
|
||||
</a-menu>
|
||||
@@ -36,7 +36,7 @@
|
||||
</template>
|
||||
{{end}}
|
||||
|
||||
{{define "component/themeSwitcher"}}
|
||||
{{define "component/aThemeSwitch"}}
|
||||
<script>
|
||||
function createThemeSwitcher() {
|
||||
const isDarkTheme = localStorage.getItem('dark-mode') === 'true';
|
||||
@@ -506,12 +506,12 @@
|
||||
</a-form>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="2" tab="JSON" force-render="true">
|
||||
<a-form-item style="margin: 10px 0"> Link: <a-input v-model.trim="outModal.link" style="width: 300px; margin-right: 5px;" placeholder="vmess:// vless:// trojan:// ss://"></a-input>
|
||||
<a-button @click="convertLink" type="primary">
|
||||
<a-icon type="form"></a-icon>
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
<textarea style="position:absolute; left: -800px;" id="outboundJson"></textarea>
|
||||
<a-space direction="vertical" :size="10" style="margin-top: 10px;">
|
||||
<a-input addon-before='{{ i18n "pages.xray.outbound.link" }}' v-model.trim="outModal.link" placeholder="vmess:// vless:// trojan:// ss://">
|
||||
<a-icon slot="addonAfter" type="form" @click="convertLink"></a-icon>
|
||||
</a-input>
|
||||
<textarea style="position:absolute; left: -800px;" id="outboundJson"></textarea>
|
||||
</a-space>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
{{end}}
|
||||
|
||||
@@ -43,6 +43,15 @@
|
||||
margin:-10px 2px !important;
|
||||
}
|
||||
}
|
||||
.dark .ant-switch-small:not(.ant-switch-checked) {
|
||||
background-color: var(--dark-color-surface-100) !important;
|
||||
}
|
||||
.ant-custom-popover-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin: 5px 0;
|
||||
}
|
||||
.ant-col-sm-24 {
|
||||
margin: 0.5rem -2rem 0.5rem 2rem;
|
||||
}
|
||||
@@ -137,406 +146,433 @@
|
||||
</a-alert>
|
||||
</transition>
|
||||
<transition name="list" appear>
|
||||
<a-card hoverable>
|
||||
<a-card size="small" style="padding: 16px;" hoverable>
|
||||
<a-row>
|
||||
<a-col :xs="24" :sm="24" :lg="12">
|
||||
{{ i18n "pages.inbounds.totalDownUp" }}:
|
||||
<a-tag color="green">[[ SizeFormatter.sizeFormat(total.up) ]] / [[ SizeFormatter.sizeFormat(total.down) ]]</a-tag>
|
||||
<a-col :sm="12" :md="6">
|
||||
<a-custom-statistic title='{{ i18n "pages.inbounds.totalDownUp" }}' :value="`${SizeFormatter.sizeFormat(total.up)} / ${SizeFormatter.sizeFormat(total.down)}`">
|
||||
<template #prefix>
|
||||
<a-icon type="swap"></a-icon>
|
||||
</template>
|
||||
</a-custom-statistic>
|
||||
</a-col>
|
||||
<a-col :xs="24" :sm="24" :lg="12">
|
||||
{{ i18n "pages.inbounds.totalUsage" }}:
|
||||
<a-tag color="green">[[ SizeFormatter.sizeFormat(total.up + total.down) ]]</a-tag>
|
||||
<a-col :sm="12" :md="6">
|
||||
<a-custom-statistic title='{{ i18n "pages.inbounds.totalUsage" }}' :value="SizeFormatter.sizeFormat(total.up + total.down)" :style="{ marginTop: isMobile ? '10px' : 0 }">
|
||||
<template #prefix>
|
||||
<a-icon type="pie-chart"></a-icon>
|
||||
</template>
|
||||
</a-custom-statistic>
|
||||
</a-col>
|
||||
<a-col :xs="24" :sm="24" :lg="12">
|
||||
{{ i18n "pages.inbounds.inboundCount" }}:
|
||||
<a-tag color="green">[[ dbInbounds.length ]]</a-tag>
|
||||
<a-col :sm="12" :md="6">
|
||||
<a-custom-statistic title='{{ i18n "pages.inbounds.inboundCount" }}' :value="dbInbounds.length" :style="{ marginTop: isMobile ? '10px' : 0 }">
|
||||
<template #prefix>
|
||||
<a-icon type="bars"></a-icon>
|
||||
</template>
|
||||
</a-custom-statistic>
|
||||
</a-col>
|
||||
<a-col :xs="24" :sm="24" :lg="12">
|
||||
<template>
|
||||
<div>
|
||||
<a-back-top :target="() => document.getElementById('content-layout')" visibility-height="200"></a-back-top>
|
||||
{{ i18n "clients" }}:
|
||||
<a-tag color="green">[[ total.clients ]]</a-tag>
|
||||
<a-popover title='{{ i18n "disabled" }}' :overlay-class-name="themeSwitcher.currentTheme">
|
||||
<template slot="content">
|
||||
<p v-for="clientEmail in total.deactive">[[ clientEmail ]]</p>
|
||||
</template>
|
||||
<a-tag v-if="total.deactive.length">[[ total.deactive.length ]]</a-tag>
|
||||
</a-popover>
|
||||
<a-popover title='{{ i18n "depleted" }}' :overlay-class-name="themeSwitcher.currentTheme">
|
||||
<template slot="content">
|
||||
<p v-for="clientEmail in total.depleted">[[ clientEmail ]]</p>
|
||||
</template>
|
||||
<a-tag color="red" v-if="total.depleted.length">[[ total.depleted.length ]]</a-tag>
|
||||
</a-popover>
|
||||
<a-popover title='{{ i18n "depletingSoon" }}' :overlay-class-name="themeSwitcher.currentTheme">
|
||||
<template slot="content">
|
||||
<p v-for="clientEmail in total.expiring">[[ clientEmail ]]</p>
|
||||
</template>
|
||||
<a-tag color="orange" v-if="total.expiring.length">[[ total.expiring.length ]]</a-tag>
|
||||
</a-popover>
|
||||
<a-popover title='{{ i18n "online" }}' :overlay-class-name="themeSwitcher.currentTheme">
|
||||
<template slot="content">
|
||||
<p v-for="clientEmail in onlineClients">[[ clientEmail ]]</p>
|
||||
</template>
|
||||
<a-tag color="blue" v-if="onlineClients.length">[[ onlineClients.length ]]</a-tag>
|
||||
</a-popover>
|
||||
</div>
|
||||
</template>
|
||||
<a-col :sm="12" :md="6">
|
||||
<a-custom-statistic title='{{ i18n "clients" }}' value=" " :style="{ marginTop: isMobile ? '10px' : 0 }">
|
||||
<template #prefix>
|
||||
<a-space direction="horizontal">
|
||||
<a-icon type="team"></a-icon>
|
||||
<div>
|
||||
<a-back-top :target="() => document.getElementById('content-layout')" visibility-height="200"></a-back-top>
|
||||
<a-tag color="green">[[ total.clients ]]</a-tag>
|
||||
<a-popover title='{{ i18n "disabled" }}' :overlay-class-name="themeSwitcher.currentTheme">
|
||||
<template slot="content">
|
||||
<div v-for="clientEmail in total.deactive"><span>[[ clientEmail ]]</span></div>
|
||||
</template>
|
||||
<a-tag v-if="total.deactive.length">[[ total.deactive.length ]]</a-tag>
|
||||
</a-popover>
|
||||
<a-popover title='{{ i18n "depleted" }}' :overlay-class-name="themeSwitcher.currentTheme">
|
||||
<template slot="content">
|
||||
<div v-for="clientEmail in total.depleted"><span>[[ clientEmail ]]</span></div>
|
||||
</template>
|
||||
<a-tag color="red" v-if="total.depleted.length">[[ total.depleted.length ]]</a-tag>
|
||||
</a-popover>
|
||||
<a-popover title='{{ i18n "depletingSoon" }}' :overlay-class-name="themeSwitcher.currentTheme">
|
||||
<template slot="content">
|
||||
<div v-for="clientEmail in total.expiring"><span>[[ clientEmail ]]</span></div>
|
||||
</template>
|
||||
<a-tag color="orange" v-if="total.expiring.length">[[ total.expiring.length ]]</a-tag>
|
||||
</a-popover>
|
||||
<a-popover title='{{ i18n "online" }}' :overlay-class-name="themeSwitcher.currentTheme">
|
||||
<template slot="content">
|
||||
<div v-for="clientEmail in onlineClients"><span>[[ clientEmail ]]</span></div>
|
||||
</template>
|
||||
<a-tag color="blue" v-if="onlineClients.length">[[ onlineClients.length ]]</a-tag>
|
||||
</a-popover>
|
||||
</div>
|
||||
</a-space>
|
||||
</template>
|
||||
</a-custom-statistic>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
</transition>
|
||||
<transition name="list" appear>
|
||||
<a-card hoverable>
|
||||
<div slot="title">
|
||||
<a-row>
|
||||
<a-col :xs="12" :sm="12" :lg="12">
|
||||
<a-button type="primary" icon="plus" @click="openAddInbound">
|
||||
<template v-if="!isMobile">{{ i18n "pages.inbounds.addInbound" }}</template>
|
||||
</a-button>
|
||||
<a-dropdown :trigger="['click']">
|
||||
<a-button type="primary" icon="menu">
|
||||
<template v-if="!isMobile">{{ i18n "pages.inbounds.generalActions" }}</template>
|
||||
</a-button>
|
||||
<a-menu slot="overlay" @click="a => generalActions(a)" :theme="themeSwitcher.currentTheme">
|
||||
<a-menu-item key="import">
|
||||
<a-icon type="import"></a-icon>
|
||||
{{ i18n "pages.inbounds.importInbound" }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="export">
|
||||
<a-icon type="export"></a-icon>
|
||||
{{ i18n "pages.inbounds.export" }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="subs" v-if="subSettings.enable">
|
||||
<a-icon type="export"></a-icon>
|
||||
{{ i18n "pages.inbounds.export" }} - {{ i18n "pages.settings.subSettings" }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="resetInbounds">
|
||||
<a-icon type="reload"></a-icon>
|
||||
{{ i18n "pages.inbounds.resetAllTraffic" }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="resetClients">
|
||||
<a-icon type="file-done"></a-icon>
|
||||
{{ i18n "pages.inbounds.resetAllClientTraffics" }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="delDepletedClients" style="color: #FF4D4F;">
|
||||
<a-icon type="rest"></a-icon>
|
||||
{{ i18n "pages.inbounds.delDepletedClients" }}
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</a-dropdown>
|
||||
</a-col>
|
||||
<a-col :xs="12" :sm="12" :lg="12" style="text-align: right;">
|
||||
<a-select v-model="refreshInterval"
|
||||
style="width: 65px;"
|
||||
v-if="isRefreshEnabled"
|
||||
@change="changeRefreshInterval"
|
||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="key in [5,10,30,60]" :value="key*1000">[[ key ]]s</a-select-option>
|
||||
</a-select>
|
||||
<a-icon type="sync" :spin="refreshing" @click="manualRefresh" style="margin: 0 5px;"></a-icon>
|
||||
<a-switch v-model="isRefreshEnabled" @change="toggleRefresh"></a-switch>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
<div :style="isMobile ? '' : 'display: flex; align-items: center; justify-content: flex-start;'">
|
||||
<a-switch v-model="enableFilter"
|
||||
:style="isMobile ? 'margin-bottom: .5rem; display: flex;' : 'margin-right: .5rem;'"
|
||||
@change="toggleFilter">
|
||||
<a-icon slot="checkedChildren" type="search"></a-icon>
|
||||
<a-icon slot="unCheckedChildren" type="filter"></a-icon>
|
||||
</a-switch>
|
||||
<a-input v-if="!enableFilter" v-model.lazy="searchKey" placeholder='{{ i18n "search" }}' autofocus style="max-width: 300px" :size="isMobile ? 'small' : ''"></a-input>
|
||||
<a-radio-group v-if="enableFilter" v-model="filterBy" @change="filterInbounds" button-style="solid" :size="isMobile ? 'small' : ''">
|
||||
<a-radio-button value="">{{ i18n "none" }}</a-radio-button>
|
||||
<a-radio-button value="deactive">{{ i18n "disabled" }}</a-radio-button>
|
||||
<a-radio-button value="depleted">{{ i18n "depleted" }}</a-radio-button>
|
||||
<a-radio-button value="expiring">{{ i18n "depletingSoon" }}</a-radio-button>
|
||||
<a-radio-button value="online">{{ i18n "online" }}</a-radio-button>
|
||||
</a-radio-group>
|
||||
</div>
|
||||
<a-back-top></a-back-top>
|
||||
<a-table :columns="isMobile ? mobileColumns : columns" :row-key="dbInbound => dbInbound.id"
|
||||
:data-source="searchedInbounds"
|
||||
:scroll="isMobile ? {} : { x: 1000 }"
|
||||
:pagination=pagination(searchedInbounds)
|
||||
:expand-icon-as-cell="false"
|
||||
:expand-row-by-click="false"
|
||||
:expand-icon-column-index="0"
|
||||
:indent-size="0"
|
||||
:row-class-name="dbInbound => (dbInbound.isMultiUser() ? '' : 'hideExpandIcon')"
|
||||
style="margin-top: 10px">
|
||||
<template slot="action" slot-scope="text, dbInbound">
|
||||
<template #title>
|
||||
<a-space direction="horizontal">
|
||||
<a-button type="primary" icon="plus" @click="openAddInbound">
|
||||
<template v-if="!isMobile">{{ i18n "pages.inbounds.addInbound" }}</template>
|
||||
</a-button>
|
||||
<a-dropdown :trigger="['click']">
|
||||
<a-icon @click="e => e.preventDefault()" type="more" style="font-size: 20px; text-decoration: solid;"></a-icon>
|
||||
<a-menu slot="overlay" @click="a => clickAction(a, dbInbound)" :theme="themeSwitcher.currentTheme">
|
||||
<a-menu-item key="edit">
|
||||
<a-icon type="edit"></a-icon>
|
||||
{{ i18n "edit" }}
|
||||
<a-button type="primary" icon="menu">
|
||||
<template v-if="!isMobile">{{ i18n "pages.inbounds.generalActions" }}</template>
|
||||
</a-button>
|
||||
<a-menu slot="overlay" @click="a => generalActions(a)" :theme="themeSwitcher.currentTheme">
|
||||
<a-menu-item key="import">
|
||||
<a-icon type="import"></a-icon>
|
||||
{{ i18n "pages.inbounds.importInbound" }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="qrcode" v-if="(dbInbound.isSS && !dbInbound.toInbound().isSSMultiUser) || dbInbound.isWireguard">
|
||||
<a-icon type="qrcode"></a-icon>
|
||||
{{ i18n "qrCode" }}
|
||||
<a-menu-item key="export">
|
||||
<a-icon type="export"></a-icon>
|
||||
{{ i18n "pages.inbounds.export" }}
|
||||
</a-menu-item>
|
||||
<template v-if="dbInbound.isMultiUser()">
|
||||
<a-menu-item key="addClient">
|
||||
<a-icon type="user-add"></a-icon>
|
||||
{{ i18n "pages.client.add"}}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="addBulkClient">
|
||||
<a-icon type="usergroup-add"></a-icon>
|
||||
{{ i18n "pages.client.bulk"}}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="resetClients">
|
||||
<a-icon type="file-done"></a-icon>
|
||||
{{ i18n "pages.inbounds.resetInboundClientTraffics"}}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="export">
|
||||
<a-icon type="export"></a-icon>
|
||||
{{ i18n "pages.inbounds.export"}}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="subs" v-if="subSettings.enable">
|
||||
<a-icon type="export"></a-icon>
|
||||
{{ i18n "pages.inbounds.export"}} - {{ i18n "pages.settings.subSettings" }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="delDepletedClients" style="color: #FF4D4F;">
|
||||
<a-icon type="rest"></a-icon>
|
||||
{{ i18n "pages.inbounds.delDepletedClients" }}
|
||||
</a-menu-item>
|
||||
</template>
|
||||
<template v-else>
|
||||
<a-menu-item key="showInfo">
|
||||
<a-icon type="info-circle"></a-icon>
|
||||
{{ i18n "info"}}
|
||||
</a-menu-item>
|
||||
</template>
|
||||
<a-menu-item key="clipboard">
|
||||
<a-icon type="copy"></a-icon>
|
||||
{{ i18n "pages.inbounds.exportInbound" }}
|
||||
<a-menu-item key="subs" v-if="subSettings.enable">
|
||||
<a-icon type="export"></a-icon>
|
||||
{{ i18n "pages.inbounds.export" }} - {{ i18n "pages.settings.subSettings" }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="resetTraffic">
|
||||
<a-icon type="retweet"></a-icon> {{ i18n "pages.inbounds.resetTraffic" }}
|
||||
<a-menu-item key="resetInbounds">
|
||||
<a-icon type="reload"></a-icon>
|
||||
{{ i18n "pages.inbounds.resetAllTraffic" }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="clone">
|
||||
<a-icon type="block"></a-icon> {{ i18n "pages.inbounds.clone"}}
|
||||
<a-menu-item key="resetClients">
|
||||
<a-icon type="file-done"></a-icon>
|
||||
{{ i18n "pages.inbounds.resetAllClientTraffics" }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="delete">
|
||||
<span style="color: #FF4D4F">
|
||||
<a-icon type="delete"></a-icon> {{ i18n "delete"}}
|
||||
</span>
|
||||
</a-menu-item>
|
||||
<a-menu-item v-if="isMobile">
|
||||
<a-switch size="small" v-model="dbInbound.enable" @change="switchEnable(dbInbound.id,dbInbound.enable)"></a-switch>
|
||||
{{ i18n "pages.inbounds.enable" }}
|
||||
<a-menu-item key="delDepletedClients" style="color: #FF4D4F;">
|
||||
<a-icon type="rest"></a-icon>
|
||||
{{ i18n "pages.inbounds.delDepletedClients" }}
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</a-dropdown>
|
||||
</template>
|
||||
<template slot="protocol" slot-scope="text, dbInbound">
|
||||
<a-tag style="margin:0;" color="purple">[[ dbInbound.protocol ]]</a-tag>
|
||||
<template v-if="dbInbound.isVMess || dbInbound.isVLess || dbInbound.isTrojan || dbInbound.isSS">
|
||||
<a-tag style="margin:0;" color="green">[[ dbInbound.toInbound().stream.network ]]</a-tag>
|
||||
<a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isTls" color="blue">TLS</a-tag>
|
||||
<a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isReality" color="blue">Reality</a-tag>
|
||||
</a-space>
|
||||
</template>
|
||||
<template #extra>
|
||||
<a-button-group>
|
||||
<a-button icon="sync" @click="manualRefresh" :loading="refreshing"></a-button>
|
||||
<a-popover placement="bottomRight" trigger="click" :overlay-class-name="themeSwitcher.currentTheme">
|
||||
<template #title>
|
||||
<div class="ant-custom-popover-title">
|
||||
<a-switch v-model="isRefreshEnabled" @change="toggleRefresh" size="small"></a-switch>
|
||||
<span>{{ i18n "pages.inbounds.autoRefresh" }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #content>
|
||||
<a-space direction="vertical">
|
||||
<span>{{ i18n "pages.inbounds.autoRefreshInterval" }}</span>
|
||||
<a-select v-model="refreshInterval"
|
||||
:disabled="!isRefreshEnabled"
|
||||
style="width: 100%;"
|
||||
@change="changeRefreshInterval"
|
||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="key in [5,10,30,60]" :value="key*1000">[[ key ]]s</a-select-option>
|
||||
</a-select>
|
||||
</a-space>
|
||||
</template>
|
||||
<a-button icon="down"></a-button>
|
||||
</a-popover>
|
||||
</a-button-group>
|
||||
</template>
|
||||
<a-space direction="vertical">
|
||||
<div :style="isMobile ? '' : 'display: flex; align-items: center; justify-content: flex-start;'">
|
||||
<a-switch v-model="enableFilter"
|
||||
:style="isMobile ? 'margin-bottom: .5rem; display: flex;' : 'margin-right: .5rem;'"
|
||||
@change="toggleFilter">
|
||||
<a-icon slot="checkedChildren" type="search"></a-icon>
|
||||
<a-icon slot="unCheckedChildren" type="filter"></a-icon>
|
||||
</a-switch>
|
||||
<a-input v-if="!enableFilter" v-model.lazy="searchKey" placeholder='{{ i18n "search" }}' autofocus style="max-width: 300px" :size="isMobile ? 'small' : ''"></a-input>
|
||||
<a-radio-group v-if="enableFilter" v-model="filterBy" @change="filterInbounds" button-style="solid" :size="isMobile ? 'small' : ''">
|
||||
<a-radio-button value="">{{ i18n "none" }}</a-radio-button>
|
||||
<a-radio-button value="deactive">{{ i18n "disabled" }}</a-radio-button>
|
||||
<a-radio-button value="depleted">{{ i18n "depleted" }}</a-radio-button>
|
||||
<a-radio-button value="expiring">{{ i18n "depletingSoon" }}</a-radio-button>
|
||||
<a-radio-button value="online">{{ i18n "online" }}</a-radio-button>
|
||||
</a-radio-group>
|
||||
</div>
|
||||
<a-table :columns="isMobile ? mobileColumns : columns" :row-key="dbInbound => dbInbound.id"
|
||||
:data-source="searchedInbounds"
|
||||
:scroll="isMobile ? {} : { x: 1000 }"
|
||||
:pagination=pagination(searchedInbounds)
|
||||
:expand-icon-as-cell="false"
|
||||
:expand-row-by-click="false"
|
||||
:expand-icon-column-index="0"
|
||||
:indent-size="0"
|
||||
:row-class-name="dbInbound => (dbInbound.isMultiUser() ? '' : 'hideExpandIcon')"
|
||||
style="margin-top: 10px">
|
||||
<template slot="action" slot-scope="text, dbInbound">
|
||||
<a-dropdown :trigger="['click']">
|
||||
<a-icon @click="e => e.preventDefault()" type="more" style="font-size: 20px; text-decoration: solid;"></a-icon>
|
||||
<a-menu slot="overlay" @click="a => clickAction(a, dbInbound)" :theme="themeSwitcher.currentTheme">
|
||||
<a-menu-item key="edit">
|
||||
<a-icon type="edit"></a-icon>
|
||||
{{ i18n "edit" }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="qrcode" v-if="(dbInbound.isSS && !dbInbound.toInbound().isSSMultiUser) || dbInbound.isWireguard">
|
||||
<a-icon type="qrcode"></a-icon>
|
||||
{{ i18n "qrCode" }}
|
||||
</a-menu-item>
|
||||
<template v-if="dbInbound.isMultiUser()">
|
||||
<a-menu-item key="addClient">
|
||||
<a-icon type="user-add"></a-icon>
|
||||
{{ i18n "pages.client.add"}}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="addBulkClient">
|
||||
<a-icon type="usergroup-add"></a-icon>
|
||||
{{ i18n "pages.client.bulk"}}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="resetClients">
|
||||
<a-icon type="file-done"></a-icon>
|
||||
{{ i18n "pages.inbounds.resetInboundClientTraffics"}}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="export">
|
||||
<a-icon type="export"></a-icon>
|
||||
{{ i18n "pages.inbounds.export"}}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="subs" v-if="subSettings.enable">
|
||||
<a-icon type="export"></a-icon>
|
||||
{{ i18n "pages.inbounds.export"}} - {{ i18n "pages.settings.subSettings" }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="delDepletedClients" style="color: #FF4D4F;">
|
||||
<a-icon type="rest"></a-icon>
|
||||
{{ i18n "pages.inbounds.delDepletedClients" }}
|
||||
</a-menu-item>
|
||||
</template>
|
||||
<template v-else>
|
||||
<a-menu-item key="showInfo">
|
||||
<a-icon type="info-circle"></a-icon>
|
||||
{{ i18n "info"}}
|
||||
</a-menu-item>
|
||||
</template>
|
||||
<a-menu-item key="clipboard">
|
||||
<a-icon type="copy"></a-icon>
|
||||
{{ i18n "pages.inbounds.exportInbound" }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="resetTraffic">
|
||||
<a-icon type="retweet"></a-icon> {{ i18n "pages.inbounds.resetTraffic" }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="clone">
|
||||
<a-icon type="block"></a-icon> {{ i18n "pages.inbounds.clone"}}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="delete">
|
||||
<span style="color: #FF4D4F">
|
||||
<a-icon type="delete"></a-icon> {{ i18n "delete"}}
|
||||
</span>
|
||||
</a-menu-item>
|
||||
<a-menu-item v-if="isMobile">
|
||||
<a-switch size="small" v-model="dbInbound.enable" @change="switchEnable(dbInbound.id,dbInbound.enable)"></a-switch>
|
||||
{{ i18n "pages.inbounds.enable" }}
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</a-dropdown>
|
||||
</template>
|
||||
</template>
|
||||
<template slot="clients" slot-scope="text, dbInbound">
|
||||
<template v-if="clientCount[dbInbound.id]">
|
||||
<a-tag style="margin:0;" color="green">[[ clientCount[dbInbound.id].clients ]]</a-tag>
|
||||
<a-popover title='{{ i18n "disabled" }}' :overlay-class-name="themeSwitcher.currentTheme">
|
||||
<template slot="protocol" slot-scope="text, dbInbound">
|
||||
<a-tag style="margin:0;" color="purple">[[ dbInbound.protocol ]]</a-tag>
|
||||
<template v-if="dbInbound.isVMess || dbInbound.isVLess || dbInbound.isTrojan || dbInbound.isSS">
|
||||
<a-tag style="margin:0;" color="green">[[ dbInbound.toInbound().stream.network ]]</a-tag>
|
||||
<a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isTls" color="blue">TLS</a-tag>
|
||||
<a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isReality" color="blue">Reality</a-tag>
|
||||
</template>
|
||||
</template>
|
||||
<template slot="clients" slot-scope="text, dbInbound">
|
||||
<template v-if="clientCount[dbInbound.id]">
|
||||
<a-tag style="margin:0;" color="green">[[ clientCount[dbInbound.id].clients ]]</a-tag>
|
||||
<a-popover title='{{ i18n "disabled" }}' :overlay-class-name="themeSwitcher.currentTheme">
|
||||
<template slot="content">
|
||||
<div v-for="clientEmail in clientCount[dbInbound.id].deactive"><span>[[ clientEmail ]]</span></div>
|
||||
</template>
|
||||
<a-tag style="margin:0; padding: 0 2px;" v-if="clientCount[dbInbound.id].deactive.length">[[ clientCount[dbInbound.id].deactive.length ]]</a-tag>
|
||||
</a-popover>
|
||||
<a-popover title='{{ i18n "depleted" }}' :overlay-class-name="themeSwitcher.currentTheme">
|
||||
<template slot="content">
|
||||
<div v-for="clientEmail in clientCount[dbInbound.id].depleted"><span>[[ clientEmail ]]</span></div>
|
||||
</template>
|
||||
<a-tag style="margin:0; padding: 0 2px;" color="red" v-if="clientCount[dbInbound.id].depleted.length">[[ clientCount[dbInbound.id].depleted.length ]]</a-tag>
|
||||
</a-popover>
|
||||
<a-popover title='{{ i18n "depletingSoon" }}' :overlay-class-name="themeSwitcher.currentTheme">
|
||||
<template slot="content">
|
||||
<div v-for="clientEmail in clientCount[dbInbound.id].expiring"><span>[[ clientEmail ]]</span></div>
|
||||
</template>
|
||||
<a-tag style="margin:0; padding: 0 2px;" color="orange" v-if="clientCount[dbInbound.id].expiring.length">[[ clientCount[dbInbound.id].expiring.length ]]</a-tag>
|
||||
</a-popover>
|
||||
<a-popover title='{{ i18n "online" }}' :overlay-class-name="themeSwitcher.currentTheme">
|
||||
<template slot="content">
|
||||
<div v-for="clientEmail in clientCount[dbInbound.id].online"><span>[[ clientEmail ]]</span></div>
|
||||
</template>
|
||||
<a-tag style="margin:0; padding: 0 2px;" color="blue" v-if="clientCount[dbInbound.id].online.length">[[ clientCount[dbInbound.id].online.length ]]</a-tag>
|
||||
</a-popover>
|
||||
</template>
|
||||
</template>
|
||||
<template slot="traffic" slot-scope="text, dbInbound">
|
||||
<a-popover :overlay-class-name="themeSwitcher.currentTheme">
|
||||
<template slot="content">
|
||||
<p v-for="clientEmail in clientCount[dbInbound.id].deactive">[[ clientEmail ]]</p>
|
||||
<table cellpadding="2" width="100%">
|
||||
<tr>
|
||||
<td>↑[[ SizeFormatter.sizeFormat(dbInbound.up) ]]</td>
|
||||
<td>↓[[ SizeFormatter.sizeFormat(dbInbound.down) ]]</td>
|
||||
</tr>
|
||||
<tr v-if="dbInbound.total > 0 && dbInbound.up + dbInbound.down < dbInbound.total">
|
||||
<td>{{ i18n "remained" }}</td>
|
||||
<td>[[ SizeFormatter.sizeFormat(dbInbound.total - dbInbound.up - dbInbound.down) ]]</td>
|
||||
</tr>
|
||||
</table>
|
||||
</template>
|
||||
<a-tag style="margin:0; padding: 0 2px;" v-if="clientCount[dbInbound.id].deactive.length">[[ clientCount[dbInbound.id].deactive.length ]]</a-tag>
|
||||
</a-popover>
|
||||
<a-popover title='{{ i18n "depleted" }}' :overlay-class-name="themeSwitcher.currentTheme">
|
||||
<template slot="content">
|
||||
<p v-for="clientEmail in clientCount[dbInbound.id].depleted">[[ clientEmail ]]</p>
|
||||
</template>
|
||||
<a-tag style="margin:0; padding: 0 2px;" color="red" v-if="clientCount[dbInbound.id].depleted.length">[[ clientCount[dbInbound.id].depleted.length ]]</a-tag>
|
||||
</a-popover>
|
||||
<a-popover title='{{ i18n "depletingSoon" }}' :overlay-class-name="themeSwitcher.currentTheme">
|
||||
<template slot="content">
|
||||
<p v-for="clientEmail in clientCount[dbInbound.id].expiring">[[ clientEmail ]]</p>
|
||||
</template>
|
||||
<a-tag style="margin:0; padding: 0 2px;" color="orange" v-if="clientCount[dbInbound.id].expiring.length">[[ clientCount[dbInbound.id].expiring.length ]]</a-tag>
|
||||
</a-popover>
|
||||
<a-popover title='{{ i18n "online" }}' :overlay-class-name="themeSwitcher.currentTheme">
|
||||
<template slot="content">
|
||||
<p v-for="clientEmail in clientCount[dbInbound.id].online">[[ clientEmail ]]</p>
|
||||
</template>
|
||||
<a-tag style="margin:0; padding: 0 2px;" color="blue" v-if="clientCount[dbInbound.id].online.length">[[ clientCount[dbInbound.id].online.length ]]</a-tag>
|
||||
<a-tag :color="ColorUtils.usageColor(dbInbound.up + dbInbound.down, app.trafficDiff, dbInbound.total)">
|
||||
[[ SizeFormatter.sizeFormat(dbInbound.up + dbInbound.down) ]] /
|
||||
<template v-if="dbInbound.total > 0">
|
||||
[[ SizeFormatter.sizeFormat(dbInbound.total) ]]
|
||||
</template>
|
||||
<template v-else>
|
||||
<svg height="10px" width="14px" viewBox="0 0 640 512" fill="currentColor">
|
||||
<path d="M484.4 96C407 96 349.2 164.1 320 208.5C290.8 164.1 233 96 155.6 96C69.75 96 0 167.8 0 256s69.75 160 155.6 160C233.1 416 290.8 347.9 320 303.5C349.2 347.9 407 416 484.4 416C570.3 416 640 344.2 640 256S570.3 96 484.4 96zM155.6 368C96.25 368 48 317.8 48 256s48.25-112 107.6-112c67.75 0 120.5 82.25 137.1 112C276 285.8 223.4 368 155.6 368zM484.4 368c-67.75 0-120.5-82.25-137.1-112C364 226.2 416.6 144 484.4 144C543.8 144 592 194.2 592 256S543.8 368 484.4 368z" fill="currentColor"></path>
|
||||
</svg>
|
||||
</template>
|
||||
</a-tag>
|
||||
</a-popover>
|
||||
</template>
|
||||
</template>
|
||||
<template slot="traffic" slot-scope="text, dbInbound">
|
||||
<a-popover :overlay-class-name="themeSwitcher.currentTheme">
|
||||
<template slot="content">
|
||||
<table cellpadding="2" width="100%">
|
||||
<tr>
|
||||
<td>↑[[ SizeFormatter.sizeFormat(dbInbound.up) ]]</td>
|
||||
<td>↓[[ SizeFormatter.sizeFormat(dbInbound.down) ]]</td>
|
||||
</tr>
|
||||
<tr v-if="dbInbound.total > 0 && dbInbound.up + dbInbound.down < dbInbound.total">
|
||||
<td>{{ i18n "remained" }}</td>
|
||||
<td>[[ SizeFormatter.sizeFormat(dbInbound.total - dbInbound.up - dbInbound.down) ]]</td>
|
||||
</tr>
|
||||
</table>
|
||||
</template>
|
||||
<a-tag :color="ColorUtils.usageColor(dbInbound.up + dbInbound.down, app.trafficDiff, dbInbound.total)">
|
||||
[[ SizeFormatter.sizeFormat(dbInbound.up + dbInbound.down) ]] /
|
||||
<template v-if="dbInbound.total > 0">
|
||||
[[ SizeFormatter.sizeFormat(dbInbound.total) ]]
|
||||
<template slot="enable" slot-scope="text, dbInbound">
|
||||
<a-switch v-model="dbInbound.enable" @change="switchEnable(dbInbound.id,dbInbound.enable)"></a-switch>
|
||||
</template>
|
||||
<template slot="expiryTime" slot-scope="text, dbInbound">
|
||||
<a-popover v-if="dbInbound.expiryTime > 0" :overlay-class-name="themeSwitcher.currentTheme">
|
||||
<template slot="content" v-if="app.datepicker === 'gregorian'">
|
||||
[[ DateUtil.formatMillis(dbInbound.expiryTime) ]]
|
||||
</template>
|
||||
<template v-else>
|
||||
<svg height="10px" width="14px" viewBox="0 0 640 512" fill="currentColor">
|
||||
<path d="M484.4 96C407 96 349.2 164.1 320 208.5C290.8 164.1 233 96 155.6 96C69.75 96 0 167.8 0 256s69.75 160 155.6 160C233.1 416 290.8 347.9 320 303.5C349.2 347.9 407 416 484.4 416C570.3 416 640 344.2 640 256S570.3 96 484.4 96zM155.6 368C96.25 368 48 317.8 48 256s48.25-112 107.6-112c67.75 0 120.5 82.25 137.1 112C276 285.8 223.4 368 155.6 368zM484.4 368c-67.75 0-120.5-82.25-137.1-112C364 226.2 416.6 144 484.4 144C543.8 144 592 194.2 592 256S543.8 368 484.4 368z" fill="currentColor"></path>
|
||||
</svg>
|
||||
<template v-else slot="content">
|
||||
[[ DateUtil.convertToJalalian(moment(dbInbound.expiryTime)) ]]
|
||||
</template>
|
||||
<a-tag style="min-width: 50px;" :color="ColorUtils.usageColor(new Date().getTime(), app.expireDiff, dbInbound._expiryTime)">
|
||||
[[ remainedDays(dbInbound._expiryTime) ]]
|
||||
</a-tag>
|
||||
</a-popover>
|
||||
<a-tag v-else color="purple" class="infinite-tag">
|
||||
<svg height="10px" width="14px" viewBox="0 0 640 512" fill="currentColor">
|
||||
<path d="M484.4 96C407 96 349.2 164.1 320 208.5C290.8 164.1 233 96 155.6 96C69.75 96 0 167.8 0 256s69.75 160 155.6 160C233.1 416 290.8 347.9 320 303.5C349.2 347.9 407 416 484.4 416C570.3 416 640 344.2 640 256S570.3 96 484.4 96zM155.6 368C96.25 368 48 317.8 48 256s48.25-112 107.6-112c67.75 0 120.5 82.25 137.1 112C276 285.8 223.4 368 155.6 368zM484.4 368c-67.75 0-120.5-82.25-137.1-112C364 226.2 416.6 144 484.4 144C543.8 144 592 194.2 592 256S543.8 368 484.4 368z" fill="currentColor"></path>
|
||||
</svg>
|
||||
</a-tag>
|
||||
</a-popover>
|
||||
</template>
|
||||
<template slot="enable" slot-scope="text, dbInbound">
|
||||
<a-switch v-model="dbInbound.enable" @change="switchEnable(dbInbound.id,dbInbound.enable)"></a-switch>
|
||||
</template>
|
||||
<template slot="expiryTime" slot-scope="text, dbInbound">
|
||||
<a-popover v-if="dbInbound.expiryTime > 0" :overlay-class-name="themeSwitcher.currentTheme">
|
||||
<template slot="content" v-if="app.datepicker === 'gregorian'">
|
||||
[[ DateUtil.formatMillis(dbInbound.expiryTime) ]]
|
||||
</template>
|
||||
<template v-else slot="content">
|
||||
[[ DateUtil.convertToJalalian(moment(dbInbound.expiryTime)) ]]
|
||||
</template>
|
||||
<a-tag style="min-width: 50px;" :color="ColorUtils.usageColor(new Date().getTime(), app.expireDiff, dbInbound._expiryTime)">
|
||||
[[ remainedDays(dbInbound._expiryTime) ]]
|
||||
</a-tag>
|
||||
</a-popover>
|
||||
<a-tag v-else color="purple" class="infinite-tag">
|
||||
<svg height="10px" width="14px" viewBox="0 0 640 512" fill="currentColor">
|
||||
<path d="M484.4 96C407 96 349.2 164.1 320 208.5C290.8 164.1 233 96 155.6 96C69.75 96 0 167.8 0 256s69.75 160 155.6 160C233.1 416 290.8 347.9 320 303.5C349.2 347.9 407 416 484.4 416C570.3 416 640 344.2 640 256S570.3 96 484.4 96zM155.6 368C96.25 368 48 317.8 48 256s48.25-112 107.6-112c67.75 0 120.5 82.25 137.1 112C276 285.8 223.4 368 155.6 368zM484.4 368c-67.75 0-120.5-82.25-137.1-112C364 226.2 416.6 144 484.4 144C543.8 144 592 194.2 592 256S543.8 368 484.4 368z" fill="currentColor"></path>
|
||||
</svg>
|
||||
</a-tag>
|
||||
</template>
|
||||
<template slot="info" slot-scope="text, dbInbound">
|
||||
<a-popover placement="bottomRight" :overlay-class-name="themeSwitcher.currentTheme" trigger="click">
|
||||
<template slot="content">
|
||||
<table cellpadding="2">
|
||||
<tr>
|
||||
<td>{{ i18n "pages.inbounds.protocol" }}</td>
|
||||
<td>
|
||||
<a-tag style="margin:0;" color="purple">[[ dbInbound.protocol ]]</a-tag>
|
||||
<template v-if="dbInbound.isVMess || dbInbound.isVLess || dbInbound.isTrojan || dbInbound.isSS">
|
||||
<a-tag style="margin:0;" color="blue">[[ dbInbound.toInbound().stream.network ]]</a-tag>
|
||||
<a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isTls" color="green">tls</a-tag>
|
||||
<a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isReality" color="green">reality</a-tag>
|
||||
</template>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ i18n "pages.inbounds.port" }}</td>
|
||||
<td><a-tag>[[ dbInbound.port ]]</a-tag></td>
|
||||
</tr>
|
||||
<tr v-if="clientCount[dbInbound.id]">
|
||||
<td>{{ i18n "clients" }}</td>
|
||||
<td>
|
||||
<a-tag style="margin:0;" color="blue">[[ clientCount[dbInbound.id].clients ]]</a-tag>
|
||||
<a-popover title='{{ i18n "disabled" }}' :overlay-class-name="themeSwitcher.currentTheme">
|
||||
<template slot="content">
|
||||
<p v-for="clientEmail in clientCount[dbInbound.id].deactive">[[ clientEmail ]]</p>
|
||||
</template>
|
||||
<template slot="info" slot-scope="text, dbInbound">
|
||||
<a-popover placement="bottomRight" :overlay-class-name="themeSwitcher.currentTheme" trigger="click">
|
||||
<template slot="content">
|
||||
<table cellpadding="2">
|
||||
<tr>
|
||||
<td>{{ i18n "pages.inbounds.protocol" }}</td>
|
||||
<td>
|
||||
<a-tag style="margin:0;" color="purple">[[ dbInbound.protocol ]]</a-tag>
|
||||
<template v-if="dbInbound.isVMess || dbInbound.isVLess || dbInbound.isTrojan || dbInbound.isSS">
|
||||
<a-tag style="margin:0;" color="blue">[[ dbInbound.toInbound().stream.network ]]</a-tag>
|
||||
<a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isTls" color="green">tls</a-tag>
|
||||
<a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isReality" color="green">reality</a-tag>
|
||||
</template>
|
||||
<a-tag style="margin:0; padding: 0 2px;" v-if="clientCount[dbInbound.id].deactive.length">[[ clientCount[dbInbound.id].deactive.length ]]</a-tag>
|
||||
</a-popover>
|
||||
<a-popover title='{{ i18n "depleted" }}' :overlay-class-name="themeSwitcher.currentTheme">
|
||||
<template slot="content">
|
||||
<p v-for="clientEmail in clientCount[dbInbound.id].depleted">[[ clientEmail ]]</p>
|
||||
</template>
|
||||
<a-tag style="margin:0; padding: 0 2px;" color="red" v-if="clientCount[dbInbound.id].depleted.length">[[ clientCount[dbInbound.id].depleted.length ]]</a-tag>
|
||||
</a-popover>
|
||||
<a-popover title='{{ i18n "depletingSoon" }}' :overlay-class-name="themeSwitcher.currentTheme">
|
||||
<template slot="content">
|
||||
<p v-for="clientEmail in clientCount[dbInbound.id].expiring">[[ clientEmail ]]</p>
|
||||
</template>
|
||||
<a-tag style="margin:0; padding: 0 2px;" color="orange" v-if="clientCount[dbInbound.id].expiring.length">[[ clientCount[dbInbound.id].expiring.length ]]</a-tag>
|
||||
</a-popover>
|
||||
<a-popover title='{{ i18n "online" }}' :overlay-class-name="themeSwitcher.currentTheme">
|
||||
<template slot="content">
|
||||
<p v-for="clientEmail in clientCount[dbInbound.id].online">[[ clientEmail ]]</p>
|
||||
</template>
|
||||
<a-tag style="margin:0; padding: 0 2px;" color="green" v-if="clientCount[dbInbound.id].online.length">[[ clientCount[dbInbound.id].online.length ]]</a-tag>
|
||||
</a-popover>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ i18n "pages.inbounds.traffic" }}</td>
|
||||
<td>
|
||||
<a-popover :overlay-class-name="themeSwitcher.currentTheme">
|
||||
<template slot="content">
|
||||
<table cellpadding="2" width="100%">
|
||||
<tr>
|
||||
<td>↑[[ SizeFormatter.sizeFormat(dbInbound.up) ]]</td>
|
||||
<td>↓[[ SizeFormatter.sizeFormat(dbInbound.down) ]]</td>
|
||||
</tr>
|
||||
<tr v-if="dbInbound.total > 0 && dbInbound.up + dbInbound.down < dbInbound.total">
|
||||
<td>{{ i18n "remained" }}</td>
|
||||
<td>[[ SizeFormatter.sizeFormat(dbInbound.total - dbInbound.up - dbInbound.down) ]]</td>
|
||||
</tr>
|
||||
</table>
|
||||
</template>
|
||||
<a-tag :color="ColorUtils.usageColor(dbInbound.up + dbInbound.down, app.trafficDiff, dbInbound.total)">
|
||||
[[ SizeFormatter.sizeFormat(dbInbound.up + dbInbound.down) ]] /
|
||||
<template v-if="dbInbound.total > 0">
|
||||
[[ SizeFormatter.sizeFormat(dbInbound.total) ]]
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ i18n "pages.inbounds.port" }}</td>
|
||||
<td><a-tag>[[ dbInbound.port ]]</a-tag></td>
|
||||
</tr>
|
||||
<tr v-if="clientCount[dbInbound.id]">
|
||||
<td>{{ i18n "clients" }}</td>
|
||||
<td>
|
||||
<a-tag style="margin:0;" color="blue">[[ clientCount[dbInbound.id].clients ]]</a-tag>
|
||||
<a-popover title='{{ i18n "disabled" }}' :overlay-class-name="themeSwitcher.currentTheme">
|
||||
<template slot="content">
|
||||
<p v-for="clientEmail in clientCount[dbInbound.id].deactive">[[ clientEmail ]]</p>
|
||||
</template>
|
||||
<a-tag style="margin:0; padding: 0 2px;" v-if="clientCount[dbInbound.id].deactive.length">[[ clientCount[dbInbound.id].deactive.length ]]</a-tag>
|
||||
</a-popover>
|
||||
<a-popover title='{{ i18n "depleted" }}' :overlay-class-name="themeSwitcher.currentTheme">
|
||||
<template slot="content">
|
||||
<p v-for="clientEmail in clientCount[dbInbound.id].depleted">[[ clientEmail ]]</p>
|
||||
</template>
|
||||
<a-tag style="margin:0; padding: 0 2px;" color="red" v-if="clientCount[dbInbound.id].depleted.length">[[ clientCount[dbInbound.id].depleted.length ]]</a-tag>
|
||||
</a-popover>
|
||||
<a-popover title='{{ i18n "depletingSoon" }}' :overlay-class-name="themeSwitcher.currentTheme">
|
||||
<template slot="content">
|
||||
<p v-for="clientEmail in clientCount[dbInbound.id].expiring">[[ clientEmail ]]</p>
|
||||
</template>
|
||||
<a-tag style="margin:0; padding: 0 2px;" color="orange" v-if="clientCount[dbInbound.id].expiring.length">[[ clientCount[dbInbound.id].expiring.length ]]</a-tag>
|
||||
</a-popover>
|
||||
<a-popover title='{{ i18n "online" }}' :overlay-class-name="themeSwitcher.currentTheme">
|
||||
<template slot="content">
|
||||
<p v-for="clientEmail in clientCount[dbInbound.id].online">[[ clientEmail ]]</p>
|
||||
</template>
|
||||
<a-tag style="margin:0; padding: 0 2px;" color="green" v-if="clientCount[dbInbound.id].online.length">[[ clientCount[dbInbound.id].online.length ]]</a-tag>
|
||||
</a-popover>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ i18n "pages.inbounds.traffic" }}</td>
|
||||
<td>
|
||||
<a-popover :overlay-class-name="themeSwitcher.currentTheme">
|
||||
<template slot="content">
|
||||
<table cellpadding="2" width="100%">
|
||||
<tr>
|
||||
<td>↑[[ SizeFormatter.sizeFormat(dbInbound.up) ]]</td>
|
||||
<td>↓[[ SizeFormatter.sizeFormat(dbInbound.down) ]]</td>
|
||||
</tr>
|
||||
<tr v-if="dbInbound.total > 0 && dbInbound.up + dbInbound.down < dbInbound.total">
|
||||
<td>{{ i18n "remained" }}</td>
|
||||
<td>[[ SizeFormatter.sizeFormat(dbInbound.total - dbInbound.up - dbInbound.down) ]]</td>
|
||||
</tr>
|
||||
</table>
|
||||
</template>
|
||||
<a-tag :color="ColorUtils.usageColor(dbInbound.up + dbInbound.down, app.trafficDiff, dbInbound.total)">
|
||||
[[ SizeFormatter.sizeFormat(dbInbound.up + dbInbound.down) ]] /
|
||||
<template v-if="dbInbound.total > 0">
|
||||
[[ SizeFormatter.sizeFormat(dbInbound.total) ]]
|
||||
</template>
|
||||
<template v-else>
|
||||
<svg height="10px" width="14px" viewBox="0 0 640 512" fill="currentColor">
|
||||
<path d="M484.4 96C407 96 349.2 164.1 320 208.5C290.8 164.1 233 96 155.6 96C69.75 96 0 167.8 0 256s69.75 160 155.6 160C233.1 416 290.8 347.9 320 303.5C349.2 347.9 407 416 484.4 416C570.3 416 640 344.2 640 256S570.3 96 484.4 96zM155.6 368C96.25 368 48 317.8 48 256s48.25-112 107.6-112c67.75 0 120.5 82.25 137.1 112C276 285.8 223.4 368 155.6 368zM484.4 368c-67.75 0-120.5-82.25-137.1-112C364 226.2 416.6 144 484.4 144C543.8 144 592 194.2 592 256S543.8 368 484.4 368z" fill="currentColor"></path>
|
||||
</svg>
|
||||
</template>
|
||||
</a-tag>
|
||||
</a-popover>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ i18n "pages.inbounds.expireDate" }}</td>
|
||||
<td>
|
||||
<a-tag style="min-width: 50px; text-align: center;" v-if="dbInbound.expiryTime > 0"
|
||||
:color="dbInbound.isExpiry? 'red': 'blue'">
|
||||
<template v-if="app.datepicker === 'gregorian'">
|
||||
[[ DateUtil.formatMillis(dbInbound.expiryTime) ]]
|
||||
</template>
|
||||
<template v-else>
|
||||
<svg height="10px" width="14px" viewBox="0 0 640 512" fill="currentColor">
|
||||
<path d="M484.4 96C407 96 349.2 164.1 320 208.5C290.8 164.1 233 96 155.6 96C69.75 96 0 167.8 0 256s69.75 160 155.6 160C233.1 416 290.8 347.9 320 303.5C349.2 347.9 407 416 484.4 416C570.3 416 640 344.2 640 256S570.3 96 484.4 96zM155.6 368C96.25 368 48 317.8 48 256s48.25-112 107.6-112c67.75 0 120.5 82.25 137.1 112C276 285.8 223.4 368 155.6 368zM484.4 368c-67.75 0-120.5-82.25-137.1-112C364 226.2 416.6 144 484.4 144C543.8 144 592 194.2 592 256S543.8 368 484.4 368z" fill="currentColor"></path>
|
||||
</svg>
|
||||
[[ DateUtil.convertToJalalian(moment(dbInbound.expiryTime)) ]]
|
||||
</template>
|
||||
</a-tag>
|
||||
</a-popover>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ i18n "pages.inbounds.expireDate" }}</td>
|
||||
<td>
|
||||
<a-tag style="min-width: 50px; text-align: center;" v-if="dbInbound.expiryTime > 0"
|
||||
:color="dbInbound.isExpiry? 'red': 'blue'">
|
||||
<template v-if="app.datepicker === 'gregorian'">
|
||||
[[ DateUtil.formatMillis(dbInbound.expiryTime) ]]
|
||||
</template>
|
||||
<template v-else>
|
||||
[[ DateUtil.convertToJalalian(moment(dbInbound.expiryTime)) ]]
|
||||
</template>
|
||||
</a-tag>
|
||||
<a-tag v-else style="text-align: center;" color="purple" class="infinite-tag">
|
||||
<svg height="10px" width="14px" viewBox="0 0 640 512" fill="currentColor">
|
||||
<path d="M484.4 96C407 96 349.2 164.1 320 208.5C290.8 164.1 233 96 155.6 96C69.75 96 0 167.8 0 256s69.75 160 155.6 160C233.1 416 290.8 347.9 320 303.5C349.2 347.9 407 416 484.4 416C570.3 416 640 344.2 640 256S570.3 96 484.4 96zM155.6 368C96.25 368 48 317.8 48 256s48.25-112 107.6-112c67.75 0 120.5 82.25 137.1 112C276 285.8 223.4 368 155.6 368zM484.4 368c-67.75 0-120.5-82.25-137.1-112C364 226.2 416.6 144 484.4 144C543.8 144 592 194.2 592 256S543.8 368 484.4 368z" fill="currentColor"></path>
|
||||
</svg>
|
||||
</a-tag>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</template>
|
||||
<a-badge>
|
||||
<a-icon v-if="!dbInbound.enable" slot="count" type="pause-circle" :style="'color: ' + themeSwitcher.isDarkTheme ? '#2c3950' : '#bcbcbc'"></a-icon>
|
||||
<a-button shape="round" size="small" style="font-size: 14px; padding: 0 10px;">
|
||||
<a-icon type="info"></a-icon>
|
||||
</a-button>
|
||||
</a-badge>
|
||||
</a-popover>
|
||||
</template>
|
||||
<template slot="expandedRowRender" slot-scope="record">
|
||||
<a-table
|
||||
:row-key="client => client.id"
|
||||
:columns="isMobile ? innerMobileColumns : innerColumns"
|
||||
:data-source="getInboundClients(record)"
|
||||
:pagination=pagination(getInboundClients(record))
|
||||
:style="isMobile ? 'margin: -10px 2px -11px;' : 'margin: -10px 22px -11px;'">
|
||||
{{template "client_table"}}
|
||||
</a-table>
|
||||
</template>
|
||||
</a-table>
|
||||
<a-tag v-else style="text-align: center;" color="purple" class="infinite-tag">
|
||||
<svg height="10px" width="14px" viewBox="0 0 640 512" fill="currentColor">
|
||||
<path d="M484.4 96C407 96 349.2 164.1 320 208.5C290.8 164.1 233 96 155.6 96C69.75 96 0 167.8 0 256s69.75 160 155.6 160C233.1 416 290.8 347.9 320 303.5C349.2 347.9 407 416 484.4 416C570.3 416 640 344.2 640 256S570.3 96 484.4 96zM155.6 368C96.25 368 48 317.8 48 256s48.25-112 107.6-112c67.75 0 120.5 82.25 137.1 112C276 285.8 223.4 368 155.6 368zM484.4 368c-67.75 0-120.5-82.25-137.1-112C364 226.2 416.6 144 484.4 144C543.8 144 592 194.2 592 256S543.8 368 484.4 368z" fill="currentColor"></path>
|
||||
</svg>
|
||||
</a-tag>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</template>
|
||||
<a-badge>
|
||||
<a-icon v-if="!dbInbound.enable" slot="count" type="pause-circle" :style="'color: ' + themeSwitcher.isDarkTheme ? '#2c3950' : '#bcbcbc'"></a-icon>
|
||||
<a-button shape="round" size="small" style="font-size: 14px; padding: 0 10px;">
|
||||
<a-icon type="info"></a-icon>
|
||||
</a-button>
|
||||
</a-badge>
|
||||
</a-popover>
|
||||
</template>
|
||||
<template slot="expandedRowRender" slot-scope="record">
|
||||
<a-table
|
||||
:row-key="client => client.id"
|
||||
:columns="isMobile ? innerMobileColumns : innerColumns"
|
||||
:data-source="getInboundClients(record)"
|
||||
:pagination=pagination(getInboundClients(record))
|
||||
:style="isMobile ? 'margin: -10px 2px -11px;' : 'margin: -10px 22px -11px;'">
|
||||
{{template "client_table"}}
|
||||
</a-table>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-space>
|
||||
</a-card>
|
||||
</transition>
|
||||
</a-spin>
|
||||
@@ -548,8 +584,9 @@
|
||||
<script src="{{ .base_path }}assets/uri/URI.min.js?{{ .cur_ver }}"></script>
|
||||
<script src="{{ .base_path }}assets/js/model/inbound.js?{{ .cur_ver }}"></script>
|
||||
<script src="{{ .base_path }}assets/js/model/dbinbound.js?{{ .cur_ver }}"></script>
|
||||
{{template "component/themeSwitcher" .}}
|
||||
{{template "component/persianDatepicker" .}}
|
||||
{{template "component/aThemeSwitch" .}}
|
||||
{{template "component/aCustomStatistic" .}}
|
||||
{{template "component/aPersianDatepicker" .}}
|
||||
<script>
|
||||
const columns = [{
|
||||
title: "ID",
|
||||
@@ -662,6 +699,7 @@
|
||||
refreshInterval: Number(localStorage.getItem("refreshInterval")) || 5000,
|
||||
subSettings: {
|
||||
enable : false,
|
||||
subTitle : '',
|
||||
subURI : '',
|
||||
subJsonURI : '',
|
||||
},
|
||||
@@ -711,6 +749,7 @@
|
||||
this.tgBotEnable = tgBotEnable;
|
||||
this.subSettings = {
|
||||
enable : subEnable,
|
||||
subTitle : subTitle,
|
||||
subURI: subURI,
|
||||
subJsonURI: subJsonURI
|
||||
};
|
||||
|
||||
@@ -21,16 +21,55 @@
|
||||
}
|
||||
.ant-backup-list-item {
|
||||
gap: 10px;
|
||||
user-select: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
.dark .ant-backup-list-item svg {
|
||||
.dark .ant-backup-list-item svg,
|
||||
.dark .ant-badge-status-text,
|
||||
.dark .ant-card-extra {
|
||||
color: var(--dark-color-text-primary);
|
||||
}
|
||||
.dark .ant-card-actions>li {
|
||||
color: rgba(255, 255, 255, 0.55);
|
||||
}
|
||||
.dark .ant-radio-inner {
|
||||
background-color: var(--dark-color-surface-100);
|
||||
border-color: var(--dark-color-surface-600);
|
||||
}
|
||||
.dark .ant-radio-checked .ant-radio-inner {
|
||||
border-color: var(--color-primary-100);
|
||||
}
|
||||
.dark .ant-backup-list,
|
||||
.dark .ant-xray-version-list {
|
||||
.dark .ant-xray-version-list,
|
||||
.dark .ant-card-actions,
|
||||
.dark .ant-card-actions>li:not(:last-child) {
|
||||
border-color: var(--dark-color-stroke);
|
||||
}
|
||||
.ant-card-actions {
|
||||
background: transparent;
|
||||
}
|
||||
.ip-hidden {
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
user-select: none;
|
||||
filter: blur(10px);
|
||||
}
|
||||
.running-animation .ant-badge-status-dot {
|
||||
animation: runningAnimation 1.2s linear infinite;
|
||||
}
|
||||
.running-animation .ant-badge-status-processing:after {
|
||||
border-color: var(--color-primary-100);
|
||||
}
|
||||
@keyframes runningAnimation {
|
||||
0%,
|
||||
50%,
|
||||
100% {
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
10% {
|
||||
transform: scale(1.5);
|
||||
opacity: .2;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<body>
|
||||
@@ -48,224 +87,251 @@
|
||||
</a-alert>
|
||||
</transition>
|
||||
<transition name="list" appear>
|
||||
<a-row>
|
||||
<a-card hoverable>
|
||||
<template>
|
||||
<a-row v-if="!status.isLoaded">
|
||||
<a-card hoverable style="text-align: center; padding: 30px 0; margin-top: 10px; background: transparent;">
|
||||
<a-spin tip="Loading..."></a-spin>
|
||||
</a-card>
|
||||
</a-row>
|
||||
<a-row v-else>
|
||||
<a-row>
|
||||
<a-col :sm="24" :md="12">
|
||||
<a-card hoverable>
|
||||
<a-row>
|
||||
<a-col :span="12" style="text-align: center">
|
||||
<a-progress type="dashboard" status="normal"
|
||||
:stroke-color="status.cpu.color"
|
||||
:percent="status.cpu.percent"></a-progress>
|
||||
<div><b>CPU:</b> [[ CPUFormatter.cpuCoreFormat(status.cpuCores) ]] <a-tooltip>
|
||||
<a-icon type="area-chart"></a-icon>
|
||||
<template slot="title">
|
||||
<div><b>Logical Processors:</b> [[ (status.logicalPro) ]]</div>
|
||||
<div><b>Speed:</b> [[ CPUFormatter.cpuSpeedFormat(status.cpuSpeedMhz) ]]</div>
|
||||
</template>
|
||||
</a-tooltip></div>
|
||||
<a-col :sm="24" :md="12">
|
||||
<a-row>
|
||||
<a-col :span="12" style="text-align: center">
|
||||
<a-progress type="dashboard" status="normal"
|
||||
:stroke-color="status.cpu.color"
|
||||
:percent="status.cpu.percent"></a-progress>
|
||||
<div><b>CPU:</b> [[ CPUFormatter.cpuCoreFormat(status.cpuCores) ]] <a-tooltip>
|
||||
<a-icon type="area-chart"></a-icon>
|
||||
<template slot="title">
|
||||
<div><b>Logical Processors:</b> [[ (status.logicalPro) ]]</div>
|
||||
<div><b>Speed:</b> [[ CPUFormatter.cpuSpeedFormat(status.cpuSpeedMhz) ]]</div>
|
||||
</template>
|
||||
</a-tooltip></div>
|
||||
</a-col>
|
||||
<a-col :span="12" style="text-align: center">
|
||||
<a-progress type="dashboard" status="normal"
|
||||
:stroke-color="status.mem.color"
|
||||
:percent="status.mem.percent"></a-progress>
|
||||
<div>
|
||||
<b>{{ i18n "pages.index.memory"}}:</b> [[ SizeFormatter.sizeFormat(status.mem.current) ]] / [[ SizeFormatter.sizeFormat(status.mem.total) ]]
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-col>
|
||||
<a-col :span="12" style="text-align: center">
|
||||
<a-progress type="dashboard" status="normal"
|
||||
:stroke-color="status.mem.color"
|
||||
:percent="status.mem.percent"></a-progress>
|
||||
<div>
|
||||
<b>{{ i18n "pages.index.memory"}}:</b> [[ SizeFormatter.sizeFormat(status.mem.current) ]] / [[ SizeFormatter.sizeFormat(status.mem.total) ]]
|
||||
</div>
|
||||
<a-col :sm="24" :md="12">
|
||||
<a-row>
|
||||
<a-col :span="12" style="text-align: center">
|
||||
<a-progress type="dashboard" status="normal"
|
||||
:stroke-color="status.swap.color"
|
||||
:percent="status.swap.percent"></a-progress>
|
||||
<div>
|
||||
<b>Swap:</b> [[ SizeFormatter.sizeFormat(status.swap.current) ]] / [[ SizeFormatter.sizeFormat(status.swap.total) ]]
|
||||
</div>
|
||||
</a-col>
|
||||
<a-col :span="12" style="text-align: center">
|
||||
<a-progress type="dashboard" status="normal"
|
||||
:stroke-color="status.disk.color"
|
||||
:percent="status.disk.percent"></a-progress>
|
||||
<div>
|
||||
<b>{{ i18n "pages.index.hard"}}:</b> [[ SizeFormatter.sizeFormat(status.disk.current) ]] / [[ SizeFormatter.sizeFormat(status.disk.total) ]]
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-col>
|
||||
<a-col :sm="24" :md="12">
|
||||
<a-row>
|
||||
<a-col :span="12" style="text-align: center">
|
||||
<a-progress type="dashboard" status="normal"
|
||||
:stroke-color="status.swap.color"
|
||||
:percent="status.swap.percent"></a-progress>
|
||||
<div>
|
||||
<b>Swap:</b> [[ SizeFormatter.sizeFormat(status.swap.current) ]] / [[ SizeFormatter.sizeFormat(status.swap.total) ]]
|
||||
</div>
|
||||
</a-col>
|
||||
<a-col :span="12" style="text-align: center">
|
||||
<a-progress type="dashboard" status="normal"
|
||||
:stroke-color="status.disk.color"
|
||||
:percent="status.disk.percent"></a-progress>
|
||||
<div>
|
||||
<b>{{ i18n "pages.index.hard"}}:</b> [[ SizeFormatter.sizeFormat(status.disk.current) ]] / [[ SizeFormatter.sizeFormat(status.disk.total) ]]
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-col>
|
||||
</a-card>
|
||||
</a-row>
|
||||
</a-card>
|
||||
</a-row>
|
||||
</transition>
|
||||
<transition name="list" appear>
|
||||
<a-row>
|
||||
<a-col :sm="24" :lg="12">
|
||||
<a-card hoverable>
|
||||
<b>3X-UI:</b>
|
||||
<a rel="noopener" href="https://github.com/MHSanaei/3x-ui/releases" target="_blank"><a-tag color="green">v{{ .cur_ver }}</a-tag></a>
|
||||
<a rel="noopener" href="https://t.me/XrayUI" target="_blank"><a-tag color="green">@XrayUI</a-tag></a>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :sm="24" :lg="12">
|
||||
<a-card hoverable>
|
||||
<b>{{ i18n "pages.index.operationHours" }}:</b>
|
||||
<a-tag :color="status.xray.color">Xray: [[ TimeFormatter.formatSecond(status.appStats.uptime) ]]</a-tag>
|
||||
<a-tag color="green">OS: [[ TimeFormatter.formatSecond(status.uptime) ]]</a-tag>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :sm="24" :lg="12">
|
||||
<a-card hoverable>
|
||||
<b>{{ i18n "pages.index.xrayStatus" }}:</b>
|
||||
<a-tag style="text-transform: capitalize;" :color="status.xray.color">[[ status.xray.state ]] </a-tag>
|
||||
<a-popover v-if="status.xray.state === State.Error" :overlay-class-name="themeSwitcher.currentTheme">
|
||||
<span slot="title" style="font-size: 12pt">An error occurred while running Xray
|
||||
<a-tag color="purple" style="cursor: pointer; float: right;" @click="openLogs()">{{ i18n "pages.index.logs" }}</a-tag>
|
||||
</span>
|
||||
<template slot="content">
|
||||
<p style="max-width: 400px" v-for="line in status.xray.errorMsg.split('\n')">[[ line ]]</p>
|
||||
<a-col :sm="24" :lg="12">
|
||||
<a-card hoverable>
|
||||
<template #title>
|
||||
<a-space direction="horizontal">
|
||||
<span>{{ i18n "pages.index.xrayStatus" }}</span>
|
||||
<a-tag v-if="isMobile && status.xray.version != 'Unknown'" color="green">
|
||||
v[[ status.xray.version ]]
|
||||
</a-tag>
|
||||
</a-space>
|
||||
</template>
|
||||
<a-icon type="question-circle"></a-icon>
|
||||
</a-popover>
|
||||
<a-tag color="purple" style="cursor: pointer;" @click="stopXrayService">{{ i18n "pages.index.stopXray" }}</a-tag>
|
||||
<a-tag color="purple" style="cursor: pointer;" @click="restartXrayService">{{ i18n "pages.index.restartXray" }}</a-tag>
|
||||
<a-tag color="purple" style="cursor: pointer;" @click="openSelectV2rayVersion">v[[ status.xray.version ]]</a-tag>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :sm="24" :lg="12">
|
||||
<a-card hoverable>
|
||||
<b>{{ i18n "menu.link" }}:</b>
|
||||
<a-tag color="purple" style="cursor: pointer;" @click="openLogs()">{{ i18n "pages.index.logs" }}</a-tag>
|
||||
<a-tag color="purple" style="cursor: pointer;" @click="openConfig">{{ i18n "pages.index.config" }}</a-tag>
|
||||
<a-tag color="purple" style="cursor: pointer;" @click="openBackup">{{ i18n "pages.index.backup" }}</a-tag>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :sm="24" :lg="12">
|
||||
<a-card hoverable>
|
||||
<b>{{ i18n "pages.index.systemLoad" }}:</b>
|
||||
<a-tag color="green">
|
||||
<a-tooltip>
|
||||
[[ status.loads[0] ]] | [[ status.loads[1] ]] | [[ status.loads[2] ]]
|
||||
<template slot="title">
|
||||
{{ i18n "pages.index.systemLoadDesc" }}
|
||||
<template #extra>
|
||||
<template v-if="status.xray.state != State.Error">
|
||||
<a-badge status="processing" class="running-animation" :text="status.xray.state" :color="status.xray.color" style="text-transform: capitalize;"/>
|
||||
</template>
|
||||
</a-tooltip>
|
||||
</a-tag>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :sm="24" :lg="12">
|
||||
<a-card hoverable>
|
||||
<b>{{ i18n "usage"}}:</b>
|
||||
<a-tag color="green"> RAM: [[ SizeFormatter.sizeFormat(status.appStats.mem) ]] </a-tag>
|
||||
<a-tag color="green"> Threads: [[ status.appStats.threads ]] </a-tag>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :sm="24" :lg="12">
|
||||
<a-card hoverable>
|
||||
<a-row>
|
||||
<a-col :span="12">
|
||||
<a-tag>
|
||||
<a-tooltip>
|
||||
<a-icon type="global"></a-icon> IPv4
|
||||
<template slot="title">
|
||||
[[ status.publicIP.ipv4 ]]
|
||||
<template v-else>
|
||||
<a-popover :overlay-class-name="themeSwitcher.currentTheme">
|
||||
<span slot="title" style="font-size: 12pt">An error occurred while running Xray
|
||||
<a-tag color="purple" style="cursor: pointer; float: right;" @click="openLogs()">{{ i18n "pages.index.logs" }}</a-tag>
|
||||
</span>
|
||||
<template slot="content">
|
||||
<p style="max-width: 400px" v-for="line in status.xray.errorMsg.split('\n')">[[ line ]]</p>
|
||||
</template>
|
||||
</a-tooltip>
|
||||
</a-tag>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-tag>
|
||||
<a-tooltip>
|
||||
<a-icon type="global"></a-icon> IPv6
|
||||
<template slot="title">
|
||||
[[ status.publicIP.ipv6 ]]
|
||||
<a-badge :text="status.xray.state" :color="status.xray.color" style="text-transform: capitalize;"/>
|
||||
</a-popover>
|
||||
</template>
|
||||
</template>
|
||||
<template #actions>
|
||||
<a-space direction="horizontal" @click="stopXrayService" style="justify-content: center;">
|
||||
<a-icon type="poweroff"></a-icon>
|
||||
<span v-if="!isMobile">{{ i18n "pages.index.stopXray" }}</span>
|
||||
</a-space>
|
||||
<a-space direction="horizontal" @click="restartXrayService" style="justify-content: center;">
|
||||
<a-icon type="reload"></a-icon>
|
||||
<span v-if="!isMobile">{{ i18n "pages.index.restartXray" }}</span>
|
||||
</a-space>
|
||||
<a-space direction="horizontal" @click="openSelectV2rayVersion" style="justify-content: center;">
|
||||
<a-icon type="tool"></a-icon>
|
||||
<span v-if="!isMobile">
|
||||
[[ status.xray.version != 'Unknown' ? `v${status.xray.version}` : '{{ i18n "pages.index.xraySwitch" }}' ]]
|
||||
</span>
|
||||
</a-space>
|
||||
</template>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :sm="24" :lg="12">
|
||||
<a-card title='{{ i18n "menu.link" }}' hoverable>
|
||||
<template #actions>
|
||||
<a-space direction="horizontal" @click="openLogs()" style="justify-content: center;">
|
||||
<a-icon type="bars"></a-icon>
|
||||
<span v-if="!isMobile">{{ i18n "pages.index.logs" }}</span>
|
||||
</a-space>
|
||||
<a-space direction="horizontal" @click="openConfig" style="justify-content: center;">
|
||||
<a-icon type="control"></a-icon>
|
||||
<span v-if="!isMobile">{{ i18n "pages.index.config" }}</span>
|
||||
</a-space>
|
||||
<a-space direction="horizontal" @click="openBackup" style="justify-content: center;">
|
||||
<a-icon type="cloud-server"></a-icon>
|
||||
<span v-if="!isMobile">{{ i18n "pages.index.backup" }}</span>
|
||||
</a-space>
|
||||
</template>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :sm="24" :lg="12">
|
||||
<a-card title='3X-UI' hoverable>
|
||||
<a rel="noopener" href="https://github.com/MHSanaei/3x-ui/releases" target="_blank"><a-tag color="green">v{{ .cur_ver }}</a-tag></a>
|
||||
<a rel="noopener" href="https://t.me/XrayUI" target="_blank"><a-tag color="green">@XrayUI</a-tag></a>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :sm="24" :lg="12">
|
||||
<a-card title='{{ i18n "pages.index.operationHours" }}' hoverable>
|
||||
<a-tag :color="status.xray.color">Xray: [[ TimeFormatter.formatSecond(status.appStats.uptime) ]]</a-tag>
|
||||
<a-tag color="green">OS: [[ TimeFormatter.formatSecond(status.uptime) ]]</a-tag>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :sm="24" :lg="12">
|
||||
<a-card title='{{ i18n "pages.index.systemLoad" }}' hoverable>
|
||||
<a-tag color="green">
|
||||
<a-tooltip>
|
||||
[[ status.loads[0] ]] | [[ status.loads[1] ]] | [[ status.loads[2] ]]
|
||||
<template slot="title">
|
||||
{{ i18n "pages.index.systemLoadDesc" }}
|
||||
</template>
|
||||
</a-tooltip>
|
||||
</a-tag>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :sm="24" :lg="12">
|
||||
<a-card title='{{ i18n "usage"}}' hoverable>
|
||||
<a-tag color="green"> RAM: [[ SizeFormatter.sizeFormat(status.appStats.mem) ]] </a-tag>
|
||||
<a-tag color="green"> Threads: [[ status.appStats.threads ]] </a-tag>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :sm="24" :lg="12">
|
||||
<a-card title='{{ i18n "pages.index.overallSpeed" }}' hoverable>
|
||||
<a-row>
|
||||
<a-col :span="12">
|
||||
<a-custom-statistic title='{{ i18n "pages.index.upload" }}' :value="SizeFormatter.sizeFormat(status.netIO.up)">
|
||||
<template #prefix>
|
||||
<a-icon type="arrow-up" />
|
||||
</template>
|
||||
</a-tooltip>
|
||||
</a-tag>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :sm="24" :lg="12">
|
||||
<a-card hoverable>
|
||||
<a-row>
|
||||
<a-col :span="12">
|
||||
<a-tag>
|
||||
<a-tooltip>
|
||||
<a-icon type="swap"></a-icon> TCP: [[ status.tcpCount ]]
|
||||
<template slot="title">
|
||||
{{ i18n "pages.index.connectionTcpCountDesc" }}
|
||||
<template #suffix>
|
||||
/s
|
||||
</template>
|
||||
</a-tooltip>
|
||||
</a-tag>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-tag>
|
||||
<a-tooltip>
|
||||
<a-icon type="swap"></a-icon> UDP: [[ status.udpCount ]]
|
||||
<template slot="title">
|
||||
{{ i18n "pages.index.connectionUdpCountDesc" }}
|
||||
</a-custom-statistic>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-custom-statistic title='{{ i18n "pages.index.download" }}' :value="SizeFormatter.sizeFormat(status.netIO.down)">
|
||||
<template #prefix>
|
||||
<a-icon type="arrow-down" />
|
||||
</template>
|
||||
</a-tooltip>
|
||||
</a-tag>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :sm="24" :lg="12">
|
||||
<a-card hoverable>
|
||||
<a-row>
|
||||
<a-col :span="12">
|
||||
<a-tag>
|
||||
<a-tooltip>
|
||||
<a-icon type="arrow-up"></a-icon> Up: [[ SizeFormatter.sizeFormat(status.netIO.up) ]]/s
|
||||
<template slot="title">
|
||||
{{ i18n "pages.index.upSpeed" }}
|
||||
<template #suffix>
|
||||
/s
|
||||
</template>
|
||||
</a-tooltip>
|
||||
</a-tag>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-tag>
|
||||
<a-tooltip>
|
||||
<a-icon type="arrow-down"></a-icon> Down: [[ SizeFormatter.sizeFormat(status.netIO.down) ]]/s
|
||||
<template slot="title">
|
||||
{{ i18n "pages.index.downSpeed" }}
|
||||
</a-custom-statistic>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :sm="24" :lg="12">
|
||||
<a-card title='{{ i18n "pages.index.totalData" }}' hoverable>
|
||||
<a-row>
|
||||
<a-col :span="12">
|
||||
<a-custom-statistic title='{{ i18n "pages.index.sent" }}' :value="SizeFormatter.sizeFormat(status.netTraffic.sent)">
|
||||
<template #prefix>
|
||||
<a-icon type="cloud-upload" />
|
||||
</template>
|
||||
</a-tooltip>
|
||||
</a-tag>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :sm="24" :lg="12">
|
||||
<a-card hoverable>
|
||||
<a-row>
|
||||
<a-col :span="12">
|
||||
<a-tag>
|
||||
<a-tooltip>
|
||||
<a-icon type="cloud-upload"></a-icon>
|
||||
<template slot="title">
|
||||
{{ i18n "pages.index.totalSent" }}
|
||||
</template> Out: [[ SizeFormatter.sizeFormat(status.netTraffic.sent) ]]
|
||||
</a-tooltip>
|
||||
</a-tag>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-tag>
|
||||
<a-tooltip>
|
||||
<a-icon type="cloud-download"></a-icon>
|
||||
<template slot="title">
|
||||
{{ i18n "pages.index.totalReceive" }}
|
||||
</template> In: [[ SizeFormatter.sizeFormat(status.netTraffic.recv) ]]
|
||||
</a-tooltip>
|
||||
</a-tag>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-custom-statistic>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-custom-statistic title='{{ i18n "pages.index.received" }}' :value="SizeFormatter.sizeFormat(status.netTraffic.recv)">
|
||||
<template #prefix>
|
||||
<a-icon type="cloud-download" />
|
||||
</template>
|
||||
</a-custom-statistic>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :sm="24" :lg="12">
|
||||
<a-card title='{{ i18n "pages.index.ipAddresses" }}' hoverable>
|
||||
<template #extra>
|
||||
<a-tooltip>
|
||||
<template #title>
|
||||
{{ i18n "pages.index.toggleIpVisibility" }}
|
||||
</template>
|
||||
<a-icon :type="showIp ? 'eye' : 'eye-invisible'" :style="{ fontSize: '1rem' }" @click="showIp = !showIp"></a-icon>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<a-row :class="showIp ? 'ip-visible' : 'ip-hidden'">
|
||||
<a-col :xs="24" :xxl="12" :style="{ marginTop: isMobile ? '10px' : 0 }">
|
||||
<a-custom-statistic title="IPv4" :value="status.publicIP.ipv4">
|
||||
<template #prefix>
|
||||
<a-icon type="global" />
|
||||
</template>
|
||||
</a-custom-statistic>
|
||||
</a-col>
|
||||
<a-col :xs="24" :xxl="12" :style="{ marginTop: isMobile ? '10px' : 0 }">
|
||||
<a-custom-statistic title="IPv6" :value="status.publicIP.ipv6">
|
||||
<template #prefix>
|
||||
<a-icon type="global" />
|
||||
</template>
|
||||
</a-custom-statistic>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :sm="24" :lg="12">
|
||||
<a-card title='{{ i18n "pages.index.connectionCount" }}' hoverable>
|
||||
<a-row>
|
||||
<a-col :span="12">
|
||||
<a-custom-statistic title="TCP" :value="status.tcpCount">
|
||||
<template #prefix>
|
||||
<a-icon type="swap" />
|
||||
</template>
|
||||
</a-custom-statistic>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-custom-statistic title="UDP" :value="status.udpCount">
|
||||
<template #prefix>
|
||||
<a-icon type="swap" />
|
||||
</template>
|
||||
</a-custom-statistic>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
</transition>
|
||||
</a-spin>
|
||||
</a-layout-content>
|
||||
@@ -275,11 +341,9 @@
|
||||
<a-alert type="warning" style="margin-bottom: 12px; width: 100%;"
|
||||
message='{{ i18n "pages.index.xraySwitchClickDesk" }}' show-icon></a-alert>
|
||||
<a-list class="ant-xray-version-list" bordered style="width: 100%;">
|
||||
<a-list-item class="ant-xray-version-list-item" v-for="version in versionModal.versions">
|
||||
<a-list-item-meta>
|
||||
<template #title>[[ version ]]</template>
|
||||
</a-list-item-meta>
|
||||
<a-radio :checked="version === `v${status.xray.version}`" @click="switchV2rayVersion(version)"></a-radio>
|
||||
<a-list-item class="ant-xray-version-list-item" v-for="version, index in versionModal.versions">
|
||||
<a-tag :color="index % 2 == 0 ? 'purple' : 'green'">[[ version ]]</a-tag>
|
||||
<a-radio :class="themeSwitcher.currentTheme" :checked="version === `v${status.xray.version}`" @click="switchV2rayVersion(version)"></a-radio>
|
||||
</a-list-item>
|
||||
</a-list>
|
||||
</a-modal>
|
||||
@@ -335,34 +399,32 @@
|
||||
footer=""
|
||||
:class="themeSwitcher.currentTheme">
|
||||
<a-list class="ant-backup-list" bordered style="width: 100%;">
|
||||
<a-list-item class="ant-backup-list-item" @click="exportDatabase()">
|
||||
<a-list-item class="ant-backup-list-item">
|
||||
<a-list-item-meta>
|
||||
<template #title>{{ i18n "pages.index.exportDatabase" }}</template>
|
||||
<template #description>{{ i18n "pages.index.exportDatabaseDesc" }}</template>
|
||||
</a-list-item-meta>
|
||||
<a-icon type="right" />
|
||||
<a-button @click="exportDatabase()" type="primary" icon="download"/>
|
||||
</a-list-item>
|
||||
<a-list-item class="ant-backup-list-item" @click="importDatabase()">
|
||||
<a-list-item class="ant-backup-list-item">
|
||||
<a-list-item-meta>
|
||||
<template #title>{{ i18n "pages.index.importDatabase" }}</template>
|
||||
<template #description>{{ i18n "pages.index.importDatabaseDesc" }}</template>
|
||||
<templaet #avatar>
|
||||
<a-icon type="import" />
|
||||
</templaet>
|
||||
</a-list-item-meta>
|
||||
<a-icon type="right" />
|
||||
<a-button @click="importDatabase()" type="primary" icon="upload" />
|
||||
</a-list-item>
|
||||
</a-list>
|
||||
</a-modal>
|
||||
</a-layout>
|
||||
{{template "js" .}}
|
||||
{{template "component/themeSwitcher" .}}
|
||||
{{template "component/aThemeSwitch" .}}
|
||||
{{template "component/aCustomStatistic" .}}
|
||||
{{template "textModal"}}
|
||||
<script>
|
||||
const State = {
|
||||
Running: "running",
|
||||
Stop: "stop",
|
||||
Error: "error",
|
||||
Running: "running",
|
||||
Stop: "stop",
|
||||
Error: "error",
|
||||
}
|
||||
Object.freeze(State);
|
||||
|
||||
@@ -393,7 +455,7 @@
|
||||
}
|
||||
|
||||
class Status {
|
||||
constructor(data) {
|
||||
constructor(data, isLoaded = false) {
|
||||
this.cpu = new CurTotal(0, 0);
|
||||
this.cpuCores = 0;
|
||||
this.logicalPro = 0;
|
||||
@@ -413,8 +475,10 @@
|
||||
this.xray = { state: State.Stop, errorMsg: "", version: "", color: "" };
|
||||
|
||||
if (data == null) {
|
||||
return;
|
||||
return;
|
||||
}
|
||||
|
||||
this.isLoaded = isLoaded;
|
||||
this.cpu = new CurTotal(data.cpu, 100);
|
||||
this.cpuCores = data.cpuCores;
|
||||
this.logicalPro = data.logicalPro;
|
||||
@@ -536,6 +600,8 @@
|
||||
spinning: false,
|
||||
loadingTip: '{{ i18n "loading"}}',
|
||||
showAlert: false,
|
||||
showIp: false,
|
||||
isMobile: window.innerWidth <= 768
|
||||
},
|
||||
methods: {
|
||||
loading(spinning, tip = '{{ i18n "loading"}}') {
|
||||
@@ -546,14 +612,14 @@
|
||||
try {
|
||||
const msg = await HttpUtil.post('/server/status');
|
||||
if (msg.success) {
|
||||
this.setStatus(msg.obj);
|
||||
this.setStatus(msg.obj, true);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Failed to get status:", e);
|
||||
}
|
||||
},
|
||||
setStatus(data) {
|
||||
this.status = new Status(data);
|
||||
setStatus(data, isLoaded = false) {
|
||||
this.status = new Status(data, isLoaded);
|
||||
},
|
||||
async openSelectV2rayVersion() {
|
||||
this.loading(true);
|
||||
|
||||
@@ -377,7 +377,7 @@
|
||||
<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>
|
||||
<a-input-number :min="0" :min="100" v-model="allSetting.tgCpu" style="width: 100%;"></a-switch>
|
||||
</template>
|
||||
</a-setting-list-item>
|
||||
</a-collapse-panel>
|
||||
@@ -409,6 +409,13 @@
|
||||
<a-switch v-model="allSetting.subEnable"></a-switch>
|
||||
</template>
|
||||
</a-setting-list-item>
|
||||
<a-setting-list-item paddings="small">
|
||||
<template #title>{{ i18n "pages.settings.subTitle"}}</template>
|
||||
<template #description>{{ i18n "pages.settings.subTitleDesc"}}</template>
|
||||
<template #control>
|
||||
<a-input type="text" v-model="allSetting.subTitle"></a-input>
|
||||
</template>
|
||||
</a-setting-list-item>
|
||||
<a-setting-list-item paddings="small">
|
||||
<template #title>{{ i18n "pages.settings.subListen"}}</template>
|
||||
<template #description>{{ i18n "pages.settings.subListenDesc"}}</template>
|
||||
@@ -656,9 +663,9 @@
|
||||
</a-layout>
|
||||
{{template "js" .}}
|
||||
<script src="{{ .base_path }}assets/js/model/setting.js?{{ .cur_ver }}"></script>
|
||||
{{template "component/themeSwitcher" .}}
|
||||
{{template "component/password" .}}
|
||||
{{template "component/setting"}}
|
||||
{{template "component/aThemeSwitch" .}}
|
||||
{{template "component/aPasswordInput" .}}
|
||||
{{template "component/aSettingListItem" .}}
|
||||
<script>
|
||||
const app = new Vue({
|
||||
delimiters: ['[[', ']]'],
|
||||
|
||||
@@ -176,10 +176,10 @@
|
||||
},
|
||||
async register() {
|
||||
warpModal.loading(true);
|
||||
keys = Wireguard.generateKeypair();
|
||||
const keys = Wireguard.generateKeypair();
|
||||
const msg = await HttpUtil.post('/panel/xray/warp/reg', keys);
|
||||
if (msg.success) {
|
||||
resp = JSON.parse(msg.obj);
|
||||
const resp = JSON.parse(msg.obj);
|
||||
warpModal.warpData = resp.data;
|
||||
warpModal.warpConfig = resp.config;
|
||||
this.collectConfig();
|
||||
|
||||
@@ -788,9 +788,9 @@
|
||||
</a-layout>
|
||||
</a-layout>
|
||||
{{template "js" .}}
|
||||
{{template "component/themeSwitcher" .}}
|
||||
{{template "component/sortableTable" .}}
|
||||
{{template "component/setting"}}
|
||||
{{template "component/aThemeSwitch" .}}
|
||||
{{template "component/aTableSortable" .}}
|
||||
{{template "component/aSettingListItem" .}}
|
||||
{{template "ruleModal"}}
|
||||
{{template "outModal"}}
|
||||
{{template "reverseModal"}}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"slices"
|
||||
"x-ui/database"
|
||||
"x-ui/database/model"
|
||||
"x-ui/logger"
|
||||
@@ -193,13 +194,7 @@ func (j *CheckClientIpJob) checkError(e error) {
|
||||
}
|
||||
|
||||
func (j *CheckClientIpJob) contains(s []string, str string) bool {
|
||||
for _, v := range s {
|
||||
if v == str {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
return slices.Contains(s, str)
|
||||
}
|
||||
|
||||
func (j *CheckClientIpJob) getInboundClientIps(clientEmail string) (*model.InboundClientIps, error) {
|
||||
|
||||
@@ -52,7 +52,7 @@ func (j *XrayTrafficJob) informTrafficToExternalAPI(inboundTraffics []*xray.Traf
|
||||
logger.Warning("get ExternalTrafficInformURI failed:", err)
|
||||
return
|
||||
}
|
||||
requestBody, err := json.Marshal(map[string]interface{}{"clientTraffics": clientTraffics, "inboundTraffics": inboundTraffics})
|
||||
requestBody, err := json.Marshal(map[string]any{"clientTraffics": clientTraffics, "inboundTraffics": inboundTraffics})
|
||||
if err != nil {
|
||||
logger.Warning("parse client/inbound traffic failed:", err)
|
||||
return
|
||||
|
||||
@@ -48,13 +48,13 @@ func InitLocalizer(i18nFS embed.FS, settingService SettingService) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func createTemplateData(params []string, seperator ...string) map[string]interface{} {
|
||||
func createTemplateData(params []string, seperator ...string) map[string]any {
|
||||
var sep string = "=="
|
||||
if len(seperator) > 0 {
|
||||
sep = seperator[0]
|
||||
}
|
||||
|
||||
templateData := make(map[string]interface{})
|
||||
templateData := make(map[string]any)
|
||||
for _, param := range params {
|
||||
parts := strings.SplitN(param, sep, 2)
|
||||
templateData[parts[0]] = parts[1]
|
||||
|
||||
@@ -413,13 +413,13 @@ func (s *InboundService) AddInboundClient(data *model.Inbound) (bool, error) {
|
||||
return false, err
|
||||
}
|
||||
|
||||
var settings map[string]interface{}
|
||||
var settings map[string]any
|
||||
err = json.Unmarshal([]byte(data.Settings), &settings)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
interfaceClients := settings["clients"].([]interface{})
|
||||
interfaceClients := settings["clients"].([]any)
|
||||
existEmail, err := s.checkEmailsExistForClients(clients)
|
||||
if err != nil {
|
||||
return false, err
|
||||
@@ -450,13 +450,13 @@ func (s *InboundService) AddInboundClient(data *model.Inbound) (bool, error) {
|
||||
}
|
||||
}
|
||||
|
||||
var oldSettings map[string]interface{}
|
||||
var oldSettings map[string]any
|
||||
err = json.Unmarshal([]byte(oldInbound.Settings), &oldSettings)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
oldClients := oldSettings["clients"].([]interface{})
|
||||
oldClients := oldSettings["clients"].([]any)
|
||||
oldClients = append(oldClients, interfaceClients...)
|
||||
|
||||
oldSettings["clients"] = oldClients
|
||||
@@ -489,7 +489,7 @@ func (s *InboundService) AddInboundClient(data *model.Inbound) (bool, error) {
|
||||
if oldInbound.Protocol == "shadowsocks" {
|
||||
cipher = oldSettings["method"].(string)
|
||||
}
|
||||
err1 := s.xrayApi.AddUser(string(oldInbound.Protocol), oldInbound.Tag, map[string]interface{}{
|
||||
err1 := s.xrayApi.AddUser(string(oldInbound.Protocol), oldInbound.Tag, map[string]any{
|
||||
"email": client.Email,
|
||||
"id": client.ID,
|
||||
"security": client.Security,
|
||||
@@ -519,7 +519,7 @@ func (s *InboundService) DelInboundClient(inboundId int, clientId string) (bool,
|
||||
logger.Error("Load Old Data Error")
|
||||
return false, err
|
||||
}
|
||||
var settings map[string]interface{}
|
||||
var settings map[string]any
|
||||
err = json.Unmarshal([]byte(oldInbound.Settings), &settings)
|
||||
if err != nil {
|
||||
return false, err
|
||||
@@ -534,11 +534,11 @@ func (s *InboundService) DelInboundClient(inboundId int, clientId string) (bool,
|
||||
client_key = "email"
|
||||
}
|
||||
|
||||
interfaceClients := settings["clients"].([]interface{})
|
||||
var newClients []interface{}
|
||||
interfaceClients := settings["clients"].([]any)
|
||||
var newClients []any
|
||||
needApiDel := false
|
||||
for _, client := range interfaceClients {
|
||||
c := client.(map[string]interface{})
|
||||
c := client.(map[string]any)
|
||||
c_id := c[client_key].(string)
|
||||
if c_id == clientId {
|
||||
email, _ = c["email"].(string)
|
||||
@@ -607,13 +607,13 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId strin
|
||||
return false, err
|
||||
}
|
||||
|
||||
var settings map[string]interface{}
|
||||
var settings map[string]any
|
||||
err = json.Unmarshal([]byte(data.Settings), &settings)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
interfaceClients := settings["clients"].([]interface{})
|
||||
interfaceClients := settings["clients"].([]any)
|
||||
|
||||
oldInbound, err := s.GetInbound(data.Id)
|
||||
if err != nil {
|
||||
@@ -662,12 +662,12 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId strin
|
||||
}
|
||||
}
|
||||
|
||||
var oldSettings map[string]interface{}
|
||||
var oldSettings map[string]any
|
||||
err = json.Unmarshal([]byte(oldInbound.Settings), &oldSettings)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
settingsClients := oldSettings["clients"].([]interface{})
|
||||
settingsClients := oldSettings["clients"].([]any)
|
||||
settingsClients[clientIndex] = interfaceClients[0]
|
||||
oldSettings["clients"] = settingsClients
|
||||
|
||||
@@ -732,7 +732,7 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId strin
|
||||
if oldInbound.Protocol == "shadowsocks" {
|
||||
cipher = oldSettings["method"].(string)
|
||||
}
|
||||
err1 := s.xrayApi.AddUser(string(oldInbound.Protocol), oldInbound.Tag, map[string]interface{}{
|
||||
err1 := s.xrayApi.AddUser(string(oldInbound.Protocol), oldInbound.Tag, map[string]any{
|
||||
"email": clients[0].Email,
|
||||
"id": clients[0].ID,
|
||||
"security": clients[0].Security,
|
||||
@@ -809,7 +809,7 @@ func (s *InboundService) addInboundTraffic(tx *gorm.DB, traffics []*xray.Traffic
|
||||
for _, traffic := range traffics {
|
||||
if traffic.IsInbound {
|
||||
err = tx.Model(&model.Inbound{}).Where("tag = ?", traffic.Tag).
|
||||
Updates(map[string]interface{}{
|
||||
Updates(map[string]any{
|
||||
"up": gorm.Expr("up + ?", traffic.Up),
|
||||
"down": gorm.Expr("down + ?", traffic.Down),
|
||||
}).Error
|
||||
@@ -893,13 +893,13 @@ func (s *InboundService) adjustTraffics(tx *gorm.DB, dbClientTraffics []*xray.Cl
|
||||
return nil, err
|
||||
}
|
||||
for inbound_index := range inbounds {
|
||||
settings := map[string]interface{}{}
|
||||
settings := map[string]any{}
|
||||
json.Unmarshal([]byte(inbounds[inbound_index].Settings), &settings)
|
||||
clients, ok := settings["clients"].([]interface{})
|
||||
clients, ok := settings["clients"].([]any)
|
||||
if ok {
|
||||
var newClients []interface{}
|
||||
var newClients []any
|
||||
for client_index := range clients {
|
||||
c := clients[client_index].(map[string]interface{})
|
||||
c := clients[client_index].(map[string]any)
|
||||
for traffic_index := range dbClientTraffics {
|
||||
if dbClientTraffics[traffic_index].ExpiryTime < 0 && c["email"] == dbClientTraffics[traffic_index].Email {
|
||||
oldExpiryTime := c["expiryTime"].(float64)
|
||||
@@ -909,7 +909,7 @@ func (s *InboundService) adjustTraffics(tx *gorm.DB, dbClientTraffics []*xray.Cl
|
||||
break
|
||||
}
|
||||
}
|
||||
newClients = append(newClients, interface{}(c))
|
||||
newClients = append(newClients, any(c))
|
||||
}
|
||||
settings["clients"] = newClients
|
||||
modifiedSettings, err := json.MarshalIndent(settings, "", " ")
|
||||
@@ -951,7 +951,7 @@ func (s *InboundService) autoRenewClients(tx *gorm.DB) (bool, int64, error) {
|
||||
var clientsToAdd []struct {
|
||||
protocol string
|
||||
tag string
|
||||
client map[string]interface{}
|
||||
client map[string]any
|
||||
}
|
||||
|
||||
for _, traffic := range traffics {
|
||||
@@ -962,11 +962,11 @@ func (s *InboundService) autoRenewClients(tx *gorm.DB) (bool, int64, error) {
|
||||
return false, 0, err
|
||||
}
|
||||
for inbound_index := range inbounds {
|
||||
settings := map[string]interface{}{}
|
||||
settings := map[string]any{}
|
||||
json.Unmarshal([]byte(inbounds[inbound_index].Settings), &settings)
|
||||
clients := settings["clients"].([]interface{})
|
||||
clients := settings["clients"].([]any)
|
||||
for client_index := range clients {
|
||||
c := clients[client_index].(map[string]interface{})
|
||||
c := clients[client_index].(map[string]any)
|
||||
for traffic_index, traffic := range traffics {
|
||||
if traffic.Email == c["email"].(string) {
|
||||
newExpiryTime := traffic.ExpiryTime
|
||||
@@ -983,14 +983,14 @@ func (s *InboundService) autoRenewClients(tx *gorm.DB) (bool, int64, error) {
|
||||
struct {
|
||||
protocol string
|
||||
tag string
|
||||
client map[string]interface{}
|
||||
client map[string]any
|
||||
}{
|
||||
protocol: string(inbounds[inbound_index].Protocol),
|
||||
tag: inbounds[inbound_index].Tag,
|
||||
client: c,
|
||||
})
|
||||
}
|
||||
clients[client_index] = interface{}(c)
|
||||
clients[client_index] = any(c)
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -1147,7 +1147,7 @@ func (s *InboundService) AddClientStat(tx *gorm.DB, inboundId int, client *model
|
||||
func (s *InboundService) UpdateClientStat(tx *gorm.DB, email string, client *model.Client) error {
|
||||
result := tx.Model(xray.ClientTraffic{}).
|
||||
Where("email = ?", email).
|
||||
Updates(map[string]interface{}{
|
||||
Updates(map[string]any{
|
||||
"enable": true,
|
||||
"email": client.Email,
|
||||
"total": client.TotalGB,
|
||||
@@ -1258,18 +1258,18 @@ func (s *InboundService) SetClientTelegramUserID(trafficId int, tgId int64) (boo
|
||||
return false, common.NewError("Client Not Found For Email:", clientEmail)
|
||||
}
|
||||
|
||||
var settings map[string]interface{}
|
||||
var settings map[string]any
|
||||
err = json.Unmarshal([]byte(inbound.Settings), &settings)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
clients := settings["clients"].([]interface{})
|
||||
var newClients []interface{}
|
||||
clients := settings["clients"].([]any)
|
||||
var newClients []any
|
||||
for client_index := range clients {
|
||||
c := clients[client_index].(map[string]interface{})
|
||||
c := clients[client_index].(map[string]any)
|
||||
if c["email"] == clientEmail {
|
||||
c["tgId"] = tgId
|
||||
newClients = append(newClients, interface{}(c))
|
||||
newClients = append(newClients, any(c))
|
||||
}
|
||||
}
|
||||
settings["clients"] = newClients
|
||||
@@ -1343,18 +1343,18 @@ func (s *InboundService) ToggleClientEnableByEmail(clientEmail string) (bool, bo
|
||||
return false, false, common.NewError("Client Not Found For Email:", clientEmail)
|
||||
}
|
||||
|
||||
var settings map[string]interface{}
|
||||
var settings map[string]any
|
||||
err = json.Unmarshal([]byte(inbound.Settings), &settings)
|
||||
if err != nil {
|
||||
return false, false, err
|
||||
}
|
||||
clients := settings["clients"].([]interface{})
|
||||
var newClients []interface{}
|
||||
clients := settings["clients"].([]any)
|
||||
var newClients []any
|
||||
for client_index := range clients {
|
||||
c := clients[client_index].(map[string]interface{})
|
||||
c := clients[client_index].(map[string]any)
|
||||
if c["email"] == clientEmail {
|
||||
c["enable"] = !clientOldEnabled
|
||||
newClients = append(newClients, interface{}(c))
|
||||
newClients = append(newClients, any(c))
|
||||
}
|
||||
}
|
||||
settings["clients"] = newClients
|
||||
@@ -1405,18 +1405,18 @@ func (s *InboundService) ResetClientIpLimitByEmail(clientEmail string, count int
|
||||
return false, common.NewError("Client Not Found For Email:", clientEmail)
|
||||
}
|
||||
|
||||
var settings map[string]interface{}
|
||||
var settings map[string]any
|
||||
err = json.Unmarshal([]byte(inbound.Settings), &settings)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
clients := settings["clients"].([]interface{})
|
||||
var newClients []interface{}
|
||||
clients := settings["clients"].([]any)
|
||||
var newClients []any
|
||||
for client_index := range clients {
|
||||
c := clients[client_index].(map[string]interface{})
|
||||
c := clients[client_index].(map[string]any)
|
||||
if c["email"] == clientEmail {
|
||||
c["limitIp"] = count
|
||||
newClients = append(newClients, interface{}(c))
|
||||
newClients = append(newClients, any(c))
|
||||
}
|
||||
}
|
||||
settings["clients"] = newClients
|
||||
@@ -1462,18 +1462,18 @@ func (s *InboundService) ResetClientExpiryTimeByEmail(clientEmail string, expiry
|
||||
return false, common.NewError("Client Not Found For Email:", clientEmail)
|
||||
}
|
||||
|
||||
var settings map[string]interface{}
|
||||
var settings map[string]any
|
||||
err = json.Unmarshal([]byte(inbound.Settings), &settings)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
clients := settings["clients"].([]interface{})
|
||||
var newClients []interface{}
|
||||
clients := settings["clients"].([]any)
|
||||
var newClients []any
|
||||
for client_index := range clients {
|
||||
c := clients[client_index].(map[string]interface{})
|
||||
c := clients[client_index].(map[string]any)
|
||||
if c["email"] == clientEmail {
|
||||
c["expiryTime"] = expiry_time
|
||||
newClients = append(newClients, interface{}(c))
|
||||
newClients = append(newClients, any(c))
|
||||
}
|
||||
}
|
||||
settings["clients"] = newClients
|
||||
@@ -1522,18 +1522,18 @@ func (s *InboundService) ResetClientTrafficLimitByEmail(clientEmail string, tota
|
||||
return false, common.NewError("Client Not Found For Email:", clientEmail)
|
||||
}
|
||||
|
||||
var settings map[string]interface{}
|
||||
var settings map[string]any
|
||||
err = json.Unmarshal([]byte(inbound.Settings), &settings)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
clients := settings["clients"].([]interface{})
|
||||
var newClients []interface{}
|
||||
clients := settings["clients"].([]any)
|
||||
var newClients []any
|
||||
for client_index := range clients {
|
||||
c := clients[client_index].(map[string]interface{})
|
||||
c := clients[client_index].(map[string]any)
|
||||
if c["email"] == clientEmail {
|
||||
c["totalGB"] = totalGB * 1024 * 1024 * 1024
|
||||
newClients = append(newClients, interface{}(c))
|
||||
newClients = append(newClients, any(c))
|
||||
}
|
||||
}
|
||||
settings["clients"] = newClients
|
||||
@@ -1551,7 +1551,7 @@ func (s *InboundService) ResetClientTrafficByEmail(clientEmail string) error {
|
||||
|
||||
result := db.Model(xray.ClientTraffic{}).
|
||||
Where("email = ?", clientEmail).
|
||||
Updates(map[string]interface{}{"enable": true, "up": 0, "down": 0})
|
||||
Updates(map[string]any{"enable": true, "up": 0, "down": 0})
|
||||
|
||||
err := result.Error
|
||||
if err != nil {
|
||||
@@ -1582,14 +1582,14 @@ func (s *InboundService) ResetClientTraffic(id int, clientEmail string) (bool, e
|
||||
s.xrayApi.Init(p.GetAPIPort())
|
||||
cipher := ""
|
||||
if string(inbound.Protocol) == "shadowsocks" {
|
||||
var oldSettings map[string]interface{}
|
||||
var oldSettings map[string]any
|
||||
err = json.Unmarshal([]byte(inbound.Settings), &oldSettings)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
cipher = oldSettings["method"].(string)
|
||||
}
|
||||
err1 := s.xrayApi.AddUser(string(inbound.Protocol), inbound.Tag, map[string]interface{}{
|
||||
err1 := s.xrayApi.AddUser(string(inbound.Protocol), inbound.Tag, map[string]any{
|
||||
"email": client.Email,
|
||||
"id": client.ID,
|
||||
"security": client.Security,
|
||||
@@ -1634,7 +1634,7 @@ func (s *InboundService) ResetAllClientTraffics(id int) error {
|
||||
|
||||
result := db.Model(xray.ClientTraffic{}).
|
||||
Where(whereText, id).
|
||||
Updates(map[string]interface{}{"enable": true, "up": 0, "down": 0})
|
||||
Updates(map[string]any{"enable": true, "up": 0, "down": 0})
|
||||
|
||||
err := result.Error
|
||||
return err
|
||||
@@ -1645,7 +1645,7 @@ func (s *InboundService) ResetAllTraffics() error {
|
||||
|
||||
result := db.Model(model.Inbound{}).
|
||||
Where("user_id > ?", 0).
|
||||
Updates(map[string]interface{}{"up": 0, "down": 0})
|
||||
Updates(map[string]any{"up": 0, "down": 0})
|
||||
|
||||
err := result.Error
|
||||
return err
|
||||
@@ -1681,17 +1681,17 @@ func (s *InboundService) DelDepletedClients(id int) (err error) {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var oldSettings map[string]interface{}
|
||||
var oldSettings map[string]any
|
||||
err = json.Unmarshal([]byte(oldInbound.Settings), &oldSettings)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
oldClients := oldSettings["clients"].([]interface{})
|
||||
var newClients []interface{}
|
||||
oldClients := oldSettings["clients"].([]any)
|
||||
var newClients []any
|
||||
for _, client := range oldClients {
|
||||
deplete := false
|
||||
c := client.(map[string]interface{})
|
||||
c := client.(map[string]any)
|
||||
for _, email := range emails {
|
||||
if email == c["email"].(string) {
|
||||
deplete = true
|
||||
@@ -1907,14 +1907,14 @@ func (s *InboundService) MigrationRequirements() {
|
||||
return
|
||||
}
|
||||
for inbound_index := range inbounds {
|
||||
settings := map[string]interface{}{}
|
||||
settings := map[string]any{}
|
||||
json.Unmarshal([]byte(inbounds[inbound_index].Settings), &settings)
|
||||
clients, ok := settings["clients"].([]interface{})
|
||||
clients, ok := settings["clients"].([]any)
|
||||
if ok {
|
||||
// Fix Client configuration problems
|
||||
var newClients []interface{}
|
||||
var newClients []any
|
||||
for client_index := range clients {
|
||||
c := clients[client_index].(map[string]interface{})
|
||||
c := clients[client_index].(map[string]any)
|
||||
|
||||
// Add email='' if it is not exists
|
||||
if _, ok := c["email"]; !ok {
|
||||
@@ -1923,7 +1923,7 @@ func (s *InboundService) MigrationRequirements() {
|
||||
|
||||
// Convert string tgId to int64
|
||||
if _, ok := c["tgId"]; ok {
|
||||
var tgId interface{} = c["tgId"]
|
||||
var tgId any = c["tgId"]
|
||||
if tgIdStr, ok2 := tgId.(string); ok2 {
|
||||
tgIdInt64, err := strconv.ParseInt(strings.ReplaceAll(tgIdStr, " ", ""), 10, 64)
|
||||
if err == nil {
|
||||
@@ -1938,7 +1938,7 @@ func (s *InboundService) MigrationRequirements() {
|
||||
c["flow"] = ""
|
||||
}
|
||||
}
|
||||
newClients = append(newClients, interface{}(c))
|
||||
newClients = append(newClients, any(c))
|
||||
}
|
||||
settings["clients"] = newClients
|
||||
modifiedSettings, err := json.MarshalIndent(settings, "", " ")
|
||||
@@ -1985,14 +1985,14 @@ func (s *InboundService) MigrationRequirements() {
|
||||
}
|
||||
|
||||
for _, ep := range externalProxy {
|
||||
var reverses interface{}
|
||||
var stream map[string]interface{}
|
||||
var reverses any
|
||||
var stream map[string]any
|
||||
json.Unmarshal(ep.StreamSettings, &stream)
|
||||
if tlsSettings, ok := stream["tlsSettings"].(map[string]interface{}); ok {
|
||||
if settings, ok := tlsSettings["settings"].(map[string]interface{}); ok {
|
||||
if domains, ok := settings["domains"].([]interface{}); ok {
|
||||
if tlsSettings, ok := stream["tlsSettings"].(map[string]any); ok {
|
||||
if settings, ok := tlsSettings["settings"].(map[string]any); ok {
|
||||
if domains, ok := settings["domains"].([]any); ok {
|
||||
for _, domain := range domains {
|
||||
if domainMap, ok := domain.(map[string]interface{}); ok {
|
||||
if domainMap, ok := domain.(map[string]any); ok {
|
||||
domainMap["forceTls"] = "same"
|
||||
domainMap["port"] = ep.Port
|
||||
domainMap["dest"] = domainMap["domain"].(string)
|
||||
|
||||
@@ -89,7 +89,7 @@ func (s *OutboundService) ResetOutboundTraffic(tag string) error {
|
||||
|
||||
result := db.Model(model.OutboundTraffics{}).
|
||||
Where(whereText, tag).
|
||||
Updates(map[string]interface{}{"up": 0, "down": 0, "total": 0})
|
||||
Updates(map[string]any{"up": 0, "down": 0, "total": 0})
|
||||
|
||||
err := result.Error
|
||||
if err != nil {
|
||||
|
||||
@@ -92,6 +92,8 @@ type Release struct {
|
||||
type ServerService struct {
|
||||
xrayService XrayService
|
||||
inboundService InboundService
|
||||
cachedIPv4 string
|
||||
cachedIPv6 string
|
||||
}
|
||||
|
||||
func getPublicIP(url string) string {
|
||||
@@ -120,6 +122,7 @@ func (s *ServerService) GetStatus(lastStatus *Status) *Status {
|
||||
T: now,
|
||||
}
|
||||
|
||||
// CPU stats
|
||||
percents, err := cpu.Percent(0, false)
|
||||
if err != nil {
|
||||
logger.Warning("get cpu percent failed:", err)
|
||||
@@ -133,22 +136,17 @@ func (s *ServerService) GetStatus(lastStatus *Status) *Status {
|
||||
}
|
||||
|
||||
status.LogicalPro = runtime.NumCPU()
|
||||
if p != nil && p.IsRunning() {
|
||||
status.AppStats.Uptime = p.GetUptime()
|
||||
} else {
|
||||
status.AppStats.Uptime = 0
|
||||
}
|
||||
|
||||
cpuInfos, err := cpu.Info()
|
||||
if err != nil {
|
||||
logger.Warning("get cpu info failed:", err)
|
||||
} else if len(cpuInfos) > 0 {
|
||||
cpuInfo := cpuInfos[0]
|
||||
status.CpuSpeedMhz = cpuInfo.Mhz // setting CPU speed in MHz
|
||||
status.CpuSpeedMhz = cpuInfos[0].Mhz
|
||||
} else {
|
||||
logger.Warning("could not find cpu info")
|
||||
}
|
||||
|
||||
// Uptime
|
||||
upTime, err := host.Uptime()
|
||||
if err != nil {
|
||||
logger.Warning("get uptime failed:", err)
|
||||
@@ -156,6 +154,7 @@ func (s *ServerService) GetStatus(lastStatus *Status) *Status {
|
||||
status.Uptime = upTime
|
||||
}
|
||||
|
||||
// Memory stats
|
||||
memInfo, err := mem.VirtualMemory()
|
||||
if err != nil {
|
||||
logger.Warning("get virtual memory failed:", err)
|
||||
@@ -172,14 +171,16 @@ func (s *ServerService) GetStatus(lastStatus *Status) *Status {
|
||||
status.Swap.Total = swapInfo.Total
|
||||
}
|
||||
|
||||
distInfo, err := disk.Usage("/")
|
||||
// Disk stats
|
||||
diskInfo, err := disk.Usage("/")
|
||||
if err != nil {
|
||||
logger.Warning("get dist usage failed:", err)
|
||||
logger.Warning("get disk usage failed:", err)
|
||||
} else {
|
||||
status.Disk.Current = distInfo.Used
|
||||
status.Disk.Total = distInfo.Total
|
||||
status.Disk.Current = diskInfo.Used
|
||||
status.Disk.Total = diskInfo.Total
|
||||
}
|
||||
|
||||
// Load averages
|
||||
avgState, err := load.Avg()
|
||||
if err != nil {
|
||||
logger.Warning("get load avg failed:", err)
|
||||
@@ -187,6 +188,7 @@ func (s *ServerService) GetStatus(lastStatus *Status) *Status {
|
||||
status.Loads = []float64{avgState.Load1, avgState.Load5, avgState.Load15}
|
||||
}
|
||||
|
||||
// Network stats
|
||||
ioStats, err := net.IOCounters(false)
|
||||
if err != nil {
|
||||
logger.Warning("get io counters failed:", err)
|
||||
@@ -207,6 +209,7 @@ func (s *ServerService) GetStatus(lastStatus *Status) *Status {
|
||||
logger.Warning("can not find io counters")
|
||||
}
|
||||
|
||||
// TCP/UDP connections
|
||||
status.TcpCount, err = sys.GetTCPCount()
|
||||
if err != nil {
|
||||
logger.Warning("get tcp connections failed:", err)
|
||||
@@ -217,9 +220,15 @@ func (s *ServerService) GetStatus(lastStatus *Status) *Status {
|
||||
logger.Warning("get udp connections failed:", err)
|
||||
}
|
||||
|
||||
status.PublicIP.IPv4 = getPublicIP("https://api.ipify.org")
|
||||
status.PublicIP.IPv6 = getPublicIP("https://api6.ipify.org")
|
||||
// IP fetching with caching
|
||||
if s.cachedIPv4 == "" || s.cachedIPv6 == "" {
|
||||
s.cachedIPv4 = getPublicIP("https://api.ipify.org")
|
||||
s.cachedIPv6 = getPublicIP("https://api6.ipify.org")
|
||||
}
|
||||
status.PublicIP.IPv4 = s.cachedIPv4
|
||||
status.PublicIP.IPv6 = s.cachedIPv6
|
||||
|
||||
// Xray status
|
||||
if s.xrayService.IsXrayRunning() {
|
||||
status.Xray.State = Running
|
||||
status.Xray.ErrorMsg = ""
|
||||
@@ -233,9 +242,10 @@ func (s *ServerService) GetStatus(lastStatus *Status) *Status {
|
||||
status.Xray.ErrorMsg = s.xrayService.GetXrayResult()
|
||||
}
|
||||
status.Xray.Version = s.xrayService.GetXrayVersion()
|
||||
|
||||
// Application stats
|
||||
var rtm runtime.MemStats
|
||||
runtime.ReadMemStats(&rtm)
|
||||
|
||||
status.AppStats.Mem = rtm.Sys
|
||||
status.AppStats.Threads = uint32(runtime.NumGoroutine())
|
||||
if p != nil && p.IsRunning() {
|
||||
@@ -440,7 +450,7 @@ func (s *ServerService) GetLogs(count string, level string, syslog string) []str
|
||||
return lines
|
||||
}
|
||||
|
||||
func (s *ServerService) GetConfigJson() (interface{}, error) {
|
||||
func (s *ServerService) GetConfigJson() (any, error) {
|
||||
config, err := s.xrayService.GetXrayConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -450,7 +460,7 @@ func (s *ServerService) GetConfigJson() (interface{}, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var jsonData interface{}
|
||||
var jsonData any
|
||||
err = json.Unmarshal(contents, &jsonData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -581,7 +591,7 @@ func (s *ServerService) ImportDB(file multipart.File) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ServerService) GetNewX25519Cert() (interface{}, error) {
|
||||
func (s *ServerService) GetNewX25519Cert() (any, error) {
|
||||
// Run the command
|
||||
cmd := exec.Command(xray.GetBinaryPath(), "x25519")
|
||||
var out bytes.Buffer
|
||||
@@ -599,7 +609,7 @@ func (s *ServerService) GetNewX25519Cert() (interface{}, error) {
|
||||
privateKey := strings.TrimSpace(privateKeyLine[1])
|
||||
publicKey := strings.TrimSpace(publicKeyLine[1])
|
||||
|
||||
keyPair := map[string]interface{}{
|
||||
keyPair := map[string]any{
|
||||
"privateKey": privateKey,
|
||||
"publicKey": publicKey,
|
||||
}
|
||||
|
||||
@@ -50,6 +50,7 @@ var defaultValueMap = map[string]string{
|
||||
"tgLang": "en-US",
|
||||
"secretEnable": "false",
|
||||
"subEnable": "false",
|
||||
"subTitle": "",
|
||||
"subListen": "",
|
||||
"subPort": "2096",
|
||||
"subPath": "/sub/",
|
||||
@@ -74,8 +75,8 @@ var defaultValueMap = map[string]string{
|
||||
|
||||
type SettingService struct{}
|
||||
|
||||
func (s *SettingService) GetDefaultJsonConfig() (interface{}, error) {
|
||||
var jsonData interface{}
|
||||
func (s *SettingService) GetDefaultJsonConfig() (any, error) {
|
||||
var jsonData any
|
||||
err := json.Unmarshal([]byte(xrayTemplateConfig), &jsonData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -418,6 +419,10 @@ func (s *SettingService) GetSubEnable() (bool, error) {
|
||||
return s.getBool("subEnable")
|
||||
}
|
||||
|
||||
func (s *SettingService) GetSubTitle() (string, error) {
|
||||
return s.getString("subTitle")
|
||||
}
|
||||
|
||||
func (s *SettingService) GetSubListen() (string, error) {
|
||||
return s.getString("subListen")
|
||||
}
|
||||
@@ -543,8 +548,8 @@ func (s *SettingService) UpdateAllSetting(allSetting *entity.AllSetting) error {
|
||||
return common.Combine(errs...)
|
||||
}
|
||||
|
||||
func (s *SettingService) GetDefaultXrayConfig() (interface{}, error) {
|
||||
var jsonData interface{}
|
||||
func (s *SettingService) GetDefaultXrayConfig() (any, error) {
|
||||
var jsonData any
|
||||
err := json.Unmarshal([]byte(xrayTemplateConfig), &jsonData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -552,24 +557,25 @@ func (s *SettingService) GetDefaultXrayConfig() (interface{}, error) {
|
||||
return jsonData, nil
|
||||
}
|
||||
|
||||
func (s *SettingService) GetDefaultSettings(host string) (interface{}, error) {
|
||||
type settingFunc func() (interface{}, error)
|
||||
func (s *SettingService) GetDefaultSettings(host string) (any, error) {
|
||||
type settingFunc func() (any, error)
|
||||
settings := map[string]settingFunc{
|
||||
"expireDiff": func() (interface{}, error) { return s.GetExpireDiff() },
|
||||
"trafficDiff": func() (interface{}, error) { return s.GetTrafficDiff() },
|
||||
"pageSize": func() (interface{}, error) { return s.GetPageSize() },
|
||||
"defaultCert": func() (interface{}, error) { return s.GetCertFile() },
|
||||
"defaultKey": func() (interface{}, error) { return s.GetKeyFile() },
|
||||
"tgBotEnable": func() (interface{}, error) { return s.GetTgbotEnabled() },
|
||||
"subEnable": func() (interface{}, error) { return s.GetSubEnable() },
|
||||
"subURI": func() (interface{}, error) { return s.GetSubURI() },
|
||||
"subJsonURI": func() (interface{}, error) { return s.GetSubJsonURI() },
|
||||
"remarkModel": func() (interface{}, error) { return s.GetRemarkModel() },
|
||||
"datepicker": func() (interface{}, error) { return s.GetDatepicker() },
|
||||
"ipLimitEnable": func() (interface{}, error) { return s.GetIpLimitEnable() },
|
||||
"expireDiff": func() (any, error) { return s.GetExpireDiff() },
|
||||
"trafficDiff": func() (any, error) { return s.GetTrafficDiff() },
|
||||
"pageSize": func() (any, error) { return s.GetPageSize() },
|
||||
"defaultCert": func() (any, error) { return s.GetCertFile() },
|
||||
"defaultKey": func() (any, error) { return s.GetKeyFile() },
|
||||
"tgBotEnable": func() (any, error) { return s.GetTgbotEnabled() },
|
||||
"subEnable": func() (any, error) { return s.GetSubEnable() },
|
||||
"subTitle": func() (any, error) { return s.GetSubTitle() },
|
||||
"subURI": func() (any, error) { return s.GetSubURI() },
|
||||
"subJsonURI": func() (any, error) { return s.GetSubJsonURI() },
|
||||
"remarkModel": func() (any, error) { return s.GetRemarkModel() },
|
||||
"datepicker": func() (any, error) { return s.GetDatepicker() },
|
||||
"ipLimitEnable": func() (any, error) { return s.GetIpLimitEnable() },
|
||||
}
|
||||
|
||||
result := make(map[string]interface{})
|
||||
result := make(map[string]any)
|
||||
|
||||
for key, fn := range settings {
|
||||
value, err := fn()
|
||||
@@ -581,6 +587,7 @@ func (s *SettingService) GetDefaultSettings(host string) (interface{}, error) {
|
||||
|
||||
if result["subEnable"].(bool) && (result["subURI"].(string) == "" || result["subJsonURI"].(string) == "") {
|
||||
subURI := ""
|
||||
subTitle, _ := s.GetSubTitle()
|
||||
subPort, _ := s.GetSubPort()
|
||||
subPath, _ := s.GetSubPath()
|
||||
subJsonPath, _ := s.GetSubJsonPath()
|
||||
@@ -607,6 +614,9 @@ func (s *SettingService) GetDefaultSettings(host string) (interface{}, error) {
|
||||
if result["subURI"].(string) == "" {
|
||||
result["subURI"] = subURI + subPath
|
||||
}
|
||||
if result["subTitle"].(string) == "" {
|
||||
result["subTitle"] = subTitle
|
||||
}
|
||||
if result["subJsonURI"].(string) == "" {
|
||||
result["subJsonURI"] = subURI + subJsonPath
|
||||
}
|
||||
|
||||
@@ -20,6 +20,8 @@ import (
|
||||
"x-ui/web/locale"
|
||||
"x-ui/xray"
|
||||
|
||||
"slices"
|
||||
|
||||
"github.com/mymmrac/telego"
|
||||
th "github.com/mymmrac/telego/telegohandler"
|
||||
tu "github.com/mymmrac/telego/telegoutil"
|
||||
@@ -894,12 +896,7 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
|
||||
}
|
||||
|
||||
func checkAdmin(tgId int64) bool {
|
||||
for _, adminId := range adminIds {
|
||||
if adminId == tgId {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
return slices.Contains(adminIds, tgId)
|
||||
}
|
||||
|
||||
func (t *Tgbot) SendAnswer(chatId int64, msg string, isAdmin bool) {
|
||||
@@ -1692,12 +1689,7 @@ func (t *Tgbot) notifyExhausted() {
|
||||
}
|
||||
|
||||
func int64Contains(slice []int64, item int64) bool {
|
||||
for _, s := range slice {
|
||||
if s == item {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
return slices.Contains(slice, item)
|
||||
}
|
||||
|
||||
func (t *Tgbot) onlineClients(chatId int64, messageID ...int) {
|
||||
|
||||
@@ -46,7 +46,7 @@ func (s *UserService) UpdateUser(id int, username string, password string) error
|
||||
db := database.GetDB()
|
||||
return db.Model(model.User{}).
|
||||
Where("id = ?", id).
|
||||
Updates(map[string]interface{}{"username": username, "password": password}).
|
||||
Updates(map[string]any{"username": username, "password": password}).
|
||||
Error
|
||||
}
|
||||
|
||||
|
||||
@@ -56,8 +56,7 @@ func (s *WarpService) GetWarpConfig() (string, error) {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
buffer := bytes.NewBuffer(make([]byte, 8192))
|
||||
buffer.Reset()
|
||||
buffer := &bytes.Buffer{}
|
||||
_, err = buffer.ReadFrom(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
@@ -87,14 +86,13 @@ func (s *WarpService) RegWarp(secretKey string, publicKey string) (string, error
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
buffer := bytes.NewBuffer(make([]byte, 8192))
|
||||
buffer.Reset()
|
||||
buffer := &bytes.Buffer{}
|
||||
_, err = buffer.ReadFrom(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var rspData map[string]interface{}
|
||||
var rspData map[string]any
|
||||
err = json.Unmarshal(buffer.Bytes(), &rspData)
|
||||
if err != nil {
|
||||
return "", err
|
||||
@@ -102,7 +100,7 @@ func (s *WarpService) RegWarp(secretKey string, publicKey string) (string, error
|
||||
|
||||
deviceId := rspData["id"].(string)
|
||||
token := rspData["token"].(string)
|
||||
license, ok := rspData["account"].(map[string]interface{})["license"].(string)
|
||||
license, ok := rspData["account"].(map[string]any)["license"].(string)
|
||||
if !ok {
|
||||
logger.Debug("Error accessing license value.")
|
||||
return "", err
|
||||
@@ -144,21 +142,20 @@ func (s *WarpService) SetWarpLicense(license string) (string, error) {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
buffer := bytes.NewBuffer(make([]byte, 8192))
|
||||
buffer.Reset()
|
||||
buffer := &bytes.Buffer{}
|
||||
_, err = buffer.ReadFrom(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var response map[string]interface{}
|
||||
var response map[string]any
|
||||
err = json.Unmarshal(buffer.Bytes(), &response)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if response["success"] == false {
|
||||
errorArr, _ := response["errors"].([]interface{})
|
||||
errorObj := errorArr[0].(map[string]interface{})
|
||||
errorArr, _ := response["errors"].([]any)
|
||||
errorObj := errorArr[0].(map[string]any)
|
||||
return "", common.NewError(errorObj["code"], errorObj["message"])
|
||||
}
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ func (s *XrayService) GetXrayVersion() string {
|
||||
return p.GetVersion()
|
||||
}
|
||||
|
||||
func RemoveIndex(s []interface{}, index int) []interface{} {
|
||||
func RemoveIndex(s []any, index int) []any {
|
||||
return append(s[:index], s[index+1:]...)
|
||||
}
|
||||
|
||||
@@ -83,16 +83,16 @@ func (s *XrayService) GetXrayConfig() (*xray.Config, error) {
|
||||
continue
|
||||
}
|
||||
// get settings clients
|
||||
settings := map[string]interface{}{}
|
||||
settings := map[string]any{}
|
||||
json.Unmarshal([]byte(inbound.Settings), &settings)
|
||||
clients, ok := settings["clients"].([]interface{})
|
||||
clients, ok := settings["clients"].([]any)
|
||||
if ok {
|
||||
// check users active or not
|
||||
clientStats := inbound.ClientStats
|
||||
for _, clientTraffic := range clientStats {
|
||||
indexDecrease := 0
|
||||
for index, client := range clients {
|
||||
c := client.(map[string]interface{})
|
||||
c := client.(map[string]any)
|
||||
if c["email"] == clientTraffic.Email {
|
||||
if !clientTraffic.Enable {
|
||||
clients = RemoveIndex(clients, index-indexDecrease)
|
||||
@@ -104,9 +104,9 @@ func (s *XrayService) GetXrayConfig() (*xray.Config, error) {
|
||||
}
|
||||
|
||||
// clear client config for additional parameters
|
||||
var final_clients []interface{}
|
||||
var final_clients []any
|
||||
for _, client := range clients {
|
||||
c := client.(map[string]interface{})
|
||||
c := client.(map[string]any)
|
||||
if c["enable"] != nil {
|
||||
if enable, ok := c["enable"].(bool); ok && !enable {
|
||||
continue
|
||||
@@ -120,7 +120,7 @@ func (s *XrayService) GetXrayConfig() (*xray.Config, error) {
|
||||
c["flow"] = "xtls-rprx-vision"
|
||||
}
|
||||
}
|
||||
final_clients = append(final_clients, interface{}(c))
|
||||
final_clients = append(final_clients, any(c))
|
||||
}
|
||||
|
||||
settings["clients"] = final_clients
|
||||
@@ -134,12 +134,12 @@ func (s *XrayService) GetXrayConfig() (*xray.Config, error) {
|
||||
|
||||
if len(inbound.StreamSettings) > 0 {
|
||||
// Unmarshal stream JSON
|
||||
var stream map[string]interface{}
|
||||
var stream map[string]any
|
||||
json.Unmarshal([]byte(inbound.StreamSettings), &stream)
|
||||
|
||||
// Remove the "settings" field under "tlsSettings" and "realitySettings"
|
||||
tlsSettings, ok1 := stream["tlsSettings"].(map[string]interface{})
|
||||
realitySettings, ok2 := stream["realitySettings"].(map[string]interface{})
|
||||
tlsSettings, ok1 := stream["tlsSettings"].(map[string]any)
|
||||
realitySettings, ok2 := stream["realitySettings"].(map[string]any)
|
||||
if ok1 || ok2 {
|
||||
if ok1 {
|
||||
delete(tlsSettings, "settings")
|
||||
|
||||
@@ -67,6 +67,9 @@
|
||||
"emptyReverseDesc" = "No added reverse proxies."
|
||||
|
||||
[menu]
|
||||
"theme" = "Theme"
|
||||
"dark" = "Dark"
|
||||
"ultraDark" = "Ultra Dark"
|
||||
"dashboard" = "Overview"
|
||||
"inbounds" = "Inbounds"
|
||||
"settings" = "Panel Settings"
|
||||
@@ -99,19 +102,21 @@
|
||||
"operationHours" = "Uptime"
|
||||
"systemLoad" = "System Load"
|
||||
"systemLoadDesc" = "System load average for the past 1, 5, and 15 minutes"
|
||||
"connectionTcpCountDesc" = "Total TCP connections across the system"
|
||||
"connectionUdpCountDesc" = "Total UDP connections across the system"
|
||||
"connectionCount" = "Connection Stats"
|
||||
"upSpeed" = "Overall upload speed across the system"
|
||||
"downSpeed" = "Overall download speed across the system"
|
||||
"totalSent" = "Total data sent across the system since OS startup"
|
||||
"totalReceive" = "Total data received across the system since OS startup"
|
||||
"ipAddresses" = "IP Addresses"
|
||||
"toggleIpVisibility" = "Toggle visibility of the IP"
|
||||
"overallSpeed" = "Overall Speed"
|
||||
"upload" = "Upload"
|
||||
"download" = "Download"
|
||||
"totalData" = "Total Data"
|
||||
"sent" = "Sent"
|
||||
"received" = "Received"
|
||||
"xraySwitchVersionDialog" = "Change Xray Version"
|
||||
"xraySwitchVersionDialogDesc" = "Are you sure you want to change the Xray version to"
|
||||
"dontRefresh" = "Installation is in progress, please do not refresh this page"
|
||||
"logs" = "Logs"
|
||||
"config" = "Config"
|
||||
"backup" = "Backup & Restore"
|
||||
"backup" = "Backup"
|
||||
"backupTitle" = "Database Backup & Restore"
|
||||
"exportDatabase" = "Back Up"
|
||||
"exportDatabaseDesc" = "Click to download a .db file containing a backup of your current database to your device."
|
||||
@@ -135,6 +140,8 @@
|
||||
"resetTraffic" = "Reset Traffic"
|
||||
"addInbound" = "Add Inbound"
|
||||
"generalActions" = "General Actions"
|
||||
"autoRefresh" = "Auto-refresh"
|
||||
"autoRefreshInterval" = "Interval"
|
||||
"create" = "Create"
|
||||
"update" = "Update"
|
||||
"modifyInbound" = "Modify Inbound"
|
||||
@@ -292,6 +299,8 @@
|
||||
"subSettings" = "Subscription"
|
||||
"subEnable" = "Enable Subscription Service"
|
||||
"subEnableDesc" = "Enables the subscription service."
|
||||
"subTitle" = "Subscription Title"
|
||||
"subTitleDesc" = "Title shown in VPN client"
|
||||
"subListen" = "Listen IP"
|
||||
"subListenDesc" = "The IP address for the subscription service. (leave blank to listen on all IPs)"
|
||||
"subPort" = "Listen Port"
|
||||
@@ -425,6 +434,7 @@
|
||||
"type" = "Type"
|
||||
"bridge" = "Bridge"
|
||||
"portal" = "Portal"
|
||||
"link" = "Link"
|
||||
"intercon" = "Interconnection"
|
||||
"settings" = "Settings"
|
||||
"accountInfo" = "Account Information"
|
||||
|
||||
@@ -67,6 +67,9 @@
|
||||
"emptyReverseDesc" = "No hay proxies inversos añadidos."
|
||||
|
||||
[menu]
|
||||
"theme" = "Tema"
|
||||
"dark" = "Oscuro"
|
||||
"ultraDark" = "Ultra Oscuro"
|
||||
"dashboard" = "Estado del Sistema"
|
||||
"inbounds" = "Entradas"
|
||||
"settings" = "Configuraciones"
|
||||
@@ -102,16 +105,20 @@
|
||||
"connectionTcpCountDesc" = "Conexiones TCP totales en todas las tarjetas de red."
|
||||
"connectionUdpCountDesc" = "Conexiones UDP totales en todas las tarjetas de red."
|
||||
"connectionCount" = "Número de Conexiones"
|
||||
"upSpeed" = "Velocidad de Subida Total para Todas las Tarjetas de Red."
|
||||
"downSpeed" = "Velocidad de Bajada Total para Todas las Tarjetas de Red."
|
||||
"totalSent" = "Tráfico Total de Subida de Todas las Tarjetas de Red desde el inicio del sistema."
|
||||
"totalReceive" = "Datos Descargados Totales en Todas las Tarjetas de Red desde el inicio del sistema."
|
||||
"ipAddresses" = "Direcciones IP"
|
||||
"toggleIpVisibility" = "Alternar visibilidad de la IP"
|
||||
"overallSpeed" = "Velocidad general"
|
||||
"upload" = "Subida"
|
||||
"download" = "Descarga"
|
||||
"totalData" = "Datos totales"
|
||||
"sent" = "Enviado"
|
||||
"received" = "Recibido"
|
||||
"xraySwitchVersionDialog" = "Cambiar Versión de Xray"
|
||||
"xraySwitchVersionDialogDesc" = "¿Estás seguro de que deseas cambiar la versión de Xray a"
|
||||
"dontRefresh" = "La instalación está en progreso, por favor no actualices esta página."
|
||||
"logs" = "Registros"
|
||||
"config" = "Configuración"
|
||||
"backup" = "Copia de Seguridad y Restauración"
|
||||
"backup" = "Сopia de Seguridad"
|
||||
"backupTitle" = "Copia de Seguridad y Restauración de la Base de Datos"
|
||||
"exportDatabase" = "Copia de seguridad"
|
||||
"exportDatabaseDesc" = "Haz clic para descargar un archivo .db que contiene una copia de seguridad de tu base de datos actual en tu dispositivo."
|
||||
@@ -135,6 +142,8 @@
|
||||
"resetTraffic" = "Restablecer Tráfico"
|
||||
"addInbound" = "Agregar Entrada"
|
||||
"generalActions" = "Acciones Generales"
|
||||
"autoRefresh" = "Auto-actualizar"
|
||||
"autoRefreshInterval" = "Intervalo"
|
||||
"create" = "Crear"
|
||||
"update" = "Actualizar"
|
||||
"modifyInbound" = "Modificar Entrada"
|
||||
@@ -292,6 +301,8 @@
|
||||
"subSettings" = "Suscripción"
|
||||
"subEnable" = "Habilitar Servicio"
|
||||
"subEnableDesc" = "Función de suscripción con configuración separada."
|
||||
"subTitle" = "Título de la Suscripción"
|
||||
"subTitleDesc" = "Título mostrado en el cliente de VPN"
|
||||
"subListen" = "Listening IP"
|
||||
"subListenDesc" = "Dejar en blanco por defecto para monitorear todas las IPs."
|
||||
"subPort" = "Puerto de Suscripción"
|
||||
@@ -425,6 +436,7 @@
|
||||
"type" = "Tipo"
|
||||
"bridge" = "puente"
|
||||
"portal" = "portal"
|
||||
"link" = "Enlace"
|
||||
"intercon" = "Interconexión"
|
||||
"settings" = "Configuración"
|
||||
"accountInfo" = "Información de la Cuenta"
|
||||
|
||||
@@ -67,6 +67,9 @@
|
||||
"emptyReverseDesc" = "هیچ پروکسی معکوس اضافه نشده است."
|
||||
|
||||
[menu]
|
||||
"theme" = "تم"
|
||||
"dark" = "تیره"
|
||||
"ultraDark" = "فوق تیره"
|
||||
"dashboard" = "نمای کلی"
|
||||
"inbounds" = "ورودیها"
|
||||
"settings" = "تنظیمات پنل"
|
||||
@@ -102,10 +105,14 @@
|
||||
"connectionTcpCountDesc" = "در تمامشبکهها TCP مجموعاتصالات"
|
||||
"connectionUdpCountDesc" = "در تمامشبکهها UDP مجموعاتصالات"
|
||||
"connectionCount" = "تعداد کانکشن ها"
|
||||
"upSpeed" = "سرعت کلی آپلود در تمامشبکهها"
|
||||
"downSpeed" = "سرعت کلی دانلود در تمامشبکهها"
|
||||
"totalSent" = "مجموع ترافیک ارسالشده پساز شروعبهکار سیستمعامل"
|
||||
"totalReceive" = "مجموع ترافیک دریافتشده پساز شروعبهکار سیستمعامل"
|
||||
"ipAddresses" = "آدرسهای IP"
|
||||
"toggleIpVisibility" = "تغییر وضعیت نمایش IP"
|
||||
"overallSpeed" = "سرعت کلی"
|
||||
"upload" = "آپلود"
|
||||
"download" = "دانلود"
|
||||
"totalData" = "دادههای کل"
|
||||
"sent" = "ارسال شده"
|
||||
"received" = "دریافت شده"
|
||||
"xraySwitchVersionDialog" = "تغییر نسخه ایکسری"
|
||||
"xraySwitchVersionDialogDesc" = "آیا از تغییر نسخه مطمئن هستید؟"
|
||||
"dontRefresh" = "در حال نصب، لطفا صفحه را رفرش نکنید"
|
||||
@@ -135,6 +142,8 @@
|
||||
"resetTraffic" = "ریست ترافیک"
|
||||
"addInbound" = "افزودن ورودی"
|
||||
"generalActions" = "عملیات کلی"
|
||||
"autoRefresh" = "تازهسازی خودکار"
|
||||
"autoRefreshInterval" = "فاصله"
|
||||
"create" = "افزودن"
|
||||
"update" = "ویرایش"
|
||||
"modifyInbound" = "ویرایش ورودی"
|
||||
@@ -292,6 +301,8 @@
|
||||
"subSettings" = "سابسکریپشن"
|
||||
"subEnable" = "فعالسازی سرویس سابسکریپشن"
|
||||
"subEnableDesc" = "سرویس سابسکریپشن را فعالمیکند"
|
||||
"subTitle" = "عنوان اشتراک"
|
||||
"subTitleDesc" = "عنوان نمایش داده شده در کلاینت VPN"
|
||||
"subListen" = "آدرس آیپی"
|
||||
"subListenDesc" = "آدرس آیپی برای سرویس سابسکریپشن. برای گوش دادن بهتمام آیپیها خالیبگذارید"
|
||||
"subPort" = "پورت"
|
||||
@@ -425,6 +436,7 @@
|
||||
"type" = "نوع"
|
||||
"bridge" = "پل"
|
||||
"portal" = "پورتال"
|
||||
"link" = "لینک"
|
||||
"intercon" = "اتصال میانی"
|
||||
"settings" = "تنظیمات"
|
||||
"accountInfo" = "اطلاعات حساب"
|
||||
|
||||
@@ -67,6 +67,9 @@
|
||||
"emptyReverseDesc" = "Tidak ada proxy terbalik yang ditambahkan."
|
||||
|
||||
[menu]
|
||||
"theme" = "Tema"
|
||||
"dark" = "Gelap"
|
||||
"ultraDark" = "Sangat Gelap"
|
||||
"dashboard" = "Ikhtisar"
|
||||
"inbounds" = "Masuk"
|
||||
"settings" = "Pengaturan Panel"
|
||||
@@ -102,16 +105,20 @@
|
||||
"connectionTcpCountDesc" = "Total koneksi TCP di seluruh sistem"
|
||||
"connectionUdpCountDesc" = "Total koneksi UDP di seluruh sistem"
|
||||
"connectionCount" = "Statistik Koneksi"
|
||||
"upSpeed" = "Kecepatan unggah keseluruhan di seluruh sistem"
|
||||
"downSpeed" = "Kecepatan unduh keseluruhan di seluruh sistem"
|
||||
"totalSent" = "Total data terkirim di seluruh sistem sejak startup OS"
|
||||
"totalReceive" = "Total data diterima di seluruh sistem sejak startup OS"
|
||||
"ipAddresses" = "Alamat IP"
|
||||
"toggleIpVisibility" = "Alihkan visibilitas IP"
|
||||
"overallSpeed" = "Kecepatan keseluruhan"
|
||||
"upload" = "Unggah"
|
||||
"download" = "Unduh"
|
||||
"totalData" = "Total data"
|
||||
"sent" = "Dikirim"
|
||||
"received" = "Diterima"
|
||||
"xraySwitchVersionDialog" = "Ganti Versi Xray"
|
||||
"xraySwitchVersionDialogDesc" = "Apakah Anda yakin ingin mengubah versi Xray menjadi"
|
||||
"dontRefresh" = "Instalasi sedang berlangsung, harap jangan menyegarkan halaman ini"
|
||||
"logs" = "Log"
|
||||
"config" = "Konfigurasi"
|
||||
"backup" = "Cadangan & Pulihkan"
|
||||
"backup" = "Cadangan"
|
||||
"backupTitle" = "Cadangan & Pulihkan Database"
|
||||
"exportDatabase" = "Cadangkan"
|
||||
"exportDatabaseDesc" = "Klik untuk mengunduh file .db yang berisi cadangan dari database Anda saat ini ke perangkat Anda."
|
||||
@@ -135,6 +142,8 @@
|
||||
"resetTraffic" = "Reset Traffic"
|
||||
"addInbound" = "Tambahkan Masuk"
|
||||
"generalActions" = "Tindakan Umum"
|
||||
"autoRefresh" = "Pembaruan otomatis"
|
||||
"autoRefreshInterval" = "Interval"
|
||||
"create" = "Buat"
|
||||
"update" = "Perbarui"
|
||||
"modifyInbound" = "Ubah Masuk"
|
||||
@@ -292,6 +301,8 @@
|
||||
"subSettings" = "Langganan"
|
||||
"subEnable" = "Aktifkan Layanan Langganan"
|
||||
"subEnableDesc" = "Mengaktifkan layanan langganan."
|
||||
"subTitle" = "Judul Langganan"
|
||||
"subTitleDesc" = "Judul yang ditampilkan di klien VPN"
|
||||
"subListen" = "IP Pendengar"
|
||||
"subListenDesc" = "Alamat IP untuk layanan langganan. (biarkan kosong untuk mendengarkan semua IP)"
|
||||
"subPort" = "Port Pendengar"
|
||||
@@ -424,6 +435,7 @@
|
||||
"type" = "Tipe"
|
||||
"bridge" = "Jembatan"
|
||||
"portal" = "Portal"
|
||||
"link" = "Tautan"
|
||||
"intercon" = "Interkoneksi"
|
||||
"settings" = "Pengaturan"
|
||||
"accountInfo" = "Informasi Akun"
|
||||
|
||||
@@ -67,6 +67,9 @@
|
||||
"emptyReverseDesc" = "追加されたリバースプロキシはありません。"
|
||||
|
||||
[menu]
|
||||
"theme" = "テーマ"
|
||||
"dark" = "ダーク"
|
||||
"ultraDark" = "ウルトラダーク"
|
||||
"dashboard" = "ダッシュボード"
|
||||
"inbounds" = "インバウンド一覧"
|
||||
"settings" = "パネル設定"
|
||||
@@ -102,16 +105,20 @@
|
||||
"connectionTcpCountDesc" = "システム内のすべてのTCP接続数"
|
||||
"connectionUdpCountDesc" = "システム内のすべてのUDP接続数"
|
||||
"connectionCount" = "接続数"
|
||||
"upSpeed" = "総アップロード速度"
|
||||
"downSpeed" = "総ダウンロード速度"
|
||||
"totalSent" = "システム起動以降の送信データ量"
|
||||
"totalReceive" = "システム起動以降の受信データ量"
|
||||
"ipAddresses" = "IPアドレス"
|
||||
"toggleIpVisibility" = "IPの表示を切り替える"
|
||||
"overallSpeed" = "全体の速度"
|
||||
"upload" = "アップロード"
|
||||
"download" = "ダウンロード"
|
||||
"totalData" = "総データ量"
|
||||
"sent" = "送信"
|
||||
"received" = "受信"
|
||||
"xraySwitchVersionDialog" = "Xrayバージョン切り替え"
|
||||
"xraySwitchVersionDialogDesc" = "Xrayのバージョンを切り替えますか?"
|
||||
"dontRefresh" = "インストール中、このページをリロードしないでください"
|
||||
"logs" = "ログ"
|
||||
"config" = "設定"
|
||||
"backup" = "バックアップと復元"
|
||||
"backup" = "バックアップ"
|
||||
"backupTitle" = "データベースのバックアップと復元"
|
||||
"exportDatabase" = "バックアップ"
|
||||
"exportDatabaseDesc" = "クリックして、現在のデータベースのバックアップを含む .db ファイルをデバイスにダウンロードします。"
|
||||
@@ -135,6 +142,8 @@
|
||||
"resetTraffic" = "トラフィックリセット"
|
||||
"addInbound" = "インバウンド追加"
|
||||
"generalActions" = "一般操作"
|
||||
"autoRefresh" = "自動更新"
|
||||
"autoRefreshInterval" = "間隔"
|
||||
"create" = "追加"
|
||||
"update" = "更新"
|
||||
"modifyInbound" = "インバウンド修正"
|
||||
@@ -292,6 +301,8 @@
|
||||
"subSettings" = "サブスクリプション設定"
|
||||
"subEnable" = "サブスクリプションサービスを有効にする"
|
||||
"subEnableDesc" = "サブスクリプションサービス機能を有効にする"
|
||||
"subTitle" = "サブスクリプションタイトル"
|
||||
"subTitleDesc" = "VPNクライアントに表示されるタイトル"
|
||||
"subListen" = "監視IP"
|
||||
"subListenDesc" = "サブスクリプションサービスが監視するIPアドレス(空白にするとすべてのIPを監視)"
|
||||
"subPort" = "監視ポート"
|
||||
@@ -425,6 +436,7 @@
|
||||
"type" = "タイプ"
|
||||
"bridge" = "ブリッジ"
|
||||
"portal" = "ポータル"
|
||||
"link" = "リンク"
|
||||
"intercon" = "インターコネクション"
|
||||
"settings" = "設定"
|
||||
"accountInfo" = "アカウント情報"
|
||||
|
||||
@@ -67,6 +67,9 @@
|
||||
"emptyReverseDesc" = "Nenhum proxy reverso adicionado."
|
||||
|
||||
[menu]
|
||||
"theme" = "Tema"
|
||||
"dark" = "Escuro"
|
||||
"ultraDark" = "Ultra Escuro"
|
||||
"dashboard" = "Visão Geral"
|
||||
"inbounds" = "Inbounds"
|
||||
"settings" = "Panel Settings"
|
||||
@@ -102,16 +105,20 @@
|
||||
"connectionTcpCountDesc" = "Total de conexões TCP no sistema"
|
||||
"connectionUdpCountDesc" = "Total de conexões UDP no sistema"
|
||||
"connectionCount" = "Estatísticas de Conexão"
|
||||
"upSpeed" = "Velocidade total de upload no sistema"
|
||||
"downSpeed" = "Velocidade total de download no sistema"
|
||||
"totalSent" = "Dados totais enviados desde a inicialização do sistema"
|
||||
"totalReceive" = "Dados totais recebidos desde a inicialização do sistema"
|
||||
"ipAddresses" = "Endereços IP"
|
||||
"toggleIpVisibility" = "Alternar visibilidade do IP"
|
||||
"overallSpeed" = "Velocidade geral"
|
||||
"upload" = "Upload"
|
||||
"download" = "Download"
|
||||
"totalData" = "Dados totais"
|
||||
"sent" = "Enviado"
|
||||
"received" = "Recebido"
|
||||
"xraySwitchVersionDialog" = "Alterar Versão do Xray"
|
||||
"xraySwitchVersionDialogDesc" = "Tem certeza de que deseja alterar a versão do Xray para"
|
||||
"dontRefresh" = "Instalação em andamento, por favor não atualize a página"
|
||||
"logs" = "Logs"
|
||||
"config" = "Configuração"
|
||||
"backup" = "Backup e Restauração"
|
||||
"backup" = "Backup"
|
||||
"backupTitle" = "Backup e Restauração do Banco de Dados"
|
||||
"exportDatabase" = "Backup"
|
||||
"exportDatabaseDesc" = "Clique para baixar um arquivo .db contendo um backup do seu banco de dados atual para o seu dispositivo."
|
||||
@@ -135,6 +142,8 @@
|
||||
"resetTraffic" = "Redefinir Tráfego"
|
||||
"addInbound" = "Adicionar Inbound"
|
||||
"generalActions" = "Ações Gerais"
|
||||
"autoRefresh" = "Atualização automática"
|
||||
"autoRefreshInterval" = "Intervalo"
|
||||
"create" = "Criar"
|
||||
"update" = "Atualizar"
|
||||
"modifyInbound" = "Modificar Inbound"
|
||||
@@ -292,6 +301,8 @@
|
||||
"subSettings" = "Assinatura"
|
||||
"subEnable" = "Ativar Serviço de Assinatura"
|
||||
"subEnableDesc" = "Ativa o serviço de assinatura."
|
||||
"subTitle" = "Título da Assinatura"
|
||||
"subTitleDesc" = "Título exibido no cliente VPN"
|
||||
"subListen" = "IP de Escuta"
|
||||
"subListenDesc" = "O endereço IP para o serviço de assinatura. (deixe em branco para escutar em todos os IPs)"
|
||||
"subPort" = "Porta de Escuta"
|
||||
@@ -425,6 +436,7 @@
|
||||
"type" = "Tipo"
|
||||
"bridge" = "Ponte"
|
||||
"portal" = "Portal"
|
||||
"link" = "Link"
|
||||
"intercon" = "Interconexão"
|
||||
"settings" = "Configurações"
|
||||
"accountInfo" = "Informações da Conta"
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
"offline" = "Офлайн"
|
||||
"online" = "Онлайн"
|
||||
"domainName" = "Домен"
|
||||
"monitor" = "Слушать IP"
|
||||
"monitor" = "Мониторинг IP"
|
||||
"certificate" = "Цифровой сертификат"
|
||||
"fail" = "Ошибка"
|
||||
"comment" = "Комментарий"
|
||||
@@ -67,6 +67,9 @@
|
||||
"emptyReverseDesc" = "Нет добавленных обратных прокси."
|
||||
|
||||
[menu]
|
||||
"theme" = "Тема"
|
||||
"dark" = "Темная"
|
||||
"ultraDark" = "Ультра темная"
|
||||
"dashboard" = "Статус системы"
|
||||
"inbounds" = "Подключения"
|
||||
"settings" = "Настройки панели"
|
||||
@@ -102,16 +105,20 @@
|
||||
"connectionTcpCountDesc" = "Общее количество подключений TCP по всем сетевым картам."
|
||||
"connectionUdpCountDesc" = "Общее количество подключений UDP по всем сетевым картам."
|
||||
"connectionCount" = "Количество соединений"
|
||||
"upSpeed" = "Общая скорость отправки для всех сетей"
|
||||
"downSpeed" = "Общая скорость загрузки для всех сетей"
|
||||
"totalSent" = "Общий объем отправляемых данных с момента запуска системы"
|
||||
"totalReceive" = "Общий объем полученных данных для всех сетей с момента запуска системы."
|
||||
"ipAddresses" = "IP-адреса"
|
||||
"toggleIpVisibility" = "Переключить видимость IP"
|
||||
"overallSpeed" = "Общая скорость"
|
||||
"upload" = "Отправка"
|
||||
"download" = "Загрузка"
|
||||
"totalData" = "Общий объем данных"
|
||||
"sent" = "Отправлено"
|
||||
"received" = "Получено"
|
||||
"xraySwitchVersionDialog" = "Переключить версию Xray"
|
||||
"xraySwitchVersionDialogDesc" = "Вы точно хотите сменить версию Xray?"
|
||||
"dontRefresh" = "Установка в процессе. Не обновляйте страницу"
|
||||
"logs" = "Логи"
|
||||
"config" = "Конфигурация"
|
||||
"backup" = "Резервное копирование и восстановление"
|
||||
"backup" = "Резервная копия"
|
||||
"backupTitle" = "База данных резервных копий"
|
||||
"exportDatabase" = "Экспорт базы данных"
|
||||
"exportDatabaseDesc" = "Нажмите, чтобы скачать файл .db, содержащий резервную копию вашей текущей базы данных на ваше устройство."
|
||||
@@ -135,6 +142,8 @@
|
||||
"resetTraffic" = "Сбросить трафик"
|
||||
"addInbound" = "Добавить подключение"
|
||||
"generalActions" = "Общие действия"
|
||||
"autoRefresh" = "Автообновление"
|
||||
"autoRefreshInterval" = "Интервал"
|
||||
"create" = "Создать"
|
||||
"update" = "Обновить"
|
||||
"modifyInbound" = "Изменить подключение"
|
||||
@@ -180,7 +189,7 @@
|
||||
"emailDesc" = "Пожалуйста, укажите уникальный Email"
|
||||
"IPLimit" = "Лимит по IP"
|
||||
"IPLimitDesc" = "Ограничение количества подключений с одного IP (0 – отключить)"
|
||||
"IPLimitlog" = "IP лог"
|
||||
"IPLimitlog" = "Лог IP-адресов"
|
||||
"IPLimitlogDesc" = "Лог IP-адресов (перед включением лога IP-адресов, вы должны очистить список)"
|
||||
"IPLimitlogclear" = "Очистить лог"
|
||||
"setDefaultCert" = "Установить сертификат с панели"
|
||||
@@ -251,7 +260,7 @@
|
||||
"privateKeyPath" = "Путь к файлу приватного ключа сертификата панели"
|
||||
"privateKeyPathDesc" = "Введите полный путь, начинающийся с '/'"
|
||||
"panelUrlPath" = "Корневой путь URL адреса панели"
|
||||
"panelUrlPathDesc" = "Должен начинаться с '/' и заканчиваться на '/'"
|
||||
"panelUrlPathDesc" = "Должен начинаться с '/' и заканчиваться '/'"
|
||||
"pageSize" = "Размер нумерации страниц"
|
||||
"pageSizeDesc" = "Определить размер страницы для входящей таблицы. Установите 0, чтобы отключить"
|
||||
"remarkModel" = "Модель примечания и символ разделения"
|
||||
@@ -272,7 +281,7 @@
|
||||
"telegramAPIServer" = "API-сервер Telegram"
|
||||
"telegramAPIServerDesc" = "Используемый API-сервер Telegram. Оставьте пустым, чтобы использовать сервер по умолчанию."
|
||||
"telegramChatId" = "Идентификатор Telegram администратора бота"
|
||||
"telegramChatIdDesc" = "Один или несколько идентификаторов администратора бота. Чтобы получить идентификатор, используйте @userinfobot или команду '/id' в боте."
|
||||
"telegramChatIdDesc" = "Один или несколько идентификаторов администратора бота. Для получения идентификатора используйте @userinfobot или команду '/id' в боте."
|
||||
"telegramNotifyTime" = "Частота уведомлений бота Telegram"
|
||||
"telegramNotifyTimeDesc" = "Укажите интервал уведомлений в формате Crontab"
|
||||
"tgNotifyBackup" = "Резервное копирование базы данных"
|
||||
@@ -290,8 +299,10 @@
|
||||
"timeZone" = "Часовой пояс"
|
||||
"timeZoneDesc" = "Запланированные задачи выполняются в соответствии со временем в этом часовом поясе"
|
||||
"subSettings" = "Подписка"
|
||||
"subEnable" = "Включить службу"
|
||||
"subEnable" = "Включить подписку"
|
||||
"subEnableDesc" = "Функция подписки с отдельной конфигурацией"
|
||||
"subTitle" = "Заголовок подписки"
|
||||
"subTitleDesc" = "Название подписки, которое видит клиент в VPN клиенте"
|
||||
"subListen" = "Прослушивание IP"
|
||||
"subListenDesc" = "Оставьте пустым по умолчанию, чтобы отслеживать все IP-адреса"
|
||||
"subPort" = "Порт подписки"
|
||||
@@ -351,9 +362,9 @@
|
||||
"basicRouting" = "Базовые соединения"
|
||||
"blockConnectionsConfigsDesc" = "Эти параметры будут блокировать трафик в зависимости от запрашиваемой страны."
|
||||
"directConnectionsConfigsDesc" = "Прямое соединение гарантирует, что определенный трафик не будет перенаправлен через другой сервер."
|
||||
"blockips" = "Блокировать IP"
|
||||
"blockips" = "Блокировать IP-адреса"
|
||||
"blockdomains" = "Блокировать домены"
|
||||
"directips" = "Прямые IP"
|
||||
"directips" = "Прямые IP-адреса"
|
||||
"directdomains" = "Прямые домены"
|
||||
"ipv4Routing" = "Правила IPv4"
|
||||
"ipv4RoutingDesc" = "Эти параметры позволят пользователям маршрутизироваться к целевым доменам только через IPv4"
|
||||
@@ -410,7 +421,7 @@
|
||||
"info" = "Информация"
|
||||
"add" = "Добавить правило"
|
||||
"edit" = "Редактировать правило"
|
||||
"useComma" = "Элементы, разделенные запятыми"
|
||||
"useComma" = "Элементы, разделённые запятыми"
|
||||
|
||||
[pages.xray.outbound]
|
||||
"addOutbound" = "Добавить исходящий"
|
||||
@@ -425,6 +436,7 @@
|
||||
"type" = "Тип"
|
||||
"bridge" = "Мост"
|
||||
"portal" = "Портал"
|
||||
"link" = "Ссылка"
|
||||
"intercon" = "Соединение"
|
||||
"settings" = "Настройки"
|
||||
"accountInfo" = "Информация об учетной записи"
|
||||
@@ -519,7 +531,7 @@
|
||||
"usage" = "❗ Пожалуйста, укажите текст для поиска!"
|
||||
"getID" = "🆔 Ваш ID: <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</code>"
|
||||
"restartSuccess" = "✅ Операция успешно завершена!"
|
||||
"restartFailed" = "❗ Ошибка в операции.\r\n\r\n<code>Ошибка: {{ .Error }}</code>."
|
||||
|
||||
@@ -67,6 +67,9 @@
|
||||
"emptyReverseDesc" = "Eklenmiş ters proxy yok."
|
||||
|
||||
[menu]
|
||||
"theme" = "Tema"
|
||||
"dark" = "Koyu"
|
||||
"ultraDark" = "Ultra Koyu"
|
||||
"dashboard" = "Genel Bakış"
|
||||
"inbounds" = "Gelenler"
|
||||
"settings" = "Panel Ayarları"
|
||||
@@ -102,16 +105,20 @@
|
||||
"connectionTcpCountDesc" = "Sistem genelinde toplam TCP bağlantıları"
|
||||
"connectionUdpCountDesc" = "Sistem genelinde toplam UDP bağlantıları"
|
||||
"connectionCount" = "Bağlantı İstatistikleri"
|
||||
"upSpeed" = "Sistem genelinde toplam yükleme hızı"
|
||||
"downSpeed" = "Sistem genelinde toplam indirme hızı"
|
||||
"totalSent" = "İşletim sistemi başlatıldığından beri sistem genelinde gönderilen toplam veri"
|
||||
"totalReceive" = "İşletim sistemi başlatıldığından beri sistem genelinde alınan toplam veri"
|
||||
"ipAddresses" = "IP adresleri"
|
||||
"toggleIpVisibility" = "IP görünürlüğünü değiştir"
|
||||
"overallSpeed" = "Genel hız"
|
||||
"upload" = "Yükleme"
|
||||
"download" = "İndirme"
|
||||
"totalData" = "Toplam veri"
|
||||
"sent" = "Gönderilen"
|
||||
"received" = "Alınan"
|
||||
"xraySwitchVersionDialog" = "Xray Sürümünü Değiştir"
|
||||
"xraySwitchVersionDialogDesc" = "Xray sürümünü değiştirmek istediğinizden emin misiniz"
|
||||
"dontRefresh" = "Kurulum devam ediyor, lütfen bu sayfayı yenilemeyin"
|
||||
"logs" = "Günlükler"
|
||||
"config" = "Yapılandırma"
|
||||
"backup" = "Yedekle & Geri Yükle"
|
||||
"backup" = "Yedek"
|
||||
"backupTitle" = "Veritabanı Yedekleme & Geri Yükleme"
|
||||
"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."
|
||||
@@ -135,6 +142,8 @@
|
||||
"resetTraffic" = "Trafiği Sıfırla"
|
||||
"addInbound" = "Gelen Ekle"
|
||||
"generalActions" = "Genel Eylemler"
|
||||
"autoRefresh" = "Otomatik yenileme"
|
||||
"autoRefreshInterval" = "Aralık"
|
||||
"create" = "Oluştur"
|
||||
"update" = "Güncelle"
|
||||
"modifyInbound" = "Geleni Düzenle"
|
||||
@@ -292,6 +301,8 @@
|
||||
"subSettings" = "Abonelik"
|
||||
"subEnable" = "Abonelik Hizmetini Etkinleştir"
|
||||
"subEnableDesc" = "Abonelik hizmetini etkinleştirir."
|
||||
"subTitle" = "Abonelik Başlığı"
|
||||
"subTitleDesc" = "VPN istemcisinde gösterilen başlık"
|
||||
"subListen" = "Dinleme IP"
|
||||
"subListenDesc" = "Abonelik hizmeti için IP adresi. (tüm IP'leri dinlemek için boş bırakın)"
|
||||
"subPort" = "Dinleme Portu"
|
||||
@@ -425,6 +436,7 @@
|
||||
"type" = "Tür"
|
||||
"bridge" = "Köprü"
|
||||
"portal" = "Portal"
|
||||
"link" = "Bağlantı"
|
||||
"intercon" = "Bağlantı"
|
||||
"settings" = "Ayarlar"
|
||||
"accountInfo" = "Hesap Bilgileri"
|
||||
|
||||
@@ -67,6 +67,9 @@
|
||||
"emptyReverseDesc" = "Немає доданих зворотних проксі."
|
||||
|
||||
[menu]
|
||||
"theme" = "Тема"
|
||||
"dark" = "Темна"
|
||||
"ultraDark" = "Ультра темна"
|
||||
"dashboard" = "Огляд"
|
||||
"inbounds" = "Вхідні"
|
||||
"settings" = "Параметри панелі"
|
||||
@@ -102,16 +105,20 @@
|
||||
"connectionTcpCountDesc" = "Загальна кількість TCP-з'єднань у системі"
|
||||
"connectionUdpCountDesc" = "Загальна кількість UDP-з'єднань у системі"
|
||||
"connectionCount" = "Статистика з'єднання"
|
||||
"upSpeed" = "Загальна швидкість завантаження в системі"
|
||||
"downSpeed" = "Загальна швидкість завантаження в системі"
|
||||
"totalSent" = "Загальна кількість даних, надісланих через систему з моменту запуску ОС"
|
||||
"totalReceive" = "Загальна кількість даних, отриманих системою з моменту запуску ОС"
|
||||
"ipAddresses" = "IP-адреси"
|
||||
"toggleIpVisibility" = "Перемкнути видимість IP"
|
||||
"overallSpeed" = "Загальна швидкість"
|
||||
"upload" = "Відправка"
|
||||
"download" = "Завантаження"
|
||||
"totalData" = "Загальний обсяг даних"
|
||||
"sent" = "Відправлено"
|
||||
"received" = "Отримано"
|
||||
"xraySwitchVersionDialog" = "Змінити версію Xray"
|
||||
"xraySwitchVersionDialogDesc" = "Ви впевнені, що бажаєте змінити версію Xray на"
|
||||
"dontRefresh" = "Інсталяція триває, будь ласка, не оновлюйте цю сторінку"
|
||||
"logs" = "Журнали"
|
||||
"config" = "Конфігурація"
|
||||
"backup" = "Резервне копіювання та відновлення"
|
||||
"backup" = "Резервна копія"
|
||||
"backupTitle" = "Резервне копіювання та відновлення бази даних"
|
||||
"exportDatabase" = "Резервна копія"
|
||||
"exportDatabaseDesc" = "Натисніть, щоб завантажити файл .db, що містить резервну копію вашої поточної бази даних на ваш пристрій."
|
||||
@@ -135,6 +142,8 @@
|
||||
"resetTraffic" = "Скинути трафік"
|
||||
"addInbound" = "Додати вхідний"
|
||||
"generalActions" = "Загальні дії"
|
||||
"autoRefresh" = "Автооновлення"
|
||||
"autoRefreshInterval" = "Інтервал"
|
||||
"create" = "Створити"
|
||||
"update" = "Оновити"
|
||||
"modifyInbound" = "Змінити вхідний"
|
||||
@@ -292,6 +301,8 @@
|
||||
"subSettings" = "Підписка"
|
||||
"subEnable" = "Увімкнути службу підписки"
|
||||
"subEnableDesc" = "Вмикає службу підписки."
|
||||
"subTitle" = "Назва Підписки"
|
||||
"subTitleDesc" = "Назва, яка відображається у VPN-клієнті"
|
||||
"subListen" = "Слухати IP"
|
||||
"subListenDesc" = "IP-адреса для служби підписки. (залиште порожнім, щоб слухати всі IP-адреси)"
|
||||
"subPort" = "Слухати порт"
|
||||
@@ -425,6 +436,7 @@
|
||||
"type" = "Тип"
|
||||
"bridge" = "Міст"
|
||||
"portal" = "Портал"
|
||||
"link" = "Посилання"
|
||||
"intercon" = "Взаємозв'язок"
|
||||
"settings" = "Налаштування"
|
||||
"accountInfo" = "Інформація про обліковий запис"
|
||||
|
||||
@@ -67,6 +67,9 @@
|
||||
"emptyReverseDesc" = "Không có proxy ngược nào được thêm."
|
||||
|
||||
[menu]
|
||||
"theme" = "Chủ đề"
|
||||
"dark" = "Tối"
|
||||
"ultraDark" = "Siêu tối"
|
||||
"dashboard" = "Trạng thái hệ thống"
|
||||
"inbounds" = "Đầu vào khách hàng"
|
||||
"settings" = "Cài đặt bảng điều khiển"
|
||||
@@ -102,16 +105,20 @@
|
||||
"connectionTcpCountDesc" = "Tổng số kết nối TCP trên tất cả các thẻ mạng."
|
||||
"connectionUdpCountDesc" = "Tổng số kết nối UDP trên tất cả các thẻ mạng."
|
||||
"connectionCount" = "Số lượng kết nối"
|
||||
"upSpeed" = "Tổng tốc độ tải lên cho tất cả các thẻ mạng."
|
||||
"downSpeed" = "Tổng tốc độ tải xuống cho tất cả các thẻ mạng."
|
||||
"totalSent" = "Tổng lưu lượng tải lên của tất cả các thẻ mạng kể từ khi hệ thống khởi động."
|
||||
"totalReceive" = "Tổng lưu lượng tải xuống trên tất cả các thẻ mạng kể từ khi hệ thống khởi động."
|
||||
"ipAddresses" = "Địa chỉ IP"
|
||||
"toggleIpVisibility" = "Chuyển đổi hiển thị IP"
|
||||
"overallSpeed" = "Tốc độ tổng thể"
|
||||
"upload" = "Tải lên"
|
||||
"download" = "Tải xuống"
|
||||
"totalData" = "Tổng dữ liệu"
|
||||
"sent" = "Đã gửi"
|
||||
"received" = "Đã nhận"
|
||||
"xraySwitchVersionDialog" = "Chuyển đổi Phiên bản Xray"
|
||||
"xraySwitchVersionDialogDesc" = "Bạn có chắc chắn muốn chuyển đổi phiên bản Xray sang"
|
||||
"dontRefresh" = "Đang tiến hành cài đặt, vui lòng không làm mới trang này."
|
||||
"logs" = "Nhật ký"
|
||||
"config" = "Cấu hình"
|
||||
"backup" = "Sao lưu & Khôi phục"
|
||||
"backup" = "Sao lưu"
|
||||
"backupTitle" = "Sao lưu & Khôi phục Cơ sở dữ liệu"
|
||||
"exportDatabase" = "Sao lưu"
|
||||
"exportDatabaseDesc" = "Nhấp để tải xuống tệp .db chứa bản sao lưu cơ sở dữ liệu hiện tại của bạn vào thiết bị."
|
||||
@@ -135,6 +142,8 @@
|
||||
"resetTraffic" = "Đặt lại lưu lượng"
|
||||
"addInbound" = "Thêm điểm vào"
|
||||
"generalActions" = "Hành động chung"
|
||||
"autoRefresh" = "Tự động làm mới"
|
||||
"autoRefreshInterval" = "Khoảng thời gian"
|
||||
"create" = "Tạo mới"
|
||||
"update" = "Cập nhật"
|
||||
"modifyInbound" = "Chỉnh sửa điểm vào (Inbound)"
|
||||
@@ -292,6 +301,8 @@
|
||||
"subSettings" = "Gói đăng ký"
|
||||
"subEnable" = "Bật dịch vụ"
|
||||
"subEnableDesc" = "Tính năng gói đăng ký với cấu hình riêng"
|
||||
"subTitle" = "Tiêu đề Đăng ký"
|
||||
"subTitleDesc" = "Tiêu đề hiển thị trong ứng dụng VPN"
|
||||
"subListen" = "Listening IP"
|
||||
"subListenDesc" = "Mặc định để trống để nghe tất cả các IP"
|
||||
"subPort" = "Cổng gói đăng ký"
|
||||
@@ -425,6 +436,7 @@
|
||||
"type" = "Loại"
|
||||
"bridge" = "Cầu"
|
||||
"portal" = "Cổng thông tin"
|
||||
"link" = "Liên kết"
|
||||
"intercon" = "Kết nối"
|
||||
"settings" = "cài đặt"
|
||||
"accountInfo" = "Thông tin tài khoản"
|
||||
|
||||
@@ -67,6 +67,9 @@
|
||||
"emptyReverseDesc" = "未添加反向代理。"
|
||||
|
||||
[menu]
|
||||
"theme" = "主题"
|
||||
"dark" = "暗色"
|
||||
"ultraDark" = "超暗色"
|
||||
"dashboard" = "系统状态"
|
||||
"inbounds" = "入站列表"
|
||||
"settings" = "面板设置"
|
||||
@@ -102,16 +105,20 @@
|
||||
"connectionTcpCountDesc" = "系统中所有 TCP 连接数"
|
||||
"connectionUdpCountDesc" = "系统中所有 UDP 连接数"
|
||||
"connectionCount" = "连接数"
|
||||
"upSpeed" = "总上传速度"
|
||||
"downSpeed" = "总下载速度"
|
||||
"totalSent" = "系统启动以来发送的总数据量"
|
||||
"totalReceive" = "系统启动以来接收的总数据量"
|
||||
"ipAddresses" = "IP地址"
|
||||
"toggleIpVisibility" = "切换IP可见性"
|
||||
"overallSpeed" = "整体速度"
|
||||
"upload" = "上传"
|
||||
"download" = "下载"
|
||||
"totalData" = "总数据"
|
||||
"sent" = "已发送"
|
||||
"received" = "已接收"
|
||||
"xraySwitchVersionDialog" = "切换 Xray 版本"
|
||||
"xraySwitchVersionDialogDesc" = "是否切换 Xray 版本至"
|
||||
"dontRefresh" = "安装中,请勿刷新此页面"
|
||||
"logs" = "日志"
|
||||
"config" = "配置"
|
||||
"backup" = "备份和恢复"
|
||||
"backup" = "备份"
|
||||
"backupTitle" = "备份和恢复数据库"
|
||||
"exportDatabase" = "备份"
|
||||
"exportDatabaseDesc" = "点击下载包含当前数据库备份的 .db 文件到您的设备。"
|
||||
@@ -135,6 +142,8 @@
|
||||
"resetTraffic" = "重置流量"
|
||||
"addInbound" = "添加入站"
|
||||
"generalActions" = "通用操作"
|
||||
"autoRefresh" = "自动刷新"
|
||||
"autoRefreshInterval" = "间隔"
|
||||
"create" = "添加"
|
||||
"update" = "修改"
|
||||
"modifyInbound" = "修改入站"
|
||||
@@ -292,6 +301,8 @@
|
||||
"subSettings" = "订阅设置"
|
||||
"subEnable" = "启用订阅服务"
|
||||
"subEnableDesc" = "启用订阅服务功能"
|
||||
"subTitle" = "订阅标题"
|
||||
"subTitleDesc" = "在VPN客户端中显示的标题"
|
||||
"subListen" = "监听 IP"
|
||||
"subListenDesc" = "订阅服务监听的 IP 地址(留空表示监听所有 IP)"
|
||||
"subPort" = "监听端口"
|
||||
@@ -425,6 +436,7 @@
|
||||
"type" = "类型"
|
||||
"bridge" = "Bridge"
|
||||
"portal" = "Portal"
|
||||
"link" = "链接"
|
||||
"intercon" = "互连"
|
||||
"settings" = "设置"
|
||||
"accountInfo" = "帐户信息"
|
||||
|
||||
@@ -67,6 +67,9 @@
|
||||
"emptyReverseDesc" = "未添加反向代理。"
|
||||
|
||||
[menu]
|
||||
"theme" = "主題"
|
||||
"dark" = "深色"
|
||||
"ultraDark" = "超深色"
|
||||
"dashboard" = "系統狀態"
|
||||
"inbounds" = "入站列表"
|
||||
"settings" = "面板設定"
|
||||
@@ -102,10 +105,14 @@
|
||||
"connectionTcpCountDesc" = "系統中所有 TCP 連線數"
|
||||
"connectionUdpCountDesc" = "系統中所有 UDP 連線數"
|
||||
"connectionCount" = "連線數"
|
||||
"upSpeed" = "總上傳速度"
|
||||
"downSpeed" = "總下載速度"
|
||||
"totalSent" = "系統啟動以來傳送的總資料量"
|
||||
"totalReceive" = "系統啟動以來接收的總資料量"
|
||||
"ipAddresses" = "IP地址"
|
||||
"toggleIpVisibility" = "切換IP可見性"
|
||||
"overallSpeed" = "整體速度"
|
||||
"upload" = "上傳"
|
||||
"download" = "下載"
|
||||
"totalData" = "總數據"
|
||||
"sent" = "已發送"
|
||||
"received" = "已接收"
|
||||
"xraySwitchVersionDialog" = "切換 Xray 版本"
|
||||
"xraySwitchVersionDialogDesc" = "是否切換 Xray 版本至"
|
||||
"dontRefresh" = "安裝中,請勿重新整理此頁面"
|
||||
@@ -135,6 +142,8 @@
|
||||
"resetTraffic" = "重置流量"
|
||||
"addInbound" = "新增入站"
|
||||
"generalActions" = "通用操作"
|
||||
"autoRefresh" = "自動刷新"
|
||||
"autoRefreshInterval" = "間隔"
|
||||
"create" = "新增"
|
||||
"update" = "修改"
|
||||
"modifyInbound" = "修改入站"
|
||||
@@ -292,6 +301,8 @@
|
||||
"subSettings" = "訂閱設定"
|
||||
"subEnable" = "啟用訂閱服務"
|
||||
"subEnableDesc" = "啟用訂閱服務功能"
|
||||
"subTitle" = "訂閱標題"
|
||||
"subTitleDesc" = "在VPN客戶端中顯示的標題"
|
||||
"subListen" = "監聽 IP"
|
||||
"subListenDesc" = "訂閱服務監聽的 IP 地址(留空表示監聽所有 IP)"
|
||||
"subPort" = "監聽埠"
|
||||
@@ -425,6 +436,7 @@
|
||||
"type" = "類型"
|
||||
"bridge" = "Bridge"
|
||||
"portal" = "Portal"
|
||||
"link" = "連結"
|
||||
"intercon" = "互連"
|
||||
"settings" = "設定"
|
||||
"accountInfo" = "帳戶資訊"
|
||||
|
||||
96
x-ui.sh
96
x-ui.sh
@@ -60,8 +60,8 @@ elif [[ "${release}" == "centos" ]]; then
|
||||
echo -e "${red} Please use CentOS 8 or higher ${plain}\n" && exit 1
|
||||
fi
|
||||
elif [[ "${release}" == "ubuntu" ]]; then
|
||||
if [[ ${os_version} -lt 2004 ]]; then
|
||||
echo -e "${red} Please use Ubuntu 20 or higher version!${plain}\n" && exit 1
|
||||
if [[ ${os_version} -lt 2204 ]]; then
|
||||
echo -e "${red} Please use Ubuntu 22 or higher version!${plain}\n" && exit 1
|
||||
fi
|
||||
elif [[ "${release}" == "fedora" ]]; then
|
||||
if [[ ${os_version} -lt 36 ]]; then
|
||||
@@ -94,7 +94,7 @@ elif [[ "${release}" == "virtuozzo" ]]; then
|
||||
else
|
||||
echo -e "${red}Your operating system is not supported by this script.${plain}\n"
|
||||
echo "Please ensure you are using one of the following supported operating systems:"
|
||||
echo "- Ubuntu 20.04+"
|
||||
echo "- Ubuntu 22.04+"
|
||||
echo "- Debian 11+"
|
||||
echo "- CentOS 8+"
|
||||
echo "- OpenEuler 22.03+"
|
||||
@@ -1127,7 +1127,7 @@ ssl_cert_issue() {
|
||||
|
||||
# issue the certificate
|
||||
~/.acme.sh/acme.sh --set-default-ca --server letsencrypt
|
||||
~/.acme.sh/acme.sh --issue -d ${domain} --listen-v6 --standalone --httpport ${WebPort}
|
||||
~/.acme.sh/acme.sh --issue -d ${domain} --listen-v6 --standalone --httpport ${WebPort} --force
|
||||
if [ $? -ne 0 ]; then
|
||||
LOGE "Issuing certificate failed, please check logs."
|
||||
rm -rf ~/.acme.sh/${domain}
|
||||
@@ -1136,10 +1136,36 @@ ssl_cert_issue() {
|
||||
LOGE "Issuing certificate succeeded, installing certificates..."
|
||||
fi
|
||||
|
||||
reloadCmd="x-ui restart"
|
||||
|
||||
LOGI "Default --reloadcmd for ACME is: ${yellow}x-ui restart"
|
||||
LOGI "This command will run on every certificate issue and renew."
|
||||
read -p "Would you like to modify --reloadcmd for ACME? (y/n): " setReloadcmd
|
||||
if [[ "$setReloadcmd" == "y" || "$setReloadcmd" == "Y" ]]; then
|
||||
echo -e "\n${green}\t1.${plain} Preset: systemctl reload nginx ; x-ui restart"
|
||||
echo -e "${green}\t2.${plain} Input your own command"
|
||||
echo -e "${green}\t0.${plain} Keep default reloadcmd"
|
||||
read -p "Choose an option: " choice
|
||||
case "$choice" in
|
||||
1)
|
||||
LOGI "Reloadcmd is: systemctl reload nginx ; x-ui restart"
|
||||
reloadCmd="systemctl reload nginx ; x-ui restart"
|
||||
;;
|
||||
2)
|
||||
LOGD "It's recommended to put x-ui restart at the end, so it won't raise an error if other services fails"
|
||||
read -p "Please enter your reloadcmd (example: systemctl reload nginx ; x-ui restart): " reloadCmd
|
||||
LOGI "Your reloadcmd is: ${reloadCmd}"
|
||||
;;
|
||||
*)
|
||||
LOGI "Keep default reloadcmd"
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# install the certificate
|
||||
~/.acme.sh/acme.sh --installcert -d ${domain} \
|
||||
--key-file /root/cert/${domain}/privkey.pem \
|
||||
--fullchain-file /root/cert/${domain}/fullchain.pem
|
||||
--fullchain-file /root/cert/${domain}/fullchain.pem --reloadcmd "${reloadCmd}"
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
LOGE "Installing certificate failed, exiting."
|
||||
@@ -1208,13 +1234,6 @@ ssl_cert_issue_CF() {
|
||||
fi
|
||||
|
||||
CF_Domain=""
|
||||
certPath="/root/cert-CF"
|
||||
if [ ! -d "$certPath" ]; then
|
||||
mkdir -p $certPath
|
||||
else
|
||||
rm -rf $certPath
|
||||
mkdir -p $certPath
|
||||
fi
|
||||
|
||||
LOGD "Please set a domain name:"
|
||||
read -p "Input your domain here: " CF_Domain
|
||||
@@ -1242,7 +1261,7 @@ ssl_cert_issue_CF() {
|
||||
export CF_Email="${CF_AccountEmail}"
|
||||
|
||||
# Issue the certificate using Cloudflare DNS
|
||||
~/.acme.sh/acme.sh --issue --dns dns_cf -d ${CF_Domain} -d *.${CF_Domain} --log
|
||||
~/.acme.sh/acme.sh --issue --dns dns_cf -d ${CF_Domain} -d *.${CF_Domain} --log --force
|
||||
if [ $? -ne 0 ]; then
|
||||
LOGE "Certificate issuance failed, script exiting..."
|
||||
exit 1
|
||||
@@ -1250,17 +1269,47 @@ ssl_cert_issue_CF() {
|
||||
LOGI "Certificate issued successfully, Installing..."
|
||||
fi
|
||||
|
||||
# Install the certificate
|
||||
mkdir -p ${certPath}/${CF_Domain}
|
||||
# Install the certificate
|
||||
certPath="/root/cert/${CF_Domain}"
|
||||
if [ -d "$certPath" ]; then
|
||||
rm -rf ${certPath}
|
||||
fi
|
||||
|
||||
mkdir -p ${certPath}
|
||||
if [ $? -ne 0 ]; then
|
||||
LOGE "Failed to create directory: ${certPath}/${CF_Domain}"
|
||||
LOGE "Failed to create directory: ${certPath}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
~/.acme.sh/acme.sh --installcert -d ${CF_Domain} -d *.${CF_Domain} \
|
||||
--fullchain-file ${certPath}/${CF_Domain}/fullchain.pem \
|
||||
--key-file ${certPath}/${CF_Domain}/privkey.pem
|
||||
reloadCmd="x-ui restart"
|
||||
|
||||
LOGI "Default --reloadcmd for ACME is: ${yellow}x-ui restart"
|
||||
LOGI "This command will run on every certificate issue and renew."
|
||||
read -p "Would you like to modify --reloadcmd for ACME? (y/n): " setReloadcmd
|
||||
if [[ "$setReloadcmd" == "y" || "$setReloadcmd" == "Y" ]]; then
|
||||
echo -e "\n${green}\t1.${plain} Preset: systemctl reload nginx ; x-ui restart"
|
||||
echo -e "${green}\t2.${plain} Input your own command"
|
||||
echo -e "${green}\t0.${plain} Keep default reloadcmd"
|
||||
read -p "Choose an option: " choice
|
||||
case "$choice" in
|
||||
1)
|
||||
LOGI "Reloadcmd is: systemctl reload nginx ; x-ui restart"
|
||||
reloadCmd="systemctl reload nginx ; x-ui restart"
|
||||
;;
|
||||
2)
|
||||
LOGD "It's recommended to put x-ui restart at the end, so it won't raise an error if other services fails"
|
||||
read -p "Please enter your reloadcmd (example: systemctl reload nginx ; x-ui restart): " reloadCmd
|
||||
LOGI "Your reloadcmd is: ${reloadCmd}"
|
||||
;;
|
||||
*)
|
||||
LOGI "Keep default reloadcmd"
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
~/.acme.sh/acme.sh --installcert -d ${CF_Domain} -d *.${CF_Domain} \
|
||||
--key-file ${certPath}/privkey.pem \
|
||||
--fullchain-file ${certPath}/fullchain.pem --reloadcmd "${reloadCmd}"
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
LOGE "Certificate installation failed, script exiting..."
|
||||
exit 1
|
||||
@@ -1275,15 +1324,15 @@ ssl_cert_issue_CF() {
|
||||
exit 1
|
||||
else
|
||||
LOGI "The certificate is installed and auto-renewal is turned on. Specific information is as follows:"
|
||||
ls -lah ${certPath}/${CF_Domain}
|
||||
chmod 755 ${certPath}/${CF_Domain}
|
||||
ls -lah ${certPath}/*
|
||||
chmod 755 ${certPath}/*
|
||||
fi
|
||||
|
||||
# Prompt user to set panel paths after successful certificate installation
|
||||
read -p "Would you like to set this certificate for the panel? (y/n): " setPanel
|
||||
if [[ "$setPanel" == "y" || "$setPanel" == "Y" ]]; then
|
||||
local webCertFile="${certPath}/${CF_Domain}/fullchain.pem"
|
||||
local webKeyFile="${certPath}/${CF_Domain}/privkey.pem"
|
||||
local webCertFile="${certPath}/fullchain.pem"
|
||||
local webKeyFile="${certPath}/privkey.pem"
|
||||
|
||||
if [[ -f "$webCertFile" && -f "$webKeyFile" ]]; then
|
||||
/usr/local/x-ui/x-ui cert -webCert "$webCertFile" -webCertKey "$webKeyFile"
|
||||
@@ -1585,7 +1634,6 @@ install_iplimit() {
|
||||
# Launching fail2ban
|
||||
if ! systemctl is-active --quiet fail2ban; then
|
||||
systemctl start fail2ban
|
||||
systemctl enable fail2ban
|
||||
else
|
||||
systemctl restart fail2ban
|
||||
fi
|
||||
|
||||
@@ -92,7 +92,7 @@ func (x *XrayAPI) DelInbound(tag string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func (x *XrayAPI) AddUser(Protocol string, inboundTag string, user map[string]interface{}) error {
|
||||
func (x *XrayAPI) AddUser(Protocol string, inboundTag string, user map[string]any) error {
|
||||
var account *serial.TypedMessage
|
||||
switch Protocol {
|
||||
case "vmess":
|
||||
|
||||
@@ -41,8 +41,16 @@ func (lw *LogWriter) Write(m []byte) (n int, err error) {
|
||||
if len(matches) > 3 {
|
||||
level := matches[2]
|
||||
msgBody := matches[3]
|
||||
msgBodyLower := strings.ToLower(msgBody)
|
||||
|
||||
if strings.Contains(strings.ToLower(msgBody), "failed") {
|
||||
if strings.Contains(msgBodyLower, "tls handshake error") ||
|
||||
strings.Contains(msgBodyLower, "connection ends") {
|
||||
logger.Debug("XRAY: " + msgBody)
|
||||
lw.lastLine = ""
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.Contains(msgBodyLower, "failed") {
|
||||
logger.Error("XRAY: " + msgBody)
|
||||
} else {
|
||||
switch level {
|
||||
@@ -60,7 +68,16 @@ func (lw *LogWriter) Write(m []byte) (n int, err error) {
|
||||
}
|
||||
lw.lastLine = ""
|
||||
} else if msg != "" {
|
||||
if strings.Contains(strings.ToLower(msg), "failed") {
|
||||
msgLower := strings.ToLower(msg)
|
||||
|
||||
if strings.Contains(msgLower, "tls handshake error") ||
|
||||
strings.Contains(msgLower, "connection ends") {
|
||||
logger.Debug("XRAY: " + msg)
|
||||
lw.lastLine = msg
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.Contains(msgLower, "failed") {
|
||||
logger.Error("XRAY: " + msg)
|
||||
} else {
|
||||
logger.Debug("XRAY: " + msg)
|
||||
|
||||
@@ -64,7 +64,7 @@ func GetAccessLogPath() (string, error) {
|
||||
return "", err
|
||||
}
|
||||
|
||||
jsonConfig := map[string]interface{}{}
|
||||
jsonConfig := map[string]any{}
|
||||
err = json.Unmarshal([]byte(config), &jsonConfig)
|
||||
if err != nil {
|
||||
logger.Warningf("Failed to parse JSON configuration: %s", err)
|
||||
@@ -72,7 +72,7 @@ func GetAccessLogPath() (string, error) {
|
||||
}
|
||||
|
||||
if jsonConfig["log"] != nil {
|
||||
jsonLog := jsonConfig["log"].(map[string]interface{})
|
||||
jsonLog := jsonConfig["log"].(map[string]any)
|
||||
if jsonLog["access"] != nil {
|
||||
accessLogPath := jsonLog["access"].(string)
|
||||
return accessLogPath, nil
|
||||
|
||||
Reference in New Issue
Block a user