diff --git a/database/db.go b/database/db.go index c72d28cf..484f1758 100644 --- a/database/db.go +++ b/database/db.go @@ -35,6 +35,7 @@ func initModels() error { &model.InboundClientIps{}, &xray.ClientTraffic{}, &model.HistoryOfSeeders{}, + &model.Server{}, } for _, model := range models { if err := db.AutoMigrate(model); err != nil { diff --git a/database/model/model.go b/database/model/model.go index 2e7095d3..68d56593 100644 --- a/database/model/model.go +++ b/database/model/model.go @@ -105,3 +105,12 @@ type Client struct { Comment string `json:"comment" form:"comment"` Reset int `json:"reset" form:"reset"` } + +type Server struct { + Id int `json:"id" gorm:"primaryKey;autoIncrement"` + Name string `json:"name" gorm:"unique;not null"` + Address string `json:"address" gorm:"not null"` + Port int `json:"port" gorm:"not null"` + APIKey string `json:"apiKey" gorm:"not null"` + Enable bool `json:"enable" gorm:"default:true"` +} diff --git a/go.mod b/go.mod index 32b6856c..4d18f4b8 100644 --- a/go.mod +++ b/go.mod @@ -61,6 +61,7 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pires/go-proxyproto v0.8.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/quic-go/qpack v0.5.1 // indirect github.com/quic-go/quic-go v0.54.0 // indirect diff --git a/install.sh b/install.sh index ce59abe6..0c5ff06d 100644 --- a/install.sh +++ b/install.sh @@ -130,6 +130,13 @@ config_after_install() { fi /usr/local/x-ui/x-ui migrate + + local existing_apiKey=$(/usr/local/x-ui/x-ui setting -show true | grep -oP 'ApiKey: \K.*') + if [[ -z "$existing_apiKey" ]]; then + local config_apiKey=$(gen_random_string 32) + /usr/local/x-ui/x-ui setting -apiKey "${config_apiKey}" + echo -e "${green}Generated random API Key: ${config_apiKey}${plain}" + fi } install_x-ui() { diff --git a/main.go b/main.go index 9986ede1..83739ebf 100644 --- a/main.go +++ b/main.go @@ -232,7 +232,7 @@ func updateTgbotSetting(tgBotToken string, tgBotChatid string, tgBotRuntime stri } } -func updateSetting(port int, username string, password string, webBasePath string, listenIP string, resetTwoFactor bool) { +func updateSetting(port int, username string, password string, webBasePath string, listenIP string, resetTwoFactor bool, apiKey string) { err := database.InitDB(config.GetDBPath()) if err != nil { fmt.Println("Database initialization failed:", err) @@ -242,6 +242,15 @@ func updateSetting(port int, username string, password string, webBasePath strin settingService := service.SettingService{} userService := service.UserService{} + if apiKey != "" { + err := settingService.SetAPIKey(apiKey) + if err != nil { + fmt.Println("Failed to set API Key:", err) + } else { + fmt.Printf("API Key set successfully: %v\n", apiKey) + } + } + if port > 0 { err := settingService.SetPort(port) if err != nil { @@ -388,9 +397,11 @@ func main() { var show bool var getCert bool var resetTwoFactor bool + var apiKey string settingCmd.BoolVar(&reset, "reset", false, "Reset all settings") settingCmd.BoolVar(&show, "show", false, "Display current settings") settingCmd.IntVar(&port, "port", 0, "Set panel port number") + settingCmd.StringVar(&apiKey, "apiKey", "", "Set API Key") settingCmd.StringVar(&username, "username", "", "Set login username") settingCmd.StringVar(&password, "password", "", "Set login password") settingCmd.StringVar(&webBasePath, "webBasePath", "", "Set base path for Panel") @@ -440,7 +451,7 @@ func main() { if reset { resetSetting() } else { - updateSetting(port, username, password, webBasePath, listenIP, resetTwoFactor) + updateSetting(port, username, password, webBasePath, listenIP, resetTwoFactor, apiKey) } if show { showSetting(show) diff --git a/sub/subService.go b/sub/subService.go index dfb0863e..81d0f9fb 100644 --- a/sub/subService.go +++ b/sub/subService.go @@ -154,26 +154,43 @@ func (s *SubService) getFallbackMaster(dest string, streamSettings string) (stri } func (s *SubService) getLink(inbound *model.Inbound, email string) string { - switch inbound.Protocol { - case "vmess": - return s.genVmessLink(inbound, email) - case "vless": - return s.genVlessLink(inbound, email) - case "trojan": - return s.genTrojanLink(inbound, email) - case "shadowsocks": - return s.genShadowsocksLink(inbound, email) + serverService := service.MultiServerService{} + servers, err := serverService.GetServers() + if err != nil { + logger.Warning("Failed to get servers for subscription:", err) + return "" } - return "" + + var links []string + for _, server := range servers { + if !server.Enable { + continue + } + var link string + switch inbound.Protocol { + case "vmess": + link = s.genVmessLink(inbound, email, server) + case "vless": + link = s.genVlessLink(inbound, email, server) + case "trojan": + link = s.genTrojanLink(inbound, email, server) + case "shadowsocks": + link = s.genShadowsocksLink(inbound, email, server) + } + if link != "" { + links = append(links, link) + } + } + return strings.Join(links, "\n") } -func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string { +func (s *SubService) genVmessLink(inbound *model.Inbound, email string, server *model.Server) string { if inbound.Protocol != model.VMESS { return "" } obj := map[string]any{ "v": "2", - "add": s.address, + "add": server.Address, "port": inbound.Port, "type": "none", } @@ -286,7 +303,7 @@ func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string { newObj[key] = value } } - newObj["ps"] = s.genRemark(inbound, email, ep["remark"].(string)) + newObj["ps"] = s.genRemark(inbound, email, ep["remark"].(string), server.Name) newObj["add"] = ep["dest"].(string) newObj["port"] = int(ep["port"].(float64)) @@ -302,14 +319,14 @@ func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string { return links } - obj["ps"] = s.genRemark(inbound, email, "") + obj["ps"] = s.genRemark(inbound, email, "", server.Name) jsonStr, _ := json.MarshalIndent(obj, "", " ") return "vmess://" + base64.StdEncoding.EncodeToString(jsonStr) } -func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string { - address := s.address +func (s *SubService) genVlessLink(inbound *model.Inbound, email string, server *model.Server) string { + address := server.Address if inbound.Protocol != model.VLESS { return "" } @@ -482,7 +499,7 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string { // Set the new query values on the URL url.RawQuery = q.Encode() - url.Fragment = s.genRemark(inbound, email, ep["remark"].(string)) + url.Fragment = s.genRemark(inbound, email, ep["remark"].(string), server.Name) if index > 0 { links += "\n" @@ -503,12 +520,12 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string { // Set the new query values on the URL url.RawQuery = q.Encode() - url.Fragment = s.genRemark(inbound, email, "") + url.Fragment = s.genRemark(inbound, email, "", server.Name) return url.String() } -func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string { - address := s.address +func (s *SubService) genTrojanLink(inbound *model.Inbound, email string, server *model.Server) string { + address := server.Address if inbound.Protocol != model.Trojan { return "" } @@ -677,7 +694,7 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string // Set the new query values on the URL url.RawQuery = q.Encode() - url.Fragment = s.genRemark(inbound, email, ep["remark"].(string)) + url.Fragment = s.genRemark(inbound, email, ep["remark"].(string), server.Name) if index > 0 { links += "\n" @@ -699,12 +716,12 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string // Set the new query values on the URL url.RawQuery = q.Encode() - url.Fragment = s.genRemark(inbound, email, "") + url.Fragment = s.genRemark(inbound, email, "", server.Name) return url.String() } -func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) string { - address := s.address +func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string, server *model.Server) string { + address := server.Address if inbound.Protocol != model.Shadowsocks { return "" } @@ -844,7 +861,7 @@ func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) st // Set the new query values on the URL url.RawQuery = q.Encode() - url.Fragment = s.genRemark(inbound, email, ep["remark"].(string)) + url.Fragment = s.genRemark(inbound, email, ep["remark"].(string), server.Name) if index > 0 { links += "\n" @@ -865,17 +882,18 @@ func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) st // Set the new query values on the URL url.RawQuery = q.Encode() - url.Fragment = s.genRemark(inbound, email, "") + url.Fragment = s.genRemark(inbound, email, "", server.Name) return url.String() } -func (s *SubService) genRemark(inbound *model.Inbound, email string, extra string) string { +func (s *SubService) genRemark(inbound *model.Inbound, email string, extra string, serverName string) string { separationChar := string(s.remarkModel[0]) orderChars := s.remarkModel[1:] orders := map[byte]string{ 'i': "", 'e': "", 'o': "", + 's': "", } if len(email) > 0 { orders['e'] = email @@ -886,6 +904,9 @@ func (s *SubService) genRemark(inbound *model.Inbound, email string, extra strin if len(extra) > 0 { orders['o'] = extra } + if len(serverName) > 0 { + orders['s'] = serverName + } var remark []string for i := 0; i < len(orderChars); i++ { diff --git a/web/controller/inbound.go b/web/controller/inbound.go index 851b4b6f..2566edab 100644 --- a/web/controller/inbound.go +++ b/web/controller/inbound.go @@ -6,6 +6,7 @@ import ( "strconv" "x-ui/database/model" + "x-ui/web/middleware" "x-ui/web/service" "x-ui/web/session" @@ -32,15 +33,26 @@ func (a *InboundController) initRouter(g *gin.RouterGroup) { g.POST("/update/:id", a.updateInbound) g.POST("/clientIps/:email", a.getClientIps) g.POST("/clearClientIps/:email", a.clearClientIps) - g.POST("/addClient", a.addInboundClient) - g.POST("/:id/delClient/:clientId", a.delInboundClient) - g.POST("/updateClient/:clientId", a.updateInboundClient) g.POST("/:id/resetClientTraffic/:email", a.resetClientTraffic) g.POST("/resetAllTraffics", a.resetAllTraffics) g.POST("/resetAllClientTraffics/:id", a.resetAllClientTraffics) g.POST("/delDepletedClients/:id", a.delDepletedClients) g.POST("/import", a.importInbound) g.POST("/onlines", a.onlines) + + // Routes for UI + g.POST("/addClient", a.addInboundClient) + g.POST("/:id/delClient/:clientId", a.delInboundClient) + g.POST("/updateClient/:clientId", a.updateInboundClient) + + // Routes for API (for slave servers) + apiGroup := g.Group("/api") + apiGroup.Use(middleware.ApiAuth()) + { + apiGroup.POST("/addClient", a.addInboundClient) + apiGroup.POST("/:id/delClient/:clientId", a.delInboundClient) + apiGroup.POST("/updateClient/:clientId", a.updateInboundClient) + } } func (a *InboundController) getInbounds(c *gin.Context) { diff --git a/web/controller/multi_server_controller.go b/web/controller/multi_server_controller.go new file mode 100644 index 00000000..65605f4a --- /dev/null +++ b/web/controller/multi_server_controller.go @@ -0,0 +1,89 @@ +package controller + +import ( + "strconv" + + "x-ui/database/model" + "x-ui/web/service" + + "github.com/gin-gonic/gin" +) + +type MultiServerController struct { + multiServerService service.MultiServerService +} + +func NewMultiServerController(g *gin.RouterGroup) *MultiServerController { + c := &MultiServerController{} + c.initRouter(g) + return c +} + +func (c *MultiServerController) initRouter(g *gin.RouterGroup) { + g = g.Group("/server") + + g.GET("/list", c.getServers) + g.POST("/add", c.addServer) + g.POST("/del/:id", c.delServer) + g.POST("/update/:id", c.updateServer) +} + +func (c *MultiServerController) getServers(ctx *gin.Context) { + servers, err := c.multiServerService.GetServers() + if err != nil { + jsonMsg(ctx, "Failed to get servers", err) + return + } + jsonObj(ctx, servers, nil) +} + +func (c *MultiServerController) addServer(ctx *gin.Context) { + server := &model.Server{} + err := ctx.ShouldBind(server) + if err != nil { + jsonMsg(ctx, "Invalid data", err) + return + } + err = c.multiServerService.AddServer(server) + if err != nil { + jsonMsg(ctx, "Failed to add server", err) + return + } + jsonMsg(ctx, "Server added successfully", nil) +} + +func (c *MultiServerController) delServer(ctx *gin.Context) { + id, err := strconv.Atoi(ctx.Param("id")) + if err != nil { + jsonMsg(ctx, "Invalid ID", err) + return + } + err = c.multiServerService.DeleteServer(id) + if err != nil { + jsonMsg(ctx, "Failed to delete server", err) + return + } + jsonMsg(ctx, "Server deleted successfully", nil) +} + +func (c *MultiServerController) updateServer(ctx *gin.Context) { + id, err := strconv.Atoi(ctx.Param("id")) + if err != nil { + jsonMsg(ctx, "Invalid ID", err) + return + } + server := &model.Server{ + Id: id, + } + err = ctx.ShouldBind(server) + if err != nil { + jsonMsg(ctx, "Invalid data", err) + return + } + err = c.multiServerService.UpdateServer(server) + if err != nil { + jsonMsg(ctx, "Failed to update server", err) + return + } + jsonMsg(ctx, "Server updated successfully", nil) +} diff --git a/web/controller/xui.go b/web/controller/xui.go index 5b4c0a18..42c02a15 100644 --- a/web/controller/xui.go +++ b/web/controller/xui.go @@ -24,6 +24,7 @@ func (a *XUIController) initRouter(g *gin.RouterGroup) { g.GET("/", a.index) g.GET("/inbounds", a.inbounds) + g.GET("/servers", a.servers) g.GET("/settings", a.settings) g.GET("/xray", a.xraySettings) @@ -47,3 +48,7 @@ func (a *XUIController) settings(c *gin.Context) { func (a *XUIController) xraySettings(c *gin.Context) { html(c, "xray.html", "pages.xray.title", nil) } + +func (a *XUIController) servers(c *gin.Context) { + html(c, "servers.html", "Servers", nil) +} diff --git a/web/html/component/aSidebar.html b/web/html/component/aSidebar.html index b69c8f3f..7f0a49a7 100644 --- a/web/html/component/aSidebar.html +++ b/web/html/component/aSidebar.html @@ -54,6 +54,11 @@ icon: 'user', title: '{{ i18n "menu.inbounds"}}' }, + { + key: '{{ .base_path }}panel/servers', + icon: 'cloud-server', + title: 'Servers' + }, { key: '{{ .base_path }}panel/settings', icon: 'setting', diff --git a/web/html/servers.html b/web/html/servers.html new file mode 100644 index 00000000..30354731 --- /dev/null +++ b/web/html/servers.html @@ -0,0 +1,165 @@ +{{template "header" .}} + +
+
+
+
+

Server Management

+
+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + +
#NameAddressPortEnabledActions
{{index + 1}}{{server.name}}{{server.address}}{{server.port}} + Yes + No + + + +
+
+
+
+ + + +
+ + + +{{template "footer" .}} diff --git a/web/middleware/auth.go b/web/middleware/auth.go new file mode 100644 index 00000000..140b3f3d --- /dev/null +++ b/web/middleware/auth.go @@ -0,0 +1,34 @@ +package middleware + +import ( + "net/http" + "x-ui/web/service" + "github.com/gin-gonic/gin" +) + +func ApiAuth() gin.HandlerFunc { + return func(c *gin.Context) { + apiKey := c.GetHeader("Api-Key") + if apiKey == "" { + c.JSON(http.StatusUnauthorized, gin.H{"error": "API key is required"}) + c.Abort() + return + } + + settingService := service.SettingService{} + panelAPIKey, err := settingService.GetAPIKey() + if err != nil || panelAPIKey == "" { + c.JSON(http.StatusInternalServerError, gin.H{"error": "API key not configured on the panel"}) + c.Abort() + return + } + + if apiKey != panelAPIKey { + c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid API key"}) + c.Abort() + return + } + + c.Next() + } +} diff --git a/web/service/inbound.go b/web/service/inbound.go index 66e1a420..06280ff8 100644 --- a/web/service/inbound.go +++ b/web/service/inbound.go @@ -1,8 +1,11 @@ package service import ( + "bytes" "encoding/json" "fmt" + "io" + "net/http" "sort" "strconv" "strings" @@ -511,6 +514,11 @@ func (s *InboundService) AddInboundClient(data *model.Inbound) (bool, error) { } s.xrayApi.Close() + if err == nil { + body, _ := json.Marshal(data) + s.syncWithSlaves("POST", "/panel/inbound/api/addClient", bytes.NewReader(body)) + } + return needRestart, tx.Save(oldInbound).Error } @@ -599,6 +607,11 @@ func (s *InboundService) DelInboundClient(inboundId int, clientId string) (bool, s.xrayApi.Close() } } + + if err == nil { + s.syncWithSlaves("POST", fmt.Sprintf("/panel/inbound/api/%d/delClient/%s", inboundId, clientId), nil) + } + return needRestart, db.Save(oldInbound).Error } @@ -753,6 +766,12 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId strin logger.Debug("Client old email not found") needRestart = true } + + if err == nil { + body, _ := json.Marshal(data) + s.syncWithSlaves("POST", fmt.Sprintf("/panel/inbound/api/updateClient/%s", clientId), bytes.NewReader(body)) + } + return needRestart, tx.Save(oldInbound).Error } @@ -2075,3 +2094,41 @@ func (s *InboundService) FilterAndSortClientEmails(emails []string) ([]string, [ return validEmails, extraEmails, nil } + +func (s *InboundService) syncWithSlaves(method string, path string, body io.Reader) { + serverService := MultiServerService{} + servers, err := serverService.GetServers() + if err != nil { + logger.Warning("Failed to get servers for syncing:", err) + return + } + + for _, server := range servers { + if !server.Enable { + continue + } + + url := fmt.Sprintf("http://%s:%d%s", server.Address, server.Port, path) + req, err := http.NewRequest(method, url, body) + if err != nil { + logger.Warningf("Failed to create request for server %s: %v", server.Name, err) + continue + } + + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Api-Key", server.APIKey) + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + logger.Warningf("Failed to send request to server %s: %v", server.Name, err) + continue + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + bodyBytes, _ := io.ReadAll(resp.Body) + logger.Warningf("Failed to sync with server %s. Status: %s, Body: %s", server.Name, resp.Status, string(bodyBytes)) + } + } +} diff --git a/web/service/inbound_service_sync_test.go b/web/service/inbound_service_sync_test.go new file mode 100644 index 00000000..cba45b55 --- /dev/null +++ b/web/service/inbound_service_sync_test.go @@ -0,0 +1,72 @@ +package service + +import ( + "encoding/json" + "io" + "net/http" + "net/http/httptest" + "net/url" + "strconv" + "testing" + "x-ui/database" + "x-ui/database/model" + + "github.com/stretchr/testify/assert" +) + +func TestInboundServiceSync(t *testing.T) { + setup() + defer teardown() + + // Mock server to simulate a slave + var receivedApiKey string + var receivedBody []byte + mockSlave := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + receivedApiKey = r.Header.Get("Api-Key") + receivedBody, _ = io.ReadAll(r.Body) + w.WriteHeader(http.StatusOK) + })) + defer mockSlave.Close() + + // Add the mock slave to the database + multiServerService := MultiServerService{} + mockSlaveURL, _ := url.Parse(mockSlave.URL) + mockSlavePort, _ := strconv.Atoi(mockSlaveURL.Port()) + slaveServer := &model.Server{ + Name: "mock-slave", + Address: mockSlaveURL.Hostname(), + Port: mockSlavePort, + APIKey: "slave-api-key", + Enable: true, + } + multiServerService.AddServer(slaveServer) + + // Create a test inbound and client + inboundService := InboundService{} + db := database.GetDB() + testInbound := &model.Inbound{ + UserId: 1, + Remark: "test-inbound", + Enable: true, + Settings: `{"clients":[]}`, + } + db.Create(testInbound) + + clientData := model.Client{ + Email: "test@example.com", + ID: "test-id", + } + clientBytes, _ := json.Marshal([]model.Client{clientData}) + inboundData := &model.Inbound{ + Id: testInbound.Id, + Settings: string(clientBytes), + } + + // Test AddInboundClient sync + inboundService.AddInboundClient(inboundData) + + assert.Equal(t, "slave-api-key", receivedApiKey) + var receivedInbound model.Inbound + json.Unmarshal(receivedBody, &receivedInbound) + assert.Equal(t, 1, receivedInbound.Id) +} diff --git a/web/service/multi_server_service.go b/web/service/multi_server_service.go new file mode 100644 index 00000000..1450ac05 --- /dev/null +++ b/web/service/multi_server_service.go @@ -0,0 +1,37 @@ +package service + +import ( + "x-ui/database" + "x-ui/database/model" +) + +type MultiServerService struct{} + +func (s *MultiServerService) GetServers() ([]*model.Server, error) { + db := database.GetDB() + var servers []*model.Server + err := db.Find(&servers).Error + return servers, err +} + +func (s *MultiServerService) GetServer(id int) (*model.Server, error) { + db := database.GetDB() + var server model.Server + err := db.First(&server, id).Error + return &server, err +} + +func (s *MultiServerService) AddServer(server *model.Server) error { + db := database.GetDB() + return db.Create(server).Error +} + +func (s *MultiServerService) UpdateServer(server *model.Server) error { + db := database.GetDB() + return db.Save(server).Error +} + +func (s *MultiServerService) DeleteServer(id int) error { + db := database.GetDB() + return db.Delete(&model.Server{}, id).Error +} diff --git a/web/service/multi_server_service_test.go b/web/service/multi_server_service_test.go new file mode 100644 index 00000000..83cd3968 --- /dev/null +++ b/web/service/multi_server_service_test.go @@ -0,0 +1,63 @@ +package service + +import ( + "os" + "testing" + "x-ui/database" + "x-ui/database/model" + + "github.com/stretchr/testify/assert" +) + +func setup() { + dbPath := "test.db" + os.Remove(dbPath) + database.InitDB(dbPath) +} + +func teardown() { + db, _ := database.GetDB().DB() + db.Close() + os.Remove("test.db") +} + +func TestMultiServerService(t *testing.T) { + setup() + defer teardown() + + service := MultiServerService{} + + // Test AddServer + server := &model.Server{ + Name: "test-server", + Address: "127.0.0.1", + Port: 54321, + APIKey: "test-key", + Enable: true, + } + err := service.AddServer(server) + assert.NoError(t, err) + + // Test GetServer + retrievedServer, err := service.GetServer(server.Id) + assert.NoError(t, err) + assert.Equal(t, server.Name, retrievedServer.Name) + + // Test GetServers + servers, err := service.GetServers() + assert.NoError(t, err) + assert.Len(t, servers, 1) + + // Test UpdateServer + retrievedServer.Name = "updated-server" + err = service.UpdateServer(retrievedServer) + assert.NoError(t, err) + updatedServer, _ := service.GetServer(server.Id) + assert.Equal(t, "updated-server", updatedServer.Name) + + // Test DeleteServer + err = service.DeleteServer(server.Id) + assert.NoError(t, err) + _, err = service.GetServer(server.Id) + assert.Error(t, err) +} diff --git a/web/service/setting.go b/web/service/setting.go index a54eaea7..a1231268 100644 --- a/web/service/setting.go +++ b/web/service/setting.go @@ -180,6 +180,21 @@ func (s *SettingService) getSetting(key string) (*model.Setting, error) { return setting, nil } +func (s *SettingService) GetAPIKey() (string, error) { + setting, err := s.getSetting("ApiKey") + if err != nil { + return "", err + } + if setting == nil { + return "", nil + } + return setting.Value, nil +} + +func (s *SettingService) SetAPIKey(apiKey string) error { + return s.saveSetting("ApiKey", apiKey) +} + func (s *SettingService) saveSetting(key string, value string) error { setting, err := s.getSetting(key) db := database.GetDB() diff --git a/web/web.go b/web/web.go index 35ccec70..1d8b4877 100644 --- a/web/web.go +++ b/web/web.go @@ -229,7 +229,7 @@ func (s *Server) initRouter() (*gin.Engine, error) { g := engine.Group(basePath) s.index = controller.NewIndexController(g) - s.server = controller.NewServerController(g) + s.server = controller.NewMultiServerController(g) s.panel = controller.NewXUIController(g) s.api = controller.NewAPIController(g)