diff --git a/public/css/aurora.css b/public/css/aurora.css index 9d912966..4f5901d1 100644 --- a/public/css/aurora.css +++ b/public/css/aurora.css @@ -289,12 +289,18 @@ a{color:inherit} .data-table .mono{font-family:var(--font-mono); font-size:12.5px; color:var(--muted)} .data-table .cell-name{font-weight:600} /* Routes table — service/domain group header rows (related routes read as a unit) */ +.data-table tr.aurora-group-row{cursor:pointer; user-select:none} .data-table tr.aurora-group-row td{background:var(--surface-2); padding:9px 14px; border-bottom:1px solid var(--line)} -.data-table tr.aurora-group-row:hover td{background:var(--surface-2)} +.data-table tr.aurora-group-row:hover td{background:var(--surface-3)} .data-table tr.aurora-group-row .group-status-dot{display:inline-block; vertical-align:middle} -.aurora-group-label{font-weight:700; font-size:12.5px; color:var(--text); margin-left:9px; vertical-align:middle} +.aurora-group-chevron{vertical-align:middle; margin-right:5px; color:var(--muted); transition:transform .2s ease} +.data-table tr.aurora-group-row.collapsed .aurora-group-chevron{transform:rotate(-90deg)} +.aurora-group-label{font-weight:700; font-size:12.5px; color:var(--text); margin-left:5px; vertical-align:middle} .aurora-group-count{margin-left:10px; font-size:11px; color:var(--faint); font-family:var(--font-mono); vertical-align:middle} .aurora-group-badge{margin-left:8px; vertical-align:middle; font-size:10.5px; padding:1px 7px} +/* "Collapse all" toolbar toggle */ +#aurora-collapse-all{color:var(--muted)} +#aurora-collapse-all:hover{color:var(--text)} .row-actions{display:flex; gap:6px; justify-content:flex-end} .icon-action{width:30px;height:30px;border-radius:8px;border:1px solid var(--line);background:var(--chip);color:var(--muted); display:grid;place-items:center;cursor:pointer;transition:.15s} diff --git a/public/js/routes.js b/public/js/routes.js index f1f919ae..3feed602 100644 --- a/public/js/routes.js +++ b/public/js/routes.js @@ -495,11 +495,18 @@ + ''; } + // Chevron for collapsible Aurora group headers (rotates via CSS .collapsed). + var AURORA_GROUP_CHEVRON = ''; + // Aurora group-header row: shown for every multi-route group (service // bundle or shared domain) so related routes read as a unit, not just a - // hinted-at indent. Mirrors the default theme's .routes-table-group row. + // hinted-at indent. Collapsible — clicking the row toggles its members via + // the shared [data-gtoggle] handler + collapsedGroups set (same persisted + // localStorage state the default/pro card view uses). Mirrors the default + // theme's .routes-table-group row. function auroraRenderGroupHead(g) { var t = GC.t; + var collapsed = collapsedGroups.has(g.key); var label = g.label != null ? escapeHtml(g.label) : escapeHtml(t['routes.group_no_domain'] || 'Without domain'); @@ -507,7 +514,8 @@ ? '' + escapeHtml(t['service_bundle.badge'] || 'SERVICE') + '' : ''; var countTxt = (t['routes.group_count'] || '{{count}} routes').replace('{{count}}', g.routes.length); - return '' + return '' + + AURORA_GROUP_CHEVRON + '' + '' + label + '' + bundleTag @@ -515,19 +523,30 @@ + ''; } + // Keys of the multi-route groups in the last Aurora render — drives the + // toolbar "collapse/expand all" toggle. Updated by auroraRenderTable(). + var auroraGroupKeys = []; + function auroraRenderTable(groups) { var t = GC.t; var rows = ''; + auroraGroupKeys = []; // Aurora: group-headed table (no batch column). Multi-route groups get a - // header row + indented sub-rows; standalone routes render bare. + // collapsible header row + indented sub-rows; collapsed groups hide their + // members. Standalone routes render bare. for (var gi = 0; gi < groups.length; gi++) { var g = groups[gi]; var grouped = !g.single && g.routes.length > 1; - if (grouped) rows += auroraRenderGroupHead(g); + if (grouped) { + auroraGroupKeys.push(g.key); + rows += auroraRenderGroupHead(g); + if (collapsedGroups.has(g.key)) continue; // collapsed → members hidden + } for (var ri = 0; ri < g.routes.length; ri++) { rows += auroraRenderTableRow(g.routes[ri], { subRow: grouped && ri > 0 }); } } + auroraUpdateCollapseAll(); if (!rows) { return '
' + escapeHtml(t['routes.no_routes'] || 'No routes configured') + '
'; @@ -556,6 +575,39 @@ }); } + // Reflects the "collapse all / expand all" toolbar button: hidden when no + // multi-route groups exist, otherwise labelled by what the click will do. + function auroraUpdateCollapseAll() { + var btn = document.getElementById('aurora-collapse-all'); + if (!btn) return; + if (!auroraGroupKeys.length) { btn.style.display = 'none'; return; } + btn.style.display = ''; + var allCollapsed = auroraGroupKeys.every(function (k) { return collapsedGroups.has(k); }); + var lbl = btn.querySelector('.aurora-collapse-all-lbl'); + if (lbl) { + lbl.textContent = allCollapsed + ? (GC.t['routes.expand_all'] || 'Expand all') + : (GC.t['routes.collapse_all'] || 'Collapse all'); + } + btn.classList.toggle('all-collapsed', allCollapsed); + } + + function auroraInitCollapseAll() { + var btn = document.getElementById('aurora-collapse-all'); + if (!btn) return; + btn.addEventListener('click', function () { + var allCollapsed = auroraGroupKeys.length > 0 + && auroraGroupKeys.every(function (k) { return collapsedGroups.has(k); }); + // All collapsed → expand all; otherwise collapse all. + auroraGroupKeys.forEach(function (k) { + if (allCollapsed) collapsedGroups.delete(k); + else collapsedGroups.add(k); + }); + lsSetSet('gc_routes_groups_collapsed_v1', collapsedGroups); + render(); + }); + } + function renderTableRow(r, opts) { if (isAurora()) return auroraRenderTableRow(r, opts); opts = opts || {}; @@ -4028,6 +4080,7 @@ // Aurora-only: wire the type toggle-group in the Aurora toolbar if (isAurora()) auroraInitTypeToggle(); + if (isAurora()) auroraInitCollapseAll(); // ─── Init ──────────────────────────────────────────────── loadRoutes(); diff --git a/src/i18n/de.json b/src/i18n/de.json index 7d9aabfc..0a7013c0 100644 --- a/src/i18n/de.json +++ b/src/i18n/de.json @@ -243,6 +243,8 @@ "routes.view_table": "Tabellenansicht", "routes.group_no_domain": "Ohne Domain", "routes.group_count": "{{count}} Routen", + "routes.collapse_all": "Alle einklappen", + "routes.expand_all": "Alle ausklappen", "routes.no_match": "Keine Routen entsprechen dem Filter", "routes.table_domain": "Domain", "routes.table_type": "Typ", diff --git a/src/i18n/en.json b/src/i18n/en.json index f6ad025a..c0b6cb6b 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -243,6 +243,8 @@ "routes.view_table": "Table view", "routes.group_no_domain": "Without domain", "routes.group_count": "{{count}} routes", + "routes.collapse_all": "Collapse all", + "routes.expand_all": "Expand all", "routes.no_match": "No routes match the filter", "routes.table_domain": "Domain", "routes.table_type": "Type", diff --git a/templates/aurora/pages/routes.njk b/templates/aurora/pages/routes.njk index 8d25dbc9..abc63f4a 100644 --- a/templates/aurora/pages/routes.njk +++ b/templates/aurora/pages/routes.njk @@ -32,6 +32,11 @@ + {# Collapse/expand all route groups — JS shows it only when multi-route groups exist #} +