From 6ced549deaecb42b9bb93ea9efcb4c1bbaabe8a4 Mon Sep 17 00:00:00 2001 From: mhsanaei Date: Sat, 20 Sep 2025 09:35:50 +0200 Subject: docs: add comments for all functions --- web/controller/api.go | 4 ++ web/controller/base.go | 5 ++ web/controller/inbound.go | 24 ++++++++ web/controller/index.go | 8 +++ web/controller/server.go | 23 +++++++ web/controller/setting.go | 10 +++ web/controller/util.go | 8 +++ web/controller/xray_setting.go | 10 +++ web/controller/xui.go | 7 +++ web/entity/entity.go | 111 +++++++++++++++++++--------------- web/global/global.go | 13 +++- web/global/hashStorage.go | 18 ++++-- web/job/check_client_ip_job.go | 2 + web/job/check_cpu_usage.go | 4 +- web/job/check_hash_storage.go | 4 +- web/job/check_xray_running_job.go | 8 ++- web/job/clear_logs_job.go | 2 + web/job/periodic_traffic_reset_job.go | 4 ++ web/job/stats_notify_job.go | 9 ++- web/job/xray_traffic_job.go | 3 + web/locale/locale.go | 19 +++++- web/middleware/domainValidator.go | 6 ++ web/middleware/redirect.go | 3 + web/network/auto_https_conn.go | 10 +++ web/network/auto_https_listener.go | 6 ++ web/service/inbound.go | 19 ++++++ web/service/outbound.go | 2 + web/service/panel.go | 2 + web/service/server.go | 15 +++-- web/service/setting.go | 2 + web/service/tgbot.go | 68 +++++++++++++++++++-- web/service/user.go | 4 ++ web/service/warp.go | 2 + web/service/xray.go | 16 ++++- web/service/xray_setting.go | 2 + web/session/session.go | 12 ++++ web/web.go | 20 +++++- 37 files changed, 407 insertions(+), 78 deletions(-) (limited to 'web') diff --git a/web/controller/api.go b/web/controller/api.go index 506f8cc3..dbd3f28d 100644 --- a/web/controller/api.go +++ b/web/controller/api.go @@ -6,6 +6,7 @@ import ( "github.com/gin-gonic/gin" ) +// APIController handles the main API routes for the 3x-ui panel, including inbounds and server management. type APIController struct { BaseController inboundController *InboundController @@ -13,12 +14,14 @@ type APIController struct { Tgbot service.Tgbot } +// NewAPIController creates a new APIController instance and initializes its routes. func NewAPIController(g *gin.RouterGroup) *APIController { a := &APIController{} a.initRouter(g) return a } +// initRouter sets up the API routes for inbounds, server, and other endpoints. func (a *APIController) initRouter(g *gin.RouterGroup) { // Main API group api := g.Group("/panel/api") @@ -36,6 +39,7 @@ func (a *APIController) initRouter(g *gin.RouterGroup) { api.GET("/backuptotgbot", a.BackuptoTgbot) } +// BackuptoTgbot sends a backup of the panel data to Telegram bot admins. func (a *APIController) BackuptoTgbot(c *gin.Context) { a.Tgbot.SendBackupToAdmins() } diff --git a/web/controller/base.go b/web/controller/base.go index 15e8cb57..7bc61b64 100644 --- a/web/controller/base.go +++ b/web/controller/base.go @@ -1,3 +1,5 @@ +// Package controller provides HTTP request handlers and controllers for the 3x-ui web management panel. +// It handles routing, authentication, and API endpoints for managing Xray inbounds, settings, and more. package controller import ( @@ -10,8 +12,10 @@ import ( "github.com/gin-gonic/gin" ) +// BaseController provides common functionality for all controllers, including authentication checks. type BaseController struct{} +// checkLogin is a middleware that verifies user authentication and handles unauthorized access. func (a *BaseController) checkLogin(c *gin.Context) { if !session.IsLogin(c) { if isAjax(c) { @@ -25,6 +29,7 @@ func (a *BaseController) checkLogin(c *gin.Context) { } } +// I18nWeb retrieves an internationalized message for the web interface based on the current locale. func I18nWeb(c *gin.Context, name string, params ...string) string { anyfunc, funcExists := c.Get("I18n") if !funcExists { diff --git a/web/controller/inbound.go b/web/controller/inbound.go index 0a988506..eeb160d6 100644 --- a/web/controller/inbound.go +++ b/web/controller/inbound.go @@ -12,17 +12,20 @@ import ( "github.com/gin-gonic/gin" ) +// InboundController handles HTTP requests related to Xray inbounds management. type InboundController struct { inboundService service.InboundService xrayService service.XrayService } +// NewInboundController creates a new InboundController and sets up its routes. func NewInboundController(g *gin.RouterGroup) *InboundController { a := &InboundController{} a.initRouter(g) return a } +// initRouter initializes the routes for inbound-related operations. func (a *InboundController) initRouter(g *gin.RouterGroup) { g.GET("/list", a.getInbounds) @@ -49,6 +52,7 @@ func (a *InboundController) initRouter(g *gin.RouterGroup) { g.POST("/:id/delClientByEmail/:email", a.delInboundClientByEmail) } +// getInbounds retrieves the list of inbounds for the logged-in user. func (a *InboundController) getInbounds(c *gin.Context) { user := session.GetLoginUser(c) inbounds, err := a.inboundService.GetInbounds(user.Id) @@ -59,6 +63,7 @@ func (a *InboundController) getInbounds(c *gin.Context) { jsonObj(c, inbounds, nil) } +// getInbound retrieves a specific inbound by its ID. func (a *InboundController) getInbound(c *gin.Context) { id, err := strconv.Atoi(c.Param("id")) if err != nil { @@ -73,6 +78,7 @@ func (a *InboundController) getInbound(c *gin.Context) { jsonObj(c, inbound, nil) } +// getClientTraffics retrieves client traffic information by email. func (a *InboundController) getClientTraffics(c *gin.Context) { email := c.Param("email") clientTraffics, err := a.inboundService.GetClientTrafficByEmail(email) @@ -83,6 +89,7 @@ func (a *InboundController) getClientTraffics(c *gin.Context) { jsonObj(c, clientTraffics, nil) } +// getClientTrafficsById retrieves client traffic information by inbound ID. func (a *InboundController) getClientTrafficsById(c *gin.Context) { id := c.Param("id") clientTraffics, err := a.inboundService.GetClientTrafficByID(id) @@ -93,6 +100,7 @@ func (a *InboundController) getClientTrafficsById(c *gin.Context) { jsonObj(c, clientTraffics, nil) } +// addInbound creates a new inbound configuration. func (a *InboundController) addInbound(c *gin.Context) { inbound := &model.Inbound{} err := c.ShouldBind(inbound) @@ -119,6 +127,7 @@ func (a *InboundController) addInbound(c *gin.Context) { } } +// delInbound deletes an inbound configuration by its ID. func (a *InboundController) delInbound(c *gin.Context) { id, err := strconv.Atoi(c.Param("id")) if err != nil { @@ -136,6 +145,7 @@ func (a *InboundController) delInbound(c *gin.Context) { } } +// updateInbound updates an existing inbound configuration. func (a *InboundController) updateInbound(c *gin.Context) { id, err := strconv.Atoi(c.Param("id")) if err != nil { @@ -161,6 +171,7 @@ func (a *InboundController) updateInbound(c *gin.Context) { } } +// getClientIps retrieves the IP addresses associated with a client by email. func (a *InboundController) getClientIps(c *gin.Context) { email := c.Param("email") @@ -173,6 +184,7 @@ func (a *InboundController) getClientIps(c *gin.Context) { jsonObj(c, ips, nil) } +// clearClientIps clears the IP addresses for a client by email. func (a *InboundController) clearClientIps(c *gin.Context) { email := c.Param("email") @@ -184,6 +196,7 @@ func (a *InboundController) clearClientIps(c *gin.Context) { jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.logCleanSuccess"), nil) } +// addInboundClient adds a new client to an existing inbound. func (a *InboundController) addInboundClient(c *gin.Context) { data := &model.Inbound{} err := c.ShouldBind(data) @@ -203,6 +216,7 @@ func (a *InboundController) addInboundClient(c *gin.Context) { } } +// delInboundClient deletes a client from an inbound by inbound ID and client ID. func (a *InboundController) delInboundClient(c *gin.Context) { id, err := strconv.Atoi(c.Param("id")) if err != nil { @@ -222,6 +236,7 @@ func (a *InboundController) delInboundClient(c *gin.Context) { } } +// updateInboundClient updates a client's configuration in an inbound. func (a *InboundController) updateInboundClient(c *gin.Context) { clientId := c.Param("clientId") @@ -243,6 +258,7 @@ func (a *InboundController) updateInboundClient(c *gin.Context) { } } +// resetClientTraffic resets the traffic counter for a specific client in an inbound. func (a *InboundController) resetClientTraffic(c *gin.Context) { id, err := strconv.Atoi(c.Param("id")) if err != nil { @@ -262,6 +278,7 @@ func (a *InboundController) resetClientTraffic(c *gin.Context) { } } +// resetAllTraffics resets all traffic counters across all inbounds. func (a *InboundController) resetAllTraffics(c *gin.Context) { err := a.inboundService.ResetAllTraffics() if err != nil { @@ -273,6 +290,7 @@ func (a *InboundController) resetAllTraffics(c *gin.Context) { jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.resetAllTrafficSuccess"), nil) } +// resetAllClientTraffics resets traffic counters for all clients in a specific inbound. func (a *InboundController) resetAllClientTraffics(c *gin.Context) { id, err := strconv.Atoi(c.Param("id")) if err != nil { @@ -290,6 +308,7 @@ func (a *InboundController) resetAllClientTraffics(c *gin.Context) { jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.resetAllClientTrafficSuccess"), nil) } +// importInbound imports an inbound configuration from provided data. func (a *InboundController) importInbound(c *gin.Context) { inbound := &model.Inbound{} err := json.Unmarshal([]byte(c.PostForm("data")), inbound) @@ -319,6 +338,7 @@ func (a *InboundController) importInbound(c *gin.Context) { } } +// delDepletedClients deletes clients in an inbound who have exhausted their traffic limits. func (a *InboundController) delDepletedClients(c *gin.Context) { id, err := strconv.Atoi(c.Param("id")) if err != nil { @@ -333,15 +353,18 @@ func (a *InboundController) delDepletedClients(c *gin.Context) { jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.delDepletedClientsSuccess"), nil) } +// onlines retrieves the list of currently online clients. func (a *InboundController) onlines(c *gin.Context) { jsonObj(c, a.inboundService.GetOnlineClients(), nil) } +// lastOnline retrieves the last online timestamps for clients. func (a *InboundController) lastOnline(c *gin.Context) { data, err := a.inboundService.GetClientsLastOnline() jsonObj(c, data, err) } +// updateClientTraffic updates the traffic statistics for a client by email. func (a *InboundController) updateClientTraffic(c *gin.Context) { email := c.Param("email") @@ -367,6 +390,7 @@ func (a *InboundController) updateClientTraffic(c *gin.Context) { jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.inboundClientUpdateSuccess"), nil) } +// delInboundClientByEmail deletes a client from an inbound by email address. func (a *InboundController) delInboundClientByEmail(c *gin.Context) { inboundId, err := strconv.Atoi(c.Param("id")) if err != nil { diff --git a/web/controller/index.go b/web/controller/index.go index f21e3128..89de710b 100644 --- a/web/controller/index.go +++ b/web/controller/index.go @@ -13,12 +13,14 @@ import ( "github.com/gin-gonic/gin" ) +// LoginForm represents the login request structure. type LoginForm struct { Username string `json:"username" form:"username"` Password string `json:"password" form:"password"` TwoFactorCode string `json:"twoFactorCode" form:"twoFactorCode"` } +// IndexController handles the main index and login-related routes. type IndexController struct { BaseController @@ -27,12 +29,14 @@ type IndexController struct { tgbot service.Tgbot } +// NewIndexController creates a new IndexController and initializes its routes. func NewIndexController(g *gin.RouterGroup) *IndexController { a := &IndexController{} a.initRouter(g) return a } +// initRouter sets up the routes for index, login, logout, and two-factor authentication. func (a *IndexController) initRouter(g *gin.RouterGroup) { g.GET("/", a.index) g.POST("/login", a.login) @@ -40,6 +44,7 @@ func (a *IndexController) initRouter(g *gin.RouterGroup) { g.POST("/getTwoFactorEnable", a.getTwoFactorEnable) } +// index handles the root route, redirecting logged-in users to the panel or showing the login page. func (a *IndexController) index(c *gin.Context) { if session.IsLogin(c) { c.Redirect(http.StatusTemporaryRedirect, "panel/") @@ -48,6 +53,7 @@ func (a *IndexController) index(c *gin.Context) { html(c, "login.html", "pages.login.title", nil) } +// login handles user authentication and session creation. func (a *IndexController) login(c *gin.Context) { var form LoginForm @@ -95,6 +101,7 @@ func (a *IndexController) login(c *gin.Context) { jsonMsg(c, I18nWeb(c, "pages.login.toasts.successLogin"), nil) } +// logout handles user logout by clearing the session and redirecting to the login page. func (a *IndexController) logout(c *gin.Context) { user := session.GetLoginUser(c) if user != nil { @@ -107,6 +114,7 @@ func (a *IndexController) logout(c *gin.Context) { c.Redirect(http.StatusTemporaryRedirect, c.GetString("base_path")) } +// getTwoFactorEnable retrieves the current status of two-factor authentication. func (a *IndexController) getTwoFactorEnable(c *gin.Context) { status, err := a.settingService.GetTwoFactorEnable() if err == nil { diff --git a/web/controller/server.go b/web/controller/server.go index 768adb52..60d165c5 100644 --- a/web/controller/server.go +++ b/web/controller/server.go @@ -15,6 +15,7 @@ import ( var filenameRegex = regexp.MustCompile(`^[a-zA-Z0-9_\-.]+$`) +// ServerController handles server management and status-related operations. type ServerController struct { BaseController @@ -27,6 +28,7 @@ type ServerController struct { lastGetVersionsTime int64 // unix seconds } +// NewServerController creates a new ServerController, initializes routes, and starts background tasks. func NewServerController(g *gin.RouterGroup) *ServerController { a := &ServerController{} a.initRouter(g) @@ -34,6 +36,7 @@ func NewServerController(g *gin.RouterGroup) *ServerController { return a } +// initRouter sets up the routes for server status, Xray management, and utility endpoints. func (a *ServerController) initRouter(g *gin.RouterGroup) { g.GET("/status", a.status) @@ -58,6 +61,7 @@ func (a *ServerController) initRouter(g *gin.RouterGroup) { g.POST("/getNewEchCert", a.getNewEchCert) } +// refreshStatus updates the cached server status and collects CPU history. func (a *ServerController) refreshStatus() { a.lastStatus = a.serverService.GetStatus(a.lastStatus) // collect cpu history when status is fresh @@ -66,6 +70,7 @@ func (a *ServerController) refreshStatus() { } } +// startTask initiates background tasks for continuous status monitoring. func (a *ServerController) startTask() { webServer := global.GetWebServer() c := webServer.GetCron() @@ -76,8 +81,10 @@ func (a *ServerController) startTask() { }) } +// status returns the current server status information. func (a *ServerController) status(c *gin.Context) { jsonObj(c, a.lastStatus, nil) } +// getCpuHistoryBucket retrieves aggregated CPU usage history based on the specified time bucket. func (a *ServerController) getCpuHistoryBucket(c *gin.Context) { bucketStr := c.Param("bucket") bucket, err := strconv.Atoi(bucketStr) @@ -101,6 +108,7 @@ func (a *ServerController) getCpuHistoryBucket(c *gin.Context) { jsonObj(c, points, nil) } +// getXrayVersion retrieves available Xray versions, with caching for 1 minute. func (a *ServerController) getXrayVersion(c *gin.Context) { now := time.Now().Unix() if now-a.lastGetVersionsTime <= 60 { // 1 minute cache @@ -120,18 +128,21 @@ func (a *ServerController) getXrayVersion(c *gin.Context) { jsonObj(c, versions, nil) } +// installXray installs or updates Xray to the specified version. func (a *ServerController) installXray(c *gin.Context) { version := c.Param("version") err := a.serverService.UpdateXray(version) jsonMsg(c, I18nWeb(c, "pages.index.xraySwitchVersionPopover"), err) } +// updateGeofile updates the specified geo file for Xray. func (a *ServerController) updateGeofile(c *gin.Context) { fileName := c.Param("fileName") err := a.serverService.UpdateGeofile(fileName) jsonMsg(c, I18nWeb(c, "pages.index.geofileUpdatePopover"), err) } +// stopXrayService stops the Xray service. func (a *ServerController) stopXrayService(c *gin.Context) { err := a.serverService.StopXrayService() if err != nil { @@ -141,6 +152,7 @@ func (a *ServerController) stopXrayService(c *gin.Context) { jsonMsg(c, I18nWeb(c, "pages.xray.stopSuccess"), err) } +// restartXrayService restarts the Xray service. func (a *ServerController) restartXrayService(c *gin.Context) { err := a.serverService.RestartXrayService() if err != nil { @@ -150,6 +162,7 @@ func (a *ServerController) restartXrayService(c *gin.Context) { jsonMsg(c, I18nWeb(c, "pages.xray.restartSuccess"), err) } +// getLogs retrieves the application logs based on count, level, and syslog filters. func (a *ServerController) getLogs(c *gin.Context) { count := c.Param("count") level := c.PostForm("level") @@ -158,6 +171,7 @@ func (a *ServerController) getLogs(c *gin.Context) { jsonObj(c, logs, nil) } +// getXrayLogs retrieves Xray logs with filtering options for direct, blocked, and proxy traffic. func (a *ServerController) getXrayLogs(c *gin.Context) { count := c.Param("count") filter := c.PostForm("filter") @@ -202,6 +216,7 @@ func (a *ServerController) getXrayLogs(c *gin.Context) { jsonObj(c, logs, nil) } +// getConfigJson retrieves the Xray configuration as JSON. func (a *ServerController) getConfigJson(c *gin.Context) { configJson, err := a.serverService.GetConfigJson() if err != nil { @@ -211,6 +226,7 @@ func (a *ServerController) getConfigJson(c *gin.Context) { jsonObj(c, configJson, nil) } +// getDb downloads the database file. func (a *ServerController) getDb(c *gin.Context) { db, err := a.serverService.GetDb() if err != nil { @@ -238,6 +254,7 @@ func isValidFilename(filename string) bool { return filenameRegex.MatchString(filename) } +// importDB imports a database file and restarts the Xray service. func (a *ServerController) importDB(c *gin.Context) { // Get the file from the request body file, _, err := c.Request.FormFile("db") @@ -258,6 +275,7 @@ func (a *ServerController) importDB(c *gin.Context) { jsonObj(c, I18nWeb(c, "pages.index.importDatabaseSuccess"), nil) } +// getNewX25519Cert generates a new X25519 certificate. func (a *ServerController) getNewX25519Cert(c *gin.Context) { cert, err := a.serverService.GetNewX25519Cert() if err != nil { @@ -267,6 +285,7 @@ func (a *ServerController) getNewX25519Cert(c *gin.Context) { jsonObj(c, cert, nil) } +// getNewmldsa65 generates a new ML-DSA-65 key. func (a *ServerController) getNewmldsa65(c *gin.Context) { cert, err := a.serverService.GetNewmldsa65() if err != nil { @@ -276,6 +295,7 @@ func (a *ServerController) getNewmldsa65(c *gin.Context) { jsonObj(c, cert, nil) } +// getNewEchCert generates a new ECH certificate for the given SNI. func (a *ServerController) getNewEchCert(c *gin.Context) { sni := c.PostForm("sni") cert, err := a.serverService.GetNewEchCert(sni) @@ -286,6 +306,7 @@ func (a *ServerController) getNewEchCert(c *gin.Context) { jsonObj(c, cert, nil) } +// getNewVlessEnc generates a new VLESS encryption key. func (a *ServerController) getNewVlessEnc(c *gin.Context) { out, err := a.serverService.GetNewVlessEnc() if err != nil { @@ -295,6 +316,7 @@ func (a *ServerController) getNewVlessEnc(c *gin.Context) { jsonObj(c, out, nil) } +// getNewUUID generates a new UUID. func (a *ServerController) getNewUUID(c *gin.Context) { uuidResp, err := a.serverService.GetNewUUID() if err != nil { @@ -305,6 +327,7 @@ func (a *ServerController) getNewUUID(c *gin.Context) { jsonObj(c, uuidResp, nil) } +// getNewmlkem768 generates a new ML-KEM-768 key. func (a *ServerController) getNewmlkem768(c *gin.Context) { out, err := a.serverService.GetNewmlkem768() if err != nil { diff --git a/web/controller/setting.go b/web/controller/setting.go index 46f760af..fc5486bc 100644 --- a/web/controller/setting.go +++ b/web/controller/setting.go @@ -12,6 +12,7 @@ import ( "github.com/gin-gonic/gin" ) +// updateUserForm represents the form for updating user credentials. type updateUserForm struct { OldUsername string `json:"oldUsername" form:"oldUsername"` OldPassword string `json:"oldPassword" form:"oldPassword"` @@ -19,18 +20,21 @@ type updateUserForm struct { NewPassword string `json:"newPassword" form:"newPassword"` } +// SettingController handles settings and user management operations. type SettingController struct { settingService service.SettingService userService service.UserService panelService service.PanelService } +// NewSettingController creates a new SettingController and initializes its routes. func NewSettingController(g *gin.RouterGroup) *SettingController { a := &SettingController{} a.initRouter(g) return a } +// initRouter sets up the routes for settings management. func (a *SettingController) initRouter(g *gin.RouterGroup) { g = g.Group("/setting") @@ -42,6 +46,7 @@ func (a *SettingController) initRouter(g *gin.RouterGroup) { g.GET("/getDefaultJsonConfig", a.getDefaultXrayConfig) } +// getAllSetting retrieves all current settings. func (a *SettingController) getAllSetting(c *gin.Context) { allSetting, err := a.settingService.GetAllSetting() if err != nil { @@ -51,6 +56,7 @@ func (a *SettingController) getAllSetting(c *gin.Context) { jsonObj(c, allSetting, nil) } +// getDefaultSettings retrieves the default settings based on the host. func (a *SettingController) getDefaultSettings(c *gin.Context) { result, err := a.settingService.GetDefaultSettings(c.Request.Host) if err != nil { @@ -60,6 +66,7 @@ func (a *SettingController) getDefaultSettings(c *gin.Context) { jsonObj(c, result, nil) } +// updateSetting updates all settings with the provided data. func (a *SettingController) updateSetting(c *gin.Context) { allSetting := &entity.AllSetting{} err := c.ShouldBind(allSetting) @@ -71,6 +78,7 @@ func (a *SettingController) updateSetting(c *gin.Context) { jsonMsg(c, I18nWeb(c, "pages.settings.toasts.modifySettings"), err) } +// updateUser updates the current user's username and password. func (a *SettingController) updateUser(c *gin.Context) { form := &updateUserForm{} err := c.ShouldBind(form) @@ -96,11 +104,13 @@ func (a *SettingController) updateUser(c *gin.Context) { jsonMsg(c, I18nWeb(c, "pages.settings.toasts.modifyUser"), err) } +// restartPanel restarts the panel service after a delay. func (a *SettingController) restartPanel(c *gin.Context) { err := a.panelService.RestartPanel(time.Second * 3) jsonMsg(c, I18nWeb(c, "pages.settings.restartPanelSuccess"), err) } +// getDefaultXrayConfig retrieves the default Xray configuration. func (a *SettingController) getDefaultXrayConfig(c *gin.Context) { defaultJsonConfig, err := a.settingService.GetDefaultXrayConfig() if err != nil { diff --git a/web/controller/util.go b/web/controller/util.go index e180b1fe..b11203bd 100644 --- a/web/controller/util.go +++ b/web/controller/util.go @@ -12,6 +12,7 @@ import ( "github.com/gin-gonic/gin" ) +// getRemoteIp extracts the real IP address from the request headers or remote address. func getRemoteIp(c *gin.Context) string { value := c.GetHeader("X-Real-IP") if value != "" { @@ -27,14 +28,17 @@ func getRemoteIp(c *gin.Context) string { return ip } +// jsonMsg sends a JSON response with a message and error status. func jsonMsg(c *gin.Context, msg string, err error) { jsonMsgObj(c, msg, nil, err) } +// jsonObj sends a JSON response with an object and error status. func jsonObj(c *gin.Context, obj any, err error) { jsonMsgObj(c, "", obj, err) } +// jsonMsgObj sends a JSON response with a message, object, and error status. func jsonMsgObj(c *gin.Context, msg string, obj any, err error) { m := entity.Msg{ Obj: obj, @@ -52,6 +56,7 @@ func jsonMsgObj(c *gin.Context, msg string, obj any, err error) { c.JSON(http.StatusOK, m) } +// pureJsonMsg sends a pure JSON message response with custom status code. func pureJsonMsg(c *gin.Context, statusCode int, success bool, msg string) { c.JSON(statusCode, entity.Msg{ Success: success, @@ -59,6 +64,7 @@ func pureJsonMsg(c *gin.Context, statusCode int, success bool, msg string) { }) } +// html renders an HTML template with the provided data and title. func html(c *gin.Context, name string, title string, data gin.H) { if data == nil { data = gin.H{} @@ -81,6 +87,7 @@ func html(c *gin.Context, name string, title string, data gin.H) { c.HTML(http.StatusOK, name, getContext(data)) } +// getContext adds version and other context data to the provided gin.H. func getContext(h gin.H) gin.H { a := gin.H{ "cur_ver": config.GetVersion(), @@ -91,6 +98,7 @@ func getContext(h gin.H) gin.H { return a } +// isAjax checks if the request is an AJAX request. func isAjax(c *gin.Context) bool { return c.GetHeader("X-Requested-With") == "XMLHttpRequest" } diff --git a/web/controller/xray_setting.go b/web/controller/xray_setting.go index bdbb370c..b78925f0 100644 --- a/web/controller/xray_setting.go +++ b/web/controller/xray_setting.go @@ -6,6 +6,7 @@ import ( "github.com/gin-gonic/gin" ) +// XraySettingController handles Xray configuration and settings operations. type XraySettingController struct { XraySettingService service.XraySettingService SettingService service.SettingService @@ -15,12 +16,14 @@ type XraySettingController struct { WarpService service.WarpService } +// 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) @@ -33,6 +36,7 @@ func (a *XraySettingController) initRouter(g *gin.RouterGroup) { g.POST("/resetOutboundsTraffic", a.resetOutboundsTraffic) } +// getXraySetting retrieves the Xray configuration template and inbound tags. func (a *XraySettingController) getXraySetting(c *gin.Context) { xraySetting, err := a.SettingService.GetXrayConfigTemplate() if err != nil { @@ -48,12 +52,14 @@ func (a *XraySettingController) getXraySetting(c *gin.Context) { jsonObj(c, xrayResponse, nil) } +// updateSetting updates the Xray configuration settings. func (a *XraySettingController) updateSetting(c *gin.Context) { xraySetting := c.PostForm("xraySetting") err := a.XraySettingService.SaveXraySetting(xraySetting) jsonMsg(c, I18nWeb(c, "pages.settings.toasts.modifySettings"), err) } +// getDefaultXrayConfig retrieves the default Xray configuration. func (a *XraySettingController) getDefaultXrayConfig(c *gin.Context) { defaultJsonConfig, err := a.SettingService.GetDefaultXrayConfig() if err != nil { @@ -63,10 +69,12 @@ func (a *XraySettingController) getDefaultXrayConfig(c *gin.Context) { 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 @@ -90,6 +98,7 @@ func (a *XraySettingController) warp(c *gin.Context) { 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 { @@ -99,6 +108,7 @@ func (a *XraySettingController) getOutboundsTraffic(c *gin.Context) { 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) diff --git a/web/controller/xui.go b/web/controller/xui.go index 1afbc427..ba415ac9 100644 --- a/web/controller/xui.go +++ b/web/controller/xui.go @@ -4,6 +4,7 @@ import ( "github.com/gin-gonic/gin" ) +// XUIController is the main controller for the X-UI panel, managing sub-controllers. type XUIController struct { BaseController @@ -13,12 +14,14 @@ type XUIController struct { xraySettingController *XraySettingController } +// NewXUIController creates a new XUIController and initializes its routes. func NewXUIController(g *gin.RouterGroup) *XUIController { a := &XUIController{} a.initRouter(g) return a } +// initRouter sets up the main panel routes and initializes sub-controllers. func (a *XUIController) initRouter(g *gin.RouterGroup) { g = g.Group("/panel") g.Use(a.checkLogin) @@ -34,18 +37,22 @@ func (a *XUIController) initRouter(g *gin.RouterGroup) { a.xraySettingController = NewXraySettingController(g) } +// index renders the main panel index page. func (a *XUIController) index(c *gin.Context) { html(c, "index.html", "pages.index.title", nil) } +// inbounds renders the inbounds management page. func (a *XUIController) inbounds(c *gin.Context) { html(c, "inbounds.html", "pages.inbounds.title", nil) } +// settings renders the settings management page. func (a *XUIController) settings(c *gin.Context) { html(c, "settings.html", "pages.settings.title", nil) } +// xraySettings renders the Xray settings page. func (a *XUIController) xraySettings(c *gin.Context) { html(c, "xray.html", "pages.xray.title", nil) } diff --git a/web/entity/entity.go b/web/entity/entity.go index 41d19d3b..adb60972 100644 --- a/web/entity/entity.go +++ b/web/entity/entity.go @@ -1,3 +1,4 @@ +// Package entity defines data structures and entities used by the web layer of the 3x-ui panel. package entity import ( @@ -10,61 +11,73 @@ import ( "github.com/mhsanaei/3x-ui/v2/util/common" ) +// Msg represents a standard API response message with success status, message text, and optional data object. type Msg struct { - Success bool `json:"success"` - Msg string `json:"msg"` - Obj any `json:"obj"` + Success bool `json:"success"` // Indicates if the operation was successful + Msg string `json:"msg"` // Response message text + Obj any `json:"obj"` // Optional data object } +// AllSetting contains all configuration settings for the 3x-ui panel including web server, Telegram bot, and subscription settings. type AllSetting struct { - WebListen string `json:"webListen" form:"webListen"` - WebDomain string `json:"webDomain" form:"webDomain"` - WebPort int `json:"webPort" form:"webPort"` - WebCertFile string `json:"webCertFile" form:"webCertFile"` - WebKeyFile string `json:"webKeyFile" form:"webKeyFile"` - WebBasePath string `json:"webBasePath" form:"webBasePath"` - SessionMaxAge int `json:"sessionMaxAge" form:"sessionMaxAge"` - PageSize int `json:"pageSize" form:"pageSize"` - ExpireDiff int `json:"expireDiff" form:"expireDiff"` - TrafficDiff int `json:"trafficDiff" form:"trafficDiff"` - RemarkModel string `json:"remarkModel" form:"remarkModel"` - TgBotEnable bool `json:"tgBotEnable" form:"tgBotEnable"` - TgBotToken string `json:"tgBotToken" form:"tgBotToken"` - TgBotProxy string `json:"tgBotProxy" form:"tgBotProxy"` - TgBotAPIServer string `json:"tgBotAPIServer" form:"tgBotAPIServer"` - TgBotChatId string `json:"tgBotChatId" form:"tgBotChatId"` - TgRunTime string `json:"tgRunTime" form:"tgRunTime"` - TgBotBackup bool `json:"tgBotBackup" form:"tgBotBackup"` - TgBotLoginNotify bool `json:"tgBotLoginNotify" form:"tgBotLoginNotify"` - TgCpu int `json:"tgCpu" form:"tgCpu"` - TgLang string `json:"tgLang" form:"tgLang"` - TimeLocation string `json:"timeLocation" form:"timeLocation"` - TwoFactorEnable bool `json:"twoFactorEnable" form:"twoFactorEnable"` - TwoFactorToken string `json:"twoFactorToken" form:"twoFactorToken"` - SubEnable bool `json:"subEnable" form:"subEnable"` - SubJsonEnable bool `json:"subJsonEnable" form:"subJsonEnable"` - SubTitle string `json:"subTitle" form:"subTitle"` - SubListen string `json:"subListen" form:"subListen"` - SubPort int `json:"subPort" form:"subPort"` - SubPath string `json:"subPath" form:"subPath"` - SubDomain string `json:"subDomain" form:"subDomain"` - SubCertFile string `json:"subCertFile" form:"subCertFile"` - SubKeyFile string `json:"subKeyFile" form:"subKeyFile"` - SubUpdates int `json:"subUpdates" form:"subUpdates"` - ExternalTrafficInformEnable bool `json:"externalTrafficInformEnable" form:"externalTrafficInformEnable"` - ExternalTrafficInformURI string `json:"externalTrafficInformURI" form:"externalTrafficInformURI"` - SubEncrypt bool `json:"subEncrypt" form:"subEncrypt"` - SubShowInfo bool `json:"subShowInfo" form:"subShowInfo"` - SubURI string `json:"subURI" form:"subURI"` - SubJsonPath string `json:"subJsonPath" form:"subJsonPath"` - SubJsonURI string `json:"subJsonURI" form:"subJsonURI"` - SubJsonFragment string `json:"subJsonFragment" form:"subJsonFragment"` - SubJsonNoises string `json:"subJsonNoises" form:"subJsonNoises"` - SubJsonMux string `json:"subJsonMux" form:"subJsonMux"` - SubJsonRules string `json:"subJsonRules" form:"subJsonRules"` - Datepicker string `json:"datepicker" form:"datepicker"` + // Web server settings + WebListen string `json:"webListen" form:"webListen"` // Web server listen IP address + WebDomain string `json:"webDomain" form:"webDomain"` // Web server domain for domain validation + WebPort int `json:"webPort" form:"webPort"` // Web server port number + WebCertFile string `json:"webCertFile" form:"webCertFile"` // Path to SSL certificate file for web server + WebKeyFile string `json:"webKeyFile" form:"webKeyFile"` // Path to SSL private key file for web server + WebBasePath string `json:"webBasePath" form:"webBasePath"` // Base path for web panel URLs + SessionMaxAge int `json:"sessionMaxAge" form:"sessionMaxAge"` // Session maximum age in minutes + + // UI settings + PageSize int `json:"pageSize" form:"pageSize"` // Number of items per page in lists + ExpireDiff int `json:"expireDiff" form:"expireDiff"` // Expiration warning threshold in days + TrafficDiff int `json:"trafficDiff" form:"trafficDiff"` // Traffic warning threshold percentage + RemarkModel string `json:"remarkModel" form:"remarkModel"` // Remark model pattern for inbounds + Datepicker string `json:"datepicker" form:"datepicker"` // Date picker format + + // Telegram bot settings + TgBotEnable bool `json:"tgBotEnable" form:"tgBotEnable"` // Enable Telegram bot notifications + TgBotToken string `json:"tgBotToken" form:"tgBotToken"` // Telegram bot token + TgBotProxy string `json:"tgBotProxy" form:"tgBotProxy"` // Proxy URL for Telegram bot + TgBotAPIServer string `json:"tgBotAPIServer" form:"tgBotAPIServer"` // Custom API server for Telegram bot + TgBotChatId string `json:"tgBotChatId" form:"tgBotChatId"` // Telegram chat ID for notifications + TgRunTime string `json:"tgRunTime" form:"tgRunTime"` // Cron schedule for Telegram notifications + TgBotBackup bool `json:"tgBotBackup" form:"tgBotBackup"` // Enable database backup via Telegram + TgBotLoginNotify bool `json:"tgBotLoginNotify" form:"tgBotLoginNotify"` // Send login notifications + TgCpu int `json:"tgCpu" form:"tgCpu"` // CPU usage threshold for alerts + TgLang string `json:"tgLang" form:"tgLang"` // Telegram bot language + + // Security settings + TimeLocation string `json:"timeLocation" form:"timeLocation"` // Time zone location + TwoFactorEnable bool `json:"twoFactorEnable" form:"twoFactorEnable"` // Enable two-factor authentication + TwoFactorToken string `json:"twoFactorToken" form:"twoFactorToken"` // Two-factor authentication token + + // Subscription server settings + SubEnable bool `json:"subEnable" form:"subEnable"` // Enable subscription server + SubJsonEnable bool `json:"subJsonEnable" form:"subJsonEnable"` // Enable JSON subscription endpoint + SubTitle string `json:"subTitle" form:"subTitle"` // Subscription title + SubListen string `json:"subListen" form:"subListen"` // Subscription server listen IP + SubPort int `json:"subPort" form:"subPort"` // Subscription server port + SubPath string `json:"subPath" form:"subPath"` // Base path for subscription URLs + SubDomain string `json:"subDomain" form:"subDomain"` // Domain for subscription server validation + SubCertFile string `json:"subCertFile" form:"subCertFile"` // SSL certificate file for subscription server + SubKeyFile string `json:"subKeyFile" form:"subKeyFile"` // SSL private key file for subscription server + SubUpdates int `json:"subUpdates" form:"subUpdates"` // Subscription update interval in minutes + ExternalTrafficInformEnable bool `json:"externalTrafficInformEnable" form:"externalTrafficInformEnable"` // Enable external traffic reporting + ExternalTrafficInformURI string `json:"externalTrafficInformURI" form:"externalTrafficInformURI"` // URI for external traffic reporting + SubEncrypt bool `json:"subEncrypt" form:"subEncrypt"` // Encrypt subscription responses + SubShowInfo bool `json:"subShowInfo" form:"subShowInfo"` // Show client information in subscriptions + SubURI string `json:"subURI" form:"subURI"` // Subscription server URI + SubJsonPath string `json:"subJsonPath" form:"subJsonPath"` // Path for JSON subscription endpoint + SubJsonURI string `json:"subJsonURI" form:"subJsonURI"` // JSON subscription server URI + SubJsonFragment string `json:"subJsonFragment" form:"subJsonFragment"` // JSON subscription fragment configuration + SubJsonNoises string `json:"subJsonNoises" form:"subJsonNoises"` // JSON subscription noise configuration + SubJsonMux string `json:"subJsonMux" form:"subJsonMux"` // JSON subscription mux configuration + SubJsonRules string `json:"subJsonRules" form:"subJsonRules"` // JSON subscription routing rules } +// CheckValid validates all settings in the AllSetting struct, checking IP addresses, ports, SSL certificates, and other configuration values. func (s *AllSetting) CheckValid() error { if s.WebListen != "" { ip := net.ParseIP(s.WebListen) diff --git a/web/global/global.go b/web/global/global.go index e92c375b..025fa081 100644 --- a/web/global/global.go +++ b/web/global/global.go @@ -1,3 +1,4 @@ +// Package global provides global variables and interfaces for accessing web and subscription servers. package global import ( @@ -12,27 +13,33 @@ var ( subServer SubServer ) +// WebServer interface defines methods for accessing the web server instance. type WebServer interface { - GetCron() *cron.Cron - GetCtx() context.Context + GetCron() *cron.Cron // Get the cron scheduler + GetCtx() context.Context // Get the server context } +// SubServer interface defines methods for accessing the subscription server instance. type SubServer interface { - GetCtx() context.Context + GetCtx() context.Context // Get the server context } +// SetWebServer sets the global web server instance. func SetWebServer(s WebServer) { webServer = s } +// GetWebServer returns the global web server instance. func GetWebServer() WebServer { return webServer } +// SetSubServer sets the global subscription server instance. func SetSubServer(s SubServer) { subServer = s } +// GetSubServer returns the global subscription server instance. func GetSubServer() SubServer { return subServer } diff --git a/web/global/hashStorage.go b/web/global/hashStorage.go index 5d8135ee..962493f7 100644 --- a/web/global/hashStorage.go +++ b/web/global/hashStorage.go @@ -8,18 +8,21 @@ import ( "time" ) +// HashEntry represents a stored hash entry with its value and timestamp. type HashEntry struct { - Hash string - Value string - Timestamp time.Time + Hash string // MD5 hash string + Value string // Original value + Timestamp time.Time // Time when the hash was created } +// HashStorage provides thread-safe storage for hash-value pairs with expiration. type HashStorage struct { sync.RWMutex - Data map[string]HashEntry - Expiration time.Duration + Data map[string]HashEntry // Map of hash to entry + Expiration time.Duration // Expiration duration for entries } +// NewHashStorage creates a new HashStorage instance with the specified expiration duration. func NewHashStorage(expiration time.Duration) *HashStorage { return &HashStorage{ Data: make(map[string]HashEntry), @@ -27,6 +30,7 @@ func NewHashStorage(expiration time.Duration) *HashStorage { } } +// SaveHash generates an MD5 hash for the given query string and stores it with a timestamp. func (h *HashStorage) SaveHash(query string) string { h.Lock() defer h.Unlock() @@ -45,6 +49,7 @@ func (h *HashStorage) SaveHash(query string) string { return md5HashString } +// GetValue retrieves the original value for the given hash, returning true if found. func (h *HashStorage) GetValue(hash string) (string, bool) { h.RLock() defer h.RUnlock() @@ -54,11 +59,13 @@ func (h *HashStorage) GetValue(hash string) (string, bool) { return entry.Value, exists } +// IsMD5 checks if the given string is a valid 32-character MD5 hash. func (h *HashStorage) IsMD5(hash string) bool { match, _ := regexp.MatchString("^[a-f0-9]{32}$", hash) return match } +// RemoveExpiredHashes removes all hash entries that have exceeded the expiration duration. func (h *HashStorage) RemoveExpiredHashes() { h.Lock() defer h.Unlock() @@ -72,6 +79,7 @@ func (h *HashStorage) RemoveExpiredHashes() { } } +// Reset clears all stored hash entries. func (h *HashStorage) Reset() { h.Lock() defer h.Unlock() diff --git a/web/job/check_client_ip_job.go b/web/job/check_client_ip_job.go index 7704e10d..e783a6df 100644 --- a/web/job/check_client_ip_job.go +++ b/web/job/check_client_ip_job.go @@ -18,6 +18,7 @@ import ( "github.com/mhsanaei/3x-ui/v2/xray" ) +// CheckClientIpJob monitors client IP addresses from access logs and manages IP blocking based on configured limits. type CheckClientIpJob struct { lastClear int64 disAllowedIps []string @@ -25,6 +26,7 @@ type CheckClientIpJob struct { var job *CheckClientIpJob +// NewCheckClientIpJob creates a new client IP monitoring job instance. func NewCheckClientIpJob() *CheckClientIpJob { job = new(CheckClientIpJob) return job diff --git a/web/job/check_cpu_usage.go b/web/job/check_cpu_usage.go index 5cb9a21e..2ea87747 100644 --- a/web/job/check_cpu_usage.go +++ b/web/job/check_cpu_usage.go @@ -9,16 +9,18 @@ import ( "github.com/shirou/gopsutil/v4/cpu" ) +// CheckCpuJob monitors CPU usage and sends Telegram notifications when usage exceeds the configured threshold. type CheckCpuJob struct { tgbotService service.Tgbot settingService service.SettingService } +// NewCheckCpuJob creates a new CPU monitoring job instance. func NewCheckCpuJob() *CheckCpuJob { return new(CheckCpuJob) } -// Here run is a interface method of Job interface +// Run checks CPU usage over the last minute and sends a Telegram alert if it exceeds the threshold. func (j *CheckCpuJob) Run() { threshold, _ := j.settingService.GetTgCpu() diff --git a/web/job/check_hash_storage.go b/web/job/check_hash_storage.go index 5f826d63..2112079e 100644 --- a/web/job/check_hash_storage.go +++ b/web/job/check_hash_storage.go @@ -4,15 +4,17 @@ import ( "github.com/mhsanaei/3x-ui/v2/web/service" ) +// CheckHashStorageJob periodically cleans up expired hash entries from the Telegram bot's hash storage. type CheckHashStorageJob struct { tgbotService service.Tgbot } +// NewCheckHashStorageJob creates a new hash storage cleanup job instance. func NewCheckHashStorageJob() *CheckHashStorageJob { return new(CheckHashStorageJob) } -// Here Run is an interface method of the Job interface +// Run removes expired hash entries from the Telegram bot's hash storage. func (j *CheckHashStorageJob) Run() { // Remove expired hashes from storage j.tgbotService.GetHashStorage().RemoveExpiredHashes() diff --git a/web/job/check_xray_running_job.go b/web/job/check_xray_running_job.go index 8f5f0889..5b53b0c6 100644 --- a/web/job/check_xray_running_job.go +++ b/web/job/check_xray_running_job.go @@ -1,3 +1,5 @@ +// Package job provides background job implementations for the 3x-ui web panel, +// including traffic monitoring, system checks, and periodic maintenance tasks. package job import ( @@ -5,16 +7,18 @@ import ( "github.com/mhsanaei/3x-ui/v2/web/service" ) +// CheckXrayRunningJob monitors Xray process health and restarts it if it crashes. type CheckXrayRunningJob struct { xrayService service.XrayService - - checkTime int + checkTime int } +// NewCheckXrayRunningJob creates a new Xray health check job instance. func NewCheckXrayRunningJob() *CheckXrayRunningJob { return new(CheckXrayRunningJob) } +// Run checks if Xray has crashed and restarts it after confirming it's down for 2 consecutive checks. func (j *CheckXrayRunningJob) Run() { if !j.xrayService.DidXrayCrash() { j.checkTime = 0 diff --git a/web/job/clear_logs_job.go b/web/job/clear_logs_job.go index 70e03688..85e0d64a 100644 --- a/web/job/clear_logs_job.go +++ b/web/job/clear_logs_job.go @@ -9,8 +9,10 @@ import ( "github.com/mhsanaei/3x-ui/v2/xray" ) +// ClearLogsJob clears old log files to prevent disk space issues. type ClearLogsJob struct{} +// NewClearLogsJob creates a new log cleanup job instance. func NewClearLogsJob() *ClearLogsJob { return new(ClearLogsJob) } diff --git a/web/job/periodic_traffic_reset_job.go b/web/job/periodic_traffic_reset_job.go index 65aefb9d..6a370a51 100644 --- a/web/job/periodic_traffic_reset_job.go +++ b/web/job/periodic_traffic_reset_job.go @@ -5,19 +5,23 @@ import ( "github.com/mhsanaei/3x-ui/v2/web/service" ) +// Period represents the time period for traffic resets. type Period string +// PeriodicTrafficResetJob resets traffic statistics for inbounds based on their configured reset period. type PeriodicTrafficResetJob struct { inboundService service.InboundService period Period } +// NewPeriodicTrafficResetJob creates a new periodic traffic reset job for the specified period. func NewPeriodicTrafficResetJob(period Period) *PeriodicTrafficResetJob { return &PeriodicTrafficResetJob{ period: period, } } +// Run resets traffic statistics for all inbounds that match the configured reset period. func (j *PeriodicTrafficResetJob) Run() { inbounds, err := j.inboundService.GetInboundsByTrafficReset(string(j.period)) if err != nil { diff --git a/web/job/stats_notify_job.go b/web/job/stats_notify_job.go index 0d0907e0..b93f05cb 100644 --- a/web/job/stats_notify_job.go +++ b/web/job/stats_notify_job.go @@ -4,23 +4,26 @@ import ( "github.com/mhsanaei/3x-ui/v2/web/service" ) +// LoginStatus represents the status of a login attempt. type LoginStatus byte const ( - LoginSuccess LoginStatus = 1 - LoginFail LoginStatus = 0 + LoginSuccess LoginStatus = 1 // Successful login + LoginFail LoginStatus = 0 // Failed login attempt ) +// StatsNotifyJob sends periodic statistics reports via Telegram bot. type StatsNotifyJob struct { xrayService service.XrayService tgbotService service.Tgbot } +// NewStatsNotifyJob creates a new statistics notification job instance. func NewStatsNotifyJob() *StatsNotifyJob { return new(StatsNotifyJob) } -// Here run is a interface method of Job interface +// Run sends a statistics report via Telegram bot if Xray is running. func (j *StatsNotifyJob) Run() { if !j.xrayService.IsXrayRunning() { return diff --git a/web/job/xray_traffic_job.go b/web/job/xray_traffic_job.go index 8ba5a9f9..a9affb4b 100644 --- a/web/job/xray_traffic_job.go +++ b/web/job/xray_traffic_job.go @@ -10,6 +10,7 @@ import ( "github.com/valyala/fasthttp" ) +// XrayTrafficJob collects and processes traffic statistics from Xray, updating the database and optionally informing external APIs. type XrayTrafficJob struct { settingService service.SettingService xrayService service.XrayService @@ -17,10 +18,12 @@ type XrayTrafficJob struct { outboundService service.OutboundService } +// NewXrayTrafficJob creates a new traffic collection job instance. func NewXrayTrafficJob() *XrayTrafficJob { return new(XrayTrafficJob) } +// Run collects traffic statistics from Xray and updates the database, triggering restart if needed. func (j *XrayTrafficJob) Run() { if !j.xrayService.IsXrayRunning() { return diff --git a/web/locale/locale.go b/web/locale/locale.go index b4cb9464..c469911a 100644 --- a/web/locale/locale.go +++ b/web/locale/locale.go @@ -1,3 +1,5 @@ +// Package locale provides internationalization (i18n) support for the 3x-ui web panel, +// including translation loading, localization, and middleware for web and bot interfaces. package locale import ( @@ -20,17 +22,20 @@ var ( LocalizerBot *i18n.Localizer ) +// I18nType represents the type of interface for internationalization. type I18nType string const ( - Bot I18nType = "bot" - Web I18nType = "web" + Bot I18nType = "bot" // Bot interface type + Web I18nType = "web" // Web interface type ) +// SettingService interface defines methods for accessing locale settings. type SettingService interface { GetTgLang() (string, error) } +// InitLocalizer initializes the internationalization system with embedded translation files. func InitLocalizer(i18nFS embed.FS, settingService SettingService) error { // set default bundle to english i18nBundle = i18n.NewBundle(language.MustParse("en-US")) @@ -49,6 +54,7 @@ func InitLocalizer(i18nFS embed.FS, settingService SettingService) error { return nil } +// createTemplateData creates a template data map from parameters with optional separator. func createTemplateData(params []string, separator ...string) map[string]any { var sep string = "==" if len(separator) > 0 { @@ -64,6 +70,9 @@ func createTemplateData(params []string, separator ...string) map[string]any { return templateData } +// I18n retrieves a localized message for the given key and type. +// It supports both bot and web contexts, with optional template parameters. +// Returns the localized message or an empty string if localization fails. func I18n(i18nType I18nType, key string, params ...string) string { var localizer *i18n.Localizer @@ -96,6 +105,7 @@ func I18n(i18nType I18nType, key string, params ...string) string { return msg } +// initTGBotLocalizer initializes the bot localizer with the configured language. func initTGBotLocalizer(settingService SettingService) error { botLang, err := settingService.GetTgLang() if err != nil { @@ -106,6 +116,10 @@ func initTGBotLocalizer(settingService SettingService) error { return nil } +// LocalizerMiddleware returns a Gin middleware that sets up localization for web requests. +// It determines the user's language from cookies or Accept-Language header, +// creates a localizer instance, and stores it in the Gin context for use in handlers. +// Also provides the I18n function in the context for template rendering. func LocalizerMiddleware() gin.HandlerFunc { return func(c *gin.Context) { // Ensure bundle is initialized so creating a Localizer won't panic @@ -152,6 +166,7 @@ func loadTranslationsFromDisk(bundle *i18n.Bundle) error { }) } +// parseTranslationFiles parses embedded translation files and adds them to the i18n bundle. func parseTranslationFiles(i18nFS embed.FS, i18nBundle *i18n.Bundle) error { err := fs.WalkDir(i18nFS, "translation", func(path string, d fs.DirEntry, err error) error { diff --git a/web/middleware/domainValidator.go b/web/middleware/domainValidator.go index c94130c0..ae4793cb 100644 --- a/web/middleware/domainValidator.go +++ b/web/middleware/domainValidator.go @@ -1,3 +1,5 @@ +// Package middleware provides HTTP middleware functions for the 3x-ui web panel, +// including domain validation and URL redirection utilities. package middleware import ( @@ -8,6 +10,10 @@ import ( "github.com/gin-gonic/gin" ) +// DomainValidatorMiddleware returns a Gin middleware that validates the request domain. +// It extracts the host from the request, strips any port number, and compares it +// against the configured domain. Requests from unauthorized domains are rejected +// with HTTP 403 Forbidden status. func DomainValidatorMiddleware(domain string) gin.HandlerFunc { return func(c *gin.Context) { host := c.Request.Host diff --git a/web/middleware/redirect.go b/web/middleware/redirect.go index e3dc8ada..966d897c 100644 --- a/web/middleware/redirect.go +++ b/web/middleware/redirect.go @@ -7,6 +7,9 @@ import ( "github.com/gin-gonic/gin" ) +// RedirectMiddleware returns a Gin middleware that handles URL redirections. +// It provides backward compatibility by redirecting old '/xui' paths to new '/panel' paths, +// including API endpoints. The middleware performs permanent redirects (301) for SEO purposes. func RedirectMiddleware(basePath string) gin.HandlerFunc { return func(c *gin.Context) { // Redirect from old '/xui' path to '/panel' diff --git a/web/network/auto_https_conn.go b/web/network/auto_https_conn.go index d1a9d521..aa0e9dea 100644 --- a/web/network/auto_https_conn.go +++ b/web/network/auto_https_conn.go @@ -1,3 +1,5 @@ +// Package network provides network utilities for the 3x-ui web panel, +// including automatic HTTP to HTTPS redirection functionality. package network import ( @@ -9,6 +11,9 @@ import ( "sync" ) +// AutoHttpsConn wraps a net.Conn to provide automatic HTTP to HTTPS redirection. +// It intercepts the first read to detect HTTP requests and responds with a 307 redirect +// to the HTTPS equivalent URL. Subsequent reads work normally for HTTPS connections. type AutoHttpsConn struct { net.Conn @@ -18,6 +23,8 @@ type AutoHttpsConn struct { readRequestOnce sync.Once } +// NewAutoHttpsConn creates a new AutoHttpsConn that wraps the given connection. +// It enables automatic redirection of HTTP requests to HTTPS. func NewAutoHttpsConn(conn net.Conn) net.Conn { return &AutoHttpsConn{ Conn: conn, @@ -49,6 +56,9 @@ func (c *AutoHttpsConn) readRequest() bool { return true } +// Read implements the net.Conn Read method with automatic HTTPS redirection. +// On the first read, it checks if the request is HTTP and redirects to HTTPS if so. +// Subsequent reads work normally. func (c *AutoHttpsConn) Read(buf []byte) (int, error) { c.readRequestOnce.Do(func() { c.readRequest() diff --git a/web/network/auto_https_listener.go b/web/network/auto_https_listener.go index 26614696..32dc307d 100644 --- a/web/network/auto_https_listener.go +++ b/web/network/auto_https_listener.go @@ -2,16 +2,22 @@ package network import "net" +// AutoHttpsListener wraps a net.Listener to provide automatic HTTPS redirection. +// It returns AutoHttpsConn connections that handle HTTP to HTTPS redirection. type AutoHttpsListener struct { net.Listener } +// NewAutoHttpsListener creates a new AutoHttpsListener that wraps the given listener. +// It enables automatic redirection of HTTP requests to HTTPS for all accepted connections. func NewAutoHttpsListener(listener net.Listener) net.Listener { return &AutoHttpsListener{ Listener: listener, } } +// Accept implements the net.Listener Accept method. +// It accepts connections and wraps them with AutoHttpsConn for HTTPS redirection. func (l *AutoHttpsListener) Accept() (net.Conn, error) { conn, err := l.Listener.Accept() if err != nil { diff --git a/web/service/inbound.go b/web/service/inbound.go index 414d5945..5c6083ee 100644 --- a/web/service/inbound.go +++ b/web/service/inbound.go @@ -1,3 +1,5 @@ +// Package service provides business logic services for the 3x-ui web panel, +// including inbound/outbound management, user administration, settings, and Xray integration. package service import ( @@ -17,10 +19,15 @@ import ( "gorm.io/gorm" ) +// InboundService provides business logic for managing Xray inbound configurations. +// It handles CRUD operations for inbounds, client management, traffic monitoring, +// and integration with the Xray API for real-time updates. type InboundService struct { xrayApi xray.XrayAPI } +// GetInbounds retrieves all inbounds for a specific user. +// Returns a slice of inbound models with their associated client statistics. func (s *InboundService) GetInbounds(userId int) ([]*model.Inbound, error) { db := database.GetDB() var inbounds []*model.Inbound @@ -31,6 +38,8 @@ func (s *InboundService) GetInbounds(userId int) ([]*model.Inbound, error) { return inbounds, nil } +// GetAllInbounds retrieves all inbounds from the database. +// Returns a slice of all inbound models with their associated client statistics. func (s *InboundService) GetAllInbounds() ([]*model.Inbound, error) { db := database.GetDB() var inbounds []*model.Inbound @@ -163,6 +172,10 @@ func (s *InboundService) checkEmailExistForInbound(inbound *model.Inbound) (stri return "", nil } +// AddInbound creates a new inbound configuration. +// It validates port uniqueness, client email uniqueness, and required fields, +// then saves the inbound to the database and optionally adds it to the running Xray instance. +// Returns the created inbound, whether Xray needs restart, and any error. func (s *InboundService) AddInbound(inbound *model.Inbound) (*model.Inbound, bool, error) { exist, err := s.checkPortExist(inbound.Listen, inbound.Port, 0) if err != nil { @@ -269,6 +282,9 @@ func (s *InboundService) AddInbound(inbound *model.Inbound) (*model.Inbound, boo return inbound, needRestart, err } +// DelInbound deletes an inbound configuration by ID. +// It removes the inbound from the database and the running Xray instance if active. +// Returns whether Xray needs restart and any error. func (s *InboundService) DelInbound(id int) (bool, error) { db := database.GetDB() @@ -322,6 +338,9 @@ func (s *InboundService) GetInbound(id int) (*model.Inbound, error) { return inbound, nil } +// UpdateInbound modifies an existing inbound configuration. +// It validates changes, updates the database, and syncs with the running Xray instance. +// Returns the updated inbound, whether Xray needs restart, and any error. func (s *InboundService) UpdateInbound(inbound *model.Inbound) (*model.Inbound, bool, error) { exist, err := s.checkPortExist(inbound.Listen, inbound.Port, inbound.Id) if err != nil { diff --git a/web/service/outbound.go b/web/service/outbound.go index 94a8f0b3..530d12eb 100644 --- a/web/service/outbound.go +++ b/web/service/outbound.go @@ -9,6 +9,8 @@ import ( "gorm.io/gorm" ) +// OutboundService provides business logic for managing Xray outbound configurations. +// It handles outbound traffic monitoring and statistics. type OutboundService struct{} func (s *OutboundService) AddTraffic(traffics []*xray.Traffic, clientTraffics []*xray.ClientTraffic) (error, bool) { diff --git a/web/service/panel.go b/web/service/panel.go index 72576299..e4fb0c68 100644 --- a/web/service/panel.go +++ b/web/service/panel.go @@ -8,6 +8,8 @@ import ( "github.com/mhsanaei/3x-ui/v2/logger" ) +// PanelService provides business logic for panel management operations. +// It handles panel restart, updates, and system-level panel controls. type PanelService struct{} func (s *PanelService) RestartPanel(delay time.Duration) error { diff --git a/web/service/server.go b/web/service/server.go index 03199b50..9fe42e2c 100644 --- a/web/service/server.go +++ b/web/service/server.go @@ -35,14 +35,18 @@ import ( "github.com/shirou/gopsutil/v4/net" ) +// ProcessState represents the current state of a system process. type ProcessState string +// Process state constants const ( - Running ProcessState = "running" - Stop ProcessState = "stop" - Error ProcessState = "error" + Running ProcessState = "running" // Process is running normally + Stop ProcessState = "stop" // Process is stopped + Error ProcessState = "error" // Process is in error state ) +// Status represents comprehensive system and application status information. +// It includes CPU, memory, disk, network statistics, and Xray process status. type Status struct { T time.Time `json:"-"` Cpu float64 `json:"cpu"` @@ -89,10 +93,13 @@ type Status struct { } `json:"appStats"` } +// Release represents information about a software release from GitHub. type Release struct { - TagName string `json:"tag_name"` + TagName string `json:"tag_name"` // The tag name of the release } +// ServerService provides business logic for server monitoring and management. +// It handles system status collection, IP detection, and application statistics. type ServerService struct { xrayService XrayService inboundService InboundService diff --git a/web/service/setting.go b/web/service/setting.go index 7046464a..530a6344 100644 --- a/web/service/setting.go +++ b/web/service/setting.go @@ -75,6 +75,8 @@ var defaultValueMap = map[string]string{ "externalTrafficInformURI": "", } +// SettingService provides business logic for application settings management. +// It handles configuration storage, retrieval, and validation for all system settings. type SettingService struct{} func (s *SettingService) GetDefaultJsonConfig() (any, error) { diff --git a/web/service/tgbot.go b/web/service/tgbot.go index 44e4af28..e575bb28 100644 --- a/web/service/tgbot.go +++ b/web/service/tgbot.go @@ -65,14 +65,18 @@ var ( var userStates = make(map[int64]string) +// LoginStatus represents the result of a login attempt. type LoginStatus byte +// Login status constants const ( - LoginSuccess LoginStatus = 1 - LoginFail LoginStatus = 0 - EmptyTelegramUserID = int64(0) + LoginSuccess LoginStatus = 1 // Login was successful + LoginFail LoginS