feat(coverage): 任务新增「资产覆盖图」力导向图 tab

后端(只读,无迁移):
- db BuildCoverageGraph(taskID,expID) 复用 covTargetCTE 拉范围内全部类型资产 +
  tested 标记,补连接节点(缺失根域名/公司,灰),按字段派生 child→parent 边
- server GET /api/tasks/{id}/coverage-graph

前端:
- 新 tab coverage-graph-tab:xyflow 渲染 + d3-force 静态力导向布局
- 已测=实色高亮、范围内未测=灰、范围外连接节点=灰虚线;点节点看详情抽屉
- 折叠/分页:同父同类型子节点默认展示 20(已测优先),超出收进折叠节点,
  抽屉「展示更多(+20)」把下一批拉进图;布局仅在可见集合变化时重算
- types/api + d3-force 依赖声明 + 注册 tab

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
Autumn-27andClaude Opus 4.8 committed 2026-08-08 12:30:41 -04:00
1 parent 4c4f65f40a
commit c396674d2b
8 files changed
+970 -5

No files matched your search

+23
View File
@@ -483,6 +483,7 @@ func (s *Server) Handler() http.Handler {
mux.HandleFunc("POST /api/tasks", s.createTask)
mux.HandleFunc("GET /api/tasks/{id}", s.getTask)
mux.HandleFunc("GET /api/tasks/{id}/coverage", s.taskCoverage)
mux.HandleFunc("GET /api/tasks/{id}/coverage-graph", s.taskCoverageGraph)
mux.HandleFunc("GET /api/tasks/{id}/scope", s.taskScopeList)
mux.HandleFunc("POST /api/tasks/{id}/control", s.control)
mux.HandleFunc("POST /api/active", s.setActive)
@@ -1107,6 +1108,28 @@ func (s *Server) taskCoverage(w http.ResponseWriter, r *http.Request) {
writeJSON(w, 200, cov)
}
// taskCoverageGraph returns the force-directed asset coverage graph for a task:
// all in-scope assets (每种类型) + 连接用的根域名/公司节点, each carrying tested/in_scope.
func (s *Server) taskCoverageGraph(w http.ResponseWriter, r *http.Request) {
t, ok := s.m.Task(r.PathValue("id"))
if !ok {
writeErr(w, 404, "task not found")
return
}
as := s.m.Assets()
if as == nil {
writeErr(w, 503, "asset store 未启用")
return
}
taskID, _ := strconv.ParseInt(t.ID, 10, 64)
g, err := as.BuildCoverageGraph(taskID, t.ExpID)
if err != nil {
writeErr(w, 500, err.Error())
return
}
writeJSON(w, 200, g)
}
// taskScopeList returns a task's scope rows (coverage denominator sources).
func (s *Server) taskScopeList(w http.ResponseWriter, r *http.Request) {
t, ok := s.m.Task(r.PathValue("id"))