|
| 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()">×</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">−</button> |
| 86 | + <button class="ctrl-btn" onclick="fitGraph()" title="Fit to screen">⤢</button> |
| 87 | + <button class="ctrl-btn" onclick="resetGraph()" title="Reset view">↺</button> |
| 88 | + </div> |
| 89 | + <div class="hint">Scroll to zoom · Drag to pan · Click nodes for details</div> |
| 90 | +</div> |
| 91 | + |
| 92 | +<footer class="footer"> |
| 93 | + <span>Generated by <strong>OSSCodeIQ</strong> — 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