diff --git a/deploy.sh b/deploy.sh new file mode 100644 index 00000000..ca6e8f09 --- /dev/null +++ b/deploy.sh @@ -0,0 +1,16 @@ +#!/bin/bash +set -e +cd /opt/3x-uiRsNest + +echo "=== Сборка бэкенда ===" +go build -o x-ui main.go + +echo "=== Остановка x-ui ===" +systemctl stop x-ui + +echo "=== Замена бинарника ===" +cp x-ui /usr/local/x-ui/x-ui + +echo "=== Запуск x-ui ===" +systemctl start x-ui +systemctl status x-ui diff --git a/web/controller/inbound.go b/web/controller/inbound.go index b012ec95..a7410b04 100644 --- a/web/controller/inbound.go +++ b/web/controller/inbound.go @@ -41,6 +41,7 @@ func (a *InboundController) initRouter(g *gin.RouterGroup) { g.POST("/clientIps/:email", a.getClientIps) g.POST("/clearClientIps/:email", a.clearClientIps) g.POST("/addClient", a.addInboundClient) + g.POST("/:id/copyClients", a.copyInboundClients) g.POST("/:id/delClient/:clientId", a.delInboundClient) g.POST("/updateClient/:clientId", a.updateInboundClient) g.POST("/:id/resetClientTraffic/:email", a.resetClientTraffic) @@ -54,6 +55,11 @@ func (a *InboundController) initRouter(g *gin.RouterGroup) { g.POST("/:id/delClientByEmail/:email", a.delInboundClientByEmail) } +type CopyInboundClientsRequest struct { + SourceInboundID int `json:"sourceInboundId"` + ClientEmails []string `json:"clientEmails"` +} + // getInbounds retrieves the list of inbounds for the logged-in user. func (a *InboundController) getInbounds(c *gin.Context) { user := session.GetLoginUser(c) @@ -260,6 +266,36 @@ func (a *InboundController) addInboundClient(c *gin.Context) { } } +// copyInboundClients copies clients from source inbound to target inbound. +func (a *InboundController) copyInboundClients(c *gin.Context) { + targetID, err := strconv.Atoi(c.Param("id")) + if err != nil { + jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err) + return + } + + req := &CopyInboundClientsRequest{} + err = c.ShouldBindJSON(req) + if err != nil { + jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err) + return + } + if req.SourceInboundID <= 0 { + jsonMsg(c, I18nWeb(c, "somethingWentWrong"), fmt.Errorf("invalid source inbound id")) + return + } + + result, needRestart, err := a.inboundService.CopyInboundClients(targetID, req.SourceInboundID, req.ClientEmails) + if err != nil { + jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err) + return + } + jsonObj(c, result, nil) + if needRestart { + a.xrayService.SetToNeedRestart() + } +} + // delInboundClient deletes a client from an inbound by inbound ID and client ID. func (a *InboundController) delInboundClient(c *gin.Context) { id, err := strconv.Atoi(c.Param("id")) diff --git a/web/html/inbounds.html b/web/html/inbounds.html index 60de0750..324b2c01 100644 --- a/web/html/inbounds.html +++ b/web/html/inbounds.html @@ -262,6 +262,10 @@ {{ i18n "pages.client.bulk"}} + + + {{ i18n "pages.client.copyFromInbound"}} + {{ i18n @@ -777,6 +781,58 @@ {{template "modals/inboundInfoModal"}} {{template "modals/clientsModal"}} {{template "modals/clientsBulkModal"}} + + + + {{ i18n "pages.client.copySource" }} + + + [[ item.label ]] + + + + + + {{ i18n "pages.client.selectAll" }} + {{ i18n "pages.client.clearAll" }} + + + + toggleCopyClientEmail(record.email, event.target.checked)"> + [[ record.email ]] + + + + + + {{ i18n "pages.client.copyEmailPreview" }} + + + [[ preview ]] + + + + +