管理后台 Agent 引用统一显示名称并可跳转
管理后台 Agent 引用统一显示名称并可跳转
- 标题:后台所有引用 Agent 的列表与详情显示 Agent 名称,点击进入代理节点详情
- 目标分支:
main - 相关记录:实施计划
摘要
管理后台此前在多个列表里把 agent_id 原样打印成裸 ID(托管路由列表、客户端详情的关联
Agent 与监听入口、服务令牌的绑定列与详情、审计日志中 resourceType=agent 的资源 ID),
另有一些位置只显示名称但没有任何回到 Agent 的入口(VPN peer 列表与详情、远程服务器列表与
详情)。运维要判断“这条路由/这个令牌到底挂在哪个节点上”只能读一串 ID,或者切回代理节点
页面对号。
本次把 Agent 引用的展示收敛到两个出口:
web/src/stores/agentIndex.ts:控制台内唯一一份 Agent 身份索引(Pinia setup store), 加载复用既有loadAgentsForSelection(getAgents)游标分页;并发去重、失败后静默降级为 裸 ID 且不再自动重试、refresh()就地覆盖且成功前不清空旧名称。web/src/components/AgentRef.vue:唯一渲染出口,router-link指向/agents/{id}, 标签优先级为“调用方显式传入的名称 > 索引命中的名称 > 原始 ID”,title恒为完整 ID, 未解析时保持等宽 ID 观感。
随后 7 个视图改为使用该组件,并把四处各自下载 Agent 清单的重复请求收敛到共享索引;代理节点
页改名后显式 refresh(),其他页面下一次渲染即可看到新名称。客户端列表的 Agent 筛选由自由
文本框换成按名称/ID 可搜索的下拉框,提交给服务端的值仍是精确 Agent ID。
改动全部在前端展示层:不修改 Go 代码、API、OpenAPI、DDL、SchemaVersion 与协议帧,因此
不触发 Schema 升级与回滚流程。
用户影响
- 托管路由列表、客户端详情(关联 Agent 与监听入口)、服务令牌绑定列与详情、VPN peer 列表与 详情、远程服务器列表与详情、审计日志的 agent 资源行统一显示 Agent 名称,单击进入该节点详情。
- 名称没有唯一约束,因此 ID 仍是权威标识:悬停显示完整 ID,筛选值、请求参数、排序与分页语义 全部不变;审计日志中非 agent 资源类型继续显示原始 ID 与复制按钮。
- 读不到的 Agent(无权限或已删除)与索引请求失败时回退显示原始 ID,链接仍然可点,不会把一行 数据隐藏或直接报错。
- 打开任意一个含 Agent 引用的页面,Agent 清单只请求一次;不展开客户端页的 Agent 下拉就不请求。
API / Schema / 配置影响
- 无 API、OpenAPI、配置项与 Schema 版本变更;
GET /api/v1/agents的权限过滤保证索引不会 暴露调用者本来看不到的 Agent。 - 新增前端模块:
web/src/stores/agentIndex.ts、web/src/components/AgentRef.vue;web/src/components.d.ts为unplugin-vue-components自动生成。 - 新增 i18n 键
clients.searchAgents(zh输入名称或 ID 搜索/ enSearch by name or ID), 与既有tokens.searchAgents、vpn.form.searchAgents同措辞。 - 移除
VpnPeers.vue的本地agentName()与RemoteServers.vue的本地agentName(row), 以及Tokens.vue的formatAgentScope()字符串拼接路径(改为bindingAgentIds()返回 ID 数组)。 - 活文档同步:
docs/user-guide/server-admin.md的 Agent 列表、Client 观测、托管路由、远程服务器、 VPN peer、审计日志与 Service Tokens 段落。
安全与授权影响
- 不新增数据来源:索引读的仍是权限过滤后的
GET /api/v1/agents,跳转目标/agents/:id为既有 路由,越权访问由服务端详情接口拒绝。 - 名称来自服务端既有字段;远程服务器继续优先使用服务端投影的
agentName,前端不做二次判定。 - 日志、URL 与请求参数不新增任何 secret;Agent ID 本就可读,未扩大可见范围。
测试证据
红灯(实现前实测输出):
FAIL src/tests/agent-index.spec.ts Failed to resolve import "../stores/agentIndex"
FAIL src/tests/agent-ref.spec.ts Failed to resolve import "../components/AgentRef.vue"
FAIL routes.spec.ts > expected '<template>…' to contain '<AgentRef :agent-id="row.agentId" />'
FAIL clients-view.spec.ts > expected undefined to be 'edge-01'
FAIL agent-token-workflows.spec.ts > expected '<template>…' to contain '<AgentRef'
FAIL agent-token-workflows.spec.ts > expected '<template>…' to contain 'bindingAgentIds'
FAIL vpn.spec.ts > expected undefined to be 'edge-01'
FAIL remote-servers-view.spec.ts > expected '<template>…' not to contain 'getAgents({ limit: 200 })'
FAIL audit-logs-view.spec.ts > expected undefined to be truthy
FAIL agents-view.spec.ts > expected "spy" to be called with arguments: [ { cursor: undefined, limit: 500 } ]
FAIL routes.spec.ts > expected '<template>…' to contain 'useAgentIndex'
绿灯与门禁:
cd web && npm test -- --run:66 个文件 503 项全部通过,含新增agent-index.spec.ts(6 项) 与agent-ref.spec.ts(6 项)。- 行为断言覆盖:名称与
href=/agents/{id}、title为完整 ID、显式名称优先于索引、解析失败回退 裸 ID 且仍可点击、一页 12 个引用只发一次清单请求、失败后不重试、改名后索引重新拉取、客户端 筛选选中后以agentId: 'agent-edge'查询、审计行仅 agent 类型变为名称链接。 npm run build通过,./scripts/verify-web-embed.sh报告web/dist与internal/server/web_dist一致。git diff --check干净。Go 侧无改动,go test ./.../go vet ./...不在本次范围。vue-tsc不是本仓库门禁;相对改动前基线新增 2 条Tokens.vue的DefaultRow → ServiceToken,与同文件其余 10 条同类 helper 调用完全同源(Element Plus 表格 作用域行类型),未放宽 helper 签名。
发布步骤
- 变更只影响管理后台静态资源,随 Server 二进制或镜像发布,无 DB、无配置、无迁移。
- 发布后先核对三个页面:客户端详情(名称 + 跳转)、托管路由列表、审计日志(agent 行为名称链接、 其余资源类型仍为可复制 ID)。
- 再核对降级路径:把一个无权访问的 Agent 引用留在页面上(或用 DevTools 阻断
/api/v1/agents), 确认显示为裸 ID、链接仍可点、不出现空白单元格。 - 观察
GET /api/v1/agents?limit=500的调用量:每个控制台会话应为 1 次,而不是此前四页各拉一份。
回滚步骤
git revert本次提交后重新cd web && npm run build并./scripts/verify-web-embed.sh, 替换web_dist并重启 Server 即可,5 分钟内完成;无数据与配置补偿。- 局部止损:任务 3–7 的每个视图相互独立,可单独把
<AgentRef>还原为原prop=列;索引与新组件 不需要回退。 - 若出现索引请求风暴或名称错乱,优先回退任务 8(四处加载收敛 + 改名后
refresh()),它是唯一带 行为副作用的改动;回退后视图继续各自loadAgentsForSelection。
Reviewer 关注点
agentIndex的error是终态:失败后不再自动重试,降级为裸 ID,恢复依赖用户刷新页面或Agents.vue的显式refresh()。请确认这个取舍(列表页可能有上百个引用单元,重试风暴比降级更难排查)。- 模块级
inflight变量做并发去重,跨 store 实例共享,settled 后立即置空;请确认 Pinia 的 HMR/多实例场景不会残留。 - 空引用的渲染选择:
detailClient.agentIds为空时渲染<AgentRef :agent-id="''" />复用组件的—,而不是在视图里重复一份 em dash 标记。 - 客户端筛选的清单请求延后到下拉框展开(
@visible-change)而非挂载即拉取,代价是首次展开可能有 一次网络等待;换来的是不碰筛选的会话零额外请求。 Agents.vue改名后Promise.all([load(), agentIndex.refresh()])是两次独立分页拉取,未做 “就地改名单条”优化;若清单规模成为问题,应在服务端提供按 ID 的批量投影而不是前端拼接。- 名称可重复(
migrations/ddl.sql的agents.name无唯一索引),因此本次不改任何以 ID 为准的 语义:筛选、排序、请求参数与详情页IdCell全部保持原样。
集成状态
- 实施计划已获用户确认后执行,9 个任务全部完成,勾选与偏差记录已补记在计划末尾。
npm test -- --run、npm run build、./scripts/verify-web-embed.sh、git diff --check通过。- 文档索引已由
python3 scripts/gen_doc_index.py重新生成。 - 尚未 commit、push 或创建远端 PR,等待用户授权。