diff --git a/backend/resources/app/templates/graph-console.tmpl b/backend/resources/app/templates/graph-console.tmpl index b40922d986..f45928b951 100644 --- a/backend/resources/app/templates/graph-console.tmpl +++ b/backend/resources/app/templates/graph-console.tmpl @@ -183,6 +183,9 @@ Graph Console
+ @@ -879,6 +882,12 @@ Graph Console }; if (layoutCfg) opts.layout = layoutCfg; g6graph = new G6.Graph(opts); + // Re-attached on every instance creation (layout/animation switches + // destroy and recreate the graph). + g6graph.on("node:click", function (e) { + const id = e.target && e.target.id; + if (id) showNodeInspector(String(id)); + }); return g6graph.render().catch(function (_err) { /* see above */ }); @@ -1019,6 +1028,81 @@ Graph Console }); } + // --- node inspector -------------------------------------------------- + // Click a node → its full attribute row from the graph DB, rendered in + // a panel under the canvas. Panel over tooltip: the projected tables + // carry ~80 columns — too much for a tooltip, and the panel persists + // for side-by-side reading without obstructing the graph. + function runConsoleQuery(query) { + const formData = new FormData(); + formData.append("query", query); + return fetch("/dbg/actions/graph-query", { + method: "POST", + headers: { "Accept": "application/json" }, + body: formData + }) + .then(function (resp) { return resp.text(); }) + .then(function (text) { return parseTransitMap(JSON.parse(text)); }); + } + + function showNodeInspector(id) { + const panel = document.getElementById("graph-node-inspector"); + if (!panel || !g6graph) return; + let datum = null; + try { datum = g6graph.getNodeData(id); } catch (_err) { return; } + const table = datum && datum.data && datum.data.table; + const label = (datum && datum.data && datum.data.label) || ""; + // Both values are interpolated into Cypher — accept only what our own + // export produces (bare table names, UUID ids). + if (!table || !/^[A-Za-z]+$/.test(table)) return; + if (!/^[0-9a-f-]{36}$/i.test(id)) return; + panel.style.display = "block"; + panel.textContent = "loading " + id + "…"; + runConsoleQuery("MATCH (n:`" + table + "` {id: uuid('" + id + "')}) RETURN n.*;") + .then(function (data) { + if (data.error) { + panel.textContent = "inspector error: " + data.error; + return; + } + const result = data["query-result"]; + const rows = (result && result.rows) || []; + let html = '" + escapeHtml(col.replace(/^n\./, ""))
+ + " | " + escapeHtml(String(val))
+ + " |