From 39a6eb6e193e6682aa8b2714247159b894a74f6a Mon Sep 17 00:00:00 2001 From: hao Date: Tue, 17 Mar 2026 01:15:00 -0700 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0:=208=20=E4=B8=AA=E6=96=87?= =?UTF-8?q?=E4=BB=B6=20-=202026-03-17=2001:15:00?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dashboard_templates/legacy/assets/app.js | 573 +++++++++ .../legacy/assets/styles.css | 728 +++++++++++ .../lab/dashboard_templates/legacy/index.html | 81 ++ .../lovart/assets/icons.svg | 74 ++ .../lovart/assets/styles.css | 1069 ++++++++++++++++ .../lab/dashboard_templates/lovart/index.html | 168 +++ .../464011bb-fbbc-4bd4-98f8-90897dd43612.html | 1097 +++++++++++++++++ .../lovart/vendor/source-manifest.json | 18 + 8 files changed, 3808 insertions(+) create mode 100644 scripts/lab/dashboard_templates/legacy/assets/app.js create mode 100644 scripts/lab/dashboard_templates/legacy/assets/styles.css create mode 100644 scripts/lab/dashboard_templates/legacy/index.html create mode 100644 scripts/lab/dashboard_templates/lovart/assets/icons.svg create mode 100644 scripts/lab/dashboard_templates/lovart/assets/styles.css create mode 100644 scripts/lab/dashboard_templates/lovart/index.html create mode 100644 scripts/lab/dashboard_templates/lovart/vendor/464011bb-fbbc-4bd4-98f8-90897dd43612.html create mode 100644 scripts/lab/dashboard_templates/lovart/vendor/source-manifest.json diff --git a/scripts/lab/dashboard_templates/legacy/assets/app.js b/scripts/lab/dashboard_templates/legacy/assets/app.js new file mode 100644 index 00000000..7710b047 --- /dev/null +++ b/scripts/lab/dashboard_templates/legacy/assets/app.js @@ -0,0 +1,573 @@ + +const state = { + summary: null, + runs: [], + systems: [], + advisories: {}, + profiles: {}, + selectedRunId: null, + selectedArtifact: null, + filters: { search: "", system: "", status: "", family: "" }, + autoRefresh: true, + refreshMs: 5000, + refreshHandle: null, +}; + +const $ = (id) => document.getElementById(id); +const statusClass = (status) => `status-pill ${({ + "blocked-artifact": "status-blocked-artifact", + "blocked-destructive": "status-blocked-destructive", + "triage-manual": "status-triage-manual", + "verified-real": "status-verified-real", + "verified-synthetic": "status-verified-synthetic", + "suspected": "status-suspected", + "completed": "status-verified-real", + "failed": "status-blocked-artifact", + "skipped": "status-triage-manual" +})[status] || "status-default"}`; + +function escapeHtml(value) { + return String(value ?? "") + .replaceAll("&", "&") + .replaceAll("<", "<") + .replaceAll(">", ">") + .replaceAll('"', """); +} + +function timeAgo(value) { + if (!value) return "-"; + const diff = Date.now() - new Date(value).getTime(); + if (Number.isNaN(diff)) return value; + const seconds = Math.floor(diff / 1000); + if (seconds < 60) return `${seconds}s ago`; + const minutes = Math.floor(seconds / 60); + if (minutes < 60) return `${minutes}m ago`; + const hours = Math.floor(minutes / 60); + if (hours < 24) return `${hours}h ago`; + const days = Math.floor(hours / 24); + return `${days}d ago`; +} + +async function fetchJson(url) { + const response = await fetch(`${url}?t=${Date.now()}`, { cache: "no-store" }); + if (!response.ok) { + throw new Error(`${url} -> ${response.status}`); + } + return response.json(); +} + +async function loadData(preserveSelection = true) { + $("syncState").innerHTML = `Refreshing${new Date().toLocaleTimeString()}`; + const previousRun = state.selectedRunId; + try { + const [summary, runs, systems, advisories, profiles] = await Promise.all([ + fetchJson("./summary.json"), + fetchJson("./runs.json"), + fetchJson("./systems.json"), + fetchJson("./advisories.json"), + fetchJson("./profiles.json"), + ]); + state.summary = summary; + state.runs = runs; + state.systems = systems; + state.advisories = advisories; + state.profiles = profiles; + hydrateFilterOptions(); + + const hashRun = location.hash.startsWith("#run=") ? location.hash.replace("#run=", "") : null; + const selectedCandidate = preserveSelection ? (hashRun || previousRun) : hashRun; + if (selectedCandidate && runs.some((item) => item.run_id === selectedCandidate)) { + state.selectedRunId = selectedCandidate; + } else { + state.selectedRunId = runs[0]?.run_id || null; + } + + renderDashboard(); + $("syncState").innerHTML = `Live${summary.generated_at || new Date().toISOString()}`; + } catch (error) { + $("syncState").innerHTML = `Load Failed${escapeHtml(error.message)}`; + $("runList").innerHTML = `
Dashboard load failed: ${escapeHtml(error.message)}
`; + $("detailRoot").innerHTML = `
Unable to load dashboard data. Check generated JSON and local static server state.
`; + } +} + +function filteredRuns() { + return state.runs.filter((item) => { + if (state.filters.system && item.system_id !== state.filters.system) return false; + if (state.filters.status && item.verification_status !== state.filters.status) return false; + if (state.filters.family && item.repro_profile_id !== state.filters.family) return false; + if (!state.filters.search) return true; + const advisoryTitle = item.advisory_meta?.title || ""; + const haystack = [item.run_id, item.advisory_id, item.system_id, item.repro_profile_id, advisoryTitle] + .join(" ") + .toLowerCase(); + return haystack.includes(state.filters.search); + }); +} + +function renderMetrics() { + const metrics = [ + { label: "Advisories", value: state.summary?.advisory_count ?? 0 }, + { label: "Run Bundles", value: state.summary?.run_count ?? 0 }, + ...Object.entries(state.summary?.statuses || {}).map(([label, value]) => ({ label, value })), + ]; + $("metrics").innerHTML = metrics + .map((item) => `
${escapeHtml(item.label)}${escapeHtml(item.value)}
`) + .join(""); +} + +function renderSystemCoverage() { + $("systemCoverage").innerHTML = state.systems + .map((system) => { + const total = Math.max(system.total || 0, 1); + const verified = (system.verified_real || 0) + (system.verified_synthetic || 0); + const fill = Math.round((verified / total) * 100); + return ` +
+
+ ${escapeHtml(system.display_name || system.system_id)} + ${escapeHtml(system.browser_present || 0)}/${escapeHtml(system.browser_required || 0)} browser +
+
${escapeHtml(system.system_id)} · latest ${escapeHtml(system.latest_update || "-")}
+
+ real ${escapeHtml(system.verified_real || 0)} + synthetic ${escapeHtml(system.verified_synthetic || 0)} + blocked ${escapeHtml(system.blocked || 0)} + manual ${escapeHtml(system.manual || 0)} +
+
+
+ `; + }) + .join(""); +} + +function renderFailures() { + const failures = state.summary?.recent_failures || []; + $("failureFeed").innerHTML = failures.length + ? failures + .map((item) => ` +
+
+ ${escapeHtml(item.run_id)} + ${escapeHtml(item.status)} +
+
${escapeHtml(item.title || item.advisory_id)}
+
${escapeHtml(item.blocked_reason || "-")}
+
+ `) + .join("") + : `
No recent blockers.
`; +} + +function renderRunList() { + const filtered = filteredRuns(); + $("runCount").textContent = `${filtered.length} shown`; + $("runList").innerHTML = filtered.length + ? filtered + .map((item) => { + const active = item.run_id === state.selectedRunId ? "is-active" : ""; + const title = item.advisory_meta?.title || item.advisory_id; + const reasoning = item.reasoning_lines?.[0] || item.blocked_reason || ""; + const browserLabel = item.browser_evidence?.present ? "ready" : (item.browser_evidence?.required ? "required" : "n/a"); + return ` + + `; + }) + .join("") + : `
No runs match the current filters.
`; + + document.querySelectorAll("[data-run-id]").forEach((button) => { + button.addEventListener("click", () => { + state.selectedRunId = button.dataset.runId; + location.hash = `run=${state.selectedRunId}`; + renderRunList(); + renderDetail(); + }); + }); +} + +function renderDashboard() { + renderMetrics(); + renderSystemCoverage(); + renderFailures(); + renderRunList(); + renderDetail(); +} + +function setFilterListeners() { + [["searchInput", "search"], ["systemFilter", "system"], ["statusFilter", "status"], ["familyFilter", "family"]].forEach(([id, key]) => { + $(id).addEventListener("input", (event) => { + state.filters[key] = String(event.target.value || "").trim().toLowerCase(); + if (key !== "search") { + state.filters[key] = String(event.target.value || ""); + } + renderRunList(); + }); + }); +} + +function hydrateFilterOptions() { + const distinct = (items) => [...new Set(items.filter(Boolean))].sort(); + const patchOptions = (id, values) => { + const control = $(id); + const current = control.value; + control.innerHTML = control.dataset.base; + control.innerHTML += distinct(values).map((value) => ``).join(""); + control.value = current; + }; + patchOptions("systemFilter", state.runs.map((item) => item.system_id)); + patchOptions("statusFilter", state.runs.map((item) => item.verification_status)); + patchOptions("familyFilter", state.runs.map((item) => item.repro_profile_id)); +} + +function defaultArtifact(run) { + const preference = ["attack", "requests", "container", "browser", "baseline", "compose", "reports"]; + for (const key of preference) { + const group = (run.artifact_groups || []).find((item) => item.key === key && item.items?.length); + if (!group) continue; + const preferredText = group.items.find((item) => item.kind === "text"); + return preferredText || group.items[0]; + } + return null; +} + +function totalProgress(progress) { + const values = Object.values(progress || {}).map((value) => Number(value || 0)); + return values.reduce((sum, value) => sum + value, 0); +} + +function renderProgressStrip(progress) { + const total = totalProgress(progress); + if (!total) { + return ` +
+
+
No timeline progress recorded.
+
+ `; + } + const order = [ + ["completed", "Completed", "progress-completed"], + ["blocked", "Blocked", "progress-blocked"], + ["failed", "Failed", "progress-failed"], + ["skipped", "Skipped", "progress-skipped"], + ["planned", "Planned", "progress-planned"], + ["other", "Other", "progress-other"], + ]; + const segments = order + .filter(([key]) => Number(progress?.[key] || 0) > 0) + .map(([key, _label, klass]) => { + const count = Number(progress?.[key] || 0); + const pct = Math.max((count / total) * 100, 4); + return `
`; + }) + .join(""); + const legend = order + .filter(([key]) => Number(progress?.[key] || 0) > 0) + .map(([key, label, klass]) => ` + + + ${escapeHtml(label)} ${escapeHtml(progress?.[key] || 0)} + + `) + .join(""); + return ` +
+
${segments}
+
${legend}
+
+ `; +} + +function renderStageCards(run) { + const timeline = run.timeline || []; + if (!timeline.length) { + return `
No stage records available.
`; + } + return ` +
+ ${timeline.map((item) => ` +
+ ${escapeHtml(item.step || "-")} +
${escapeHtml(item.status || "unknown")}
+
${escapeHtml(item.detail || "-")}
+
${escapeHtml(item.at || "-")}
+
+ `).join("")} +
+ `; +} + +async function openArtifact(href, label, kind) { + state.selectedArtifact = { href, label, kind }; + document.querySelectorAll(".artifact-button").forEach((button) => { + button.classList.toggle("is-active", button.dataset.href === href); + }); + $("artifactLabel").textContent = label; + $("artifactOpen").href = href; + $("artifactMeta").textContent = href; + try { + if (kind === "image") { + $("artifactViewer").innerHTML = `${escapeHtml(label)}`; + return; + } + if (href.endsWith(".html")) { + $("artifactViewer").innerHTML = ``; + return; + } + const response = await fetch(`${href}?t=${Date.now()}`, { cache: "no-store" }); + if (!response.ok) throw new Error(`${href} -> ${response.status}`); + const text = await response.text(); + let formatted = text; + if (href.endsWith(".json")) { + try { + formatted = JSON.stringify(JSON.parse(text), null, 2); + } catch (_error) { + } + } + $("artifactViewer").innerHTML = `
${escapeHtml(formatted)}
`; + } catch (error) { + $("artifactViewer").innerHTML = `
Artifact load failed: ${escapeHtml(error.message)}
`; + } +} + +function renderDetail() { + const run = state.runs.find((item) => item.run_id === state.selectedRunId); + if (!run) { + $("detailRoot").innerHTML = `
Select a run to inspect full timeline, logs, sources, and reasoning.
`; + return; + } + + const advisory = run.advisory_meta || {}; + const profile = run.profile_meta || {}; + const screenshotItems = (run.artifact_groups || []) + .find((group) => group.key === "browser") + ?.items.filter((item) => item.kind === "image") || []; + + $("detailRoot").innerHTML = ` +
+
Local Verification Workspace
+
+ ${escapeHtml(run.verification_status)} +
+ ${escapeHtml(run.system_id)} + ${escapeHtml(run.repro_profile_id)} + ${escapeHtml(run.artifact_mode)} + ${escapeHtml(run.verification_mode)} + ${escapeHtml(run.target_env || "local-docker")} +
+
+

${escapeHtml(advisory.title || run.advisory_id)}

+

${escapeHtml(advisory.summary || "No summary available.")}

+ +
+
Timeline Steps${escapeHtml(run.timeline?.length || 0)}
+
Artifacts${escapeHtml((run.artifact_groups || []).reduce((sum, group) => sum + group.count, 0))}
+
Browser${run.browser_evidence?.present ? "Ready" : (run.browser_evidence?.required ? "Required" : "Optional")}
+
Finished${escapeHtml(timeAgo(run.finished_at))}
+
+
+ +
+
+
+ Progress Timeline${escapeHtml(run.timeline?.length || 0)} steps +
+ ${renderProgressStrip(run.progress)} + ${renderStageCards(run)} +
+ ${(run.timeline || []).map((item) => ` +
+
${escapeHtml(item.at || "-")}
+
${escapeHtml(item.step || "-")}
+
+
${escapeHtml(item.status || "unknown")}
+
${escapeHtml(item.detail || "-")}
+
+
+ `).join("") || `
No timeline items available.
`} +
+
+
+ +
+ Attack Plan & Reasoning${escapeHtml(profile.vuln_family || "unknown")} +
+ ${run.blocked_reason ? `
Failure reason
${escapeHtml(run.blocked_reason)}
` : ""} +
+ destructive risk ${escapeHtml(profile.destructive_risk || "-")} + cleanup ${escapeHtml(profile.cleanup_policy || "-")} + targets ${(profile.allowed_target_types || []).join(", ") || "-"} +
+
+ ${(run.reasoning_lines || []).map((line) => `
${escapeHtml(line)}
`).join("")} +
+
+ ${(profile.success_criteria || []).map((line) => `${escapeHtml(line)}`).join("")} +
+
+
+ +
+ Evidence Explorer${escapeHtml((run.artifact_groups || []).length)} groups +
+ ${(run.artifact_groups || []).map((group) => ` +
+

${escapeHtml(group.label)} · ${escapeHtml(group.count)}

+
+ ${group.items.map((item) => ` + + `).join("")} +
+
+ `).join("") || `
No artifacts linked for this run.
`} + ${screenshotItems.length ? ` + + ` : ""} +
+
+ +
+ Live Log Viewer${state.selectedArtifact ? "active" : "idle"} +
+
+
+
+ ${escapeHtml(state.selectedArtifact?.label || "Select an artifact")} +
${escapeHtml(state.selectedArtifact?.href || "Artifacts and logs can be previewed here.")}
+
+
+ Open artifact + +
+
+
Select a report, log, JSON, screenshot, or timeline file to preview it here.
+
+
+
+
+ +
+
+ Sources & Fix Topics${escapeHtml((advisory.secondary_source_urls || []).length + (advisory.official_source_url ? 1 : 0))} links +
+
+ ${(advisory.aliases || []).map((alias) => `${escapeHtml(alias)}`).join("")} +
+
+ ${advisory.official_source_url ? `${escapeHtml(advisory.official_source_url)}` : `
No official source linked.
`} + ${(advisory.secondary_source_urls || []).map((ref) => `${escapeHtml(ref)}`).join("")} +
+
+ ${(advisory.secure_code_topics || []).map((topic) => `${escapeHtml(topic)}`).join("")} +
+
+
+ +
+ Run JSONraw +
${escapeHtml(JSON.stringify(run, null, 2))}
+
+ +
+ Advisory JSONraw +
${escapeHtml(JSON.stringify(advisory, null, 2))}
+
+ +
+ Profile JSONraw +
${escapeHtml(JSON.stringify(profile, null, 2))}
+
+
+
+ `; + + document.querySelectorAll(".artifact-button").forEach((button) => { + button.addEventListener("click", () => openArtifact(button.dataset.href, button.dataset.label, button.dataset.kind)); + }); + + $("refreshArtifact")?.addEventListener("click", () => { + if (state.selectedArtifact) { + openArtifact(state.selectedArtifact.href, state.selectedArtifact.label, state.selectedArtifact.kind); + } + }); + + if (!state.selectedArtifact || !(run.artifact_groups || []).some((group) => group.items.some((item) => item.href === state.selectedArtifact.href))) { + const artifact = defaultArtifact(run); + if (artifact) { + openArtifact(artifact.href, artifact.label, artifact.kind); + } + } else { + openArtifact(state.selectedArtifact.href, state.selectedArtifact.label, state.selectedArtifact.kind); + } +} + +function attachGlobalActions() { + $("searchInput").addEventListener("input", (event) => { + state.filters.search = String(event.target.value || "").trim().toLowerCase(); + renderRunList(); + }); + [["systemFilter", "system"], ["statusFilter", "status"], ["familyFilter", "family"]].forEach(([id, key]) => { + $(id).addEventListener("input", (event) => { + state.filters[key] = String(event.target.value || ""); + renderRunList(); + }); + }); + $("refreshDashboard").addEventListener("click", () => loadData(false)); + $("autoRefresh").addEventListener("change", (event) => { + state.autoRefresh = Boolean(event.target.checked); + startRefreshLoop(); + }); +} + +function startRefreshLoop() { + if (state.refreshHandle) { + clearInterval(state.refreshHandle); + state.refreshHandle = null; + } + if (!state.autoRefresh) return; + state.refreshHandle = setInterval(() => loadData(true), state.refreshMs); +} + +async function init() { + ["systemFilter", "statusFilter", "familyFilter"].forEach((id) => { + $(id).dataset.base = $(id).innerHTML; + }); + attachGlobalActions(); + await loadData(false); + startRefreshLoop(); + window.addEventListener("hashchange", () => loadData(false)); +} + +document.addEventListener("DOMContentLoaded", init); diff --git a/scripts/lab/dashboard_templates/legacy/assets/styles.css b/scripts/lab/dashboard_templates/legacy/assets/styles.css new file mode 100644 index 00000000..61c46ff2 --- /dev/null +++ b/scripts/lab/dashboard_templates/legacy/assets/styles.css @@ -0,0 +1,728 @@ + +:root { + --bg: #07111f; + --panel: rgba(9, 18, 32, 0.86); + --panel-2: rgba(10, 24, 44, 0.92); + --panel-soft: rgba(18, 32, 56, 0.74); + --border: rgba(137, 171, 214, 0.22); + --text: #f7fafc; + --muted: #9fb3ca; + --accent: #5eead4; + --accent-2: #ffb86b; + --accent-3: #90cdf4; + --danger: #ff7b7b; + --warning: #ffd166; + --success: #6ee7a5; + --shadow: 0 24px 80px rgba(1, 7, 20, 0.45); + --radius: 20px; +} + +* { box-sizing: border-box; } +html, body { margin: 0; min-height: 100%; } +body { + font-family: "IBM Plex Sans", "Avenir Next", "Segoe UI", sans-serif; + background: + radial-gradient(circle at top left, rgba(94, 234, 212, 0.15), transparent 28%), + radial-gradient(circle at top right, rgba(255, 184, 107, 0.18), transparent 22%), + linear-gradient(145deg, #050c16 0%, #08111f 44%, #0d1c31 100%); + color: var(--text); + overflow-x: hidden; +} + +body::before { + content: ""; + position: fixed; + inset: 0; + pointer-events: none; + background-image: + linear-gradient(rgba(255,255,255,0.03) 1px, transparent 1px), + linear-gradient(90deg, rgba(255,255,255,0.03) 1px, transparent 1px); + background-size: 32px 32px; + mask-image: radial-gradient(circle at center, black 36%, transparent 78%); + opacity: 0.28; +} + +a { color: var(--accent); text-decoration: none; } +a:hover { text-decoration: underline; } +button, input, select { + font: inherit; +} + +.dashboard-shell { + position: relative; + max-width: 1640px; + margin: 0 auto; + padding: 32px 24px 40px; +} + +.hero { + position: sticky; + top: 0; + z-index: 20; + backdrop-filter: blur(18px); + background: linear-gradient(180deg, rgba(7, 17, 31, 0.94), rgba(7, 17, 31, 0.75)); + border: 1px solid var(--border); + border-radius: 28px; + padding: 24px 24px 20px; + box-shadow: var(--shadow); +} + +.hero-grid { + display: grid; + grid-template-columns: 1.6fr 1fr; + gap: 20px; + align-items: start; +} + +.eyebrow { + display: inline-flex; + align-items: center; + gap: 8px; + color: var(--muted); + font-size: 0.88rem; + letter-spacing: 0.12em; + text-transform: uppercase; +} + +.eyebrow::before { + content: ""; + width: 10px; + height: 10px; + border-radius: 999px; + background: radial-gradient(circle, var(--accent), rgba(94, 234, 212, 0.15)); + box-shadow: 0 0 24px rgba(94, 234, 212, 0.8); + animation: pulse 2.8s ease-in-out infinite; +} + +.hero h1 { + margin: 12px 0 10px; + font-family: "IBM Plex Serif", "Iowan Old Style", Georgia, serif; + font-size: clamp(2rem, 4vw, 3.5rem); + line-height: 1.02; +} + +.hero p { + margin: 0; + color: var(--muted); + max-width: 74ch; +} + +.hero-actions { + display: flex; + flex-wrap: wrap; + gap: 12px; + margin-top: 18px; +} + +.chip, .ghost-chip { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 8px; + border-radius: 999px; + border: 1px solid var(--border); + padding: 10px 14px; + background: rgba(255,255,255,0.06); + color: var(--text); +} + +.ghost-chip { + background: rgba(255,255,255,0.04); +} + +.hero-meta { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 14px; +} + +.meta-card, .glass-panel { + background: var(--panel); + border: 1px solid var(--border); + border-radius: var(--radius); + box-shadow: var(--shadow); +} + +.meta-card { + padding: 18px; + min-height: 116px; +} + +.meta-card strong { + display: block; + color: var(--muted); + font-size: 0.84rem; + letter-spacing: 0.08em; + text-transform: uppercase; +} + +.meta-card span { + display: block; + margin-top: 10px; + font-size: 2rem; + font-weight: 700; +} + +.workspace { + display: grid; + grid-template-columns: 420px minmax(0, 1fr); + gap: 20px; + margin-top: 22px; +} + +.sidebar { + display: flex; + flex-direction: column; + gap: 18px; +} + +.panel-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + margin-bottom: 16px; +} + +.panel-header h2, .panel-header h3 { + margin: 0; + font-size: 1rem; + letter-spacing: 0.04em; + text-transform: uppercase; + color: var(--muted); +} + +.glass-panel { + padding: 18px; + background: + linear-gradient(180deg, rgba(255,255,255,0.04), transparent 35%), + var(--panel); +} + +.filters { + display: grid; + gap: 12px; +} + +.filters label { + display: grid; + gap: 6px; + color: var(--muted); + font-size: 0.9rem; +} + +.filters input, .filters select { + width: 100%; + background: rgba(255,255,255,0.05); + color: var(--text); + border: 1px solid rgba(159, 179, 202, 0.18); + border-radius: 14px; + padding: 12px 14px; +} + +.run-list { + display: grid; + gap: 12px; + max-height: calc(100vh - 460px); + overflow: auto; + padding-right: 4px; +} + +.run-card { + width: 100%; + text-align: left; + padding: 16px; + border-radius: 18px; + border: 1px solid rgba(159, 179, 202, 0.14); + background: linear-gradient(180deg, rgba(255,255,255,0.05), rgba(255,255,255,0.03)); + color: var(--text); + cursor: pointer; + transition: transform 180ms ease, border-color 180ms ease, background 180ms ease; +} + +.run-card:hover, .run-card.is-active { + transform: translateY(-1px); + border-color: rgba(94, 234, 212, 0.42); + background: linear-gradient(180deg, rgba(94, 234, 212, 0.14), rgba(255,255,255,0.05)); +} + +.run-card-top, .flex-row { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; +} + +.run-card h4 { + margin: 10px 0 8px; + font-size: 1rem; + line-height: 1.35; +} + +.mini-muted { + color: var(--muted); + font-size: 0.86rem; +} + +.status-pill { + display: inline-flex; + align-items: center; + gap: 7px; + border-radius: 999px; + padding: 6px 10px; + font-size: 0.82rem; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.06em; + border: 1px solid transparent; +} + +.status-pill::before { + content: ""; + width: 8px; + height: 8px; + border-radius: 999px; + background: currentColor; + box-shadow: 0 0 16px currentColor; +} + +.status-blocked-artifact, .status-blocked-destructive { + color: var(--danger); + background: rgba(255, 123, 123, 0.14); + border-color: rgba(255, 123, 123, 0.24); +} + +.status-triage-manual, .status-suspected { + color: var(--warning); + background: rgba(255, 209, 102, 0.14); + border-color: rgba(255, 209, 102, 0.24); +} + +.status-verified-real { + color: var(--success); + background: rgba(110, 231, 165, 0.14); + border-color: rgba(110, 231, 165, 0.24); +} + +.status-verified-synthetic { + color: var(--accent-3); + background: rgba(144, 205, 244, 0.14); + border-color: rgba(144, 205, 244, 0.24); +} + +.status-default { + color: var(--accent); + background: rgba(94, 234, 212, 0.14); + border-color: rgba(94, 234, 212, 0.24); +} + +.detail-view { + display: grid; + gap: 18px; +} + +.detail-hero { + padding: 22px; + overflow: hidden; + position: relative; +} + +.detail-hero::after { + content: ""; + position: absolute; + inset: auto -20% -55% 25%; + height: 220px; + background: radial-gradient(circle, rgba(94, 234, 212, 0.2), transparent 55%); + pointer-events: none; +} + +.detail-headline { + margin: 8px 0 12px; + font-family: "IBM Plex Serif", "Iowan Old Style", Georgia, serif; + font-size: clamp(1.6rem, 3vw, 2.8rem); + line-height: 1.08; +} + +.tag-row, .link-row, .artifact-row { + display: flex; + flex-wrap: wrap; + gap: 10px; +} + +.tag { + display: inline-flex; + align-items: center; + padding: 7px 10px; + border-radius: 999px; + background: rgba(255,255,255,0.06); + border: 1px solid rgba(159, 179, 202, 0.18); + color: var(--text); + font-size: 0.86rem; +} + +.stat-grid { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 12px; + margin-top: 18px; +} + +.stat-card { + padding: 14px; + border-radius: 16px; + background: rgba(255,255,255,0.04); + border: 1px solid rgba(159, 179, 202, 0.16); +} + +.stat-card strong { + display: block; + color: var(--muted); + font-size: 0.78rem; + text-transform: uppercase; + letter-spacing: 0.08em; +} + +.stat-card span { + display: block; + margin-top: 10px; + font-size: 1.15rem; + font-weight: 700; +} + +.detail-grid { + display: grid; + grid-template-columns: minmax(0, 1fr) 360px; + gap: 18px; +} + +.stack { + display: grid; + gap: 18px; +} + +.progress-strip { + display: grid; + gap: 12px; + margin-bottom: 16px; +} + +.progress-bar { + display: flex; + width: 100%; + min-height: 12px; + overflow: hidden; + border-radius: 999px; + background: rgba(255,255,255,0.08); + border: 1px solid rgba(159, 179, 202, 0.14); +} + +.progress-segment { + min-width: 10px; + transition: width 180ms ease; +} + +.progress-completed { background: linear-gradient(90deg, rgba(110, 231, 165, 0.9), rgba(94, 234, 212, 0.9)); } +.progress-blocked { background: linear-gradient(90deg, rgba(255, 123, 123, 0.95), rgba(255, 160, 122, 0.9)); } +.progress-failed { background: linear-gradient(90deg, rgba(255, 123, 123, 0.92), rgba(255, 209, 102, 0.88)); } +.progress-skipped { background: linear-gradient(90deg, rgba(255,255,255,0.22), rgba(159, 179, 202, 0.3)); } +.progress-planned { background: linear-gradient(90deg, rgba(144, 205, 244, 0.82), rgba(94, 234, 212, 0.72)); } +.progress-other { background: linear-gradient(90deg, rgba(255,255,255,0.18), rgba(255,255,255,0.1)); } + +.progress-legend { + display: flex; + flex-wrap: wrap; + gap: 10px; +} + +.progress-legend .tag { + gap: 7px; +} + +.progress-legend .swatch { + width: 10px; + height: 10px; + border-radius: 999px; + display: inline-block; +} + +.stage-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 12px; + margin-bottom: 18px; +} + +.stage-card { + padding: 14px; + border-radius: 16px; + background: rgba(255,255,255,0.04); + border: 1px solid rgba(159, 179, 202, 0.16); +} + +.stage-card strong { + display: block; + margin-bottom: 10px; +} + +.accordion { + overflow: hidden; +} + +.accordion > summary { + list-style: none; + cursor: pointer; + padding: 18px 20px; + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; +} + +.accordion > summary::-webkit-details-marker { display: none; } +.accordion > summary span { + font-size: 1rem; + text-transform: uppercase; + letter-spacing: 0.08em; + color: var(--muted); +} + +.accordion .accordion-content { + padding: 0 20px 20px; + border-top: 1px solid rgba(159, 179, 202, 0.12); +} + +.timeline-list { + display: grid; + gap: 12px; +} + +.timeline-item { + display: grid; + grid-template-columns: 120px 180px minmax(0, 1fr); + gap: 12px; + padding: 12px 0; + border-bottom: 1px solid rgba(159, 179, 202, 0.12); +} + +.timeline-item:last-child { + border-bottom: 0; +} + +.timeline-step { + font-weight: 700; +} + +.artifact-group { + margin-bottom: 14px; +} + +.artifact-group h4 { + margin: 0 0 10px; + color: var(--muted); + font-size: 0.88rem; + text-transform: uppercase; + letter-spacing: 0.08em; +} + +.artifact-button { + display: inline-flex; + align-items: center; + gap: 8px; + margin: 0 10px 10px 0; + padding: 10px 12px; + border-radius: 14px; + border: 1px solid rgba(159, 179, 202, 0.16); + background: rgba(255,255,255,0.05); + color: var(--text); + cursor: pointer; +} + +.artifact-button:hover, .artifact-button.is-active { + border-color: rgba(94, 234, 212, 0.4); + background: rgba(94, 234, 212, 0.12); +} + +.log-viewer { + min-height: 420px; + display: grid; + gap: 14px; +} + +.viewer-toolbar { + display: flex; + flex-wrap: wrap; + justify-content: space-between; + gap: 10px; + align-items: center; +} + +.viewer-frame { + background: rgba(2, 8, 22, 0.88); + border: 1px solid rgba(159, 179, 202, 0.18); + border-radius: 16px; + min-height: 300px; + overflow: hidden; +} + +.viewer-frame pre { + margin: 0; + padding: 18px; + max-height: 560px; + overflow: auto; + font-family: "IBM Plex Mono", "SFMono-Regular", "Menlo", monospace; + font-size: 0.88rem; + line-height: 1.6; + color: #d6e5f5; + white-space: pre-wrap; +} + +.viewer-frame img { + display: block; + width: 100%; + height: auto; +} + +.gallery { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); + gap: 14px; +} + +.gallery button { + all: unset; + cursor: pointer; + border-radius: 18px; + overflow: hidden; + border: 1px solid rgba(159, 179, 202, 0.18); + background: rgba(255,255,255,0.04); +} + +.gallery img { + display: block; + width: 100%; + aspect-ratio: 4 / 3; + object-fit: cover; +} + +.gallery figcaption { + padding: 10px 12px 14px; + color: var(--muted); + font-size: 0.84rem; +} + +.failure-callout { + padding: 16px 18px; + border-radius: 18px; + border: 1px solid rgba(255, 123, 123, 0.2); + background: rgba(255, 123, 123, 0.09); +} + +.json-block { + background: rgba(2, 8, 22, 0.72); + border-radius: 16px; + border: 1px solid rgba(159, 179, 202, 0.14); + padding: 16px; + overflow: auto; + font-family: "IBM Plex Mono", "SFMono-Regular", monospace; + font-size: 0.84rem; + line-height: 1.55; + color: #c9d8e8; +} + +.empty-state { + padding: 40px 24px; + text-align: center; + color: var(--muted); +} + +.failure-feed { + display: grid; + gap: 10px; +} + +.failure-item { + padding: 12px 14px; + border-radius: 16px; + background: rgba(255,255,255,0.04); + border: 1px solid rgba(159, 179, 202, 0.16); +} + +.system-grid { + display: grid; + gap: 10px; +} + +.system-card { + padding: 14px 16px; + border-radius: 16px; + background: rgba(255,255,255,0.04); + border: 1px solid rgba(159, 179, 202, 0.14); +} + +.meter { + position: relative; + height: 10px; + border-radius: 999px; + background: rgba(255,255,255,0.08); + overflow: hidden; + margin-top: 10px; +} + +.meter > span { + position: absolute; + inset: 0 auto 0 0; + width: var(--fill, 0%); + background: linear-gradient(90deg, var(--accent), var(--accent-2)); + border-radius: inherit; +} + +.sync-indicator { + display: inline-flex; + align-items: center; + gap: 8px; +} + +.sync-indicator strong { + color: var(--text); +} + +.dot { + width: 10px; + height: 10px; + border-radius: 999px; + background: var(--accent); + box-shadow: 0 0 18px rgba(94, 234, 212, 0.8); +} + +@keyframes pulse { + 0%, 100% { transform: scale(1); opacity: 0.88; } + 50% { transform: scale(1.35); opacity: 1; } +} + +@media (max-width: 1280px) { + .workspace, .detail-grid, .hero-grid { + grid-template-columns: 1fr; + } + + .stat-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } +} + +@media (max-width: 760px) { + .dashboard-shell { + padding: 18px 14px 32px; + } + + .hero { + position: static; + } + + .stat-grid, .hero-meta { + grid-template-columns: 1fr; + } + + .timeline-item { + grid-template-columns: 1fr; + } +} diff --git a/scripts/lab/dashboard_templates/legacy/index.html b/scripts/lab/dashboard_templates/legacy/index.html new file mode 100644 index 00000000..3bbf6390 --- /dev/null +++ b/scripts/lab/dashboard_templates/legacy/index.html @@ -0,0 +1,81 @@ + + + + + + + websafe authorized lab dashboard + + + +
+
+
+
+
Authorized Lab Dashboard
+

本地攻防实证工作台

+

面向授权实验场景的本地静态前端。聚合 advisory、run bundle、日志、浏览器证据、失败原因、利用思路与源头信息,并支持可折叠细节与自动刷新。

+
+ + + Open Summary JSON + Open Feature Docs +
+
+
+
+

Sync State

+
BootingLoading generated JSON
+
+
+
+
+
+ +
+ + +
+
Select a run to inspect full details.
+
+
+
+ + + diff --git a/scripts/lab/dashboard_templates/lovart/assets/icons.svg b/scripts/lab/dashboard_templates/lovart/assets/icons.svg new file mode 100644 index 00000000..714a6d98 --- /dev/null +++ b/scripts/lab/dashboard_templates/lovart/assets/icons.svg @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/lab/dashboard_templates/lovart/assets/styles.css b/scripts/lab/dashboard_templates/lovart/assets/styles.css new file mode 100644 index 00000000..4788a321 --- /dev/null +++ b/scripts/lab/dashboard_templates/lovart/assets/styles.css @@ -0,0 +1,1069 @@ +:root { + --bg-dark: #0b1020; + --bg-deeper: #11182a; + --bg-card: rgba(21, 27, 40, 0.88); + --bg-card-strong: rgba(19, 25, 38, 0.96); + --bg-card-hover: rgba(34, 42, 60, 0.94); + --accent-blue: #4d8dff; + --accent-purple: #7c5cff; + --accent-green: #1ed49d; + --accent-red: #ff6b7a; + --accent-yellow: #ffb547; + --text-primary: #edf2ff; + --text-secondary: #95a2c2; + --text-dim: #7080a3; + --border-color: rgba(148, 163, 184, 0.18); + --border-strong: rgba(148, 163, 184, 0.3); + --shadow-lg: 0 24px 80px rgba(2, 6, 23, 0.45); + --shadow-md: 0 16px 40px rgba(2, 6, 23, 0.34); + --radius-xl: 24px; + --radius-lg: 18px; + --radius-md: 14px; + --radius-sm: 10px; +} + +* { + box-sizing: border-box; +} + +html, +body { + margin: 0; + min-height: 100%; +} + +body { + font-family: "IBM Plex Sans", "Avenir Next", "Segoe UI", system-ui, sans-serif; + color: var(--text-primary); + background: + radial-gradient(circle at 12% 18%, rgba(77, 141, 255, 0.16), transparent 26%), + radial-gradient(circle at 86% 22%, rgba(124, 92, 255, 0.16), transparent 24%), + linear-gradient(180deg, #08111f 0%, #0a1323 46%, #0d1728 100%); +} + +a { + color: inherit; + text-decoration: none; +} + +button, +input, +select { + font: inherit; +} + +.grid-bg { + position: fixed; + inset: 0; + background-image: + linear-gradient(rgba(148, 163, 184, 0.08) 1px, transparent 1px), + linear-gradient(90deg, rgba(148, 163, 184, 0.08) 1px, transparent 1px); + background-size: 38px 38px; + mask-image: radial-gradient(circle at center, black 28%, transparent 82%); + pointer-events: none; + z-index: -1; +} + +.dashboard-shell { + max-width: 1760px; + margin: 0 auto; + padding: 20px 20px 28px; +} + +.icon { + width: 16px; + height: 16px; + flex: 0 0 auto; +} + +.icon-xl { + width: 28px; + height: 28px; +} + +.hero { + position: sticky; + top: 0; + z-index: 50; + padding: 24px 24px 22px; + border: 1px solid var(--border-color); + border-radius: 28px; + background: + linear-gradient(135deg, rgba(11, 16, 32, 0.96) 0%, rgba(17, 24, 42, 0.94) 48%, rgba(27, 18, 52, 0.96) 100%); + backdrop-filter: blur(18px); + box-shadow: var(--shadow-lg); + overflow: hidden; +} + +.hero-glow { + position: absolute; + width: 420px; + height: 420px; + border-radius: 999px; + filter: blur(90px); + pointer-events: none; +} + +.hero-glow-left { + top: -180px; + left: -120px; + background: rgba(77, 141, 255, 0.22); +} + +.hero-glow-right { + top: -220px; + right: -100px; + background: rgba(124, 92, 255, 0.2); +} + +.hero-top { + position: relative; + display: grid; + grid-template-columns: minmax(0, 1.25fr) minmax(360px, 0.95fr); + gap: 24px; + align-items: start; +} + +.hero-eyebrow { + display: inline-flex; + align-items: center; + gap: 8px; + color: var(--text-secondary); + text-transform: uppercase; + letter-spacing: 0.14em; + font-size: 0.78rem; +} + +.hero-copy h1 { + margin: 14px 0 12px; + font-family: "IBM Plex Serif", Georgia, serif; + font-size: clamp(2rem, 4vw, 3.5rem); + line-height: 1.02; +} + +.hero-copy p { + margin: 0; + max-width: 72ch; + color: var(--text-secondary); + line-height: 1.6; +} + +.hero-actions { + position: relative; + display: grid; + gap: 14px; +} + +.button { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 8px; + min-height: 42px; + padding: 10px 14px; + border-radius: 12px; + border: 1px solid var(--border-color); + cursor: pointer; + transition: transform 0.18s ease, border-color 0.18s ease, background 0.18s ease, box-shadow 0.18s ease; +} + +.button:hover { + transform: translateY(-1px); + border-color: var(--border-strong); +} + +.button-primary { + border-color: rgba(77, 141, 255, 0.45); + background: linear-gradient(135deg, rgba(77, 141, 255, 0.94), rgba(90, 121, 255, 0.94)); + color: #fff; + box-shadow: 0 0 24px rgba(77, 141, 255, 0.28); +} + +.button-secondary { + background: rgba(255, 255, 255, 0.05); + color: var(--text-primary); +} + +.toggle-card, +.sync-state, +.hero-links { + border: 1px solid var(--border-color); + border-radius: 14px; + background: rgba(255, 255, 255, 0.04); +} + +.toggle-card { + display: flex; + justify-content: space-between; + align-items: center; + gap: 16px; + padding: 10px 14px; +} + +.toggle-label { + color: var(--text-secondary); + font-size: 0.92rem; +} + +.toggle-switch { + position: relative; + display: inline-flex; + width: 44px; + height: 24px; +} + +.toggle-switch input { + position: absolute; + opacity: 0; + inset: 0; +} + +.toggle-slider { + position: absolute; + inset: 0; + border-radius: 999px; + background: rgba(148, 163, 184, 0.18); + border: 1px solid rgba(148, 163, 184, 0.28); + transition: background 0.18s ease, border-color 0.18s ease; +} + +.toggle-slider::before { + content: ""; + position: absolute; + top: 2px; + left: 2px; + width: 18px; + height: 18px; + border-radius: 999px; + background: #fff; + transition: transform 0.18s ease; +} + +.toggle-switch input:checked + .toggle-slider { + background: rgba(77, 141, 255, 0.95); + border-color: rgba(77, 141, 255, 0.95); +} + +.toggle-switch input:checked + .toggle-slider::before { + transform: translateX(20px); +} + +.sync-state { + display: flex; + align-items: center; + gap: 12px; + padding: 12px 14px; +} + +.sync-state strong { + display: block; + font-size: 0.92rem; +} + +.sync-state span { + display: block; + margin-top: 3px; + color: var(--text-secondary); + font-size: 0.8rem; +} + +.icon-sync { + color: var(--accent-blue); + width: 14px; + height: 14px; + filter: drop-shadow(0 0 10px rgba(77, 141, 255, 0.75)); +} + +.hero-links { + display: flex; + flex-wrap: wrap; + gap: 10px; + padding: 12px; +} + +.metrics-row { + position: relative; + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 16px; + margin-top: 22px; +} + +.metric-card { + position: relative; + padding: 16px 18px; + border-radius: 18px; + border: 1px solid var(--border-color); + background: rgba(8, 13, 24, 0.62); + box-shadow: var(--shadow-md); + overflow: hidden; +} + +.metric-card::before { + content: ""; + position: absolute; + inset: 0 auto 0 0; + width: 4px; + background: var(--metric-color, var(--accent-purple)); +} + +.metric-label { + display: flex; + align-items: center; + gap: 8px; + color: var(--text-secondary); + text-transform: uppercase; + letter-spacing: 0.08em; + font-size: 0.76rem; +} + +.metric-value { + margin-top: 14px; + font-size: clamp(1.8rem, 3vw, 2.4rem); + font-weight: 700; +} + +.metric-note { + margin-top: 8px; + color: var(--text-dim); + font-size: 0.82rem; +} + +.main-container { + display: grid; + grid-template-columns: 320px minmax(0, 1fr); + gap: 20px; + margin-top: 20px; + min-height: calc(100vh - 260px); +} + +.sidebar { + display: flex; + flex-direction: column; + gap: 16px; +} + +.sidebar-section, +.panel, +.workspace-empty { + border: 1px solid var(--border-color); + border-radius: 18px; + background: var(--bg-card); + box-shadow: var(--shadow-md); +} + +.sidebar-section { + padding: 16px; +} + +.sidebar-section-fill { + flex: 1 1 auto; + min-height: 260px; +} + +.section-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + margin-bottom: 14px; + color: var(--text-secondary); + text-transform: uppercase; + letter-spacing: 0.08em; + font-size: 0.76rem; +} + +.section-header span:first-child { + display: inline-flex; + align-items: center; + gap: 8px; +} + +.section-badge, +.tag, +.status-pill, +.section-chip { + display: inline-flex; + align-items: center; + gap: 8px; + min-height: 26px; + padding: 4px 10px; + border-radius: 999px; + border: 1px solid var(--border-color); + font-size: 0.76rem; + white-space: nowrap; +} + +.field { + display: grid; + gap: 8px; + color: var(--text-secondary); + font-size: 0.84rem; +} + +.filter-group { + display: grid; + gap: 12px; +} + +.search-box, +.filter-select { + display: flex; + align-items: center; + gap: 8px; + min-height: 42px; + padding: 0 12px; + border-radius: 12px; + border: 1px solid var(--border-color); + background: rgba(255, 255, 255, 0.04); + color: var(--text-primary); +} + +.search-box input { + width: 100%; + border: 0; + outline: none; + background: transparent; + color: inherit; +} + +.filter-select { + appearance: none; +} + +.system-stats, +.failure-list, +.run-list { + display: grid; + gap: 12px; +} + +.system-card, +.failure-card, +.run-card, +.plan-card, +.detail-stat, +.artifact-group, +.viewer-card, +.json-card { + border: 1px solid var(--border-color); + border-radius: 14px; + background: rgba(255, 255, 255, 0.03); +} + +.system-card, +.failure-card, +.run-card, +.plan-card, +.detail-stat, +.json-card { + padding: 12px 14px; +} + +.system-title, +.failure-title, +.run-title { + font-weight: 600; +} + +.system-meta, +.failure-reason, +.run-meta, +.muted, +.detail-subtitle, +.plan-copy, +.source-links a, +.timeline-detail, +.timeline-time, +.viewer-meta, +.footer-note, +.empty-copy { + color: var(--text-secondary); +} + +.meter { + height: 8px; + margin-top: 12px; + border-radius: 999px; + background: rgba(255, 255, 255, 0.08); + overflow: hidden; +} + +.meter > span { + display: block; + height: 100%; + width: var(--fill, 0%); + background: linear-gradient(90deg, var(--accent-blue), var(--accent-purple)); +} + +.run-list { + max-height: calc(100vh - 470px); + overflow: auto; + padding-right: 4px; +} + +.run-card { + cursor: pointer; + transition: transform 0.18s ease, border-color 0.18s ease, background 0.18s ease; +} + +.run-card:hover, +.run-card.is-active { + transform: translateY(-1px); + border-color: rgba(77, 141, 255, 0.42); + background: rgba(77, 141, 255, 0.08); +} + +.run-topline, +.detail-topline, +.viewer-toolbar, +.panel-header, +.detail-actions, +.tag-row, +.timeline-head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; +} + +.tag-row { + flex-wrap: wrap; +} + +.status-pill { + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.07em; +} + +.status-pill::before { + content: ""; + width: 8px; + height: 8px; + border-radius: 999px; + background: currentColor; + box-shadow: 0 0 14px currentColor; +} + +.status-verified-real { + color: var(--accent-green); + border-color: rgba(30, 212, 157, 0.28); + background: rgba(30, 212, 157, 0.12); +} + +.status-verified-synthetic { + color: #8fd8ff; + border-color: rgba(143, 216, 255, 0.28); + background: rgba(143, 216, 255, 0.12); +} + +.status-blocked-artifact, +.status-blocked-destructive { + color: var(--accent-red); + border-color: rgba(255, 107, 122, 0.32); + background: rgba(255, 107, 122, 0.12); +} + +.status-triage-manual, +.status-suspected { + color: var(--accent-yellow); + border-color: rgba(255, 181, 71, 0.28); + background: rgba(255, 181, 71, 0.12); +} + +.status-default { + color: var(--accent-blue); + border-color: rgba(77, 141, 255, 0.28); + background: rgba(77, 141, 255, 0.12); +} + +.workspace { + min-width: 0; +} + +.workspace-empty { + display: grid; + place-items: center; + gap: 10px; + min-height: 320px; + padding: 28px; + text-align: center; +} + +.workspace-empty h2 { + margin: 0; + font-family: "IBM Plex Serif", Georgia, serif; + font-size: 1.8rem; +} + +.detail-hero { + padding: 22px 22px 20px; + margin-bottom: 18px; + border: 1px solid var(--border-color); + border-radius: 22px; + background: + linear-gradient(135deg, rgba(17, 24, 42, 0.98) 0%, rgba(22, 17, 44, 0.96) 100%); + box-shadow: var(--shadow-lg); +} + +.detail-title { + margin: 12px 0 8px; + font-family: "IBM Plex Serif", Georgia, serif; + font-size: clamp(1.7rem, 3vw, 2.6rem); + line-height: 1.08; +} + +.detail-actions { + flex-wrap: wrap; + margin-top: 18px; +} + +.detail-stat-grid, +.plan-grid, +.raw-json-grid { + display: grid; + gap: 14px; +} + +.detail-stat-grid { + grid-template-columns: repeat(4, minmax(0, 1fr)); + margin-top: 18px; +} + +.detail-stat strong, +.plan-label { + display: block; + color: var(--text-secondary); + text-transform: uppercase; + letter-spacing: 0.08em; + font-size: 0.74rem; +} + +.detail-stat span { + display: block; + margin-top: 10px; + font-size: 1.2rem; + font-weight: 700; +} + +.panel { + overflow: hidden; + margin-bottom: 16px; +} + +.panel-header { + width: 100%; + padding: 16px 18px; + background: rgba(255, 255, 255, 0.02); + border: 0; + color: inherit; + cursor: pointer; +} + +.panel-title { + display: inline-flex; + align-items: center; + gap: 10px; + font-weight: 700; +} + +.panel-meta { + display: inline-flex; + align-items: center; + gap: 12px; + color: var(--text-secondary); +} + +.panel-chevron { + transition: transform 0.2s ease; +} + +.panel.is-collapsed .panel-chevron { + transform: rotate(-90deg); +} + +.panel-content { + display: grid; + grid-template-rows: 1fr; + transition: grid-template-rows 0.24s ease, opacity 0.24s ease; +} + +.panel-content-inner { + min-height: 0; + overflow: hidden; + padding: 0 18px 18px; +} + +.panel.is-collapsed .panel-content { + grid-template-rows: 0fr; + opacity: 0; +} + +.timeline { + display: grid; + gap: 14px; +} + +.timeline-item { + position: relative; + padding-left: 24px; + padding-bottom: 12px; + border-left: 1px solid rgba(148, 163, 184, 0.18); +} + +.timeline-item:last-child { + padding-bottom: 0; +} + +.timeline-dot { + position: absolute; + left: -7px; + top: 4px; + width: 12px; + height: 12px; + border-radius: 999px; + border: 2px solid currentColor; + background: var(--bg-dark); +} + +.timeline-success { + color: var(--accent-green); +} + +.timeline-blocked, +.timeline-failed { + color: var(--accent-red); +} + +.timeline-pending { + color: var(--accent-blue); +} + +.timeline-neutral { + color: var(--text-dim); +} + +.timeline-head strong { + font-size: 0.96rem; +} + +.timeline-time { + font-size: 0.76rem; +} + +.timeline-detail { + margin-top: 6px; + line-height: 1.55; + font-size: 0.9rem; +} + +.progress-bar { + height: 10px; + border-radius: 999px; + background: rgba(255, 255, 255, 0.07); + overflow: hidden; + margin-bottom: 14px; +} + +.progress-segment { + height: 100%; + float: left; +} + +.progress-completed { + background: linear-gradient(90deg, var(--accent-green), #54f0bf); +} + +.progress-blocked { + background: linear-gradient(90deg, var(--accent-red), #ff95a0); +} + +.progress-failed { + background: linear-gradient(90deg, #ff8a47, var(--accent-red)); +} + +.progress-skipped { + background: rgba(148, 163, 184, 0.35); +} + +.progress-planned { + background: linear-gradient(90deg, var(--accent-blue), #8fbaff); +} + +.progress-other { + background: rgba(148, 163, 184, 0.2); +} + +.progress-legend { + display: flex; + flex-wrap: wrap; + gap: 10px; + margin-bottom: 16px; +} + +.swatch { + width: 10px; + height: 10px; + border-radius: 999px; +} + +.plan-grid, +.raw-json-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); +} + +.plan-copy { + margin-top: 8px; + line-height: 1.55; + font-size: 0.92rem; +} + +.callout { + padding: 14px 16px; + border-radius: 14px; + border: 1px solid rgba(255, 107, 122, 0.28); + background: rgba(255, 107, 122, 0.12); + margin-bottom: 14px; +} + +.artifact-groups { + display: grid; + gap: 16px; +} + +.artifact-group h3 { + margin: 0 0 12px; + font-size: 0.86rem; + color: var(--text-secondary); + text-transform: uppercase; + letter-spacing: 0.08em; +} + +.artifact-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); + gap: 12px; +} + +.artifact-button { + display: grid; + gap: 8px; + width: 100%; + padding: 12px; + text-align: left; + border: 1px solid var(--border-color); + border-radius: 14px; + background: rgba(255, 255, 255, 0.04); + color: inherit; + cursor: pointer; + transition: transform 0.18s ease, border-color 0.18s ease, background 0.18s ease; +} + +.artifact-button:hover, +.artifact-button.is-active { + transform: translateY(-1px); + border-color: rgba(77, 141, 255, 0.42); + background: rgba(77, 141, 255, 0.08); +} + +.artifact-kind { + color: var(--text-dim); + font-size: 0.78rem; +} + +.gallery { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); + gap: 12px; + margin-top: 12px; +} + +.gallery-card { + display: block; + width: 100%; + padding: 0; + overflow: hidden; +} + +.gallery-card img { + display: block; + width: 100%; + aspect-ratio: 4 / 3; + object-fit: cover; +} + +.gallery-card span { + display: block; + padding: 10px 12px 12px; +} + +.viewer-card { + padding: 14px; +} + +.viewer-toolbar { + flex-wrap: wrap; + margin-bottom: 14px; +} + +.viewer-label { + font-size: 1rem; + font-weight: 600; +} + +.viewer-meta { + margin-top: 4px; + font-size: 0.82rem; + word-break: break-all; +} + +.viewer-frame { + min-height: 320px; + border: 1px solid rgba(148, 163, 184, 0.16); + border-radius: 14px; + background: rgba(5, 9, 18, 0.92); + overflow: hidden; +} + +.viewer-frame pre { + margin: 0; + max-height: 560px; + padding: 16px; + overflow: auto; + color: #dbe7ff; + font-family: "IBM Plex Mono", "SFMono-Regular", Menlo, monospace; + font-size: 0.84rem; + line-height: 1.6; + white-space: pre-wrap; +} + +.viewer-frame img { + display: block; + max-width: 100%; + height: auto; +} + +.viewer-frame iframe { + display: block; + width: 100%; + min-height: 580px; + border: 0; + background: #fff; +} + +.source-links { + display: grid; + gap: 10px; + margin-top: 14px; +} + +.source-links a { + text-decoration: underline; + text-underline-offset: 3px; +} + +.json-card pre { + margin: 0; + max-height: 420px; + overflow: auto; + color: #dbe7ff; + font-family: "IBM Plex Mono", "SFMono-Regular", Menlo, monospace; + font-size: 0.82rem; + line-height: 1.55; + white-space: pre-wrap; +} + +.dashboard-footer { + display: flex; + justify-content: space-between; + gap: 16px; + align-items: center; + margin-top: 16px; + padding: 14px 6px 0; +} + +.footer-note { + display: inline-flex; + align-items: center; + gap: 8px; + font-size: 0.84rem; +} + +.footer-links { + display: flex; + flex-wrap: wrap; + gap: 12px; + font-size: 0.84rem; + color: var(--text-secondary); +} + +.footer-links a { + text-decoration: underline; + text-underline-offset: 3px; +} + +.empty-state { + padding: 24px; + text-align: center; + color: var(--text-secondary); +} + +.empty-copy { + line-height: 1.55; +} + +@media (max-width: 1320px) { + .hero-top, + .main-container, + .detail-stat-grid, + .plan-grid, + .raw-json-grid { + grid-template-columns: 1fr; + } + + .sidebar { + order: 2; + } + + .workspace { + order: 1; + } +} + +@media (max-width: 960px) { + .dashboard-shell { + padding: 14px 14px 22px; + } + + .hero { + position: static; + padding: 18px; + } + + .metrics-row { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .hero-links, + .detail-actions, + .tag-row, + .panel-meta, + .viewer-toolbar, + .dashboard-footer { + flex-direction: column; + align-items: stretch; + } +} + +@media (max-width: 640px) { + .metrics-row, + .artifact-grid, + .gallery { + grid-template-columns: 1fr; + } + + .hero-copy h1 { + font-size: 1.85rem; + } +} diff --git a/scripts/lab/dashboard_templates/lovart/index.html b/scripts/lab/dashboard_templates/lovart/index.html new file mode 100644 index 00000000..a7d919a3 --- /dev/null +++ b/scripts/lab/dashboard_templates/lovart/index.html @@ -0,0 +1,168 @@ + + + + + + Authorized Lab Dashboard + + + + +
+
+ + + +
+
+
+ + Authorized Lab Dashboard +
+

本地攻防实证工作台

+

+ Lovart 设计外壳已本地化并接入真实 run bundle 数据。页面只面向授权实验资产, + 聚合 advisory、timeline、evidence、logs、sources、raw JSON 与失败原因。 +

+
+ +
+ + +
+ +
+ Booting + Loading generated JSON +
+
+ +
+
+ +
+
+ +
+ + +
+
+ +

Select a run

+

Pick a run from the left queue to inspect timeline, evidence, logs, sources and raw JSON.

+
+
+
+ + +
+ + + + diff --git a/scripts/lab/dashboard_templates/lovart/vendor/464011bb-fbbc-4bd4-98f8-90897dd43612.html b/scripts/lab/dashboard_templates/lovart/vendor/464011bb-fbbc-4bd4-98f8-90897dd43612.html new file mode 100644 index 00000000..d040f251 --- /dev/null +++ b/scripts/lab/dashboard_templates/lovart/vendor/464011bb-fbbc-4bd4-98f8-90897dd43612.html @@ -0,0 +1,1097 @@ + + + + + + Authorized Lab Dashboard + + + + + +
+ + +
+
+
+

Authorized Lab Dashboard

+

Real-time Empirical Security Analysis & Vulnerability Reproduction Environment

+
+
+
+ Auto-Refresh + +
+ +
+
+
+
+ Total Runs + 1,248 + 12% this week +
+
+ Reproduction Success + 856 + 98.2% Accuracy +
+
+ Blocked / Failed + 42 + Env Issues +
+
+ Active Analysis + 18 + 4 Queued +
+
+
+ +
+ + + + +
+ +
+
+
+

+ RUN-2023-1045 + Analysis In Progress +

+
+
+ CVE-2023-22515 + Severity: Critical (9.8) + Agent: node-alpha-01 +
+
+ +
+ + +
+
+ Execution Timeline + +
+
+
+
+
+
+ Environment Provisioning + 10:42:05 +
+
Docker container `atlassian/confluence-server:8.0.0` started successfully on port 8090.
+
+
+
+
+ Network Reachability Check + 10:42:35 +
+
Target responding to HTTP GET / with 200 OK. Latency 12ms.
+
+
+
+
+ Vulnerability Identification + 10:42:38 +
+
Detected version 8.0.0 match. Initial check for /server-info.action accessible.
+
+
+
+
+ Exploit Execution (Stage 1) + Running... +
+
Sending modified XWork action request to bypass authentication middleware...
+
+
+
+
+ Admin Account Creation + Pending +
+
+
+
+
+ + +
+
+ Attack Plan & Reasoning + +
+
+
+
+
Strategy
+
+ The attack leverages an improperly handled parameter in the XWork action configuration. By manipulating the bootstrapStatusProvider.applicationConfig.setupComplete parameter, we can trick the application into thinking setup is incomplete. +
+
+
+
Success Criteria
+
+ 1. HTTP 200 Response on payload delivery.
+ 2. Access to /setup/setupadministrator-start.action without auth.
+ 3. Successful creation of user 'unauthorized_admin'. +
+
+
+
Payload Structure
+
+ GET /server-info.action?bootstrapStatusProvider.applicationConfig.setupComplete=false +
+
+
+
+
+ + +
+
+ Live Log Viewer + +
+
+
+
+ 2023-10-27 10:42:05 + [INFO] + Initializing experiment controller... +
+
+ 2023-10-27 10:42:12 + [INFO] + Pulling image atlassian/confluence-server:8.0.0 +
+
+ 2023-10-27 10:42:35 + [INFO] + Container started. ID: a1b2c3d4e5f6 +
+
+ 2023-10-27 10:42:40 + [WARN] + Response delay detected (1500ms). Retrying health check. +
+
+ 2023-10-27 10:42:42 + [INFO] + Target is healthy. Starting exploit chain. +
+
+ 2023-10-27 10:42:45 + [INFO] + Sending Stage 1 Payload: GET /server-info.action... +
+
+
+
+ + +
+
+ Evidence Explorer + +
+
+
+
+ + full_report.pdf +
+
+ + screenshot_01.png +
+
+ + http_dump.har +
+
+ + docker-compose.yml +
+
+ + db_snapshot.sql +
+
+
+
+ + + + + +
+
+ Sources & Fix Topics + +
+
+
+ Broken Access Control + Privilege Escalation + Java + Struts2 +
+
+

Official Advisory: Atlassian Security Advisory 2023-10-04

+

NVD Entry: CVE-2023-22515

+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/scripts/lab/dashboard_templates/lovart/vendor/source-manifest.json b/scripts/lab/dashboard_templates/lovart/vendor/source-manifest.json new file mode 100644 index 00000000..a46e5036 --- /dev/null +++ b/scripts/lab/dashboard_templates/lovart/vendor/source-manifest.json @@ -0,0 +1,18 @@ +{ + "template_id": "lovart-authorized-lab-dashboard", + "source_url": "https://assets-persist.lovart.ai/agent_images/464011bb-fbbc-4bd4-98f8-90897dd43612.html", + "downloaded_at": "2026-03-17T07:56:29Z", + "original_filename": "464011bb-fbbc-4bd4-98f8-90897dd43612.html", + "vendor_source_path": "scripts/lab/dashboard_templates/lovart/vendor/464011bb-fbbc-4bd4-98f8-90897dd43612.html", + "runtime_template": { + "index": "scripts/lab/dashboard_templates/lovart/index.html", + "styles": "scripts/lab/dashboard_templates/lovart/assets/styles.css", + "app": "scripts/lab/dashboard_templates/lovart/assets/app.js", + "icons": "scripts/lab/dashboard_templates/lovart/assets/icons.svg" + }, + "notes": [ + "The remote Lovart HTML is tracked for provenance only and is not used at runtime.", + "Runtime assets are localized into repository-managed templates and generated output.", + "External fonts and icon CDNs are intentionally removed from the generated dashboard." + ] +}