1、修复流量包里的复制按钮不工作问题
2、修复新建任务关联资产不同步到任务测试范围问题
This commit is contained in:
pphua committed 2026-08-28 07:37:24 +08:00
1 parent 3675bd815c
commit cf0989c67d
10 files changed
+297 -32

No files matched your search

+2 -2
View File
@@ -2613,8 +2613,8 @@ function CreateTaskSheet({
portalContainer={sheetContentRef}
/>
<FieldDescription>
将所选企业的域名、IP、CIDR、ICP 和企业关键词作为 Agent
可选范围上下文;不会自动生成意图或强制改变执行目标。
创建任务时会将所选企业当前已有资产加入“测试资产”,并将域名、IP、CIDR、ICP 和企业关键词提供给 Agent
作为范围上下文;不会自动生成意图或强制改变执行目标。
</FieldDescription>
</Field>
<Field>
+5 -5
View File
@@ -39,7 +39,7 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
import { api } from "@/lib/api";
import type { TrafficDetail, TrafficExchange, TrafficHost, TrafficResp } from "@/lib/types";
import { cn } from "@/lib/utils";
import { cn, copyText } from "@/lib/utils";
function fmtTime(ts: string) {
return new Date(ts).toLocaleString("zh-CN", {
@@ -199,12 +199,12 @@ function HttpCodeBlock({ raw }: { raw: string }) {
}, [copied]);
const copyPacket = async () => {
try {
await navigator.clipboard.writeText(value);
const ok = await copyText(value);
if (ok) {
setCopied(true);
} catch {
toast.error("复制失败,请使用 Ctrl/Cmd+A 后复制");
return;
}
toast.error("复制失败,请使用 Ctrl/Cmd+A 后复制");
};
const renderLine = (line: string, index: number) => {
+63 -16
View File
@@ -42,9 +42,35 @@ const mockCompanies = structuredClone(D.companies);
const mockAssets = structuredClone(D.assets);
const mockTaskAssetIDs = new Map(D.tasks.map((task, index) => [task.id, index + 1]));
const mockTaskScopes = new Map<string, TaskScopeRow[]>();
type MockTaskAssetSource = Pick<Asset, "task_source" | "task_source_summary" | "task_source_node_id">;
const mockTaskAssetSources = new Map<string, MockTaskAssetSource>();
let nextMockTaskAssetID = D.tasks.length + 1;
let mockActiveTask = D.ACTIVE_TASK;
function mockTaskAssetSourceKey(taskID: string, assetID: number): string {
return JSON.stringify([taskID, assetID]);
}
function setMockTaskAssetSource(taskID: string, assetID: number, source: MockTaskAssetSource) {
mockTaskAssetSources.set(mockTaskAssetSourceKey(taskID, assetID), source);
}
function mockAssetForTask(taskID: string, asset: Asset): Asset {
const source = mockTaskAssetSources.get(mockTaskAssetSourceKey(taskID, asset.id));
return source ? { ...asset, ...source } : asset;
}
function deleteMockTaskAssetSources(taskID: string, assetID?: number) {
if (assetID !== undefined) {
mockTaskAssetSources.delete(mockTaskAssetSourceKey(taskID, assetID));
return;
}
for (const key of mockTaskAssetSources.keys()) {
const [linkedTaskID] = JSON.parse(key) as [string, number];
if (linkedTaskID === taskID) mockTaskAssetSources.delete(key);
}
}
function mockTaskAssetID(taskID: string): number | undefined {
const numeric = Number(taskID);
if (Number.isInteger(numeric) && numeric > 0) return numeric;
@@ -289,7 +315,10 @@ function route(m: string, path: string, seg: string[], q: URLSearchParams, b: Re
const now = new Date();
const profileIDs = [...((b.llm_profile_ids as number[] | undefined) ?? [])];
const sourceTaskIDs = [...((b.source_task_ids as string[] | undefined) ?? [])];
const companyIDs = [...((b.company_ids as number[] | undefined) ?? [])];
const companyIDs = [...new Set((b.company_ids as number[] | undefined) ?? [])];
if (companyIDs.some((companyID) => !mockCompanies.some((company) => company.id === companyID))) {
throw new Error("关联企业不存在或无效");
}
const categoryID = typeof b.category_id === "number" ? b.category_id : undefined;
const category = categoryID === undefined ? undefined : mockTaskCategories.find((item) => item.id === categoryID);
if (categoryID !== undefined && !category) throw new Error("任务分类不存在");
@@ -318,7 +347,19 @@ function route(m: string, path: string, seg: string[], q: URLSearchParams, b: Re
source_task_ids: sourceTaskIDs,
company_ids: companyIDs,
};
mockTaskAssetIDs.set(id, nextMockTaskAssetID++);
const numericTaskID = nextMockTaskAssetID++;
mockTaskAssetIDs.set(id, numericTaskID);
const selectedCompanies = new Set(companyIDs);
for (const asset of mockAssets) {
if (asset.company_id === undefined || !selectedCompanies.has(asset.company_id)) continue;
if (!asset.task_ids.includes(numericTaskID)) asset.task_ids.push(numericTaskID);
const company = mockCompanies.find((candidate) => candidate.id === asset.company_id);
setMockTaskAssetSource(id, asset.id, {
task_source: "company",
task_source_summary: `任务创建时关联企业:${company?.name ?? `#${asset.company_id}`}`,
task_source_node_id: undefined,
});
}
for (const item of mockTasks) item.active = false;
mockTasks.unshift(created);
mockActiveTask = id;
@@ -462,9 +503,14 @@ function route(m: string, path: string, seg: string[], q: URLSearchParams, b: Re
}
if (seg[0] === "tasks" && seg.length === 2 && m === "DELETE") {
const id = seg[1];
const numericTaskID = mockTaskAssetID(id);
const index = mockTasks.findIndex((item) => item.id === id);
if (index >= 0) mockTasks.splice(index, 1);
mockTaskAssetIDs.delete(id);
deleteMockTaskAssetSources(id);
if (numericTaskID !== undefined) {
for (const asset of mockAssets) asset.task_ids = asset.task_ids.filter((taskID) => taskID !== numericTaskID);
}
let findingsDeleted = 0;
if (b.delete_findings) {
@@ -701,7 +747,7 @@ function route(m: string, path: string, seg: string[], q: URLSearchParams, b: Re
});
const limit = Number(q.get("limit") ?? 50);
const offset = Number(q.get("offset") ?? 0);
const page = list.slice(offset, offset + limit);
const page = list.slice(offset, offset + limit).map((asset) => (taskID ? mockAssetForTask(taskID, asset) : asset));
return { count: page.length, total: list.length, assets: page };
}
if (path === "/assets" && m === "DELETE") {
@@ -792,9 +838,11 @@ function route(m: string, path: string, seg: string[], q: URLSearchParams, b: Re
asset.task_ids.push(numericTaskID);
mutation.assets_linked++;
}
asset.task_source = "manual";
asset.task_source_summary = "用户在测试资产页手工新增";
asset.task_source_node_id = undefined;
setMockTaskAssetSource(seg[1], asset.id, {
task_source: "manual",
task_source_summary: "用户在测试资产页手工新增",
task_source_node_id: undefined,
});
}
mockTaskScopes.set(seg[1], currentScopes);
return mutation;
@@ -812,9 +860,11 @@ function route(m: string, path: string, seg: string[], q: URLSearchParams, b: Re
asset.task_ids.push(numericTaskID);
mutation.attached++;
}
asset.task_source = "manual";
asset.task_source_summary = sourceSummary;
asset.task_source_node_id = undefined;
setMockTaskAssetSource(seg[1], asset.id, {
task_source: "manual",
task_source_summary: sourceSummary,
task_source_node_id: undefined,
});
}
return mutation;
}
@@ -824,11 +874,7 @@ function route(m: string, path: string, seg: string[], q: URLSearchParams, b: Re
if (numericTaskID === undefined || !asset) throw new Error("任务或资产不存在");
if (!asset.task_ids.includes(numericTaskID)) throw new Error("资产未关联当前任务");
asset.task_ids = asset.task_ids.filter((id) => id !== numericTaskID);
if (asset.task_ids.length === 0) {
asset.task_source = undefined;
asset.task_source_summary = undefined;
asset.task_source_node_id = undefined;
}
deleteMockTaskAssetSources(seg[1], asset.id);
return { detached: asset.id };
}
if (seg[0] === "tasks" && seg[2] === "intent-assets" && seg.length === 3 && m === "GET") {
@@ -843,8 +889,9 @@ function route(m: string, path: string, seg: string[], q: URLSearchParams, b: Re
}
const sourceTaskID = mockTaskAssetID(seg[1]) ?? 0;
const assets: IntentAsset[] = mappings.flatMap((mapping) => {
const asset = mockAssets.find((item) => item.id === mapping.assetID);
if (!asset) return [];
const storedAsset = mockAssets.find((item) => item.id === mapping.assetID);
if (!storedAsset) return [];
const asset = mockAssetForTask(seg[1], storedAsset);
return [
{
intent_id: mapping.intentID,
+1
View File
@@ -13,6 +13,7 @@ const TASK_ASSET_SOURCE_LABELS: Record<string, string> = {
agent: "Agent 发现",
anchor: "黑板锚点",
api: "资产 API",
company: "企业关联",
legacy: "历史关联",
manual: "人工加入",
system: "系统关联",
+1 -1
View File
@@ -35,7 +35,7 @@ export interface Task {
llm_failover_state?: "default" | "ready" | "chain_exhausted" | string;
llm_failover_reason?: string;
source_task_ids?: string[]; // directly related tasks inherited as read-only context
company_ids?: number[]; // associated company asset scopes available as task context
company_ids?: number[]; // associated company scopes; current company assets join the task at creation
coverage_enabled?: boolean; // 资产覆盖度功能开关(创建时定,默认开);false=不计算/不展示覆盖度
}