${open}${esc(label)}
@@ -521,13 +683,16 @@ function applyFilterClasses() {
canvas.classList.toggle('tp-no-traffic', !state.filters.traffic);
canvas.classList.toggle('tp-only-10g', state.filters.tenG);
canvas.classList.toggle('tp-alerts-only', state.filters.alerts);
- document.querySelectorAll('[data-filter]').forEach(cb => { cb.checked = !!state.filters[cb.dataset.filter]; });
+ document.querySelectorAll('[data-filter]').forEach(el => {
+ if (el.tagName === 'SELECT') el.value = state.filters[el.dataset.filter];
+ else el.checked = !!state.filters[el.dataset.filter];
+ });
}
function render() {
const data = state.data;
if (!data) return;
- state.layout = layout(data, state.filters);
+ state.layout = layout(data, state.filters, state.expanded);
const lay = state.layout;
const world = document.getElementById('tp-world');
const cables = lay.links.map(l => renderLink(l, lay));
@@ -764,7 +929,13 @@ function initEvents() {
window.location.href = `${deviceHref(port.dataset.switch)}#port-${encodeURIComponent(port.dataset.portIdx)}`;
return;
}
+ const group = e.target.closest('[data-expand]');
const link = e.target.closest('.tp-link');
+ if (group || (link && link.dataset.link.startsWith('bundle:'))) {
+ state.expanded.add(group ? group.dataset.expand : link.dataset.link.slice('bundle:'.length));
+ render();
+ return;
+ }
if (link) {
const found = state.data.links.find(l => l.id === link.dataset.link);
if (found) showPanel(found);
@@ -774,6 +945,13 @@ function initEvents() {
});
svg.addEventListener('keydown', e => {
if (e.key !== 'Enter' && e.key !== ' ') return;
+ const group = e.target.closest('[data-expand]');
+ if (group) {
+ e.preventDefault();
+ state.expanded.add(group.dataset.expand);
+ render();
+ return;
+ }
const link = e.target.closest('.tp-link');
if (link) {
e.preventDefault();
@@ -794,11 +972,12 @@ function initEvents() {
if (e.target.closest('[data-action="close-panel"]')) hidePanel();
});
- document.querySelectorAll('[data-filter]').forEach(cb => {
- cb.addEventListener('change', () => {
- state.filters[cb.dataset.filter] = cb.checked;
+ document.querySelectorAll('[data-filter]').forEach(el => {
+ el.addEventListener('change', () => {
+ state.filters[el.dataset.filter] = el.tagName === 'SELECT' ? el.value : el.checked;
saveFilters();
- if (cb.dataset.filter === 'stubs' || cb.dataset.filter === 'power') render();
+ if (el.dataset.filter === 'stubs') state.expanded.clear();
+ if (el.dataset.filter === 'stubs' || el.dataset.filter === 'power') render();
else applyFilterClasses();
});
});
diff --git a/templates/topology.html b/templates/topology.html
index 9fa79c5..eba55e8 100644
--- a/templates/topology.html
+++ b/templates/topology.html
@@ -5,13 +5,18 @@