diff --git a/web/controller/inbound.go b/web/controller/inbound.go index b012ec95..ee024cc6 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,12 @@ func (a *InboundController) initRouter(g *gin.RouterGroup) { g.POST("/:id/delClientByEmail/:email", a.delInboundClientByEmail) } +type CopyInboundClientsRequest struct { + SourceInboundID int `form:"sourceInboundId" json:"sourceInboundId"` + ClientEmails []string `form:"clientEmails" json:"clientEmails"` + Flow string `form:"flow" json:"flow"` +} + // getInbounds retrieves the list of inbounds for the logged-in user. func (a *InboundController) getInbounds(c *gin.Context) { user := session.GetLoginUser(c) @@ -260,6 +267,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.ShouldBind(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, req.Flow) + 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..b8485702 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,218 @@ {{template "modals/inboundInfoModal"}} {{template "modals/clientsModal"}} {{template "modals/clientsBulkModal"}} + copyClientsModal.ok()" + @cancel="() => copyClientsModal.close()" + width="900px"> + + + {{ i18n "pages.client.copySource" }} + copyClientsModal.onSourceChange(id)"> + + [[ item.label ]] + + + + + + copyClientsModal.selectAll()">{{ i18n "pages.client.selectAll" }} + copyClientsModal.clearAll()">{{ i18n "pages.client.clearAll" }} + + + + copyClientsModal.toggleEmail(record.email, event.target.checked)"> + [[ record.email ]] + + + + + + {{ i18n "pages.client.copyFlowLabel" }} + + {{ i18n "none" }} + xtls-rprx-vision + xtls-rprx-vision-udp443 + + + {{ i18n "pages.client.copyFlowHint" }} + + + + {{ i18n "pages.client.copyEmailPreview" }} + + + [[ preview ]] + + + + + +