feat(tasks): 删除任务可选连带删除资产/漏洞/流量/文件(默认不选)
删除弹窗新增四个默认不勾的复选框:
- 删除关联的资产:仅删本任务独有资产,与其他任务共享的仅解除关联(保留)。
- 删除发现的漏洞:删本任务全部 findings。
- 删除测试的流量:按本任务资产的 host 清录制流量(host 无任务标签,会连带清
其他任务命中同一 host 的流量)。
- 删除测试过程中写的文件:删 <workDir>/tasks/<id> 目录(worker 产物 + 上传)。
- db: AssetStore.DeleteByTaskID(独有删+共享解关联)/HostsByTask;DeleteFindingsByTask。
- server: pgDeleteTask 读可选 JSON body {assets,findings,traffic,files},先按 host
清流量(需在删资产前取 host)→删漏洞→删资产→删任务→删文件目录;空 body=只删任务。
- web: api.deleteTask(id,opts);删除弹窗四复选框(默认不选)+说明。
go build/vet、tsc 通过;新增 TestDeleteByTaskID(独有删/共享解关联/host 反查)。
This commit is contained in:
1 parent
2aeb2e49da
commit
4f7c1a199d
6 files changed
+215
-14
No files matched your search
@@ -227,15 +227,18 @@ export default function TasksPage() {
|
||||
return () => clearInterval(i);
|
||||
}, [hasRunning]);
|
||||
|
||||
const deleteTask = React.useCallback(async (id: string) => {
|
||||
try {
|
||||
await api.deleteTask(id);
|
||||
toast.success("任务已删除(全局资产图保留)");
|
||||
load();
|
||||
} catch (e) {
|
||||
toast.error("删除失败:" + (e as Error).message);
|
||||
}
|
||||
}, [load]);
|
||||
const deleteTask = React.useCallback(
|
||||
async (id: string, opts: DeleteOpts) => {
|
||||
try {
|
||||
await api.deleteTask(id, opts);
|
||||
toast.success("任务已删除");
|
||||
load();
|
||||
} catch (e) {
|
||||
toast.error("删除失败:" + (e as Error).message);
|
||||
}
|
||||
},
|
||||
[load],
|
||||
);
|
||||
|
||||
return (
|
||||
<Card>
|
||||
@@ -333,6 +336,15 @@ export default function TasksPage() {
|
||||
// the POLL_MS list refresh only re-render the rows whose data actually moved — a table page
|
||||
// is 20 rows × (StatusBadge + Link + a Radix AlertDialog tree), far too heavy to rebuild
|
||||
// wholesale on every parent render.
|
||||
type DeleteOpts = { assets: boolean; findings: boolean; traffic: boolean; files: boolean };
|
||||
|
||||
const DELETE_EXTRAS: { key: keyof DeleteOpts; label: string; hint: string }[] = [
|
||||
{ key: "assets", label: "删除关联的资产", hint: "仅本任务独有的资产;与其他任务共享的仅解除关联" },
|
||||
{ key: "findings", label: "删除发现的漏洞", hint: "本任务记录的全部漏洞" },
|
||||
{ key: "traffic", label: "删除测试的流量", hint: "按本任务涉及的 host 清除录制流量" },
|
||||
{ key: "files", label: "删除测试过程中写的文件", hint: "本任务工作目录 tasks/<id>(产物 + 上传)" },
|
||||
];
|
||||
|
||||
const TaskRow = React.memo(function TaskRow({
|
||||
task,
|
||||
nowSec,
|
||||
@@ -340,8 +352,14 @@ const TaskRow = React.memo(function TaskRow({
|
||||
}: {
|
||||
task: Task;
|
||||
nowSec: number;
|
||||
onDelete: (id: string) => void;
|
||||
onDelete: (id: string, opts: DeleteOpts) => void;
|
||||
}) {
|
||||
const [delOpts, setDelOpts] = React.useState<DeleteOpts>({
|
||||
assets: false,
|
||||
findings: false,
|
||||
traffic: false,
|
||||
files: false,
|
||||
});
|
||||
return (
|
||||
<TableRow className="group border-border/60">
|
||||
<TableCell>
|
||||
@@ -430,12 +448,35 @@ const TaskRow = React.memo(function TaskRow({
|
||||
) : (
|
||||
"该任务"
|
||||
)}
|
||||
的执行记录、会话与产物将被删除,此操作不可撤销(全局资产图保留)。
|
||||
的执行记录、会话与产物将被删除,此操作不可撤销。
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
|
||||
{/* 额外清理项:默认都不选,勾选后连带删除。全局资产图默认保留。 */}
|
||||
<div className="grid gap-2 rounded-md border bg-muted/30 p-3">
|
||||
<span className="text-xs font-medium text-muted-foreground">
|
||||
同时删除以下关联数据(默认不删):
|
||||
</span>
|
||||
{DELETE_EXTRAS.map((opt) => (
|
||||
<label key={opt.key} className="flex items-start gap-2 text-sm">
|
||||
<Checkbox
|
||||
className="mt-0.5"
|
||||
checked={delOpts[opt.key]}
|
||||
onCheckedChange={(v) =>
|
||||
setDelOpts((s) => ({ ...s, [opt.key]: v === true }))
|
||||
}
|
||||
/>
|
||||
<span className="grid gap-0.5">
|
||||
<span>{opt.label}</span>
|
||||
<span className="text-xs text-muted-foreground">{opt.hint}</span>
|
||||
</span>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>取消</AlertDialogCancel>
|
||||
<AlertDialogAction onClick={() => onDelete(task.id)}>
|
||||
<AlertDialogAction onClick={() => onDelete(task.id, delOpts)}>
|
||||
删除
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
|
||||
+10
-1
@@ -122,7 +122,16 @@ export const api = {
|
||||
seed_first_intent: seedFirstIntent ?? false,
|
||||
plan_heartbeat_seconds: planHeartbeatSeconds ?? 0, // 0 = 后端归一到默认 600(10min)
|
||||
}),
|
||||
deleteTask: (id: string) => del<{ deleted: number }>(`/tasks/${id}`),
|
||||
// 删除任务;opts 里的四个可选项默认不删:assets 关联资产、findings 发现漏洞、
|
||||
// traffic 测试流量(按任务 host 清)、files 测试过程写的文件(tasks/<id> 目录)。
|
||||
deleteTask: (
|
||||
id: string,
|
||||
opts?: { assets?: boolean; findings?: boolean; traffic?: boolean; files?: boolean },
|
||||
) =>
|
||||
del<{ deleted: string; assets_deleted?: number; findings_deleted?: number; traffic_deleted?: number; files_deleted?: boolean }>(
|
||||
`/tasks/${id}`,
|
||||
opts,
|
||||
),
|
||||
controlTask: (id: string, action: "pause" | "resume") => post<{ id: string; paused: boolean }>(`/tasks/${id}/control`, { action }),
|
||||
// 重跑一条没跑成功的意图(blocked/exhausted/stopped):置回 open,worker 会重新认领、从头再跑。
|
||||
rerunIntent: (taskId: string, intentId: string) => post<{ id: string; reopened: number }>(`/tasks/${taskId}/intents/${intentId}/rerun`),
|
||||
|
||||
Reference in new issue
Block a user