diff --git a/k8s/deployment.yaml b/k8s/deployment.yaml index 83f4886..54e3d23 100644 --- a/k8s/deployment.yaml +++ b/k8s/deployment.yaml @@ -15,7 +15,7 @@ spec: spec: containers: - name: cloud-dashboard - image: naeel/cloud-dashboard:v9.50 + image: naeel/cloud-dashboard:v9.51 imagePullPolicy: Always ports: - containerPort: 8080 diff --git a/static/graph.js b/static/graph.js index 4faeb91..e6f485a 100644 --- a/static/graph.js +++ b/static/graph.js @@ -5,9 +5,9 @@ var COLS=5, NW=170, NH=56, RW=160, RH=44, GX=30, GY=70, PX=40, PY=30; var INFRA = ["s3","vcloud","штурвал","cloud direct","edge","vdc","vapp"]; - function isInfra(svc){ + function isInfra(svc) { var s=(svc||"").toLowerCase(); - for(var i=0;i=0)return true;} + for(var i=0;i=0) return true; } return false; } @@ -22,182 +22,161 @@ ]; function svcStyle(svc){ var s=(svc||"").toLowerCase(); - for(var i=0;i=0)return{bg:SVC_STYLE[i][1],border:SVC_STYLE[i][2]}; - } - return{bg:"#1e293b",border:"#475569"}; + for(var i=0;i=0) return {bg:SVC_STYLE[i][1],border:SVC_STYLE[i][2]}; } + return {bg:"#1e293b",border:"#475569"}; } - function svcColor(svc){return svcStyle(svc).border;} + function svcColor(svc){ return svcStyle(svc).border; } function statusDot(s){ - var m={running:"#56d364",suspended:"#e3b341",deleted:"#f85149", - deleting:"#f85149",pending:"#58a6ff",creating:"#58a6ff"}; + var m={running:"#56d364",suspended:"#e3b341",deleted:"#f85149",deleting:"#f85149",pending:"#58a6ff",creating:"#58a6ff"}; return m[(s||"").toLowerCase()]||"#6e7681"; } - function esc(v){ - return String(v==null?"":v) - .replace(/&/g,"&").replace(//g,">").replace(/"/g,"""); - } - function trunc(s,n){return(!s)?"":s.length>n?s.slice(0,n-1)+"\u2026":s;} + function esc(v){ return String(v==null?"":v).replace(/&/g,"&").replace(//g,">").replace(/"/g,"""); } + function trunc(s,n){ return (!s)?"": s.length>n?s.slice(0,n-1)+"\u2026":s; } - // ── State ───────────────────────────────────────────────────────────────── + // ── State ──────────────────────────────────────────────────────────────── var graphCache=null, building=false; - function getOverlay(){return document.getElementById("graph-overlay");} - function getCanvas() {return document.getElementById("graph-canvas");} - function getStatus() {return document.getElementById("graph-status");} - function setStatus(m){var el=getStatus();if(el)el.textContent=m;} + function getOverlay(){ return document.getElementById("graph-overlay"); } + function getCanvas() { return document.getElementById("graph-canvas"); } + function getStatus() { return document.getElementById("graph-status"); } + function setStatus(m){ var el=getStatus(); if(el) el.textContent=m; } function openGraph(){ - var ov=getOverlay();if(!ov)return; + var ov=getOverlay(); if(!ov) return; ov.style.display="flex"; - if(graphCache){getCanvas().innerHTML=graphCache;attachPanzoom();} - else if(!building)buildGraph(); + if(graphCache){ getCanvas().innerHTML=graphCache; attachPanzoom(); } + else if(!building) buildGraph(); } - function closeGraph(){var ov=getOverlay();if(ov)ov.style.display="none";} - function refreshGraph(){graphCache=null;building=false;var c=getCanvas();if(c)c.innerHTML="";buildGraph();} + function closeGraph() { var ov=getOverlay(); if(ov) ov.style.display="none"; } + function refreshGraph(){ graphCache=null; building=false; var c=getCanvas(); if(c) c.innerHTML=""; buildGraph(); } - // ── Layout ──────────────────────────────────────────────────────────────── + // ── Layout ─────────────────────────────────────────────────────────────── function computeLayout(instances){ - var realms=[],seen={}; - instances.forEach(function(i){ - var r=i.resourceRealm||""; - if(r&&!seen[r]){seen[r]=true;realms.push(r);} - }); - var infra = instances.filter(function(i){return isInfra(i.svc);}); - var others = instances.filter(function(i){return !isInfra(i.svc);}); + var realms=[], seen={}; + instances.forEach(function(i){ var r=i.resourceRealm||""; if(r&&!seen[r]){seen[r]=true;realms.push(r);} }); + + var infra = instances.filter(function(i){ return isInfra(i.svc); }); + var others = instances.filter(function(i){ return !isInfra(i.svc); }); + var canvasW = PX*2 + COLS*NW + (COLS-1)*GX; - // Row 0: platforms — spread evenly across full width + // Row 0: platforms — spread evenly var realmNodes = realms.map(function(r,i){ var sp=canvasW/realms.length, cx=sp*i+sp/2; - return{id:"realm:"+r,label:r,x:cx-RW/2,y:PY,w:RW,h:RH,cx:cx,cy:PY+RH/2}; + return {id:"realm:"+r, label:r, x:cx-RW/2, y:PY, w:RW, h:RH, cx:cx, cy:PY+RH/2}; }); - // Row 1: infra (S3, Cloud Director, Edge) — spread evenly + // Row 1: infra (S3, CloudDir, Edge) — grid max COLS var row1Y = PY+RH+GY; + var infraRows = Math.ceil(Math.max(infra.length,1)/COLS); var infraNodes = infra.map(function(inst,i){ - var sp=canvasW/Math.max(infra.length,1), cx=sp*i+sp/2; - return{uid:inst.instanceUid,inst:inst,x:cx-NW/2,y:row1Y,w:NW,h:NH,cx:cx,cy:row1Y+NH/2}; + var col=i%COLS, row=Math.floor(i/COLS); + var x=PX+col*(NW+GX), y=row1Y+row*(NH+GY); + return {uid:inst.instanceUid, inst:inst, x:x, y:y, w:NW, h:NH, cx:x+NW/2, cy:y+NH/2}; }); - // Row 2+: other instances — grid, max COLS per row - var row2Y = row1Y+(infra.length>0?NH+GY:0); + // Row 2+: others — grid max COLS + var row2Y = row1Y + (infra.length>0 ? infraRows*(NH+GY) : 0); var otherNodes = others.map(function(inst,i){ var col=i%COLS, row=Math.floor(i/COLS); var x=PX+col*(NW+GX), y=row2Y+row*(NH+GY); - return{uid:inst.instanceUid,inst:inst,x:x,y:y,w:NW,h:NH,cx:x+NW/2,cy:y+NH/2}; + return {uid:inst.instanceUid, inst:inst, x:x, y:y, w:NW, h:NH, cx:x+NW/2, cy:y+NH/2}; }); - return{realms:realmNodes,infra:infraNodes,others:otherNodes}; + + return {realms:realmNodes, infra:infraNodes, others:otherNodes}; } - // ── Manhattan (orthogonal) path ─────────────────────────────────────────── + // ── Manhattan path ─────────────────────────────────────────────────────── + // bottom-center of src → horizontal mid → top-center of tgt function mpath(src,tgt){ - var x1=src.cx, y1=src.y+src.h; // source bottom-center - var x2=tgt.cx, y2=tgt.y; // target top-center + var x1=src.cx, y1=src.y+src.h; + var x2=tgt.cx, y2=tgt.y; if(Math.abs(y1-y2)<8){ - // same row: dip below both nodes - var dip=Math.max(src.y+src.h,tgt.y+tgt.h)+24; - return"M"+x1+","+y1+" L"+x1+","+dip+" L"+x2+","+dip+" L"+x2+","+y2; + var dip=Math.max(src.y+src.h, tgt.y+tgt.h)+24; + return "M"+x1+","+y1+" L"+x1+","+dip+" L"+x2+","+dip+" L"+x2+","+y2; } var my=(y1+y2)/2; - return"M"+x1+","+y1+" L"+x1+","+my+" L"+x2+","+my+" L"+x2+","+y2; + return "M"+x1+","+y1+" L"+x1+","+my+" L"+x2+","+my+" L"+x2+","+y2; } - // ── SVG render ──────────────────────────────────────────────────────────── + // ── SVG render ─────────────────────────────────────────────────────────── function renderSVG(instances, depEdges){ var layout=computeLayout(instances); var nodeMap={}; - layout.realms.forEach(function(n){nodeMap[n.id]=n;}); - layout.infra.forEach(function(n){nodeMap[n.uid]=n;}); - layout.others.forEach(function(n){nodeMap[n.uid]=n;}); + layout.realms.forEach(function(n){ nodeMap[n.id]=n; }); + layout.infra.forEach(function(n){ nodeMap[n.uid]=n; }); + layout.others.forEach(function(n){ nodeMap[n.uid]=n; }); var allN=layout.realms.concat(layout.infra).concat(layout.others); var maxX=0, maxY=0; - allN.forEach(function(n){ - if(n.x+n.w>maxX)maxX=n.x+n.w; - if(n.y+n.h>maxY)maxY=n.y+n.h; - }); + allN.forEach(function(n){ if(n.x+n.w>maxX)maxX=n.x+n.w; if(n.y+n.h>maxY)maxY=n.y+n.h; }); var W=maxX+PX, H=maxY+PY; - // Collect unique edge colors for arrowhead markers var colors={"#334155":1}; - depEdges.forEach(function(e){if(e.color)colors[e.color]=1;}); + depEdges.forEach(function(e){ if(e.color) colors[e.color]=1; }); - var p=[ - '', - '' - ]; + var p=['','']; Object.keys(colors).forEach(function(c){ var id="arr"+c.replace(/[^a-zA-Z0-9]/g,""); - p.push(''+ - ''); + p.push(''); }); p.push(''); - // Platform → instance lines (thin dashed, gray) + // Platform → instance (thin dashed) instances.forEach(function(inst){ - var r=inst.resourceRealm; if(!r)return; + var r=inst.resourceRealm; if(!r) return; var src=nodeMap["realm:"+r], tgt=nodeMap[inst.instanceUid]; - if(!src||!tgt)return; + if(!src||!tgt) return; p.push(''); }); - // Dependency edges (colored Manhattan lines with arrow) + // Dependency edges (colored Manhattan) depEdges.forEach(function(e){ var src=nodeMap[e.from], tgt=nodeMap[e.to]; - if(!src||!tgt)return; + if(!src||!tgt) return; var c=e.color||"#475569"; var id="arr"+c.replace(/[^a-zA-Z0-9]/g,""); - p.push(''); + p.push(''); }); - // Realm (platform) nodes + // Realm nodes layout.realms.forEach(function(n){ - p.push(''); - p.push('ПЛАТФОРМА'); - p.push(''+ - esc(trunc(n.label,20))+''); + p.push(''); + p.push('ПЛАТФОРМА'); + p.push(''+esc(trunc(n.label,20))+''); }); - // Instance nodes (infra row + grid rows) + // Instance nodes layout.infra.concat(layout.others).forEach(function(n){ var st=svcStyle(n.inst.svc), dot=statusDot(n.inst.explainedStatus); var lbl=trunc(n.inst.displayName||n.inst.instanceUid,18); var svc=trunc(n.inst.svc||"",22); - p.push(''); + p.push(''); p.push(''); - p.push(''+esc(lbl)+''); - p.push(''+esc(svc)+''); + p.push(''+esc(lbl)+''); + p.push(''+esc(svc)+''); }); p.push(''); return p.join("\n"); } - // ── Panzoom ─────────────────────────────────────────────────────────────── + // ── Panzoom ────────────────────────────────────────────────────────────── function attachPanzoom(){ var svg=document.getElementById("graph-svg"); - if(!svg||typeof panzoom==="undefined")return; + if(!svg||typeof panzoom==="undefined") return; if(svg._pz){try{svg._pz.dispose();}catch(e){}} svg._pz=panzoom(svg,{maxZoom:4,minZoom:0.1,smoothScroll:false}); } - // ── Build ───────────────────────────────────────────────────────────────── + // ── Build ──────────────────────────────────────────────────────────────── function buildGraph(){ - if(building)return; + if(building) return; building=true; graphCache=null; - var canvas=getCanvas(); if(!canvas)return; + var canvas=getCanvas(); if(!canvas) return; canvas.innerHTML=""; setStatus("Загрузка\u2026"); - var instances=(typeof window._getVisibleInstances==="function")?window._getVisibleInstances() - :(typeof window._getInstances==="function"?window._getInstances():[]); + var instances=(typeof window._getVisibleInstances==="function") ? window._getVisibleInstances() + :(typeof window._getInstances==="function" ? window._getInstances() : []); if(!instances||!instances.length){ setStatus("Нет данных. Обновите таблицу и откройте схему снова."); @@ -216,45 +195,42 @@ } var uid=instances[idx].instanceUid, srcSvc=instances[idx].svc||""; fetch("/dashboard/api/instances/"+uid,{ - headers:{ - "X-Deck-Token":localStorage.getItem("deck_token")||"", - "X-Deck-Env":localStorage.getItem("deck_env")||"test" - } + headers:{"X-Deck-Token":localStorage.getItem("deck_token")||"","X-Deck-Env":localStorage.getItem("deck_env")||"test"} }) - .then(function(r){return r.ok?r.json():null;}) + .then(function(r){ return r.ok?r.json():null; }) .then(function(data){ done++; setStatus("Зависимости: "+done+"/"+total); if(data){ var inst=data.instance||data; [].concat(inst.dependencies||[],inst.deps||[]).forEach(function(d){ var to=d.uid||d.instanceUid||d.dependencyUid; - if(to)edges.push({from:uid,to:to,color:svcColor(d.svc||srcSvc)}); + if(to) edges.push({from:uid,to:to,color:svcColor(d.svc||srcSvc)}); }); [].concat(inst.dependentInstances||[],inst.dependsOn||[]).forEach(function(d){ var from=d.uid||d.instanceUid||d.dependencyUid; - if(from)edges.push({from:from,to:uid,color:svcColor(d.svc||srcSvc)}); + if(from) edges.push({from:from,to:uid,color:svcColor(d.svc||srcSvc)}); }); } setTimeout(function(){loadNext(idx+1);},20); }) - .catch(function(){done++;setTimeout(function(){loadNext(idx+1);},20);}); + .catch(function(){ done++; setTimeout(function(){loadNext(idx+1);},20); }); } loadNext(0); } - // ── Init ────────────────────────────────────────────────────────────────── + // ── Init ──────────────────────────────────────────────────────────────── function init(){ - var a=document.getElementById("btn-graph-open"); - var b=document.getElementById("btn-graph-close"); - var c=document.getElementById("btn-graph-refresh"); - var ov=document.getElementById("graph-overlay"); - if(a)a.addEventListener("click",openGraph); - if(b)b.addEventListener("click",closeGraph); - if(c)c.addEventListener("click",refreshGraph); - if(ov)ov.addEventListener("click",function(e){if(e.target===ov)closeGraph();}); + var btnOpen=document.getElementById("btn-graph-open"); + var btnClose=document.getElementById("btn-graph-close"); + var btnRefresh=document.getElementById("btn-graph-refresh"); + var overlay=document.getElementById("graph-overlay"); + if(btnOpen) btnOpen.addEventListener("click",openGraph); + if(btnClose) btnClose.addEventListener("click",closeGraph); + if(btnRefresh) btnRefresh.addEventListener("click",refreshGraph); + if(overlay) overlay.addEventListener("click",function(e){if(e.target===overlay)closeGraph();}); document.addEventListener("keydown",function(e){if(e.key==="Escape")closeGraph();}); } - if(document.readyState==="loading"){document.addEventListener("DOMContentLoaded",init);}else{init();} + if(document.readyState==="loading"){ document.addEventListener("DOMContentLoaded",init); } else { init(); } window.graphModule={open:openGraph,close:closeGraph,refresh:refreshGraph}; })();