From e491e365cdb665bfdfd319fa9da2b1bc0f79d68c Mon Sep 17 00:00:00 2001 From: Peter Howell Date: Wed, 8 Oct 2025 02:29:25 +0000 Subject: [PATCH] updates --- ex4.html | 41 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/ex4.html b/ex4.html index 2897609..cae0e42 100644 --- a/ex4.html +++ b/ex4.html @@ -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 aT + 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(); });