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 --- config/config.go | 12 ++++ database/db.go | 11 ++++ database/model/model.go | 76 +++++++++++++---------- logger/logger.go | 20 +++++- main.go | 14 +++++ sub/sub.go | 9 +++ sub/subController.go | 7 +++ sub/subJsonService.go | 3 + sub/subService.go | 7 +++ util/common/err.go | 4 ++ util/common/format.go | 1 + util/common/multi_error.go | 3 + util/crypto/crypto.go | 3 + util/json_util/json.go | 7 ++- util/random/random.go | 5 ++ util/reflect_util/reflect.go | 3 + util/sys/psutil.go | 2 + util/sys/sys_linux.go | 4 ++ util/sys/sys_windows.go | 5 ++ 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 +++++- xray/api.go | 14 +++++ xray/client_traffic.go | 2 + xray/config.go | 3 + xray/inbound.go | 3 + xray/log_writer.go | 3 + xray/process.go | 29 +++++++++ xray/traffic.go | 2 + 63 files changed, 624 insertions(+), 113 deletions(-) diff --git a/config/config.go b/config/config.go index d5fe65ff..c9a3e83c 100644 --- a/config/config.go +++ b/config/config.go @@ -1,3 +1,5 @@ +// Package config provides configuration management utilities for the 3x-ui panel, +// including version information, logging levels, database paths, and environment variable handling. package config import ( @@ -16,8 +18,10 @@ var version string //go:embed name var name string +// LogLevel represents the logging level for the application. type LogLevel string +// Logging level constants const ( Debug LogLevel = "debug" Info LogLevel = "info" @@ -26,14 +30,17 @@ const ( Error LogLevel = "error" ) +// GetVersion returns the version string of the 3x-ui application. func GetVersion() string { return strings.TrimSpace(version) } +// GetName returns the name of the 3x-ui application. func GetName() string { return strings.TrimSpace(name) } +// GetLogLevel returns the current logging level based on environment variables or defaults to Info. func GetLogLevel() LogLevel { if IsDebug() { return Debug @@ -45,10 +52,12 @@ func GetLogLevel() LogLevel { return LogLevel(logLevel) } +// IsDebug returns true if debug mode is enabled via the XUI_DEBUG environment variable. func IsDebug() bool { return os.Getenv("XUI_DEBUG") == "true" } +// GetBinFolderPath returns the path to the binary folder, defaulting to "bin" if not set via XUI_BIN_FOLDER. func GetBinFolderPath() string { binFolderPath := os.Getenv("XUI_BIN_FOLDER") if binFolderPath == "" { @@ -74,6 +83,7 @@ func getBaseDir() string { return exeDir } +// GetDBFolderPath returns the path to the database folder based on environment variables or platform defaults. func GetDBFolderPath() string { dbFolderPath := os.Getenv("XUI_DB_FOLDER") if dbFolderPath != "" { @@ -85,10 +95,12 @@ func GetDBFolderPath() string { return "/etc/x-ui" } +// GetDBPath returns the full path to the database file. func GetDBPath() string { return fmt.Sprintf("%s/%s.db", GetDBFolderPath(), GetName()) } +// GetLogFolder returns the path to the log folder based on environment variables or platform defaults. func GetLogFolder() string { logFolderPath := os.Getenv("XUI_LOG_FOLDER") if logFolderPath != "" { diff --git a/database/db.go b/database/db.go index 8414b118..6de81d79 100644 --- a/database/db.go +++ b/database/db.go @@ -1,3 +1,5 @@ +// Package database provides database initialization, migration, and management utilities +// for the 3x-ui panel using GORM with SQLite. package database import ( @@ -45,6 +47,7 @@ func initModels() error { return nil } +// initUser creates a default admin user if the users table is empty. func initUser() error { empty, err := isTableEmpty("users") if err != nil { @@ -68,6 +71,7 @@ func initUser() error { return nil } +// runSeeders migrates user passwords to bcrypt and records seeder execution to prevent re-running. func runSeeders(isUsersEmpty bool) error { empty, err := isTableEmpty("history_of_seeders") if err != nil { @@ -107,12 +111,14 @@ func runSeeders(isUsersEmpty bool) error { return nil } +// isTableEmpty returns true if the named table contains zero rows. func isTableEmpty(tableName string) (bool, error) { var count int64 err := db.Table(tableName).Count(&count).Error return count == 0, err } +// InitDB sets up the database connection, migrates models, and runs seeders. func InitDB(dbPath string) error { dir := path.Dir(dbPath) err := os.MkdirAll(dir, fs.ModePerm) @@ -151,6 +157,7 @@ func InitDB(dbPath string) error { return runSeeders(isUsersEmpty) } +// CloseDB closes the database connection if it exists. func CloseDB() error { if db != nil { sqlDB, err := db.DB() @@ -162,14 +169,17 @@ func CloseDB() error { return nil } +// GetDB returns the global GORM database instance. func GetDB() *gorm.DB { return db } +// IsNotFound checks if the given error is a GORM record not found error. func IsNotFound(err error) bool { return err == gorm.ErrRecordNotFound } +// IsSQLiteDB checks if the given file is a valid SQLite database by reading its signature. func IsSQLiteDB(file io.ReaderAt) (bool, error) { signature := []byte("SQLite format 3\x00") buf := make([]byte, len(signature)) @@ -180,6 +190,7 @@ func IsSQLiteDB(file io.ReaderAt) (bool, error) { return bytes.Equal(buf, signature), nil } +// Checkpoint performs a WAL checkpoint on the SQLite database to ensure data consistency. func Checkpoint() error { // Update WAL err := db.Exec("PRAGMA wal_checkpoint;").Error diff --git a/database/model/model.go b/database/model/model.go index abf8075c..720f0223 100644 --- a/database/model/model.go +++ b/database/model/model.go @@ -1,3 +1,4 @@ +// Package model defines the database models and data structures used by the 3x-ui panel. package model import ( @@ -7,8 +8,10 @@ import ( "github.com/mhsanaei/3x-ui/v2/xray" ) +// Protocol represents the protocol type for Xray inbounds. type Protocol string +// Protocol constants for different Xray inbound protocols const ( VMESS Protocol = "vmess" VLESS Protocol = "vless" @@ -20,27 +23,29 @@ const ( WireGuard Protocol = "wireguard" ) +// User represents a user account in the 3x-ui panel. type User struct { Id int `json:"id" gorm:"primaryKey;autoIncrement"` Username string `json:"username"` Password string `json:"password"` } +// Inbound represents an Xray inbound configuration with traffic statistics and settings. type Inbound struct { - Id int `json:"id" form:"id" gorm:"primaryKey;autoIncrement"` - UserId int `json:"-"` - Up int64 `json:"up" form:"up"` - Down int64 `json:"down" form:"down"` - Total int64 `json:"total" form:"total"` - AllTime int64 `json:"allTime" form:"allTime" gorm:"default:0"` - Remark string `json:"remark" form:"remark"` - Enable bool `json:"enable" form:"enable" gorm:"index:idx_enable_traffic_reset,priority:1"` - ExpiryTime int64 `json:"expiryTime" form:"expiryTime"` - TrafficReset string `json:"trafficReset" form:"trafficReset" gorm:"default:never;index:idx_enable_traffic_reset,priority:2"` - LastTrafficResetTime int64 `json:"lastTrafficResetTime" form:"lastTrafficResetTime" gorm:"default:0"` - ClientStats []xray.ClientTraffic `gorm:"foreignKey:InboundId;references:Id" json:"clientStats" form:"clientStats"` + Id int `json:"id" form:"id" gorm:"primaryKey;autoIncrement"` // Unique identifier + UserId int `json:"-"` // Associated user ID + Up int64 `json:"up" form:"up"` // Upload traffic in bytes + Down int64 `json:"down" form:"down"` // Download traffic in bytes + Total int64 `json:"total" form:"total"` // Total traffic limit in bytes + AllTime int64 `json:"allTime" form:"allTime" gorm:"default:0"` // All-time traffic usage + Remark string `json:"remark" form:"remark"` // Human-readable remark + Enable bool `json:"enable" form:"enable" gorm:"index:idx_enable_traffic_reset,priority:1"` // Whether the inbound is enabled + ExpiryTime int64 `json:"expiryTime" form:"expiryTime"` // Expiration timestamp + TrafficReset string `json:"trafficReset" form:"trafficReset" gorm:"default:never;index:idx_enable_traffic_reset,priority:2"` // Traffic reset schedule + LastTrafficResetTime int64 `json:"lastTrafficResetTime" form:"lastTrafficResetTime" gorm:"default:0"` // Last traffic reset timestamp + ClientStats []xray.ClientTraffic `gorm:"foreignKey:InboundId;references:Id" json:"clientStats" form:"clientStats"` // Client traffic statistics - // config part + // Xray configuration fields Listen string `json:"listen" form:"listen"` Port int `json:"port" form:"port"` Protocol Protocol `json:"protocol" form:"protocol"` @@ -50,6 +55,7 @@ type Inbound struct { Sniffing string `json:"sniffing" form:"sniffing"` } +// OutboundTraffics tracks traffic statistics for Xray outbound connections. type OutboundTraffics struct { Id int `json:"id" form:"id" gorm:"primaryKey;autoIncrement"` Tag string `json:"tag" form:"tag" gorm:"unique"` @@ -58,17 +64,20 @@ type OutboundTraffics struct { Total int64 `json:"total" form:"total" gorm:"default:0"` } +// InboundClientIps stores IP addresses associated with inbound clients for access control. type InboundClientIps struct { Id int `json:"id" gorm:"primaryKey;autoIncrement"` ClientEmail string `json:"clientEmail" form:"clientEmail" gorm:"unique"` Ips string `json:"ips" form:"ips"` } +// HistoryOfSeeders tracks which database seeders have been executed to prevent re-running. type HistoryOfSeeders struct { Id int `json:"id" gorm:"primaryKey;autoIncrement"` SeederName string `json:"seederName"` } +// GenXrayInboundConfig generates an Xray inbound configuration from the Inbound model. func (i *Inbound) GenXrayInboundConfig() *xray.InboundConfig { listen := i.Listen if listen != "" { @@ -85,33 +94,36 @@ func (i *Inbound) GenXrayInboundConfig() *xray.InboundConfig { } } +// Setting stores key-value configuration settings for the 3x-ui panel. type Setting struct { Id int `json:"id" form:"id" gorm:"primaryKey;autoIncrement"` Key string `json:"key" form:"key"` Value string `json:"value" form:"value"` } +// Client represents a client configuration for Xray inbounds with traffic limits and settings. type Client struct { - ID string `json:"id"` - Security string `json:"security"` - Password string `json:"password"` - Flow string `json:"flow"` - Email string `json:"email"` - LimitIP int `json:"limitIp"` - TotalGB int64 `json:"totalGB" form:"totalGB"` - ExpiryTime int64 `json:"expiryTime" form:"expiryTime"` - Enable bool `json:"enable" form:"enable"` - TgID int64 `json:"tgId" form:"tgId"` - SubID string `json:"subId" form:"subId"` - Comment string `json:"comment" form:"comment"` - Reset int `json:"reset" form:"reset"` - CreatedAt int64 `json:"created_at,omitempty"` - UpdatedAt int64 `json:"updated_at,omitempty"` + ID string `json:"id"` // Unique client identifier + Security string `json:"security"` // Security method (e.g., "auto", "aes-128-gcm") + Password string `json:"password"` // Client password + Flow string `json:"flow"` // Flow control (XTLS) + Email string `json:"email"` // Client email identifier + LimitIP int `json:"limitIp"` // IP limit for this client + TotalGB int64 `json:"totalGB" form:"totalGB"` // Total traffic limit in GB + ExpiryTime int64 `json:"expiryTime" form:"expiryTime"` // Expiration timestamp + Enable bool `json:"enable" form:"enable"` // Whether the client is enabled + TgID int64 `json:"tgId" form:"tgId"` // Telegram user ID for notifications + SubID string `json:"subId" form:"subId"` // Subscription identifier + Comment string `json:"comment" form:"comment"` // Client comment + Reset int `json:"reset" form:"reset"` // Reset period in days + CreatedAt int64 `json:"created_at,omitempty"` // Creation timestamp + UpdatedAt int64 `json:"updated_at,omitempty"` // Last update timestamp } +// VLESSSettings contains VLESS protocol-specific configuration settings. type VLESSSettings struct { - Clients []Client `json:"clients"` - Decryption string `json:"decryption"` - Encryption string `json:"encryption"` - Fallbacks []any `json:"fallbacks"` + Clients []Client `json:"clients"` // List of VLESS clients + Decryption string `json:"decryption"` // Decryption method + Encryption string `json:"encryption"` // Encryption method (usually "none" for VLESS) + Fallbacks []any `json:"fallbacks"` // Fallback configurations } diff --git a/logger/logger.go b/logger/logger.go index 3705c3df..ccacf697 100644 --- a/logger/logger.go +++ b/logger/logger.go @@ -1,3 +1,5 @@ +// Package logger provides logging functionality for the 3x-ui panel with +// buffered log storage and multiple log levels. package logger import ( @@ -9,7 +11,11 @@ import ( ) var ( - logger *logging.Logger + logger *logging.Logger + + // addToBuffer appends a log entry into the in-memory ring buffer used for + // retrieving recent logs via the web UI. It keeps the buffer bounded to avoid + // uncontrolled growth. logBuffer []struct { time string level logging.Level @@ -21,6 +27,7 @@ func init() { InitLogger(logging.INFO) } +// InitLogger initializes the logger with the specified logging level. func InitLogger(level logging.Level) { newLogger := logging.MustGetLogger("x-ui") var err error @@ -47,51 +54,61 @@ func InitLogger(level logging.Level) { logger = newLogger } +// Debug logs a debug message and adds it to the log buffer. func Debug(args ...any) { logger.Debug(args...) addToBuffer("DEBUG", fmt.Sprint(args...)) } +// Debugf logs a formatted debug message and adds it to the log buffer. func Debugf(format string, args ...any) { logger.Debugf(format, args...) addToBuffer("DEBUG", fmt.Sprintf(format, args...)) } +// Info logs an info message and adds it to the log buffer. func Info(args ...any) { logger.Info(args...) addToBuffer("INFO", fmt.Sprint(args...)) } +// Infof logs a formatted info message and adds it to the log buffer. func Infof(format string, args ...any) { logger.Infof(format, args...) addToBuffer("INFO", fmt.Sprintf(format, args...)) } +// Notice logs a notice message and adds it to the log buffer. func Notice(args ...any) { logger.Notice(args...) addToBuffer("NOTICE", fmt.Sprint(args...)) } +// Noticef logs a formatted notice message and adds it to the log buffer. func Noticef(format string, args ...any) { logger.Noticef(format, args...) addToBuffer("NOTICE", fmt.Sprintf(format, args...)) } +// Warning logs a warning message and adds it to the log buffer. func Warning(args ...any) { logger.Warning(args...) addToBuffer("WARNING", fmt.Sprint(args...)) } +// Warningf logs a formatted warning message and adds it to the log buffer. func Warningf(format string, args ...any) { logger.Warningf(format, args...) addToBuffer("WARNING", fmt.Sprintf(format, args...)) } +// Error logs an error message and adds it to the log buffer. func Error(args ...any) { logger.Error(args...) addToBuffer("ERROR", fmt.Sprint(args...)) } +// Errorf logs a formatted error message and adds it to the log buffer. func Errorf(format string, args ...any) { logger.Errorf(format, args...) addToBuffer("ERROR", fmt.Sprintf(format, args...)) @@ -115,6 +132,7 @@ func addToBuffer(level string, newLog string) { }) } +// GetLogs retrieves up to c log entries from the buffer that are at or below the specified level. func GetLogs(c int, level string) []string { var output []string logLevel, _ := logging.LogLevel(level) diff --git a/main.go b/main.go index d02a37eb..119dc4d9 100644 --- a/main.go +++ b/main.go @@ -1,3 +1,5 @@ +// Package main is the entry point for the 3x-ui web panel application. +// It initializes the database, web server, and handles command-line operations for managing the panel. package main import ( @@ -22,6 +24,7 @@ import ( "github.com/op/go-logging" ) +// runWebServer initializes and starts the web server for the 3x-ui panel. func runWebServer() { log.Printf("Starting %v %v", config.GetName(), config.GetVersion()) @@ -111,6 +114,7 @@ func runWebServer() { } } +// resetSetting resets all panel settings to their default values. func resetSetting() { err := database.InitDB(config.GetDBPath()) if err != nil { @@ -127,6 +131,7 @@ func resetSetting() { } } +// showSetting displays the current panel settings if show is true. func showSetting(show bool) { if show { settingService := service.SettingService{} @@ -176,6 +181,7 @@ func showSetting(show bool) { } } +// updateTgbotEnableSts enables or disables the Telegram bot notifications based on the status parameter. func updateTgbotEnableSts(status bool) { settingService := service.SettingService{} currentTgSts, err := settingService.GetTgbotEnabled() @@ -195,6 +201,7 @@ func updateTgbotEnableSts(status bool) { } } +// updateTgbotSetting updates Telegram bot settings including token, chat ID, and runtime schedule. func updateTgbotSetting(tgBotToken string, tgBotChatid string, tgBotRuntime string) { err := database.InitDB(config.GetDBPath()) if err != nil { @@ -232,6 +239,7 @@ func updateTgbotSetting(tgBotToken string, tgBotChatid string, tgBotRuntime stri } } +// updateSetting updates various panel settings including port, credentials, base path, listen IP, and two-factor authentication. func updateSetting(port int, username string, password string, webBasePath string, listenIP string, resetTwoFactor bool) { err := database.InitDB(config.GetDBPath()) if err != nil { @@ -290,6 +298,7 @@ func updateSetting(port int, username string, password string, webBasePath strin } } +// updateCert updates the SSL certificate files for the panel. func updateCert(publicKey string, privateKey string) { err := database.InitDB(config.GetDBPath()) if err != nil { @@ -317,6 +326,7 @@ func updateCert(publicKey string, privateKey string) { } } +// GetCertificate displays the current SSL certificate settings if getCert is true. func GetCertificate(getCert bool) { if getCert { settingService := service.SettingService{} @@ -334,6 +344,7 @@ func GetCertificate(getCert bool) { } } +// GetListenIP displays the current panel listen IP address if getListen is true. func GetListenIP(getListen bool) { if getListen { @@ -348,6 +359,7 @@ func GetListenIP(getListen bool) { } } +// migrateDb performs database migration operations for the 3x-ui panel. func migrateDb() { inboundService := service.InboundService{} @@ -360,6 +372,8 @@ func migrateDb() { fmt.Println("Migration done!") } +// main is the entry point of the 3x-ui application. +// It parses command-line arguments to run the web server, migrate database, or update settings. func main() { if len(os.Args) < 2 { runWebServer() diff --git a/sub/sub.go b/sub/sub.go index 448842ae..c5445339 100644 --- a/sub/sub.go +++ b/sub/sub.go @@ -1,3 +1,5 @@ +// Package sub provides subscription server functionality for the 3x-ui panel, +// including HTTP/HTTPS servers for serving subscription links and JSON configurations. package sub import ( @@ -39,6 +41,7 @@ func setEmbeddedTemplates(engine *gin.Engine) error { return nil } +// Server represents the subscription server that serves subscription links and JSON configurations. type Server struct { httpServer *http.Server listener net.Listener @@ -50,6 +53,7 @@ type Server struct { cancel context.CancelFunc } +// NewServer creates a new subscription server instance with a cancellable context. func NewServer() *Server { ctx, cancel := context.WithCancel(context.Background()) return &Server{ @@ -58,6 +62,8 @@ func NewServer() *Server { } } +// initRouter configures the subscription server's Gin engine, middleware, +// templates and static assets and returns the ready-to-use engine. func (s *Server) initRouter() (*gin.Engine, error) { // Always run in release mode for the subscription server gin.DefaultWriter = io.Discard @@ -222,6 +228,7 @@ func (s *Server) getHtmlFiles() ([]string, error) { return files, nil } +// Start initializes and starts the subscription server with configured settings. func (s *Server) Start() (err error) { // This is an anonymous function, no function name defer func() { @@ -295,6 +302,7 @@ func (s *Server) Start() (err error) { return nil } +// Stop gracefully shuts down the subscription server and closes the listener. func (s *Server) Stop() error { s.cancel() @@ -309,6 +317,7 @@ func (s *Server) Stop() error { return common.Combine(err1, err2) } +// GetCtx returns the server's context for cancellation and deadline management. func (s *Server) GetCtx() context.Context { return s.ctx } diff --git a/sub/subController.go b/sub/subController.go index d6bc0923..42a33ee6 100644 --- a/sub/subController.go +++ b/sub/subController.go @@ -10,6 +10,7 @@ import ( "github.com/gin-gonic/gin" ) +// SUBController handles HTTP requests for subscription links and JSON configurations. type SUBController struct { subTitle string subPath string @@ -22,6 +23,7 @@ type SUBController struct { subJsonService *SubJsonService } +// NewSUBController creates a new subscription controller with the given configuration. func NewSUBController( g *gin.RouterGroup, subPath string, @@ -53,6 +55,8 @@ func NewSUBController( return a } +// initRouter registers HTTP routes for subscription links and JSON endpoints +// on the provided router group. func (a *SUBController) initRouter(g *gin.RouterGroup) { gLink := g.Group(a.subPath) gLink.GET(":subid", a.subs) @@ -62,6 +66,7 @@ func (a *SUBController) initRouter(g *gin.RouterGroup) { } } +// subs handles HTTP requests for subscription links, returning either HTML page or base64-encoded subscription data. func (a *SUBController) subs(c *gin.Context) { subId := c.Param("subid") scheme, host, hostWithPort, hostHeader := a.subService.ResolveRequest(c) @@ -119,6 +124,7 @@ func (a *SUBController) subs(c *gin.Context) { } } +// subJsons handles HTTP requests for JSON subscription configurations. func (a *SUBController) subJsons(c *gin.Context) { subId := c.Param("subid") _, host, _, _ := a.subService.ResolveRequest(c) @@ -134,6 +140,7 @@ func (a *SUBController) subJsons(c *gin.Context) { } } +// ApplyCommonHeaders sets common HTTP headers for subscription responses including user info, update interval, and profile title. func (a *SUBController) ApplyCommonHeaders(c *gin.Context, header, updateInterval, profileTitle string) { c.Writer.Header().Set("Subscription-Userinfo", header) c.Writer.Header().Set("Profile-Update-Interval", updateInterval) diff --git a/sub/subJsonService.go b/sub/subJsonService.go index d55c7f81..f440ab65 100644 --- a/sub/subJsonService.go +++ b/sub/subJsonService.go @@ -17,6 +17,7 @@ import ( //go:embed default.json var defaultJson string +// SubJsonService handles JSON subscription configuration generation and management. type SubJsonService struct { configJson map[string]any defaultOutbounds []json_util.RawMessage @@ -28,6 +29,7 @@ type SubJsonService struct { SubService *SubService } +// NewSubJsonService creates a new JSON subscription service with the given configuration. func NewSubJsonService(fragment string, noises string, mux string, rules string, subService *SubService) *SubJsonService { var configJson map[string]any var defaultOutbounds []json_util.RawMessage @@ -67,6 +69,7 @@ func NewSubJsonService(fragment string, noises string, mux string, rules string, } } +// GetJson generates a JSON subscription configuration for the given subscription ID and host. func (s *SubJsonService) GetJson(subId string, host string) (string, string, error) { inbounds, err := s.SubService.getInboundsBySubId(subId) if err != nil || len(inbounds) == 0 { diff --git a/sub/subService.go b/sub/subService.go index 206be24e..9f28b35b 100644 --- a/sub/subService.go +++ b/sub/subService.go @@ -20,6 +20,7 @@ import ( "github.com/mhsanaei/3x-ui/v2/xray" ) +// SubService provides business logic for generating subscription links and managing subscription data. type SubService struct { address string showInfo bool @@ -29,6 +30,7 @@ type SubService struct { settingService service.SettingService } +// NewSubService creates a new subscription service with the given configuration. func NewSubService(showInfo bool, remarkModel string) *SubService { return &SubService{ showInfo: showInfo, @@ -36,6 +38,7 @@ func NewSubService(showInfo bool, remarkModel string) *SubService { } } +// GetSubs retrieves subscription links for a given subscription ID and host. func (s *SubService) GetSubs(subId string, host string) ([]string, int64, xray.ClientTraffic, error) { s.address = host var result []string @@ -1008,6 +1011,7 @@ func searchHost(headers any) string { } // PageData is a view model for subpage.html +// PageData contains data for rendering the subscription information page. type PageData struct { Host string BasePath string @@ -1029,6 +1033,7 @@ type PageData struct { } // ResolveRequest extracts scheme and host info from request/headers consistently. +// ResolveRequest extracts scheme, host, and header information from an HTTP request. func (s *SubService) ResolveRequest(c *gin.Context) (scheme string, host string, hostWithPort string, hostHeader string) { // scheme scheme = "http" @@ -1072,6 +1077,7 @@ func (s *SubService) ResolveRequest(c *gin.Context) (scheme string, host string, } // BuildURLs constructs absolute subscription and json URLs. +// BuildURLs constructs subscription and JSON subscription URLs for a given subscription ID. func (s *SubService) BuildURLs(scheme, hostWithPort, subPath, subJsonPath, subId string) (subURL, subJsonURL string) { if strings.HasSuffix(subPath, "/") { subURL = scheme + "://" + hostWithPort + subPath + subId @@ -1087,6 +1093,7 @@ func (s *SubService) BuildURLs(scheme, hostWithPort, subPath, subJsonPath, subId } // BuildPageData parses header and prepares the template view model. +// BuildPageData constructs page data for rendering the subscription information page. func (s *SubService) BuildPageData(subId string, hostHeader string, traffic xray.ClientTraffic, lastOnline int64, subs []string, subURL, subJsonURL string) PageData { download := common.FormatTraffic(traffic.Down) upload := common.FormatTraffic(traffic.Up) diff --git a/util/common/err.go b/util/common/err.go index 85a743ad..e12bd13f 100644 --- a/util/common/err.go +++ b/util/common/err.go @@ -1,3 +1,4 @@ +// Package common provides common utility functions for error handling, formatting, and multi-error management. package common import ( @@ -7,16 +8,19 @@ import ( "github.com/mhsanaei/3x-ui/v2/logger" ) +// NewErrorf creates a new error with formatted message. func NewErrorf(format string, a ...any) error { msg := fmt.Sprintf(format, a...) return errors.New(msg) } +// NewError creates a new error from the given arguments. func NewError(a ...any) error { msg := fmt.Sprintln(a...) return errors.New(msg) } +// Recover handles panic recovery and logs the panic error if a message is provided. func Recover(msg string) any { panicErr := recover() if panicErr != nil { diff --git a/util/common/format.go b/util/common/format.go index c73e3a01..c40bd3dc 100644 --- a/util/common/format.go +++ b/util/common/format.go @@ -4,6 +4,7 @@ import ( "fmt" ) +// FormatTraffic formats traffic bytes into human-readable units (B, KB, MB, GB, TB, PB). func FormatTraffic(trafficBytes int64) string { units := []string{"B", "KB", "MB", "GB", "TB", "PB"} unitIndex := 0 diff --git a/util/common/multi_error.go b/util/common/multi_error.go index ff9ff628..c695e3c0 100644 --- a/util/common/multi_error.go +++ b/util/common/multi_error.go @@ -4,8 +4,10 @@ import ( "strings" ) +// multiError represents a collection of errors. type multiError []error +// Error returns a string representation of all errors joined with " | ". func (e multiError) Error() string { var r strings.Builder r.WriteString("multierr: ") @@ -16,6 +18,7 @@ func (e multiError) Error() string { return r.String() } +// Combine combines multiple errors into a single error, filtering out nil errors. func Combine(maybeError ...error) error { var errs multiError for _, err := range maybeError { diff --git a/util/crypto/crypto.go b/util/crypto/crypto.go index f600e7a6..05d088a8 100644 --- a/util/crypto/crypto.go +++ b/util/crypto/crypto.go @@ -1,14 +1,17 @@ +// Package crypto provides cryptographic utilities for password hashing and verification. package crypto import ( "golang.org/x/crypto/bcrypt" ) +// HashPasswordAsBcrypt generates a bcrypt hash of the given password. func HashPasswordAsBcrypt(password string) (string, error) { hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) return string(hash), err } +// CheckPasswordHash verifies if the given password matches the bcrypt hash. func CheckPasswordHash(hash, password string) bool { err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) return err == nil diff --git a/util/json_util/json.go b/util/json_util/json.go index 54e3728a..d2d391bf 100644 --- a/util/json_util/json.go +++ b/util/json_util/json.go @@ -1,12 +1,15 @@ +// Package json_util provides JSON utilities including a custom RawMessage type. package json_util import ( "errors" ) +// RawMessage is a custom JSON raw message type that marshals empty slices as "null". type RawMessage []byte -// MarshalJSON: Customize json.RawMessage default behavior +// MarshalJSON customizes the JSON marshaling behavior for RawMessage. +// Empty RawMessage values are marshaled as "null" instead of "[]". func (m RawMessage) MarshalJSON() ([]byte, error) { if len(m) == 0 { return []byte("null"), nil @@ -14,7 +17,7 @@ func (m RawMessage) MarshalJSON() ([]byte, error) { return m, nil } -// UnmarshalJSON: sets *m to a copy of data. +// UnmarshalJSON sets *m to a copy of the JSON data. func (m *RawMessage) UnmarshalJSON(data []byte) error { if m == nil { return errors.New("json.RawMessage: UnmarshalJSON on nil pointer") diff --git a/util/random/random.go b/util/random/random.go index 67ee0691..9610e26c 100644 --- a/util/random/random.go +++ b/util/random/random.go @@ -1,3 +1,4 @@ +// Package random provides utilities for generating random strings and numbers. package random import ( @@ -13,6 +14,8 @@ var ( allSeq [62]rune ) +// init initializes the character sequences used for random string generation. +// It sets up arrays for numbers, lowercase letters, uppercase letters, and combinations. func init() { for i := 0; i < 10; i++ { numSeq[i] = rune('0' + i) @@ -33,6 +36,7 @@ func init() { copy(allSeq[len(numSeq)+len(lowerSeq):], upperSeq[:]) } +// Seq generates a random string of length n containing alphanumeric characters (numbers, lowercase and uppercase letters). func Seq(n int) string { runes := make([]rune, n) for i := 0; i < n; i++ { @@ -41,6 +45,7 @@ func Seq(n int) string { return string(runes) } +// Num generates a random integer between 0 and n-1. func Num(n int) int { return rand.Intn(n) } diff --git a/util/reflect_util/reflect.go b/util/reflect_util/reflect.go index 1fdaec50..1f557e0d 100644 --- a/util/reflect_util/reflect.go +++ b/util/reflect_util/reflect.go @@ -1,7 +1,9 @@ +// Package reflect_util provides reflection utilities for working with struct fields and values. package reflect_util import "reflect" +// GetFields returns all struct fields of the given reflect.Type. func GetFields(t reflect.Type) []reflect.StructField { num := t.NumField() fields := make([]reflect.StructField, 0, num) @@ -11,6 +13,7 @@ func GetFields(t reflect.Type) []reflect.StructField { return fields } +// GetFieldValues returns all field values of the given reflect.Value. func GetFieldValues(v reflect.Value) []reflect.Value { num := v.NumField() fields := make([]reflect.Value, 0, num) diff --git a/util/sys/psutil.go b/util/sys/psutil.go index 3d7cac80..98adf775 100644 --- a/util/sys/psutil.go +++ b/util/sys/psutil.go @@ -1,3 +1,5 @@ +// Package sys provides system utilities for monitoring network connections and CPU usage. +// Platform-specific implementations are provided for Windows, Linux, and macOS. package sys import ( diff --git a/util/sys/sys_linux.go b/util/sys/sys_linux.go index 8a494d62..23483b57 100644 --- a/util/sys/sys_linux.go +++ b/util/sys/sys_linux.go @@ -45,6 +45,8 @@ func getLinesNum(filename string) (int, error) { return sum, nil } +// GetTCPCount returns the number of active TCP connections by reading +// /proc/net/tcp and /proc/net/tcp6 when available. func GetTCPCount() (int, error) { root := HostProc() @@ -75,6 +77,8 @@ func GetUDPCount() (int, error) { return udp4 + udp6, nil } +// safeGetLinesNum returns 0 if the file does not exist, otherwise forwards +// to getLinesNum to count the number of lines. func safeGetLinesNum(path string) (int, error) { if _, err := os.Stat(path); os.IsNotExist(err) { return 0, nil diff --git a/util/sys/sys_windows.go b/util/sys/sys_windows.go index f3eae076..186fa4bb 100644 --- a/util/sys/sys_windows.go +++ b/util/sys/sys_windows.go @@ -12,6 +12,7 @@ import ( "github.com/shirou/gopsutil/v4/net" ) +// GetConnectionCount returns the number of active connections for the specified protocol ("tcp" or "udp"). func GetConnectionCount(proto string) (int, error) { if proto != "tcp" && proto != "udp" { return 0, errors.New("invalid protocol") @@ -24,10 +25,12 @@ func GetConnectionCount(proto string) (int, error) { return len(stats), nil } +// GetTCPCount returns the number of active TCP connections. func GetTCPCount() (int, error) { return GetConnectionCount("tcp") } +// GetUDPCount returns the number of active UDP connections. func GetUDPCount() (int, error) { return GetConnectionCount("udp") } @@ -50,6 +53,8 @@ type filetime struct { HighDateTime uint32 } +// ftToUint64 converts a Windows FILETIME-like struct to a uint64 for +// arithmetic and delta calculations used by CPUPercentRaw. func ftToUint64(ft filetime) uint64 { return (uint64(ft.HighDateTime) << 32) | uint64(ft.LowDateTime) } 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.Contex