docs: add comments for all functions

This commit is contained in:
mhsanaei
2025-09-20 09:35:50 +02:00
parent f60682a6b7
commit 6ced549dea
63 changed files with 624 additions and 113 deletions

View File

@@ -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 {

View File

@@ -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) {

View File

@@ -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 {

View File

@@ -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

View File

@@ -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) {

View File

@@ -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 LoginStatus = 0 // Login failed
EmptyTelegramUserID = int64(0) // Default value for empty Telegram user ID
)
// Tgbot provides business logic for Telegram bot integration.
// It handles bot commands, user interactions, and status reporting via Telegram.
type Tgbot struct {
inboundService InboundService
settingService SettingService
@@ -81,18 +85,22 @@ type Tgbot struct {
lastStatus *Status
}
// NewTgbot creates a new Tgbot instance.
func (t *Tgbot) NewTgbot() *Tgbot {
return new(Tgbot)
}
// I18nBot retrieves a localized message for the bot interface.
func (t *Tgbot) I18nBot(name string, params ...string) string {
return locale.I18n(locale.Bot, name, params...)
}
// GetHashStorage returns the hash storage instance for callback queries.
func (t *Tgbot) GetHashStorage() *global.HashStorage {
return hashStorage
}
// Start initializes and starts the Telegram bot with the provided translation files.
func (t *Tgbot) Start(i18nFS embed.FS) error {
// Initialize localizer
err := locale.InitLocalizer(i18nFS, &t.settingService)
@@ -173,6 +181,7 @@ func (t *Tgbot) Start(i18nFS embed.FS) error {
return nil
}
// NewBot creates a new Telegram bot instance with optional proxy and API server settings.
func (t *Tgbot) NewBot(token string, proxyUrl string, apiServerUrl string) (*telego.Bot, error) {
if proxyUrl == "" && apiServerUrl == "" {
return telego.NewBot(token)
@@ -209,10 +218,12 @@ func (t *Tgbot) NewBot(token string, proxyUrl string, apiServerUrl string) (*tel
return telego.NewBot(token, telego.WithAPIServer(apiServerUrl))
}
// IsRunning checks if the Telegram bot is currently running.
func (t *Tgbot) IsRunning() bool {
return isRunning
}
// SetHostname sets the hostname for the bot.
func (t *Tgbot) SetHostname() {
host, err := os.Hostname()
if err != nil {
@@ -223,6 +234,7 @@ func (t *Tgbot) SetHostname() {
hostname = host
}
// Stop stops the Telegram bot and cleans up resources.
func (t *Tgbot) Stop() {
if botHandler != nil {
botHandler.Stop()
@@ -232,6 +244,7 @@ func (t *Tgbot) Stop() {
adminIds = nil
}
// encodeQuery encodes the query string if it's longer than 64 characters.
func (t *Tgbot) encodeQuery(query string) string {
// NOTE: we only need to hash for more than 64 chars
if len(query) <= 64 {
@@ -241,6 +254,7 @@ func (t *Tgbot) encodeQuery(query string) string {
return hashStorage.SaveHash(query)
}
// decodeQuery decodes a hashed query string back to its original form.
func (t *Tgbot) decodeQuery(query string) (string, error) {
if !hashStorage.IsMD5(query) {
return query, nil
@@ -254,6 +268,7 @@ func (t *Tgbot) decodeQuery(query string) (string, error) {
return decoded, nil
}
// OnReceive starts the message receiving loop for the Telegram bot.
func (t *Tgbot) OnReceive() {
params := telego.GetUpdatesParams{
Timeout: 10,
@@ -430,6 +445,7 @@ func (t *Tgbot) OnReceive() {
botHandler.Start()
}
// answerCommand processes incoming command messages from Telegram users.
func (t *Tgbot) answerCommand(message *telego.Message, chatId int64, isAdmin bool) {
msg, onlyMessage := "", false
@@ -505,7 +521,7 @@ func (t *Tgbot) answerCommand(message *telego.Message, chatId int64, isAdmin boo
}
}
// Helper function to send the message based on onlyMessage flag.
// sendResponse sends the response message based on the onlyMessage flag.
func (t *Tgbot) sendResponse(chatId int64, msg string, onlyMessage, isAdmin bool) {
if onlyMessage {
t.SendMsgToTgbot(chatId, msg)
@@ -514,6 +530,7 @@ func (t *Tgbot) sendResponse(chatId int64, msg string, onlyMessage, isAdmin bool
}
}
// randomLowerAndNum generates a random string of lowercase letters and numbers.
func (t *Tgbot) randomLowerAndNum(length int) string {
charset := "abcdefghijklmnopqrstuvwxyz0123456789"
bytes := make([]byte, length)
@@ -524,6 +541,7 @@ func (t *Tgbot) randomLowerAndNum(length int) string {
return string(bytes)
}
// randomShadowSocksPassword generates a random password for Shadowsocks.
func (t *Tgbot) randomShadowSocksPassword() string {
array := make([]byte, 32)
_, err := rand.Read(array)
@@ -533,6 +551,7 @@ func (t *Tgbot) randomShadowSocksPassword() string {
return base64.StdEncoding.EncodeToString(array)
}
// answerCallback processes callback queries from inline keyboards.
func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool) {
chatId := callbackQuery.Message.GetChat().ID
@@ -1815,6 +1834,7 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
}
}
// BuildInboundClientDataMessage builds a message with client data for the given inbound and protocol.
func (t *Tgbot) BuildInboundClientDataMessage(inbound_remark string, protocol model.Protocol) (string, error) {
var message string
@@ -1864,6 +1884,7 @@ func (t *Tgbot) BuildInboundClientDataMessage(inbound_remark string, protocol mo
return message, nil
}
// BuildJSONForProtocol builds a JSON string for the given protocol with client data.
func (t *Tgbot) BuildJSONForProtocol(protocol model.Protocol) (string, error) {
var jsonString string
@@ -1942,6 +1963,7 @@ func (t *Tgbot) BuildJSONForProtocol(protocol model.Protocol) (string, error) {
return jsonString, nil
}
// SubmitAddClient submits the client addition request to the inbound service.
func (t *Tgbot) SubmitAddClient() (bool, error) {
inbound, err := t.inboundService.GetInbound(receiver_inbound_ID)
@@ -1964,6 +1986,7 @@ func (t *Tgbot) SubmitAddClient() (bool, error) {
return t.inboundService.AddInboundClient(newInbound)
}
// checkAdmin checks if the given Telegram ID is an admin.
func checkAdmin(tgId int64) bool {
for _, adminId := range adminIds {
if adminId == tgId {
@@ -1973,6 +1996,7 @@ func checkAdmin(tgId int64) bool {
return false
}
// SendAnswer sends a response message with an inline keyboard to the specified chat.
func (t *Tgbot) SendAnswer(chatId int64, msg string, isAdmin bool) {
numericKeyboard := tu.InlineKeyboard(
tu.InlineKeyboardRow(
@@ -2028,6 +2052,7 @@ func (t *Tgbot) SendAnswer(chatId int64, msg string, isAdmin bool) {
t.SendMsgToTgbot(chatId, msg, ReplyMarkup)
}
// SendMsgToTgbot sends a message to the Telegram bot with optional reply markup.
func (t *Tgbot) SendMsgToTgbot(chatId int64, msg string, replyMarkup ...telego.ReplyMarkup) {
if !isRunning {
return
@@ -2143,6 +2168,7 @@ func (t *Tgbot) buildSubscriptionURLs(email string) (string, string, error) {
return subURL, subJsonURL, nil
}
// sendClientSubLinks sends the subscription links for the client to the chat.
func (t *Tgbot) sendClientSubLinks(chatId int64, email string) {
subURL, subJsonURL, err := t.buildSubscriptionURLs(email)
if err != nil {
@@ -2338,6 +2364,7 @@ func (t *Tgbot) sendClientQRLinks(chatId int64, email string) {
}
}
// SendMsgToTgbotAdmins sends a message to all admin Telegram chats.
func (t *Tgbot) SendMsgToTgbotAdmins(msg string, replyMarkup ...telego.ReplyMarkup) {
if len(replyMarkup) > 0 {
for _, adminId := range adminIds {
@@ -2350,6 +2377,7 @@ func (t *Tgbot) SendMsgToTgbotAdmins(msg string, replyMarkup ...telego.ReplyMark
}
}
// SendReport sends a periodic report to admin chats.
func (t *Tgbot) SendReport() {
runTime, err := t.settingService.GetTgbotRuntime()
if err == nil && len(runTime) > 0 {
@@ -2371,6 +2399,7 @@ func (t *Tgbot) SendReport() {
}
}
// SendBackupToAdmins sends a database backup to admin chats.
func (t *Tgbot) SendBackupToAdmins() {
if !t.IsRunning() {
return
@@ -2380,6 +2409,7 @@ func (t *Tgbot) SendBackupToAdmins() {
}
}
// sendExhaustedToAdmins sends notifications about exhausted clients to admins.
func (t *Tgbot) sendExhaustedToAdmins() {
if !t.IsRunning() {
return
@@ -2389,6 +2419,7 @@ func (t *Tgbot) sendExhaustedToAdmins() {
}
}
// getServerUsage retrieves and formats server usage information.
func (t *Tgbot) getServerUsage(chatId int64, messageID ...int) string {
info := t.prepareServerUsageInfo()
@@ -2410,6 +2441,7 @@ func (t *Tgbot) sendServerUsage() string {
return info
}
// prepareServerUsageInfo prepares the server usage information string.
func (t *Tgbot) prepareServerUsageInfo() string {
info, ipv4, ipv6 := "", "", ""
@@ -2459,6 +2491,7 @@ func (t *Tgbot) prepareServerUsageInfo() string {
return info
}
// UserLoginNotify sends a notification about user login attempts to admins.
func (t *Tgbot) UserLoginNotify(username string, password string, ip string, time string, status LoginStatus) {
if !t.IsRunning() {
return
@@ -2490,6 +2523,7 @@ func (t *Tgbot) UserLoginNotify(username string, password string, ip string, tim
t.SendMsgToTgbotAdmins(msg)
}
// getInboundUsages retrieves and formats inbound usage information.
func (t *Tgbot) getInboundUsages() string {
info := ""
// get traffic
@@ -2515,6 +2549,8 @@ func (t *Tgbot) getInboundUsages() string {
}
return info
}
// getInbounds creates an inline keyboard with all inbounds.
func (t *Tgbot) getInbounds() (*telego.InlineKeyboardMarkup, error) {
inbounds, err := t.inboundService.GetAllInbounds()
if err != nil {
@@ -2546,8 +2582,7 @@ func (t *Tgbot) getInbounds() (*telego.InlineKeyboardMarkup, error) {
return keyboard, nil
}
// getInboundsFor builds an inline keyboard of inbounds where each button leads to a custom next action
// nextAction should be one of: get_clients_for_sub|get_clients_for_individual|get_clients_for_qr
// getInboundsFor builds an inline keyboard of inbounds for a custom next action.
func (t *Tgbot) getInboundsFor(nextAction string) (*telego.InlineKeyboardMarkup, error) {
inbounds, err := t.inboundService.GetAllInbounds()
if err != nil {
@@ -2614,6 +2649,7 @@ func (t *Tgbot) getInboundClientsFor(inboundID int, action string) (*telego.Inli
return keyboard, nil
}
// getInboundsAddClient creates an inline keyboard for adding clients to inbounds.
func (t *Tgbot) getInboundsAddClient() (*telego.InlineKeyboardMarkup, error) {
inbounds, err := t.inboundService.GetAllInbounds()
if err != nil {
@@ -2656,6 +2692,7 @@ func (t *Tgbot) getInboundsAddClient() (*telego.InlineKeyboardMarkup, error) {
return keyboard, nil
}
// getInboundClients creates an inline keyboard with clients of a specific inbound.
func (t *Tgbot) getInboundClients(id int) (*telego.InlineKeyboardMarkup, error) {
inbound, err := t.inboundService.GetInbound(id)
if err != nil {
@@ -2690,6 +2727,7 @@ func (t *Tgbot) getInboundClients(id int) (*telego.InlineKeyboardMarkup, error)
return keyboard, nil
}
// clientInfoMsg formats client information message based on traffic and flags.
func (t *Tgbot) clientInfoMsg(
traffic *xray.ClientTraffic,
printEnabled bool,
@@ -2796,6 +2834,7 @@ func (t *Tgbot) clientInfoMsg(
return output
}
// getClientUsage retrieves and sends client usage information to the chat.
func (t *Tgbot) getClientUsage(chatId int64, tgUserID int64, email ...string) {
traffics, err := t.inboundService.GetClientTrafficTgBot(tgUserID)
if err != nil {
@@ -2838,6 +2877,7 @@ func (t *Tgbot) getClientUsage(chatId int64, tgUserID int64, email ...string) {
t.SendAnswer(chatId, output, false)
}
// searchClientIps searches and sends client IP addresses for the given email.
func (t *Tgbot) searchClientIps(chatId int64, email string, messageID ...int) {
ips, err := t.inboundService.GetInboundClientIps(email)
if err != nil || len(ips) == 0 {
@@ -2865,6 +2905,7 @@ func (t *Tgbot) searchClientIps(chatId int64, email string, messageID ...int) {
}
}
// clientTelegramUserInfo retrieves and sends Telegram user info for the client.
func (t *Tgbot) clientTelegramUserInfo(chatId int64, email string, messageID ...int) {
traffic, client, err := t.inboundService.GetClientByEmail(email)
if err != nil {
@@ -2917,6 +2958,7 @@ func (t *Tgbot) clientTelegramUserInfo(chatId int64, email string, messageID ...
}
}
// searchClient searches for a client by email and sends the information.
func (t *Tgbot) searchClient(chatId int64, email string, messageID ...int) {
traffic, err := t.inboundService.GetClientTrafficByEmail(email)
if err != nil {
@@ -2962,6 +3004,7 @@ func (t *Tgbot) searchClient(chatId int64, email string, messageID ...int) {
}
}
// addClient handles the process of adding a new client to an inbound.
func (t *Tgbot) addClient(chatId int64, msg string, messageID ...int) {
inbound, err := t.inboundService.GetInbound(receiver_inbound_ID)
if err != nil {
@@ -3058,6 +3101,7 @@ func (t *Tgbot) addClient(chatId int64, msg string, messageID ...int) {
}
// searchInbound searches for inbounds by remark and sends the results.
func (t *Tgbot) searchInbound(chatId int64, remark string) {
inbounds, err := t.inboundService.SearchInbounds(remark)
if err != nil {
@@ -3095,6 +3139,7 @@ func (t *Tgbot) searchInbound(chatId int64, remark string) {
}
}
// getExhausted retrieves and sends information about exhausted clients.
func (t *Tgbot) getExhausted(chatId int64) {
trDiff := int64(0)
exDiff := int64(0)
@@ -3191,6 +3236,7 @@ func (t *Tgbot) getExhausted(chatId int64) {
}
}
// notifyExhausted sends notifications for exhausted clients.
func (t *Tgbot) notifyExhausted() {
trDiff := int64(0)
exDiff := int64(0)
@@ -3262,6 +3308,7 @@ func (t *Tgbot) notifyExhausted() {
}
}
// int64Contains checks if an int64 slice contains a specific item.
func int64Contains(slice []int64, item int64) bool {
for _, s := range slice {
if s == item {
@@ -3271,6 +3318,7 @@ func int64Contains(slice []int64, item int64) bool {
return false
}
// onlineClients retrieves and sends information about online clients.
func (t *Tgbot) onlineClients(chatId int64, messageID ...int) {
if !p.IsRunning() {
return
@@ -3305,6 +3353,7 @@ func (t *Tgbot) onlineClients(chatId int64, messageID ...int) {
}
}
// sendBackup sends a backup of the database and configuration files.
func (t *Tgbot) sendBackup(chatId int64) {
output := t.I18nBot("tgbot.messages.backupTime", "Time=="+time.Now().Format("2006-01-02 15:04:05"))
t.SendMsgToTgbot(chatId, output)
@@ -3344,6 +3393,7 @@ func (t *Tgbot) sendBackup(chatId int64) {
}
}
// sendBanLogs sends the ban logs to the specified chat.
func (t *Tgbot) sendBanLogs(chatId int64, dt bool) {
if dt {
output := t.I18nBot("tgbot.messages.datetime", "DateTime=="+time.Now().Format("2006-01-02 15:04:05"))
@@ -3393,6 +3443,7 @@ func (t *Tgbot) sendBanLogs(chatId int64, dt bool) {
}
}
// sendCallbackAnswerTgBot answers a callback query with a message.
func (t *Tgbot) sendCallbackAnswerTgBot(id string, message string) {
params := telego.AnswerCallbackQueryParams{
CallbackQueryID: id,
@@ -3403,6 +3454,7 @@ func (t *Tgbot) sendCallbackAnswerTgBot(id string, message string) {
}
}
// editMessageCallbackTgBot edits the reply markup of a message.
func (t *Tgbot) editMessageCallbackTgBot(chatId int64, messageID int, inlineKeyboard *telego.InlineKeyboardMarkup) {
params := telego.EditMessageReplyMarkupParams{
ChatID: tu.ID(chatId),
@@ -3414,6 +3466,7 @@ func (t *Tgbot) editMessageCallbackTgBot(chatId int64, messageID int, inlineKeyb
}
}
// editMessageTgBot edits the text and reply markup of a message.
func (t *Tgbot) editMessageTgBot(chatId int64, messageID int, text string, inlineKeyboard ...*telego.InlineKeyboardMarkup) {
params := telego.EditMessageTextParams{
ChatID: tu.ID(chatId),
@@ -3429,6 +3482,7 @@ func (t *Tgbot) editMessageTgBot(chatId int64, messageID int, text string, inlin
}
}
// SendMsgToTgbotDeleteAfter sends a message and deletes it after a specified delay.
func (t *Tgbot) SendMsgToTgbotDeleteAfter(chatId int64, msg string, delayInSeconds int, replyMarkup ...telego.ReplyMarkup) {
// Determine if replyMarkup was passed; otherwise, set it to nil
var replyMarkupParam telego.ReplyMarkup
@@ -3455,6 +3509,7 @@ func (t *Tgbot) SendMsgToTgbotDeleteAfter(chatId int64, msg string, delayInSecon
}()
}
// deleteMessageTgBot deletes a message from the chat.
func (t *Tgbot) deleteMessageTgBot(chatId int64, messageID int) {
params := telego.DeleteMessageParams{
ChatID: tu.ID(chatId),
@@ -3467,6 +3522,7 @@ func (t *Tgbot) deleteMessageTgBot(chatId int64, messageID int) {
}
}
// isSingleWord checks if the text contains only a single word.
func (t *Tgbot) isSingleWord(text string) bool {
text = strings.TrimSpace(text)
re := regexp.MustCompile(`\s+`)

View File

@@ -12,10 +12,14 @@ import (
"gorm.io/gorm"
)
// UserService provides business logic for user management and authentication.
// It handles user creation, login, password management, and 2FA operations.
type UserService struct {
settingService SettingService
}
// GetFirstUser retrieves the first user from the database.
// This is typically used for initial setup or when there's only one admin user.
func (s *UserService) GetFirstUser() (*model.User, error) {
db := database.GetDB()

View File

@@ -12,6 +12,8 @@ import (
"github.com/mhsanaei/3x-ui/v2/util/common"
)
// WarpService provides business logic for Cloudflare WARP integration.
// It manages WARP configuration and connectivity settings.
type WarpService struct {
SettingService
}

View File

@@ -20,16 +20,20 @@ var (
result string
)
// XrayService provides business logic for Xray process management.
// It handles starting, stopping, restarting Xray, and managing its configuration.
type XrayService struct {
inboundService InboundService
settingService SettingService
xrayAPI xray.XrayAPI
}
// IsXrayRunning checks if the Xray process is currently running.
func (s *XrayService) IsXrayRunning() bool {
return p != nil && p.IsRunning()
}
// GetXrayErr returns the error from the Xray process, if any.
func (s *XrayService) GetXrayErr() error {
if p == nil {
return nil
@@ -46,6 +50,7 @@ func (s *XrayService) GetXrayErr() error {
return err
}
// GetXrayResult returns the result string from the Xray process.
func (s *XrayService) GetXrayResult() string {
if result != "" {
return result
@@ -68,6 +73,7 @@ func (s *XrayService) GetXrayResult() string {
return result
}
// GetXrayVersion returns the version of the running Xray process.
func (s *XrayService) GetXrayVersion() string {
if p == nil {
return "Unknown"
@@ -75,10 +81,13 @@ func (s *XrayService) GetXrayVersion() string {
return p.GetVersion()
}
// RemoveIndex removes an element at the specified index from a slice.
// Returns a new slice with the element removed.
func RemoveIndex(s []any, index int) []any {
return append(s[:index], s[index+1:]...)
}
// GetXrayConfig retrieves and builds the Xray configuration from settings and inbounds.
func (s *XrayService) GetXrayConfig() (*xray.Config, error) {
templateConfig, err := s.settingService.GetXrayConfigTemplate()
if err != nil {
@@ -182,6 +191,7 @@ func (s *XrayService) GetXrayConfig() (*xray.Config, error) {
return xrayConfig, nil
}
// GetXrayTraffic fetches the current traffic statistics from the running Xray process.
func (s *XrayService) GetXrayTraffic() ([]*xray.Traffic, []*xray.ClientTraffic, error) {
if !s.IsXrayRunning() {
err := errors.New("xray is not running")
@@ -200,6 +210,7 @@ func (s *XrayService) GetXrayTraffic() ([]*xray.Traffic, []*xray.ClientTraffic,
return traffic, clientTraffic, nil
}
// RestartXray restarts the Xray process, optionally forcing a restart even if config unchanged.
func (s *XrayService) RestartXray(isForce bool) error {
lock.Lock()
defer lock.Unlock()
@@ -229,6 +240,7 @@ func (s *XrayService) RestartXray(isForce bool) error {
return nil
}
// StopXray stops the running Xray process.
func (s *XrayService) StopXray() error {
lock.Lock()
defer lock.Unlock()
@@ -240,15 +252,17 @@ func (s *XrayService) StopXray() error {
return errors.New("xray is not running")
}
// SetToNeedRestart marks that Xray needs to be restarted.
func (s *XrayService) SetToNeedRestart() {
isNeedXrayRestart.Store(true)
}
// IsNeedRestartAndSetFalse checks if restart is needed and resets the flag to false.
func (s *XrayService) IsNeedRestartAndSetFalse() bool {
return isNeedXrayRestart.CompareAndSwap(true, false)
}
// Check if Xray is not running and wasn't stopped manually, i.e. crashed
// DidXrayCrash checks if Xray crashed by verifying it's not running and wasn't manually stopped.
func (s *XrayService) DidXrayCrash() bool {
return !s.IsXrayRunning() && !isManuallyStopped.Load()
}

View File

@@ -8,6 +8,8 @@ import (
"github.com/mhsanaei/3x-ui/v2/xray"
)
// XraySettingService provides business logic for Xray configuration management.
// It handles validation and storage of Xray template configurations.
type XraySettingService struct {
SettingService
}