mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2025-08-23 19:36:54 +00:00
![google-labs-jules[bot]](/assets/img/avatar_default.png)
This commit introduces a multi-server architecture to the Sanai panel, allowing you to manage clients across multiple servers from a central panel. Key changes include: - **Database Schema:** Added a `servers` table to store information about slave servers. - **Server Management:** Implemented a new service and controller (`MultiServerService` and `MultiServerController`) for CRUD operations on servers. - **Web UI:** Created a new web page for managing servers, accessible from the sidebar. - **Client Synchronization:** Modified the `InboundService` to synchronize client additions, updates, and deletions across all active slave servers via a REST API. - **API Security:** Added an API key authentication middleware to secure the communication between the master and slave panels. - **Multi-Server Subscriptions:** Updated the subscription service to generate links that include configurations for all active servers. - **Installation Script:** Modified the `install.sh` script to generate a random API key during installation. **Known Issues:** - The integration test for client synchronization (`TestInboundServiceSync`) is currently failing. It seems that the API request to the mock slave server is not being sent correctly or the API key is not being included in the request header. Further investigation is needed to resolve this issue.
378 lines
10 KiB
Go
378 lines
10 KiB
Go
package controller
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"strconv"
|
|
|
|
"x-ui/database/model"
|
|
"x-ui/web/middleware"
|
|
"x-ui/web/service"
|
|
"x-ui/web/session"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
type InboundController struct {
|
|
inboundService service.InboundService
|
|
xrayService service.XrayService
|
|
}
|
|
|
|
func NewInboundController(g *gin.RouterGroup) *InboundController {
|
|
a := &InboundController{}
|
|
a.initRouter(g)
|
|
return a
|
|
}
|
|
|
|
func (a *InboundController) initRouter(g *gin.RouterGroup) {
|
|
g = g.Group("/inbound")
|
|
|
|
g.POST("/list", a.getInbounds)
|
|
g.POST("/add", a.addInbound)
|
|
g.POST("/del/:id", a.delInbound)
|
|
g.POST("/update/:id", a.updateInbound)
|
|
g.POST("/clientIps/:email", a.getClientIps)
|
|
g.POST("/clearClientIps/:email", a.clearClientIps)
|
|
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) {
|
|
user := session.GetLoginUser(c)
|
|
inbounds, err := a.inboundService.GetInbounds(user.Id)
|
|
if err != nil {
|
|
jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.obtain"), err)
|
|
return
|
|
}
|
|
jsonObj(c, inbounds, nil)
|
|
}
|
|
|
|
func (a *InboundController) getInbound(c *gin.Context) {
|
|
id, err := strconv.Atoi(c.Param("id"))
|
|
if err != nil {
|
|
jsonMsg(c, I18nWeb(c, "get"), err)
|
|
return
|
|
}
|
|
inbound, err := a.inboundService.GetInbound(id)
|
|
if err != nil {
|
|
jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.obtain"), err)
|
|
return
|
|
}
|
|
jsonObj(c, inbound, nil)
|
|
}
|
|
|
|
func (a *InboundController) getClientTraffics(c *gin.Context) {
|
|
email := c.Param("email")
|
|
clientTraffics, err := a.inboundService.GetClientTrafficByEmail(email)
|
|
if err != nil {
|
|
jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.trafficGetError"), err)
|
|
return
|
|
}
|
|
jsonObj(c, clientTraffics, nil)
|
|
}
|
|
|
|
func (a *InboundController) getClientTrafficsById(c *gin.Context) {
|
|
id := c.Param("id")
|
|
clientTraffics, err := a.inboundService.GetClientTrafficByID(id)
|
|
if err != nil {
|
|
jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.trafficGetError"), err)
|
|
return
|
|
}
|
|
jsonObj(c, clientTraffics, nil)
|
|
}
|
|
|
|
func (a *InboundController) addInbound(c *gin.Context) {
|
|
inbound := &model.Inbound{}
|
|
err := c.ShouldBind(inbound)
|
|
if err != nil {
|
|
jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.inboundCreateSuccess"), err)
|
|
return
|
|
}
|
|
user := session.GetLoginUser(c)
|
|
inbound.UserId = user.Id
|
|
if inbound.Listen == "" || inbound.Listen == "0.0.0.0" || inbound.Listen == "::" || inbound.Listen == "::0" {
|
|
inbound.Tag = fmt.Sprintf("inbound-%v", inbound.Port)
|
|
} else {
|
|
inbound.Tag = fmt.Sprintf("inbound-%v:%v", inbound.Listen, inbound.Port)
|
|
}
|
|
|
|
needRestart := false
|
|
inbound, needRestart, err = a.inboundService.AddInbound(inbound)
|
|
if err != nil {
|
|
jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err)
|
|
return
|
|
}
|
|
jsonMsgObj(c, I18nWeb(c, "pages.inbounds.toasts.inboundCreateSuccess"), inbound, err)
|
|
if err == nil && needRestart {
|
|
a.xrayService.SetToNeedRestart()
|
|
}
|
|
}
|
|
|
|
func (a *InboundController) delInbound(c *gin.Context) {
|
|
id, err := strconv.Atoi(c.Param("id"))
|
|
if err != nil {
|
|
jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.inboundDeleteSuccess"), err)
|
|
return
|
|
}
|
|
needRestart := true
|
|
needRestart, err = a.inboundService.DelInbound(id)
|
|
if err != nil {
|
|
jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err)
|
|
return
|
|
}
|
|
jsonMsgObj(c, I18nWeb(c, "pages.inbounds.toasts.inboundDeleteSuccess"), id, err)
|
|
if err == nil && needRestart {
|
|
a.xrayService.SetToNeedRestart()
|
|
}
|
|
}
|
|
|
|
func (a *InboundController) updateInbound(c *gin.Context) {
|
|
id, err := strconv.Atoi(c.Param("id"))
|
|
if err != nil {
|
|
jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.inboundUpdateSuccess"), err)
|
|
return
|
|
}
|
|
inbound := &model.Inbound{
|
|
Id: id,
|
|
}
|
|
err = c.ShouldBind(inbound)
|
|
if err != nil {
|
|
jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.inboundUpdateSuccess"), err)
|
|
return
|
|
}
|
|
needRestart := true
|
|
inbound, needRestart, err = a.inboundService.UpdateInbound(inbound)
|
|
if err != nil {
|
|
jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err)
|
|
return
|
|
}
|
|
jsonMsgObj(c, I18nWeb(c, "pages.inbounds.toasts.inboundUpdateSuccess"), inbound, err)
|
|
if err == nil && needRestart {
|
|
a.xrayService.SetToNeedRestart()
|
|
}
|
|
}
|
|
|
|
func (a *InboundController) getClientIps(c *gin.Context) {
|
|
email := c.Param("email")
|
|
|
|
ips, err := a.inboundService.GetInboundClientIps(email)
|
|
if err != nil || ips == "" {
|
|
jsonObj(c, "No IP Record", nil)
|
|
return
|
|
}
|
|
|
|
jsonObj(c, ips, nil)
|
|
}
|
|
|
|
func (a *InboundController) clearClientIps(c *gin.Context) {
|
|
email := c.Param("email")
|
|
|
|
err := a.inboundService.ClearClientIps(email)
|
|
if err != nil {
|
|
jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.updateSuccess"), err)
|
|
return
|
|
}
|
|
jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.logCleanSuccess"), nil)
|
|
}
|
|
|
|
func (a *InboundController) addInboundClient(c *gin.Context) {
|
|
data := &model.Inbound{}
|
|
err := c.ShouldBind(data)
|
|
if err != nil {
|
|
jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.inboundUpdateSuccess"), err)
|
|
return
|
|
}
|
|
|
|
needRestart := true
|
|
|
|
needRestart, err = a.inboundService.AddInboundClient(data)
|
|
if err != nil {
|
|
jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err)
|
|
return
|
|
}
|
|
jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.inboundClientAddSuccess"), nil)
|
|
if needRestart {
|
|
a.xrayService.SetToNeedRestart()
|
|
}
|
|
}
|
|
|
|
func (a *InboundController) delInboundClient(c *gin.Context) {
|
|
id, err := strconv.Atoi(c.Param("id"))
|
|
if err != nil {
|
|
jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.inboundUpdateSuccess"), err)
|
|
return
|
|
}
|
|
clientId := c.Param("clientId")
|
|
|
|
needRestart := true
|
|
|
|
needRestart, err = a.inboundService.DelInboundClient(id, clientId)
|
|
if err != nil {
|
|
jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err)
|
|
return
|
|
}
|
|
jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.inboundClientDeleteSuccess"), nil)
|
|
if needRestart {
|
|
a.xrayService.SetToNeedRestart()
|
|
}
|
|
}
|
|
|
|
func (a *InboundController) updateInboundClient(c *gin.Context) {
|
|
clientId := c.Param("clientId")
|
|
|
|
inbound := &model.Inbound{}
|
|
err := c.ShouldBind(inbound)
|
|
if err != nil {
|
|
jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.inboundUpdateSuccess"), err)
|
|
return
|
|
}
|
|
|
|
needRestart := true
|
|
|
|
needRestart, err = a.inboundService.UpdateInboundClient(inbound, clientId)
|
|
if err != nil {
|
|
jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err)
|
|
return
|
|
}
|
|
jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.inboundClientUpdateSuccess"), nil)
|
|
if needRestart {
|
|
a.xrayService.SetToNeedRestart()
|
|
}
|
|
}
|
|
|
|
func (a *InboundController) resetClientTraffic(c *gin.Context) {
|
|
id, err := strconv.Atoi(c.Param("id"))
|
|
if err != nil {
|
|
jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.inboundUpdateSuccess"), err)
|
|
return
|
|
}
|
|
email := c.Param("email")
|
|
|
|
needRestart, err := a.inboundService.ResetClientTraffic(id, email)
|
|
if err != nil {
|
|
jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err)
|
|
return
|
|
}
|
|
jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.resetInboundClientTrafficSuccess"), nil)
|
|
if needRestart {
|
|
a.xrayService.SetToNeedRestart()
|
|
}
|
|
}
|
|
|
|
func (a *InboundController) resetAllTraffics(c *gin.Context) {
|
|
err := a.inboundService.ResetAllTraffics()
|
|
if err != nil {
|
|
jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err)
|
|
return
|
|
} else {
|
|
a.xrayService.SetToNeedRestart()
|
|
}
|
|
jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.resetAllTrafficSuccess"), nil)
|
|
}
|
|
|
|
func (a *InboundController) resetAllClientTraffics(c *gin.Context) {
|
|
id, err := strconv.Atoi(c.Param("id"))
|
|
if err != nil {
|
|
jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.inboundUpdateSuccess"), err)
|
|
return
|
|
}
|
|
|
|
err = a.inboundService.ResetAllClientTraffics(id)
|
|
if err != nil {
|
|
jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err)
|
|
return
|
|
} else {
|
|
a.xrayService.SetToNeedRestart()
|
|
}
|
|
jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.resetAllClientTrafficSuccess"), nil)
|
|
}
|
|
|
|
func (a *InboundController) importInbound(c *gin.Context) {
|
|
inbound := &model.Inbound{}
|
|
err := json.Unmarshal([]byte(c.PostForm("data")), inbound)
|
|
if err != nil {
|
|
jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err)
|
|
return
|
|
}
|
|
user := session.GetLoginUser(c)
|
|
inbound.Id = 0
|
|
inbound.UserId = user.Id
|
|
if inbound.Listen == "" || inbound.Listen == "0.0.0.0" || inbound.Listen == "::" || inbound.Listen == "::0" {
|
|
inbound.Tag = fmt.Sprintf("inbound-%v", inbound.Port)
|
|
} else {
|
|
inbound.Tag = fmt.Sprintf("inbound-%v:%v", inbound.Listen, inbound.Port)
|
|
}
|
|
|
|
for index := range inbound.ClientStats {
|
|
inbound.ClientStats[index].Id = 0
|
|
inbound.ClientStats[index].Enable = true
|
|
}
|
|
|
|
needRestart := false
|
|
inbound, needRestart, err = a.inboundService.AddInbound(inbound)
|
|
jsonMsgObj(c, I18nWeb(c, "pages.inbounds.toasts.inboundCreateSuccess"), inbound, err)
|
|
if err == nil && needRestart {
|
|
a.xrayService.SetToNeedRestart()
|
|
}
|
|
}
|
|
|
|
func (a *InboundController) delDepletedClients(c *gin.Context) {
|
|
id, err := strconv.Atoi(c.Param("id"))
|
|
if err != nil {
|
|
jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.inboundUpdateSuccess"), err)
|
|
return
|
|
}
|
|
err = a.inboundService.DelDepletedClients(id)
|
|
if err != nil {
|
|
jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err)
|
|
return
|
|
}
|
|
jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.delDepletedClientsSuccess"), nil)
|
|
}
|
|
|
|
func (a *InboundController) onlines(c *gin.Context) {
|
|
jsonObj(c, a.inboundService.GetOnlineClients(), nil)
|
|
}
|
|
|
|
func (a *InboundController) updateClientTraffic(c *gin.Context) {
|
|
email := c.Param("email")
|
|
|
|
// Define the request structure for traffic update
|
|
type TrafficUpdateRequest struct {
|
|
Upload int64 `json:"upload"`
|
|
Download int64 `json:"download"`
|
|
}
|
|
|
|
var request TrafficUpdateRequest
|
|
err := c.ShouldBindJSON(&request)
|
|
if err != nil {
|
|
jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.inboundUpdateSuccess"), err)
|
|
return
|
|
}
|
|
|
|
err = a.inboundService.UpdateClientTrafficByEmail(email, request.Upload, request.Download)
|
|
if err != nil {
|
|
jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err)
|
|
return
|
|
}
|
|
|
|
jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.inboundClientUpdateSuccess"), nil)
|
|
}
|