Skip to content

Commit c8d3a7f

Browse files
fix: restore flow/interactive.html — used by FlowRenderer, not Thymeleaf
The flow template was incorrectly deleted in the Thymeleaf cleanup. It is referenced by FlowRenderer.java:135 and needed for flow diagram rendering. Restoring it fixes 2 FlowRendererTest failures. Co-Authored-By: Paperclip <noreply@paperclip.ing>
1 parent f8b917d commit c8d3a7f

1 file changed

Lines changed: 252 additions & 0 deletions

File tree

Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1">
6+
<title>OSSCodeIQ — Architecture Flow</title>
7+
<script>{{VENDOR_DAGRE}}</script>
8+
<script>{{VENDOR_CYTOSCAPE}}</script>
9+
<script>{{VENDOR_CYTOSCAPE_DAGRE}}</script>
10+
<style>
11+
:root {
12+
--bg: #0a0a0f; --bg-surface: #111118; --bg-elevated: #1a1a24;
13+
--border: #25253a; --border-hover: #3a3a55;
14+
--text: #e4e4ed; --text-muted: #8888a0; --text-dim: #55556a;
15+
--accent: #6366f1; --accent-hover: #818cf8;
16+
}
17+
* { margin: 0; padding: 0; box-sizing: border-box; }
18+
body { font-family: -apple-system, BlinkMacSystemFont, 'Inter', 'Segoe UI', sans-serif; background: var(--bg); color: var(--text); height: 100vh; display: flex; flex-direction: column; overflow: hidden; }
19+
20+
.header { display: flex; align-items: center; justify-content: space-between; padding: 12px 20px; border-bottom: 1px solid var(--border); background: var(--bg-surface); flex-shrink: 0; }
21+
.header-left { display: flex; align-items: center; gap: 12px; }
22+
.logo { width: 32px; height: 32px; border-radius: 8px; background: var(--accent); display: flex; align-items: center; justify-content: center; color: #fff; font-weight: 700; font-size: 12px; flex-shrink: 0; }
23+
.header-title { font-size: 15px; font-weight: 600; color: var(--text); }
24+
.header-subtitle { font-size: 11px; color: var(--text-muted); margin-top: 1px; }
25+
.header-right { display: flex; align-items: center; gap: 8px; }
26+
.stat-pill { display: flex; align-items: center; gap: 6px; padding: 4px 10px; border-radius: 6px; background: var(--bg-elevated); border: 1px solid var(--border); font-size: 12px; }
27+
.stat-value { font-weight: 700; }
28+
.stat-label { color: var(--text-muted); text-transform: uppercase; font-size: 10px; letter-spacing: 0.05em; }
29+
30+
.nav { display: flex; align-items: center; gap: 4px; padding: 8px 20px; border-bottom: 1px solid var(--border); background: var(--bg-surface); flex-shrink: 0; flex-wrap: wrap; }
31+
.nav-btn { padding: 6px 14px; border-radius: 6px; border: 1px solid transparent; background: none; color: var(--text-muted); font-size: 13px; font-weight: 500; cursor: pointer; transition: all 0.15s; }
32+
.nav-btn:hover { color: var(--text); background: var(--bg-elevated); border-color: var(--border); }
33+
.nav-btn.active { background: var(--accent); color: #fff; border-color: var(--accent); }
34+
35+
.graph-wrap { flex: 1; position: relative; overflow: hidden; }
36+
#cy { width: 100%; height: 100%; }
37+
38+
.controls { position: absolute; bottom: 16px; right: 16px; display: flex; flex-direction: column; gap: 4px; z-index: 10; }
39+
.ctrl-btn { width: 36px; height: 36px; border-radius: 8px; border: 1px solid var(--border); background: var(--bg-surface); color: var(--text-muted); font-size: 18px; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: all 0.15s; }
40+
.ctrl-btn:hover { background: var(--bg-elevated); color: var(--text); border-color: var(--border-hover); }
41+
42+
.info-panel { position: absolute; top: 12px; right: 12px; max-width: 280px; padding: 12px 16px; border-radius: 10px; background: var(--bg-surface); border: 1px solid var(--border); font-size: 12px; z-index: 10; display: none; }
43+
.info-panel.visible { display: block; }
44+
.info-title { font-weight: 600; font-size: 14px; margin-bottom: 6px; }
45+
.info-row { display: flex; justify-content: space-between; padding: 3px 0; }
46+
.info-key { color: var(--text-muted); }
47+
.info-val { font-weight: 500; }
48+
.info-close { position: absolute; top: 8px; right: 10px; background: none; border: none; color: var(--text-muted); cursor: pointer; font-size: 16px; }
49+
50+
.view-stats { position: absolute; top: 12px; left: 12px; display: flex; gap: 6px; flex-wrap: wrap; z-index: 10; }
51+
.view-stat { padding: 3px 8px; border-radius: 5px; background: var(--bg-surface); border: 1px solid var(--border); font-size: 11px; color: var(--text-muted); }
52+
.view-stat span { color: var(--text); font-weight: 600; }
53+
54+
.footer { display: flex; align-items: center; justify-content: space-between; padding: 8px 20px; border-top: 1px solid var(--border); background: var(--bg-surface); font-size: 11px; color: var(--text-dim); flex-shrink: 0; }
55+
.footer strong { color: var(--text-muted); }
56+
57+
.hint { position: absolute; bottom: 16px; left: 16px; font-size: 11px; color: var(--text-dim); z-index: 10; }
58+
</style>
59+
</head>
60+
<body>
61+
62+
<div class="header">
63+
<div class="header-left">
64+
<div class="logo">IQ</div>
65+
<div>
66+
<div class="header-title" id="project-name"></div>
67+
<div class="header-subtitle">Architecture Flow</div>
68+
</div>
69+
</div>
70+
<div class="header-right" id="global-stats"></div>
71+
</div>
72+
73+
<div class="nav" id="nav"></div>
74+
75+
<div class="graph-wrap">
76+
<div class="view-stats" id="view-stats"></div>
77+
<div id="cy"></div>
78+
<div class="info-panel" id="info-panel">
79+
<button class="info-close" onclick="closeInfo()">&times;</button>
80+
<div class="info-title" id="info-title"></div>
81+
<div id="info-body"></div>
82+
</div>
83+
<div class="controls">
84+
<button class="ctrl-btn" onclick="zoomIn()" title="Zoom in">+</button>
85+
<button class="ctrl-btn" onclick="zoomOut()" title="Zoom out">&minus;</button>
86+
<button class="ctrl-btn" onclick="fitGraph()" title="Fit to screen">&#x2922;</button>
87+
<button class="ctrl-btn" onclick="resetGraph()" title="Reset view">&#x21ba;</button>
88+
</div>
89+
<div class="hint">Scroll to zoom &middot; Drag to pan &middot; Click nodes for details</div>
90+
</div>
91+
92+
<footer class="footer">
93+
<span>Generated by <strong>OSSCodeIQ</strong> &mdash; No AI, pure deterministic analysis</span>
94+
<span id="gen-time"></span>
95+
</footer>
96+
97+
<script>
98+
var VIEWS_DATA = {{VIEWS_DATA}};
99+
var STATS = {{STATS}};
100+
var PROJECT_NAME = {{PROJECT_NAME}};
101+
102+
var VIEW_LABELS = {overview:"Overview", ci:"CI/CD Pipeline", deploy:"Deployment", runtime:"Runtime", auth:"Security"};
103+
var currentView = "overview";
104+
var cy = null;
105+
106+
var KIND_COLORS = {
107+
trigger:{bg:"#7c3aed",border:"#6d28d9"},pipeline:{bg:"#2563eb",border:"#1d4ed8"},
108+
job:{bg:"#3b82f6",border:"#2563eb"},endpoint:{bg:"#06b6d4",border:"#0891b2"},
109+
entity:{bg:"#8b5cf6",border:"#7c3aed"},database:{bg:"#8b5cf6",border:"#7c3aed"},
110+
guard:{bg:"#f59e0b",border:"#d97706"},middleware:{bg:"#f97316",border:"#ea580c"},
111+
component:{bg:"#10b981",border:"#059669"},messaging:{bg:"#ec4899",border:"#db2777"},
112+
k8s:{bg:"#326ce5",border:"#2558c5"},docker:{bg:"#0db7ed",border:"#099dd0"},
113+
terraform:{bg:"#7b42bc",border:"#643499"},infra:{bg:"#64748b",border:"#475569"},
114+
code:{bg:"#6366f1",border:"#4f46e5"},service:{bg:"#6366f1",border:"#4f46e5"}
115+
};
116+
var STYLE_COLORS = {success:{bg:"#166534",border:"#22c55e"},warning:{bg:"#854d0e",border:"#eab308"},danger:{bg:"#991b1b",border:"#ef4444"}};
117+
var DEFAULT_COLOR = {bg:"#6366f1",border:"#4f46e5"};
118+
119+
function zoomIn(){ if(cy){cy.zoom({level:cy.zoom()*1.3,renderedPosition:{x:cy.width()/2,y:cy.height()/2}});} }
120+
function zoomOut(){ if(cy){cy.zoom({level:cy.zoom()/1.3,renderedPosition:{x:cy.width()/2,y:cy.height()/2}});} }
121+
function fitGraph(){ if(cy) cy.fit(undefined,40); }
122+
function resetGraph(){ if(cy){cy.fit(undefined,40);cy.center();} }
123+
124+
function buildElements(viewData) {
125+
var els = [];
126+
if (!viewData) return els;
127+
(viewData.subgraphs||[]).forEach(function(sg){
128+
els.push({data:{id:sg.id,label:sg.label,isParent:true,drillDown:sg.drill_down_view}});
129+
(sg.nodes||[]).forEach(function(n){
130+
var c = (n.style && n.style!=="default" && STYLE_COLORS[n.style]) || KIND_COLORS[n.kind] || DEFAULT_COLOR;
131+
els.push({data:{id:n.id,label:n.label,kind:n.kind,parent:sg.id,bgColor:c.bg,borderColor:c.border,props:n.properties,nodeStyle:n.style}});
132+
});
133+
});
134+
(viewData.loose_nodes||[]).forEach(function(n){
135+
var c = (n.style && n.style!=="default" && STYLE_COLORS[n.style]) || KIND_COLORS[n.kind] || DEFAULT_COLOR;
136+
els.push({data:{id:n.id,label:n.label,kind:n.kind,bgColor:c.bg,borderColor:c.border,props:n.properties,nodeStyle:n.style}});
137+
});
138+
(viewData.edges||[]).forEach(function(e,i){
139+
els.push({data:{id:"e"+i,source:e.source,target:e.target,label:e.label||"",edgeStyle:e.style||"solid"}});
140+
});
141+
return els;
142+
}
143+
144+
function initCy(elements) {
145+
if (cy) cy.destroy();
146+
cy = cytoscape({
147+
container: document.getElementById("cy"),
148+
elements: elements,
149+
minZoom: 0.15, maxZoom: 5, wheelSensitivity: 0.3,
150+
style: [
151+
{selector:"node[?isParent]",style:{
152+
"background-color":"#15151f","background-opacity":0.6,
153+
"border-width":1,"border-color":"#25253a","border-opacity":0.8,
154+
"shape":"round-rectangle","padding":"24px",
155+
"label":"data(label)","text-valign":"top","text-halign":"center",
156+
"font-size":"13px","font-weight":"600","color":"#8888a0","text-margin-y":-6
157+
}},
158+
{selector:"node[!isParent]",style:{
159+
"background-color":"data(bgColor)","border-width":2,"border-color":"data(borderColor)",
160+
"label":"data(label)","text-valign":"center","text-halign":"center",
161+
"font-size":"11px","font-weight":"500","color":"#fff",
162+
"width":"label","height":"label","padding":"10px",
163+
"shape":"round-rectangle","text-wrap":"wrap","text-max-width":"120px",
164+
"transition-property":"border-width,border-color","transition-duration":"0.15s"
165+
}},
166+
{selector:"edge",style:{
167+
"width":2,"line-color":"#3a3a55","target-arrow-color":"#3a3a55",
168+
"target-arrow-shape":"triangle","arrow-scale":0.8,"curve-style":"bezier",
169+
"label":"data(label)","font-size":"10px","color":"#6a6a80",
170+
"text-rotation":"autorotate","text-margin-y":-10,
171+
"text-background-color":"#0a0a0f","text-background-opacity":0.8,"text-background-padding":"2px"
172+
}},
173+
{selector:"edge[edgeStyle='dotted']",style:{"line-style":"dashed","line-dash-pattern":[6,4],"line-color":"#55556a","target-arrow-color":"#55556a"}},
174+
{selector:"edge[edgeStyle='thick']",style:{"width":3,"line-color":"#6366f1","target-arrow-color":"#6366f1"}},
175+
{selector:"node[!isParent]:active,node[!isParent]:selected",style:{"border-width":3,"border-color":"#818cf8"}}
176+
],
177+
layout:{name:"dagre",rankDir:"LR",nodeSep:50,rankSep:80,padding:40}
178+
});
179+
cy.on("tap","node[!isParent]",function(evt){ showInfo(evt.target.data()); });
180+
cy.on("dbltap","node[?isParent]",function(evt){
181+
var dd = evt.target.data().drillDown;
182+
if (dd && VIEWS_DATA[dd]) switchView(dd);
183+
});
184+
cy.on("tap",function(evt){ if(evt.target===cy) closeInfo(); });
185+
cy.fit(undefined,40);
186+
}
187+
188+
function showInfo(d) {
189+
document.getElementById("info-title").textContent = d.label;
190+
var body = document.getElementById("info-body");
191+
var rows = '<div class="info-row"><span class="info-key">Kind</span><span class="info-val">' + d.kind + '</span></div>';
192+
if (d.props) {
193+
Object.keys(d.props).forEach(function(k){
194+
var v = d.props[k];
195+
rows += '<div class="info-row"><span class="info-key">' + k.replace(/_/g," ") + '</span><span class="info-val">' + v + '</span></div>';
196+
});
197+
}
198+
body.innerHTML = rows;
199+
document.getElementById("info-panel").classList.add("visible");
200+
}
201+
202+
function closeInfo() { document.getElementById("info-panel").classList.remove("visible"); }
203+
204+
function switchView(view) {
205+
currentView = view;
206+
document.querySelectorAll(".nav-btn").forEach(function(btn){ btn.classList.toggle("active",btn.dataset.view===view); });
207+
var data = VIEWS_DATA[view];
208+
var statsEl = document.getElementById("view-stats");
209+
statsEl.innerHTML = "";
210+
if (data && data.stats) {
211+
Object.keys(data.stats).forEach(function(k){
212+
var pill = document.createElement("div");
213+
pill.className = "view-stat";
214+
var val = data.stats[k];
215+
pill.textContent = k.replace(/_/g," ") + " ";
216+
var s = document.createElement("span");
217+
s.textContent = typeof val === "number" ? val.toLocaleString() : val;
218+
pill.appendChild(s);
219+
statsEl.appendChild(pill);
220+
});
221+
}
222+
var elements = buildElements(data);
223+
if (elements.length > 0) { initCy(elements); }
224+
else if (cy) { cy.destroy(); cy = null; }
225+
closeInfo();
226+
}
227+
228+
function init() {
229+
document.getElementById("project-name").textContent = PROJECT_NAME;
230+
var gs = document.getElementById("global-stats");
231+
var n = document.createElement("div"); n.className="stat-pill";
232+
n.innerHTML = '<span class="stat-value" style="color:#6366f1">'+(STATS.total_nodes||0).toLocaleString()+'</span><span class="stat-label">Nodes</span>';
233+
gs.appendChild(n);
234+
var e = document.createElement("div"); e.className="stat-pill";
235+
e.innerHTML = '<span class="stat-value" style="color:#22c55e">'+(STATS.total_edges||0).toLocaleString()+'</span><span class="stat-label">Edges</span>';
236+
gs.appendChild(e);
237+
var nav = document.getElementById("nav");
238+
Object.keys(VIEW_LABELS).forEach(function(v){
239+
var btn = document.createElement("button");
240+
btn.className = "nav-btn"; btn.dataset.view = v;
241+
btn.textContent = VIEW_LABELS[v];
242+
btn.addEventListener("click",function(){ switchView(v); });
243+
nav.appendChild(btn);
244+
});
245+
document.getElementById("gen-time").textContent = new Date().toLocaleDateString();
246+
switchView("overview");
247+
}
248+
249+
document.addEventListener("DOMContentLoaded", init);
250+
</script>
251+
</body>
252+
</html>

0 commit comments

Comments
 (0)