From cdfca84664841c91f58619bee2b46dc57c8a8860 Mon Sep 17 00:00:00 2001 From: Marco Carvalho Date: Wed, 18 Mar 2026 14:41:57 -0300 Subject: [PATCH] feat: add collapsible groups to status page (#7154) Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- src/components/PublicGroupList.vue | 259 +++++++++++++++++++---------- 1 file changed, 168 insertions(+), 91 deletions(-) diff --git a/src/components/PublicGroupList.vue b/src/components/PublicGroupList.vue index d9c13aa24..7512190e0 100644 --- a/src/components/PublicGroupList.vue +++ b/src/components/PublicGroupList.vue @@ -17,11 +17,20 @@ class="action remove me-3" @click="removeGroup(group.index)" /> + + + @@ -33,97 +42,101 @@ /> -
-
- {{ $t("No Monitors") }} -
+ +
+
+ {{ $t("No Monitors") }} +
- - - - + +
+
@@ -183,6 +196,59 @@ export default { // Sorting is now handled by GroupSortDropdown component }, methods: { + /** + * Toggle collapsed state for a group + * @param {object} group Group to toggle + * @returns {void} + */ + toggleGroup(group) { + if (!this.$router) { + return; + } + + const groupId = this.getGroupIdentifier(group); + const collapsed = this.getCollapsedList(); + const index = collapsed.indexOf(groupId); + + if (index >= 0) { + collapsed.splice(index, 1); + } else { + collapsed.push(groupId); + } + + const query = { ...this.$route.query }; + if (collapsed.length > 0) { + query.collapse = collapsed; + } else { + delete query.collapse; + } + + this.$router.push({ query }).catch(() => {}); + }, + + /** + * Check if a group is collapsed + * @param {object} group Group to check + * @returns {boolean} Whether the group is collapsed + */ + isGroupCollapsed(group) { + return this.getCollapsedList().includes(this.getGroupIdentifier(group)); + }, + + /** + * Get list of collapsed group identifiers from the query param. + * Vue Router normalises repeated params (?collapse=1&collapse=2) into an array. + * @returns {string[]} Collapsed group identifiers + */ + getCollapsedList() { + const raw = this.$route.query.collapse; + if (!raw) { + return []; + } + // Normalise to array: a single query param is a string, repeated params are already an array + return [].concat(raw); + }, + /** * Remove the specified group * @param {number} index Index of group to remove @@ -278,14 +344,10 @@ export default { * @returns {string} group identifier */ getGroupIdentifier(group) { - // Use the name directly if available - if (group.name) { - // Only remove spaces and use encodeURIComponent for URL safety - const cleanName = group.name.replace(/\s+/g, ""); - return cleanName; + if (group.id !== undefined && group.id !== null) { + return group.id.toString(); } - // Fallback to ID or index - return group.id ? `group${group.id}` : `group${this.$root.publicGroupList.indexOf(group)}`; + return `group${this.$root.publicGroupList.indexOf(group)}`; }, }, }; @@ -363,6 +425,21 @@ export default { } } +.collapse-toggle { + cursor: pointer; + padding: 2px; +} + +.chevron { + font-size: 0.8em; + color: #bbb; + transition: all 0.2s $easing-in; + + &.collapsed { + transform: rotate(-90deg); + } +} + .mobile { .item { padding: 13px 0 10px;