Pyramid web app for inspecting arborist Merkle trees over sharded SQLite: document roots, inclusion proofs, an interactive 3D Merkle lattice (six.js / WebGL with graceful fallback), audit-chain linkage, and a live SSE event stream. Generated artifacts (env/, vendored static/six/ bundles, demo databases) are gitignored and rebuilt via the Makefile (venv, six, init-db, serve).
244 lines
9.2 KiB
Python
244 lines
9.2 KiB
Python
"""Saved-dashboard metadata endpoints.
|
|
|
|
Phase 1: backed by SQLAlchemy. The three canned configs (Audit
|
|
Operations, Claim Graveyard, Merkle Root Proof) are upserted on first
|
|
access so the UI always has a starter catalog.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
|
|
from pyramid.httpexceptions import HTTPNotFound
|
|
from pyramid.response import Response
|
|
from pyramid.view import view_config
|
|
from sqlalchemy import create_engine
|
|
from sqlalchemy.orm import sessionmaker
|
|
|
|
from ..models import Base, Dashboard
|
|
|
|
|
|
_engine = None
|
|
_Session = None
|
|
|
|
|
|
def _session_factory(settings):
|
|
global _engine, _Session
|
|
if _engine is None:
|
|
_engine = create_engine(settings["sqlalchemy.url"], future=True)
|
|
Base.metadata.create_all(_engine)
|
|
_Session = sessionmaker(bind=_engine, expire_on_commit=False, future=True)
|
|
return _Session
|
|
|
|
|
|
# Canned starter catalog. Upserted on first access so the demo always
|
|
# has something to display even before fox saves their own layouts.
|
|
CANNED: dict[str, dict] = {
|
|
"default": {
|
|
"id": "default",
|
|
"title": "Audit Operations View",
|
|
"description": "Operator default — recent audit chain, live claim status, root explorer.",
|
|
"version": "dashboard-v1",
|
|
"layout": [
|
|
{"panel_id": "root", "x": 0, "y": 0, "w": 4, "h": 4, "dock": "left"},
|
|
{"panel_id": "audit", "x": 4, "y": 0, "w": 8, "h": 4, "dock": "main"},
|
|
{"panel_id": "claim", "x": 0, "y": 4, "w": 6, "h": 4},
|
|
{"panel_id": "run", "x": 6, "y": 4, "w": 6, "h": 4},
|
|
],
|
|
"widgets": [
|
|
{
|
|
"widget_id": "root",
|
|
"type": "root-explorer",
|
|
"title": "Root Explorer",
|
|
"data_source_id": "shards",
|
|
"query": {"select": ["root_hash", "leaf_count", "ingest_ts"], "from": "documents", "limit": 50},
|
|
"view": {"renderer": "table", "options": {}},
|
|
"source_roots": [],
|
|
},
|
|
{
|
|
"widget_id": "audit",
|
|
"type": "audit-timeline",
|
|
"title": "Audit Timeline",
|
|
"data_source_id": "shards",
|
|
"query": {"select": ["event_hash", "event_type", "ts"], "from": "audit_events", "limit": 200},
|
|
"view": {"renderer": "timeline", "options": {}},
|
|
"source_roots": [],
|
|
},
|
|
{
|
|
"widget_id": "claim",
|
|
"type": "claim-warrant",
|
|
"title": "Claim Warrants",
|
|
"data_source_id": "shards",
|
|
"query": {"select": ["claim_id", "warrant", "status"], "from": "claims", "limit": 100},
|
|
"view": {"renderer": "table", "options": {}},
|
|
"source_roots": [],
|
|
},
|
|
{
|
|
"widget_id": "run",
|
|
"type": "run-dag",
|
|
"title": "Latest Ingest Run",
|
|
"data_source_id": "shards",
|
|
"query": {"select": ["run_id", "stage", "status"], "from": "runs", "limit": 1},
|
|
"view": {"renderer": "dag", "options": {}},
|
|
"source_roots": [],
|
|
},
|
|
],
|
|
"data_sources": [
|
|
{"id": "shards", "kind": "arborist_shard", "locator": "default", "read_only": True}
|
|
],
|
|
"global_filters": [],
|
|
"interactions": [],
|
|
},
|
|
"graveyard": {
|
|
"id": "graveyard",
|
|
"title": "Claim Graveyard View",
|
|
"description": "Negative memory — failed/stale/quarantined claims plus their falsifiers (#000059 projection).",
|
|
"version": "dashboard-v1",
|
|
"layout": [{"panel_id": "gv", "x": 0, "y": 0, "w": 12, "h": 8, "dock": "main"}],
|
|
"widgets": [
|
|
{
|
|
"widget_id": "gv",
|
|
"type": "claim-graveyard",
|
|
"title": "Claim Graveyard",
|
|
"data_source_id": "shards",
|
|
"query": {"select": ["failed_claim_hash", "failure_class", "created_at"], "from": "graveyard", "limit": 500},
|
|
"view": {"renderer": "table", "options": {}},
|
|
"source_roots": [],
|
|
}
|
|
],
|
|
"data_sources": [{"id": "shards", "kind": "arborist_shard", "locator": "default", "read_only": True}],
|
|
},
|
|
"proof": {
|
|
"id": "proof",
|
|
"title": "Merkle Root Proof View",
|
|
"description": "Single-root deep dive — inclusion proofs, leaves, audit linkage, 3D lattice.",
|
|
"version": "dashboard-v1",
|
|
"layout": [
|
|
{"panel_id": "root", "x": 0, "y": 0, "w": 4, "h": 3},
|
|
{"panel_id": "lattice", "x": 4, "y": 0, "w": 8, "h": 6},
|
|
{"panel_id": "proof", "x": 0, "y": 3, "w": 4, "h": 3},
|
|
],
|
|
"widgets": [
|
|
{
|
|
"widget_id": "root",
|
|
"type": "root-explorer",
|
|
"title": "Root",
|
|
"data_source_id": "shards",
|
|
"query": {"select": ["*"], "from": "documents", "limit": 1},
|
|
"view": {"renderer": "card", "options": {}},
|
|
"source_roots": [],
|
|
},
|
|
{
|
|
"widget_id": "lattice",
|
|
"type": "merkle-lattice-3d",
|
|
"title": "3D Merkle Lattice",
|
|
"data_source_id": "shards",
|
|
"query": {"select": ["layers"], "from": "merkle_tree", "limit": 1},
|
|
"view": {"renderer": "three-d", "options": {"animate_proof_path": True}},
|
|
"source_roots": [],
|
|
},
|
|
{
|
|
"widget_id": "proof",
|
|
"type": "merkle-proof",
|
|
"title": "Inclusion Proof",
|
|
"data_source_id": "shards",
|
|
"query": {"select": ["proof_path"], "from": "proofs", "limit": 1},
|
|
"view": {"renderer": "card", "options": {}},
|
|
"source_roots": [],
|
|
},
|
|
],
|
|
"data_sources": [{"id": "shards", "kind": "arborist_shard", "locator": "default", "read_only": True}],
|
|
},
|
|
}
|
|
|
|
|
|
def _upsert_canned(session_factory):
|
|
"""Insert the canned catalog if it isn't already there."""
|
|
Session = session_factory
|
|
with Session() as s:
|
|
for cfg in CANNED.values():
|
|
existing = s.get(Dashboard, cfg["id"])
|
|
if existing is None:
|
|
s.add(Dashboard(
|
|
id=cfg["id"], title=cfg["title"], description=cfg.get("description"),
|
|
version=cfg.get("version", "dashboard-v1"), config_json=cfg,
|
|
))
|
|
s.commit()
|
|
|
|
|
|
def _json(body, status=200) -> Response:
|
|
return Response(json_body=body, status=status)
|
|
|
|
|
|
@view_config(route_name="api_dashboards", request_method="GET")
|
|
def list_dashboards(request):
|
|
Session = _session_factory(request.registry.settings)
|
|
_upsert_canned(Session)
|
|
with Session() as s:
|
|
rows = s.query(Dashboard).order_by(Dashboard.id).all()
|
|
return _json({"dashboards": [r.config_json for r in rows]})
|
|
|
|
|
|
@view_config(route_name="api_dashboards", request_method="POST")
|
|
def create_dashboard(request):
|
|
try:
|
|
body = request.json_body
|
|
except Exception:
|
|
return _json({"error": "invalid JSON"}, status=400)
|
|
if not body.get("id") or not body.get("title"):
|
|
return _json({"error": "id and title required"}, status=400)
|
|
Session = _session_factory(request.registry.settings)
|
|
with Session() as s:
|
|
if s.get(Dashboard, body["id"]):
|
|
return _json({"error": "exists", "id": body["id"]}, status=409)
|
|
d = Dashboard(
|
|
id=body["id"], title=body["title"], description=body.get("description"),
|
|
version=body.get("version", "dashboard-v1"), config_json=body,
|
|
)
|
|
s.add(d)
|
|
s.commit()
|
|
return _json({"created": body})
|
|
|
|
|
|
@view_config(route_name="api_dashboard", request_method="GET")
|
|
def get_dashboard(request):
|
|
dashboard_id = request.matchdict["dashboard_id"]
|
|
Session = _session_factory(request.registry.settings)
|
|
_upsert_canned(Session)
|
|
with Session() as s:
|
|
d = s.get(Dashboard, dashboard_id)
|
|
if d is None:
|
|
raise HTTPNotFound(json_body={"error": "unknown dashboard", "id": dashboard_id})
|
|
return _json(d.config_json)
|
|
|
|
|
|
@view_config(route_name="api_dashboard", request_method="PUT")
|
|
def update_dashboard(request):
|
|
dashboard_id = request.matchdict["dashboard_id"]
|
|
try:
|
|
body = request.json_body
|
|
except Exception:
|
|
return _json({"error": "invalid JSON"}, status=400)
|
|
Session = _session_factory(request.registry.settings)
|
|
with Session() as s:
|
|
d = s.get(Dashboard, dashboard_id)
|
|
if d is None:
|
|
raise HTTPNotFound(json_body={"error": "unknown dashboard", "id": dashboard_id})
|
|
d.title = body.get("title", d.title)
|
|
d.description = body.get("description", d.description)
|
|
d.config_json = body
|
|
s.commit()
|
|
return _json({"updated": dashboard_id, "config": body})
|
|
|
|
|
|
@view_config(route_name="api_dashboard", request_method="DELETE")
|
|
def delete_dashboard(request):
|
|
dashboard_id = request.matchdict["dashboard_id"]
|
|
Session = _session_factory(request.registry.settings)
|
|
with Session() as s:
|
|
d = s.get(Dashboard, dashboard_id)
|
|
if d is None:
|
|
raise HTTPNotFound(json_body={"error": "unknown dashboard", "id": dashboard_id})
|
|
s.delete(d)
|
|
s.commit()
|
|
return _json({"deleted": dashboard_id})
|