package controller import ( "encoding/json" "fmt" "time" "github.com/mhsanaei/3x-ui/v3/database/model" "github.com/mhsanaei/3x-ui/v3/web/service" "github.com/mhsanaei/3x-ui/v3/web/websocket" "github.com/gin-gonic/gin" ) func notifyClientsChanged() { websocket.BroadcastInvalidate(websocket.MessageTypeClients) } 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/:email", a.get) g.GET("/traffic/:email", a.getTrafficByEmail) g.GET("/subLinks/:subId", a.getSubLinks) g.GET("/links/:email", a.getClientLinks) g.POST("/add", a.create) g.POST("/update/:email", a.update) g.POST("/del/:email", a.delete) g.POST("/:email/attach", a.attach) g.POST("/:email/detach", a.detach) g.POST("/resetAllTraffics", a.resetAllTraffics) g.POST("/delDepleted", a.delDepleted) g.POST("/resetTraffic/:email", a.resetTrafficByEmail) g.POST("/updateTraffic/:email", a.updateTrafficByEmail) g.POST("/ips/:email", a.getIps) g.POST("/clearIps/:email", a.clearIps) g.POST("/onlines", a.onlines) g.POST("/lastOnline", a.lastOnline) } 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) { email := c.Param("email") rec, err := a.clientService.GetRecordByEmail(nil, email) if err != nil { jsonMsg(c, I18nWeb(c, "get"), err) return } inboundIds, err := a.clientService.GetInboundIdsForRecord(rec.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() } notifyClientsChanged() } func (a *ClientController) update(c *gin.Context) { email := c.Param("email") var updated model.Client if err := c.ShouldBindJSON(&updated); err != nil { jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err) return } needRestart, err := a.clientService.UpdateByEmail(&a.inboundService, email, 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() } notifyClientsChanged() } func (a *ClientController) delete(c *gin.Context) { email := c.Param("email") keepTraffic := c.Query("keepTraffic") == "1" needRestart, err := a.clientService.DeleteByEmail(&a.inboundService, email, 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() } notifyClientsChanged() } type attachDetachBody struct { InboundIds []int `json:"inboundIds"` } func (a *ClientController) attach(c *gin.Context) { email := c.Param("email") var body attachDetachBody if err := c.ShouldBindJSON(&body); err != nil { jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err) return } needRestart, err := a.clientService.AttachByEmail(&a.inboundService, email, 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() } notifyClientsChanged() } func (a *ClientController) resetAllTraffics(c *gin.Context) { needRestart, err := a.clientService.ResetAllTraffics() if err != nil { jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err) return } jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.resetAllClientTrafficSuccess"), nil) if needRestart { a.xrayService.SetToNeedRestart() } notifyClientsChanged() } func (a *ClientController) delDepleted(c *gin.Context) { deleted, needRestart, err := a.clientService.DelDepleted(&a.inboundService) if err != nil { jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err) return } jsonObj(c, gin.H{"deleted": deleted}, nil) if needRestart { a.xrayService.SetToNeedRestart() } notifyClientsChanged() } func (a *ClientController) resetTrafficByEmail(c *gin.Context) { email := c.Param("email") needRestart, err := a.clientService.ResetTrafficByEmail(&a.inboundService, 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() } notifyClientsChanged() } type trafficUpdateRequest struct { Upload int64 `json:"upload"` Download int64 `json:"download"` } func (a *ClientController) updateTrafficByEmail(c *gin.Context) { email := c.Param("email") var req trafficUpdateRequest if err := c.ShouldBindJSON(&req); err != nil { jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err) return } if err := a.inboundService.UpdateClientTrafficByEmail(email, req.Upload, req.Download); err != nil { jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err) return } jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.inboundClientUpdateSuccess"), nil) notifyClientsChanged() } func (a *ClientController) getIps(c *gin.Context) { email := c.Param("email") ips, err := a.inboundService.GetInboundClientIps(email) if err != nil || ips == "" { jsonObj(c, "No IP Record", nil) return } type ipWithTimestamp struct { IP string `json:"ip"` Timestamp int64 `json:"timestamp"` } var ipsWithTime []ipWithTimestamp if err := json.Unmarshal([]byte(ips), &ipsWithTime); err == nil && len(ipsWithTime) > 0 { formatted := make([]string, 0, len(ipsWithTime)) for _, item := range ipsWithTime { if item.IP == "" { continue } if item.Timestamp > 0 { ts := time.Unix(item.Timestamp, 0).Local().Format("2006-01-02 15:04:05") formatted = append(formatted, fmt.Sprintf("%s (%s)", item.IP, ts)) continue } formatted = append(formatted, item.IP) } jsonObj(c, formatted, nil) return } var oldIps []string if err := json.Unmarshal([]byte(ips), &oldIps); err == nil && len(oldIps) > 0 { jsonObj(c, oldIps, nil) return } jsonObj(c, ips, nil) } func (a *ClientController) clearIps(c *gin.Context) { email := c.Param("email") if err := a.inboundService.ClearClientIps(email); err != nil { jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.updateSuccess"), err) return } jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.logCleanSuccess"), nil) } func (a *ClientController) onlines(c *gin.Context) { jsonObj(c, a.inboundService.GetOnlineClients(), nil) } func (a *ClientController) lastOnline(c *gin.Context) { data, err := a.inboundService.GetClientsLastOnline() jsonObj(c, data, err) } func (a *ClientController) getTrafficByEmail(c *gin.Context) { email := c.Param("email") traffic, err := a.inboundService.GetClientTrafficByEmail(email) if err != nil { jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.trafficGetError"), err) return } jsonObj(c, traffic, nil) } func (a *ClientController) getSubLinks(c *gin.Context) { links, err := a.inboundService.GetSubLinks(resolveHost(c), c.Param("subId")) if err != nil { jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.obtain"), err) return } jsonObj(c, links, nil) } func (a *ClientController) getClientLinks(c *gin.Context) { links, err := a.inboundService.GetAllClientLinks(resolveHost(c), c.Param("email")) if err != nil { jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.obtain"), err) return } jsonObj(c, links, nil) } func (a *ClientController) detach(c *gin.Context) { email := c.Param("email") var body attachDetachBody if err := c.ShouldBindJSON(&body); err != nil { jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err) return } needRestart, err := a.clientService.DetachByEmailMany(&a.inboundService, email, 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() } notifyClientsChanged() }