From 7f83401b2e61eeeba8c95e7320e1afc203295cfd Mon Sep 17 00:00:00 2001 From: engigu Date: Tue, 30 Dec 2025 20:29:04 +0800 Subject: [PATCH] fix: agent page and agent cmd --- internal/controllers/agent_controller.go | 24 +++---- internal/database/migrate.go | 13 ++-- internal/models/agent.go | 18 ++--- internal/router/router.go | 6 +- internal/services/agent_service.go | 89 +++++++++++------------- web/src/api/index.ts | 12 ++-- web/src/views/agents/Agents.vue | 82 +++++++++++----------- 7 files changed, 119 insertions(+), 125 deletions(-) diff --git a/internal/controllers/agent_controller.go b/internal/controllers/agent_controller.go index 17e9643..dc4d127 100644 --- a/internal/controllers/agent_controller.go +++ b/internal/controllers/agent_controller.go @@ -519,16 +519,16 @@ func (c *AgentController) NotifyTaskUpdate(agentID uint) { c.wsManager.BroadcastTasks(agentID) } -// ========== 注册码管理 ========== +// ========== 令牌管理 ========== -// ListRegCodes 获取注册码列表 -func (c *AgentController) ListRegCodes(ctx *gin.Context) { - codes := c.agentService.ListRegCodes() - utils.Success(ctx, codes) +// ListTokens 获取令牌列表 +func (c *AgentController) ListTokens(ctx *gin.Context) { + tokens := c.agentService.ListTokens() + utils.Success(ctx, tokens) } -// CreateRegCode 创建注册码 -func (c *AgentController) CreateRegCode(ctx *gin.Context) { +// CreateToken 创建令牌 +func (c *AgentController) CreateToken(ctx *gin.Context) { var req struct { Remark string `json:"remark"` MaxUses int `json:"max_uses"` @@ -550,24 +550,24 @@ func (c *AgentController) CreateRegCode(ctx *gin.Context) { expiresAt = &t } - code, err := c.agentService.CreateRegCode(req.Remark, req.MaxUses, expiresAt) + token, err := c.agentService.CreateToken(req.Remark, req.MaxUses, expiresAt) if err != nil { utils.ServerError(ctx, err.Error()) return } - utils.Success(ctx, code) + utils.Success(ctx, token) } -// DeleteRegCode 删除注册码 -func (c *AgentController) DeleteRegCode(ctx *gin.Context) { +// DeleteToken 删除令牌 +func (c *AgentController) DeleteToken(ctx *gin.Context) { id, err := strconv.ParseUint(ctx.Param("id"), 10, 32) if err != nil { utils.BadRequest(ctx, "无效的 ID") return } - if err := c.agentService.DeleteRegCode(uint(id)); err != nil { + if err := c.agentService.DeleteToken(uint(id)); err != nil { utils.ServerError(ctx, err.Error()) return } diff --git a/internal/database/migrate.go b/internal/database/migrate.go index c27eced..b13b2ba 100644 --- a/internal/database/migrate.go +++ b/internal/database/migrate.go @@ -22,18 +22,19 @@ func Migrate() error { &models.SendStats{}, &models.Dependency{}, &models.Agent{}, - &models.AgentRegCode{}, + &models.AgentToken{}, ) } // customMigrations 自定义迁移(处理 AutoMigrate 无法自动完成的变更) func customMigrations() error { - // 检查 ql_tokens 表是否存在,如果存在则修改 code 列大小为 64 + // 检查 ql_tokens 表是否存在 if DB.Migrator().HasTable("ql_tokens") { - // MySQL: 修改 code 列大小 - if err := DB.Exec("ALTER TABLE ql_tokens MODIFY COLUMN code VARCHAR(64)").Error; err != nil { - // 忽略错误(可能是 SQLite 或列已经是正确大小) - logger.Debugf("[Database] 修改 ql_tokens.code 列: %v", err) + // 将 code 列重命名为 token(如果 code 列存在) + if DB.Migrator().HasColumn(&models.AgentToken{}, "code") { + if err := DB.Migrator().RenameColumn(&models.AgentToken{}, "code", "token"); err != nil { + logger.Debugf("[Database] 重命名 ql_tokens.code 列: %v", err) + } } } return nil diff --git a/internal/models/agent.go b/internal/models/agent.go index 722b7b8..253a3a9 100644 --- a/internal/models/agent.go +++ b/internal/models/agent.go @@ -32,21 +32,21 @@ func (Agent) TableName() string { return constant.TablePrefix + "agents" } -// AgentRegCode 注册码 -type AgentRegCode struct { +// AgentToken Agent 令牌 +type AgentToken struct { ID uint `json:"id" gorm:"primaryKey"` - Code string `json:"code" gorm:"size:64;uniqueIndex;not null"` // 令牌 - Remark string `json:"remark" gorm:"size:255"` // 备注 - MaxUses int `json:"max_uses" gorm:"default:0"` // 最大使用次数,0 表示无限制 - UsedCount int `json:"used_count" gorm:"default:0"` // 已使用次数 - ExpiresAt *LocalTime `json:"expires_at"` // 过期时间,null 表示永不过期 - Enabled bool `json:"enabled" gorm:"default:true"` // 是否启用 + Token string `json:"token" gorm:"size:64;uniqueIndex;not null"` // 令牌 + Remark string `json:"remark" gorm:"size:255"` // 备注 + MaxUses int `json:"max_uses" gorm:"default:0"` // 最大使用次数,0 表示无限制 + UsedCount int `json:"used_count" gorm:"default:0"` // 已使用次数 + ExpiresAt *LocalTime `json:"expires_at"` // 过期时间,null 表示永不过期 + Enabled bool `json:"enabled" gorm:"default:true"` // 是否启用 CreatedAt LocalTime `json:"created_at"` UpdatedAt LocalTime `json:"updated_at"` DeletedAt gorm.DeletedAt `json:"-" gorm:"index"` } -func (AgentRegCode) TableName() string { +func (AgentToken) TableName() string { return constant.TablePrefix + "tokens" } diff --git a/internal/router/router.go b/internal/router/router.go index 1b6110c..accc83f 100644 --- a/internal/router/router.go +++ b/internal/router/router.go @@ -209,9 +209,9 @@ func Setup(c *Controllers) *gin.Engine { agents.POST("/:id/token", c.Agent.RegenerateToken) agents.POST("/:id/update", c.Agent.ForceUpdate) // 令牌管理 - agents.GET("/regcodes", c.Agent.ListRegCodes) - agents.POST("/regcodes", c.Agent.CreateRegCode) - agents.DELETE("/regcodes/:id", c.Agent.DeleteRegCode) + agents.GET("/tokens", c.Agent.ListTokens) + agents.POST("/tokens", c.Agent.CreateToken) + agents.DELETE("/tokens/:id", c.Agent.DeleteToken) } } diff --git a/internal/services/agent_service.go b/internal/services/agent_service.go index 1aec1d0..2a97743 100644 --- a/internal/services/agent_service.go +++ b/internal/services/agent_service.go @@ -24,87 +24,80 @@ func NewAgentService() *AgentService { return &AgentService{} } -// generateToken 生成随机 Token +// generateToken 生成随机 Token(64位十六进制) func generateToken() string { bytes := make([]byte, 32) rand.Read(bytes) return hex.EncodeToString(bytes) } -// generateRegCode 生成令牌(64位,与认证 Token 相同) -func generateRegCode() string { - bytes := make([]byte, 32) - rand.Read(bytes) - return hex.EncodeToString(bytes) -} +// ========== 令牌管理 ========== -// ========== 注册码管理 ========== - -// CreateRegCode 创建令牌(同时创建 Agent 记录) -func (s *AgentService) CreateRegCode(remark string, maxUses int, expiresAt *time.Time) (*models.AgentRegCode, error) { +// CreateToken 创建令牌 +func (s *AgentService) CreateToken(remark string, maxUses int, expiresAt *time.Time) (*models.AgentToken, error) { var expires *models.LocalTime if expiresAt != nil { t := models.LocalTime(*expiresAt) expires = &t } - token := generateRegCode() + token := generateToken() - regCode := &models.AgentRegCode{ - Code: token, + agentToken := &models.AgentToken{ + Token: token, Remark: remark, MaxUses: maxUses, ExpiresAt: expires, Enabled: true, } - if err := database.DB.Create(regCode).Error; err != nil { + if err := database.DB.Create(agentToken).Error; err != nil { return nil, err } logger.Infof("[Agent] 创建令牌: %s (max_uses=%d)", token[:8]+"...", maxUses) - return regCode, nil + return agentToken, nil } -// ListRegCodes 获取注册码列表 -func (s *AgentService) ListRegCodes() []models.AgentRegCode { - var codes []models.AgentRegCode - database.DB.Order("id DESC").Find(&codes) - return codes +// ListTokens 获取令牌列表 +func (s *AgentService) ListTokens() []models.AgentToken { + var tokens []models.AgentToken + database.DB.Order("id DESC").Find(&tokens) + return tokens } -// DeleteRegCode 删除注册码 -func (s *AgentService) DeleteRegCode(id uint) error { - return database.DB.Delete(&models.AgentRegCode{}, id).Error +// DeleteToken 删除令牌 +func (s *AgentService) DeleteToken(id uint) error { + return database.DB.Delete(&models.AgentToken{}, id).Error } -// ValidateRegCode 验证注册码 -func (s *AgentService) ValidateRegCode(code string) (*models.AgentRegCode, error) { - var regCode models.AgentRegCode - if err := database.DB.Where("code = ?", code).First(®Code).Error; err != nil { - return nil, &ServiceError{Message: "无效的注册码"} +// ValidateToken 验证令牌 +func (s *AgentService) ValidateToken(token string) (*models.AgentToken, error) { + var agentToken models.AgentToken + if err := database.DB.Where("token = ?", token).First(&agentToken).Error; err != nil { + return nil, &ServiceError{Message: "无效的令牌"} } - if !regCode.Enabled { - return nil, &ServiceError{Message: "注册码已禁用"} + if !agentToken.Enabled { + return nil, &ServiceError{Message: "令牌已禁用"} } // 检查使用次数 - if regCode.MaxUses > 0 && regCode.UsedCount >= regCode.MaxUses { - return nil, &ServiceError{Message: "注册码已达到使用上限"} + if agentToken.MaxUses > 0 && agentToken.UsedCount >= agentToken.MaxUses { + return nil, &ServiceError{Message: "令牌已达到使用上限"} } // 检查过期时间 - if regCode.ExpiresAt != nil && time.Time(*regCode.ExpiresAt).Before(time.Now()) { - return nil, &ServiceError{Message: "注册码已过期"} + if agentToken.ExpiresAt != nil && time.Time(*agentToken.ExpiresAt).Before(time.Now()) { + return nil, &ServiceError{Message: "令牌已过期"} } - return ®Code, nil + return &agentToken, nil } -// UseRegCode 使用注册码(增加使用计数) -func (s *AgentService) UseRegCode(id uint) { - database.DB.Model(&models.AgentRegCode{}).Where("id = ?", id).UpdateColumn("used_count", gorm.Expr("used_count + 1")) +// UseToken 使用令牌(增加使用计数) +func (s *AgentService) UseToken(id uint) { + database.DB.Model(&models.AgentToken{}).Where("id = ?", id).UpdateColumn("used_count", gorm.Expr("used_count + 1")) } // ========== Agent 注册 ========== @@ -113,7 +106,7 @@ func (s *AgentService) UseRegCode(id uint) { // 返回: agent, isNewAgent, error func (s *AgentService) RegisterByToken(token string, machineID string, ip string) (*models.Agent, bool, error) { // 验证令牌 - regCode, err := s.ValidateRegCode(token) + agentToken, err := s.ValidateToken(token) if err != nil { return nil, false, err } @@ -125,12 +118,12 @@ func (s *AgentService) RegisterByToken(token string, machineID string, ip string // 已存在,更新 token 和状态,复用已有 Agent now := models.LocalTime(time.Now()) database.DB.Model(&existing).Updates(map[string]interface{}{ - "token": token, - "ip": ip, - "status": "online", + "token": token, + "ip": ip, + "status": "online", "last_seen": now, }) - s.UseRegCode(regCode.ID) + s.UseToken(agentToken.ID) logger.Infof("[Agent] Agent #%d 通过 machine_id 复用 (%s)", existing.ID, machineID[:8]+"...") return &existing, false, nil } @@ -152,7 +145,7 @@ func (s *AgentService) RegisterByToken(token string, machineID string, ip string return nil, false, err } - s.UseRegCode(regCode.ID) + s.UseToken(agentToken.ID) logger.Infof("[Agent] Agent 通过令牌注册: #%d (%s)", agent.ID, ip) return agent, true, nil } @@ -161,10 +154,10 @@ func (s *AgentService) RegisterByToken(token string, machineID string, ip string func (s *AgentService) Register(req *models.AgentRegisterRequest, ip string) (*models.Agent, string, error) { // 必须提供令牌 if req.Token == "" { - return nil, "", &ServiceError{Message: "缺少注册令牌"} + return nil, "", &ServiceError{Message: "缺少令牌"} } - regCode, err := s.ValidateRegCode(req.Token) + agentToken, err := s.ValidateToken(req.Token) if err != nil { return nil, "", err } @@ -193,7 +186,7 @@ func (s *AgentService) Register(req *models.AgentRegisterRequest, ip string) (*m return nil, "", err } - s.UseRegCode(regCode.ID) + s.UseToken(agentToken.ID) logger.Infof("[Agent] Agent 注册成功: %s (%s)", req.Name, ip) return agent, req.Token, nil } diff --git a/web/src/api/index.ts b/web/src/api/index.ts index 102834b..0071ef0 100644 --- a/web/src/api/index.ts +++ b/web/src/api/index.ts @@ -217,10 +217,10 @@ export const api = { forceUpdate: (id: number) => request('/agents/' + id + '/update', { method: 'POST' }), downloadUrl: (os: string, arch: string) => `${BASE_URL}/agent/download?os=${os}&arch=${arch}`, // 令牌管理 - listRegCodes: () => request('/agents/regcodes'), - createRegCode: (data: { remark?: string; max_uses?: number; expires_at?: string }) => - request('/agents/regcodes', { method: 'POST', body: JSON.stringify(data) }), - deleteRegCode: (id: number) => request('/agents/regcodes/' + id, { method: 'DELETE' }) + listTokens: () => request('/agents/tokens'), + createToken: (data: { remark?: string; max_uses?: number; expires_at?: string }) => + request('/agents/tokens', { method: 'POST', body: JSON.stringify(data) }), + deleteToken: (id: number) => request('/agents/tokens/' + id, { method: 'DELETE' }) } } @@ -411,9 +411,9 @@ export interface Agent { updated_at: string } -export interface AgentRegCode { +export interface AgentToken { id: number - code: string + token: string remark: string max_uses: number used_count: number diff --git a/web/src/views/agents/Agents.vue b/web/src/views/agents/Agents.vue index 719a451..cd09fe4 100644 --- a/web/src/views/agents/Agents.vue +++ b/web/src/views/agents/Agents.vue @@ -7,14 +7,14 @@ import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogD import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from '@/components/ui/alert-dialog' import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' import { RefreshCw, Trash2, Edit, Copy, Server, Search, Download, RotateCw, Plus, Ticket, Power, PowerOff, ListTodo, Eye } from 'lucide-vue-next' -import { api, type Agent, type AgentRegCode } from '@/api' +import { api, type Agent, type AgentToken } from '@/api' import { toast } from 'vue-sonner' import { useRouter } from 'vue-router' const router = useRouter() const agents = ref([]) -const regCodes = ref([]) +const tokens = ref([]) const loading = ref(false) const searchQuery = ref('') const activeTab = ref('agents') @@ -23,10 +23,10 @@ const platforms = ref<{ os: string; arch: string; filename: string }[]>([]) const showEditDialog = ref(false) const showDeleteDialog = ref(false) const showDownloadDialog = ref(false) -const showRegCodeDialog = ref(false) +const showTokenDialog = ref(false) const showDetailDialog = ref(false) const formData = ref({ name: '', description: '' }) -const regCodeForm = ref({ remark: '', max_uses: 0, expires_at: '' }) +const tokenForm = ref({ remark: '', max_uses: 0, expires_at: '' }) const editingAgent = ref(null) const deletingAgent = ref(null) const viewingAgent = ref(null) @@ -53,15 +53,15 @@ function isOnline(agent: Agent): boolean { async function loadAgents() { loading.value = true try { - const [agentList, versionInfo, codeList] = await Promise.all([ + const [agentList, versionInfo, tokenList] = await Promise.all([ api.agents.list(), api.agents.getVersion(), - api.agents.listRegCodes() + api.agents.listTokens() ]) agents.value = agentList agentVersion.value = versionInfo.version || '' platforms.value = versionInfo.platforms || [] - regCodes.value = codeList + tokens.value = tokenList } catch { toast.error('加载失败') } finally { @@ -133,20 +133,20 @@ function viewTasks(agent: Agent) { router.push({ path: '/tasks', query: { agent_id: String(agent.id) } }) } -function copyRegCode(code: string) { - navigator.clipboard.writeText(code) +function copyToken(token: string) { + navigator.clipboard.writeText(token) toast.success('已复制') } -async function createRegCode() { +async function createToken() { try { - await api.agents.createRegCode({ - remark: regCodeForm.value.remark, - max_uses: regCodeForm.value.max_uses, - expires_at: regCodeForm.value.expires_at || undefined + await api.agents.createToken({ + remark: tokenForm.value.remark, + max_uses: tokenForm.value.max_uses, + expires_at: tokenForm.value.expires_at || undefined }) - showRegCodeDialog.value = false - regCodeForm.value = { remark: '', max_uses: 0, expires_at: '' } + showTokenDialog.value = false + tokenForm.value = { remark: '', max_uses: 0, expires_at: '' } await loadAgents() toast.success('创建成功') } catch (e: unknown) { @@ -154,9 +154,9 @@ async function createRegCode() { } } -async function deleteRegCode(id: number) { +async function deleteToken(id: number) { try { - await api.agents.deleteRegCode(id) + await api.agents.deleteToken(id) await loadAgents() toast.success('删除成功') } catch (e: unknown) { @@ -164,13 +164,13 @@ async function deleteRegCode(id: number) { } } -function isRegCodeExpired(code: AgentRegCode) { - if (!code.expires_at) return false - return new Date(code.expires_at) < new Date() +function isTokenExpired(token: AgentToken) { + if (!token.expires_at) return false + return new Date(token.expires_at) < new Date() } -function isRegCodeExhausted(code: AgentRegCode) { - return code.max_uses > 0 && code.used_count >= code.max_uses +function isTokenExhausted(token: AgentToken) { + return token.max_uses > 0 && token.used_count >= token.max_uses } function downloadAgent(os: string, arch: string) { @@ -290,35 +290,35 @@ onUnmounted(() => { 使用次数 -
-
+
暂无令牌
-
+
- - + + - {{ code.code }} - {{ code.remark || '-' }} + {{ token.token }} + {{ token.remark || '-' }} - {{ code.used_count }}/{{ code.max_uses === 0 ? '∞' : code.max_uses }} + {{ token.used_count }}/{{ token.max_uses === 0 ? '∞' : token.max_uses }} - - @@ -478,28 +478,28 @@ onUnmounted(() => { - + - 生成注册令牌 + 生成令牌
- +
- +
- +
- - + +