This commit is contained in:
Peter Howell 2025-10-08 02:29:25 +00:00
parent 97b3486c72
commit e491e365cd
1 changed files with 39 additions and 2 deletions

View File

@ -176,7 +176,10 @@
}},
{ selector: 'edge[label]', style: { 'label': 'data(label)' }},
{ selector: 'edge.route', style: { 'line-style': 'dashed','line-color': '#94a3b8','target-arrow-color': '#94a3b8' }},
{ selector: 'edge.sg-attach', style: { 'line-style': 'dotted','line-color': '#f59e0b','target-arrow-color': '#f59e0b' }}
{ selector: 'edge.sg-attach', style: { 'line-style': 'dotted','line-color': '#f59e0b','target-arrow-color': '#f59e0b' }},
{ selector: 'edge.sg-sg', style: { 'line-color': '#f59e0b','target-arrow-color': '#f59e0b','source-arrow-color': '#f59e0b' }},
{ selector: 'edge.sg-sg[tarrow > 0]', style: { 'target-arrow-shape': 'triangle' }},
{ selector: 'edge.sg-sg[sarrow > 0]', style: { 'source-arrow-shape': 'triangle' }}
]
});
@ -219,6 +222,32 @@
const label = `${sg.name || sg.id}\ningress: ${(rin[0]||'—')}${rin.length>1?'…':''}`;
elements.push({ data: { id: sg.id, type: 'sg', label, rules: { in: rin, out: rout } } });
}
// SG<->SG edges based on rules referencing other SGs
function parseSgRef(rule){
// match: "proto port from sg-xxxx"
const m = (rule||'').match(/^(\S+)\s+([^\s]+)\s+from\s+(sg-[0-9a-zA-Z]+)/);
if(!m) return null; return { proto:m[1], port:m[2], sg:m[3] };
}
const pairMap = new Map(); // key: A|B -> {inOn:{}, outFrom:{}}
function keyAB(a,b){ return a<b ? (a+'|'+b) : (b+'|'+a); }
function ensurePair(a,b){ const k=keyAB(a,b); if(!pairMap.has(k)) pairMap.set(k,{inOn:{},outFrom:{}}); return pairMap.get(k); }
function addIn(target, src, proto, port){ const p=ensurePair(target,src); (p.inOn[target]||(p.inOn[target]=[])).push({proto,port,src}); }
function addOut(src, target, proto, port){ const p=ensurePair(src,target); (p.outFrom[src]||(p.outFrom[src]=[])).push({proto,port,target}); }
for (const sg of sgs) {
for (const r of (sg.rules_in||[])) { const x=parseSgRef(r); if(x && sgById[x.sg]) addIn(sg.id, x.sg, x.proto, x.port); }
for (const r of (sg.rules_out||[])) { const x=parseSgRef(r); if(x && sgById[x.sg]) addOut(sg.id, x.sg, x.proto, x.port); }
}
function summarize(list){ const uniq=new Set(); for(const it of (list||[])){ const text=`${it.proto} ${it.port}`; uniq.add(text); } return Array.from(uniq).join(', '); }
for (const [k, val] of pairMap.entries()){
const [a,b]=k.split('|');
const s=a, t=b; // orient stable by id
const s2tList = (val.outFrom[s]||[]).concat(val.inOn[t]||[]);
const t2sList = (val.outFrom[t]||[]).concat(val.inOn[s]||[]);
const tarrow = s2tList.length ? 1 : 0; // arrow at target if flow S->T
const sarrow = t2sList.length ? 1 : 0; // arrow at source if flow T->S
const label = [summarize(s2tList), summarize(t2sList)].filter(Boolean).join(' | ');
elements.push({ data: { id:`sg:${s}|${t}`, source:s, target:t, label, tarrow, sarrow }, classes:'sg-sg' });
}
// EC2
for (const i of ec2s) {
const label = `${i.name || i.id}\n${i.id}\n${i.type || ''}\n${i.privateIp ? ('Private IP: ' + i.privateIp) : ''}`;
@ -431,7 +460,7 @@
document.getElementById('toggle-sg').addEventListener('change',(e)=>{
const show=e.target.checked;
cy.batch(()=>{ cy.nodes('[type = "sg"]').style('display', show?'element':'none'); cy.edges('.sg-attach').style('display', show?'element':'none'); });
cy.batch(()=>{ cy.nodes('[type = "sg"]').style('display', show?'element':'none'); cy.edges('.sg-attach, .sg-sg').style('display', show?'element':'none'); });
});
document.getElementById('toggle-routes').addEventListener('change',(e)=>{
const show=e.target.checked;
@ -470,6 +499,12 @@
});
cy.fit(undefined,24);
}
async function tryLoadStartupLayout(){
try{
const resp = await fetch('layout.json', { cache: 'no-cache' });
if(resp && resp.ok){ const data = await resp.json(); applyLayoutData(data); }
}catch(e){ /* ignore if missing */ }
}
document.getElementById('btn-save').addEventListener('click',()=> downloadJSON(snapshotLayout(),'vpc-layout.json'));
document.getElementById('btn-load').addEventListener('click',()=> document.getElementById('load-file').click());
document.getElementById('load-file').addEventListener('change',(e)=>{
@ -494,6 +529,8 @@
const LKEY='vpc-layout-autosave';
function saveLocal(){ try{ localStorage.setItem(LKEY, JSON.stringify(snapshotLayout())); }catch{} }
function restoreLocal(){ try{ const raw=localStorage.getItem(LKEY); if(raw) applyLayoutData(JSON.parse(raw)); }catch{} }
// Load file-based layout first (if present), then restore any local override
await tryLoadStartupLayout();
restoreLocal();
cy.on('position','node', saveLocal);
cy.on('mouseup', ()=> { if(labelMode) saveLocal(); });