diff --git a/web/controller/multi_server_controller.go b/web/controller/multi_server_controller.go
index 4164febb..f10b1db9 100644
--- a/web/controller/multi_server_controller.go
+++ b/web/controller/multi_server_controller.go
@@ -26,6 +26,7 @@ func (c *MultiServerController) initRouter(g *gin.RouterGroup) {
g.POST("/del/:id", c.delServer)
g.POST("/update/:id", c.updateServer)
g.GET("/onlines", c.getOnlineClients)
+ g.POST("/sync/:id", c.syncServer)
}
func (c *MultiServerController) getServers(ctx *gin.Context) {
@@ -96,3 +97,17 @@ func (c *MultiServerController) updateServer(ctx *gin.Context) {
}
jsonMsg(ctx, "Server updated successfully", nil)
}
+
+func (c *MultiServerController) syncServer(ctx *gin.Context) {
+ id, err := strconv.Atoi(ctx.Param("id"))
+ if err != nil {
+ jsonMsg(ctx, "Invalid ID", err)
+ return
+ }
+ err = c.multiServerService.SyncServer(id)
+ if err != nil {
+ jsonMsg(ctx, "Failed to sync server", err)
+ return
+ }
+ jsonMsg(ctx, "Server synced successfully", nil)
+}
diff --git a/web/html/servers.html b/web/html/servers.html
index ad240642..9872b370 100644
--- a/web/html/servers.html
+++ b/web/html/servers.html
@@ -3,7 +3,6 @@
{{ template "page/head_end" .}} {{ template
"page/body_start" .}}
-
@@ -111,9 +110,11 @@
class="badge bg-danger">No
|
+ @click="showEditModal(server)">Edit
+
+
@@ -331,6 +332,19 @@
alert(error.response.data.msg);
});
},
+ syncServer(server) {
+ if (!confirm("Are you sure you want to sync inbounds with server \"" + server.name + "\" server?")) {
+ return;
+ }
+ axios
+ .post(`/panel/api/servers/sync/${server.id}`)
+ .then((response) => {
+ alert(response.data.msg);
+ })
+ .catch((error) => {
+ alert(error.response.data.msg);
+ });
+ },
deleteServer(id) {
if (!confirm("Are you sure you want to delete this server?")) {
return;
diff --git a/web/service/multi_server_service.go b/web/service/multi_server_service.go
index 7cb38527..4d94cbc5 100644
--- a/web/service/multi_server_service.go
+++ b/web/service/multi_server_service.go
@@ -1,12 +1,14 @@
package service
import (
+ "bytes"
"encoding/json"
"fmt"
"net/http"
"github.com/mhsanaei/3x-ui/v2/database"
"github.com/mhsanaei/3x-ui/v2/database/model"
+ "github.com/mhsanaei/3x-ui/v2/logger"
)
type MultiServerService struct{}
@@ -34,7 +36,7 @@ func (s *MultiServerService) GetOnlineClients() (map[int][]string, error) {
return nil, err
}
- clients := make( map[int][]string)
+ clients := make(map[int][]string)
for _, server := range servers {
var onlineResp struct {
Success bool `json:"success"`
@@ -72,3 +74,132 @@ func (s *MultiServerService) DeleteServer(id int) error {
db := database.GetDB()
return db.Delete(&model.Server{}, id).Error
}
+
+// SyncServer synchronizes the inbounds list between the given server and the local inbounds list.
+// It gets the inbounds list from the server, and then syncs it with the local inbounds list.
+// If an inbound exists on the server but not locally, it adds the inbound.
+// If an inbound exists locally but not on the server, it removes the inbound.
+// If an inbound exists on both the server and locally, it updates the inbound if they are different.
+func (s *MultiServerService) SyncServer(id int) error {
+ inboundService := &InboundService{}
+ inboundsSource, err := inboundService.GetAllInbounds()
+ if err != nil {
+ logger.Error("failed to get all inbounds", "err", err)
+ return err
+ }
+
+ db := database.GetDB()
+ var server model.Server
+ if err = db.First(&server, id).Error; err != nil {
+ logger.Error("failed to get server", "err", err)
+ return err
+ }
+
+ //get inbounds from server throw api
+ listURL := fmt.Sprintf("http://%s:%d%spanel/api/inbounds/list", server.Address, server.Port, server.SecretWebPath)
+ req, _ := http.NewRequest("GET", listURL, nil)
+ req.Header.Set("X-API-KEY", server.APIKey)
+ httpResp, err := http.DefaultClient.Do(req)
+ if err != nil {
+ logger.Error("failed to get inbounds from server", "err", err)
+ return err
+ }
+ defer httpResp.Body.Close()
+
+ var resp struct {
+ Success bool `json:"success"`
+ Msg string `json:"msg"`
+ Obj []model.Inbound `json:"obj"`
+ }
+ if err := json.NewDecoder(httpResp.Body).Decode(&resp); err != nil {
+ logger.Error("failed to decode inbounds response", "err", err)
+ return err
+ }
+
+ type InboundPayload struct {
+ Up int64 `json:"up"`
+ Down int64 `json:"down"`
+ Total int64 `json:"total"`
+ Remark string `json:"remark"`
+ Enable bool `json:"enable"`
+ ExpiryTime int64 `json:"expiryTime"`
+ Listen string `json:"listen"`
+ Port int `json:"port"`
+ Protocol model.Protocol `json:"protocol"`
+ Settings string `json:"settings"`
+ StreamSettings string `json:"streamSettings"`
+ Sniffing string `json:"sniffing"`
+ }
+
+ //sync inbounds
+ for _, src := range inboundsSource {
+ logger.Debugf("syncing inbound %d", src.Id)
+ found := false
+ for _, remote := range resp.Obj {
+ if remote.Tag == src.Tag {
+ found = true
+ break
+ }
+ }
+
+ payload := InboundPayload{
+ Up: src.Up, Down: src.Down, Total: src.Total,
+ Remark: src.Remark, Enable: src.Enable,
+ ExpiryTime: src.ExpiryTime, Listen: src.Listen,
+ Port: src.Port, Protocol: src.Protocol,
+ Settings: src.Settings, StreamSettings: src.StreamSettings, Sniffing: src.Sniffing,
+ }
+
+ data, _ := json.Marshal(payload)
+
+ if found {
+ //update inbound trow api
+ updateURL := fmt.Sprintf("http://%s:%d%spanel/api/inbounds/update/%d", server.Address, server.Port, server.SecretWebPath, src.Id)
+ req, _ := http.NewRequest("POST", updateURL, bytes.NewBuffer(data))
+ req.Header.Set("X-API-KEY", server.APIKey)
+ req.Header.Set("Content-Type", "application/json")
+ resp, err := http.DefaultClient.Do(req)
+ if err != nil {
+ logger.Error("failed to update inbounds at server", "err", err)
+ return err
+ }
+ var updateResp struct {
+ Success bool `json:"success"`
+ Msg string `json:"msg"`
+ }
+ if err := json.NewDecoder(httpResp.Body).Decode(&updateResp); err != nil {
+ logger.Error("failed to decode update inbounds response", "err", err)
+ return fmt.Errorf("decode update inbounds: %w", err)
+ }
+ resp.Body.Close()
+ if !updateResp.Success {
+ return fmt.Errorf("failed to update inbounds at %s %s", server.Name, server.Address)
+ }
+ } else {
+ // add inbound trow api
+ addURL := fmt.Sprintf("http://%s:%d%spanel/api/inbounds/add", server.Address, server.Port, server.SecretWebPath)
+ req, _ := http.NewRequest("POST", addURL, bytes.NewBuffer(data))
+ req.Header.Set("X-API-KEY", server.APIKey)
+ req.Header.Set("Content-Type", "application/json")
+ resp, err := http.DefaultClient.Do(req)
+ if err != nil {
+ logger.Error("failed to add inbounds at server", "err", err)
+ return err
+ }
+
+ var addResp struct {
+ Success bool `json:"success"`
+ Msg string `json:"msg"`
+ }
+ if err := json.NewDecoder(httpResp.Body).Decode(&addResp); err != nil {
+ logger.Error("failed to decode add inbounds response", "err", err)
+ return fmt.Errorf("decode add inbounds: %w", err)
+ }
+ resp.Body.Close()
+ if !addResp.Success {
+ return fmt.Errorf("failed to add inbounds at %s %s", server.Name, server.Address)
+ }
+ }
+ }
+ return nil
+}