mirror of
https://github.com/RuoJi6/ARTEX.git
synced 2026-10-28 01:03:50 +08:00
feat: 支持会话级漏洞复测与运行状态展示
This commit is contained in:
1 parent
8dae851b9b
commit
82c1ed9cbb
30 files changed
+2080
-80
No files matched your search
@@ -16,6 +16,7 @@ import type {
|
||||
Company,
|
||||
CompanyScopeRule,
|
||||
Conversation,
|
||||
FindingRetest,
|
||||
IntentAsset,
|
||||
ScopeRow,
|
||||
Task,
|
||||
@@ -39,6 +40,32 @@ const mockLLMRecords = structuredClone(D.llmRecords);
|
||||
const mockTaskTemplates = structuredClone(D.taskTemplates);
|
||||
const mockTaskCategories = structuredClone(D.taskCategories);
|
||||
const mockConversations = structuredClone(D.conversations);
|
||||
const mockRetests: FindingRetest[] = [];
|
||||
const mockRetestMessages: Record<number, Activity[]> = {};
|
||||
|
||||
function advanceMockRetests() {
|
||||
for (const retest of mockRetests) {
|
||||
if (retest.status !== "running" || retest.conversation_id == null) continue;
|
||||
if (Date.now() - Date.parse(retest.created_at) < 15000) continue;
|
||||
retest.status = "completed";
|
||||
retest.verdict = "inconclusive";
|
||||
retest.summary = "演示环境未执行真实验证,无法确认漏洞当前状态。";
|
||||
retest.evidence = "### 演示记录\n\n已关联原漏洞。此环境未连接真实 Agent,也未向目标发送请求;请在实际部署中执行复测。";
|
||||
retest.finished_at = new Date().toISOString();
|
||||
mockRetestMessages[retest.conversation_id].push({
|
||||
seq: 3, worker: "retester", ts: retest.finished_at, kind: "text", summary: retest.summary, detail: retest.evidence,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function stopMockRetest(conversationID: number) {
|
||||
for (const retest of mockRetests) {
|
||||
if (retest.conversation_id !== conversationID || !["pending", "running"].includes(retest.status)) continue;
|
||||
retest.status = "stopped";
|
||||
retest.finished_at = new Date().toISOString();
|
||||
retest.error = "演示复测已停止";
|
||||
}
|
||||
}
|
||||
const mockIntents = structuredClone(D.intents);
|
||||
const mockCompanies = structuredClone(D.companies);
|
||||
const mockAssets = structuredClone(D.assets);
|
||||
@@ -837,6 +864,7 @@ function parseBody(body?: BodyInit | null): Record<string, unknown> {
|
||||
}
|
||||
|
||||
export async function mockHandle<T>(method: string, rawPath: string, body?: BodyInit | null): Promise<T> {
|
||||
advanceMockRetests();
|
||||
await delay();
|
||||
const [path, qs] = rawPath.split("?");
|
||||
const q = new URLSearchParams(qs ?? "");
|
||||
@@ -1700,6 +1728,35 @@ function route(m: string, path: string, seg: string[], q: URLSearchParams, b: Re
|
||||
page_size: pageSize,
|
||||
};
|
||||
}
|
||||
if (path === "/exploration/findings/retests/active" && m === "GET") {
|
||||
return { retests: mockRetests
|
||||
.filter((item) => ["pending", "running"].includes(item.status) && item.conversation_id != null)
|
||||
.map((item) => ({ id: item.id, finding_id: D.findings[item.finding_id - 1].id, conversation_id: item.conversation_id, status: item.status })) };
|
||||
}
|
||||
if (seg[0] === "exploration" && seg[1] === "findings" && seg[3] === "retests") {
|
||||
const finding = mockFindings.find((item) => item.id === seg[2]);
|
||||
if (!finding) throw new Error("漏洞不存在");
|
||||
const findingID = D.findings.findIndex((item) => item.id === finding.id) + 1;
|
||||
if (m === "GET") return { retests: structuredClone(mockRetests.filter((item) => item.finding_id === findingID)) };
|
||||
if (m === "POST") {
|
||||
const existing = mockRetests.find((item) => item.finding_id === findingID && ["pending", "running"].includes(item.status));
|
||||
if (existing) return { retest: structuredClone(existing), created: false };
|
||||
const now = new Date().toISOString();
|
||||
const conversationID = mockConversations.reduce((max, item) => Math.max(max, item.id), 0) + 1;
|
||||
mockConversations.unshift({ id: conversationID, agent_key: "retester", title: `复测 #${finding.id} · ${finding.name || finding.vulnclass}`, pinned: false, created_at: now, updated_at: now });
|
||||
const retest: FindingRetest = {
|
||||
id: mockRetests.length + 1, finding_id: findingID, conversation_id: conversationID,
|
||||
status: "running", verdict: "", notes: String(b.notes ?? ""), summary: "", evidence: "",
|
||||
error: "", created_at: now, started_at: now, finished_at: null,
|
||||
};
|
||||
mockRetests.unshift(retest);
|
||||
mockRetestMessages[conversationID] = [
|
||||
{ seq: 1, worker: "retester", ts: now, kind: "user", summary: `请复测漏洞 #${finding.id}`, detail: retest.notes },
|
||||
{ seq: 2, worker: "retester", ts: now, kind: "text", summary: "演示复测进行中(未向目标发送请求)", detail: "演示复测进行中(未向目标发送请求)" },
|
||||
];
|
||||
return { retest: structuredClone(retest), created: true };
|
||||
}
|
||||
}
|
||||
if (seg[0] === "exploration" && seg[1] === "findings" && seg[3] === "deepen" && m === "POST") {
|
||||
const finding = mockFindings.find((candidate) => candidate.id === seg[2]);
|
||||
const description = String(b.description ?? "").trim();
|
||||
@@ -1887,7 +1944,10 @@ function route(m: string, path: string, seg: string[], q: URLSearchParams, b: Re
|
||||
// ── conversations ──
|
||||
if (path === "/conversations" && m === "GET") {
|
||||
sortMockConversations();
|
||||
return { conversations: structuredClone(mockConversations) };
|
||||
return { conversations: structuredClone(mockConversations.map((conversation) => ({
|
||||
...conversation,
|
||||
running: mockRetests.some((item) => item.conversation_id === conversation.id && ["pending", "running"].includes(item.status)),
|
||||
}))) };
|
||||
}
|
||||
if (path === "/conversations" && m === "POST") {
|
||||
const now = new Date().toISOString();
|
||||
@@ -1918,8 +1978,10 @@ function route(m: string, path: string, seg: string[], q: URLSearchParams, b: Re
|
||||
}
|
||||
if (seg[0] === "conversations" && seg.length === 2 && m === "DELETE") {
|
||||
const id = Number(seg[1]);
|
||||
stopMockRetest(id);
|
||||
const index = mockConversations.findIndex((item) => item.id === id);
|
||||
if (index >= 0) mockConversations.splice(index, 1);
|
||||
for (const retest of mockRetests) if (retest.conversation_id === id) retest.conversation_id = null;
|
||||
return { deleted: id };
|
||||
}
|
||||
if (path === "/conversations/delete/batch" && m === "POST") {
|
||||
@@ -1930,21 +1992,27 @@ function route(m: string, path: string, seg: string[], q: URLSearchParams, b: Re
|
||||
const index = mockConversations.findIndex((item) => item.id === id);
|
||||
if (index < 0) return { id, ok: false, error: "conversation not found" };
|
||||
mockConversations.splice(index, 1);
|
||||
stopMockRetest(id);
|
||||
for (const retest of mockRetests) if (retest.conversation_id === id) retest.conversation_id = null;
|
||||
return { id, ok: true };
|
||||
});
|
||||
return { items };
|
||||
}
|
||||
if (seg[0] === "conversations" && seg[2] === "messages" && seg.length === 3 && m === "GET") {
|
||||
const items = D.conversationMessages[Number(seg[1])] ?? [];
|
||||
return { items, cursor: items.length ? items[items.length - 1].seq : 0, running: false };
|
||||
const items = mockRetestMessages[Number(seg[1])] ?? D.conversationMessages[Number(seg[1])] ?? [];
|
||||
const running = mockRetests.some((item) => item.conversation_id === Number(seg[1]) && ["pending", "running"].includes(item.status));
|
||||
return { items, cursor: items.length ? items[items.length - 1].seq : 0, running };
|
||||
}
|
||||
if (seg[0] === "conversations" && seg[2] === "messages" && seg.length === 4) {
|
||||
const msgs = D.conversationMessages[Number(seg[1])] ?? [];
|
||||
const msgs = mockRetestMessages[Number(seg[1])] ?? D.conversationMessages[Number(seg[1])] ?? [];
|
||||
const a = msgs.find((x) => x.seq === Number(seg[3]));
|
||||
return { detail: a?.detail ?? a?.summary ?? "" };
|
||||
}
|
||||
if (seg[0] === "conversations" && seg[2] === "messages" && m === "POST") return { status: "ok" };
|
||||
if (seg[0] === "conversations" && seg[2] === "stop") return { status: "stopped" };
|
||||
if (seg[0] === "conversations" && seg[2] === "stop") {
|
||||
stopMockRetest(Number(seg[1]));
|
||||
return { status: "stopped" };
|
||||
}
|
||||
|
||||
// ── tools ──
|
||||
if (path === "/tools" && m === "GET") return { tools: D.tools };
|
||||
|
||||
Reference in new issue
Block a user