mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2025-11-29 02:42:51 +00:00
feat sync btn
This commit is contained in:
parent
72109c040e
commit
b2efeec70f
3 changed files with 165 additions and 5 deletions
|
|
@ -26,6 +26,7 @@ func (c *MultiServerController) initRouter(g *gin.RouterGroup) {
|
||||||
g.POST("/del/:id", c.delServer)
|
g.POST("/del/:id", c.delServer)
|
||||||
g.POST("/update/:id", c.updateServer)
|
g.POST("/update/:id", c.updateServer)
|
||||||
g.GET("/onlines", c.getOnlineClients)
|
g.GET("/onlines", c.getOnlineClients)
|
||||||
|
g.POST("/sync/:id", c.syncServer)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *MultiServerController) getServers(ctx *gin.Context) {
|
func (c *MultiServerController) getServers(ctx *gin.Context) {
|
||||||
|
|
@ -96,3 +97,17 @@ func (c *MultiServerController) updateServer(ctx *gin.Context) {
|
||||||
}
|
}
|
||||||
jsonMsg(ctx, "Server updated successfully", nil)
|
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)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@
|
||||||
<link rel="stylesheet" href="{{ .base_path }}assets/bootstrap/bootstrap.min.css">
|
<link rel="stylesheet" href="{{ .base_path }}assets/bootstrap/bootstrap.min.css">
|
||||||
{{ template "page/head_end" .}} {{ template
|
{{ template "page/head_end" .}} {{ template
|
||||||
"page/body_start" .}}
|
"page/body_start" .}}
|
||||||
|
|
||||||
<a-layout id="app" v-cloak :class="themeSwitcher.currentTheme + ' settings-page'">
|
<a-layout id="app" v-cloak :class="themeSwitcher.currentTheme + ' settings-page'">
|
||||||
<a-sidebar></a-sidebar>
|
<a-sidebar></a-sidebar>
|
||||||
<a-layout id="content-layout">
|
<a-layout id="content-layout">
|
||||||
|
|
@ -111,9 +110,11 @@
|
||||||
class="badge bg-danger">No</span>
|
class="badge bg-danger">No</span>
|
||||||
</td>
|
</td>
|
||||||
<td><button class="btn btn-info btn-sm"
|
<td><button class="btn btn-info btn-sm"
|
||||||
@click="showEditModal(server)">Edit</button><button
|
@click="showEditModal(server)">Edit</button>
|
||||||
class="btn btn-danger btn-sm"
|
<button class="btn btn-danger btn-sm"
|
||||||
@click="deleteServer(server.id)">Delete</button></td>
|
@click="deleteServer(server.id)">Delete</button>
|
||||||
|
<button class="btn btn-sm btn-secondary"
|
||||||
|
@click="syncServer(server)">Sync</button></td>
|
||||||
</tr>
|
</tr>
|
||||||
</template>
|
</template>
|
||||||
<tr v-else>
|
<tr v-else>
|
||||||
|
|
@ -331,6 +332,19 @@
|
||||||
alert(error.response.data.msg);
|
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) {
|
deleteServer(id) {
|
||||||
if (!confirm("Are you sure you want to delete this server?")) {
|
if (!confirm("Are you sure you want to delete this server?")) {
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,14 @@
|
||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/mhsanaei/3x-ui/v2/database"
|
"github.com/mhsanaei/3x-ui/v2/database"
|
||||||
"github.com/mhsanaei/3x-ui/v2/database/model"
|
"github.com/mhsanaei/3x-ui/v2/database/model"
|
||||||
|
"github.com/mhsanaei/3x-ui/v2/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
type MultiServerService struct{}
|
type MultiServerService struct{}
|
||||||
|
|
@ -34,7 +36,7 @@ func (s *MultiServerService) GetOnlineClients() (map[int][]string, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
clients := make( map[int][]string)
|
clients := make(map[int][]string)
|
||||||
for _, server := range servers {
|
for _, server := range servers {
|
||||||
var onlineResp struct {
|
var onlineResp struct {
|
||||||
Success bool `json:"success"`
|
Success bool `json:"success"`
|
||||||
|
|
@ -72,3 +74,132 @@ func (s *MultiServerService) DeleteServer(id int) error {
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
return db.Delete(&model.Server{}, id).Error
|
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
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue