diff --git a/k8s/deployment.yaml b/k8s/deployment.yaml index 58007d3..332ccc9 100644 --- a/k8s/deployment.yaml +++ b/k8s/deployment.yaml @@ -15,7 +15,7 @@ spec: spec: containers: - name: cloud-dashboard - image: naeel/cloud-dashboard:v9.11 + image: naeel/cloud-dashboard:v9.12 imagePullPolicy: Always ports: - containerPort: 8080 diff --git a/main.py b/main.py index 047533b..eb9e53a 100644 --- a/main.py +++ b/main.py @@ -1,10 +1,8 @@ from fastapi import FastAPI, Header, HTTPException, Request -from fastapi.responses import StreamingResponse from fastapi.staticfiles import StaticFiles import httpx from typing import Annotated import asyncio -import json import time app = FastAPI() @@ -193,21 +191,6 @@ async def get_graph( if cnt and realm_name: realm_to_platform[realm_name] = uid_d - # Build directed edges from dependencies/dependentInstances - # Arrow points TO the dependency (what you depend on) - edge_set: set[tuple[str, str]] = set() - for uid_d, inst_d in detail_map.items(): - if uid_d not in allowed_uids: - continue - for dep in inst_d.get("dependencies", []) or []: - src = dep.get("uid") or dep.get("instanceUid") or "" - if src and src in allowed_uids and src != uid_d: - edge_set.add((uid_d, src)) # uid_d depends on src, arrow uid_d→src - for dep_on in inst_d.get("dependentInstances", []) or []: - dst = dep_on.get("uid") or dep_on.get("instanceUid") or "" - if dst and dst in allowed_uids and dst != uid_d: - edge_set.add((dst, uid_d)) # dst depends on uid_d, arrow dst→uid_d - nodes = [] for uid_i in allowed_uids: i = uid_to_inst.get(uid_i, {}) @@ -241,22 +224,21 @@ async def get_graph( for _n in nodes: _l = _n.get("lane", "unknown") _lane_counts[_l] = _lane_counts.get(_l, 0) + 1 - print(f"[GRAPH] Lanes: {_lane_counts}, nodes: {len(nodes)}, edges: {len(edge_set)}", flush=True) + print(f"[GRAPH] Lanes: {_lane_counts}, nodes: {len(nodes)}", flush=True) for _n in nodes: if _n["lane"] in ("platform", "singleton"): print(f" {_n['lane'].upper()}: {_n['label']} | svc={_n['svc']}", flush=True) - edges = [{"source": s, "target": t} for s, t in sorted(edge_set)] - base_graph = {"nodes": nodes, "edges": edges} + base_graph = {"nodes": nodes, "edges": []} GRAPH_CACHE[cache_key] = (now, base_graph) # Return full graph fast if not root_uid: return { "nodes": base_graph["nodes"], - "edges": base_graph["edges"], + "edges": [], "total_nodes": len(base_graph["nodes"]), - "total_edges": len(base_graph["edges"]), + "total_edges": 0, "cached": bool(cached), } @@ -264,87 +246,16 @@ async def get_graph( if root_uid not in node_ids: return {"nodes": [], "edges": [], "total_nodes": 0, "total_edges": 0, "cached": bool(cached)} - adj: dict[str, set[str]] = {uid: set() for uid in node_ids} - for e in base_graph["edges"]: - s, t = e["source"], e["target"] - if s in adj and t in adj: - adj[s].add(t) - adj[t].add(s) - - frontier = {root_uid} - seen = {root_uid} - for _ in range(depth): - nxt = set() - for node in frontier: - nxt |= adj.get(node, set()) - nxt -= seen - if not nxt: - break - seen |= nxt - frontier = nxt - - sub_nodes = [n for n in base_graph["nodes"] if n["id"] in seen] - sub_edges = [e for e in base_graph["edges"] if e["source"] in seen and e["target"] in seen] + sub_nodes = [n for n in base_graph["nodes"] if n["id"] == root_uid] return { "nodes": sub_nodes, - "edges": sub_edges, + "edges": [], "total_nodes": len(sub_nodes), - "total_edges": len(sub_edges), + "total_edges": 0, "cached": bool(cached), } -@app.get("/api/instances/stream") -async def stream_instances( - request: Request, - x_deck_token: Annotated[str | None, Header()] = None, - x_deck_env: Annotated[str | None, Header()] = None, -): - if not x_deck_token: - raise HTTPException(status_code=401, detail="Token required") - _, deck_api = resolve_deck_api(x_deck_env) - - all_instances = await fetch_all_instances(x_deck_token, deck_api) - uid_to_inst = { - i.get("instanceUid"): i - for i in all_instances - if i.get("instanceUid") - } - - async def generate(): - sem = asyncio.Semaphore(20) - queue: asyncio.Queue = asyncio.Queue() - total = len(uid_to_inst) - - async def fetch_one(uid: str, client: httpx.AsyncClient): - async with sem: - try: - r = await client.get( - f"{deck_api}/index.cfm/instances/{uid}", - headers=auth_headers(x_deck_token), - ) - detail = None if r.status_code >= 400 else r.json().get("instance") - except Exception: - detail = None - await queue.put((uid, detail)) - - async with httpx.AsyncClient(timeout=25) as shared_client: - tasks = [ - asyncio.create_task(fetch_one(uid, shared_client)) - for uid in uid_to_inst - ] - for _ in range(total): - uid, detail = await queue.get() - row = dict(uid_to_inst.get(uid, {})) - if detail: - row["_detail"] = detail - yield json.dumps(row, ensure_ascii=False) + "\n" - - yield json.dumps({"_done": True, "total": total}, ensure_ascii=False) + "\n" - - return StreamingResponse(generate(), media_type="application/x-ndjson") - - @app.get("/api/instances/{uid}") async def get_instance( request: Request, diff --git a/static/index.html b/static/index.html index 8cb8e55..8f83616 100644 --- a/static/index.html +++ b/static/index.html @@ -81,12 +81,12 @@ .tab { padding: 7px 14px; font-size: 13px; cursor: pointer; border-bottom: 2px solid transparent; color: #8b949e; } .tab.active { color: #58a6ff; border-bottom-color: #58a6ff; } .panel { display: none; } - .panel-params { background: #0d1117; } - .panel-history { background: #0b0f1a; } - .panel-deps { background: #0f1107; } - body.light .panel-params { background: #f8fafc; } - body.light .panel-history { background: #f0f4ff; } - body.light .panel-deps { background: #f0fff4; } + .panel-params { background: #111827; border-top: 2px solid #1e3a5f; } + .panel-history { background: #0f1a0f; border-top: 2px solid #1a4a2e; } + .panel-deps { background: #1a1209; border-top: 2px solid #3d2a00; } + body.light .panel-params { background: #eff6ff; border-top: 2px solid #bfdbfe; } + body.light .panel-history { background: #f0fdf4; border-top: 2px solid #bbf7d0; } + body.light .panel-deps { background: #fffbeb; border-top: 2px solid #fde68a; } .panel.active { display: block; } .inst-graph { height: 420px; border: 1px solid #30363d; border-radius: 8px; background: #0b0f14; } .graph-hint { color: #8b949e; font-size: 12px; margin-bottom: 8px; } @@ -412,14 +412,6 @@ function buildRowLayout(data, containerWidth, forceShowAux){ return { positions: positions, laneById: laneById }; } -function edgeColorForRows(sourceRow, targetRow, edgeIndex){ - var palette = ["#e11d48", "#0ea5e9", "#22c55e", "#f59e0b", "#a855f7", "#14b8a6", "#f97316"]; - var s = Number.isFinite(sourceRow) ? sourceRow : 0; - var t = Number.isFinite(targetRow) ? targetRow : 0; - var idx = Math.abs((s * 11) + (t * 17) + edgeIndex) % palette.length; - return palette[idx]; -} - // Service-based background colors for graph nodes var SVC_COLORS = { "cloud director": "#1e3a5f", @@ -503,20 +495,6 @@ function graphElements(data, focusUid, laneById){ classes: (focusUid && n.id === focusUid) ? "focus" : "" }); }); - (data.edges || []).forEach(function(e, i){ - var sourceRow = Number.isFinite(laneById[e.source]) ? laneById[e.source] : 0; - var targetRow = Number.isFinite(laneById[e.target]) ? laneById[e.target] : 0; - elems.push({ - data: { - id: "e" + i + "-" + e.source + "-" + e.target, - source: e.source, - target: e.target, - sourceRow: sourceRow, - targetRow: targetRow, - color: edgeColorForRows(sourceRow, targetRow, i) - } - }); - }); return elems; } @@ -538,7 +516,7 @@ function drawGraph(containerId, data, focusUid, forceShowAux){ var visibleIds = new Set(visibleNodes.map(function(n){ return n.id; })); var filteredData = { nodes: visibleNodes, - edges: (data.edges || []).filter(function(e){ return visibleIds.has(e.source) && visibleIds.has(e.target); }) + edges: [] }; var layoutCfg = { name: "preset", @@ -563,20 +541,7 @@ function drawGraph(containerId, data, focusUid, forceShowAux){ "border-color": "data(color)", "border-width": 2.4 }}, - { selector: "node.focus", style: { "border-color": "#facc15", "border-width": 3 }}, - { selector: "edge", style: { - "line-color": "data(color)", - "target-arrow-shape": "none", - "source-arrow-shape": "none", - "mid-target-arrow-shape": "none", - "mid-source-arrow-shape": "none", - "curve-style": "taxi", - "taxi-direction": "auto", - "taxi-turn": "50px", - "taxi-turn-min-distance": "10px", - "width": 2, - "opacity": 0.7 - }} + { selector: "node.focus", style: { "border-color": "#facc15", "border-width": 3 }} ], layout: layoutCfg });