mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-06-06 13:14:11 +00:00
- Update go.mod module path from mhsanaei/3x-ui/v3 to saeederamy/3x-ui/v3 - Update all 73 Go files' import paths accordingly - Fix README.fa_IR.md install command to point to fork's main branch The fork was referencing the original repo's module path in go.mod and all Go source imports, making it dependent on MHSanaei's namespace at build time. https://claude.ai/code/session_01M6d5atbWjuLTj6UwRHoK5m
224 lines
7.5 KiB
Go
224 lines
7.5 KiB
Go
package controller
|
|
|
|
import (
|
|
"encoding/json"
|
|
|
|
"github.com/saeederamy/3x-ui/v3/util/common"
|
|
"github.com/saeederamy/3x-ui/v3/web/service"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
// XraySettingController handles Xray configuration and settings operations.
|
|
type XraySettingController struct {
|
|
XraySettingService service.XraySettingService
|
|
SettingService service.SettingService
|
|
InboundService service.InboundService
|
|
OutboundService service.OutboundService
|
|
XrayService service.XrayService
|
|
WarpService service.WarpService
|
|
NordService service.NordService
|
|
}
|
|
|
|
// NewXraySettingController creates a new XraySettingController and initializes its routes.
|
|
func NewXraySettingController(g *gin.RouterGroup) *XraySettingController {
|
|
a := &XraySettingController{}
|
|
a.initRouter(g)
|
|
return a
|
|
}
|
|
|
|
// initRouter sets up the routes for Xray settings management.
|
|
func (a *XraySettingController) initRouter(g *gin.RouterGroup) {
|
|
g = g.Group("/xray")
|
|
g.GET("/getDefaultJsonConfig", a.getDefaultXrayConfig)
|
|
g.GET("/getOutboundsTraffic", a.getOutboundsTraffic)
|
|
g.GET("/getXrayResult", a.getXrayResult)
|
|
|
|
g.POST("/", a.getXraySetting)
|
|
g.POST("/warp/:action", a.warp)
|
|
g.POST("/nord/:action", a.nord)
|
|
g.POST("/update", a.updateSetting)
|
|
g.POST("/resetOutboundsTraffic", a.resetOutboundsTraffic)
|
|
g.POST("/testOutbound", a.testOutbound)
|
|
}
|
|
|
|
// getXraySetting retrieves the Xray configuration template, inbound tags, and outbound test URL.
|
|
func (a *XraySettingController) getXraySetting(c *gin.Context) {
|
|
xraySetting, err := a.SettingService.GetXrayConfigTemplate()
|
|
if err != nil {
|
|
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err)
|
|
return
|
|
}
|
|
// Older versions of this handler embedded the raw DB value as
|
|
// `xraySetting` in the response without checking if the value
|
|
// already had that wrapper shape. When the frontend saved it
|
|
// back through the textarea verbatim, the wrapper got persisted
|
|
// and every subsequent save nested another layer, which is what
|
|
// eventually produced the blank Xray Settings page in #4059.
|
|
// Strip any such wrapper here, and heal the DB if we found one so
|
|
// the next read is O(1) instead of climbing the same pile again.
|
|
if unwrapped := service.UnwrapXrayTemplateConfig(xraySetting); unwrapped != xraySetting {
|
|
if saveErr := a.XraySettingService.SaveXraySetting(unwrapped); saveErr == nil {
|
|
xraySetting = unwrapped
|
|
} else {
|
|
// Don't fail the read — just serve the unwrapped value
|
|
// and leave the DB healing for a later save.
|
|
xraySetting = unwrapped
|
|
}
|
|
}
|
|
inboundTags, err := a.InboundService.GetInboundTags()
|
|
if err != nil {
|
|
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err)
|
|
return
|
|
}
|
|
clientReverseTags, err := a.InboundService.GetClientReverseTags()
|
|
if err != nil {
|
|
clientReverseTags = "[]"
|
|
}
|
|
outboundTestUrl, _ := a.SettingService.GetXrayOutboundTestUrl()
|
|
if outboundTestUrl == "" {
|
|
outboundTestUrl = "https://www.google.com/generate_204"
|
|
}
|
|
xrayResponse := map[string]any{
|
|
"xraySetting": json.RawMessage(xraySetting),
|
|
"inboundTags": json.RawMessage(inboundTags),
|
|
"clientReverseTags": json.RawMessage(clientReverseTags),
|
|
"outboundTestUrl": outboundTestUrl,
|
|
}
|
|
result, err := json.Marshal(xrayResponse)
|
|
if err != nil {
|
|
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err)
|
|
return
|
|
}
|
|
jsonObj(c, string(result), nil)
|
|
}
|
|
|
|
// updateSetting updates the Xray configuration settings.
|
|
func (a *XraySettingController) updateSetting(c *gin.Context) {
|
|
xraySetting := c.PostForm("xraySetting")
|
|
if err := a.XraySettingService.SaveXraySetting(xraySetting); err != nil {
|
|
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.modifySettings"), err)
|
|
return
|
|
}
|
|
outboundTestUrl := c.PostForm("outboundTestUrl")
|
|
if outboundTestUrl == "" {
|
|
outboundTestUrl = "https://www.google.com/generate_204"
|
|
}
|
|
if err := a.SettingService.SetXrayOutboundTestUrl(outboundTestUrl); err != nil {
|
|
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.modifySettings"), err)
|
|
return
|
|
}
|
|
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.modifySettings"), nil)
|
|
}
|
|
|
|
// getDefaultXrayConfig retrieves the default Xray configuration.
|
|
func (a *XraySettingController) getDefaultXrayConfig(c *gin.Context) {
|
|
defaultJsonConfig, err := a.SettingService.GetDefaultXrayConfig()
|
|
if err != nil {
|
|
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err)
|
|
return
|
|
}
|
|
jsonObj(c, defaultJsonConfig, nil)
|
|
}
|
|
|
|
// getXrayResult retrieves the current Xray service result.
|
|
func (a *XraySettingController) getXrayResult(c *gin.Context) {
|
|
jsonObj(c, a.XrayService.GetXrayResult(), nil)
|
|
}
|
|
|
|
// warp handles Warp-related operations based on the action parameter.
|
|
func (a *XraySettingController) warp(c *gin.Context) {
|
|
action := c.Param("action")
|
|
var resp string
|
|
var err error
|
|
switch action {
|
|
case "data":
|
|
resp, err = a.WarpService.GetWarpData()
|
|
case "del":
|
|
err = a.WarpService.DelWarpData()
|
|
case "config":
|
|
resp, err = a.WarpService.GetWarpConfig()
|
|
case "reg":
|
|
skey := c.PostForm("privateKey")
|
|
pkey := c.PostForm("publicKey")
|
|
resp, err = a.WarpService.RegWarp(skey, pkey)
|
|
case "license":
|
|
license := c.PostForm("license")
|
|
resp, err = a.WarpService.SetWarpLicense(license)
|
|
}
|
|
|
|
jsonObj(c, resp, err)
|
|
}
|
|
|
|
// nord handles NordVPN-related operations based on the action parameter.
|
|
func (a *XraySettingController) nord(c *gin.Context) {
|
|
action := c.Param("action")
|
|
var resp string
|
|
var err error
|
|
switch action {
|
|
case "countries":
|
|
resp, err = a.NordService.GetCountries()
|
|
case "servers":
|
|
countryId := c.PostForm("countryId")
|
|
resp, err = a.NordService.GetServers(countryId)
|
|
case "reg":
|
|
token := c.PostForm("token")
|
|
resp, err = a.NordService.GetCredentials(token)
|
|
case "setKey":
|
|
key := c.PostForm("key")
|
|
resp, err = a.NordService.SetKey(key)
|
|
case "data":
|
|
resp, err = a.NordService.GetNordData()
|
|
case "del":
|
|
err = a.NordService.DelNordData()
|
|
}
|
|
|
|
jsonObj(c, resp, err)
|
|
}
|
|
|
|
// getOutboundsTraffic retrieves the traffic statistics for outbounds.
|
|
func (a *XraySettingController) getOutboundsTraffic(c *gin.Context) {
|
|
outboundsTraffic, err := a.OutboundService.GetOutboundsTraffic()
|
|
if err != nil {
|
|
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getOutboundTrafficError"), err)
|
|
return
|
|
}
|
|
jsonObj(c, outboundsTraffic, nil)
|
|
}
|
|
|
|
// resetOutboundsTraffic resets the traffic statistics for the specified outbound tag.
|
|
func (a *XraySettingController) resetOutboundsTraffic(c *gin.Context) {
|
|
tag := c.PostForm("tag")
|
|
err := a.OutboundService.ResetOutboundTraffic(tag)
|
|
if err != nil {
|
|
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.resetOutboundTrafficError"), err)
|
|
return
|
|
}
|
|
jsonObj(c, "", nil)
|
|
}
|
|
|
|
// testOutbound tests an outbound configuration and returns the delay/response time.
|
|
// Optional form "allOutbounds": JSON array of all outbounds; used to resolve sockopt.dialerProxy dependencies.
|
|
// Optional form "mode": "tcp" for a fast dial-only probe (parallel-safe),
|
|
// anything else (default) for a full HTTP probe through a temp xray instance.
|
|
func (a *XraySettingController) testOutbound(c *gin.Context) {
|
|
outboundJSON := c.PostForm("outbound")
|
|
allOutboundsJSON := c.PostForm("allOutbounds")
|
|
mode := c.PostForm("mode")
|
|
|
|
if outboundJSON == "" {
|
|
jsonMsg(c, I18nWeb(c, "somethingWentWrong"), common.NewError("outbound parameter is required"))
|
|
return
|
|
}
|
|
|
|
// Load the test URL from server settings to prevent SSRF via user-controlled URLs
|
|
testURL, _ := a.SettingService.GetXrayOutboundTestUrl()
|
|
|
|
result, err := a.OutboundService.TestOutbound(outboundJSON, testURL, allOutboundsJSON, mode)
|
|
if err != nil {
|
|
jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err)
|
|
return
|
|
}
|
|
|
|
jsonObj(c, result, nil)
|
|
}
|