mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-06-06 21:24:10 +00:00
feat: support full mihomo template and multi-server for Clash Link
- Add splitTemplate() to split at proxies:/proxy-groups: markers (like mihomo-gen) - Store clash_template.yaml and servers.yaml as files alongside x-ui.json - Add Clash/Servers editors in Xray advanced config page - Support multi-server proxy generation (each server × each client) - Remove inline template editor from Clash settings panel - Bump version to v1.7.2.1
This commit is contained in:
parent
67c4f6a1ad
commit
25cf22d161
14 changed files with 338 additions and 76 deletions
|
|
@ -129,6 +129,85 @@ func GetTrafficPendingPath() string {
|
|||
return filepath.Join(GetDBFolderPath(), "traffic-pending.json")
|
||||
}
|
||||
|
||||
// GetClashTemplatePath returns the path to the clash_template.yaml file.
|
||||
func GetClashTemplatePath() string {
|
||||
return filepath.Join(GetDBFolderPath(), "clash_template.yaml")
|
||||
}
|
||||
|
||||
// GetServersPath returns the path to the servers.yaml file.
|
||||
func GetServersPath() string {
|
||||
return filepath.Join(GetDBFolderPath(), "servers.yaml")
|
||||
}
|
||||
|
||||
// ReadClashTemplate reads the clash template from disk.
|
||||
// Returns the default mihomo template if the file does not exist.
|
||||
func ReadClashTemplate() (string, error) {
|
||||
data, err := os.ReadFile(GetClashTemplatePath())
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return defaultClashTemplate, nil
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
return string(data), nil
|
||||
}
|
||||
|
||||
// SaveClashTemplate writes the clash template to disk.
|
||||
func SaveClashTemplate(content string) error {
|
||||
path := GetClashTemplatePath()
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
return os.WriteFile(path, []byte(content), 0644)
|
||||
}
|
||||
|
||||
// ReadServers reads the servers config from disk.
|
||||
// Returns a default empty servers config if the file does not exist.
|
||||
func ReadServers() (string, error) {
|
||||
data, err := os.ReadFile(GetServersPath())
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return defaultServers, nil
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
return string(data), nil
|
||||
}
|
||||
|
||||
// SaveServers writes the servers config to disk.
|
||||
func SaveServers(content string) error {
|
||||
path := GetServersPath()
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
return os.WriteFile(path, []byte(content), 0644)
|
||||
}
|
||||
|
||||
const defaultServers = `servers: []
|
||||
`
|
||||
|
||||
const defaultClashTemplate = `port: 7890
|
||||
socks-port: 7891
|
||||
allow-lan: false
|
||||
mode: rule
|
||||
log-level: info
|
||||
proxies:
|
||||
proxy-groups:
|
||||
- name: Proxy
|
||||
type: select
|
||||
proxies:
|
||||
- DIRECT
|
||||
dns:
|
||||
enable: true
|
||||
enhanced-mode: fake-ip
|
||||
nameserver:
|
||||
- 8.8.8.8
|
||||
- 1.1.1.1
|
||||
rules:
|
||||
- GEOIP,LAN,DIRECT
|
||||
- MATCH,Proxy
|
||||
`
|
||||
|
||||
// GetLogFolder returns the path to the log folder based on environment variables or platform defaults.
|
||||
func GetLogFolder() string {
|
||||
logFolderPath := os.Getenv("XUI_LOG_FOLDER")
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
v1.7.0.1
|
||||
v1.7.2.1
|
||||
|
|
|
|||
24
docs/Tasktracking/2026-04-25-clash-full-mihomo-template.md
Normal file
24
docs/Tasktracking/2026-04-25-clash-full-mihomo-template.md
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
# Clash Link: Full Mihomo Template + Multi-Server Support
|
||||
|
||||
## Date: 2026-04-25
|
||||
|
||||
## Changes
|
||||
|
||||
### Backend
|
||||
- `config/config.go` — Added `GetClashTemplatePath()`, `GetServersPath()`, `ReadClashTemplate()`, `SaveClashTemplate()`, `ReadServers()`, `SaveServers()`. Files stored at `/etc/x-ui/clash_template.yaml` and `/etc/x-ui/servers.yaml`
|
||||
- `sub/subClashService.go` — Added `splitTemplate()` (from mihomo-gen), modified `GetClash()` to split at `proxies:`/`proxy-groups:` markers instead of `proxies: []` replacement. Added multi-server support: each `ClashServer` × each client generates a proxy entry. Falls back to old approach if split fails.
|
||||
- `sub/sub.go` — Reads template and servers from files via `config.ReadClashTemplate()`/`config.ReadServers()`. Added `ClashServer` struct and `parseServers()`.
|
||||
- `sub/subController.go` — Updated `NewSUBController` to accept `clashServers []ClashServer`
|
||||
- `web/controller/xray_setting.go` — Added 4 API endpoints: `GET/POST /xray/clashTemplate`, `GET/POST /xray/servers`
|
||||
- `web/service/setting.go` — Cleared `subClashTemplate` default (template now from file)
|
||||
|
||||
### Frontend
|
||||
- `web/html/settings/xray/advanced.html` — Added "Clash" and "Servers" radio buttons in Xray advanced config
|
||||
- `web/html/xray.html` — Added `clashTemplate`/`servers` data with old-value tracking, load/save methods, YAML CodeMirror mode, smart save button dispatches to correct save handler
|
||||
- `web/html/settings/panel/subscription/clash.html` — Removed template editor (now in Xray advanced config)
|
||||
- `web/html/settings.html` — Removed `initClashCodeMirror()` (template editor moved)
|
||||
- `web/translation/translate.en_US.toml` — Added "Servers" key
|
||||
- `web/translation/translate.zh_CN.toml` — Added "Servers" key
|
||||
|
||||
### Version
|
||||
- `config/version` — Bumped to v1.7.2.1
|
||||
41
sub/sub.go
41
sub/sub.go
|
|
@ -18,6 +18,8 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/goccy/go-yaml"
|
||||
"github.com/mhsanaei/3x-ui/v2/config"
|
||||
"github.com/mhsanaei/3x-ui/v2/logger"
|
||||
"github.com/mhsanaei/3x-ui/v2/util/common"
|
||||
webpkg "github.com/mhsanaei/3x-ui/v2/web"
|
||||
|
|
@ -31,6 +33,26 @@ import (
|
|||
|
||||
type subscriptionAssetManifest map[string]string
|
||||
|
||||
// ClashServer represents a proxy server entry from servers.yaml.
|
||||
type ClashServer struct {
|
||||
Name string `yaml:"name"`
|
||||
Server string `yaml:"server"`
|
||||
}
|
||||
|
||||
// serversConfig is the top-level structure of servers.yaml.
|
||||
type serversConfig struct {
|
||||
Servers []ClashServer `yaml:"servers"`
|
||||
}
|
||||
|
||||
// parseServers parses YAML data into a list of ClashServer entries.
|
||||
func parseServers(data []byte) ([]ClashServer, error) {
|
||||
var cfg serversConfig
|
||||
if err := yaml.Unmarshal(data, &cfg); err != nil {
|
||||
return nil, fmt.Errorf("parse servers: %w", err)
|
||||
}
|
||||
return cfg.Servers, nil
|
||||
}
|
||||
|
||||
// setEmbeddedTemplates parses and sets embedded templates on the engine
|
||||
func setEmbeddedTemplates(engine *gin.Engine) error {
|
||||
t, err := template.New("").Funcs(engine.FuncMap).ParseFS(
|
||||
|
|
@ -237,11 +259,26 @@ func (s *Server) initRouter() (*gin.Engine, error) {
|
|||
SubClashPath = "/clash/"
|
||||
}
|
||||
|
||||
SubClashTemplate, err := s.settingService.GetSubClashTemplate()
|
||||
// Read clash template from file (alongside x-ui.json)
|
||||
SubClashTemplate, err := config.ReadClashTemplate()
|
||||
if err != nil {
|
||||
logger.Warning("sub: failed to read clash template:", err)
|
||||
SubClashTemplate = ""
|
||||
}
|
||||
|
||||
// Read servers config from file
|
||||
serversData, err := config.ReadServers()
|
||||
var clashServers []ClashServer
|
||||
if err != nil {
|
||||
logger.Warning("sub: failed to read servers config:", err)
|
||||
} else {
|
||||
clashServers, err = parseServers([]byte(serversData))
|
||||
if err != nil {
|
||||
logger.Warning("sub: failed to parse servers config:", err)
|
||||
clashServers = nil
|
||||
}
|
||||
}
|
||||
|
||||
// set per-request localizer from headers/cookies
|
||||
engine.Use(locale.LocalizerMiddleware())
|
||||
|
||||
|
|
@ -323,7 +360,7 @@ func (s *Server) initRouter() (*gin.Engine, error) {
|
|||
g, LinksPath, JsonPath, subJsonEnable, Encrypt, ShowInfo, RemarkModel, SubUpdates,
|
||||
SubJsonFragment, SubJsonNoises, SubJsonMux, SubJsonRules, SubTitle, SubSupportUrl,
|
||||
SubProfileUrl, SubAnnounce, SubEnableRouting, SubRoutingRules,
|
||||
subClashEnable, SubClashPath, SubClashTemplate)
|
||||
subClashEnable, SubClashPath, SubClashTemplate, clashServers)
|
||||
|
||||
return engine, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,18 +14,50 @@ import (
|
|||
// SubClashService handles Clash YAML subscription generation.
|
||||
type SubClashService struct {
|
||||
template string
|
||||
servers []ClashServer
|
||||
inboundService service.InboundService
|
||||
SubService *SubService
|
||||
}
|
||||
|
||||
// NewSubClashService creates a new Clash subscription service with the given template.
|
||||
func NewSubClashService(template string, subService *SubService) *SubClashService {
|
||||
// NewSubClashService creates a new Clash subscription service with the given template and servers.
|
||||
func NewSubClashService(template string, servers []ClashServer, subService *SubService) *SubClashService {
|
||||
return &SubClashService{
|
||||
template: template,
|
||||
servers: servers,
|
||||
SubService: subService,
|
||||
}
|
||||
}
|
||||
|
||||
// splitTemplate splits a full mihomo template at "proxies:" and "proxy-groups:" markers.
|
||||
// Returns header (everything before proxies) and footer (everything from proxy-groups onwards).
|
||||
func splitTemplate(template string) (header, footer string, err error) {
|
||||
proxiesIdx := strings.Index(template, "\nproxies:")
|
||||
if proxiesIdx == -1 {
|
||||
if strings.HasPrefix(template, "proxies:") {
|
||||
proxiesIdx = 0
|
||||
} else {
|
||||
return "", "", fmt.Errorf("template: 'proxies:' section not found")
|
||||
}
|
||||
} else {
|
||||
proxiesIdx++ // skip the leading newline
|
||||
}
|
||||
|
||||
proxyGroupsIdx := strings.Index(template, "\nproxy-groups:")
|
||||
if proxyGroupsIdx == -1 {
|
||||
if strings.HasPrefix(template[proxiesIdx:], "proxy-groups:") {
|
||||
proxyGroupsIdx = proxiesIdx
|
||||
} else {
|
||||
return "", "", fmt.Errorf("template: 'proxy-groups:' section not found")
|
||||
}
|
||||
} else {
|
||||
proxyGroupsIdx++ // skip the leading newline
|
||||
}
|
||||
|
||||
header = template[:proxiesIdx]
|
||||
footer = template[proxyGroupsIdx:]
|
||||
return header, footer, nil
|
||||
}
|
||||
|
||||
// GetClash generates a Clash YAML configuration for the given subscription ID.
|
||||
func (s *SubClashService) GetClash(subId string) (string, string, error) {
|
||||
if s.template == "" {
|
||||
|
|
@ -101,14 +133,22 @@ func (s *SubClashService) GetClash(subId string) (string, string, error) {
|
|||
proxiesYaml += " - " + p + "\n"
|
||||
}
|
||||
|
||||
// Inject proxies into template by replacing "proxies: []"
|
||||
result := strings.Replace(s.template, "proxies: []", "proxies:\n"+proxiesYaml, 1)
|
||||
// Try split-template approach first (for full mihomo templates)
|
||||
var result string
|
||||
if header, footer, err := splitTemplate(s.template); err == nil {
|
||||
result = header + "proxies:\n" + proxiesYaml + footer
|
||||
} else {
|
||||
// Fall back to old "proxies: []" replacement for backward compatibility
|
||||
result = strings.Replace(s.template, "proxies: []", "proxies:\n"+proxiesYaml, 1)
|
||||
}
|
||||
|
||||
header = fmt.Sprintf("upload=%d; download=%d; total=%d; expire=%d", traffic.Up, traffic.Down, traffic.Total, traffic.ExpiryTime/1000)
|
||||
return result, header, nil
|
||||
}
|
||||
|
||||
// getProxy generates Clash proxy entries for a client.
|
||||
// If servers are configured, generates one entry per server using the server's name and address.
|
||||
// Otherwise, uses the inbound's own address (backward compatible).
|
||||
func (s *SubClashService) getProxy(inbound *model.Inbound, client model.Client) []string {
|
||||
var proxies []string
|
||||
var stream map[string]any
|
||||
|
|
@ -116,17 +156,14 @@ func (s *SubClashService) getProxy(inbound *model.Inbound, client model.Client)
|
|||
logger.Warning("SubClashService - failed to parse StreamSettings for inbound", inbound.Tag, ":", err)
|
||||
}
|
||||
|
||||
// Resolve address
|
||||
var address string
|
||||
// Resolve default address from inbound
|
||||
var defaultAddress string
|
||||
if inbound.Listen == "" || inbound.Listen == "0.0.0.0" || inbound.Listen == "::" || inbound.Listen == "::0" {
|
||||
address = s.SubService.address
|
||||
defaultAddress = s.SubService.address
|
||||
} else {
|
||||
address = inbound.Listen
|
||||
defaultAddress = inbound.Listen
|
||||
}
|
||||
|
||||
// Get remark
|
||||
remark := s.SubService.genRemark(inbound, client.Email, "")
|
||||
|
||||
// Parse stream settings
|
||||
network, _ := stream["network"].(string)
|
||||
security, _ := stream["security"].(string)
|
||||
|
|
@ -141,9 +178,40 @@ func (s *SubClashService) getProxy(inbound *model.Inbound, client model.Client)
|
|||
}
|
||||
}
|
||||
|
||||
// If servers are configured, generate one proxy per server
|
||||
if len(s.servers) > 0 {
|
||||
for _, server := range s.servers {
|
||||
for _, ep := range externalProxies {
|
||||
externalProxy, _ := ep.(map[string]any)
|
||||
destAddress := address
|
||||
destPort := inbound.Port
|
||||
if port, ok := externalProxy["port"].(float64); ok && port > 0 {
|
||||
destPort = int(port)
|
||||
}
|
||||
|
||||
forceTls, _ := externalProxy["forceTls"].(string)
|
||||
tlsEnabled := false
|
||||
switch forceTls {
|
||||
case "tls":
|
||||
tlsEnabled = true
|
||||
case "none":
|
||||
tlsEnabled = false
|
||||
default: // "same"
|
||||
tlsEnabled = security == "tls" || security == "reality"
|
||||
}
|
||||
|
||||
proxy := s.buildProxyEntry(inbound, client, server.Server, destPort, network, security, tlsEnabled, server.Name, stream)
|
||||
proxies = append(proxies, proxy)
|
||||
}
|
||||
}
|
||||
return proxies
|
||||
}
|
||||
|
||||
// No servers configured — use inbound's own address (backward compatible)
|
||||
remark := s.SubService.genRemark(inbound, client.Email, "")
|
||||
|
||||
for _, ep := range externalProxies {
|
||||
externalProxy, _ := ep.(map[string]any)
|
||||
destAddress := defaultAddress
|
||||
destPort := inbound.Port
|
||||
|
||||
if dest, ok := externalProxy["dest"].(string); ok && dest != "" {
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@ func NewSUBController(
|
|||
clashEnabled bool,
|
||||
subClashPath string,
|
||||
subClashTemplate string,
|
||||
clashServers []ClashServer,
|
||||
) *SUBController {
|
||||
sub := NewSubService(showInfo, rModel)
|
||||
a := &SUBController{
|
||||
|
|
@ -76,7 +77,7 @@ func NewSUBController(
|
|||
|
||||
subService: sub,
|
||||
subJsonService: NewSubJsonService(jsonFragment, jsonNoise, jsonMux, jsonRules, sub),
|
||||
subClashService: NewSubClashService(subClashTemplate, sub),
|
||||
subClashService: NewSubClashService(subClashTemplate, clashServers, sub),
|
||||
}
|
||||
a.initRouter(g)
|
||||
return a
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package controller
|
|||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/mhsanaei/3x-ui/v2/config"
|
||||
"github.com/mhsanaei/3x-ui/v2/util/common"
|
||||
"github.com/mhsanaei/3x-ui/v2/web/service"
|
||||
|
||||
|
|
@ -38,6 +39,11 @@ func (a *XraySettingController) initRouter(g *gin.RouterGroup) {
|
|||
g.POST("/update", a.updateSetting)
|
||||
g.POST("/resetOutboundsTraffic", a.resetOutboundsTraffic)
|
||||
g.POST("/testOutbound", a.testOutbound)
|
||||
|
||||
g.GET("/clashTemplate", a.getClashTemplate)
|
||||
g.POST("/clashTemplate", a.saveClashTemplate)
|
||||
g.GET("/servers", a.getServers)
|
||||
g.POST("/servers", a.saveServers)
|
||||
}
|
||||
|
||||
// getXraySetting retrieves the Xray configuration template, inbound tags, and outbound test URL.
|
||||
|
|
@ -166,3 +172,43 @@ func (a *XraySettingController) testOutbound(c *gin.Context) {
|
|||
|
||||
jsonObj(c, result, nil)
|
||||
}
|
||||
|
||||
// getClashTemplate reads the clash_template.yaml file and returns its content.
|
||||
func (a *XraySettingController) getClashTemplate(c *gin.Context) {
|
||||
content, err := config.ReadClashTemplate()
|
||||
if err != nil {
|
||||
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err)
|
||||
return
|
||||
}
|
||||
jsonObj(c, content, nil)
|
||||
}
|
||||
|
||||
// saveClashTemplate writes the clash_template.yaml file.
|
||||
func (a *XraySettingController) saveClashTemplate(c *gin.Context) {
|
||||
content := c.PostForm("content")
|
||||
if err := config.SaveClashTemplate(content); err != nil {
|
||||
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.modifySettings"), err)
|
||||
return
|
||||
}
|
||||
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.modifySettings"), nil)
|
||||
}
|
||||
|
||||
// getServers reads the servers.yaml file and returns its content.
|
||||
func (a *XraySettingController) getServers(c *gin.Context) {
|
||||
content, err := config.ReadServers()
|
||||
if err != nil {
|
||||
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err)
|
||||
return
|
||||
}
|
||||
jsonObj(c, content, nil)
|
||||
}
|
||||
|
||||
// saveServers writes the servers.yaml file.
|
||||
func (a *XraySettingController) saveServers(c *gin.Context) {
|
||||
content := c.PostForm("content")
|
||||
if err := config.SaveServers(content); err != nil {
|
||||
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.modifySettings"), err)
|
||||
return
|
||||
}
|
||||
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.modifySettings"), nil)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -255,29 +255,6 @@
|
|||
},
|
||||
methods: {
|
||||
onSettingsTabChange(key) {
|
||||
if (key === '6') {
|
||||
this.$nextTick(() => this.initClashCodeMirror());
|
||||
}
|
||||
},
|
||||
initClashCodeMirror() {
|
||||
if (this.clashCm != null) {
|
||||
this.clashCm.toTextArea();
|
||||
}
|
||||
const el = document.getElementById('clashTemplate');
|
||||
if (!el) return;
|
||||
el.value = this.allSetting.subClashTemplate;
|
||||
this.clashCm = CodeMirror.fromTextArea(el, {
|
||||
lineNumbers: true,
|
||||
mode: "text/x-yaml",
|
||||
theme: "xq",
|
||||
lineWrapping: true,
|
||||
indentUnit: 2,
|
||||
tabSize: 2,
|
||||
smartIndent: true,
|
||||
});
|
||||
this.clashCm.on('change', editor => {
|
||||
this.allSetting.subClashTemplate = editor.getValue();
|
||||
});
|
||||
},
|
||||
loading(spinning = true) {
|
||||
this.loadingStates.spinning = spinning;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
{{define "settings/panel/subscription/clash"}}
|
||||
<a-collapse default-active-key="['1','2']">
|
||||
<a-collapse default-active-key="['1']">
|
||||
<a-collapse-panel key="1" header='{{ i18n "pages.xray.generalConfigs"}}'>
|
||||
<a-setting-list-item paddings="small">
|
||||
<template #title>{{ i18n "pages.settings.subPath"}}</template>
|
||||
|
|
@ -20,14 +20,5 @@
|
|||
</template>
|
||||
</a-setting-list-item>
|
||||
</a-collapse-panel>
|
||||
<a-collapse-panel key="2" header="Clash YAML Template">
|
||||
<a-setting-list-item paddings="small">
|
||||
<template #title>Template</template>
|
||||
<template #description>Complete Clash YAML template with proxies: [] placeholder. The panel will replace proxies: [] with generated proxy entries.</template>
|
||||
<template #control>
|
||||
<textarea id="clashTemplate" style="display:none"></textarea>
|
||||
</template>
|
||||
</a-setting-list-item>
|
||||
</a-collapse-panel>
|
||||
</a-collapse>
|
||||
{{end}}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@
|
|||
<a-radio-button value="inboundSettings">{{ i18n "pages.xray.Inbounds" }}</a-radio-button>
|
||||
<a-radio-button value="outboundSettings">{{ i18n "pages.xray.Outbounds" }}</a-radio-button>
|
||||
<a-radio-button value="routingRuleSettings">{{ i18n "pages.xray.Routings" }}</a-radio-button>
|
||||
<a-radio-button value="clashTemplate">Clash</a-radio-button>
|
||||
<a-radio-button value="servers">{{ i18n "pages.xray.Servers" }}</a-radio-button>
|
||||
</a-radio-group>
|
||||
<textarea :style="{ position: 'absolute', left: '-800px' }" id="xraySetting"></textarea>
|
||||
</a-space>
|
||||
|
|
|
|||
|
|
@ -274,6 +274,10 @@
|
|||
showAlert: false,
|
||||
advSettings: 'xraySetting',
|
||||
obsSettings: '',
|
||||
clashTemplate: '',
|
||||
oldClashTemplate: '',
|
||||
servers: '',
|
||||
oldServers: '',
|
||||
cm: null,
|
||||
cmOptions: {
|
||||
lineNumbers: true,
|
||||
|
|
@ -420,6 +424,11 @@
|
|||
},
|
||||
async updateXraySetting() {
|
||||
this.loading(true);
|
||||
if (this.advSettings === 'clashTemplate') {
|
||||
await this.saveClashTemplate();
|
||||
} else if (this.advSettings === 'servers') {
|
||||
await this.saveServers();
|
||||
} else {
|
||||
const msg = await HttpUtil.post("/panel/xray/update", {
|
||||
xraySetting: this.xraySetting,
|
||||
outboundTestUrl: this.outboundTestUrl || 'https://www.google.com/generate_204'
|
||||
|
|
@ -428,6 +437,43 @@
|
|||
if (msg.success) {
|
||||
await this.getXraySetting();
|
||||
}
|
||||
return;
|
||||
}
|
||||
this.loading(false);
|
||||
},
|
||||
async getClashTemplate() {
|
||||
const msg = await HttpUtil.get("/panel/xray/clashTemplate");
|
||||
if (msg.success) {
|
||||
this.clashTemplate = msg.obj;
|
||||
this.oldClashTemplate = msg.obj;
|
||||
}
|
||||
},
|
||||
async saveClashTemplate() {
|
||||
this.loading(true);
|
||||
const msg = await HttpUtil.post("/panel/xray/clashTemplate", {
|
||||
content: this.clashTemplate
|
||||
});
|
||||
this.loading(false);
|
||||
if (msg.success) {
|
||||
this.oldClashTemplate = this.clashTemplate;
|
||||
}
|
||||
},
|
||||
async getServers() {
|
||||
const msg = await HttpUtil.get("/panel/xray/servers");
|
||||
if (msg.success) {
|
||||
this.servers = msg.obj;
|
||||
this.oldServers = msg.obj;
|
||||
}
|
||||
},
|
||||
async saveServers() {
|
||||
this.loading(true);
|
||||
const msg = await HttpUtil.post("/panel/xray/servers", {
|
||||
content: this.servers
|
||||
});
|
||||
this.loading(false);
|
||||
if (msg.success) {
|
||||
this.oldServers = this.servers;
|
||||
}
|
||||
},
|
||||
async restartXray() {
|
||||
this.loading(true);
|
||||
|
|
@ -533,10 +579,15 @@
|
|||
}
|
||||
const textAreaObj = document.getElementById('xraySetting');
|
||||
textAreaObj.value = this[this.advSettings];
|
||||
this.cm = CodeMirror.fromTextArea(textAreaObj, this.cmOptions);
|
||||
const isYaml = this.advSettings === 'clashTemplate' || this.advSettings === 'servers';
|
||||
const options = Object.assign({}, this.cmOptions, {
|
||||
mode: isYaml ? "text/x-yaml" : "application/json",
|
||||
lint: !isYaml,
|
||||
});
|
||||
this.cm = CodeMirror.fromTextArea(textAreaObj, options);
|
||||
this.cm.on('change', editor => {
|
||||
const value = editor.getValue();
|
||||
if (this.isJsonString(value)) {
|
||||
if (isYaml || this.isJsonString(value)) {
|
||||
this[this.advSettings] = value;
|
||||
}
|
||||
});
|
||||
|
|
@ -1079,6 +1130,8 @@
|
|||
settingsLoaded = await this.getXraySetting();
|
||||
await this.getXrayResult();
|
||||
await this.getOutboundsTraffic();
|
||||
await this.getClashTemplate();
|
||||
await this.getServers();
|
||||
} finally {
|
||||
this.loadingStates.fetched = true;
|
||||
}
|
||||
|
|
@ -1098,7 +1151,8 @@
|
|||
|
||||
while (true) {
|
||||
await PromiseUtil.sleep(800);
|
||||
this.saveBtnDisable = this.oldXraySetting === this.xraySetting && this.oldOutboundTestUrl === this.outboundTestUrl;
|
||||
this.saveBtnDisable = this.oldXraySetting === this.xraySetting && this.oldOutboundTestUrl === this.outboundTestUrl
|
||||
&& this.oldClashTemplate === this.clashTemplate && this.oldServers === this.servers;
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
|
|
|||
|
|
@ -81,26 +81,7 @@ var defaultValueMap = map[string]string{
|
|||
"subClashEnable": "false",
|
||||
"subClashPath": "/clash/",
|
||||
"subClashURI": "",
|
||||
"subClashTemplate": `port: 7890
|
||||
socks-port: 7891
|
||||
allow-lan: false
|
||||
mode: rule
|
||||
log-level: info
|
||||
proxies: []
|
||||
proxy-groups:
|
||||
- name: Proxy
|
||||
type: select
|
||||
proxies:
|
||||
- DIRECT
|
||||
dns:
|
||||
enable: true
|
||||
enhanced-mode: fake-ip
|
||||
nameserver:
|
||||
- 8.8.8.8
|
||||
- 1.1.1.1
|
||||
rules:
|
||||
- GEOIP,LAN,DIRECT
|
||||
- MATCH,Proxy`,
|
||||
"subClashTemplate": "",
|
||||
"datepicker": "gregorian",
|
||||
"warp": "",
|
||||
"externalTrafficInformEnable": "false",
|
||||
|
|
|
|||
|
|
@ -537,6 +537,7 @@
|
|||
"OutboundsDesc" = "Set the outgoing traffic pathway."
|
||||
"Routings" = "Routing Rules"
|
||||
"RoutingsDesc" = "The priority of each rule is important!"
|
||||
"Servers" = "Servers"
|
||||
"completeTemplate" = "All"
|
||||
"logLevel" = "Log Level"
|
||||
"logLevelDesc" = "The log level for error logs, indicating the information that needs to be recorded."
|
||||
|
|
|
|||
|
|
@ -537,6 +537,7 @@
|
|||
"OutboundsDesc" = "设置出站流量传出方式"
|
||||
"Routings" = "路由规则"
|
||||
"RoutingsDesc" = "每条规则的优先级都很重要"
|
||||
"Servers" = "服务器"
|
||||
"completeTemplate" = "全部"
|
||||
"logLevel" = "日志级别"
|
||||
"logLevelDesc" = "错误日志的日志级别,用于指示需要记录的信息"
|
||||
|
|
|
|||
Loading…
Reference in a new issue