mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-05-29 16:39:35 +00:00
- Bulk-attach an inbound's clients onto other inbounds (same identity, shared traffic): new ClientService.BulkAttach + POST /clients/bulkAttach, an inbound row action, and AttachClientsModal. - Assign all of an inbound's clients to a group from the inbound page, reusing /clients/bulkAssignGroup and the existing BulkAssignGroupModal. - Default a random user/pass account for new Mixed and HTTP inbounds instead of an empty accounts list. - Capitalize the inbound Security toggle labels (None/TLS/Reality).
547 lines
15 KiB
Go
547 lines
15 KiB
Go
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
|
|
settingService service.SettingService
|
|
}
|
|
|
|
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("/list/paged", a.listPaged)
|
|
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("/bulkAdjust", a.bulkAdjust)
|
|
g.POST("/bulkDel", a.bulkDelete)
|
|
g.POST("/bulkCreate", a.bulkCreate)
|
|
g.POST("/bulkAssignGroup", a.bulkAssignGroup)
|
|
g.POST("/bulkAttach", a.bulkAttach)
|
|
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)
|
|
|
|
g.GET("/groups", a.listGroups)
|
|
g.GET("/groups/:name/emails", a.groupEmails)
|
|
g.POST("/groups/create", a.createGroup)
|
|
g.POST("/groups/rename", a.renameGroup)
|
|
g.POST("/groups/delete", a.deleteGroup)
|
|
g.POST("/bulkResetTraffic", a.bulkResetTraffic)
|
|
}
|
|
|
|
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) listPaged(c *gin.Context) {
|
|
var params service.ClientPageParams
|
|
if err := c.ShouldBindQuery(¶ms); err != nil {
|
|
jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.obtain"), err)
|
|
return
|
|
}
|
|
resp, err := a.clientService.ListPaged(&a.inboundService, &a.settingService, params)
|
|
if err != nil {
|
|
jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.obtain"), err)
|
|
return
|
|
}
|
|
jsonObj(c, resp, 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()
|
|
}
|
|
|
|
type bulkAdjustRequest struct {
|
|
Emails []string `json:"emails"`
|
|
AddDays int `json:"addDays"`
|
|
AddBytes int64 `json:"addBytes"`
|
|
}
|
|
|
|
func (a *ClientController) bulkAdjust(c *gin.Context) {
|
|
var req bulkAdjustRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err)
|
|
return
|
|
}
|
|
result, needRestart, err := a.clientService.BulkAdjust(&a.inboundService, req.Emails, req.AddDays, req.AddBytes)
|
|
if err != nil {
|
|
jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err)
|
|
return
|
|
}
|
|
jsonObj(c, result, nil)
|
|
if needRestart {
|
|
a.xrayService.SetToNeedRestart()
|
|
}
|
|
notifyClientsChanged()
|
|
}
|
|
|
|
type bulkDeleteRequest struct {
|
|
Emails []string `json:"emails"`
|
|
KeepTraffic bool `json:"keepTraffic"`
|
|
}
|
|
|
|
type bulkAssignGroupRequest struct {
|
|
Emails []string `json:"emails"`
|
|
Group string `json:"group"`
|
|
}
|
|
|
|
func (a *ClientController) bulkAssignGroup(c *gin.Context) {
|
|
var req bulkAssignGroupRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err)
|
|
return
|
|
}
|
|
affected, err := a.clientService.AssignGroup(req.Emails, req.Group)
|
|
if err != nil {
|
|
jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err)
|
|
return
|
|
}
|
|
jsonObj(c, gin.H{"affected": affected}, nil)
|
|
a.xrayService.SetToNeedRestart()
|
|
notifyClientsChanged()
|
|
}
|
|
|
|
type bulkAttachRequest struct {
|
|
Emails []string `json:"emails"`
|
|
InboundIds []int `json:"inboundIds"`
|
|
}
|
|
|
|
func (a *ClientController) bulkAttach(c *gin.Context) {
|
|
var req bulkAttachRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err)
|
|
return
|
|
}
|
|
result, needRestart, err := a.clientService.BulkAttach(&a.inboundService, req.Emails, req.InboundIds)
|
|
if err != nil {
|
|
jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err)
|
|
return
|
|
}
|
|
jsonObj(c, result, nil)
|
|
if needRestart {
|
|
a.xrayService.SetToNeedRestart()
|
|
}
|
|
notifyClientsChanged()
|
|
}
|
|
|
|
func (a *ClientController) bulkDelete(c *gin.Context) {
|
|
var req bulkDeleteRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err)
|
|
return
|
|
}
|
|
result, needRestart, err := a.clientService.BulkDelete(&a.inboundService, req.Emails, req.KeepTraffic)
|
|
if err != nil {
|
|
jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err)
|
|
return
|
|
}
|
|
jsonObj(c, result, nil)
|
|
if needRestart {
|
|
a.xrayService.SetToNeedRestart()
|
|
}
|
|
notifyClientsChanged()
|
|
}
|
|
|
|
func (a *ClientController) bulkCreate(c *gin.Context) {
|
|
var payloads []service.ClientCreatePayload
|
|
if err := c.ShouldBindJSON(&payloads); err != nil {
|
|
jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err)
|
|
return
|
|
}
|
|
result, needRestart, err := a.clientService.BulkCreate(&a.inboundService, payloads)
|
|
if err != nil {
|
|
jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err)
|
|
return
|
|
}
|
|
jsonObj(c, result, 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()
|
|
}
|
|
|
|
func (a *ClientController) listGroups(c *gin.Context) {
|
|
rows, err := a.clientService.ListGroups()
|
|
if err != nil {
|
|
jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err)
|
|
return
|
|
}
|
|
jsonObj(c, rows, nil)
|
|
}
|
|
|
|
func (a *ClientController) groupEmails(c *gin.Context) {
|
|
name := c.Param("name")
|
|
emails, err := a.clientService.EmailsByGroup(name)
|
|
if err != nil {
|
|
jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err)
|
|
return
|
|
}
|
|
jsonObj(c, emails, nil)
|
|
}
|
|
|
|
type bulkResetRequest struct {
|
|
Emails []string `json:"emails"`
|
|
}
|
|
|
|
func (a *ClientController) bulkResetTraffic(c *gin.Context) {
|
|
var req bulkResetRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err)
|
|
return
|
|
}
|
|
affected, err := a.clientService.BulkResetTraffic(&a.inboundService, req.Emails)
|
|
if err != nil {
|
|
jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err)
|
|
return
|
|
}
|
|
jsonObj(c, gin.H{"affected": affected}, nil)
|
|
a.xrayService.SetToNeedRestart()
|
|
notifyClientsChanged()
|
|
}
|
|
|
|
type groupCreateBody struct {
|
|
Name string `json:"name"`
|
|
}
|
|
|
|
func (a *ClientController) createGroup(c *gin.Context) {
|
|
var body groupCreateBody
|
|
if err := c.ShouldBindJSON(&body); err != nil {
|
|
jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err)
|
|
return
|
|
}
|
|
if err := a.clientService.CreateGroup(body.Name); err != nil {
|
|
jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err)
|
|
return
|
|
}
|
|
jsonObj(c, gin.H{"name": body.Name}, nil)
|
|
notifyClientsChanged()
|
|
}
|
|
|
|
type groupRenameBody struct {
|
|
OldName string `json:"oldName"`
|
|
NewName string `json:"newName"`
|
|
}
|
|
|
|
func (a *ClientController) renameGroup(c *gin.Context) {
|
|
var body groupRenameBody
|
|
if err := c.ShouldBindJSON(&body); err != nil {
|
|
jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err)
|
|
return
|
|
}
|
|
affected, err := a.clientService.RenameGroup(body.OldName, body.NewName)
|
|
if err != nil {
|
|
jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err)
|
|
return
|
|
}
|
|
a.xrayService.SetToNeedRestart()
|
|
jsonObj(c, gin.H{"affected": affected}, nil)
|
|
notifyClientsChanged()
|
|
}
|
|
|
|
type groupDeleteBody struct {
|
|
Name string `json:"name"`
|
|
}
|
|
|
|
func (a *ClientController) deleteGroup(c *gin.Context) {
|
|
var body groupDeleteBody
|
|
if err := c.ShouldBindJSON(&body); err != nil {
|
|
jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err)
|
|
return
|
|
}
|
|
affected, err := a.clientService.DeleteGroup(body.Name)
|
|
if err != nil {
|
|
jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err)
|
|
return
|
|
}
|
|
a.xrayService.SetToNeedRestart()
|
|
jsonObj(c, gin.H{"affected": affected}, nil)
|
|
notifyClientsChanged()
|
|
}
|