diff --git a/state-hub/dashboard/src/data/repos.json.py b/state-hub/dashboard/src/data/repos.json.py index 963e475..613bc7a 100644 --- a/state-hub/dashboard/src/data/repos.json.py +++ b/state-hub/dashboard/src/data/repos.json.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Observable data loader: fetches /repos/ from the API.""" +"""Observable data loader: fetches /repos/ enriched with SBOM snapshot stats.""" import json import os import urllib.request @@ -7,9 +7,36 @@ import urllib.error API_BASE = os.environ.get("API_BASE", "http://127.0.0.1:8000").rstrip("/") -try: - with urllib.request.urlopen(f"{API_BASE}/repos/", timeout=10) as resp: - data = json.loads(resp.read()) - print(json.dumps(data)) -except urllib.error.URLError as e: - print(json.dumps({"error": str(e), "repos": []})) + +def fetch(url): + try: + with urllib.request.urlopen(url, timeout=10) as resp: + return json.loads(resp.read()) + except urllib.error.URLError as e: + print(f"Warning: could not fetch {url}: {e}", flush=True) + return [] + + +repos = fetch(f"{API_BASE}/repos/") +snapshots = fetch(f"{API_BASE}/sbom/snapshots/") + +# Build map: repo_id → {count, latest_at, latest_entry_count} +snap_stats: dict = {} +for s in snapshots: + rid = s["repo_id"] + if rid not in snap_stats: + snap_stats[rid] = {"count": 0, "latest_at": None, "latest_entry_count": 0} + snap_stats[rid]["count"] += 1 + if snap_stats[rid]["latest_at"] is None or s["snapshot_at"] > snap_stats[rid]["latest_at"]: + snap_stats[rid]["latest_at"] = s["snapshot_at"] + snap_stats[rid]["latest_entry_count"] = s["entry_count"] + +# Enrich repos — fall back to snapshot data if denormalized field is missing +for r in repos: + stats = snap_stats.get(str(r["id"]), {}) + if not r.get("last_sbom_at") and stats.get("latest_at"): + r["last_sbom_at"] = stats["latest_at"] + r["sbom_snapshot_count"] = stats.get("count", 0) + r["sbom_entry_count"] = stats.get("latest_entry_count", 0) + +print(json.dumps(repos)) diff --git a/state-hub/dashboard/src/repo-sync.md b/state-hub/dashboard/src/repo-sync.md index 84c1b5a..08d6a72 100644 --- a/state-hub/dashboard/src/repo-sync.md +++ b/state-hub/dashboard/src/repo-sync.md @@ -43,18 +43,28 @@ function syncColor(ts) { const activeRepos = repos.filter(r => r.status === "active"); const staleCount = activeRepos.filter(r => !r.last_state_synced_at || ageMs(r.last_state_synced_at) > 86400000).length; const freshCount = activeRepos.filter(r => r.last_state_synced_at && ageMs(r.last_state_synced_at) < 3600000).length; +const sbomCount = activeRepos.filter(r => r.last_sbom_at).length; +const noSbomCount = activeRepos.length - sbomCount; ``` ```js display(html` -
+
${freshCount}
-
synced < 1h
+
state synced < 1h
${staleCount}
-
stale / never
+
state stale / never
+
+
+
${sbomCount}
+
SBOM ingested
+
+
+
${noSbomCount}
+
no SBOM yet
${activeRepos.length}
@@ -72,6 +82,7 @@ const table = html`Domain + @@ -82,7 +93,8 @@ const table = html`
Last Synced Last SBOMEntries Status
${r.slug} - + +
${r.domain_slug} ${fmtAge(r.last_state_synced_at)}${fmtAge(r.last_sbom_at)}${fmtAge(r.last_sbom_at)}${r.sbom_entry_count > 0 ? r.sbom_entry_count.toLocaleString() : "—"} ${r.status}