diff --git a/db/exploration.go b/db/exploration.go
index e5b0fd5..4b6aef3 100644
--- a/db/exploration.go
+++ b/db/exploration.go
@@ -1293,13 +1293,17 @@ func (d *DB) LastActivityAll() (map[int64]int64, error) {
// GoalCounts is the goal summary for one exploration.
type GoalCounts struct{ Total, Met int }
+// FindingSeverityCounts breaks a task's findings down by severity for the task
+// list (severity 白名单外/为空的记录不计入任一档)。
+type FindingSeverityCounts struct{ Critical, High, Medium, Low int }
+
// TaskListMetrics contains the aggregates rendered in task lists.
type TaskListMetrics struct {
Tokens TokenUsage
LastActivity int64
Goals GoalCounts
- RunningIntents int // kind='intent' 且 state='running' 的条数,即运行中 Worker 数
- Findings int // findings 表里该任务的漏洞条数
+ RunningIntents int // kind='intent' 且 state='running' 的条数,即运行中 Worker 数
+ Findings FindingSeverityCounts // findings 表里该任务的漏洞数(按严重度分档)
}
// TaskListMetricsAll returns list aggregates for every live task in one query.
@@ -1316,7 +1320,10 @@ func (d *DB) TaskListMetricsAll() (map[int64]TaskListMetrics, error) {
COALESCE(goal_metrics.total,0),
COALESCE(goal_metrics.met,0),
COALESCE(intent_metrics.running,0),
- COALESCE(finding_metrics.findings,0)
+ COALESCE(finding_metrics.critical,0),
+ COALESCE(finding_metrics.high,0),
+ COALESCE(finding_metrics.medium,0),
+ COALESCE(finding_metrics.low,0)
FROM tasks task
LEFT JOIN LATERAL (
SELECT SUM(input_tokens) AS input_tokens,
@@ -1345,7 +1352,10 @@ func (d *DB) TaskListMetricsAll() (map[int64]TaskListMetrics, error) {
WHERE exploration_id=task.exploration_id AND kind='intent' AND state='running'
) intent_metrics ON true
LEFT JOIN LATERAL (
- SELECT COUNT(*) AS findings
+ SELECT COUNT(*) FILTER (WHERE severity='critical') AS critical,
+ COUNT(*) FILTER (WHERE severity='high') AS high,
+ COUNT(*) FILTER (WHERE severity='medium') AS medium,
+ COUNT(*) FILTER (WHERE severity='low') AS low
FROM findings
WHERE task_id=task.id
) finding_metrics ON true
@@ -1368,7 +1378,10 @@ func (d *DB) TaskListMetricsAll() (map[int64]TaskListMetrics, error) {
&metrics.Goals.Total,
&metrics.Goals.Met,
&metrics.RunningIntents,
- &metrics.Findings,
+ &metrics.Findings.Critical,
+ &metrics.Findings.High,
+ &metrics.Findings.Medium,
+ &metrics.Findings.Low,
); err != nil {
return nil, err
}
diff --git a/server/dto.go b/server/dto.go
index de51334..38c5211 100644
--- a/server/dto.go
+++ b/server/dto.go
@@ -30,37 +30,45 @@ func rawString(raw json.RawMessage) string {
// ---- Task (frontend "Task") ---- created_at as RFC3339, plus a derived status.
type TaskDTO struct {
- ID string `json:"id"`
- ExplorationID int64 `json:"exploration_id"`
- Name string `json:"name"` // 可选任务名称;空=未命名
- CategoryID *int64 `json:"category_id,omitempty"`
- CategoryName string `json:"category_name,omitempty"`
- Pinned bool `json:"pinned"`
- PinnedAt string `json:"pinned_at,omitempty"`
- Description string `json:"description"`
- Goal string `json:"goal"`
- Status string `json:"status"` // created | running | paused | done | failed
- CreatedAt string `json:"created_at"`
- CreatedUnix int64 `json:"created_unix"` // created_at as unix seconds (for run-duration calc)
- CompletedAt string `json:"completed_at"` // RFC3339 finish time (done/failed); "" if unfinished
- CompletedUnix int64 `json:"completed_unix"` // completed_at as unix seconds (0 if unfinished)
- LastActivity int64 `json:"last_activity_unix"` // unix seconds of the last activity (0 if none)
- Paused bool `json:"paused"`
- Queued bool `json:"queued"`
- Tokens TokenTotalDTO `json:"tokens"` // whole-task token consumption
- GoalsTotal int `json:"goals_total"`
- GoalsMet int `json:"goals_met"`
- InFlight int `json:"in_flight"` // 运行中 Worker 数(state=running 的意图)
- Findings int `json:"findings"` // 该任务已登记的漏洞数(findings 表)
- LLMProfileID *int64 `json:"llm_profile_id,omitempty"` // LLM profile used for this task; nil = default
- LLMProfileIDs []int64 `json:"llm_profile_ids"`
- ActiveLLMProfileID *int64 `json:"active_llm_profile_id,omitempty"`
- LLMFailoverState string `json:"llm_failover_state"`
- LLMFailoverReason string `json:"llm_failover_reason,omitempty"`
- SourceTaskIDs []string `json:"source_task_ids"`
- ArchiveBlockedBy string `json:"archive_blocked_by_task_id,omitempty"`
- CompanyIDs []int64 `json:"company_ids"`
- CoverageEnabled bool `json:"coverage_enabled"` // 资产覆盖度功能开关(创建时定)
+ ID string `json:"id"`
+ ExplorationID int64 `json:"exploration_id"`
+ Name string `json:"name"` // 可选任务名称;空=未命名
+ CategoryID *int64 `json:"category_id,omitempty"`
+ CategoryName string `json:"category_name,omitempty"`
+ Pinned bool `json:"pinned"`
+ PinnedAt string `json:"pinned_at,omitempty"`
+ Description string `json:"description"`
+ Goal string `json:"goal"`
+ Status string `json:"status"` // created | running | paused | done | failed
+ CreatedAt string `json:"created_at"`
+ CreatedUnix int64 `json:"created_unix"` // created_at as unix seconds (for run-duration calc)
+ CompletedAt string `json:"completed_at"` // RFC3339 finish time (done/failed); "" if unfinished
+ CompletedUnix int64 `json:"completed_unix"` // completed_at as unix seconds (0 if unfinished)
+ LastActivity int64 `json:"last_activity_unix"` // unix seconds of the last activity (0 if none)
+ Paused bool `json:"paused"`
+ Queued bool `json:"queued"`
+ Tokens TokenTotalDTO `json:"tokens"` // whole-task token consumption
+ GoalsTotal int `json:"goals_total"`
+ GoalsMet int `json:"goals_met"`
+ InFlight int `json:"in_flight"` // 运行中 Worker 数(state=running 的意图)
+ Findings FindingSeverityDTO `json:"findings"` // 该任务已登记的漏洞数(findings 表,按严重度分档)
+ LLMProfileID *int64 `json:"llm_profile_id,omitempty"` // LLM profile used for this task; nil = default
+ LLMProfileIDs []int64 `json:"llm_profile_ids"`
+ ActiveLLMProfileID *int64 `json:"active_llm_profile_id,omitempty"`
+ LLMFailoverState string `json:"llm_failover_state"`
+ LLMFailoverReason string `json:"llm_failover_reason,omitempty"`
+ SourceTaskIDs []string `json:"source_task_ids"`
+ ArchiveBlockedBy string `json:"archive_blocked_by_task_id,omitempty"`
+ CompanyIDs []int64 `json:"company_ids"`
+ CoverageEnabled bool `json:"coverage_enabled"` // 资产覆盖度功能开关(创建时定)
+}
+
+// FindingSeverityDTO 是任务列表里按严重度分档的漏洞计数(严重/高/中/低)。
+type FindingSeverityDTO struct {
+ Critical int `json:"critical"`
+ High int `json:"high"`
+ Medium int `json:"medium"`
+ Low int `json:"low"`
}
func applyTaskArchiveBlocker(dto *TaskDTO, blockers map[int64]int64) {
diff --git a/server/server.go b/server/server.go
index 5e6d0d9..66a26ad 100644
--- a/server/server.go
+++ b/server/server.go
@@ -148,7 +148,7 @@ func New(ctx context.Context, m *Manager, skillDir string, dataDir string, keyDi
chatCancel: map[string]context.CancelCauseFunc{}, triggerQ: map[string][]triggeredRun{},
triggerActive: map[string]int{}, triggerCfg: map[string]triggerBehavior{},
profChatAgents: map[int64]*agent.ChatAgent{},
- provByProfile: map[int64]*provEntry{}, llmHealth: newLLMHealthRegistry(m.pg),
+ provByProfile: map[int64]*provEntry{}, llmHealth: newLLMHealthRegistry(m.pg),
taskAgents: map[string]*taskAgentBundle{}, archiveWake: make(chan struct{}, 1)}
s.initSideQuestions()
// 熔断阈值/冷却是失败路径上的热参数,启动时把全局重试策略推给 Registry 一次;
@@ -1040,7 +1040,12 @@ func (s *Server) listTasks(w http.ResponseWriter, r *http.Request) {
dto.GoalsTotal = metric.Goals.Total
dto.GoalsMet = metric.Goals.Met
dto.InFlight = metric.RunningIntents
- dto.Findings = metric.Findings
+ dto.Findings = FindingSeverityDTO{
+ Critical: metric.Findings.Critical,
+ High: metric.Findings.High,
+ Medium: metric.Findings.Medium,
+ Low: metric.Findings.Low,
+ }
dtos = append(dtos, dto)
}
writeJSON(w, 200, map[string]any{"tasks": dtos, "active": active})
diff --git a/web/src/app/(main)/function/tasks/page.tsx b/web/src/app/(main)/function/tasks/page.tsx
index 89df25d..95b07b3 100644
--- a/web/src/app/(main)/function/tasks/page.tsx
+++ b/web/src/app/(main)/function/tasks/page.tsx
@@ -950,7 +950,9 @@ export default function TasksPage() {
onSort={sortTasksBy}
/>
目标进度
- 漏洞
+
+ 漏洞 严/高/中/低
+
运行中 Worker
- {task.findings && task.findings > 0 ? (
- {task.findings}
- ) : (
- 0
- )}
+ {(() => {
+ const f = task.findings;
+ const total = f ? f.critical + f.high + f.medium + f.low : 0;
+ if (!f || total === 0) return 0;
+ const seg = (n: number, cls: string) => (
+ 0 ? cls : "text-muted-foreground"}>{n}
+ );
+ return (
+
+ {seg(f.critical, "text-rose-600 dark:text-rose-400")}
+ /
+ {seg(f.high, "text-red-600 dark:text-red-400")}
+ /
+ {seg(f.medium, "text-amber-600 dark:text-amber-400")}
+ /
+ {seg(f.low, "text-slate-600 dark:text-slate-400")}
+
+ );
+ })()}
{task.in_flight && task.in_flight > 0 ? (
diff --git a/web/src/lib/mock/data.ts b/web/src/lib/mock/data.ts
index 1ed2d77..f4d922f 100644
--- a/web/src/lib/mock/data.ts
+++ b/web/src/lib/mock/data.ts
@@ -65,7 +65,7 @@ export const tasks: Task[] = [
paused: false,
active: true,
in_flight: 3,
- findings: 4,
+ findings: { critical: 1, high: 2, medium: 1, low: 0 },
stalled: false,
goals_total: 5,
goals_met: 2,
@@ -91,7 +91,7 @@ export const tasks: Task[] = [
paused: false,
active: false,
in_flight: 2,
- findings: 2,
+ findings: { critical: 0, high: 1, medium: 1, low: 0 },
stalled: false,
goals_total: 4,
goals_met: 1,
diff --git a/web/src/lib/mock/handler.ts b/web/src/lib/mock/handler.ts
index da22153..476ef0f 100644
--- a/web/src/lib/mock/handler.ts
+++ b/web/src/lib/mock/handler.ts
@@ -1192,7 +1192,7 @@ function route(m: string, path: string, seg: string[], q: URLSearchParams, b: Re
paused: false,
active: true,
in_flight: 0,
- findings: 0,
+ findings: { critical: 0, high: 0, medium: 0, low: 0 },
stalled: false,
goals_total: 0,
goals_met: 0,
diff --git a/web/src/lib/types.ts b/web/src/lib/types.ts
index 7dc8e3f..e57bde6 100644
--- a/web/src/lib/types.ts
+++ b/web/src/lib/types.ts
@@ -23,7 +23,7 @@ export interface Task {
queued?: boolean;
active?: boolean;
in_flight?: number;
- findings?: number; // 该任务已登记的漏洞数
+ findings?: { critical: number; high: number; medium: number; low: number }; // 已登记漏洞数(按严重度分档)
last_activity?: string;
stalled?: boolean;
goals_total?: number;