From 8813d1f0d67e3abc49f77d3031cd25a319e8958f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9D=D0=B5=D1=81=D1=82=D0=B5=D1=80=D0=BE=D0=B2=20=D0=A0?= =?UTF-8?q?=D1=83=D1=81=D0=BB=D0=B0=D0=BD?= Date: Wed, 22 Apr 2026 15:46:54 +0300 Subject: [PATCH] feat: copy clients between inbounds --- deploy.sh | 16 +++ web/controller/inbound.go | 36 +++++ web/html/inbounds.html | 170 +++++++++++++++++++++++ web/service/inbound.go | 197 +++++++++++++++++++++++++++ web/translation/translate.en_US.toml | 9 ++ web/translation/translate.ru_RU.toml | 9 ++ 6 files changed, 437 insertions(+) create mode 100644 deploy.sh 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" }} + + + + +
+
+
{{ i18n "pages.client.copyEmailPreview" }}
+
+ + [[ preview ]] + +
+
+
+