feat(topology): M2 - API-driven demo page with layout tables and dynamic rendering

- Add diagram_layouts and diagram_nodes tables
- Seed layout coordinates extracted from SVG
- Extend snapshot API to return layout data
- Rewrite demo page to fetch from API dynamically
- Support ?mission= and ?at= URL parameters
- Modal details populated from database component data
- Show/hide nodes based on component existence at snapshot time

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-07-09 21:06:13 +08:00
co-authored by Claude
parent b5156d1306
commit 772f0f78f6
6 changed files with 570 additions and 493 deletions
+45
View File
@@ -201,3 +201,48 @@ def fetch_snapshot(snapshot_at):
})
return result
def fetch_layout():
"""Fetch the default diagram layout with node positions."""
with read_cursor() as cur:
cur.execute("""
SELECT
dl.layout_key,
dl.name AS layout_name,
dl.canvas_width,
dl.canvas_height,
dn.node_key,
dn.component_id,
c.component_key,
dn.x, dn.y, dn.width, dn.height,
dn.style_key, dn.label_override, dn.z_index
FROM diagram_layouts dl
JOIN diagram_nodes dn ON dn.layout_id = dl.id
LEFT JOIN components c ON c.id = dn.component_id
WHERE dl.base_id = (SELECT id FROM bases WHERE code = 'guanghan')
AND dl.is_default = true
ORDER BY dn.z_index, dn.node_key
""")
nodes = []
layout_info = None
for row in cur.fetchall():
if layout_info is None:
layout_info = {
"key": row["layout_key"],
"name": row["layout_name"],
"canvasWidth": float(row["canvas_width"]),
"canvasHeight": float(row["canvas_height"]),
}
nodes.append({
"nodeKey": row["node_key"],
"componentKey": row["component_key"],
"x": float(row["x"]),
"y": float(row["y"]),
"width": float(row["width"]),
"height": float(row["height"]),
"styleKey": row["style_key"],
"labelOverride": row["label_override"],
"zIndex": row["z_index"],
})
return {"layout": layout_info, "nodes": nodes} if layout_info else None