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

+4 -1
View File
@@ -11,7 +11,7 @@ import type {
PromptVersion, MCPServer, MCPTool, SkillItem, TokenUsage, TokenTotal, DailyTokenBucket,
Tool, Conversation, AgentTrigger,
InterceptRule, InterceptPending, InterceptApprovalRow,
TaskAssetView, SSProject, SSTask, ConvTokenSummary,
TaskAssetView, SSProject, SSTask, ConvTokenSummary, CoverageGraphData,
CommandRecord, LLMRecordItem, LLMRecordDetail,
} from "@/lib/types";
@@ -124,6 +124,9 @@ export const api = {
pct: number | null;
by_type: { type: string; total: number; tested: number }[];
}>(`/tasks/${id}/coverage`),
// 资产覆盖图:范围内全部资产 + 连接用的根域名/公司节点,含 tested/in_scope。
taskCoverageGraph: (id: string) =>
get<CoverageGraphData>(`/tasks/${id}/coverage-graph`),
// ---- assets ----
// Server-side paginated: pass limit/offset, get back the page + full match total.
+32
View File
@@ -133,6 +133,38 @@ export interface Asset {
last_seen: string;
}
// ---- Asset coverage graph (per task) ----
// 力导向「资产覆盖图」的一个节点。key 唯一:资产="a:<id>"、公司="c:<id>"、
// 无资产行的根域名="r:<domain>"。in_scope=false 的是仅用于连线的灰色上下文节点。
export interface CoverageGraphNode {
key: string;
kind: "company" | "root_domain" | "subdomain" | "ip" | "service" | "app" | "endpoint";
label: string;
tested: boolean;
in_scope: boolean;
asset_id?: number;
company_id?: number;
domain?: string;
root_domain?: string;
ip?: string;
url?: string;
port?: number;
service_type?: string;
app_name?: string;
page_title?: string;
status_code?: number;
}
export interface CoverageGraphEdge {
src: string;
dst: string;
}
export interface CoverageGraphData {
nodes: CoverageGraphNode[];
edges: CoverageGraphEdge[];
}
// 公司资产范围规则的一条(归属唯一真值来源)。
export interface ScopeRow {
id: number;