基于现有 WebUI 设计模式的详细实施指南
基于对现有代码的深入分析,已采用所有推荐方案:
| 决策点 | 推荐方案 | 理由 |
|---|---|---|
| Tab 内嵌 | ✅ 采用 | 现有导航 8 项满,新增会溢出;SSR 天生支持 URL 状态 |
| 双栏设计 | ✅ 采用 | 符合现有 llm.html 表格布局;易复用现有模态框模式 |
| 健康状态 | ✅ 初始加载后无实时 | 管理员低频访问,30s 延迟可接受;最小改动 |
| 告警保留 | ✅ 90 天滑动 | 防止 DB 爆炸;分页加载避免过大 |
| Semantic Route | ✅ v2.21 延后 | 复杂度高;v2.20 focus Target Set + Alert |
| 成员字段 | ✅ 仅 enabled | 简单可用;其他 v2.21 扩展 |
| reason 字段 | ✅ 可选 | 体验优先;审计可选 |
| 快速面板 | ✅ 启用 | 低成本;提升 overview 体验 |
| 时间表 | ✅ 1 周完成 | 工作量合理;风险低 |
请求 → ParseForm → 参数验证 → DB操作 → 日志 → 302重定向(flash/error)
// 成功:
http.Redirect(w, r, "/dashboard/page?flash=成功消息", http.StatusSeeOther)
// 错误:
http.Redirect(w, r, "/dashboard/page?error=错误信息", http.StatusSeeOther)
// 模板:
{{if .Flash}} <div class="bg-green-100">{{.Flash}}</div> {{end}}
{{if .Error}} <div class="bg-red-100">{{.Error}}</div> {{end}}// 打开:移除 hidden class
document.getElementById('modal').classList.remove('hidden');
// 关闭:添加 hidden class
document.getElementById('modal').classList.add('hidden');
// 表单 action 动态设置:
form.action = '/endpoint/' + id + '/update';<!-- URL 驱动,无 JS 路由 -->
<a href="/dashboard/page?tab=targets">LLM 目标</a>
<a href="/dashboard/page?tab=targetsets">目标集</a>
<!-- 模板条件渲染 -->
{{if eq .ActiveTab "targets"}} ... {{end}}
{{if eq .ActiveTab "targetsets"}} ... {{end}}// 只读路由
mux.Handle("GET /dashboard/page", h.requireSession(handler))
// 写操作路由(需要可写节点)
rw := func(hf http.Handler) http.Handler {
return h.requireSession(h.requireWritableNode(hf))
}
mux.Handle("POST /dashboard/page", rw(handler))修改:
├─ internal/dashboard/handler.go
│ ├─ 新增字段:groupTargetSetRepo
│ ├─ 新增 Setter:SetGroupTargetSetRepo
│ ├─ RegisterRoutes 追加 6 条路由
│ └─ 新增 6 个 Handler 函数
│
├─ internal/dashboard/llm_handler.go
│ ├─ llmPageData 新增字段:ActiveTab, TargetSets, SelectedSetID, GroupsForBind
│ ├─ handleLLMPage 扩展 Tab 分支逻辑
│ └─ 新增 targetSetWithMembers 结构
│
├─ internal/db/group_target_set_repo.go
│ └─ 新增 ListMembersForSets() 方法(批量查询,避免 N+1)
│
└─ internal/dashboard/templates/llm.html
├─ 添加 Tab 栏(3 个标签页)
├─ 重组现有内容到 named template(targets、bindings)
├─ 新增 targetsets Tab 双栏布局
├─ 新增 targetSetModal 模态框
└─ 新增 JavaScript 交互函数
type Handler struct {
// ... 现有 ...
groupTargetSetRepo *db.GroupTargetSetRepo
}
func (h *Handler) SetGroupTargetSetRepo(repo *db.GroupTargetSetRepo) {
h.groupTargetSetRepo = repo
}
func (h *Handler) RegisterRoutes(mux *http.ServeMux) {
// ... 现有 ...
rw := func(hf http.Handler) http.Handler {
return h.requireSession(h.requireWritableNode(hf))
}
mux.Handle("POST /dashboard/llm/targetsets",
rw(http.HandlerFunc(h.handleTargetSetCreate)))
mux.Handle("POST /dashboard/llm/targetsets/{id}/update",
rw(http.HandlerFunc(h.handleTargetSetUpdate)))
mux.Handle("POST /dashboard/llm/targetsets/{id}/delete",
rw(http.HandlerFunc(h.handleTargetSetDelete)))
mux.Handle("POST /dashboard/llm/targetsets/{id}/members",
rw(http.HandlerFunc(h.handleTargetSetAddMember)))
mux.Handle("POST /dashboard/llm/targetsets/{id}/members/{memberID}/update",
rw(http.HandlerFunc(h.handleTargetSetUpdateMember)))
mux.Handle("POST /dashboard/llm/targetsets/{id}/members/{memberID}/delete",
rw(http.HandlerFunc(h.handleTargetSetRemoveMember)))
}type llmPageData struct {
baseData
// 现有字段...
// v2.20 新增
ActiveTab string
TargetSets []targetSetWithMembers
SelectedSetID string
GroupsForBind []db.Group
}
type targetSetWithMembers struct {
db.GroupTargetSet
Members []db.GroupTargetSetMember
BoundGroupName string
MemberCount int
HealthyCount int
DegradedCount int
UnhealthyCount int
}<div class="border-b border-gray-200 mb-6">
<nav class="flex gap-1">
<a href="/dashboard/llm?tab=targets"
class="px-3 py-2 {{if eq .ActiveTab "targets"}}text-indigo-600 border-b-2 border-indigo-600{{else}}text-gray-600{{end}}">
LLM 目标
</a>
<a href="/dashboard/llm?tab=targetsets"
class="px-3 py-2 {{if eq .ActiveTab "targetsets"}}text-indigo-600 border-b-2 border-indigo-600{{else}}text-gray-600{{end}}">
目标集 ({{len .TargetSets}})
</a>
<a href="/dashboard/llm?tab=bindings"
class="px-3 py-2 {{if eq .ActiveTab "bindings"}}text-indigo-600 border-b-2 border-indigo-600{{else}}text-gray-600{{end}}">
绑定关系 ({{len .Bindings}})
</a>
</nav>
</div>
{{if eq .ActiveTab "targets"}}
{{template "llm-targets-tab" .}}
{{else if eq .ActiveTab "targetsets"}}
{{template "llm-targetsets-tab" .}}
{{else}}
{{template "llm-bindings-tab" .}}
{{end}}修改:
├─ internal/dashboard/handler.go
│ ├─ 新增字段:alertRepo
│ ├─ RegisterRoutes 新增 resolve/resolve-batch 路由
│ ├─ 修改 handleAlertsPage 支持多 Tab
│ ├─ 新增 handleAlertResolve(单条)
│ └─ 新增 handleAlertResolveBatch(批量)
│
└─ internal/dashboard/templates/alerts.html
├─ 添加 Tab 栏(3 个标签页:live、active、history)
├─ 保留现有 live Tab 内容
├─ 新增 active Tab(活跃告警表)
├─ 新增 history Tab(历史查询)
└─ 新增 JavaScript 交互
- ✅ 列表展示(checkbox 选择)
- ✅ 统计卡片(Critical/Error/Warning)
- ✅ 单条解决(确认框)
- ✅ 批量解决(POST /dashboard/alerts/resolve-batch)
- ✅ 表格操作按钮
- ✅ 时间范围选择(7/30/90 天)
- ✅ 目标筛选(可选)
- ✅ 分页加载(每页 50 条)
- ✅ 表格显示(发生时间、解决时间等)
修改:
└─ internal/dashboard/templates/overview.html
├─ 新增 section:运维快速操作
├─ 3 个卡片:LLM 状态、告警、用户/分组
└─ 异步 fetch 加载数据(页面加载后)
-
LLM 目标状态
- 健康:3/4
- 告警:2 条
- 目标集:3 个
- 快捷链接:进入 LLM 管理
-
系统告警
- 未解决:5 条
- 级别分布:Critical/Error/Warning
- 快捷链接:查看告警详情
-
用户/分组
- 活跃用户:42
- 总分组:8
- 今日新增:2
- 快捷链接:管理用户
// 不推荐:循环查询
for _, setID := range setIDs {
members := repo.GetMembers(setID) // N+1!
}
// 推荐:批量查询
membersMap := repo.ListMembersForSets(setIDs) // 1 次查询<!-- 模板层防守 -->
{{if not .IsWorkerNode}}
<button onclick="...">添加成员</button>
{{else}}
<button disabled class="opacity-50">添加成员(仅主节点)</button>
{{end}}// 路由层防守(已有中间件)
rw := func(hf http.Handler) http.Handler {
return h.requireSession(h.requireWritableNode(hf))
}// 目标集删除时,级联删除成员
if err := h.groupTargetSetRepo.Delete(id); err != nil {
// 在 repo.Delete 中使用事务
// 1. 删除所有成员
// 2. 删除目标集本身
}// 初始:显示值
<span id="weight-123">3</span>
// 编辑:显示输入框
<input type="number" value="3" id="weight-input-123">
// 保存:提交表单
POST /dashboard/llm/targetsets/{setID}/members/{memberID}/update
body: weight=5&priority=0- 创建目标集
- 编辑目标集名称、绑定分组、策略
- 删除目标集(级联删除成员)
- 添加成员到目标集
- 修改成员权重/优先级(内联编辑)
- 删除成员
- 批量解决告警
- 历史查询分页
- 快速面板卡片加载
- Tab 切换(targets/targetsets/bindings)
- URL 书签保持(?tab=targetsets&selected=id)
- Flash 消息正常显示
- Worker 节点按钮禁用
- 响应式设计(桌面/平板/手机)
- 页面加载时间 < 1s
- 表格操作响应 < 200ms
- 无明显的 N+1 SQL 查询
- 成员列表支持 >100 条
- Chrome/Edge
- Firefox
- Safari
- ✅ Phase 1: Group-Target Set UI
- ✅ Phase 2: Alert 活跃 + 批量解决
- ✅ Phase 3: 快速操作面板
- Alert 历史查询优化
- Semantic Route UI(延后至 v2.21)
所有代码设计基于现有 WebUI 实现,无兼容性问题,无破坏性修改。
✅ Tab 内嵌方式与现有导航一致 ✅ 模态框、Flash 消息、中间件全部复用 ✅ Tailwind 样式与现有设计统一 ✅ JavaScript 交互遵循现有模式 ✅ 数据库操作遵循现有 repo 模式
可立即开始代码实施!