mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2025-09-10 04:06:18 +00:00
Merge branch 'MHSanaei:main' into main
This commit is contained in:
commit
f87991dce3
49 changed files with 1475 additions and 223 deletions
7
.github/workflows/release.yml
vendored
7
.github/workflows/release.yml
vendored
|
@ -25,7 +25,7 @@ jobs:
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5.0.0
|
uses: actions/setup-go@v5.0.0
|
||||||
with:
|
with:
|
||||||
go-version: '1.21'
|
go-version: '1.22'
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
|
@ -116,12 +116,11 @@ jobs:
|
||||||
- name: Package
|
- name: Package
|
||||||
run: tar -zcvf x-ui-linux-${{ matrix.platform }}.tar.gz x-ui
|
run: tar -zcvf x-ui-linux-${{ matrix.platform }}.tar.gz x-ui
|
||||||
|
|
||||||
- name: Upload
|
- name: Upload files to GH release
|
||||||
uses: svenstaro/upload-release-action@2.7.0
|
uses: MHSanaei/upload-release-action@2.8.0
|
||||||
with:
|
with:
|
||||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
tag: ${{ github.ref }}
|
tag: ${{ github.ref }}
|
||||||
file: x-ui-linux-${{ matrix.platform }}.tar.gz
|
file: x-ui-linux-${{ matrix.platform }}.tar.gz
|
||||||
asset_name: x-ui-linux-${{ matrix.platform }}.tar.gz
|
asset_name: x-ui-linux-${{ matrix.platform }}.tar.gz
|
||||||
prerelease: true
|
prerelease: true
|
||||||
overwrite: true
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# ========================================================
|
# ========================================================
|
||||||
# Stage: Builder
|
# Stage: Builder
|
||||||
# ========================================================
|
# ========================================================
|
||||||
FROM golang:1.21-alpine AS builder
|
FROM golang:1.22-alpine AS builder
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
ARG TARGETARCH
|
ARG TARGETARCH
|
||||||
|
|
||||||
|
|
44
README.md
44
README.md
|
@ -69,7 +69,17 @@ certbot renew --dry-run
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
ARCH=$(uname -m)
|
ARCH=$(uname -m)
|
||||||
[[ "${ARCH}" == "aarch64" || "${ARCH}" == "arm64" ]] && XUI_ARCH="arm64" || XUI_ARCH="amd64"
|
case "${ARCH}" in
|
||||||
|
x86_64 | x64 | amd64) XUI_ARCH="amd64" ;;
|
||||||
|
i*86 | x86) XUI_ARCH="386" ;;
|
||||||
|
armv8* | armv8 | arm64 | aarch64) XUI_ARCH="arm64" ;;
|
||||||
|
armv7* | armv7) XUI_ARCH="armv7" ;;
|
||||||
|
armv6* | armv6) XUI_ARCH="armv6" ;;
|
||||||
|
armv5* | armv5) XUI_ARCH="armv5" ;;
|
||||||
|
*) XUI_ARCH="amd64" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
|
||||||
wget https://github.com/MHSanaei/3x-ui/releases/latest/download/x-ui-linux-${XUI_ARCH}.tar.gz
|
wget https://github.com/MHSanaei/3x-ui/releases/latest/download/x-ui-linux-${XUI_ARCH}.tar.gz
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -77,7 +87,16 @@ wget https://github.com/MHSanaei/3x-ui/releases/latest/download/x-ui-linux-${XUI
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
ARCH=$(uname -m)
|
ARCH=$(uname -m)
|
||||||
[[ "${ARCH}" == "aarch64" || "${ARCH}" == "arm64" ]] && XUI_ARCH="arm64" || XUI_ARCH="amd64"
|
case "${ARCH}" in
|
||||||
|
x86_64 | x64 | amd64) XUI_ARCH="amd64" ;;
|
||||||
|
i*86 | x86) XUI_ARCH="386" ;;
|
||||||
|
armv8* | armv8 | arm64 | aarch64) XUI_ARCH="arm64" ;;
|
||||||
|
armv7* | armv7) XUI_ARCH="armv7" ;;
|
||||||
|
armv6* | armv6) XUI_ARCH="armv6" ;;
|
||||||
|
armv5* | armv5) XUI_ARCH="armv5" ;;
|
||||||
|
*) XUI_ARCH="amd64" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
cd /root/
|
cd /root/
|
||||||
rm -rf x-ui/ /usr/local/x-ui/ /usr/bin/x-ui
|
rm -rf x-ui/ /usr/local/x-ui/ /usr/bin/x-ui
|
||||||
tar zxvf x-ui-linux-${XUI_ARCH}.tar.gz
|
tar zxvf x-ui-linux-${XUI_ARCH}.tar.gz
|
||||||
|
@ -164,21 +183,25 @@ remove 3x-ui from docker
|
||||||
- AlmaLinux 9+
|
- AlmaLinux 9+
|
||||||
- Rockylinux 9+
|
- Rockylinux 9+
|
||||||
|
|
||||||
## Compatible Architectures & Devices
|
## Supported Architectures and Devices
|
||||||
|
|
||||||
Supports a variety of different architectures and devices. Here are some of the main architectures that we support:
|
<details>
|
||||||
|
<summary>Click for Supported Architectures and devices details</summary>
|
||||||
|
|
||||||
- **amd64**: This is the most common architecture for personal computers and servers. It supports most modern operating systems.
|
Our platform offers compatibility with a diverse range of architectures and devices, ensuring flexibility across various computing environments. The following are key architectures that we support:
|
||||||
|
|
||||||
- **x86 / i386**: This architecture is prevalent in desktop and laptop computers. It's widely supported by various operating systems and applications. (Ex: Most Windows, macOS, and Linux systems)
|
- **amd64**: This prevalent architecture is the standard for personal computers and servers, accommodating most modern operating systems seamlessly.
|
||||||
|
|
||||||
- **armv8 / arm64 / aarch64**: This is the architecture for modern mobile and embedded devices, including smartphones and tablets. (Ex: Raspberry Pi 4, Raspberry Pi 3, Raspberry Pi Zero 2/Zero 2 W, Orange Pi 3 LTS,...)
|
- **x86 / i386**: Widely adopted in desktop and laptop computers, this architecture enjoys broad support from numerous operating systems and applications, including but not limited to Windows, macOS, and Linux systems.
|
||||||
|
|
||||||
- **armv7 / arm / arm32**: This is the architecture for older mobile and embedded devices. It is still widely used in many devices. (Ex: Orange Pi Zero LTS, Orange Pi PC Plus, Raspberry Pi 2,...)
|
- **armv8 / arm64 / aarch64**: Tailored for contemporary mobile and embedded devices, such as smartphones and tablets, this architecture is exemplified by devices like Raspberry Pi 4, Raspberry Pi 3, Raspberry Pi Zero 2/Zero 2 W, Orange Pi 3 LTS, and more.
|
||||||
|
|
||||||
- **armv6 / arm / arm32**: This is the architecture for very old embedded devices. While not as common as before, there are still some devices using this architecture. (Ex: Raspberry Pi 1, Raspberry Pi Zero/Zero W,...)
|
- **armv7 / arm / arm32**: Serving as the architecture for older mobile and embedded devices, it remains widely utilized in devices like Orange Pi Zero LTS, Orange Pi PC Plus, Raspberry Pi 2, among others.
|
||||||
|
|
||||||
- **armv5 / arm / arm32**: This is an older architecture primarily used in early embedded systems. While it's less common today, some legacy devices may still rely on this architecture. (Ex: Early versions of Raspberry Pi, some older smartphones)
|
- **armv6 / arm / arm32**: Geared towards very old embedded devices, this architecture, while less prevalent, is still in use. Devices such as Raspberry Pi 1, Raspberry Pi Zero/Zero W, rely on this architecture.
|
||||||
|
|
||||||
|
- **armv5 / arm / arm32**: An older architecture primarily associated with early embedded systems, it is less common today but may still be found in legacy devices like early Raspberry Pi versions and some older smartphones.
|
||||||
|
</details>
|
||||||
|
|
||||||
## Languages
|
## Languages
|
||||||
|
|
||||||
|
@ -188,6 +211,7 @@ Supports a variety of different architectures and devices. Here are some of the
|
||||||
- Russian
|
- Russian
|
||||||
- Vietnamese
|
- Vietnamese
|
||||||
- Spanish
|
- Spanish
|
||||||
|
- Indonesian
|
||||||
|
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
2
go.mod
2
go.mod
|
@ -1,6 +1,6 @@
|
||||||
module x-ui
|
module x-ui
|
||||||
|
|
||||||
go 1.21.4
|
go 1.22.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Calidity/gin-sessions v1.3.1
|
github.com/Calidity/gin-sessions v1.3.1
|
||||||
|
|
105
sub/default.json
Normal file
105
sub/default.json
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
{
|
||||||
|
"dns": {
|
||||||
|
"tag": "dns_out",
|
||||||
|
"queryStrategy": "UseIP",
|
||||||
|
"servers": [
|
||||||
|
{
|
||||||
|
"address": "8.8.8.8",
|
||||||
|
"skipFallback": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"inbounds": [
|
||||||
|
{
|
||||||
|
"port": 10808,
|
||||||
|
"protocol": "socks",
|
||||||
|
"settings": {
|
||||||
|
"auth": "noauth",
|
||||||
|
"udp": true,
|
||||||
|
"userLevel": 8
|
||||||
|
},
|
||||||
|
"sniffing": {
|
||||||
|
"destOverride": [
|
||||||
|
"http",
|
||||||
|
"tls",
|
||||||
|
"fakedns"
|
||||||
|
],
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"tag": "socks"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"port": 10809,
|
||||||
|
"protocol": "http",
|
||||||
|
"settings": {
|
||||||
|
"userLevel": 8
|
||||||
|
},
|
||||||
|
"tag": "http"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"log": {
|
||||||
|
"loglevel": "warning"
|
||||||
|
},
|
||||||
|
"outbounds": [
|
||||||
|
{
|
||||||
|
"tag": "direct",
|
||||||
|
"protocol": "freedom",
|
||||||
|
"settings": {
|
||||||
|
"domainStrategy": "UseIP"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tag": "block",
|
||||||
|
"protocol": "blackhole",
|
||||||
|
"settings": {
|
||||||
|
"response": {
|
||||||
|
"type": "http"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"policy": {
|
||||||
|
"levels": {
|
||||||
|
"8": {
|
||||||
|
"connIdle": 300,
|
||||||
|
"downlinkOnly": 1,
|
||||||
|
"handshake": 4,
|
||||||
|
"uplinkOnly": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"system": {
|
||||||
|
"statsOutboundUplink": true,
|
||||||
|
"statsOutboundDownlink": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"routing": {
|
||||||
|
"domainStrategy": "AsIs",
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"type": "field",
|
||||||
|
"network": "tcp,udp",
|
||||||
|
"balancerTag": "all"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"balancers": [
|
||||||
|
{
|
||||||
|
"tag": "all",
|
||||||
|
"selector": [
|
||||||
|
"proxy"
|
||||||
|
],
|
||||||
|
"strategy": {
|
||||||
|
"type": "leastPing"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"observatory": {
|
||||||
|
"probeInterval": "5m",
|
||||||
|
"probeURL": "https://api.github.com/_private/browser/stats",
|
||||||
|
"subjectSelector": [
|
||||||
|
"proxy"
|
||||||
|
],
|
||||||
|
"EnableConcurrency": true
|
||||||
|
},
|
||||||
|
"stats": {}
|
||||||
|
}
|
44
sub/sub.go
44
sub/sub.go
|
@ -47,11 +47,6 @@ func (s *Server) initRouter() (*gin.Engine, error) {
|
||||||
|
|
||||||
engine := gin.Default()
|
engine := gin.Default()
|
||||||
|
|
||||||
subPath, err := s.settingService.GetSubPath()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
subDomain, err := s.settingService.GetSubDomain()
|
subDomain, err := s.settingService.GetSubDomain()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -61,9 +56,44 @@ func (s *Server) initRouter() (*gin.Engine, error) {
|
||||||
engine.Use(middleware.DomainValidatorMiddleware(subDomain))
|
engine.Use(middleware.DomainValidatorMiddleware(subDomain))
|
||||||
}
|
}
|
||||||
|
|
||||||
g := engine.Group(subPath)
|
LinksPath, err := s.settingService.GetSubPath()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
s.sub = NewSUBController(g)
|
JsonPath, err := s.settingService.GetSubJsonPath()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
Encrypt, err := s.settingService.GetSubEncrypt()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ShowInfo, err := s.settingService.GetSubShowInfo()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
RemarkModel, err := s.settingService.GetRemarkModel()
|
||||||
|
if err != nil {
|
||||||
|
RemarkModel = "-ieo"
|
||||||
|
}
|
||||||
|
|
||||||
|
SubUpdates, err := s.settingService.GetSubUpdates()
|
||||||
|
if err != nil {
|
||||||
|
SubUpdates = "10"
|
||||||
|
}
|
||||||
|
|
||||||
|
SubJsonFragment, err := s.settingService.GetSubJsonFragment()
|
||||||
|
if err != nil {
|
||||||
|
SubJsonFragment = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
g := engine.Group("/")
|
||||||
|
|
||||||
|
s.sub = NewSUBController(g, LinksPath, JsonPath, Encrypt, ShowInfo, RemarkModel, SubUpdates, SubJsonFragment)
|
||||||
|
|
||||||
return engine, nil
|
return engine, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,34 +3,57 @@ package sub
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"strings"
|
"strings"
|
||||||
"x-ui/web/service"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
type SUBController struct {
|
type SUBController struct {
|
||||||
subService SubService
|
subPath string
|
||||||
settingService service.SettingService
|
subJsonPath string
|
||||||
|
subEncrypt bool
|
||||||
|
updateInterval string
|
||||||
|
|
||||||
|
subService *SubService
|
||||||
|
subJsonService *SubJsonService
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSUBController(g *gin.RouterGroup) *SUBController {
|
func NewSUBController(
|
||||||
a := &SUBController{}
|
g *gin.RouterGroup,
|
||||||
|
subPath string,
|
||||||
|
jsonPath string,
|
||||||
|
encrypt bool,
|
||||||
|
showInfo bool,
|
||||||
|
rModel string,
|
||||||
|
update string,
|
||||||
|
jsonFragment string) *SUBController {
|
||||||
|
|
||||||
|
a := &SUBController{
|
||||||
|
subPath: subPath,
|
||||||
|
subJsonPath: jsonPath,
|
||||||
|
subEncrypt: encrypt,
|
||||||
|
updateInterval: update,
|
||||||
|
|
||||||
|
subService: NewSubService(showInfo, rModel),
|
||||||
|
subJsonService: NewSubJsonService(jsonFragment),
|
||||||
|
}
|
||||||
a.initRouter(g)
|
a.initRouter(g)
|
||||||
return a
|
return a
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *SUBController) initRouter(g *gin.RouterGroup) {
|
func (a *SUBController) initRouter(g *gin.RouterGroup) {
|
||||||
g = g.Group("/")
|
gLink := g.Group(a.subPath)
|
||||||
|
gJson := g.Group(a.subJsonPath)
|
||||||
|
|
||||||
g.GET("/:subid", a.subs)
|
gLink.GET(":subid", a.subs)
|
||||||
|
|
||||||
|
gJson.GET(":subid", a.subJsons)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *SUBController) subs(c *gin.Context) {
|
func (a *SUBController) subs(c *gin.Context) {
|
||||||
subEncrypt, _ := a.settingService.GetSubEncrypt()
|
println(c.Request.Header["User-Agent"][0])
|
||||||
subShowInfo, _ := a.settingService.GetSubShowInfo()
|
|
||||||
subId := c.Param("subid")
|
subId := c.Param("subid")
|
||||||
host := strings.Split(c.Request.Host, ":")[0]
|
host := strings.Split(c.Request.Host, ":")[0]
|
||||||
subs, headers, err := a.subService.GetSubs(subId, host, subShowInfo)
|
subs, header, err := a.subService.GetSubs(subId, host)
|
||||||
if err != nil || len(subs) == 0 {
|
if err != nil || len(subs) == 0 {
|
||||||
c.String(400, "Error!")
|
c.String(400, "Error!")
|
||||||
} else {
|
} else {
|
||||||
|
@ -40,14 +63,32 @@ func (a *SUBController) subs(c *gin.Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add headers
|
// Add headers
|
||||||
c.Writer.Header().Set("Subscription-Userinfo", headers[0])
|
c.Writer.Header().Set("Subscription-Userinfo", header)
|
||||||
c.Writer.Header().Set("Profile-Update-Interval", headers[1])
|
c.Writer.Header().Set("Profile-Update-Interval", a.updateInterval)
|
||||||
c.Writer.Header().Set("Profile-Title", headers[2])
|
c.Writer.Header().Set("Profile-Title", subId)
|
||||||
|
|
||||||
if subEncrypt {
|
if a.subEncrypt {
|
||||||
c.String(200, base64.StdEncoding.EncodeToString([]byte(result)))
|
c.String(200, base64.StdEncoding.EncodeToString([]byte(result)))
|
||||||
} else {
|
} else {
|
||||||
c.String(200, result)
|
c.String(200, result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *SUBController) subJsons(c *gin.Context) {
|
||||||
|
println(c.Request.Header["User-Agent"][0])
|
||||||
|
subId := c.Param("subid")
|
||||||
|
host := strings.Split(c.Request.Host, ":")[0]
|
||||||
|
jsonSub, header, err := a.subJsonService.GetJson(subId, host)
|
||||||
|
if err != nil || len(jsonSub) == 0 {
|
||||||
|
c.String(400, "Error!")
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// 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.String(200, jsonSub)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
355
sub/subJsonService.go
Normal file
355
sub/subJsonService.go
Normal file
|
@ -0,0 +1,355 @@
|
||||||
|
package sub
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "embed"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"x-ui/database/model"
|
||||||
|
"x-ui/logger"
|
||||||
|
"x-ui/util/json_util"
|
||||||
|
"x-ui/util/random"
|
||||||
|
"x-ui/web/service"
|
||||||
|
"x-ui/xray"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed default.json
|
||||||
|
var defaultJson string
|
||||||
|
|
||||||
|
type SubJsonService struct {
|
||||||
|
fragmanet string
|
||||||
|
|
||||||
|
inboundService service.InboundService
|
||||||
|
SubService
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSubJsonService(fragment string) *SubJsonService {
|
||||||
|
return &SubJsonService{
|
||||||
|
fragmanet: fragment,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SubJsonService) GetJson(subId string, host string) (string, string, error) {
|
||||||
|
inbounds, err := s.SubService.getInboundsBySubId(subId)
|
||||||
|
if err != nil || len(inbounds) == 0 {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
var header string
|
||||||
|
var traffic xray.ClientTraffic
|
||||||
|
var clientTraffics []xray.ClientTraffic
|
||||||
|
var configJson map[string]interface{}
|
||||||
|
var defaultOutbounds []json_util.RawMessage
|
||||||
|
|
||||||
|
json.Unmarshal([]byte(defaultJson), &configJson)
|
||||||
|
if outboundSlices, ok := configJson["outbounds"].([]interface{}); ok {
|
||||||
|
for _, defaultOutbound := range outboundSlices {
|
||||||
|
jsonBytes, _ := json.Marshal(defaultOutbound)
|
||||||
|
defaultOutbounds = append(defaultOutbounds, jsonBytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
outbounds := []json_util.RawMessage{}
|
||||||
|
startIndex := 0
|
||||||
|
// Prepare Inbounds
|
||||||
|
for _, inbound := range inbounds {
|
||||||
|
clients, err := s.inboundService.GetClients(inbound)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("SubJsonService - GetClients: Unable to get clients from inbound")
|
||||||
|
}
|
||||||
|
if clients == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if len(inbound.Listen) > 0 && inbound.Listen[0] == '@' {
|
||||||
|
listen, port, streamSettings, err := s.getFallbackMaster(inbound.Listen, inbound.StreamSettings)
|
||||||
|
if err == nil {
|
||||||
|
inbound.Listen = listen
|
||||||
|
inbound.Port = port
|
||||||
|
inbound.StreamSettings = streamSettings
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var subClients []model.Client
|
||||||
|
for _, client := range clients {
|
||||||
|
if client.Enable && client.SubID == subId {
|
||||||
|
subClients = append(subClients, client)
|
||||||
|
clientTraffics = append(clientTraffics, s.SubService.getClientTraffics(inbound.ClientStats, client.Email))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
outbound := s.getOutbound(inbound, subClients, host, startIndex)
|
||||||
|
if outbound != nil {
|
||||||
|
outbounds = append(outbounds, outbound...)
|
||||||
|
startIndex += len(outbound)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(outbounds) == 0 {
|
||||||
|
return "", "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare statistics
|
||||||
|
for index, clientTraffic := range clientTraffics {
|
||||||
|
if index == 0 {
|
||||||
|
traffic.Up = clientTraffic.Up
|
||||||
|
traffic.Down = clientTraffic.Down
|
||||||
|
traffic.Total = clientTraffic.Total
|
||||||
|
if clientTraffic.ExpiryTime > 0 {
|
||||||
|
traffic.ExpiryTime = clientTraffic.ExpiryTime
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
traffic.Up += clientTraffic.Up
|
||||||
|
traffic.Down += clientTraffic.Down
|
||||||
|
if traffic.Total == 0 || clientTraffic.Total == 0 {
|
||||||
|
traffic.Total = 0
|
||||||
|
} else {
|
||||||
|
traffic.Total += clientTraffic.Total
|
||||||
|
}
|
||||||
|
if clientTraffic.ExpiryTime != traffic.ExpiryTime {
|
||||||
|
traffic.ExpiryTime = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.fragmanet != "" {
|
||||||
|
outbounds = append(outbounds, json_util.RawMessage(s.fragmanet))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Combile outbounds
|
||||||
|
outbounds = append(outbounds, defaultOutbounds...)
|
||||||
|
configJson["outbounds"] = outbounds
|
||||||
|
finalJson, _ := json.MarshalIndent(configJson, "", " ")
|
||||||
|
|
||||||
|
header = fmt.Sprintf("upload=%d; download=%d; total=%d; expire=%d", traffic.Up, traffic.Down, traffic.Total, traffic.ExpiryTime/1000)
|
||||||
|
return string(finalJson), header, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SubJsonService) getOutbound(inbound *model.Inbound, clients []model.Client, host string, startIndex int) []json_util.RawMessage {
|
||||||
|
var newOutbounds []json_util.RawMessage
|
||||||
|
stream := s.streamData(inbound.StreamSettings)
|
||||||
|
|
||||||
|
externalProxies, ok := stream["externalProxy"].([]interface{})
|
||||||
|
if !ok || len(externalProxies) == 0 {
|
||||||
|
externalProxies = []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"forceTls": "same",
|
||||||
|
"dest": host,
|
||||||
|
"port": float64(inbound.Port),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(stream, "externalProxy")
|
||||||
|
|
||||||
|
config_index := startIndex
|
||||||
|
for _, ep := range externalProxies {
|
||||||
|
extPrxy := ep.(map[string]interface{})
|
||||||
|
inbound.Listen = extPrxy["dest"].(string)
|
||||||
|
inbound.Port = int(extPrxy["port"].(float64))
|
||||||
|
newStream := stream
|
||||||
|
switch extPrxy["forceTls"].(string) {
|
||||||
|
case "tls":
|
||||||
|
if newStream["security"] != "tls" {
|
||||||
|
newStream["security"] = "tls"
|
||||||
|
newStream["tslSettings"] = map[string]interface{}{}
|
||||||
|
}
|
||||||
|
case "none":
|
||||||
|
if newStream["security"] != "none" {
|
||||||
|
newStream["security"] = "none"
|
||||||
|
delete(newStream, "tslSettings")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
streamSettings, _ := json.MarshalIndent(newStream, "", " ")
|
||||||
|
inbound.StreamSettings = string(streamSettings)
|
||||||
|
|
||||||
|
for _, client := range clients {
|
||||||
|
inbound.Tag = fmt.Sprintf("proxy_%d", config_index)
|
||||||
|
switch inbound.Protocol {
|
||||||
|
case "vmess", "vless":
|
||||||
|
newOutbounds = append(newOutbounds, s.genVnext(inbound, client))
|
||||||
|
case "trojan", "shadowsocks":
|
||||||
|
newOutbounds = append(newOutbounds, s.genServer(inbound, client))
|
||||||
|
}
|
||||||
|
config_index += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return newOutbounds
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SubJsonService) streamData(stream string) map[string]interface{} {
|
||||||
|
var streamSettings map[string]interface{}
|
||||||
|
json.Unmarshal([]byte(stream), &streamSettings)
|
||||||
|
security, _ := streamSettings["security"].(string)
|
||||||
|
if security == "tls" {
|
||||||
|
streamSettings["tlsSettings"] = s.tlsData(streamSettings["tlsSettings"].(map[string]interface{}))
|
||||||
|
} else if security == "reality" {
|
||||||
|
streamSettings["realitySettings"] = s.realityData(streamSettings["realitySettings"].(map[string]interface{}))
|
||||||
|
}
|
||||||
|
delete(streamSettings, "sockopt")
|
||||||
|
|
||||||
|
if s.fragmanet != "" {
|
||||||
|
streamSettings["sockopt"] = json_util.RawMessage(`{"dialerProxy": "fragment", "tcpKeepAliveIdle": 100, "TcpNoDelay": true}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove proxy protocol
|
||||||
|
network, _ := streamSettings["network"].(string)
|
||||||
|
switch network {
|
||||||
|
case "tcp":
|
||||||
|
streamSettings["tcpSettings"] = s.removeAcceptProxy(streamSettings["tcpSettings"])
|
||||||
|
case "ws":
|
||||||
|
streamSettings["wsSettings"] = s.removeAcceptProxy(streamSettings["wsSettings"])
|
||||||
|
}
|
||||||
|
|
||||||
|
return streamSettings
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SubJsonService) removeAcceptProxy(setting interface{}) map[string]interface{} {
|
||||||
|
netSettings, ok := setting.(map[string]interface{})
|
||||||
|
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{})
|
||||||
|
|
||||||
|
tlsData["serverName"] = tData["serverName"]
|
||||||
|
tlsData["alpn"] = tData["alpn"]
|
||||||
|
if allowInsecure, ok := tlsClientSettings["allowInsecure"].(string); ok {
|
||||||
|
tlsData["allowInsecure"] = allowInsecure
|
||||||
|
}
|
||||||
|
if fingerprint, ok := tlsClientSettings["fingerprint"].(string); ok {
|
||||||
|
tlsData["fingerprint"] = fingerprint
|
||||||
|
}
|
||||||
|
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{})
|
||||||
|
|
||||||
|
rltyData["show"] = false
|
||||||
|
rltyData["publicKey"] = rltyClientSettings["publicKey"]
|
||||||
|
rltyData["fingerprint"] = rltyClientSettings["fingerprint"]
|
||||||
|
|
||||||
|
// Set random data
|
||||||
|
rltyData["spiderX"] = "/" + random.Seq(15)
|
||||||
|
shortIds, ok := rData["shortIds"].([]interface{})
|
||||||
|
if ok && len(shortIds) > 0 {
|
||||||
|
rltyData["shortId"] = shortIds[random.Num(len(shortIds))].(string)
|
||||||
|
} else {
|
||||||
|
rltyData["shortId"] = ""
|
||||||
|
}
|
||||||
|
serverNames, ok := rData["serverNames"].([]interface{})
|
||||||
|
if ok && len(serverNames) > 0 {
|
||||||
|
rltyData["serverName"] = serverNames[random.Num(len(serverNames))].(string)
|
||||||
|
} else {
|
||||||
|
rltyData["serverName"] = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return rltyData
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SubJsonService) genVnext(inbound *model.Inbound, client model.Client) json_util.RawMessage {
|
||||||
|
outbound := Outbound{}
|
||||||
|
usersData := make([]UserVnext, 1)
|
||||||
|
|
||||||
|
usersData[0].ID = client.ID
|
||||||
|
usersData[0].Level = 8
|
||||||
|
if inbound.Protocol == model.VLESS {
|
||||||
|
usersData[0].Flow = client.Flow
|
||||||
|
usersData[0].Encryption = "none"
|
||||||
|
}
|
||||||
|
|
||||||
|
vnextData := make([]VnextSetting, 1)
|
||||||
|
vnextData[0] = VnextSetting{
|
||||||
|
Address: inbound.Listen,
|
||||||
|
Port: inbound.Port,
|
||||||
|
Users: usersData,
|
||||||
|
}
|
||||||
|
|
||||||
|
outbound.Protocol = string(inbound.Protocol)
|
||||||
|
outbound.Tag = inbound.Tag
|
||||||
|
outbound.StreamSettings = json_util.RawMessage(inbound.StreamSettings)
|
||||||
|
outbound.Settings = OutboundSettings{
|
||||||
|
Vnext: vnextData,
|
||||||
|
}
|
||||||
|
|
||||||
|
result, _ := json.MarshalIndent(outbound, "", " ")
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SubJsonService) genServer(inbound *model.Inbound, client model.Client) json_util.RawMessage {
|
||||||
|
outbound := Outbound{}
|
||||||
|
|
||||||
|
serverData := make([]ServerSetting, 1)
|
||||||
|
serverData[0] = ServerSetting{
|
||||||
|
Address: inbound.Listen,
|
||||||
|
Port: inbound.Port,
|
||||||
|
Level: 8,
|
||||||
|
Password: client.Password,
|
||||||
|
}
|
||||||
|
|
||||||
|
if inbound.Protocol == model.Shadowsocks {
|
||||||
|
var inboundSettings map[string]interface{}
|
||||||
|
json.Unmarshal([]byte(inbound.Settings), &inboundSettings)
|
||||||
|
method, _ := inboundSettings["method"].(string)
|
||||||
|
serverData[0].Method = method
|
||||||
|
|
||||||
|
// server password in multi-user 2022 protocols
|
||||||
|
if strings.HasPrefix(method, "2022") {
|
||||||
|
if serverPassword, ok := inboundSettings["password"].(string); ok {
|
||||||
|
serverData[0].Password = fmt.Sprintf("%s:%s", serverPassword, client.Password)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
outbound.Protocol = string(inbound.Protocol)
|
||||||
|
outbound.Tag = inbound.Tag
|
||||||
|
outbound.StreamSettings = json_util.RawMessage(inbound.StreamSettings)
|
||||||
|
outbound.Settings = OutboundSettings{
|
||||||
|
Servers: serverData,
|
||||||
|
}
|
||||||
|
|
||||||
|
result, _ := json.MarshalIndent(outbound, "", " ")
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
type Outbound struct {
|
||||||
|
Protocol string `json:"protocol"`
|
||||||
|
Tag string `json:"tag"`
|
||||||
|
StreamSettings json_util.RawMessage `json:"streamSettings"`
|
||||||
|
Mux map[string]interface{} `json:"mux,omitempty"`
|
||||||
|
ProxySettings map[string]interface{} `json:"proxySettings,omitempty"`
|
||||||
|
Settings OutboundSettings `json:"settings,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type OutboundSettings struct {
|
||||||
|
Vnext []VnextSetting `json:"vnext,omitempty"`
|
||||||
|
Servers []ServerSetting `json:"servers,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type VnextSetting struct {
|
||||||
|
Address string `json:"address"`
|
||||||
|
Port int `json:"port"`
|
||||||
|
Users []UserVnext `json:"users"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserVnext struct {
|
||||||
|
Encryption string `json:"encryption,omitempty"`
|
||||||
|
Flow string `json:"flow,omitempty"`
|
||||||
|
ID string `json:"id"`
|
||||||
|
Level int `json:"level"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ServerSetting struct {
|
||||||
|
Password string `json:"password"`
|
||||||
|
Level int `json:"level"`
|
||||||
|
Address string `json:"address"`
|
||||||
|
Port int `json:"port"`
|
||||||
|
Flow string `json:"flow,omitempty"`
|
||||||
|
Method string `json:"method,omitempty"`
|
||||||
|
}
|
|
@ -25,47 +25,42 @@ type SubService struct {
|
||||||
settingService service.SettingService
|
settingService service.SettingService
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SubService) GetSubs(subId string, host string, showInfo bool) ([]string, []string, error) {
|
func NewSubService(showInfo bool, remarkModel string) *SubService {
|
||||||
|
return &SubService{
|
||||||
|
showInfo: showInfo,
|
||||||
|
remarkModel: remarkModel,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SubService) GetSubs(subId string, host string) ([]string, string, error) {
|
||||||
s.address = host
|
s.address = host
|
||||||
s.showInfo = showInfo
|
|
||||||
var result []string
|
var result []string
|
||||||
var headers []string
|
var header string
|
||||||
var traffic xray.ClientTraffic
|
var traffic xray.ClientTraffic
|
||||||
var clientTraffics []xray.ClientTraffic
|
var clientTraffics []xray.ClientTraffic
|
||||||
inbounds, err := s.getInboundsBySubId(subId)
|
inbounds, err := s.getInboundsBySubId(subId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, "", err
|
||||||
}
|
|
||||||
s.remarkModel, err = s.settingService.GetRemarkModel()
|
|
||||||
if err != nil {
|
|
||||||
s.remarkModel = "-ieo"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
s.datepicker, err = s.settingService.GetDatepicker()
|
s.datepicker, err = s.settingService.GetDatepicker()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.datepicker = "gregorian"
|
s.datepicker = "gregorian"
|
||||||
}
|
}
|
||||||
for _, inbound := range inbounds {
|
for _, inbound := range inbounds {
|
||||||
clients, err := s.inboundService.GetClients(inbound)
|
clients, err := s.inboundService.GetClients(inbound)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("SubService - GetSub: Unable to get clients from inbound")
|
logger.Error("SubService - GetClients: Unable to get clients from inbound")
|
||||||
}
|
}
|
||||||
if clients == nil {
|
if clients == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if len(inbound.Listen) > 0 && inbound.Listen[0] == '@' {
|
if len(inbound.Listen) > 0 && inbound.Listen[0] == '@' {
|
||||||
fallbackMaster, err := s.getFallbackMaster(inbound.Listen)
|
listen, port, streamSettings, err := s.getFallbackMaster(inbound.Listen, inbound.StreamSettings)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
inbound.Listen = fallbackMaster.Listen
|
inbound.Listen = listen
|
||||||
inbound.Port = fallbackMaster.Port
|
inbound.Port = port
|
||||||
var stream map[string]interface{}
|
inbound.StreamSettings = streamSettings
|
||||||
json.Unmarshal([]byte(inbound.StreamSettings), &stream)
|
|
||||||
var masterStream map[string]interface{}
|
|
||||||
json.Unmarshal([]byte(fallbackMaster.StreamSettings), &masterStream)
|
|
||||||
stream["security"] = masterStream["security"]
|
|
||||||
stream["tlsSettings"] = masterStream["tlsSettings"]
|
|
||||||
stream["externalProxy"] = masterStream["externalProxy"]
|
|
||||||
modifiedStream, _ := json.MarshalIndent(stream, "", " ")
|
|
||||||
inbound.StreamSettings = string(modifiedStream)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, client := range clients {
|
for _, client := range clients {
|
||||||
|
@ -76,6 +71,8 @@ func (s *SubService) GetSubs(subId string, host string, showInfo bool) ([]string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Prepare statistics
|
||||||
for index, clientTraffic := range clientTraffics {
|
for index, clientTraffic := range clientTraffics {
|
||||||
if index == 0 {
|
if index == 0 {
|
||||||
traffic.Up = clientTraffic.Up
|
traffic.Up = clientTraffic.Up
|
||||||
|
@ -97,11 +94,8 @@ func (s *SubService) GetSubs(subId string, host string, showInfo bool) ([]string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
headers = append(headers, fmt.Sprintf("upload=%d; download=%d; total=%d; expire=%d", traffic.Up, traffic.Down, traffic.Total, traffic.ExpiryTime/1000))
|
header = fmt.Sprintf("upload=%d; download=%d; total=%d; expire=%d", traffic.Up, traffic.Down, traffic.Total, traffic.ExpiryTime/1000)
|
||||||
updateInterval, _ := s.settingService.GetSubUpdates()
|
return result, header, nil
|
||||||
headers = append(headers, fmt.Sprintf("%d", updateInterval))
|
|
||||||
headers = append(headers, subId)
|
|
||||||
return result, headers, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SubService) getInboundsBySubId(subId string) ([]*model.Inbound, error) {
|
func (s *SubService) getInboundsBySubId(subId string) ([]*model.Inbound, error) {
|
||||||
|
@ -130,7 +124,7 @@ func (s *SubService) getClientTraffics(traffics []xray.ClientTraffic, email stri
|
||||||
return xray.ClientTraffic{}
|
return xray.ClientTraffic{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SubService) getFallbackMaster(dest string) (*model.Inbound, error) {
|
func (s *SubService) getFallbackMaster(dest string, streamSettings string) (string, int, string, error) {
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
var inbound *model.Inbound
|
var inbound *model.Inbound
|
||||||
err := db.Model(model.Inbound{}).
|
err := db.Model(model.Inbound{}).
|
||||||
|
@ -138,9 +132,19 @@ func (s *SubService) getFallbackMaster(dest string) (*model.Inbound, error) {
|
||||||
Where("EXISTS (SELECT * FROM json_each(settings, '$.fallbacks') WHERE json_extract(value, '$.dest') = ?)", dest).
|
Where("EXISTS (SELECT * FROM json_each(settings, '$.fallbacks') WHERE json_extract(value, '$.dest') = ?)", dest).
|
||||||
Find(&inbound).Error
|
Find(&inbound).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return "", 0, "", err
|
||||||
}
|
}
|
||||||
return inbound, nil
|
|
||||||
|
var stream map[string]interface{}
|
||||||
|
json.Unmarshal([]byte(streamSettings), &stream)
|
||||||
|
var masterStream map[string]interface{}
|
||||||
|
json.Unmarshal([]byte(inbound.StreamSettings), &masterStream)
|
||||||
|
stream["security"] = masterStream["security"]
|
||||||
|
stream["tlsSettings"] = masterStream["tlsSettings"]
|
||||||
|
stream["externalProxy"] = masterStream["externalProxy"]
|
||||||
|
modifiedStream, _ := json.MarshalIndent(stream, "", " ")
|
||||||
|
|
||||||
|
return inbound.Listen, inbound.Port, string(modifiedStream), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SubService) getLink(inbound *model.Inbound, email string) string {
|
func (s *SubService) getLink(inbound *model.Inbound, email string) string {
|
||||||
|
@ -578,6 +582,7 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
||||||
if sniValue, ok := searchKey(tlsSetting, "serverName"); ok {
|
if sniValue, ok := searchKey(tlsSetting, "serverName"); ok {
|
||||||
params["sni"], _ = sniValue.(string)
|
params["sni"], _ = sniValue.(string)
|
||||||
}
|
}
|
||||||
|
|
||||||
tlsSettings, _ := searchKey(tlsSetting, "settings")
|
tlsSettings, _ := searchKey(tlsSetting, "settings")
|
||||||
if tlsSetting != nil {
|
if tlsSetting != nil {
|
||||||
if fpValue, ok := searchKey(tlsSettings, "fingerprint"); ok {
|
if fpValue, ok := searchKey(tlsSettings, "fingerprint"); ok {
|
||||||
|
|
|
@ -1050,6 +1050,12 @@ li.ant-select-dropdown-menu-item:empty:after {
|
||||||
color: rgba(255, 255, 255, 0.25);
|
color: rgba(255, 255, 255, 0.25);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dark .ant-message-notice-content {
|
||||||
|
background-color: #222d42;
|
||||||
|
border: 1px solid #2c3950;
|
||||||
|
color: rgba(255, 255, 255, 0.65);
|
||||||
|
}
|
||||||
|
|
||||||
.ant-input-group.ant-input-group-compact-addon:not(:first-child):not(
|
.ant-input-group.ant-input-group-compact-addon:not(:first-child):not(
|
||||||
:last-child
|
:last-child
|
||||||
),
|
),
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 15 KiB |
|
@ -28,6 +28,7 @@ class AllSetting {
|
||||||
this.subListen = "";
|
this.subListen = "";
|
||||||
this.subPort = "2096";
|
this.subPort = "2096";
|
||||||
this.subPath = "/sub/";
|
this.subPath = "/sub/";
|
||||||
|
this.subJsonPath = "/json/";
|
||||||
this.subDomain = "";
|
this.subDomain = "";
|
||||||
this.subCertFile = "";
|
this.subCertFile = "";
|
||||||
this.subKeyFile = "";
|
this.subKeyFile = "";
|
||||||
|
@ -35,6 +36,8 @@ class AllSetting {
|
||||||
this.subEncrypt = true;
|
this.subEncrypt = true;
|
||||||
this.subShowInfo = false;
|
this.subShowInfo = false;
|
||||||
this.subURI = '';
|
this.subURI = '';
|
||||||
|
this.subJsonURI = '';
|
||||||
|
this.subJsonFragment = '';
|
||||||
|
|
||||||
this.timeLocation = "Asia/Tehran";
|
this.timeLocation = "Asia/Tehran";
|
||||||
|
|
||||||
|
|
|
@ -1146,10 +1146,6 @@ class Inbound extends XrayCommonClass {
|
||||||
return [Protocols.VMESS, Protocols.VLESS, Protocols.TROJAN, Protocols.SHADOWSOCKS].includes(this.protocol);
|
return [Protocols.VMESS, Protocols.VLESS, Protocols.TROJAN, Protocols.SHADOWSOCKS].includes(this.protocol);
|
||||||
}
|
}
|
||||||
|
|
||||||
canSniffing() {
|
|
||||||
return [Protocols.VMESS, Protocols.VLESS, Protocols.TROJAN, Protocols.SHADOWSOCKS].includes(this.protocol);
|
|
||||||
}
|
|
||||||
|
|
||||||
reset() {
|
reset() {
|
||||||
this.port = RandomUtil.randomIntRange(10000, 60000);
|
this.port = RandomUtil.randomIntRange(10000, 60000);
|
||||||
this.listen = '';
|
this.listen = '';
|
||||||
|
@ -2299,7 +2295,7 @@ Inbound.WireguardSettings = class extends XrayCommonClass {
|
||||||
}
|
}
|
||||||
|
|
||||||
addPeer() {
|
addPeer() {
|
||||||
this.peers.push(new Inbound.WireguardSettings.Peer());
|
this.peers.push(new Inbound.WireguardSettings.Peer(null,null,'',['10.0.0.' + (this.peers.length+2)]));
|
||||||
}
|
}
|
||||||
|
|
||||||
delPeer(index) {
|
delPeer(index) {
|
||||||
|
@ -2327,7 +2323,7 @@ Inbound.WireguardSettings = class extends XrayCommonClass {
|
||||||
};
|
};
|
||||||
|
|
||||||
Inbound.WireguardSettings.Peer = class extends XrayCommonClass {
|
Inbound.WireguardSettings.Peer = class extends XrayCommonClass {
|
||||||
constructor(privateKey, publicKey, psk='', allowedIPs=['10.0.0.0/24'], keepAlive=0) {
|
constructor(privateKey, publicKey, psk='', allowedIPs=['10.0.0.2/32'], keepAlive=0) {
|
||||||
super();
|
super();
|
||||||
this.privateKey = privateKey
|
this.privateKey = privateKey
|
||||||
this.publicKey = publicKey;
|
this.publicKey = publicKey;
|
||||||
|
@ -2335,6 +2331,9 @@ Inbound.WireguardSettings.Peer = class extends XrayCommonClass {
|
||||||
[this.publicKey, this.privateKey] = Object.values(Wireguard.generateKeypair())
|
[this.publicKey, this.privateKey] = Object.values(Wireguard.generateKeypair())
|
||||||
}
|
}
|
||||||
this.psk = psk;
|
this.psk = psk;
|
||||||
|
allowedIPs.forEach((a,index) => {
|
||||||
|
if (a.length>0 && !a.includes('/')) allowedIPs[index] += '/32';
|
||||||
|
})
|
||||||
this.allowedIPs = allowedIPs;
|
this.allowedIPs = allowedIPs;
|
||||||
this.keepAlive = keepAlive;
|
this.keepAlive = keepAlive;
|
||||||
}
|
}
|
||||||
|
@ -2350,6 +2349,9 @@ Inbound.WireguardSettings.Peer = class extends XrayCommonClass {
|
||||||
}
|
}
|
||||||
|
|
||||||
toJson() {
|
toJson() {
|
||||||
|
this.allowedIPs.forEach((a,index) => {
|
||||||
|
if (a.length>0 && !a.includes('/')) this.allowedIPs[index] += '/32';
|
||||||
|
});
|
||||||
return {
|
return {
|
||||||
privateKey: this.privateKey,
|
privateKey: this.privateKey,
|
||||||
publicKey: this.publicKey,
|
publicKey: this.publicKey,
|
||||||
|
|
|
@ -81,7 +81,6 @@ func (a *XraySettingController) warp(c *gin.Context) {
|
||||||
resp, err = a.XraySettingService.RegWarp(skey, pkey)
|
resp, err = a.XraySettingService.RegWarp(skey, pkey)
|
||||||
case "license":
|
case "license":
|
||||||
license := c.PostForm("license")
|
license := c.PostForm("license")
|
||||||
println(license)
|
|
||||||
resp, err = a.XraySettingService.SetWarpLicence(license)
|
resp, err = a.XraySettingService.SetWarpLicence(license)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -48,6 +48,9 @@ type AllSetting struct {
|
||||||
SubEncrypt bool `json:"subEncrypt" form:"subEncrypt"`
|
SubEncrypt bool `json:"subEncrypt" form:"subEncrypt"`
|
||||||
SubShowInfo bool `json:"subShowInfo" form:"subShowInfo"`
|
SubShowInfo bool `json:"subShowInfo" form:"subShowInfo"`
|
||||||
SubURI string `json:"subURI" form:"subURI"`
|
SubURI string `json:"subURI" form:"subURI"`
|
||||||
|
SubJsonPath string `json:"subJsonPath" form:"subJsonPath"`
|
||||||
|
SubJsonURI string `json:"subJsonURI" form:"subJsonURI"`
|
||||||
|
SubJsonFragment string `json:"subJsonFragment" form:"subJsonFragment"`
|
||||||
Datepicker string `json:"datepicker" form:"datepicker"`
|
Datepicker string `json:"datepicker" form:"datepicker"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,6 +108,13 @@ func (s *AllSetting) CheckValid() error {
|
||||||
s.SubPath += "/"
|
s.SubPath += "/"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !strings.HasPrefix(s.SubJsonPath, "/") {
|
||||||
|
s.SubJsonPath = "/" + s.SubJsonPath
|
||||||
|
}
|
||||||
|
if !strings.HasSuffix(s.SubJsonPath, "/") {
|
||||||
|
s.SubJsonPath += "/"
|
||||||
|
}
|
||||||
|
|
||||||
_, err := time.LoadLocation(s.TimeLocation)
|
_, err := time.LoadLocation(s.TimeLocation)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return common.NewError("time location not exist:", s.TimeLocation)
|
return common.NewError("time location not exist:", s.TimeLocation)
|
||||||
|
|
|
@ -7,8 +7,6 @@
|
||||||
<link rel="stylesheet" href="{{ .base_path }}assets/ant-design-vue@1.7.8/antd.min.css">
|
<link rel="stylesheet" href="{{ .base_path }}assets/ant-design-vue@1.7.8/antd.min.css">
|
||||||
<link rel="stylesheet" href="{{ .base_path }}assets/element-ui@2.15.0/theme-chalk/display.css">
|
<link rel="stylesheet" href="{{ .base_path }}assets/element-ui@2.15.0/theme-chalk/display.css">
|
||||||
<link rel="stylesheet" href="{{ .base_path }}assets/css/custom.css?{{ .cur_ver }}">
|
<link rel="stylesheet" href="{{ .base_path }}assets/css/custom.css?{{ .cur_ver }}">
|
||||||
<link rel=”icon” type=”image/x-icon” href="{{ .base_path }}assets/favicon.ico">
|
|
||||||
<link rel="shortcut icon" type="image/x-icon" href="{{ .base_path }}assets/favicon.ico">
|
|
||||||
<link rel="manifest" href="{{ .base_path }}assets/manifest.json">
|
<link rel="manifest" href="{{ .base_path }}assets/manifest.json">
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
@ -40,4 +38,5 @@
|
||||||
</style>
|
</style>
|
||||||
<title>{{ .host }}-{{ i18n .title}}</title>
|
<title>{{ .host }}-{{ i18n .title}}</title>
|
||||||
</head>
|
</head>
|
||||||
|
<div id="message"></div>
|
||||||
{{end}}
|
{{end}}
|
|
@ -8,13 +8,23 @@
|
||||||
{{ i18n "pages.inbounds.clickOnQRcode" }}
|
{{ i18n "pages.inbounds.clickOnQRcode" }}
|
||||||
</a-tag>
|
</a-tag>
|
||||||
<template v-if="app.subSettings.enable && qrModal.subId">
|
<template v-if="app.subSettings.enable && qrModal.subId">
|
||||||
<a-divider>Subscription</a-divider>
|
<a-divider>{{ i18n "pages.settings.subSettings"}}</a-divider>
|
||||||
<div class="qr-bg"><canvas @click="copyToClipboard('qrCode-sub',genSubLink(qrModal.client.subId))" id="qrCode-sub" style="width: 100%; height: 100%;"></canvas></div>
|
<canvas @click="copyToClipboard('qrCode-sub',genSubLink(qrModal.client.subId))"
|
||||||
|
id="qrCode-sub"
|
||||||
|
style="width: 100%; height: 100%; display: flex; border-radius: 1rem;">
|
||||||
|
</canvas>
|
||||||
|
<a-divider>{{ i18n "pages.settings.subSettings"}} Json</a-divider>
|
||||||
|
<canvas @click="copyToClipboard('qrCode-subJson',genSubJsonLink(qrModal.client.subId))"
|
||||||
|
id="qrCode-subJson"
|
||||||
|
style="width: 100%; height: 100%; display: flex; border-radius: 1rem;">
|
||||||
|
</canvas>
|
||||||
</template>
|
</template>
|
||||||
<a-divider>{{ i18n "pages.inbounds.client" }}</a-divider>
|
<a-divider>{{ i18n "pages.inbounds.client" }}</a-divider>
|
||||||
<template v-for="(row, index) in qrModal.qrcodes">
|
<template v-for="(row, index) in qrModal.qrcodes">
|
||||||
<a-tag color="green" style="margin: 10px 0; display: block; text-align: center;">[[ row.remark ]]</a-tag>
|
<a-tag color="blue" style="margin: 10px 0; display: block; text-align: center;">[[ row.remark ]]</a-tag>
|
||||||
<div class="qr-bg"><canvas @click="copyToClipboard('qrCode-'+index, row.link)" :id="'qrCode-'+index" style="width: 100%; height: 100%;"></canvas></div>
|
<canvas @click="copyToClipboard('qrCode-'+index, row.link)"
|
||||||
|
:id="'qrCode-'+index"
|
||||||
|
style="width: 100%; height: 100%; display: flex; border-radius: 1rem;"></canvas>
|
||||||
</template>
|
</template>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
|
|
||||||
|
@ -82,12 +92,16 @@
|
||||||
},
|
},
|
||||||
genSubLink(subID) {
|
genSubLink(subID) {
|
||||||
return app.subSettings.subURI+subID;
|
return app.subSettings.subURI+subID;
|
||||||
|
},
|
||||||
|
genSubJsonLink(subID) {
|
||||||
|
return app.subSettings.subJsonURI+subID;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
updated() {
|
updated() {
|
||||||
if (qrModal.client && qrModal.client.subId) {
|
if (qrModal.client && qrModal.client.subId) {
|
||||||
qrModal.subId = qrModal.client.subId;
|
qrModal.subId = qrModal.client.subId;
|
||||||
this.setQrCode("qrCode-sub", this.genSubLink(qrModal.subId));
|
this.setQrCode("qrCode-sub", this.genSubLink(qrModal.subId));
|
||||||
|
this.setQrCode("qrCode-subJson", this.genSubJsonLink(qrModal.subId));
|
||||||
}
|
}
|
||||||
qrModal.qrcodes.forEach((element, index) => {
|
qrModal.qrcodes.forEach((element, index) => {
|
||||||
this.setQrCode("qrCode-" + index, element.link);
|
this.setQrCode("qrCode-" + index, element.link);
|
||||||
|
|
|
@ -1,15 +1,16 @@
|
||||||
{{define "textModal"}}
|
{{define "textModal"}}
|
||||||
<a-modal id="text-modal" v-model="txtModal.visible" :title="txtModal.title"
|
<a-modal id="text-modal" v-model="txtModal.visible" :title="txtModal.title"
|
||||||
:closable="true" ok-text='{{ i18n "copy" }}' cancel-text='{{ i18n "close" }}'
|
:closable="true"
|
||||||
:class="themeSwitcher.currentTheme"
|
:class="themeSwitcher.currentTheme">
|
||||||
:ok-button-props="{attrs:{id:'txt-modal-ok-btn'}}">
|
<template slot="footer">
|
||||||
<a-button v-if="!ObjectUtil.isEmpty(txtModal.fileName)" type="primary" style="margin-bottom: 10px;"
|
<a-button v-if="!ObjectUtil.isEmpty(txtModal.fileName)" icon="download"
|
||||||
:href="'data:application/text;charset=utf-8,' + encodeURIComponent(txtModal.content)"
|
:href="'data:application/text;charset=utf-8,' + encodeURIComponent(txtModal.content)"
|
||||||
:download="txtModal.fileName">
|
:download="txtModal.fileName">[[ txtModal.fileName ]]
|
||||||
{{ i18n "download" }} [[ txtModal.fileName ]]
|
</a-button>
|
||||||
</a-button>
|
<a-button type="primary" id="copy-btn">{{ i18n "copy" }}</a-button>
|
||||||
|
</template>
|
||||||
<a-input type="textarea" v-model="txtModal.content"
|
<a-input type="textarea" v-model="txtModal.content"
|
||||||
:autosize="{ minRows: 10, maxRows: 20}"></a-input>
|
:autosize="{ minRows: 10, maxRows: 20}"></a-input>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
@ -28,7 +29,7 @@
|
||||||
this.visible = true;
|
this.visible = true;
|
||||||
textModalApp.$nextTick(() => {
|
textModalApp.$nextTick(() => {
|
||||||
if (this.clipboard === null) {
|
if (this.clipboard === null) {
|
||||||
this.clipboard = new ClipboardJS('#txt-modal-ok-btn', {
|
this.clipboard = new ClipboardJS('#copy-btn', {
|
||||||
text: () => this.content,
|
text: () => this.content,
|
||||||
});
|
});
|
||||||
this.clipboard.on('success', () => {
|
this.clipboard.on('success', () => {
|
||||||
|
|
|
@ -220,7 +220,7 @@
|
||||||
clientsBulkModal.visible = false;
|
clientsBulkModal.visible = false;
|
||||||
clientsBulkModal.loading(false);
|
clientsBulkModal.loading(false);
|
||||||
},
|
},
|
||||||
loading(loading) {
|
loading(loading=true) {
|
||||||
clientsBulkModal.confirmLoading = loading;
|
clientsBulkModal.confirmLoading = loading;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -72,7 +72,7 @@
|
||||||
clientModal.visible = false;
|
clientModal.visible = false;
|
||||||
clientModal.loading(false);
|
clientModal.loading(false);
|
||||||
},
|
},
|
||||||
loading(loading) {
|
loading(loading=true) {
|
||||||
clientModal.confirmLoading = loading;
|
clientModal.confirmLoading = loading;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
this.isDarkTheme = !this.isDarkTheme;
|
this.isDarkTheme = !this.isDarkTheme;
|
||||||
localStorage.setItem('dark-mode', this.isDarkTheme);
|
localStorage.setItem('dark-mode', this.isDarkTheme);
|
||||||
document.querySelector('body').setAttribute('class', this.isDarkTheme ? 'dark' : 'light')
|
document.querySelector('body').setAttribute('class', this.isDarkTheme ? 'dark' : 'light')
|
||||||
|
document.getElementById('message').className = themeSwitcher.currentTheme;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -31,6 +32,10 @@
|
||||||
props: [],
|
props: [],
|
||||||
template: `{{template "component/themeSwitchTemplate"}}`,
|
template: `{{template "component/themeSwitchTemplate"}}`,
|
||||||
data: () => ({ themeSwitcher }),
|
data: () => ({ themeSwitcher }),
|
||||||
|
mounted() {
|
||||||
|
this.$message.config({getContainer: () => document.getElementById('message')});
|
||||||
|
document.getElementById('message').className = themeSwitcher.currentTheme;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{{end}}
|
{{end}}
|
86
web/html/xui/dns_modal.html
Normal file
86
web/html/xui/dns_modal.html
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
{{define "dnsModal"}}
|
||||||
|
<a-modal id="dns-modal" v-model="dnsModal.visible" :title="dnsModal.title" @ok="dnsModal.ok"
|
||||||
|
:closable="true" :mask-closable="false"
|
||||||
|
:ok-text="dnsModal.okText" cancel-text='{{ i18n "close" }}' :class="themeSwitcher.currentTheme">
|
||||||
|
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
||||||
|
<a-form-item label='{{ i18n "pages.xray.outbound.address" }}'>
|
||||||
|
<a-input v-model.trim="dnsModal.dnsServer.address"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label='{{ i18n "pages.xray.dns.domains" }}'>
|
||||||
|
<a-button size="small" type="primary" @click="dnsModal.dnsServer.domains.push('')">+</a-button>
|
||||||
|
<template v-for="(domain, index) in dnsModal.dnsServer.domains">
|
||||||
|
<a-input v-model.trim="dnsModal.dnsServer.domains[index]">
|
||||||
|
<a-button size="small" slot="addonAfter" @click="dnsModal.dnsServer.domains.splice(index,1)">-</a-button>
|
||||||
|
</a-input>
|
||||||
|
</template>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label='{{ i18n "pages.xray.dns.strategy" }}' v-if="isAdvanced">
|
||||||
|
<a-select
|
||||||
|
v-model="dnsModal.dnsServer.queryStrategy"
|
||||||
|
style="width: 100%"
|
||||||
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option :value="l" :label="l" v-for="l in ['UseIP', 'UseIPv4', 'UseIPv6']">
|
||||||
|
[[ l ]]
|
||||||
|
</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
</a-modal>
|
||||||
|
<script>
|
||||||
|
const dnsModal = {
|
||||||
|
title: '',
|
||||||
|
visible: false,
|
||||||
|
okText: '{{ i18n "confirm" }}',
|
||||||
|
isEdit: false,
|
||||||
|
confirm: null,
|
||||||
|
dnsServer: {
|
||||||
|
address: "localhost",
|
||||||
|
domains: [],
|
||||||
|
queryStrategy: 'UseIP',
|
||||||
|
},
|
||||||
|
ok() {
|
||||||
|
domains = dnsModal.dnsServer.domains.filter(d => d.length>0);
|
||||||
|
dnsModal.dnsServer.domains = domains;
|
||||||
|
newDnsServer = domains.length > 0 ? dnsModal.dnsServer : dnsModal.dnsServer.address;
|
||||||
|
ObjectUtil.execute(dnsModal.confirm, newDnsServer);
|
||||||
|
},
|
||||||
|
show({ title='', okText='{{ i18n "confirm" }}', dnsServer, confirm=(dnsServer)=>{}, isEdit=false }) {
|
||||||
|
this.title = title;
|
||||||
|
this.okText = okText;
|
||||||
|
this.confirm = confirm;
|
||||||
|
this.visible = true;
|
||||||
|
if(isEdit) {
|
||||||
|
if (typeof dnsServer == 'object'){
|
||||||
|
this.dnsServer = dnsServer;
|
||||||
|
} else {
|
||||||
|
this.dnsServer.address = dnsServer?? '';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.dnsServer = {
|
||||||
|
address: "localhost",
|
||||||
|
domains: [],
|
||||||
|
queryStrategy: 'UseIP',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.isEdit = isEdit;
|
||||||
|
},
|
||||||
|
close() {
|
||||||
|
dnsModal.visible = false;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
new Vue({
|
||||||
|
delimiters: ['[[', ']]'],
|
||||||
|
el: '#dns-modal',
|
||||||
|
data: {
|
||||||
|
dnsModal: dnsModal,
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
isAdvanced: {
|
||||||
|
get: function () { return dnsModal.dnsServer.domains.length>0 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
{{end}}
|
57
web/html/xui/fakedns_modal.html
Normal file
57
web/html/xui/fakedns_modal.html
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
{{define "fakednsModal"}}
|
||||||
|
<a-modal id="fakedns-modal" v-model="fakednsModal.visible" :title="fakednsModal.title" @ok="fakednsModal.ok"
|
||||||
|
:closable="true" :mask-closable="false"
|
||||||
|
:ok-text="fakednsModal.okText" cancel-text='{{ i18n "close" }}' :class="themeSwitcher.currentTheme">
|
||||||
|
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
||||||
|
<a-form-item label='{{ i18n "pages.xray.fakedns.ipPool" }}'>
|
||||||
|
<a-input v-model.trim="fakednsModal.fakeDns.ipPool"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label='{{ i18n "pages.xray.fakedns.poolSize" }}'>
|
||||||
|
<a-input type="number" min="1" v-model.trim="fakednsModal.fakeDns.poolSize"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
</a-modal>
|
||||||
|
<script>
|
||||||
|
const fakednsModal = {
|
||||||
|
title: '',
|
||||||
|
visible: false,
|
||||||
|
okText: '{{ i18n "confirm" }}',
|
||||||
|
isEdit: false,
|
||||||
|
confirm: null,
|
||||||
|
fakeDns: {
|
||||||
|
ipPool: "198.18.0.0/16",
|
||||||
|
poolSize: 65535,
|
||||||
|
},
|
||||||
|
ok() {
|
||||||
|
ObjectUtil.execute(fakednsModal.confirm, fakednsModal.fakeDns);
|
||||||
|
},
|
||||||
|
show({ title='', okText='{{ i18n "confirm" }}', fakeDns, confirm=(fakeDns)=>{}, isEdit=false }) {
|
||||||
|
this.title = title;
|
||||||
|
this.okText = okText;
|
||||||
|
this.confirm = confirm;
|
||||||
|
this.visible = true;
|
||||||
|
if(isEdit) {
|
||||||
|
this.fakeDns = fakeDns;
|
||||||
|
} else {
|
||||||
|
this.fakeDns = {
|
||||||
|
ipPool: "198.18.0.0/16",
|
||||||
|
poolSize: 65535,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.isEdit = isEdit;
|
||||||
|
},
|
||||||
|
close() {
|
||||||
|
fakednsModal.visible = false;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
new Vue({
|
||||||
|
delimiters: ['[[', ']]'],
|
||||||
|
el: '#fakedns-modal',
|
||||||
|
data: {
|
||||||
|
fakednsModal: fakednsModal,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
{{end}}
|
|
@ -114,7 +114,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- sniffing -->
|
<!-- sniffing -->
|
||||||
<template v-if="inbound.canSniffing()">
|
<template>
|
||||||
{{template "form/sniffing"}}
|
{{template "form/sniffing"}}
|
||||||
</template>
|
</template>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{{define "form/sniffing"}}
|
{{define "form/sniffing"}}
|
||||||
<a-divider style="margin:5px 0 0;"></a-divider>
|
<a-divider style="margin:5px 0 0;"></a-divider>
|
||||||
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<span slot="label">
|
<span slot="label">
|
||||||
Sniffing
|
Sniffing
|
||||||
|
|
|
@ -40,7 +40,7 @@
|
||||||
<a-switch v-model="client.enable" @change="switchEnableClient(record.id,client)"></a-switch>
|
<a-switch v-model="client.enable" @change="switchEnableClient(record.id,client)"></a-switch>
|
||||||
</template>
|
</template>
|
||||||
<template slot="online" slot-scope="text, client, index">
|
<template slot="online" slot-scope="text, client, index">
|
||||||
<template v-if="isClientOnline(client.email)">
|
<template v-if="client.enable && isClientOnline(client.email)">
|
||||||
<a-tag color="green">{{ i18n "online" }}</a-tag>
|
<a-tag color="green">{{ i18n "online" }}</a-tag>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
|
@ -52,7 +52,7 @@
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
<template v-if="!isClientEnabled(record, client.email)">{{ i18n "depleted" }}</template>
|
<template v-if="!isClientEnabled(record, client.email)">{{ i18n "depleted" }}</template>
|
||||||
<template v-else-if="!client.enable">{{ i18n "disabled" }}</template>
|
<template v-else-if="!client.enable">{{ i18n "disabled" }}</template>
|
||||||
<template v-else-if="isClientOnline(client.email)">{{ i18n "online" }}</template>
|
<template v-else-if="client.enable && isClientOnline(client.email)">{{ i18n "online" }}</template>
|
||||||
</template>
|
</template>
|
||||||
<a-badge :class="isClientOnline(client.email)? 'online-animation' : ''" :color="client.enable ? statsExpColor(record, client.email) : themeSwitcher.isDarkTheme ? '#2c3950' : '#bcbcbc'">
|
<a-badge :class="isClientOnline(client.email)? 'online-animation' : ''" :color="client.enable ? statsExpColor(record, client.email) : themeSwitcher.isDarkTheme ? '#2c3950' : '#bcbcbc'">
|
||||||
</a-badge>
|
</a-badge>
|
||||||
|
|
|
@ -166,7 +166,7 @@
|
||||||
<template v-if="app.subSettings.enable && infoModal.clientSettings.subId">
|
<template v-if="app.subSettings.enable && infoModal.clientSettings.subId">
|
||||||
<a-divider>Subscription URL</a-divider>
|
<a-divider>Subscription URL</a-divider>
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-col :sx="24" :md="22"><a :href="[[ infoModal.subLink ]]" target="_blank">[[ infoModal.subLink ]]</a></a-col>
|
<a-col :sx="24" :md="22">SUB: <a :href="[[ infoModal.subLink ]]" target="_blank">[[ infoModal.subLink ]]</a></a-col>
|
||||||
<a-col :sx="24" :md="2" style="text-align: right;">
|
<a-col :sx="24" :md="2" style="text-align: right;">
|
||||||
<a-tooltip title='{{ i18n "copy" }}'>
|
<a-tooltip title='{{ i18n "copy" }}'>
|
||||||
<button class="ant-btn ant-btn-primary" id="copy-sub-link" @click="copyToClipboard('copy-sub-link', infoModal.subLink)">
|
<button class="ant-btn ant-btn-primary" id="copy-sub-link" @click="copyToClipboard('copy-sub-link', infoModal.subLink)">
|
||||||
|
@ -175,6 +175,16 @@
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
|
<a-row>
|
||||||
|
<a-col :sx="24" :md="22">JSON: <a :href="[[ infoModal.subJsonLink ]]" target="_blank">[[ infoModal.subJsonLink ]]</a></a-col>
|
||||||
|
<a-col :sx="24" :md="2" style="text-align: right; margin-top: 5px;">
|
||||||
|
<a-tooltip title='{{ i18n "copy" }}'>
|
||||||
|
<button class="ant-btn ant-btn-primary" id="copy-subJson-link" @click="copyToClipboard('copy-subJson-link', infoModal.subJsonLink)">
|
||||||
|
<a-icon type="snippets"></a-icon>
|
||||||
|
</button>
|
||||||
|
</a-tooltip>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
</template>
|
</template>
|
||||||
<template v-if="app.tgBotEnable && infoModal.clientSettings.tgId">
|
<template v-if="app.tgBotEnable && infoModal.clientSettings.tgId">
|
||||||
<a-divider>Telegram ID</a-divider>
|
<a-divider>Telegram ID</a-divider>
|
||||||
|
@ -345,6 +355,7 @@
|
||||||
index: null,
|
index: null,
|
||||||
isExpired: false,
|
isExpired: false,
|
||||||
subLink: '',
|
subLink: '',
|
||||||
|
subJsonLink: '',
|
||||||
show(dbInbound, index) {
|
show(dbInbound, index) {
|
||||||
this.index = index;
|
this.index = index;
|
||||||
this.inbound = dbInbound.toInbound();
|
this.inbound = dbInbound.toInbound();
|
||||||
|
@ -360,6 +371,7 @@
|
||||||
if (this.clientSettings) {
|
if (this.clientSettings) {
|
||||||
if (this.clientSettings.subId) {
|
if (this.clientSettings.subId) {
|
||||||
this.subLink = this.genSubLink(this.clientSettings.subId);
|
this.subLink = this.genSubLink(this.clientSettings.subId);
|
||||||
|
this.subJsonLink = this.genSubJsonLink(this.clientSettings.subId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.visible = true;
|
this.visible = true;
|
||||||
|
@ -369,6 +381,9 @@
|
||||||
},
|
},
|
||||||
genSubLink(subID) {
|
genSubLink(subID) {
|
||||||
return app.subSettings.subURI+subID;
|
return app.subSettings.subURI+subID;
|
||||||
|
},
|
||||||
|
genSubJsonLink(subID) {
|
||||||
|
return app.subSettings.subJsonURI+subID;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -40,7 +40,7 @@
|
||||||
inModal.visible = false;
|
inModal.visible = false;
|
||||||
inModal.loading(false);
|
inModal.loading(false);
|
||||||
},
|
},
|
||||||
loading(loading) {
|
loading(loading=true) {
|
||||||
inModal.confirmLoading = loading;
|
inModal.confirmLoading = loading;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -56,9 +56,13 @@
|
||||||
<a-layout-content>
|
<a-layout-content>
|
||||||
<a-spin :spinning="spinning" :delay="500" tip='{{ i18n "loading"}}'>
|
<a-spin :spinning="spinning" :delay="500" tip='{{ i18n "loading"}}'>
|
||||||
<transition name="list" appear>
|
<transition name="list" appear>
|
||||||
<a-tag v-if="false" color="red" style="margin-bottom: 10px">
|
<a-alert type="error" v-if="showAlert" style="margin-bottom: 10px"
|
||||||
Please go to the panel settings as soon as possible to modify the username and password, otherwise there may be a risk of leaking account information
|
message='{{ i18n "secAlertTitle" }}'
|
||||||
</a-tag>
|
color="red"
|
||||||
|
description='{{ i18n "secAlertSsl" }}'
|
||||||
|
show-icon closable
|
||||||
|
>
|
||||||
|
</a-alert>
|
||||||
</transition>
|
</transition>
|
||||||
<transition name="list" appear>
|
<transition name="list" appear>
|
||||||
<a-card hoverable>
|
<a-card hoverable>
|
||||||
|
@ -133,7 +137,7 @@
|
||||||
<a-icon type="export"></a-icon>
|
<a-icon type="export"></a-icon>
|
||||||
{{ i18n "pages.inbounds.export" }}
|
{{ i18n "pages.inbounds.export" }}
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
<a-menu-item key="subs">
|
<a-menu-item key="subs" v-if="subSettings.enable">
|
||||||
<a-icon type="export"></a-icon>
|
<a-icon type="export"></a-icon>
|
||||||
{{ i18n "pages.inbounds.export" }} - {{ i18n "pages.settings.subSettings" }}
|
{{ i18n "pages.inbounds.export" }} - {{ i18n "pages.settings.subSettings" }}
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
|
@ -221,7 +225,7 @@
|
||||||
<a-icon type="export"></a-icon>
|
<a-icon type="export"></a-icon>
|
||||||
{{ i18n "pages.inbounds.export"}}
|
{{ i18n "pages.inbounds.export"}}
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
<a-menu-item key="subs">
|
<a-menu-item key="subs" v-if="subSettings.enable">
|
||||||
<a-icon type="export"></a-icon>
|
<a-icon type="export"></a-icon>
|
||||||
{{ i18n "pages.inbounds.export"}} - {{ i18n "pages.settings.subSettings" }}
|
{{ i18n "pages.inbounds.export"}} - {{ i18n "pages.settings.subSettings" }}
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
|
@ -567,11 +571,13 @@
|
||||||
refreshInterval: Number(localStorage.getItem("refreshInterval")) || 5000,
|
refreshInterval: Number(localStorage.getItem("refreshInterval")) || 5000,
|
||||||
subSettings: {
|
subSettings: {
|
||||||
enable : false,
|
enable : false,
|
||||||
subURI : ''
|
subURI : '',
|
||||||
|
subJsonURI : '',
|
||||||
},
|
},
|
||||||
remarkModel: '-ieo',
|
remarkModel: '-ieo',
|
||||||
datepicker: 'gregorian',
|
datepicker: 'gregorian',
|
||||||
tgBotEnable: false,
|
tgBotEnable: false,
|
||||||
|
showAlert: false,
|
||||||
pageSize: 0,
|
pageSize: 0,
|
||||||
isMobile: window.innerWidth <= 768,
|
isMobile: window.innerWidth <= 768,
|
||||||
},
|
},
|
||||||
|
@ -613,7 +619,8 @@
|
||||||
this.tgBotEnable = tgBotEnable;
|
this.tgBotEnable = tgBotEnable;
|
||||||
this.subSettings = {
|
this.subSettings = {
|
||||||
enable : subEnable,
|
enable : subEnable,
|
||||||
subURI: subURI
|
subURI: subURI,
|
||||||
|
subJsonURI: subJsonURI
|
||||||
};
|
};
|
||||||
this.pageSize = pageSize;
|
this.pageSize = pageSize;
|
||||||
this.remarkModel = remarkModel;
|
this.remarkModel = remarkModel;
|
||||||
|
@ -651,9 +658,9 @@
|
||||||
clientCount = clients.length;
|
clientCount = clients.length;
|
||||||
if (dbInbound.enable) {
|
if (dbInbound.enable) {
|
||||||
clients.forEach(client => {
|
clients.forEach(client => {
|
||||||
if (client.enable && this.isClientOnline(client.email)) {
|
if (client.enable) {
|
||||||
active.push(client.email);
|
active.push(client.email);
|
||||||
online.push(client.email);
|
if (this.isClientOnline(client.email)) online.push(client.email);
|
||||||
} else {
|
} else {
|
||||||
deactive.push(client.email);
|
deactive.push(client.email);
|
||||||
}
|
}
|
||||||
|
@ -831,7 +838,7 @@
|
||||||
protocol: baseInbound.protocol,
|
protocol: baseInbound.protocol,
|
||||||
settings: Inbound.Settings.getSettings(baseInbound.protocol).toString(),
|
settings: Inbound.Settings.getSettings(baseInbound.protocol).toString(),
|
||||||
streamSettings: baseInbound.stream.toString(),
|
streamSettings: baseInbound.stream.toString(),
|
||||||
sniffing: baseInbound.canSniffing() ? baseInbound.sniffing.toString() : '{}',
|
sniffing: baseInbound.sniffing.toString(),
|
||||||
};
|
};
|
||||||
await this.submit('/panel/inbound/add', data, inModal);
|
await this.submit('/panel/inbound/add', data, inModal);
|
||||||
},
|
},
|
||||||
|
@ -880,7 +887,7 @@
|
||||||
settings: inbound.settings.toString(),
|
settings: inbound.settings.toString(),
|
||||||
};
|
};
|
||||||
if (inbound.canEnableStream()) data.streamSettings = inbound.stream.toString();
|
if (inbound.canEnableStream()) data.streamSettings = inbound.stream.toString();
|
||||||
if (inbound.canSniffing()) data.sniffing = inbound.sniffing.toString();
|
data.sniffing = inbound.sniffing.toString();
|
||||||
|
|
||||||
await this.submit('/panel/inbound/add', data, inModal);
|
await this.submit('/panel/inbound/add', data, inModal);
|
||||||
},
|
},
|
||||||
|
@ -899,7 +906,7 @@
|
||||||
settings: inbound.settings.toString(),
|
settings: inbound.settings.toString(),
|
||||||
};
|
};
|
||||||
if (inbound.canEnableStream()) data.streamSettings = inbound.stream.toString();
|
if (inbound.canEnableStream()) data.streamSettings = inbound.stream.toString();
|
||||||
if (inbound.canSniffing()) data.sniffing = inbound.sniffing.toString();
|
data.sniffing = inbound.sniffing.toString();
|
||||||
|
|
||||||
await this.submit(`/panel/inbound/update/${dbInbound.id}`, data, inModal);
|
await this.submit(`/panel/inbound/update/${dbInbound.id}`, data, inModal);
|
||||||
},
|
},
|
||||||
|
@ -987,7 +994,7 @@
|
||||||
},
|
},
|
||||||
delInbound(dbInboundId) {
|
delInbound(dbInboundId) {
|
||||||
this.$confirm({
|
this.$confirm({
|
||||||
title: '{{ i18n "pages.inbounds.deleteInbound"}}',
|
title: '{{ i18n "pages.inbounds.deleteInbound"}}' + ' #' + dbInboundId,
|
||||||
content: '{{ i18n "pages.inbounds.deleteInboundContent"}}',
|
content: '{{ i18n "pages.inbounds.deleteInboundContent"}}',
|
||||||
class: themeSwitcher.currentTheme,
|
class: themeSwitcher.currentTheme,
|
||||||
okText: '{{ i18n "delete"}}',
|
okText: '{{ i18n "delete"}}',
|
||||||
|
@ -1000,7 +1007,7 @@
|
||||||
clientId = this.getClientId(dbInbound.protocol, client);
|
clientId = this.getClientId(dbInbound.protocol, client);
|
||||||
if (confirmation){
|
if (confirmation){
|
||||||
this.$confirm({
|
this.$confirm({
|
||||||
title: '{{ i18n "pages.inbounds.deleteClient"}}',
|
title: '{{ i18n "pages.inbounds.deleteClient"}}' + ' ' + client.email,
|
||||||
content: '{{ i18n "pages.inbounds.deleteClientContent"}}',
|
content: '{{ i18n "pages.inbounds.deleteClientContent"}}',
|
||||||
class: themeSwitcher.currentTheme,
|
class: themeSwitcher.currentTheme,
|
||||||
okText: '{{ i18n "delete"}}',
|
okText: '{{ i18n "delete"}}',
|
||||||
|
@ -1291,7 +1298,7 @@
|
||||||
pagination(obj){
|
pagination(obj){
|
||||||
if (this.pageSize > 0 && obj.length>this.pageSize) {
|
if (this.pageSize > 0 && obj.length>this.pageSize) {
|
||||||
// Set page options based on object size
|
// Set page options based on object size
|
||||||
sizeOptions = []
|
sizeOptions = [];
|
||||||
for (i=this.pageSize;i<=obj.length;i=i+this.pageSize) {
|
for (i=this.pageSize;i<=obj.length;i=i+this.pageSize) {
|
||||||
sizeOptions.push(i.toString());
|
sizeOptions.push(i.toString());
|
||||||
}
|
}
|
||||||
|
@ -1304,8 +1311,8 @@
|
||||||
position: 'bottom',
|
position: 'bottom',
|
||||||
pageSize: this.pageSize,
|
pageSize: this.pageSize,
|
||||||
pageSizeOptions: sizeOptions
|
pageSizeOptions: sizeOptions
|
||||||
}
|
};
|
||||||
return p
|
return p;
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
},
|
},
|
||||||
|
@ -1319,6 +1326,9 @@
|
||||||
}, 500)
|
}, 500)
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
if (window.location.protocol !== "https:") {
|
||||||
|
this.showAlert = true;
|
||||||
|
}
|
||||||
window.addEventListener('resize', this.onResize);
|
window.addEventListener('resize', this.onResize);
|
||||||
this.onResize();
|
this.onResize();
|
||||||
this.loading();
|
this.loading();
|
||||||
|
@ -1356,7 +1366,6 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{{template "inboundModal"}}
|
{{template "inboundModal"}}
|
||||||
|
@ -1366,6 +1375,5 @@
|
||||||
{{template "inboundInfoModal"}}
|
{{template "inboundInfoModal"}}
|
||||||
{{template "clientsModal"}}
|
{{template "clientsModal"}}
|
||||||
{{template "clientsBulkModal"}}
|
{{template "clientsBulkModal"}}
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -34,6 +34,15 @@
|
||||||
<a-layout id="content-layout">
|
<a-layout id="content-layout">
|
||||||
<a-layout-content>
|
<a-layout-content>
|
||||||
<a-spin :spinning="spinning" :delay="200" :tip="loadingTip"/>
|
<a-spin :spinning="spinning" :delay="200" :tip="loadingTip"/>
|
||||||
|
<transition name="list" appear>
|
||||||
|
<a-alert type="error" v-if="showAlert" style="margin-bottom: 10px"
|
||||||
|
message='{{ i18n "secAlertTitle" }}'
|
||||||
|
color="red"
|
||||||
|
description='{{ i18n "secAlertSsl" }}'
|
||||||
|
show-icon closable
|
||||||
|
>
|
||||||
|
</a-alert>
|
||||||
|
</transition>
|
||||||
<transition name="list" appear>
|
<transition name="list" appear>
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-card hoverable>
|
<a-card hoverable>
|
||||||
|
@ -276,45 +285,45 @@
|
||||||
</template>
|
</template>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
|
|
||||||
<a-modal id="log-modal" v-model="logModal.visible" title="Logs"
|
<a-modal id="log-modal" v-model="logModal.visible"
|
||||||
:closable="true" @ok="() => logModal.visible = false" @cancel="() => logModal.visible = false"
|
:closable="true" @cancel="() => logModal.visible = false"
|
||||||
:class="themeSwitcher.currentTheme"
|
:class="themeSwitcher.currentTheme"
|
||||||
width="800px"
|
width="800px" footer="">
|
||||||
footer="">
|
<template slot="title">
|
||||||
|
{{ i18n "pages.index.logs" }}
|
||||||
|
<a-icon :spin="logModal.loading"
|
||||||
|
type="sync"
|
||||||
|
style="vertical-align: middle; margin-left: 10px;"
|
||||||
|
:disabled="logModal.loading"
|
||||||
|
@click="openLogs()">
|
||||||
|
</a-icon>
|
||||||
|
</template>
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<a-form-item label="Count">
|
<a-form-item>
|
||||||
<a-select v-model="logModal.rows"
|
<a-input-group compact>
|
||||||
style="width: 80px"
|
<a-select v-model="logModal.rows" style="width:70px;"
|
||||||
@change="openLogs()"
|
@change="openLogs()" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
<a-select-option value="10">10</a-select-option>
|
||||||
<a-select-option value="10">10</a-select-option>
|
<a-select-option value="20">20</a-select-option>
|
||||||
<a-select-option value="20">20</a-select-option>
|
<a-select-option value="50">50</a-select-option>
|
||||||
<a-select-option value="50">50</a-select-option>
|
<a-select-option value="100">100</a-select-option>
|
||||||
<a-select-option value="100">100</a-select-option>
|
</a-select>
|
||||||
</a-select>
|
<a-select v-model="logModal.level" style="width:100px;"
|
||||||
</a-form-item>
|
@change="openLogs()" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-form-item label="Log Level">
|
<a-select-option value="debug">Debug</a-select-option>
|
||||||
<a-select v-model="logModal.level"
|
<a-select-option value="info">Info</a-select-option>
|
||||||
style="width: 120px"
|
<a-select-option value="notice">Notice</a-select-option>
|
||||||
@change="openLogs()"
|
<a-select-option value="warning">Warning</a-select-option>
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
<a-select-option value="err">Error</a-select-option>
|
||||||
<a-select-option value="debug">Debug</a-select-option>
|
</a-select>
|
||||||
<a-select-option value="info">Info</a-select-option>
|
</a-input-group>
|
||||||
<a-select-option value="notice">Notice</a-select-option>
|
|
||||||
<a-select-option value="warning">Warning</a-select-option>
|
|
||||||
<a-select-option value="err">Error</a-select-option>
|
|
||||||
</a-select>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label="SysLog">
|
|
||||||
<a-checkbox v-model="logModal.syslog" @change="openLogs()"></a-checkbox>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-button class="ant-btn ant-btn-primary" :loading="logModal.loading" @click="openLogs()"><a-icon :spin="logModal.loading" type="sync"></a-icon> Reload</a-button>
|
<a-checkbox v-model="logModal.syslog" @change="openLogs()">SysLog</a-checkbox>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item>
|
<a-form-item style="float: right;">
|
||||||
<a-button type="primary" style="margin-bottom: 10px;"
|
<a-button type="primary" icon="download"
|
||||||
:href="'data:application/text;charset=utf-8,' + encodeURIComponent(logModal.logs.join('\n'))" download="x-ui.log">
|
:href="'data:application/text;charset=utf-8,' + encodeURIComponent(logModal.logs)" download="x-ui.log">
|
||||||
{{ i18n "download" }} x-ui.log
|
|
||||||
</a-button>
|
</a-button>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
|
@ -322,8 +331,8 @@
|
||||||
</a-modal>
|
</a-modal>
|
||||||
|
|
||||||
<a-modal id="backup-modal" v-model="backupModal.visible" :title="backupModal.title"
|
<a-modal id="backup-modal" v-model="backupModal.visible" :title="backupModal.title"
|
||||||
:closable="true" :class="themeSwitcher.currentTheme"
|
:closable="true" footer=""
|
||||||
@ok="() => backupModal.hide()" @cancel="() => backupModal.hide()">
|
:class="themeSwitcher.currentTheme">
|
||||||
<a-alert type="warning" style="margin-bottom: 10px; width: fit-content"
|
<a-alert type="warning" style="margin-bottom: 10px; width: fit-content"
|
||||||
:message="backupModal.description"
|
:message="backupModal.description"
|
||||||
show-icon
|
show-icon
|
||||||
|
@ -446,16 +455,15 @@
|
||||||
|
|
||||||
const logModal = {
|
const logModal = {
|
||||||
visible: false,
|
visible: false,
|
||||||
logs: [],
|
logs: '',
|
||||||
formattedLogs: '',
|
|
||||||
rows: 20,
|
rows: 20,
|
||||||
level: 'info',
|
level: 'info',
|
||||||
syslog: false,
|
syslog: false,
|
||||||
loading: false,
|
loading: false,
|
||||||
show(logs) {
|
show(logs) {
|
||||||
this.visible = true;
|
this.visible = true;
|
||||||
this.logs = logs || [];
|
this.logs = logs;
|
||||||
this.formattedLogs = this.logs.length > 0 ? this.formatLogs(this.logs) : "No Record...";
|
this.formattedLogs = this.logs?.length > 0 ? this.formatLogs(this.logs) : "No Record...";
|
||||||
},
|
},
|
||||||
formatLogs(logs) {
|
formatLogs(logs) {
|
||||||
let formattedLogs = '';
|
let formattedLogs = '';
|
||||||
|
@ -533,6 +541,7 @@
|
||||||
backupModal,
|
backupModal,
|
||||||
spinning: false,
|
spinning: false,
|
||||||
loadingTip: '{{ i18n "loading"}}',
|
loadingTip: '{{ i18n "loading"}}',
|
||||||
|
showAlert: false,
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
loading(spinning, tip = '{{ i18n "loading"}}') {
|
loading(spinning, tip = '{{ i18n "loading"}}') {
|
||||||
|
@ -656,14 +665,14 @@
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
async mounted() {
|
async mounted() {
|
||||||
let retries = 0;
|
if (window.location.protocol !== "https:") {
|
||||||
while (retries < 5) {
|
this.showAlert = true;
|
||||||
|
}
|
||||||
|
while (true) {
|
||||||
try {
|
try {
|
||||||
await this.getStatus();
|
await this.getStatus();
|
||||||
retries = 0;
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Error occurred while fetching status:", e);
|
console.error(e);
|
||||||
retries++;
|
|
||||||
}
|
}
|
||||||
await PromiseUtil.sleep(2000);
|
await PromiseUtil.sleep(2000);
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,28 +75,43 @@
|
||||||
<a-layout id="content-layout">
|
<a-layout id="content-layout">
|
||||||
<a-layout-content>
|
<a-layout-content>
|
||||||
<a-spin :spinning="spinning" :delay="500" tip='{{ i18n "loading"}}'>
|
<a-spin :spinning="spinning" :delay="500" tip='{{ i18n "loading"}}'>
|
||||||
|
<transition name="list" appear>
|
||||||
|
<a-alert type="error" v-if="showAlert" style="margin-bottom: 10px"
|
||||||
|
message='{{ i18n "secAlertTitle" }}'
|
||||||
|
color="red"
|
||||||
|
description='{{ i18n "secAlertSsl" }}'
|
||||||
|
show-icon closable
|
||||||
|
>
|
||||||
|
</a-alert>
|
||||||
|
<a-alert type="error" v-if="confAlerts.length>0" style="margin-bottom: 10px"
|
||||||
|
message='{{ i18n "secAlertTitle" }}'
|
||||||
|
color="red"
|
||||||
|
show-icon closable
|
||||||
|
>
|
||||||
|
<template slot="description">
|
||||||
|
{{ i18n "secAlertConf" }}
|
||||||
|
<li v-for="a in confAlerts">- [[ a ]]</li>
|
||||||
|
</template>
|
||||||
|
</a-alert>
|
||||||
|
</transition>
|
||||||
<a-space direction="vertical">
|
<a-space direction="vertical">
|
||||||
<a-card hoverable style="margin-bottom: .5rem; overflow-x: hidden;">
|
<a-card hoverable style="margin-bottom: .5rem;">
|
||||||
<a-row style="display: flex; flex-wrap: wrap; align-items: center;">
|
<a-row>
|
||||||
<a-col :xs="24" :sm="10" style="padding: 4px;">
|
<a-col :xs="24" :sm="8" style="padding: 4px;">
|
||||||
<a-space direction="horizontal">
|
<a-space direction="horizontal">
|
||||||
<a-button type="primary" :disabled="saveBtnDisable" @click="updateAllSetting">{{ i18n "pages.settings.save" }}</a-button>
|
<a-button type="primary" :disabled="saveBtnDisable" @click="updateAllSetting">{{ i18n "pages.settings.save" }}</a-button>
|
||||||
<a-button type="danger" :disabled="!saveBtnDisable" @click="restartPanel">{{ i18n "pages.settings.restartPanel" }}</a-button>
|
<a-button type="danger" :disabled="!saveBtnDisable" @click="restartPanel">{{ i18n "pages.settings.restartPanel" }}</a-button>
|
||||||
</a-space>
|
</a-space>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :xs="24" :sm="14">
|
<a-col :xs="24" :sm="16">
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<template>
|
<a-back-top :target="() => document.getElementById('content-layout')" visibility-height="200">
|
||||||
<div>
|
</a-back-top>
|
||||||
<a-back-top :target="() => document.getElementById('content-layout')" visibility-height="200">
|
<a-alert type="warning" style="float: right; width: fit-content"
|
||||||
</a-back-top>
|
message='{{ i18n "pages.settings.infoDesc" }}'
|
||||||
<a-alert type="warning" style="float: right; width: fit-content"
|
show-icon
|
||||||
message='{{ i18n "pages.settings.infoDesc" }}'
|
>
|
||||||
show-icon
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</a-col>
|
</a-col>
|
||||||
|
@ -164,7 +179,6 @@
|
||||||
<a-col :lg="24" :xl="12">
|
<a-col :lg="24" :xl="12">
|
||||||
<a-list-item-meta title="Language" />
|
<a-list-item-meta title="Language" />
|
||||||
</a-col>
|
</a-col>
|
||||||
|
|
||||||
<a-col :lg="24" :xl="12">
|
<a-col :lg="24" :xl="12">
|
||||||
<template>
|
<template>
|
||||||
<a-select
|
<a-select
|
||||||
|
@ -234,7 +248,6 @@
|
||||||
<a-button type="primary" :loading="this.changeSecret" @click="updateSecret">{{ i18n "confirm" }}</a-button>
|
<a-button type="primary" :loading="this.changeSecret" @click="updateSecret">{{ i18n "confirm" }}</a-button>
|
||||||
</a-form>
|
</a-form>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
|
|
||||||
<a-tab-pane key="3" tab='{{ i18n "pages.settings.TGBotSettings"}}'>
|
<a-tab-pane key="3" tab='{{ i18n "pages.settings.TGBotSettings"}}'>
|
||||||
<a-list item-layout="horizontal">
|
<a-list item-layout="horizontal">
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.telegramBotEnable" }}' desc='{{ i18n "pages.settings.telegramBotEnableDesc" }}' v-model="allSetting.tgBotEnable"></setting-list-item>
|
<setting-list-item type="switch" title='{{ i18n "pages.settings.telegramBotEnable" }}' desc='{{ i18n "pages.settings.telegramBotEnableDesc" }}' v-model="allSetting.tgBotEnable"></setting-list-item>
|
||||||
|
@ -257,8 +270,7 @@
|
||||||
ref="selectBotLang"
|
ref="selectBotLang"
|
||||||
v-model="allSetting.tgLang"
|
v-model="allSetting.tgLang"
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme"
|
:dropdown-class-name="themeSwitcher.currentTheme"
|
||||||
style="width: 100%"
|
style="width: 100%">
|
||||||
>
|
|
||||||
<a-select-option :value="l.value" :label="l.value" v-for="l in supportLangs">
|
<a-select-option :value="l.value" :label="l.value" v-for="l in supportLangs">
|
||||||
<span role="img" :aria-label="l.name" v-text="l.icon"></span>
|
<span role="img" :aria-label="l.name" v-text="l.icon"></span>
|
||||||
<span v-text="l.name"></span>
|
<span v-text="l.name"></span>
|
||||||
|
@ -285,6 +297,17 @@
|
||||||
<setting-list-item type="number" title='{{ i18n "pages.settings.subUpdates"}}' desc='{{ i18n "pages.settings.subUpdatesDesc"}}' v-model="allSetting.subUpdates"></setting-list-item>
|
<setting-list-item type="number" title='{{ i18n "pages.settings.subUpdates"}}' desc='{{ i18n "pages.settings.subUpdatesDesc"}}' v-model="allSetting.subUpdates"></setting-list-item>
|
||||||
</a-list>
|
</a-list>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
|
<a-tab-pane key="5" tab='{{ i18n "pages.settings.subSettings" }} Json' v-if="allSetting.subEnable">
|
||||||
|
<a-list item-layout="horizontal">
|
||||||
|
<setting-list-item type="text" title='{{ i18n "pages.settings.subPath"}}' desc='{{ i18n "pages.settings.subPathDesc"}}' v-model="allSetting.subJsonPath"></setting-list-item>
|
||||||
|
<setting-list-item type="text" title='{{ i18n "pages.settings.subURI"}}' desc='{{ i18n "pages.settings.subURIDesc"}}' v-model="allSetting.subJsonURI" placeholder="(http|https)://domain[:port]/path/"></setting-list-item>
|
||||||
|
<setting-list-item type="switch" title='{{ i18n "pages.settings.fragment"}}' desc='{{ i18n "pages.settings.fragmentDesc"}}' v-model="fragment"></setting-list-item>
|
||||||
|
<template v-if="fragment">
|
||||||
|
<setting-list-item type="text" title='length' v-model="fragmentLength" placeholder="100-200"></setting-list-item>
|
||||||
|
<setting-list-item type="text" title='Interval' v-model="fragmentInterval" placeholder="10-20"></setting-list-item>
|
||||||
|
</template>
|
||||||
|
</a-list>
|
||||||
|
</a-tab-pane>
|
||||||
</a-tabs>
|
</a-tabs>
|
||||||
</a-space>
|
</a-space>
|
||||||
</a-spin>
|
</a-spin>
|
||||||
|
@ -315,6 +338,24 @@
|
||||||
remarkSeparators: [' ','-','_','@',':','~','|',',','.','/'],
|
remarkSeparators: [' ','-','_','@',':','~','|',',','.','/'],
|
||||||
datepickerList: [{name:'Gregorian (Standard)', value: 'gregorian'}, {name:'Jalalian (شمسی)', value: 'jalalian'}],
|
datepickerList: [{name:'Gregorian (Standard)', value: 'gregorian'}, {name:'Jalalian (شمسی)', value: 'jalalian'}],
|
||||||
remarkSample: '',
|
remarkSample: '',
|
||||||
|
defaultFragment: {
|
||||||
|
tag: "fragment",
|
||||||
|
protocol: "freedom",
|
||||||
|
settings: {
|
||||||
|
domainStrategy: "AsIs",
|
||||||
|
fragment: {
|
||||||
|
packets: "tlshello",
|
||||||
|
length: "100-200",
|
||||||
|
interval: "10-20"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
streamSettings: {
|
||||||
|
sockopt: {
|
||||||
|
tcpKeepAliveIdle: 100,
|
||||||
|
TcpNoDelay: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
get remarkModel() {
|
get remarkModel() {
|
||||||
rm = this.allSetting.remarkModel;
|
rm = this.allSetting.remarkModel;
|
||||||
return rm.length>1 ? rm.substring(1).split('') : [];
|
return rm.length>1 ? rm.substring(1).split('') : [];
|
||||||
|
@ -443,13 +484,60 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
async mounted() {
|
computed: {
|
||||||
await this.getAllSetting();
|
fragment: {
|
||||||
while (true) {
|
get: function() { return this.allSetting?.subJsonFragment != ""; },
|
||||||
await PromiseUtil.sleep(600);
|
set: function (v) {
|
||||||
this.saveBtnDisable = this.oldAllSetting.equals(this.allSetting);
|
this.allSetting.subJsonFragment = v ? JSON.stringify(this.defaultFragment) : "";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fragmentLength: {
|
||||||
|
get: function() { return this.fragment ? JSON.parse(this.allSetting.subJsonFragment).settings.fragment.length : ""; },
|
||||||
|
set: function(v) {
|
||||||
|
if (v != ""){
|
||||||
|
newFragment = JSON.parse(this.allSetting.subJsonFragment);
|
||||||
|
newFragment.settings.fragment.length = v;
|
||||||
|
this.allSetting.subJsonFragment = JSON.stringify(newFragment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fragmentInterval: {
|
||||||
|
get: function() { return this.fragment ? JSON.parse(this.allSetting.subJsonFragment).settings.fragment.interval : ""; },
|
||||||
|
set: function(v) {
|
||||||
|
if (v != ""){
|
||||||
|
newFragment = JSON.parse(this.allSetting.subJsonFragment);
|
||||||
|
newFragment.settings.fragment.interval = v;
|
||||||
|
this.allSetting.subJsonFragment = JSON.stringify(newFragment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
confAlerts: {
|
||||||
|
get: function() {
|
||||||
|
if (!this.allSetting) return [];
|
||||||
|
var alerts = []
|
||||||
|
if (this.allSetting.port == 54321) alerts.push('{{ i18n "pages.settings.panelPort"}}');
|
||||||
|
panelPath = window.location.pathname.split('/').length<4
|
||||||
|
if (panelPath && this.allSetting.webBasePath == '/') alerts.push('{{ i18n "pages.settings.panelSettings"}} {{ i18n "pages.settings.panelUrlPath"}}');
|
||||||
|
if (this.allSetting.subEnable) {
|
||||||
|
subPath = this.allSetting.subURI.length >0 ? new URL(this.allSetting.subURI).pathname : this.allSetting.subPath;
|
||||||
|
if (subPath == '/sub/') alerts.push('{{ i18n "pages.settings.subSettings"}} {{ i18n "pages.settings.subPath"}}');
|
||||||
|
subJsonPath = this.allSetting.subJsonURI.length >0 ? new URL(this.allSetting.subJsonURI).pathname : this.allSetting.subJsonPath;
|
||||||
|
if (subJsonPath == '/json/') alerts.push('JSON {{ i18n "pages.settings.subPath"}}');
|
||||||
|
}
|
||||||
|
return alerts
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
async mounted() {
|
||||||
|
if (window.location.protocol !== "https:") {
|
||||||
|
this.showAlert = true;
|
||||||
|
}
|
||||||
|
await this.getAllSetting();
|
||||||
|
while (true) {
|
||||||
|
await PromiseUtil.sleep(1000);
|
||||||
|
this.saveBtnDisable = this.oldAllSetting.equals(this.allSetting);
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
@ -108,7 +108,7 @@
|
||||||
this.visible = false;
|
this.visible = false;
|
||||||
this.loading(false);
|
this.loading(false);
|
||||||
},
|
},
|
||||||
loading(loading) {
|
loading(loading=true) {
|
||||||
this.confirmLoading = loading;
|
this.confirmLoading = loading;
|
||||||
},
|
},
|
||||||
async getData(){
|
async getData(){
|
||||||
|
|
|
@ -63,6 +63,15 @@
|
||||||
<a-layout id="content-layout">
|
<a-layout id="content-layout">
|
||||||
<a-layout-content>
|
<a-layout-content>
|
||||||
<a-spin :spinning="spinning" :delay="500" tip='{{ i18n "loading"}}'>
|
<a-spin :spinning="spinning" :delay="500" tip='{{ i18n "loading"}}'>
|
||||||
|
<transition name="list" appear>
|
||||||
|
<a-alert type="error" v-if="showAlert" style="margin-bottom: 10px"
|
||||||
|
message='{{ i18n "secAlertTitle" }}'
|
||||||
|
color="red"
|
||||||
|
description='{{ i18n "secAlertSsl" }}'
|
||||||
|
show-icon closable
|
||||||
|
>
|
||||||
|
</a-alert>
|
||||||
|
</transition>
|
||||||
<a-space direction="vertical">
|
<a-space direction="vertical">
|
||||||
<a-card hoverable style="margin-bottom: .5rem;">
|
<a-card hoverable style="margin-bottom: .5rem;">
|
||||||
<a-row>
|
<a-row>
|
||||||
|
@ -133,12 +142,10 @@
|
||||||
description='{{ i18n "pages.xray.RoutingStrategyDesc" }}' />
|
description='{{ i18n "pages.xray.RoutingStrategyDesc" }}' />
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :lg="24" :xl="12">
|
<a-col :lg="24" :xl="12">
|
||||||
<template>
|
|
||||||
<a-select v-model="routingStrategy" :dropdown-class-name="themeSwitcher.currentTheme"
|
<a-select v-model="routingStrategy" :dropdown-class-name="themeSwitcher.currentTheme"
|
||||||
style="width: 100%">
|
style="width: 100%">
|
||||||
<a-select-option v-for="s in routingDomainStrategies" :value="s">[[ s ]]</a-select-option>
|
<a-select-option v-for="s in routingDomainStrategies" :value="s">[[ s ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</template>
|
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
</a-list-item>
|
</a-list-item>
|
||||||
|
@ -516,6 +523,86 @@
|
||||||
</template>
|
</template>
|
||||||
</a-table>
|
</a-table>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
|
<a-tab-pane key="tpl-6" tab='DNS' style="padding-top: 20px;" force-render="true">
|
||||||
|
<setting-list-item type="switch" title='{{ i18n "pages.xray.dns.enable" }}' desc='{{ i18n "pages.xray.dns.enableDesc" }}' v-model="enableDNS"></setting-list-item>
|
||||||
|
<template v-if="enableDNS">
|
||||||
|
<a-list-item>
|
||||||
|
<a-row style="padding: 20px">
|
||||||
|
<a-col :lg="24" :xl="12">
|
||||||
|
<a-list-item-meta title='{{ i18n "pages.xray.dns.strategy" }}' description='{{ i18n "pages.xray.dns.strategyDesc" }}' />
|
||||||
|
</a-col>
|
||||||
|
<a-col :lg="24" :xl="12">
|
||||||
|
<a-select
|
||||||
|
v-model="dnsStrategy"
|
||||||
|
style="width: 100%"
|
||||||
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option :value="l" :label="l" v-for="l in ['UseIP', 'UseIPv4', 'UseIPv6']">
|
||||||
|
[[ l ]]
|
||||||
|
</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</a-list-item>
|
||||||
|
<a-divider>DNS</a-divider>
|
||||||
|
<a-button type="primary" icon="plus" @click="addDNSServer()" style="margin-bottom: 10px;">{{ i18n "pages.xray.dns.add" }}</a-button>
|
||||||
|
<a-table :columns="dnsColumns" bordered v-if="dnsServers.length>0"
|
||||||
|
:row-key="r => r.key"
|
||||||
|
:data-source="dnsServers"
|
||||||
|
:scroll="isMobile ? {} : { x: 200 }"
|
||||||
|
:pagination="false"
|
||||||
|
:indent-size="0"
|
||||||
|
:style="isMobile ? 'padding: 5px 0' : 'margin-left: 1px;'">
|
||||||
|
<template slot="action" slot-scope="text,dns,index">
|
||||||
|
[[ index+1 ]]
|
||||||
|
<a-dropdown :trigger="['click']">
|
||||||
|
<a-icon @click="e => e.preventDefault()" type="more" style="font-size: 16px; text-decoration: bold;"></a-icon>
|
||||||
|
<a-menu slot="overlay" :theme="themeSwitcher.currentTheme">
|
||||||
|
<a-menu-item @click="editDNSServer(index)">
|
||||||
|
<a-icon type="edit"></a-icon>
|
||||||
|
{{ i18n "edit" }}
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item @click="deleteDNSServer(index)">
|
||||||
|
<span style="color: #FF4D4F">
|
||||||
|
<a-icon type="delete"></a-icon> {{ i18n "delete"}}
|
||||||
|
</span>
|
||||||
|
</a-menu-item>
|
||||||
|
</a-menu>
|
||||||
|
</a-dropdown>
|
||||||
|
</template>
|
||||||
|
<template slot="address" slot-scope="dns,index">
|
||||||
|
<span v-if="typeof dns == 'object'">[[ dns.address ]]</span>
|
||||||
|
<span v-else>[[ dns ]]</span>
|
||||||
|
</template>
|
||||||
|
<template slot="domain" slot-scope="dns,index">
|
||||||
|
<span v-if="typeof dns == 'object'">[[ dns.domains.join(",") ]]</span>
|
||||||
|
</template>
|
||||||
|
</a-table>
|
||||||
|
<a-divider>Fake DNS</a-divider>
|
||||||
|
<a-button type="primary" icon="plus" @click="addFakedns()" style="margin-bottom: 10px;">{{ i18n "pages.xray.fakedns.add" }}</a-button>
|
||||||
|
<a-table :columns="fakednsColumns" bordered v-if="fakeDns && fakeDns.length>0" :row-key="r => r.key"
|
||||||
|
:data-source="fakeDns" :scroll="isMobile ? {} : { x: 200 }" :pagination="false" :indent-size="0"
|
||||||
|
:style="isMobile ? 'padding: 5px 0' : 'margin-left: 1px;'">
|
||||||
|
<template slot="action" slot-scope="text,fakedns,index">
|
||||||
|
[[ index+1 ]]
|
||||||
|
<a-dropdown :trigger="['click']">
|
||||||
|
<a-icon @click="e => e.preventDefault()" type="more"
|
||||||
|
style="font-size: 16px; text-decoration: bold;"></a-icon>
|
||||||
|
<a-menu slot="overlay" :theme="themeSwitcher.currentTheme">
|
||||||
|
<a-menu-item @click="editFakedns(index)">
|
||||||
|
<a-icon type="edit"></a-icon>
|
||||||
|
{{ i18n "edit" }}
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item @click="deleteFakedns(index)">
|
||||||
|
<span style="color: #FF4D4F">
|
||||||
|
<a-icon type="delete"></a-icon> {{ i18n "delete"}}
|
||||||
|
</span>
|
||||||
|
</a-menu-item>
|
||||||
|
</a-menu>
|
||||||
|
</a-dropdown>
|
||||||
|
</template>
|
||||||
|
</a-table>
|
||||||
|
</template>
|
||||||
|
</a-tab-pane>
|
||||||
<a-tab-pane key="tpl-advanced" tab='{{ i18n "pages.xray.advancedTemplate"}}' style="padding-top: 20px;" force-render="true">
|
<a-tab-pane key="tpl-advanced" tab='{{ i18n "pages.xray.advancedTemplate"}}' style="padding-top: 20px;" force-render="true">
|
||||||
<a-list-item-meta title='{{ i18n "pages.xray.Template"}}' description='{{ i18n "pages.xray.TemplateDesc"}}'></a-list-item-meta>
|
<a-list-item-meta title='{{ i18n "pages.xray.Template"}}' description='{{ i18n "pages.xray.TemplateDesc"}}'></a-list-item-meta>
|
||||||
<a-radio-group v-model="advSettings" @change="changeCode" button-style="solid" style="margin: 10px 0;" :size="isMobile ? 'small' : ''">
|
<a-radio-group v-model="advSettings" @change="changeCode" button-style="solid" style="margin: 10px 0;" :size="isMobile ? 'small' : ''">
|
||||||
|
@ -539,6 +626,8 @@
|
||||||
{{template "outModal"}}
|
{{template "outModal"}}
|
||||||
{{template "reverseModal"}}
|
{{template "reverseModal"}}
|
||||||
{{template "balancerModal"}}
|
{{template "balancerModal"}}
|
||||||
|
{{template "dnsModal"}}
|
||||||
|
{{template "fakednsModal"}}
|
||||||
{{template "warpModal"}}
|
{{template "warpModal"}}
|
||||||
<script>
|
<script>
|
||||||
const rulesColumns = [
|
const rulesColumns = [
|
||||||
|
@ -583,6 +672,18 @@
|
||||||
{ title: '{{ i18n "pages.xray.outbound.domain"}}', dataIndex: 'domain', align: 'center', width: 50 },
|
{ title: '{{ i18n "pages.xray.outbound.domain"}}', dataIndex: 'domain', align: 'center', width: 50 },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const dnsColumns = [
|
||||||
|
{ title: "#", align: 'center', width: 20, scopedSlots: { customRender: 'action' } },
|
||||||
|
{ title: '{{ i18n "pages.xray.outbound.address"}}', align: 'center', width: 50, scopedSlots: { customRender: 'address' } },
|
||||||
|
{ title: '{{ i18n "pages.xray.dns.domains"}}', align: 'center', width: 50, scopedSlots: { customRender: 'domain' } },
|
||||||
|
];
|
||||||
|
|
||||||
|
const fakednsColumns = [
|
||||||
|
{ title: "#", align: 'center', width: 20, scopedSlots: { customRender: 'action' } },
|
||||||
|
{ title: '{{ i18n "pages.xray.fakedns.ipPool"}}', dataIndex: 'ipPool', align: 'center', width: 50 },
|
||||||
|
{ title: '{{ i18n "pages.xray.fakedns.poolSize"}}', dataIndex: 'poolSize', align: 'center', width: 50 },
|
||||||
|
];
|
||||||
|
|
||||||
const balancerColumns = [
|
const balancerColumns = [
|
||||||
{ title: "#", align: 'center', width: 20, scopedSlots: { customRender: 'action' } },
|
{ title: "#", align: 'center', width: 20, scopedSlots: { customRender: 'action' } },
|
||||||
{ title: '{{ i18n "pages.xray.balancer.tag"}}', dataIndex: 'tag', align: 'center', width: 50 },
|
{ title: '{{ i18n "pages.xray.balancer.tag"}}', dataIndex: 'tag', align: 'center', width: 50 },
|
||||||
|
@ -605,6 +706,7 @@
|
||||||
saveBtnDisable: true,
|
saveBtnDisable: true,
|
||||||
refreshing: false,
|
refreshing: false,
|
||||||
restartResult: '',
|
restartResult: '',
|
||||||
|
showAlert: false,
|
||||||
isMobile: window.innerWidth <= 768,
|
isMobile: window.innerWidth <= 768,
|
||||||
advSettings: 'xraySetting',
|
advSettings: 'xraySetting',
|
||||||
cm: null,
|
cm: null,
|
||||||
|
@ -1158,6 +1260,66 @@
|
||||||
|
|
||||||
this.templateSettings = newTemplateSettings;
|
this.templateSettings = newTemplateSettings;
|
||||||
},
|
},
|
||||||
|
addDNSServer(){
|
||||||
|
dnsModal.show({
|
||||||
|
title: '{{ i18n "pages.xray.dns.add" }}',
|
||||||
|
confirm: (dnsServer) => {
|
||||||
|
dnsServers = this.dnsServers;
|
||||||
|
dnsServers.push(dnsServer);
|
||||||
|
this.dnsServers = dnsServers;
|
||||||
|
dnsModal.close();
|
||||||
|
},
|
||||||
|
isEdit: false
|
||||||
|
});
|
||||||
|
},
|
||||||
|
editDNSServer(index){
|
||||||
|
dnsModal.show({
|
||||||
|
title: '{{ i18n "pages.xray.dns.edit" }} #' + (index+1),
|
||||||
|
dnsServer: this.dnsServers[index],
|
||||||
|
confirm: (dnsServer) => {
|
||||||
|
dnsServers = this.dnsServers;
|
||||||
|
dnsServers[index] = dnsServer;
|
||||||
|
this.dnsServers = dnsServers;
|
||||||
|
dnsModal.close();
|
||||||
|
},
|
||||||
|
isEdit: true
|
||||||
|
});
|
||||||
|
},
|
||||||
|
deleteDNSServer(index){
|
||||||
|
newDnsServers = this.dnsServers;
|
||||||
|
newDnsServers.splice(index,1);
|
||||||
|
this.dnsServers = newDnsServers;
|
||||||
|
},
|
||||||
|
addFakedns() {
|
||||||
|
fakednsModal.show({
|
||||||
|
title: '{{ i18n "pages.xray.fakedns.add" }}',
|
||||||
|
confirm: (item) => {
|
||||||
|
fakeDns = this.fakeDns?? [];
|
||||||
|
fakeDns.push(item);
|
||||||
|
this.fakeDns = fakeDns;
|
||||||
|
fakednsModal.close();
|
||||||
|
},
|
||||||
|
isEdit: false
|
||||||
|
});
|
||||||
|
},
|
||||||
|
editFakedns(index){
|
||||||
|
fakednsModal.show({
|
||||||
|
title: '{{ i18n "pages.xray.fakedns.edit" }} #' + (index+1),
|
||||||
|
fakeDns: this.fakeDns[index],
|
||||||
|
confirm: (item) => {
|
||||||
|
fakeDns = this.fakeDns;
|
||||||
|
fakeDns[index] = item;
|
||||||
|
this.fakeDns = fakeDns;
|
||||||
|
fakednsModal.close();
|
||||||
|
},
|
||||||
|
isEdit: true
|
||||||
|
});
|
||||||
|
},
|
||||||
|
deleteFakedns(index){
|
||||||
|
fakeDns = this.fakeDns;
|
||||||
|
fakeDns.splice(index,1);
|
||||||
|
this.fakeDns = fakeDns;
|
||||||
|
},
|
||||||
addRule(){
|
addRule(){
|
||||||
ruleModal.show({
|
ruleModal.show({
|
||||||
title: '{{ i18n "pages.xray.rules.add"}}',
|
title: '{{ i18n "pages.xray.rules.add"}}',
|
||||||
|
@ -1204,6 +1366,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async mounted() {
|
async mounted() {
|
||||||
|
if (window.location.protocol !== "https:") {
|
||||||
|
this.showAlert = true;
|
||||||
|
}
|
||||||
await this.getXraySetting();
|
await this.getXraySetting();
|
||||||
await this.getXrayResult();
|
await this.getXrayResult();
|
||||||
await this.getOutboundsTraffic();
|
await this.getOutboundsTraffic();
|
||||||
|
@ -1499,14 +1664,14 @@
|
||||||
familyProtectSettings: {
|
familyProtectSettings: {
|
||||||
get: function () {
|
get: function () {
|
||||||
if (!this.templateSettings || !this.templateSettings.dns || !this.templateSettings.dns.servers) return false;
|
if (!this.templateSettings || !this.templateSettings.dns || !this.templateSettings.dns.servers) return false;
|
||||||
return doAllItemsExist(this.templateSettings.dns.servers, this.settingsData.familyProtectDNS.servers);
|
return doAllItemsExist(this.settingsData.familyProtectDNS.servers, this.templateSettings.dns.servers);
|
||||||
},
|
},
|
||||||
set: function (newValue) {
|
set: function (newValue) {
|
||||||
newTemplateSettings = this.templateSettings;
|
newTemplateSettings = this.templateSettings;
|
||||||
if (newValue) {
|
if (newValue) {
|
||||||
newTemplateSettings.dns = this.settingsData.familyProtectDNS;
|
newTemplateSettings.dns = this.settingsData.familyProtectDNS;
|
||||||
} else {
|
} else {
|
||||||
delete newTemplateSettings.dns;
|
newTemplateSettings.dns.servers = newTemplateSettings.dns?.servers?.filter(data => !this.settingsData.familyProtectDNS.servers.includes(data))
|
||||||
}
|
}
|
||||||
this.templateSettings = newTemplateSettings;
|
this.templateSettings = newTemplateSettings;
|
||||||
},
|
},
|
||||||
|
@ -1780,6 +1945,42 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
enableDNS: {
|
||||||
|
get: function () {
|
||||||
|
return this.templateSettings ? this.templateSettings.dns != null : false;
|
||||||
|
},
|
||||||
|
set: function (newValue) {
|
||||||
|
newTemplateSettings = this.templateSettings;
|
||||||
|
newTemplateSettings.dns = newValue ? { servers: [], queryStrategy: "UseIP" } : null;
|
||||||
|
this.templateSettings = newTemplateSettings;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dnsStrategy: {
|
||||||
|
get: function () {
|
||||||
|
return this.enableDNS ? this.templateSettings.dns.queryStrategy : null;
|
||||||
|
},
|
||||||
|
set: function (newValue) {
|
||||||
|
newTemplateSettings = this.templateSettings;
|
||||||
|
newTemplateSettings.dns.queryStrategy = newValue;
|
||||||
|
this.templateSettings = newTemplateSettings;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dnsServers: {
|
||||||
|
get: function () { return this.enableDNS ? this.templateSettings.dns.servers : []; },
|
||||||
|
set: function (newValue) {
|
||||||
|
newTemplateSettings = this.templateSettings;
|
||||||
|
newTemplateSettings.dns.servers = newValue;
|
||||||
|
this.templateSettings = newTemplateSettings;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fakeDns: {
|
||||||
|
get: function () { return this.templateSettings && this.templateSettings.fakedns ? this.templateSettings.fakedns : []; },
|
||||||
|
set: function (newValue) {
|
||||||
|
newTemplateSettings = this.templateSettings;
|
||||||
|
newTemplateSettings.fakedns = newValue.length >0 ? newValue : null;
|
||||||
|
this.templateSettings = newTemplateSettings;
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -80,7 +80,7 @@
|
||||||
balancerModal.visible = false;
|
balancerModal.visible = false;
|
||||||
balancerModal.loading(false);
|
balancerModal.loading(false);
|
||||||
},
|
},
|
||||||
loading(loading) {
|
loading(loading=true) {
|
||||||
balancerModal.confirmLoading = loading;
|
balancerModal.confirmLoading = loading;
|
||||||
},
|
},
|
||||||
check() {
|
check() {
|
||||||
|
|
|
@ -42,7 +42,7 @@
|
||||||
outModal.visible = false;
|
outModal.visible = false;
|
||||||
outModal.loading(false);
|
outModal.loading(false);
|
||||||
},
|
},
|
||||||
loading(loading) {
|
loading(loading=true) {
|
||||||
outModal.confirmLoading = loading;
|
outModal.confirmLoading = loading;
|
||||||
},
|
},
|
||||||
check(){
|
check(){
|
||||||
|
|
|
@ -120,7 +120,7 @@
|
||||||
reverseModal.visible = false;
|
reverseModal.visible = false;
|
||||||
reverseModal.loading(false);
|
reverseModal.loading(false);
|
||||||
},
|
},
|
||||||
loading(loading) {
|
loading(loading=true) {
|
||||||
reverseModal.confirmLoading = loading;
|
reverseModal.confirmLoading = loading;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -132,8 +132,6 @@
|
||||||
reverseModal: reverseModal,
|
reverseModal: reverseModal,
|
||||||
reverseTypes: { bridge: '{{ i18n "pages.xray.outbound.bridge" }}', portal:'{{ i18n "pages.xray.outbound.portal" }}'},
|
reverseTypes: { bridge: '{{ i18n "pages.xray.outbound.bridge" }}', portal:'{{ i18n "pages.xray.outbound.portal" }}'},
|
||||||
},
|
},
|
||||||
methods: {
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -211,7 +211,7 @@
|
||||||
ruleModal.visible = false;
|
ruleModal.visible = false;
|
||||||
ruleModal.loading(false);
|
ruleModal.loading(false);
|
||||||
},
|
},
|
||||||
loading(loading) {
|
loading(loading=true) {
|
||||||
ruleModal.confirmLoading = loading;
|
ruleModal.confirmLoading = loading;
|
||||||
},
|
},
|
||||||
getResult() {
|
getResult() {
|
||||||
|
|
|
@ -14,6 +14,7 @@ import (
|
||||||
|
|
||||||
"x-ui/database"
|
"x-ui/database"
|
||||||
"x-ui/database/model"
|
"x-ui/database/model"
|
||||||
|
"x-ui/config"
|
||||||
"x-ui/logger"
|
"x-ui/logger"
|
||||||
"x-ui/xray"
|
"x-ui/xray"
|
||||||
)
|
)
|
||||||
|
@ -38,8 +39,10 @@ func NewCheckClientIpJob() *CheckClientIpJob {
|
||||||
|
|
||||||
func (j *CheckClientIpJob) Run() {
|
func (j *CheckClientIpJob) Run() {
|
||||||
|
|
||||||
// create files required for iplimit if not exists
|
// create files and dirs required for iplimit if not exists
|
||||||
for i := 0; i < len(ipFiles); i++ {
|
for i := 0; i < len(ipFiles); i++ {
|
||||||
|
err := os.MkdirAll(config.GetLogFolder(), 0770)
|
||||||
|
j.checkError(err)
|
||||||
file, err := os.OpenFile(ipFiles[i], os.O_CREATE|os.O_APPEND|os.O_RDWR, 0644)
|
file, err := os.OpenFile(ipFiles[i], os.O_CREATE|os.O_APPEND|os.O_RDWR, 0644)
|
||||||
j.checkError(err)
|
j.checkError(err)
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
|
|
|
@ -57,6 +57,9 @@ var defaultValueMap = map[string]string{
|
||||||
"subEncrypt": "true",
|
"subEncrypt": "true",
|
||||||
"subShowInfo": "true",
|
"subShowInfo": "true",
|
||||||
"subURI": "",
|
"subURI": "",
|
||||||
|
"subJsonPath": "/json/",
|
||||||
|
"subJsonURI": "",
|
||||||
|
"subJsonFragment": "",
|
||||||
"datepicker": "gregorian",
|
"datepicker": "gregorian",
|
||||||
"warp": "",
|
"warp": "",
|
||||||
}
|
}
|
||||||
|
@ -387,17 +390,11 @@ func (s *SettingService) GetSubPort() (int, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SettingService) GetSubPath() (string, error) {
|
func (s *SettingService) GetSubPath() (string, error) {
|
||||||
subPath, err := s.getString("subPath")
|
return s.getString("subPath")
|
||||||
if err != nil {
|
}
|
||||||
return "", err
|
|
||||||
}
|
func (s *SettingService) GetSubJsonPath() (string, error) {
|
||||||
if !strings.HasPrefix(subPath, "/") {
|
return s.getString("subJsonPath")
|
||||||
subPath = "/" + subPath
|
|
||||||
}
|
|
||||||
if !strings.HasSuffix(subPath, "/") {
|
|
||||||
subPath += "/"
|
|
||||||
}
|
|
||||||
return subPath, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SettingService) GetSubDomain() (string, error) {
|
func (s *SettingService) GetSubDomain() (string, error) {
|
||||||
|
@ -412,8 +409,8 @@ func (s *SettingService) GetSubKeyFile() (string, error) {
|
||||||
return s.getString("subKeyFile")
|
return s.getString("subKeyFile")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SettingService) GetSubUpdates() (int, error) {
|
func (s *SettingService) GetSubUpdates() (string, error) {
|
||||||
return s.getInt("subUpdates")
|
return s.getString("subUpdates")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SettingService) GetSubEncrypt() (bool, error) {
|
func (s *SettingService) GetSubEncrypt() (bool, error) {
|
||||||
|
@ -432,6 +429,14 @@ func (s *SettingService) GetSubURI() (string, error) {
|
||||||
return s.getString("subURI")
|
return s.getString("subURI")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) GetSubJsonURI() (string, error) {
|
||||||
|
return s.getString("subJsonURI")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) GetSubJsonFragment() (string, error) {
|
||||||
|
return s.getString("subJsonFragment")
|
||||||
|
}
|
||||||
|
|
||||||
func (s *SettingService) GetDatepicker() (string, error) {
|
func (s *SettingService) GetDatepicker() (string, error) {
|
||||||
return s.getString("datepicker")
|
return s.getString("datepicker")
|
||||||
}
|
}
|
||||||
|
@ -484,6 +489,7 @@ func (s *SettingService) GetDefaultSettings(host string) (interface{}, error) {
|
||||||
"tgBotEnable": func() (interface{}, error) { return s.GetTgbotenabled() },
|
"tgBotEnable": func() (interface{}, error) { return s.GetTgbotenabled() },
|
||||||
"subEnable": func() (interface{}, error) { return s.GetSubEnable() },
|
"subEnable": func() (interface{}, error) { return s.GetSubEnable() },
|
||||||
"subURI": func() (interface{}, error) { return s.GetSubURI() },
|
"subURI": func() (interface{}, error) { return s.GetSubURI() },
|
||||||
|
"subJsonURI": func() (interface{}, error) { return s.GetSubJsonURI() },
|
||||||
"remarkModel": func() (interface{}, error) { return s.GetRemarkModel() },
|
"remarkModel": func() (interface{}, error) { return s.GetRemarkModel() },
|
||||||
"datepicker": func() (interface{}, error) { return s.GetDatepicker() },
|
"datepicker": func() (interface{}, error) { return s.GetDatepicker() },
|
||||||
}
|
}
|
||||||
|
@ -498,10 +504,11 @@ func (s *SettingService) GetDefaultSettings(host string) (interface{}, error) {
|
||||||
result[key] = value
|
result[key] = value
|
||||||
}
|
}
|
||||||
|
|
||||||
if result["subEnable"].(bool) && result["subURI"].(string) == "" {
|
if result["subEnable"].(bool) && (result["subURI"].(string) == "" || result["subJsonURI"].(string) == "") {
|
||||||
subURI := ""
|
subURI := ""
|
||||||
subPort, _ := s.GetSubPort()
|
subPort, _ := s.GetSubPort()
|
||||||
subPath, _ := s.GetSubPath()
|
subPath, _ := s.GetSubPath()
|
||||||
|
subJsonPath, _ := s.GetSubJsonPath()
|
||||||
subDomain, _ := s.GetSubDomain()
|
subDomain, _ := s.GetSubDomain()
|
||||||
subKeyFile, _ := s.GetSubKeyFile()
|
subKeyFile, _ := s.GetSubKeyFile()
|
||||||
subCertFile, _ := s.GetSubCertFile()
|
subCertFile, _ := s.GetSubCertFile()
|
||||||
|
@ -522,12 +529,12 @@ func (s *SettingService) GetDefaultSettings(host string) (interface{}, error) {
|
||||||
} else {
|
} else {
|
||||||
subURI += fmt.Sprintf("%s:%d", subDomain, subPort)
|
subURI += fmt.Sprintf("%s:%d", subDomain, subPort)
|
||||||
}
|
}
|
||||||
if subPath[0] == byte('/') {
|
if result["subURI"].(string) == "" {
|
||||||
subURI += subPath
|
result["subURI"] = subURI + subPath
|
||||||
} else {
|
}
|
||||||
subURI += "/" + subPath
|
if result["subJsonURI"].(string) == "" {
|
||||||
|
result["subJsonURI"] = subURI + subJsonPath
|
||||||
}
|
}
|
||||||
result["subURI"] = subURI
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result, nil
|
return result, nil
|
||||||
|
|
|
@ -52,6 +52,9 @@
|
||||||
"secretToken" = "Secret Token"
|
"secretToken" = "Secret Token"
|
||||||
"remained" = "Remained"
|
"remained" = "Remained"
|
||||||
"security" = "Security"
|
"security" = "Security"
|
||||||
|
"secAlertTitle" = "Security Alert"
|
||||||
|
"secAlertSsl" = "This connection is not secure. Please avoid entering sensitive information until TLS is activated for data protection."
|
||||||
|
"secAlertConf" = "Certain configurations have been identified as susceptible to attacks, prompting immediate action to reinforce security protocols and safeguard against potential security breaches."
|
||||||
|
|
||||||
[menu]
|
[menu]
|
||||||
"dashboard" = "Overview"
|
"dashboard" = "Overview"
|
||||||
|
@ -302,6 +305,8 @@
|
||||||
"subShowInfoDesc" = "The remaining traffic and date will be displayed in the client apps."
|
"subShowInfoDesc" = "The remaining traffic and date will be displayed in the client apps."
|
||||||
"subURI" = "Reverse Proxy URI"
|
"subURI" = "Reverse Proxy URI"
|
||||||
"subURIDesc" = "The URI path of the subscription URL for use behind proxies."
|
"subURIDesc" = "The URI path of the subscription URL for use behind proxies."
|
||||||
|
"fragment" = "Fragmentation"
|
||||||
|
"fragmentDesc" = "Enable fragmentation for TLS hello packet"
|
||||||
|
|
||||||
[pages.xray]
|
[pages.xray]
|
||||||
"title" = "Xray Configs"
|
"title" = "Xray Configs"
|
||||||
|
@ -449,6 +454,21 @@
|
||||||
"psk" = "PreShared Key"
|
"psk" = "PreShared Key"
|
||||||
"domainStrategy" = "Domain Strategy"
|
"domainStrategy" = "Domain Strategy"
|
||||||
|
|
||||||
|
[pages.xray.dns]
|
||||||
|
"enable" = "Enable DNS"
|
||||||
|
"enableDesc" = "Enable built-in DNS server"
|
||||||
|
"strategy" = "Query Strategy"
|
||||||
|
"strategyDesc" = "Overall strategy to resolve domain names"
|
||||||
|
"add" = "Add Server"
|
||||||
|
"edit" = "Edit Server"
|
||||||
|
"domains" = "Domains"
|
||||||
|
|
||||||
|
[pages.xray.fakedns]
|
||||||
|
"add" = "Add Fake DNS"
|
||||||
|
"edit" = "Edit Fake DNS"
|
||||||
|
"ipPool" = "IP Pool Subnet"
|
||||||
|
"poolSize" = "Pool Size"
|
||||||
|
|
||||||
[pages.settings.security]
|
[pages.settings.security]
|
||||||
"admin" = "Admin"
|
"admin" = "Admin"
|
||||||
"secret" = "Secret Token"
|
"secret" = "Secret Token"
|
||||||
|
|
|
@ -52,6 +52,9 @@
|
||||||
"secretToken" = "Token Secreto"
|
"secretToken" = "Token Secreto"
|
||||||
"remained" = "Restante"
|
"remained" = "Restante"
|
||||||
"security" = "Seguridad"
|
"security" = "Seguridad"
|
||||||
|
"secAlertTitle" = "Alerta de seguridad"
|
||||||
|
"secAlertSsl" = "Esta conexión no es segura. Evite ingresar información confidencial hasta que TLS esté activado para la protección de datos."
|
||||||
|
"secAlertConf" = "Se han identificado ciertas configuraciones como susceptibles a ataques, lo que genera acciones inmediatas para reforzar los protocolos de seguridad y proteger contra posibles violaciones de seguridad."
|
||||||
|
|
||||||
[menu]
|
[menu]
|
||||||
"dashboard" = "Estado del Sistema"
|
"dashboard" = "Estado del Sistema"
|
||||||
|
@ -302,6 +305,8 @@
|
||||||
"subShowInfoDesc" = "Mostrar tráfico restante y fecha después del nombre de configuración."
|
"subShowInfoDesc" = "Mostrar tráfico restante y fecha después del nombre de configuración."
|
||||||
"subURI" = "URI de proxy inverso"
|
"subURI" = "URI de proxy inverso"
|
||||||
"subURIDesc" = "Cambiar el URI base de la URL de suscripción para usar detrás de los servidores proxy"
|
"subURIDesc" = "Cambiar el URI base de la URL de suscripción para usar detrás de los servidores proxy"
|
||||||
|
"fragment" = "Fragmentación"
|
||||||
|
"fragmentDesc" = "Habilitar la fragmentación para el paquete de saludo TLS"
|
||||||
|
|
||||||
[pages.xray]
|
[pages.xray]
|
||||||
"title" = "Xray Configuración"
|
"title" = "Xray Configuración"
|
||||||
|
@ -449,6 +454,21 @@
|
||||||
"psk" = "Clave precompartida"
|
"psk" = "Clave precompartida"
|
||||||
"domainStrategy" = "Estrategia de dominio"
|
"domainStrategy" = "Estrategia de dominio"
|
||||||
|
|
||||||
|
[pages.xray.dns]
|
||||||
|
"enable" = "Habilitar DNS"
|
||||||
|
"enableDesc" = "Habilitar servidor DNS integrado"
|
||||||
|
"strategy" = "Estrategia de consulta"
|
||||||
|
"strategyDesc" = "Estrategia general para resolver nombres de dominio"
|
||||||
|
"add" = "Agregar servidor"
|
||||||
|
"edit" = "Editar servidor"
|
||||||
|
"domains" = "Dominios"
|
||||||
|
|
||||||
|
[pages.xray.fakedns]
|
||||||
|
"add" = "Agregar DNS falso"
|
||||||
|
"edit" = "Editar DNS falso"
|
||||||
|
"ipPool" = "Subred del grupo de IP"
|
||||||
|
"poolSize" = "Tamaño del grupo"
|
||||||
|
|
||||||
[pages.settings.security]
|
[pages.settings.security]
|
||||||
"admin" = "Administrador"
|
"admin" = "Administrador"
|
||||||
"secret" = "Token Secreto"
|
"secret" = "Token Secreto"
|
||||||
|
|
|
@ -52,6 +52,9 @@
|
||||||
"secretToken" = "توکن امنیتی"
|
"secretToken" = "توکن امنیتی"
|
||||||
"remained" = "باقیمانده"
|
"remained" = "باقیمانده"
|
||||||
"security" = "امنیت"
|
"security" = "امنیت"
|
||||||
|
"secAlertTitle" = "هشدارامنیتی"
|
||||||
|
"secAlertSsl" = "ایناتصالامن نیست. لطفا تازمانیکه تیالاس برای محافظت از دادهها فعال نشدهاست، از وارد کردن اطلاعات حساس خودداری کنید"
|
||||||
|
"secAlertConf" = "پیکربندیهای خاصی مستعد حملات سایبری شناسایی شدهاند، اقدام فوری برای تقویت پروتکلهای امنیتی و محافظت در برابر نقضهای امنیتی لازم است"
|
||||||
|
|
||||||
[menu]
|
[menu]
|
||||||
"dashboard" = "نمای کلی"
|
"dashboard" = "نمای کلی"
|
||||||
|
@ -302,6 +305,8 @@
|
||||||
"subShowInfoDesc" = "ترافیک و زمان باقیمانده را در برنامههای کاربری نمایش میدهد"
|
"subShowInfoDesc" = "ترافیک و زمان باقیمانده را در برنامههای کاربری نمایش میدهد"
|
||||||
"subURI" = "پروکسی معکوس URI مسیر"
|
"subURI" = "پروکسی معکوس URI مسیر"
|
||||||
"subURIDesc" = "سابسکریپشن را برای استفاده در پشت پراکسیها تغییر میدهد URI مسیر"
|
"subURIDesc" = "سابسکریپشن را برای استفاده در پشت پراکسیها تغییر میدهد URI مسیر"
|
||||||
|
"fragment" = "تکهتکه شدن"
|
||||||
|
"fragmentDesc" = "فعال کردن تکه تکه شدن برای بسته نخست تیالاس"
|
||||||
|
|
||||||
[pages.xray]
|
[pages.xray]
|
||||||
"title" = "پیکربندی ایکسری"
|
"title" = "پیکربندی ایکسری"
|
||||||
|
@ -449,6 +454,21 @@
|
||||||
"psk" = "کلید مشترک"
|
"psk" = "کلید مشترک"
|
||||||
"domainStrategy" = "استراتژی حل دامنه"
|
"domainStrategy" = "استراتژی حل دامنه"
|
||||||
|
|
||||||
|
[pages.xray.dns]
|
||||||
|
"enable" = "فعال کردن حل دامنه"
|
||||||
|
"enableDesc" = "سرور حل دامنه داخلی را فعال کنید"
|
||||||
|
"strategy" = "استراتژی پرسوجو"
|
||||||
|
"strategyDesc" = "استراتژی کلی برای حل نام دامنه"
|
||||||
|
"add" = "افزودن سرور"
|
||||||
|
"edit" = "ویرایش سرور"
|
||||||
|
"domains" = "دامنهها"
|
||||||
|
|
||||||
|
[pages.xray.fakedns]
|
||||||
|
"add" = "افزودن دیاناس جعلی"
|
||||||
|
"edit" = "ویرایش دیاناس جعلی"
|
||||||
|
"ipPool" = "زیرشبکه استخر آیپی"
|
||||||
|
"poolSize" = "اندازه استخر"
|
||||||
|
|
||||||
[pages.settings.security]
|
[pages.settings.security]
|
||||||
"admin" = "مدیر"
|
"admin" = "مدیر"
|
||||||
"secret" = "توکن مخفی"
|
"secret" = "توکن مخفی"
|
||||||
|
|
|
@ -52,6 +52,9 @@
|
||||||
"secretToken" = "Token Rahasia"
|
"secretToken" = "Token Rahasia"
|
||||||
"remained" = "Tersisa"
|
"remained" = "Tersisa"
|
||||||
"security" = "Keamanan"
|
"security" = "Keamanan"
|
||||||
|
"secAlertTitle" = "Peringatan keamanan"
|
||||||
|
"secAlertSsl" = "Koneksi ini tidak aman. Harap hindari memasukkan informasi sensitif sampai TLS diaktifkan untuk perlindungan data."
|
||||||
|
"secAlertConf" = "Konfigurasi tertentu telah diidentifikasi rentan terhadap serangan, sehingga mendorong tindakan segera untuk memperkuat protokol keamanan dan melindungi dari potensi pelanggaran keamanan."
|
||||||
|
|
||||||
[menu]
|
[menu]
|
||||||
"dashboard" = "Ikhtisar"
|
"dashboard" = "Ikhtisar"
|
||||||
|
@ -302,6 +305,8 @@
|
||||||
"subShowInfoDesc" = "Sisa traffic dan tanggal akan ditampilkan di aplikasi klien."
|
"subShowInfoDesc" = "Sisa traffic dan tanggal akan ditampilkan di aplikasi klien."
|
||||||
"subURI" = "URI Proxy Terbalik"
|
"subURI" = "URI Proxy Terbalik"
|
||||||
"subURIDesc" = "URI path URL langganan untuk penggunaan di belakang proxy."
|
"subURIDesc" = "URI path URL langganan untuk penggunaan di belakang proxy."
|
||||||
|
"fragment" = "Fragmentasi"
|
||||||
|
"fragmentDesc" = "Aktifkan fragmentasi untuk paket hello TLS"
|
||||||
|
|
||||||
[pages.xray]
|
[pages.xray]
|
||||||
"title" = "Konfigurasi Xray"
|
"title" = "Konfigurasi Xray"
|
||||||
|
@ -449,6 +454,21 @@
|
||||||
"psk" = "Kunci Pra-Bagi"
|
"psk" = "Kunci Pra-Bagi"
|
||||||
"domainStrategy" = "Strategi Domain"
|
"domainStrategy" = "Strategi Domain"
|
||||||
|
|
||||||
|
[pages.xray.dns]
|
||||||
|
"enable" = "Aktifkan DNS"
|
||||||
|
"enableDesc" = "Aktifkan server DNS bawaan"
|
||||||
|
"strategy" = "Strategi Kueri"
|
||||||
|
"strategyDesc" = "Strategi keseluruhan untuk menyelesaikan nama domain"
|
||||||
|
"add" = "Tambahkan Server"
|
||||||
|
"edit" = "Sunting Server"
|
||||||
|
"domains" = "Domains"
|
||||||
|
|
||||||
|
[pages.xray.fakedns]
|
||||||
|
"add" = "Tambahkan DNS Palsu"
|
||||||
|
"edit" = "Edit DNS Palsu"
|
||||||
|
"ipPool" = "Subnet Kumpulan IP"
|
||||||
|
"poolSize" = "Ukuran Kolam"
|
||||||
|
|
||||||
[pages.settings.security]
|
[pages.settings.security]
|
||||||
"admin" = "Admin"
|
"admin" = "Admin"
|
||||||
"secret" = "Token Rahasia"
|
"secret" = "Token Rahasia"
|
||||||
|
|
|
@ -52,6 +52,9 @@
|
||||||
"secretToken" = "Секретный токен"
|
"secretToken" = "Секретный токен"
|
||||||
"remained" = "остались"
|
"remained" = "остались"
|
||||||
"security" = "Безопасность"
|
"security" = "Безопасность"
|
||||||
|
"secAlertTitle" = "Предупреждение системы безопасности"
|
||||||
|
"secAlertSsl" = "Это соединение не защищено. Пожалуйста, воздержитесь от ввода конфиденциальной информации до тех пор, пока не будет активирован TLS для защиты данных"
|
||||||
|
"secAlertConf" = "Некоторые конфигурации были определены как уязвимые для атак, что требует немедленных действий по усилению протоколов безопасности и защите от потенциальных нарушений безопасности."
|
||||||
|
|
||||||
[menu]
|
[menu]
|
||||||
"dashboard" = "Статус системы"
|
"dashboard" = "Статус системы"
|
||||||
|
@ -302,6 +305,8 @@
|
||||||
"subShowInfoDesc" = "Показывать восстановленный трафик и дату после имени конфигурации"
|
"subShowInfoDesc" = "Показывать восстановленный трафик и дату после имени конфигурации"
|
||||||
"subURI" = "URI обратного прокси"
|
"subURI" = "URI обратного прокси"
|
||||||
"subURIDesc" = "Изменить базовый URI URL-адреса подписки для использования за прокси-серверами"
|
"subURIDesc" = "Изменить базовый URI URL-адреса подписки для использования за прокси-серверами"
|
||||||
|
"fragment" = "Фрагментация"
|
||||||
|
"fragmentDesc" = "Включить фрагментацию для пакета приветствия TLS"
|
||||||
|
|
||||||
[pages.xray]
|
[pages.xray]
|
||||||
"title" = "Настройки Xray"
|
"title" = "Настройки Xray"
|
||||||
|
@ -449,6 +454,21 @@
|
||||||
"psk" = "Общий ключ"
|
"psk" = "Общий ключ"
|
||||||
"domainStrategy" = "Стратегия домена"
|
"domainStrategy" = "Стратегия домена"
|
||||||
|
|
||||||
|
[pages.xray.dns]
|
||||||
|
"enable" = "Включить DNS"
|
||||||
|
"enableDesc" = "Включить встроенный DNS-сервер"
|
||||||
|
"strategy" = "Стратегия запроса"
|
||||||
|
"strategyDesc" = "Общая стратегия разрешения доменных имен"
|
||||||
|
"add" = "Добавить сервер"
|
||||||
|
"edit" = "Редактировать сервер"
|
||||||
|
"domains" = "Домены"
|
||||||
|
|
||||||
|
[pages.xray.fakedns]
|
||||||
|
"add" = "Добавить поддельный DNS"
|
||||||
|
"edit" = "Редактировать поддельный DNS"
|
||||||
|
"ipPool" = "Подсеть пула IP"
|
||||||
|
"poolSize" = "Размер пула"
|
||||||
|
|
||||||
[pages.settings.security]
|
[pages.settings.security]
|
||||||
"admin" = "Админ"
|
"admin" = "Админ"
|
||||||
"secret" = "Секретный токен"
|
"secret" = "Секретный токен"
|
||||||
|
|
|
@ -52,6 +52,9 @@
|
||||||
"secretToken" = "Mã bí mật"
|
"secretToken" = "Mã bí mật"
|
||||||
"remained" = "Còn lại"
|
"remained" = "Còn lại"
|
||||||
"security" = "Bảo vệ"
|
"security" = "Bảo vệ"
|
||||||
|
"secAlertTitle" = "Cảnh báo an ninh-Tiếng Việt by Ohoang7"
|
||||||
|
"secAlertSsl" = "Kết nối này không an toàn; Vui lòng không nhập thông tin nhạy cảm cho đến khi TLS được kích hoạt để bảo vệ dữ liệu của Bạn"
|
||||||
|
"secAlertConf" = "Một số cấu hình nhất định đã được xác định là dễ bị tấn công, thúc đẩy hành động ngay lập tức để củng cố các giao thức bảo mật và bảo vệ chống lại các vi phạm bảo mật tiềm ẩn."
|
||||||
|
|
||||||
[menu]
|
[menu]
|
||||||
"dashboard" = "Trạng thái hệ thống"
|
"dashboard" = "Trạng thái hệ thống"
|
||||||
|
@ -302,6 +305,8 @@
|
||||||
"subShowInfoDesc" = "Hiển thị lưu lượng truy cập còn lại và ngày sau tên cấu hình"
|
"subShowInfoDesc" = "Hiển thị lưu lượng truy cập còn lại và ngày sau tên cấu hình"
|
||||||
"subURI" = "URI proxy trung gian"
|
"subURI" = "URI proxy trung gian"
|
||||||
"subURIDesc" = "Thay đổi URI cơ sở của URL gói đăng ký để sử dụng cho proxy trung gian"
|
"subURIDesc" = "Thay đổi URI cơ sở của URL gói đăng ký để sử dụng cho proxy trung gian"
|
||||||
|
"fragment" = "Sự phân mảnh"
|
||||||
|
"fragmentDesc" = "Kích hoạt phân mảnh cho gói TLS hello"
|
||||||
|
|
||||||
[pages.xray]
|
[pages.xray]
|
||||||
"title" = "Cài đặt Xray"
|
"title" = "Cài đặt Xray"
|
||||||
|
@ -449,6 +454,21 @@
|
||||||
"psk" = "Khóa chia sẻ"
|
"psk" = "Khóa chia sẻ"
|
||||||
"domainStrategy" = "Chiến lược tên miền"
|
"domainStrategy" = "Chiến lược tên miền"
|
||||||
|
|
||||||
|
[pages.xray.dns]
|
||||||
|
"enable" = "Kích hoạt DNS"
|
||||||
|
"enableDesc" = "Kích hoạt máy chủ DNS tích hợp"
|
||||||
|
"strategy" = "Chiến lược truy vấn"
|
||||||
|
"strategyDesc" = "Chiến lược tổng thể để phân giải tên miền"
|
||||||
|
"add" = "Thêm máy chủ"
|
||||||
|
"edit" = "Chỉnh sửa máy chủ"
|
||||||
|
"domains" = "Tên miền"
|
||||||
|
|
||||||
|
[pages.xray.fakedns]
|
||||||
|
"add" = "Thêm DNS giả"
|
||||||
|
"edit" = "Chỉnh sửa DNS giả"
|
||||||
|
"ipPool" = "Mạng con nhóm IP"
|
||||||
|
"poolSize" = "Kích thước bể bơi"
|
||||||
|
|
||||||
[pages.settings.security]
|
[pages.settings.security]
|
||||||
"admin" = "Quản trị viên"
|
"admin" = "Quản trị viên"
|
||||||
"secret" = "Mã thông báo bí mật"
|
"secret" = "Mã thông báo bí mật"
|
||||||
|
|
|
@ -52,6 +52,9 @@
|
||||||
"secretToken" = "安全密钥"
|
"secretToken" = "安全密钥"
|
||||||
"remained" = "剩余"
|
"remained" = "剩余"
|
||||||
"security" = "安全"
|
"security" = "安全"
|
||||||
|
"secAlertTitle" = "安全警报"
|
||||||
|
"secAlertSsl" = "此连接不安全;在激活 TLS 进行数据保护之前,请勿输入敏感信息"
|
||||||
|
"secAlertConf" = "某些配置已被确定为容易受到攻击,促使立即采取行动以加强安全协议并防范潜在的安全漏洞。"
|
||||||
|
|
||||||
[menu]
|
[menu]
|
||||||
"dashboard" = "系统状态"
|
"dashboard" = "系统状态"
|
||||||
|
@ -302,6 +305,8 @@
|
||||||
"subShowInfoDesc" = "在配置名称后显示剩余流量和日期"
|
"subShowInfoDesc" = "在配置名称后显示剩余流量和日期"
|
||||||
"subURI" = "反向代理 URI"
|
"subURI" = "反向代理 URI"
|
||||||
"subURIDesc" = "更改订阅 URL 的基本 URI 以在代理后面使用"
|
"subURIDesc" = "更改订阅 URL 的基本 URI 以在代理后面使用"
|
||||||
|
"fragment" = "碎片"
|
||||||
|
"fragmentDesc" = "启用 TLS hello 数据包分段"
|
||||||
|
|
||||||
[pages.xray]
|
[pages.xray]
|
||||||
"title" = "Xray 设置"
|
"title" = "Xray 设置"
|
||||||
|
@ -449,6 +454,21 @@
|
||||||
"psk" = "共享密钥"
|
"psk" = "共享密钥"
|
||||||
"domainStrategy" = "域策略"
|
"domainStrategy" = "域策略"
|
||||||
|
|
||||||
|
[pages.xray.dns]
|
||||||
|
"enable" = "启用 DNS"
|
||||||
|
"enableDesc" = "启用内置 DNS 服务器"
|
||||||
|
"strategy" = "查询策略"
|
||||||
|
"strategyDesc" = "解析域名的总体策略"
|
||||||
|
"add" = "添加服务器"
|
||||||
|
"edit" = "编辑服务器"
|
||||||
|
"domains" = "域"
|
||||||
|
|
||||||
|
[pages.xray.fakedns]
|
||||||
|
"add" = "添加假 DNS"
|
||||||
|
"edit" = "编辑假 DNS"
|
||||||
|
"ipPool" = "IP 池子网"
|
||||||
|
"poolSize" = "池大小"
|
||||||
|
|
||||||
[pages.settings.security]
|
[pages.settings.security]
|
||||||
"admin" = "管理员"
|
"admin" = "管理员"
|
||||||
"secret" = "密钥"
|
"secret" = "密钥"
|
||||||
|
|
43
x-ui.sh
43
x-ui.sh
|
@ -345,6 +345,47 @@ show_banlog() {
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bbr_menu() {
|
||||||
|
echo -e "${green}\t1.${plain} Enable BBR"
|
||||||
|
echo -e "${green}\t2.${plain} Disable BBR"
|
||||||
|
echo -e "${green}\t0.${plain} Back to Main Menu"
|
||||||
|
read -p "Choose an option: " choice
|
||||||
|
case "$choice" in
|
||||||
|
0)
|
||||||
|
show_menu
|
||||||
|
;;
|
||||||
|
1)
|
||||||
|
enable_bbr
|
||||||
|
;;
|
||||||
|
2)
|
||||||
|
disable_bbr
|
||||||
|
;;
|
||||||
|
*) echo "Invalid choice" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
disable_bbr() {
|
||||||
|
|
||||||
|
if ! grep -q "net.core.default_qdisc=fq" /etc/sysctl.conf || ! grep -q "net.ipv4.tcp_congestion_control=bbr" /etc/sysctl.conf; then
|
||||||
|
echo -e "${yellow}BBR is not currently enabled.${plain}"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Replace BBR with CUBIC configurations
|
||||||
|
sed -i 's/net.core.default_qdisc=fq/net.core.default_qdisc=pfifo_fast/' /etc/sysctl.conf
|
||||||
|
sed -i 's/net.ipv4.tcp_congestion_control=bbr/net.ipv4.tcp_congestion_control=cubic/' /etc/sysctl.conf
|
||||||
|
|
||||||
|
# Apply changes
|
||||||
|
sysctl -p
|
||||||
|
|
||||||
|
# Verify that BBR is replaced with CUBIC
|
||||||
|
if [[ $(sysctl net.ipv4.tcp_congestion_control | awk '{print $3}') == "cubic" ]]; then
|
||||||
|
echo -e "${green}BBR has been replaced with CUBIC successfully.${plain}"
|
||||||
|
else
|
||||||
|
echo -e "${red}Failed to replace BBR with CUBIC. Please check your system configuration.${plain}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
enable_bbr() {
|
enable_bbr() {
|
||||||
if grep -q "net.core.default_qdisc=fq" /etc/sysctl.conf && grep -q "net.ipv4.tcp_congestion_control=bbr" /etc/sysctl.conf; then
|
if grep -q "net.core.default_qdisc=fq" /etc/sysctl.conf && grep -q "net.ipv4.tcp_congestion_control=bbr" /etc/sysctl.conf; then
|
||||||
echo -e "${green}BBR is already enabled!${plain}"
|
echo -e "${green}BBR is already enabled!${plain}"
|
||||||
|
@ -1263,7 +1304,7 @@ show_menu() {
|
||||||
firewall_menu
|
firewall_menu
|
||||||
;;
|
;;
|
||||||
21)
|
21)
|
||||||
enable_bbr
|
bbr_menu
|
||||||
;;
|
;;
|
||||||
22)
|
22)
|
||||||
update_geo
|
update_geo
|
||||||
|
|
|
@ -16,7 +16,8 @@ type Config struct {
|
||||||
API json_util.RawMessage `json:"api"`
|
API json_util.RawMessage `json:"api"`
|
||||||
Stats json_util.RawMessage `json:"stats"`
|
Stats json_util.RawMessage `json:"stats"`
|
||||||
Reverse json_util.RawMessage `json:"reverse"`
|
Reverse json_util.RawMessage `json:"reverse"`
|
||||||
FakeDNS json_util.RawMessage `json:"fakeDns"`
|
FakeDNS json_util.RawMessage `json:"fakedns"`
|
||||||
|
Observatory json_util.RawMessage `json:"observatory"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) Equals(other *Config) bool {
|
func (c *Config) Equals(other *Config) bool {
|
||||||
|
|
|
@ -31,13 +31,13 @@ func (lw *LogWriter) Write(m []byte) (n int, err error) {
|
||||||
// Map the level to the appropriate logger function
|
// Map the level to the appropriate logger function
|
||||||
switch level {
|
switch level {
|
||||||
case "Debug":
|
case "Debug":
|
||||||
logger.Debug(msgBody)
|
logger.Debug("XRAY: " + msgBody)
|
||||||
case "Info":
|
case "Info":
|
||||||
logger.Info(msgBody)
|
logger.Info("XRAY: " + msgBody)
|
||||||
case "Warning":
|
case "Warning":
|
||||||
logger.Warning(msgBody)
|
logger.Warning("XRAY: " + msgBody)
|
||||||
case "Error":
|
case "Error":
|
||||||
logger.Error(msgBody)
|
logger.Error("XRAY: " + msgBody)
|
||||||
default:
|
default:
|
||||||
logger.Debug("XRAY: " + msg)
|
logger.Debug("XRAY: " + msg)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue