feat: add balance display, user columns, device mgmt and sessions for agent

- Backend: add balance to agent stats API
- Backend: return full user details (balance, expiry, online_status, device_count, application) in agent users API
- Backend: export admin device/session handlers, add agent routes for devices and sessions
- Backend: add card type permission check and balance deduction for agent user creation
- Backend: extract checkAgentUserPermission helper, add user edit API
- Frontend: add balance card to agent dashboard
- Frontend: add application, online_status, device_count, balance, expiry_at columns to agent user table
- Frontend: add agent devices page (reuse admin data-table component)
- Frontend: add agent sessions page (reuse admin data-table component)
- Frontend: add devices and sessions to agent navigation and routes
- Add i18n keys for agent devices, sessions, user columns, dashboard balance
This commit is contained in:
2026-05-11 10:46:29 +08:00
parent a76a30ee78
commit 853c22fdbe
12 changed files with 848 additions and 32 deletions
+16 -16
View File
@@ -22,22 +22,22 @@ type DeviceWithDetails struct {
func SetupDeviceRoutes(r *gin.RouterGroup) {
devices := r.Group("/devices")
{
devices.GET("", handleGetDevices)
devices.PUT("/:id/status", handleUpdateDeviceStatus)
devices.DELETE("/:id", handleUnbindDevice)
devices.DELETE("/batch", handleBatchUnbindDevices)
devices.POST("/batch/status", handleBatchUpdateDeviceStatus)
devices.POST("/:id/force-offline", handleForceOfflineDevice)
devices.GET("", HandleGetDevices)
devices.PUT("/:id/status", HandleUpdateDeviceStatus)
devices.DELETE("/:id", HandleUnbindDevice)
devices.DELETE("/batch", HandleBatchUnbindDevices)
devices.POST("/batch/status", HandleBatchUpdateDeviceStatus)
devices.POST("/:id/force-offline", HandleForceOfflineDevice)
}
sessions := r.Group("/sessions")
{
sessions.GET("", handleGetSessions)
sessions.DELETE("/:id", handleDeleteSession)
sessions.GET("", HandleGetSessions)
sessions.DELETE("/:id", HandleDeleteSession)
}
}
func handleGetDevices(c *gin.Context) {
func HandleGetDevices(c *gin.Context) {
userID := c.GetUint("user_id")
log.Printf("[DEBUG] handleGetDevices called, userID: %d", userID)
@@ -180,7 +180,7 @@ func handleGetDevices(c *gin.Context) {
})
}
func handleUpdateDeviceStatus(c *gin.Context) {
func HandleUpdateDeviceStatus(c *gin.Context) {
userID := c.GetUint("user_id")
deviceID := c.Param("id")
@@ -221,7 +221,7 @@ func handleUpdateDeviceStatus(c *gin.Context) {
response.Success(c, device)
}
func handleUnbindDevice(c *gin.Context) {
func HandleUnbindDevice(c *gin.Context) {
userID := c.GetUint("user_id")
deviceID := c.Param("id")
@@ -257,7 +257,7 @@ func handleUnbindDevice(c *gin.Context) {
response.Success(c, nil)
}
func handleBatchUnbindDevices(c *gin.Context) {
func HandleBatchUnbindDevices(c *gin.Context) {
userID := c.GetUint("user_id")
var req struct {
DeviceIDs []uint `json:"device_ids"`
@@ -303,7 +303,7 @@ func handleBatchUnbindDevices(c *gin.Context) {
response.Success(c, nil)
}
func handleBatchUpdateDeviceStatus(c *gin.Context) {
func HandleBatchUpdateDeviceStatus(c *gin.Context) {
userID := c.GetUint("user_id")
var req struct {
DeviceIDs []uint `json:"device_ids"`
@@ -356,7 +356,7 @@ type SessionWithDetails struct {
IsOnline bool `json:"is_online"`
}
func handleGetSessions(c *gin.Context) {
func HandleGetSessions(c *gin.Context) {
userID := c.GetUint("user_id")
deviceIDFilter := c.Query("device_id")
appIDFilter := c.Query("app_id")
@@ -478,7 +478,7 @@ func handleGetSessions(c *gin.Context) {
response.Success(c, gin.H{"sessions": sessionsWithDetails, "total": total, "page": page, "page_size": pageSize})
}
func handleDeleteSession(c *gin.Context) {
func HandleDeleteSession(c *gin.Context) {
userID := c.GetUint("user_id")
sessionID := c.Param("id")
@@ -529,7 +529,7 @@ func handleDeleteSession(c *gin.Context) {
response.Success(c, nil)
}
func handleForceOfflineDevice(c *gin.Context) {
func HandleForceOfflineDevice(c *gin.Context) {
userID := c.GetUint("user_id")
deviceID := c.Param("id")
+1 -1
View File
@@ -47,7 +47,7 @@ func SetupUserRoutes(r *gin.RouterGroup) {
appUsers.PUT("/:id", handleUpdateUser)
appUsers.DELETE("/:id", handleDeleteUser)
appUsers.GET("/:id/devices", handleGetUserDevices)
appUsers.DELETE("/:id/devices/:deviceId", handleUnbindDevice)
appUsers.DELETE("/:id/devices/:deviceId", HandleUnbindDevice)
appUsers.PUT("/:id/expiry", handleUpdateExpiry)
appUsers.POST("/batch/status", handleBatchUpdateStatus)
appUsers.DELETE("/batch", handleBatchDelete)