import { useEffect, useMemo, useRef, useState } from "react"; import { Link } from "react-router-dom"; import type { OverviewStats, TrendDay } from "@/shared/types"; import { SEV_ORDER, severityLabel } from "../shared/lib/format"; const SEV = { high: "#F24F4F", medium: "#FF733C", low: "#F7C530", critical: "#C22828", } as const; type Pt = TrendDay; function stackedAreas(data: Pt[], w = 560, h = 180) { const keys = ["critical", "high", "medium", "low"] as const; const max = Math.max(...data.map((d) => (d.critical??0)+d.high+d.medium+d.low ), 1); const x = (i: number) => (data.length <= 1 ? 0 : (i / (data.length - 1)) * w); const y = (v: number) => h - (v / max) * (h - 16); const acc = data.map(() => 0); return keys.map((k) => { const top = data.map((d, i) => { acc[i] += d[k]; return [x(i), y(acc[i])] as const; }); const base = data.map((d, i) => [x(i), y(acc[i] - d[k])] as const).reverse(); const line = top .map((p, i) => `${i ? "L" : "M"}${p[0].toFixed(1)},${p[1].toFixed(1)}`) .join(""); const area = `${line}${base.map((p) => `L${p[0].toFixed(1)},${p[1].toFixed(1)}`).join("")}Z`; return { key: k, area, line }; }); } function usePrefersReducedMotion(): boolean { const [reduced, setReduced] = useState(false); useEffect(() => { const mq = window.matchMedia("(prefers-reduced-motion: reduce)"); setReduced(mq.matches); const fn = () => setReduced(mq.matches); mq.addEventListener("change", fn); return () => mq.removeEventListener("change", fn); }, []); return reduced; } function formatElapsed(sec: number): string { if (sec < 60) return `${sec}s`; const m = Math.floor(sec / 60); if (m < 60) return `${m}m`; return `${Math.floor(m / 60)}h ${m % 60}m`; } const PANEL = "rounded-lg border border-line bg-surface-raised p-4"; const EYEBROW = "font-mono text-[11px] uppercase tracking-wider text-ink-tertiary"; /** 首页第二页(浅色):趋势图 / LIVE / 新增漏洞事件卡 / TOP CWE */ export function PlatformPulse({ stats }: { stats: OverviewStats | undefined }) { const reduced = usePrefersReducedMotion(); const trend = stats?.trend ?? []; const layers = useMemo(() => (trend.length ? stackedAreas(trend) : []), [trend]); const pathRefs = useRef<(SVGPathElement | null)[]>([]); useEffect(() => { if (reduced) return; for (const el of pathRefs.current) { if (!el) continue; const len = el.getTotalLength(); el.style.strokeDasharray = `${len}`; el.style.strokeDashoffset = `${len}`; el.getBoundingClientRect(); el.style.transition = "stroke-dashoffset 900ms ease-out"; el.style.strokeDashoffset = "0"; } }, [layers, reduced]); const sevTotals = stats?.severity_counts ?? { critical: 0, high: 0, medium: 0, low: 0 }; const live = stats?.live; const scanningCount = live?.scanning.length ?? 0; const queuedCount = live?.queued_count ?? 0; const cweTop = stats?.cwe_top ?? []; const cweMax = Math.max(1, ...cweTop.map((c) => c.count)); const hasFindings = (stats?.finding_total ?? 0) > 0 || trend.some((d) => (d.critical??0)+d.high+d.medium+d.low > 0); const xTicks = useMemo(() => { if (trend.length < 2) return []; const idxs = [0, 7, 14, 21, 29].filter((i) => i < trend.length); return idxs.map((i) => ({ i, label: trend[i].date.slice(5), x: (i / (trend.length - 1)) * 560, })); }, [trend]); return (

Platform Pulse · Live

{/* Findings 趋势 */}

Findings · last 30 days

{(stats?.finding_total ?? 0).toLocaleString()}

{!hasFindings && (
Awaiting first scans
)} {[0.25, 0.5, 0.75].map((g) => ( ))} {[...layers].reverse().map((layer) => ( ))} {layers.map((layer, idx) => ( { pathRefs.current[idx] = el; }} d={layer.line} fill="none" stroke={SEV[layer.key]} strokeWidth={1.5} /> ))} {xTicks.map((t) => ( = 28 ? "end" : "middle"} className="fill-ink-tertiary" style={{ fontSize: 10, fontFamily: "ui-monospace, monospace" }} > {t.label} ))}
{SEV_ORDER.map((s) => ( {severityLabel(s)} {sevTotals[s]} ))}
{/* LIVE NOW */}

Live now

{(scanningCount > 0 || queuedCount > 0) && ( )} 0 ? "bg-success" : "bg-ink-tertiary" }`} /> {scanningCount > 0 || queuedCount > 0 ? ( <> {scanningCount} scanning · {queuedCount} queued ) : ( Idle · queue empty )}

    {(live?.scanning ?? []).map((s) => (
  • {s.full_name} {formatElapsed(s.elapsed_sec)}
  • ))}
{/* TOP CWE */}

Top CWE

{cweTop.length === 0 ? (

No CWE data yet.

) : (
    {cweTop.map((c) => (
  • {c.cwe} {c.name ?? "—"}
    {c.count}
  • ))}
)}
); } function LiveDot() { return ( ); }