更新: 2531 个文件 - 2026-03-17 21:00:03
这个提交包含在:
@@ -38,6 +38,7 @@ const ARTIFACT_KIND_LABELS = {
|
||||
const DOC_HUB_ITEMS = [
|
||||
{ title: "项目功能总览", href: "/docs/project-features.html", description: "项目定位、功能版图、自动化链路和 CLI 入口。", badge: "docs" },
|
||||
{ title: "前端设计文档", href: "/docs/frontend-dashboard-design.html", description: "工作台布局、交互、折叠逻辑和视觉规范。", badge: "ui" },
|
||||
{ title: "中文完整度报告", href: "/docs/testing-completeness-report.html", description: "89 条 advisory 的最新完整度、family 矩阵与 ingest 健康度。", badge: "report" },
|
||||
{ title: "架构库镜像", href: "/docs/architecture-library.html", description: "当前架构库的结构化镜像页,可直接查看 JSON 真值。", badge: "architecture" },
|
||||
{ title: "仓库入口镜像", href: "/docs/root-readme.html", description: "根 README 的本地镜像,包含能力矩阵与主入口。", badge: "readme" },
|
||||
{ title: "授权模型", href: "/docs/authorization-model.html", description: "目标范围、授权模型、最小化验证建议和记录要求。", badge: "scope" },
|
||||
@@ -50,6 +51,7 @@ const DOC_HUB_ITEMS = [
|
||||
|
||||
const DATA_HUB_ITEMS = [
|
||||
{ title: "summary.json", href: "/summary.json", description: "全局摘要、状态分布、最近失败与系统汇总。", badge: "json" },
|
||||
{ title: "completeness.json", href: "/data/completeness.json", description: "最新 advisory 完整度、系统/family 进度与 ingest 健康度。", badge: "json" },
|
||||
{ title: "runs.json", href: "/runs.json", description: "最近运行的结构化详情,可用于 UI 和调试。", badge: "json" },
|
||||
{ title: "systems.json", href: "/systems.json", description: "系统级覆盖、分类、更新时间和浏览器证据统计。", badge: "json" },
|
||||
{ title: "advisories.json", href: "/advisories.json", description: "漏洞条目元数据、来源和 secure-code 主题。", badge: "json" },
|
||||
@@ -84,6 +86,7 @@ const state = {
|
||||
advisories: {},
|
||||
profiles: {},
|
||||
architecture: null,
|
||||
completeness: null,
|
||||
selectedRunId: null,
|
||||
selectedArtifact: null,
|
||||
refreshHandle: null,
|
||||
@@ -275,15 +278,17 @@ function familyOptions() {
|
||||
}
|
||||
|
||||
function metricCards() {
|
||||
const successCount = Number(state.summary?.statuses?.["verified-real"] || 0) + Number(state.summary?.statuses?.["verified-synthetic"] || 0);
|
||||
const blockedCount = sumStatuses((key) => key.startsWith("blocked"));
|
||||
const inProgressCount = Math.max(Number(state.summary?.run_count || 0) - successCount - blockedCount, 0);
|
||||
const completeness = state.completeness || state.summary?.completeness || {};
|
||||
const successCount = Number(completeness.verified_real || 0) + Number(completeness.verified_synthetic || 0);
|
||||
const blockedCount = Number(completeness.blocked || 0);
|
||||
const inProgressCount = Number(completeness.manual || 0);
|
||||
const advisoryTotal = Number(completeness.advisory_total || state.summary?.advisory_count || 0);
|
||||
|
||||
return [
|
||||
{
|
||||
label: "运行总数",
|
||||
value: state.summary?.run_count || 0,
|
||||
note: `已索引漏洞条目 ${state.summary?.advisory_count || 0} 条`,
|
||||
label: "最新 advisory",
|
||||
value: advisoryTotal,
|
||||
note: `历史运行 ${state.summary?.run_count || 0} 次`,
|
||||
color: "var(--accent-purple)",
|
||||
iconName: "report"
|
||||
},
|
||||
@@ -297,14 +302,14 @@ function metricCards() {
|
||||
{
|
||||
label: "当前阻塞",
|
||||
value: blockedCount,
|
||||
note: "制品阻塞或破坏性风险阻塞",
|
||||
note: "latest advisory 状态里的 blocked-*",
|
||||
color: "var(--accent-red)",
|
||||
iconName: "failure"
|
||||
},
|
||||
{
|
||||
label: "待处理 / 进行中",
|
||||
value: inProgressCount,
|
||||
note: "人工分诊、待补证据或未完成实证",
|
||||
note: "人工分诊或待补证据的 latest advisory",
|
||||
color: "var(--accent-blue)",
|
||||
iconName: "timeline"
|
||||
}
|
||||
@@ -755,6 +760,54 @@ function renderPanel(panelKey, title, meta, iconName, content) {
|
||||
`;
|
||||
}
|
||||
|
||||
function renderCompletenessPanel(panelKey, compact = false) {
|
||||
const completeness = state.completeness || state.summary?.completeness || {};
|
||||
const systems = (state.completeness?.systems || []).map((system) => `
|
||||
<article class="plan-card">
|
||||
<span class="plan-label">${escapeHtml(system.system_id)}</span>
|
||||
<div class="plan-copy">${escapeHtml(`${system.verified_real}/${system.total} verified-real`)}</div>
|
||||
<div class="tag-row">
|
||||
${(system.families || []).map((family) => `<span class="tag">${escapeHtml(`${family.family} ${family.verified_real}/${family.total}`)}</span>`).join("")}
|
||||
</div>
|
||||
</article>
|
||||
`).join("");
|
||||
const failures = (state.completeness?.ingest_health?.failures || []).slice(0, 5);
|
||||
return renderPanel(
|
||||
panelKey,
|
||||
"最新 advisory 完整度",
|
||||
`${escapeHtml(completeness.verified_real || 0)}/${escapeHtml(completeness.advisory_total || 0)}`,
|
||||
"shield",
|
||||
`
|
||||
<div class="detail-stat-grid">
|
||||
<article class="detail-stat">
|
||||
<strong>verified-real</strong>
|
||||
<span>${escapeHtml(completeness.verified_real || 0)}</span>
|
||||
</article>
|
||||
<article class="detail-stat">
|
||||
<strong>blocked</strong>
|
||||
<span>${escapeHtml(completeness.blocked || 0)}</span>
|
||||
</article>
|
||||
<article class="detail-stat">
|
||||
<strong>manual</strong>
|
||||
<span>${escapeHtml(completeness.manual || 0)}</span>
|
||||
</article>
|
||||
<article class="detail-stat">
|
||||
<strong>ingest failures</strong>
|
||||
<span>${escapeHtml(state.completeness?.ingest_health?.failure_count || 0)}</span>
|
||||
</article>
|
||||
</div>
|
||||
<div class="plan-grid" style="margin-top:16px;">${systems || `<div class="empty-state">暂无系统完整度数据。</div>`}</div>
|
||||
${compact ? "" : `
|
||||
<div class="detail-actions" style="margin-top:16px;">
|
||||
<a class="button button-secondary" href="/docs/testing-completeness-report.html" target="_blank" rel="noreferrer">${icon("docs")}<span>打开中文报告</span></a>
|
||||
<a class="button button-secondary" href="/data/completeness.json" target="_blank" rel="noreferrer">${icon("json")}<span>打开 completeness.json</span></a>
|
||||
</div>
|
||||
${failures.length ? `<div class="callout" style="margin-top:16px;"><strong>Ingest 未清零</strong><div class="plan-copy">${escapeHtml(failures.join(" | "))}</div></div>` : ""}
|
||||
`}
|
||||
`
|
||||
);
|
||||
}
|
||||
|
||||
function renderArchitectureFields(fields = []) {
|
||||
if (!fields.length) return "";
|
||||
return `
|
||||
@@ -1061,6 +1114,7 @@ function renderRunWorkspace() {
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
${renderCompletenessPanel("runs_completeness", true)}
|
||||
${renderPanel("timeline", "进度时间线", `${escapeHtml(run.timeline?.length || 0)} 步`, "timeline", `
|
||||
<div class="progress-bar">${progressSegments(progress).bar}</div>
|
||||
<div class="progress-legend">${progressSegments(progress).legend}</div>
|
||||
@@ -1130,6 +1184,7 @@ function renderOverviewWorkspace() {
|
||||
<h2 class="detail-title">按板块浏览当前工作台</h2>
|
||||
<div class="detail-subtitle">根入口保留为概览页,同时新增运行、系统、架构、文档和数据的独立 URL。顶部菜单负责分类切换,搜索与筛选会同步到地址栏。</div>
|
||||
</section>
|
||||
${renderCompletenessPanel("overview_completeness")}
|
||||
${renderPanel("overview_runs", "最新运行", `${escapeHtml(runs.length)} 条`, "queue", renderRunList(runs, "暂无运行数据。"))}
|
||||
${renderPanel("overview_systems", "系统覆盖概览", `${escapeHtml(systems.length)} 个系统`, "systems", `<div class="system-grid">${renderSystemCards(systems)}</div>`)}
|
||||
${renderArchitecturePanel()}
|
||||
@@ -1195,6 +1250,7 @@ function renderDocsWorkspace() {
|
||||
<h2 class="detail-title">文档入口按板块集中</h2>
|
||||
<div class="detail-subtitle">不再把所有入口混在首页链接堆里。这里按说明、设计、真值镜像和 secure-code 索引集中展示。</div>
|
||||
</section>
|
||||
${renderCompletenessPanel("docs_completeness", true)}
|
||||
${renderPanel("docs_hub", "文档与镜像页", `${escapeHtml(DOC_HUB_ITEMS.length)} 个入口`, "docs", renderHubCards(DOC_HUB_ITEMS))}
|
||||
</div>
|
||||
`;
|
||||
@@ -1215,6 +1271,7 @@ function renderDataWorkspace() {
|
||||
<h2 class="detail-title">数据入口按类型集中</h2>
|
||||
<div class="detail-subtitle">summary、runs、systems、advisories、profiles、architecture 已单独归入数据中心,避免和文档、运行详情混在一个地址里。</div>
|
||||
</section>
|
||||
${renderCompletenessPanel("data_completeness", true)}
|
||||
${renderPanel("data_hub", "JSON 与生成数据", `${escapeHtml(DATA_HUB_ITEMS.length)} 个入口`, "json", renderHubCards(DATA_HUB_ITEMS))}
|
||||
</div>
|
||||
`;
|
||||
@@ -1416,13 +1473,14 @@ async function loadData(preserveSelection = true) {
|
||||
renderSyncState("loading", "刷新中", `本地时间 ${new Date().toLocaleTimeString("zh-CN", { hour12: false })}`);
|
||||
|
||||
try {
|
||||
const [summary, runs, systems, advisories, profiles, architecture] = await Promise.all([
|
||||
const [summary, runs, systems, advisories, profiles, architecture, completeness] = await Promise.all([
|
||||
fetchJson("/summary.json"),
|
||||
fetchJson("/runs.json"),
|
||||
fetchJson("/systems.json"),
|
||||
fetchJson("/advisories.json"),
|
||||
fetchJson("/profiles.json"),
|
||||
fetchJson("/architecture.json")
|
||||
fetchJson("/architecture.json"),
|
||||
fetchJson("/data/completeness.json")
|
||||
]);
|
||||
|
||||
state.summary = summary;
|
||||
@@ -1431,6 +1489,7 @@ async function loadData(preserveSelection = true) {
|
||||
state.advisories = advisories;
|
||||
state.profiles = profiles;
|
||||
state.architecture = architecture;
|
||||
state.completeness = completeness;
|
||||
|
||||
const filtered = filteredRuns();
|
||||
const candidate = preserveSelection ? (state.selectedRunId || previousRunId) : state.selectedRunId;
|
||||
|
||||
@@ -111,11 +111,12 @@ def _check_ports(profiles: Iterable[Dict[str, Any]]) -> Dict[str, Any]:
|
||||
|
||||
def run_checks(profiles: Iterable[Dict[str, Any]] | None = None) -> Dict[str, Any]:
|
||||
selected = list(profiles or [])
|
||||
require_browser = not selected or any(bool(item.get("browser_assertions", {}).get("required")) for item in selected)
|
||||
checks = [
|
||||
_check_docker_cli(),
|
||||
_check_docker_daemon(),
|
||||
_check_playwright_import(),
|
||||
_check_chromium_launch(),
|
||||
_check_playwright_import() if require_browser else _result("playwright-import", True, "not required for selected profiles"),
|
||||
_check_chromium_launch() if require_browser else _result("playwright-browser", True, "not required for selected profiles"),
|
||||
_check_ports(selected),
|
||||
]
|
||||
ok = all(item["ok"] for item in checks)
|
||||
@@ -128,4 +129,3 @@ def run_checks(profiles: Iterable[Dict[str, Any]] | None = None) -> Dict[str, An
|
||||
"failure_count": len(failures),
|
||||
"summary": "; ".join(item["detail"] for item in failures) if failures else "all checks passed",
|
||||
}
|
||||
|
||||
|
||||
@@ -52,13 +52,17 @@ def _timeline_event(timeline: List[Dict[str, Any]], step: str, status: str, deta
|
||||
|
||||
def _sync_registry_outputs() -> None:
|
||||
from intel.config import GENERATED_DIR, load_source_map # noqa: E402
|
||||
from intel.main import _load_existing_advisories, _load_existing_triage, _write_outputs # noqa: E402
|
||||
from intel.main import _load_existing_advisories, _load_existing_triage # noqa: E402
|
||||
from intel.render import render_case_pages, render_generated, render_secure_code, render_system_scaffolding # noqa: E402
|
||||
|
||||
source_map = load_source_map()
|
||||
advisories = _load_existing_advisories()
|
||||
triage = _load_existing_triage()
|
||||
summary = read_json(GENERATED_DIR / "run-summary.json", default={}) or {}
|
||||
_write_outputs(source_map, advisories, triage, summary.get("failures", []), summary)
|
||||
render_system_scaffolding(source_map, advisories)
|
||||
render_case_pages(advisories)
|
||||
render_secure_code(source_map)
|
||||
render_generated(source_map, advisories, triage, summary.get("failures", []), summary)
|
||||
|
||||
|
||||
def _resolve_profile(advisory: Dict[str, Any]) -> Dict[str, Any]:
|
||||
@@ -336,8 +340,6 @@ def _execute_case(canonical_id: str, run_id: str | None = None, dry_run: bool =
|
||||
write_json(RUNS_DIR / f"{resolved_run_id}.json", bundle)
|
||||
if sync_outputs:
|
||||
_sync_registry_outputs()
|
||||
else:
|
||||
render.render_dashboard()
|
||||
return bundle
|
||||
|
||||
|
||||
|
||||
@@ -8,8 +8,8 @@ from pathlib import Path
|
||||
from typing import Any, Dict, List
|
||||
|
||||
from lab.config import ADVISORIES_DIR, CASE_RUNS_DIR, DASHBOARD_DIR, REPRO_MAP_PATH, ROOT, RUNS_DIR, SOURCE_MAP_PATH
|
||||
from lab.repro import load_profiles
|
||||
from lab.utils import ensure_dir, isoformat, load_json_dir, now_utc, read_yaml, unique, write_json, write_text
|
||||
from lab.repro import annotate_with_latest_run, latest_runs_by_advisory, load_profiles
|
||||
from lab.utils import ensure_dir, isoformat, load_json_dir, now_utc, read_json, read_yaml, unique, write_json, write_text
|
||||
|
||||
|
||||
TEMPLATES_DIR = ROOT / "scripts" / "lab" / "dashboard_templates"
|
||||
@@ -156,9 +156,12 @@ def _profile_meta(profile: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"cleanup_policy": profile.get("cleanup_policy"),
|
||||
"artifact_source": profile.get("artifact_source", {}),
|
||||
"success_criteria": profile.get("success_criteria", []),
|
||||
"success_assertions": profile.get("success_assertions", []),
|
||||
"seed_actions": profile.get("seed_actions", []),
|
||||
"attack_actions": profile.get("attack_actions", []),
|
||||
"browser_assertions": profile.get("browser_assertions", {}),
|
||||
"runner_id": profile.get("runner_id"),
|
||||
"fixture_path": profile.get("fixture_path"),
|
||||
"allowed_target_types": profile.get("allowed_target_types", []),
|
||||
"required_services": profile.get("required_services", []),
|
||||
}
|
||||
@@ -211,6 +214,157 @@ def _status_label(value: str | None) -> str:
|
||||
return STATUS_LABELS.get(value or "", value or "-")
|
||||
|
||||
|
||||
def _family_name(advisory: Dict[str, Any], profile_map: Dict[str, Dict[str, Any]]) -> str:
|
||||
profile = profile_map.get(advisory.get("repro_profile_id") or "", {})
|
||||
return profile.get("vuln_family") or advisory.get("repro_profile_id") or "unknown"
|
||||
|
||||
|
||||
def _latest_advisories(advisories: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
||||
run_map = latest_runs_by_advisory()
|
||||
return [annotate_with_latest_run(item, run_map.get(item.get("canonical_id"))) for item in advisories]
|
||||
|
||||
|
||||
def _build_completeness(
|
||||
advisories: List[Dict[str, Any]],
|
||||
runs: List[Dict[str, Any]],
|
||||
profile_map: Dict[str, Dict[str, Any]],
|
||||
run_summary: Dict[str, Any],
|
||||
) -> Dict[str, Any]:
|
||||
latest_statuses: Dict[str, int] = {}
|
||||
historical_statuses: Dict[str, int] = {}
|
||||
systems: Dict[str, Dict[str, Any]] = {}
|
||||
|
||||
for item in advisories:
|
||||
status = item.get("verification_status", "triage-manual")
|
||||
latest_statuses[status] = latest_statuses.get(status, 0) + 1
|
||||
system = systems.setdefault(
|
||||
item["system_id"],
|
||||
{
|
||||
"system_id": item["system_id"],
|
||||
"display_name": item.get("display_name", item["system_id"]),
|
||||
"total": 0,
|
||||
"verified_real": 0,
|
||||
"verified_synthetic": 0,
|
||||
"blocked": 0,
|
||||
"manual": 0,
|
||||
"families": {},
|
||||
},
|
||||
)
|
||||
system["total"] += 1
|
||||
family = _family_name(item, profile_map)
|
||||
family_entry = system["families"].setdefault(
|
||||
family,
|
||||
{"family": family, "total": 0, "verified_real": 0, "verified_synthetic": 0, "blocked": 0, "manual": 0},
|
||||
)
|
||||
family_entry["total"] += 1
|
||||
if status == "verified-real":
|
||||
system["verified_real"] += 1
|
||||
family_entry["verified_real"] += 1
|
||||
elif status == "verified-synthetic":
|
||||
system["verified_synthetic"] += 1
|
||||
family_entry["verified_synthetic"] += 1
|
||||
elif status.startswith("blocked-"):
|
||||
system["blocked"] += 1
|
||||
family_entry["blocked"] += 1
|
||||
else:
|
||||
system["manual"] += 1
|
||||
family_entry["manual"] += 1
|
||||
|
||||
for item in runs:
|
||||
status = item.get("verification_status", "triage-manual")
|
||||
historical_statuses[status] = historical_statuses.get(status, 0) + 1
|
||||
|
||||
systems_list = []
|
||||
for entry in sorted(systems.values(), key=lambda value: value["system_id"]):
|
||||
entry["families"] = sorted(entry["families"].values(), key=lambda value: value["family"])
|
||||
systems_list.append(entry)
|
||||
|
||||
advisory_total = len(advisories)
|
||||
verified_real = latest_statuses.get("verified-real", 0)
|
||||
verified_synthetic = latest_statuses.get("verified-synthetic", 0)
|
||||
blocked = sum(count for key, count in latest_statuses.items() if key.startswith("blocked-"))
|
||||
manual = advisory_total - verified_real - verified_synthetic - blocked
|
||||
complete = advisory_total > 0 and advisory_total == verified_real
|
||||
return {
|
||||
"generated_at": isoformat(now_utc()),
|
||||
"advisory_total": advisory_total,
|
||||
"latest_statuses": latest_statuses,
|
||||
"historical_statuses": historical_statuses,
|
||||
"verified_real": verified_real,
|
||||
"verified_synthetic": verified_synthetic,
|
||||
"blocked": blocked,
|
||||
"manual": manual,
|
||||
"verified_ratio": round((verified_real / advisory_total) * 100, 1) if advisory_total else 0.0,
|
||||
"complete": complete,
|
||||
"systems": systems_list,
|
||||
"ingest_health": {
|
||||
"failure_count": len(run_summary.get("failures", []) or []),
|
||||
"failures": run_summary.get("failures", []) or [],
|
||||
},
|
||||
"historical_blockers": [
|
||||
"Docker daemon unavailable caused provision-compose-environment blocked-artifact.",
|
||||
"Family profiles previously used note-only attack runners and dry-run placeholders.",
|
||||
"Baseline and browser steps were skipped when environment readiness was not enforced.",
|
||||
"Latest completeness now uses one advisory -> latest run semantics instead of historical run piles.",
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def _write_testing_completeness_report(completeness: Dict[str, Any]) -> None:
|
||||
lines = [
|
||||
"# 全库 Advisory 完整度报告",
|
||||
"",
|
||||
f"- 生成时间: `{completeness['generated_at']}`",
|
||||
f"- 最新 advisory 完整度: `{completeness['verified_real']}/{completeness['advisory_total']}` `verified-real`",
|
||||
f"- 合成验证数量: `{completeness['verified_synthetic']}`",
|
||||
f"- 阻塞数量: `{completeness['blocked']}`",
|
||||
f"- 人工/待补证据数量: `{completeness['manual']}`",
|
||||
f"- 完整度百分比: `{completeness['verified_ratio']}%`",
|
||||
"",
|
||||
"## 系统覆盖矩阵",
|
||||
"",
|
||||
"| 系统 | 总数 | verified-real | verified-synthetic | blocked | manual | family 覆盖 |",
|
||||
"| --- | ---: | ---: | ---: | ---: | ---: | --- |",
|
||||
]
|
||||
for system in completeness["systems"]:
|
||||
family_text = ", ".join(
|
||||
f"{item['family']}({item['verified_real']}/{item['total']})" for item in system["families"]
|
||||
)
|
||||
lines.append(
|
||||
f"| {system['system_id']} | {system['total']} | {system['verified_real']} | {system['verified_synthetic']} | {system['blocked']} | {system['manual']} | {family_text} |"
|
||||
)
|
||||
lines.extend(
|
||||
[
|
||||
"",
|
||||
"## 历史阻塞项修复纪要",
|
||||
"",
|
||||
]
|
||||
)
|
||||
for item in completeness.get("historical_blockers", []):
|
||||
lines.append(f"- {item}")
|
||||
lines.extend(
|
||||
[
|
||||
"",
|
||||
"## Ingest / Source 健康度",
|
||||
"",
|
||||
f"- source failures: `{completeness['ingest_health']['failure_count']}`",
|
||||
]
|
||||
)
|
||||
for item in completeness["ingest_health"].get("failures", []):
|
||||
lines.append(f"- {item}")
|
||||
lines.extend(
|
||||
[
|
||||
"",
|
||||
"## 剩余风险说明",
|
||||
"",
|
||||
"- 本报告按 advisory 的最新 run 计算;历史失败 run 仅保留审计价值,不再污染完整度数字。",
|
||||
"- `browser_required=true` 的案例必须同时存在基线与攻击后浏览器证据,缺失则不会进入 `verified-real`。",
|
||||
"- source collector 健康度单独计数;只有当 failures 归零时,报告与 dashboard 才算真正全绿。",
|
||||
]
|
||||
)
|
||||
write_text(ROOT / "docs" / "testing-completeness-report.md", "\n".join(lines))
|
||||
|
||||
|
||||
def _build_architecture_data(summary: Dict[str, Any], source_map: Dict[str, Any], repro_map: Dict[str, Any]) -> Dict[str, Any]:
|
||||
source_systems = source_map.get("systems", []) or []
|
||||
repro_by_system = {item.get("system_id"): item for item in (repro_map.get("systems", []) or []) if item.get("system_id")}
|
||||
@@ -225,6 +379,7 @@ def _build_architecture_data(summary: Dict[str, Any], source_map: Dict[str, Any]
|
||||
_link("旧版工作台", "/legacy/index.html", "保留的 legacy 回退入口。"),
|
||||
_link("项目功能文档", "/docs/project-features.html", "项目能力、目录结构与自动化链路总览。"),
|
||||
_link("前端设计文档", "/docs/frontend-dashboard-design.html", "当前本地工作台的交互与视觉规范。"),
|
||||
_link("完整度报告", "/docs/testing-completeness-report.html", "89 条 advisory 的最新完整度中文报告。"),
|
||||
_link("安全编码索引", "/docs/secure-code-index.html", "secure-code 修复库本地镜像。"),
|
||||
_link("仓库入口镜像", "/docs/root-readme.html", "仓库根 README 的本地镜像。"),
|
||||
_link("授权模型", "/docs/authorization-model.html", "允许目标范围、全局原则与记录要求。"),
|
||||
@@ -237,6 +392,7 @@ def _build_architecture_data(summary: Dict[str, Any], source_map: Dict[str, Any]
|
||||
|
||||
data_links = [
|
||||
_link("summary.json", "/summary.json", "全局摘要、状态分布和最近失败。"),
|
||||
_link("completeness.json", "/data/completeness.json", "最新 advisory 完整度、系统/family 进度与 ingest 健康度。"),
|
||||
_link("runs.json", "/runs.json", "最近 run 的结构化详情。"),
|
||||
_link("systems.json", "/systems.json", "系统级覆盖与浏览器证据摘要。"),
|
||||
_link("advisories.json", "/advisories.json", "漏洞条目元数据与来源。"),
|
||||
@@ -736,6 +892,12 @@ def _write_dashboard_docs(architecture: Dict[str, Any]) -> None:
|
||||
(ROOT / "08-threat-intel" / "generated" / "coverage-matrix.md").read_text(encoding="utf-8"),
|
||||
"工作台内置镜像页:当前覆盖矩阵生成结果。",
|
||||
),
|
||||
(
|
||||
"testing-completeness-report.html",
|
||||
"中文完整度报告",
|
||||
(ROOT / "docs" / "testing-completeness-report.md").read_text(encoding="utf-8"),
|
||||
"工作台内置镜像页:89 条 advisory 最新完整度、family 矩阵与 ingest 健康度。",
|
||||
),
|
||||
]
|
||||
|
||||
manifest_body = LOVART_VENDOR_MANIFEST.read_text(encoding="utf-8") if LOVART_VENDOR_MANIFEST.exists() else "{}"
|
||||
@@ -981,16 +1143,18 @@ def render_dashboard() -> Dict[str, str]:
|
||||
ensure_dir(DASHBOARD_DIR)
|
||||
advisory_records = load_json_dir(ADVISORIES_DIR)
|
||||
runs = load_json_dir(RUNS_DIR)
|
||||
run_summary = read_json(ROOT / "08-threat-intel" / "generated" / "run-summary.json", default={}) or {}
|
||||
source_map = read_yaml(SOURCE_MAP_PATH, default={}) or {}
|
||||
repro_map = read_yaml(REPRO_MAP_PATH, default={}) or {}
|
||||
source_system_map = {item["system_id"]: item for item in source_map.get("systems", []) if item.get("system_id")}
|
||||
advisory_map = {item["canonical_id"]: item for item in advisory_records if item.get("canonical_id")}
|
||||
merged_advisories = _latest_advisories(advisory_records)
|
||||
advisory_map = {item["canonical_id"]: item for item in merged_advisories if item.get("canonical_id")}
|
||||
profile_map = load_profiles()
|
||||
|
||||
_sync_run_bundles(runs)
|
||||
|
||||
systems: Dict[str, Dict[str, Any]] = {}
|
||||
for advisory in advisory_records:
|
||||
for advisory in merged_advisories:
|
||||
system = systems.setdefault(
|
||||
advisory["system_id"],
|
||||
{
|
||||
@@ -1007,6 +1171,7 @@ def render_dashboard() -> Dict[str, str]:
|
||||
"category": source_system_map.get(advisory["system_id"], {}).get("category", advisory.get("category")),
|
||||
"tier": source_system_map.get(advisory["system_id"], {}).get("tier"),
|
||||
"output_dir": source_system_map.get(advisory["system_id"], {}).get("output_dir"),
|
||||
"families": {},
|
||||
},
|
||||
)
|
||||
system["total"] += 1
|
||||
@@ -1019,7 +1184,7 @@ def render_dashboard() -> Dict[str, str]:
|
||||
system["blocked"] += 1
|
||||
else:
|
||||
system["manual"] += 1
|
||||
browser = advisory.get("browser_evidence", {})
|
||||
browser = advisory.get("browser_evidence") or {}
|
||||
if browser.get("required"):
|
||||
system["browser_required"] += 1
|
||||
if browser.get("present"):
|
||||
@@ -1027,6 +1192,13 @@ def render_dashboard() -> Dict[str, str]:
|
||||
latest = advisory.get("updated_at") or advisory.get("published_at") or ""
|
||||
if latest > system["latest_update"]:
|
||||
system["latest_update"] = latest
|
||||
family = _family_name(advisory, profile_map)
|
||||
family_entry = system["families"].setdefault(family, {"family": family, "total": 0, "verified_real": 0, "manual": 0})
|
||||
family_entry["total"] += 1
|
||||
if status == "verified-real":
|
||||
family_entry["verified_real"] += 1
|
||||
elif status not in {"verified-synthetic"}:
|
||||
family_entry["manual"] += 1
|
||||
|
||||
recent_runs = sorted(runs, key=lambda item: item.get("finished_at") or "", reverse=True)[:100]
|
||||
decorated_runs: List[Dict[str, Any]] = []
|
||||
@@ -1079,32 +1251,57 @@ def render_dashboard() -> Dict[str, str]:
|
||||
|
||||
summary = {
|
||||
"generated_at": isoformat(now_utc()),
|
||||
"advisory_count": len(advisory_records),
|
||||
"advisory_count": len(merged_advisories),
|
||||
"run_count": len(runs),
|
||||
"statuses": {},
|
||||
"run_statuses": {},
|
||||
"recent_failures": [],
|
||||
}
|
||||
for item in runs:
|
||||
for item in merged_advisories:
|
||||
status = item.get("verification_status", "triage-manual")
|
||||
summary["statuses"][status] = summary["statuses"].get(status, 0) + 1
|
||||
summary["systems"] = sorted(systems.values(), key=lambda entry: (-entry["total"], entry["system_id"]))
|
||||
for item in runs:
|
||||
status = item.get("verification_status", "triage-manual")
|
||||
summary["run_statuses"][status] = summary["run_statuses"].get(status, 0) + 1
|
||||
summary["systems"] = sorted(
|
||||
[
|
||||
{
|
||||
**entry,
|
||||
"families": sorted(entry["families"].values(), key=lambda value: value["family"]),
|
||||
}
|
||||
for entry in systems.values()
|
||||
],
|
||||
key=lambda entry: (-entry["total"], entry["system_id"]),
|
||||
)
|
||||
summary["recent_failures"] = [
|
||||
{
|
||||
"run_id": item["run_id"],
|
||||
"advisory_id": item["advisory_id"],
|
||||
"run_id": item.get("last_run_id"),
|
||||
"advisory_id": item["canonical_id"],
|
||||
"status": item.get("verification_status"),
|
||||
"title": item.get("advisory_meta", {}).get("title"),
|
||||
"title": item.get("title"),
|
||||
"blocked_reason": item.get("blocked_reason"),
|
||||
}
|
||||
for item in decorated_runs
|
||||
for item in sorted(merged_advisories, key=lambda value: value.get("updated_at") or value.get("published_at") or "", reverse=True)
|
||||
if item.get("verification_status") in {"triage-manual", "blocked-artifact", "blocked-destructive"}
|
||||
][:20]
|
||||
completeness = _build_completeness(merged_advisories, runs, profile_map, run_summary)
|
||||
summary["completeness"] = {
|
||||
"advisory_total": completeness["advisory_total"],
|
||||
"verified_real": completeness["verified_real"],
|
||||
"verified_synthetic": completeness["verified_synthetic"],
|
||||
"blocked": completeness["blocked"],
|
||||
"manual": completeness["manual"],
|
||||
"verified_ratio": completeness["verified_ratio"],
|
||||
"complete": completeness["complete"],
|
||||
}
|
||||
|
||||
write_json(DASHBOARD_DIR / "summary.json", summary)
|
||||
write_json(DASHBOARD_DIR / "runs.json", decorated_runs)
|
||||
write_json(DASHBOARD_DIR / "systems.json", summary["systems"])
|
||||
write_json(DASHBOARD_DIR / "advisories.json", {key: _advisory_meta(value) for key, value in advisory_map.items()})
|
||||
write_json(DASHBOARD_DIR / "profiles.json", {key: _profile_meta(value) for key, value in profile_map.items()})
|
||||
write_json(DASHBOARD_DIR / "data" / "completeness.json", completeness)
|
||||
_write_testing_completeness_report(completeness)
|
||||
architecture = _build_architecture_data(summary, source_map, repro_map)
|
||||
write_json(DASHBOARD_DIR / "architecture.json", architecture)
|
||||
|
||||
|
||||
在新工单中引用
屏蔽一个用户