feat sync btn

This commit is contained in:
Дмитрий Саенко 2025-10-29 22:17:26 +03:00
parent 72109c040e
commit b2efeec70f
3 changed files with 165 additions and 5 deletions

View file

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

View file

@ -3,7 +3,6 @@
<link rel="stylesheet" href="{{ .base_path }}assets/bootstrap/bootstrap.min.css">
{{ template "page/head_end" .}} {{ template
"page/body_start" .}}
<a-layout id="app" v-cloak :class="themeSwitcher.currentTheme + ' settings-page'">
<a-sidebar></a-sidebar>
<a-layout id="content-layout">
@ -111,9 +110,11 @@
class="badge bg-danger">No</span>
</td>
<td><button class="btn btn-info btn-sm"
@click="showEditModal(server)">Edit</button><button
class="btn btn-danger btn-sm"
@click="deleteServer(server.id)">Delete</button></td>
@click="showEditModal(server)">Edit</button>
<button class="btn btn-danger btn-sm"
@click="deleteServer(server.id)">Delete</button>
<button class="btn btn-sm btn-secondary"
@click="syncServer(server)">Sync</button></td>
</tr>
</template>
<tr v-else>
@ -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;

View file

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