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 @@
{{ t('routes.type_l4') }}
{{ t('nav.rdp') }}
+ {# Collapse/expand all route groups — JS shows it only when multi-route groups exist #}
+
+
+ {{ t('routes.collapse_all') }}
+