feat(dashboard): add expand/collapse all groups button (#6743)

Co-authored-by: Frank Elsinga <frank@elsinga.de>
This commit is contained in:
Dorian Grasset
2026-01-18 10:49:45 +00:00
committed by GitHub
co-authored by Frank Elsinga
parent a0d73aba1a
commit bf9b734f6c
6 changed files with 94 additions and 7 deletions
+63 -3
View File
@@ -35,7 +35,13 @@
:aria-label="selectAll ? $t('deselectAllMonitorsAria') : $t('selectAllMonitorsAria')"
/>
<MonitorListFilter :filterState="filterState" @update-filter="updateFilter" />
<MonitorListFilter
:filterState="filterState"
:allCollapsed="allGroupsCollapsed"
:hasGroups="groupMonitors.length >= 2"
@update-filter="updateFilter"
@toggle-collapse-all="toggleCollapseAll"
/>
</div>
</div>
@@ -100,8 +106,8 @@
</div>
<MonitorListItem
v-for="(item, index) in sortedMonitorList"
:key="index"
v-for="item in sortedMonitorList"
:key="`${item.id}-${collapseKey}`"
:monitor="item"
:isSelectMode="selectMode"
:isSelected="isSelected"
@@ -154,6 +160,7 @@ export default {
active: null,
tags: null,
},
collapseKey: 0,
};
},
computed: {
@@ -229,6 +236,38 @@ export default {
this.searchText !== ""
);
},
/**
* Gets all group monitors at root level that have children
* @returns {Array} Array of group monitors with children
*/
groupMonitors() {
const monitors = Object.values(this.$root.monitorList);
return monitors.filter(
(m) => m.type === "group" && m.parent === null && monitors.some((child) => child.parent === m.id)
);
},
/**
* Determines if all groups are collapsed.
* Note: collapseKey is included to force re-computation when toggleCollapseAll()
* updates localStorage, since Vue cannot detect localStorage changes.
* @returns {boolean} True if all groups are collapsed
*/
allGroupsCollapsed() {
// collapseKey forces this computed to re-evaluate after localStorage updates
if (this.collapseKey < 0 || this.groupMonitors.length === 0) {
return true;
}
const storage = window.localStorage.getItem("monitorCollapsed");
if (storage === null) {
return true; // Default is collapsed
}
const storageObject = JSON.parse(storage);
return this.groupMonitors.every((group) => storageObject[`monitor_${group.id}`] !== false);
},
},
watch: {
searchText() {
@@ -303,6 +342,26 @@ export default {
updateFilter(newFilter) {
this.filterState = newFilter;
},
/**
* Toggle collapse state for all group monitors
* @returns {void}
*/
toggleCollapseAll() {
const shouldCollapse = !this.allGroupsCollapsed;
let storageObject = {};
const storage = window.localStorage.getItem("monitorCollapsed");
if (storage !== null) {
storageObject = JSON.parse(storage);
}
this.groupMonitors.forEach((group) => {
storageObject[`monitor_${group.id}`] = shouldCollapse;
});
window.localStorage.setItem("monitorCollapsed", JSON.stringify(storageObject));
this.collapseKey++;
},
/**
* Deselect a monitor
* @param {number} id ID of monitor
@@ -731,6 +790,7 @@ export default {
.search-input {
width: 100%;
padding-right: 30px;
transition: none !important;
}
.monitor-item {
+22 -1
View File
@@ -137,6 +137,15 @@
</li>
</template>
</MonitorListFilterDropdown>
<button
v-if="hasGroups"
type="button"
class="btn btn-outline-normal btn-collapse-all"
:title="allCollapsed ? $t('Expand All Groups') : $t('Collapse All Groups')"
@click="$emit('toggle-collapse-all')"
>
<font-awesome-icon :icon="allCollapsed ? 'folder' : 'folder-open'" fixed-width />
</button>
</template>
<script>
@@ -155,8 +164,16 @@ export default {
type: Object,
required: true,
},
allCollapsed: {
type: Boolean,
default: true,
},
hasGroups: {
type: Boolean,
default: false,
},
},
emits: ["updateFilter"],
emits: ["updateFilter", "toggle-collapse-all"],
data() {
return {
tagsList: [],
@@ -322,4 +339,8 @@ export default {
}
}
}
.btn-collapse-all {
transition: none !important;
}
</style>