feat(tasks): 漏洞列改为按严重度分档显示 严/高/中/低
- TaskListMetricsAll 用 COUNT(*) FILTER 按 severity 分四档统计(同一条查询)
- TaskListMetrics.Findings 及 TaskDTO.findings 由数字改为 {critical,high,medium,low}
- 列表页「漏洞」列渲染 x/x/x/x,配色沿用 severity 语义、为 0 的档位弱化
- severity 为空/白名单外的记录不计入任一档;mock 同步为对象结构
This commit is contained in:
1 parent
e65d3e5202
commit
87b3c3c770
7 files changed
+90
-48
No files matched your search
+18
-5
@@ -1293,13 +1293,17 @@ func (d *DB) LastActivityAll() (map[int64]int64, error) {
|
|||||||
// GoalCounts is the goal summary for one exploration.
|
// GoalCounts is the goal summary for one exploration.
|
||||||
type GoalCounts struct{ Total, Met int }
|
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.
|
// TaskListMetrics contains the aggregates rendered in task lists.
|
||||||
type TaskListMetrics struct {
|
type TaskListMetrics struct {
|
||||||
Tokens TokenUsage
|
Tokens TokenUsage
|
||||||
LastActivity int64
|
LastActivity int64
|
||||||
Goals GoalCounts
|
Goals GoalCounts
|
||||||
RunningIntents int // kind='intent' 且 state='running' 的条数,即运行中 Worker 数
|
RunningIntents int // kind='intent' 且 state='running' 的条数,即运行中 Worker 数
|
||||||
Findings int // findings 表里该任务的漏洞条数
|
Findings FindingSeverityCounts // findings 表里该任务的漏洞数(按严重度分档)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TaskListMetricsAll returns list aggregates for every live task in one query.
|
// 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.total,0),
|
||||||
COALESCE(goal_metrics.met,0),
|
COALESCE(goal_metrics.met,0),
|
||||||
COALESCE(intent_metrics.running,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
|
FROM tasks task
|
||||||
LEFT JOIN LATERAL (
|
LEFT JOIN LATERAL (
|
||||||
SELECT SUM(input_tokens) AS input_tokens,
|
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'
|
WHERE exploration_id=task.exploration_id AND kind='intent' AND state='running'
|
||||||
) intent_metrics ON true
|
) intent_metrics ON true
|
||||||
LEFT JOIN LATERAL (
|
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
|
FROM findings
|
||||||
WHERE task_id=task.id
|
WHERE task_id=task.id
|
||||||
) finding_metrics ON true
|
) finding_metrics ON true
|
||||||
@@ -1368,7 +1378,10 @@ func (d *DB) TaskListMetricsAll() (map[int64]TaskListMetrics, error) {
|
|||||||
&metrics.Goals.Total,
|
&metrics.Goals.Total,
|
||||||
&metrics.Goals.Met,
|
&metrics.Goals.Met,
|
||||||
&metrics.RunningIntents,
|
&metrics.RunningIntents,
|
||||||
&metrics.Findings,
|
&metrics.Findings.Critical,
|
||||||
|
&metrics.Findings.High,
|
||||||
|
&metrics.Findings.Medium,
|
||||||
|
&metrics.Findings.Low,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
+39
-31
@@ -30,37 +30,45 @@ func rawString(raw json.RawMessage) string {
|
|||||||
|
|
||||||
// ---- Task (frontend "Task") ---- created_at as RFC3339, plus a derived status.
|
// ---- Task (frontend "Task") ---- created_at as RFC3339, plus a derived status.
|
||||||
type TaskDTO struct {
|
type TaskDTO struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
ExplorationID int64 `json:"exploration_id"`
|
ExplorationID int64 `json:"exploration_id"`
|
||||||
Name string `json:"name"` // 可选任务名称;空=未命名
|
Name string `json:"name"` // 可选任务名称;空=未命名
|
||||||
CategoryID *int64 `json:"category_id,omitempty"`
|
CategoryID *int64 `json:"category_id,omitempty"`
|
||||||
CategoryName string `json:"category_name,omitempty"`
|
CategoryName string `json:"category_name,omitempty"`
|
||||||
Pinned bool `json:"pinned"`
|
Pinned bool `json:"pinned"`
|
||||||
PinnedAt string `json:"pinned_at,omitempty"`
|
PinnedAt string `json:"pinned_at,omitempty"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
Goal string `json:"goal"`
|
Goal string `json:"goal"`
|
||||||
Status string `json:"status"` // created | running | paused | done | failed
|
Status string `json:"status"` // created | running | paused | done | failed
|
||||||
CreatedAt string `json:"created_at"`
|
CreatedAt string `json:"created_at"`
|
||||||
CreatedUnix int64 `json:"created_unix"` // created_at as unix seconds (for run-duration calc)
|
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
|
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)
|
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)
|
LastActivity int64 `json:"last_activity_unix"` // unix seconds of the last activity (0 if none)
|
||||||
Paused bool `json:"paused"`
|
Paused bool `json:"paused"`
|
||||||
Queued bool `json:"queued"`
|
Queued bool `json:"queued"`
|
||||||
Tokens TokenTotalDTO `json:"tokens"` // whole-task token consumption
|
Tokens TokenTotalDTO `json:"tokens"` // whole-task token consumption
|
||||||
GoalsTotal int `json:"goals_total"`
|
GoalsTotal int `json:"goals_total"`
|
||||||
GoalsMet int `json:"goals_met"`
|
GoalsMet int `json:"goals_met"`
|
||||||
InFlight int `json:"in_flight"` // 运行中 Worker 数(state=running 的意图)
|
InFlight int `json:"in_flight"` // 运行中 Worker 数(state=running 的意图)
|
||||||
Findings int `json:"findings"` // 该任务已登记的漏洞数(findings 表)
|
Findings FindingSeverityDTO `json:"findings"` // 该任务已登记的漏洞数(findings 表,按严重度分档)
|
||||||
LLMProfileID *int64 `json:"llm_profile_id,omitempty"` // LLM profile used for this task; nil = default
|
LLMProfileID *int64 `json:"llm_profile_id,omitempty"` // LLM profile used for this task; nil = default
|
||||||
LLMProfileIDs []int64 `json:"llm_profile_ids"`
|
LLMProfileIDs []int64 `json:"llm_profile_ids"`
|
||||||
ActiveLLMProfileID *int64 `json:"active_llm_profile_id,omitempty"`
|
ActiveLLMProfileID *int64 `json:"active_llm_profile_id,omitempty"`
|
||||||
LLMFailoverState string `json:"llm_failover_state"`
|
LLMFailoverState string `json:"llm_failover_state"`
|
||||||
LLMFailoverReason string `json:"llm_failover_reason,omitempty"`
|
LLMFailoverReason string `json:"llm_failover_reason,omitempty"`
|
||||||
SourceTaskIDs []string `json:"source_task_ids"`
|
SourceTaskIDs []string `json:"source_task_ids"`
|
||||||
ArchiveBlockedBy string `json:"archive_blocked_by_task_id,omitempty"`
|
ArchiveBlockedBy string `json:"archive_blocked_by_task_id,omitempty"`
|
||||||
CompanyIDs []int64 `json:"company_ids"`
|
CompanyIDs []int64 `json:"company_ids"`
|
||||||
CoverageEnabled bool `json:"coverage_enabled"` // 资产覆盖度功能开关(创建时定)
|
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) {
|
func applyTaskArchiveBlocker(dto *TaskDTO, blockers map[int64]int64) {
|
||||||
|
|||||||
+7
-2
@@ -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{},
|
chatCancel: map[string]context.CancelCauseFunc{}, triggerQ: map[string][]triggeredRun{},
|
||||||
triggerActive: map[string]int{}, triggerCfg: map[string]triggerBehavior{},
|
triggerActive: map[string]int{}, triggerCfg: map[string]triggerBehavior{},
|
||||||
profChatAgents: map[int64]*agent.ChatAgent{},
|
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)}
|
taskAgents: map[string]*taskAgentBundle{}, archiveWake: make(chan struct{}, 1)}
|
||||||
s.initSideQuestions()
|
s.initSideQuestions()
|
||||||
// 熔断阈值/冷却是失败路径上的热参数,启动时把全局重试策略推给 Registry 一次;
|
// 熔断阈值/冷却是失败路径上的热参数,启动时把全局重试策略推给 Registry 一次;
|
||||||
@@ -1040,7 +1040,12 @@ func (s *Server) listTasks(w http.ResponseWriter, r *http.Request) {
|
|||||||
dto.GoalsTotal = metric.Goals.Total
|
dto.GoalsTotal = metric.Goals.Total
|
||||||
dto.GoalsMet = metric.Goals.Met
|
dto.GoalsMet = metric.Goals.Met
|
||||||
dto.InFlight = metric.RunningIntents
|
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)
|
dtos = append(dtos, dto)
|
||||||
}
|
}
|
||||||
writeJSON(w, 200, map[string]any{"tasks": dtos, "active": active})
|
writeJSON(w, 200, map[string]any{"tasks": dtos, "active": active})
|
||||||
|
|||||||
@@ -950,7 +950,9 @@ export default function TasksPage() {
|
|||||||
onSort={sortTasksBy}
|
onSort={sortTasksBy}
|
||||||
/>
|
/>
|
||||||
<TableHead className="text-center">目标进度</TableHead>
|
<TableHead className="text-center">目标进度</TableHead>
|
||||||
<TableHead className="text-center">漏洞</TableHead>
|
<TableHead className="text-center" title="严重 / 高 / 中 / 低">
|
||||||
|
漏洞 <span className="text-muted-foreground font-normal">严/高/中/低</span>
|
||||||
|
</TableHead>
|
||||||
<TableHead className="text-center">运行中 Worker</TableHead>
|
<TableHead className="text-center">运行中 Worker</TableHead>
|
||||||
<SortableTaskHead
|
<SortableTaskHead
|
||||||
field="created"
|
field="created"
|
||||||
@@ -1217,11 +1219,25 @@ const TaskRow = React.memo(function TaskRow({
|
|||||||
)}
|
)}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className="text-center text-xs tabular-nums">
|
<TableCell className="text-center text-xs tabular-nums">
|
||||||
{task.findings && task.findings > 0 ? (
|
{(() => {
|
||||||
<span className="text-destructive font-medium">{task.findings}</span>
|
const f = task.findings;
|
||||||
) : (
|
const total = f ? f.critical + f.high + f.medium + f.low : 0;
|
||||||
<span className="text-muted-foreground">0</span>
|
if (!f || total === 0) return <span className="text-muted-foreground">0</span>;
|
||||||
)}
|
const seg = (n: number, cls: string) => (
|
||||||
|
<span className={n > 0 ? cls : "text-muted-foreground"}>{n}</span>
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<span className="font-medium whitespace-nowrap" title="严重 / 高 / 中 / 低">
|
||||||
|
{seg(f.critical, "text-rose-600 dark:text-rose-400")}
|
||||||
|
<span className="text-muted-foreground">/</span>
|
||||||
|
{seg(f.high, "text-red-600 dark:text-red-400")}
|
||||||
|
<span className="text-muted-foreground">/</span>
|
||||||
|
{seg(f.medium, "text-amber-600 dark:text-amber-400")}
|
||||||
|
<span className="text-muted-foreground">/</span>
|
||||||
|
{seg(f.low, "text-slate-600 dark:text-slate-400")}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
})()}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className="text-center text-xs tabular-nums">
|
<TableCell className="text-center text-xs tabular-nums">
|
||||||
{task.in_flight && task.in_flight > 0 ? (
|
{task.in_flight && task.in_flight > 0 ? (
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ export const tasks: Task[] = [
|
|||||||
paused: false,
|
paused: false,
|
||||||
active: true,
|
active: true,
|
||||||
in_flight: 3,
|
in_flight: 3,
|
||||||
findings: 4,
|
findings: { critical: 1, high: 2, medium: 1, low: 0 },
|
||||||
stalled: false,
|
stalled: false,
|
||||||
goals_total: 5,
|
goals_total: 5,
|
||||||
goals_met: 2,
|
goals_met: 2,
|
||||||
@@ -91,7 +91,7 @@ export const tasks: Task[] = [
|
|||||||
paused: false,
|
paused: false,
|
||||||
active: false,
|
active: false,
|
||||||
in_flight: 2,
|
in_flight: 2,
|
||||||
findings: 2,
|
findings: { critical: 0, high: 1, medium: 1, low: 0 },
|
||||||
stalled: false,
|
stalled: false,
|
||||||
goals_total: 4,
|
goals_total: 4,
|
||||||
goals_met: 1,
|
goals_met: 1,
|
||||||
|
|||||||
@@ -1192,7 +1192,7 @@ function route(m: string, path: string, seg: string[], q: URLSearchParams, b: Re
|
|||||||
paused: false,
|
paused: false,
|
||||||
active: true,
|
active: true,
|
||||||
in_flight: 0,
|
in_flight: 0,
|
||||||
findings: 0,
|
findings: { critical: 0, high: 0, medium: 0, low: 0 },
|
||||||
stalled: false,
|
stalled: false,
|
||||||
goals_total: 0,
|
goals_total: 0,
|
||||||
goals_met: 0,
|
goals_met: 0,
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export interface Task {
|
|||||||
queued?: boolean;
|
queued?: boolean;
|
||||||
active?: boolean;
|
active?: boolean;
|
||||||
in_flight?: number;
|
in_flight?: number;
|
||||||
findings?: number; // 该任务已登记的漏洞数
|
findings?: { critical: number; high: number; medium: number; low: number }; // 已登记漏洞数(按严重度分档)
|
||||||
last_activity?: string;
|
last_activity?: string;
|
||||||
stalled?: boolean;
|
stalled?: boolean;
|
||||||
goals_total?: number;
|
goals_total?: number;
|
||||||
|
|||||||
Reference in new issue
Block a user