mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-05-31 18:24:10 +00:00
Adds /panel/api/clients endpoints (list, get, add, update, del, attach, detach) backed by ClientService methods that orchestrate the per-inbound Add/Update/Del flows so a single client row is created once and attached to many inbounds in one operation. The frontend gains a dedicated Clients page (frontend/clients.html + src/pages/clients/) with an AntD table, multi-inbound attach modal, and full CRUD. Axios interceptor learns to honour Content-Type: application/json so the JSON endpoints work alongside the legacy form-encoded ones. The legacy per-inbound client modal stays untouched in this PR — both flows now write to the same source of truth. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
165 lines
4.2 KiB
Go
165 lines
4.2 KiB
Go
package controller
|
|
|
|
import (
|
|
"strconv"
|
|
|
|
"github.com/mhsanaei/3x-ui/v3/database/model"
|
|
"github.com/mhsanaei/3x-ui/v3/web/service"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
type ClientController struct {
|
|
clientService service.ClientService
|
|
inboundService service.InboundService
|
|
xrayService service.XrayService
|
|
}
|
|
|
|
func NewClientController(g *gin.RouterGroup) *ClientController {
|
|
a := &ClientController{}
|
|
a.initRouter(g)
|
|
return a
|
|
}
|
|
|
|
func (a *ClientController) initRouter(g *gin.RouterGroup) {
|
|
g.GET("/list", a.list)
|
|
g.GET("/get/:id", a.get)
|
|
g.POST("/add", a.create)
|
|
g.POST("/update/:id", a.update)
|
|
g.POST("/del/:id", a.delete)
|
|
g.POST("/:id/attach", a.attach)
|
|
g.POST("/:id/detach", a.detach)
|
|
}
|
|
|
|
func (a *ClientController) list(c *gin.Context) {
|
|
rows, err := a.clientService.List()
|
|
if err != nil {
|
|
jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.obtain"), err)
|
|
return
|
|
}
|
|
jsonObj(c, rows, nil)
|
|
}
|
|
|
|
func (a *ClientController) get(c *gin.Context) {
|
|
id, err := strconv.Atoi(c.Param("id"))
|
|
if err != nil {
|
|
jsonMsg(c, I18nWeb(c, "get"), err)
|
|
return
|
|
}
|
|
rec, err := a.clientService.GetByID(id)
|
|
if err != nil {
|
|
jsonMsg(c, I18nWeb(c, "get"), err)
|
|
return
|
|
}
|
|
inboundIds, err := a.clientService.GetInboundIdsForRecord(id)
|
|
if err != nil {
|
|
jsonMsg(c, I18nWeb(c, "get"), err)
|
|
return
|
|
}
|
|
jsonObj(c, gin.H{"client": rec, "inboundIds": inboundIds}, nil)
|
|
}
|
|
|
|
func (a *ClientController) create(c *gin.Context) {
|
|
var payload service.ClientCreatePayload
|
|
if err := c.ShouldBindJSON(&payload); err != nil {
|
|
jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err)
|
|
return
|
|
}
|
|
needRestart, err := a.clientService.Create(&a.inboundService, &payload)
|
|
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 *ClientController) update(c *gin.Context) {
|
|
id, err := strconv.Atoi(c.Param("id"))
|
|
if err != nil {
|
|
jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err)
|
|
return
|
|
}
|
|
var updated model.Client
|
|
if err := c.ShouldBindJSON(&updated); err != nil {
|
|
jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err)
|
|
return
|
|
}
|
|
needRestart, err := a.clientService.Update(&a.inboundService, id, updated)
|
|
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 *ClientController) delete(c *gin.Context) {
|
|
id, err := strconv.Atoi(c.Param("id"))
|
|
if err != nil {
|
|
jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err)
|
|
return
|
|
}
|
|
keepTraffic := c.Query("keepTraffic") == "1"
|
|
needRestart, err := a.clientService.Delete(&a.inboundService, id, keepTraffic)
|
|
if err != nil {
|
|
jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err)
|
|
return
|
|
}
|
|
jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.inboundClientDeleteSuccess"), nil)
|
|
if needRestart {
|
|
a.xrayService.SetToNeedRestart()
|
|
}
|
|
}
|
|
|
|
type attachDetachBody struct {
|
|
InboundIds []int `json:"inboundIds"`
|
|
}
|
|
|
|
func (a *ClientController) attach(c *gin.Context) {
|
|
id, err := strconv.Atoi(c.Param("id"))
|
|
if err != nil {
|
|
jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err)
|
|
return
|
|
}
|
|
var body attachDetachBody
|
|
if err := c.ShouldBindJSON(&body); err != nil {
|
|
jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err)
|
|
return
|
|
}
|
|
needRestart, err := a.clientService.Attach(&a.inboundService, id, body.InboundIds)
|
|
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 *ClientController) detach(c *gin.Context) {
|
|
id, err := strconv.Atoi(c.Param("id"))
|
|
if err != nil {
|
|
jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err)
|
|
return
|
|
}
|
|
var body attachDetachBody
|
|
if err := c.ShouldBindJSON(&body); err != nil {
|
|
jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err)
|
|
return
|
|
}
|
|
needRestart, err := a.clientService.Detach(&a.inboundService, id, body.InboundIds)
|
|
if err != nil {
|
|
jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err)
|
|
return
|
|
}
|
|
jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.inboundClientDeleteSuccess"), nil)
|
|
if needRestart {
|
|
a.xrayService.SetToNeedRestart()
|
|
}
|
|
}
|