feat: add collapsible groups to status page (#7154)

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
pull/7174/head^2
Marco Carvalho 2 months ago committed by GitHub
parent 4586896978
commit cdfca84664
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -17,11 +17,20 @@
class="action remove me-3"
@click="removeGroup(group.index)"
/>
<span class="collapse-toggle" @click="toggleGroup(group.element)">
<font-awesome-icon
icon="chevron-down"
class="chevron me-2"
:class="{ collapsed: isGroupCollapsed(group.element) }"
/>
</span>
<Editable
v-model="group.element.name"
:contenteditable="editMode"
tag="span"
:class="{ 'collapse-toggle': !editMode }"
data-testid="group-name"
@click="!editMode && toggleGroup(group.element)"
/>
</div>
@ -33,97 +42,101 @@
/>
</h2>
<div class="shadow-box monitor-list mt-4 position-relative">
<div v-if="group.element.monitorList.length === 0" class="text-center no-monitor-msg">
{{ $t("No Monitors") }}
</div>
<transition name="slide-fade-up">
<div v-if="!isGroupCollapsed(group.element)" class="shadow-box monitor-list mt-4 position-relative">
<div v-if="group.element.monitorList.length === 0" class="text-center no-monitor-msg">
{{ $t("No Monitors") }}
</div>
<!-- Monitor List -->
<!-- animation is not working, no idea why -->
<Draggable
v-model="group.element.monitorList"
class="monitor-list"
group="same-group"
:disabled="!editMode"
:animation="100"
item-key="id"
>
<template #item="monitor">
<div class="item" data-testid="monitor">
<div class="row">
<div class="col-9 col-xl-6 small-padding">
<div class="info">
<font-awesome-icon
v-if="editMode"
icon="arrows-alt-v"
class="action drag me-3"
/>
<font-awesome-icon
v-if="editMode"
icon="times"
class="action remove me-3"
@click="removeMonitor(group.index, monitor.index)"
/>
<font-awesome-icon
v-if="editMode"
icon="cog"
class="action me-3 ms-0"
:class="{ 'link-active': true, 'btn-link': true }"
data-testid="monitor-settings"
@click="$refs.monitorSettingDialog.show(group, monitor)"
/>
<Status
v-if="showOnlyLastHeartbeat"
:status="statusOfLastHeartbeat(monitor.element.id)"
/>
<Uptime v-else :monitor="monitor.element" type="24" :pill="true" />
<a
v-if="showLink(monitor)"
:href="monitor.element.url"
class="item-name"
target="_blank"
rel="noopener noreferrer"
data-testid="monitor-name"
>
{{ monitor.element.name }}
</a>
<p v-else class="item-name" data-testid="monitor-name">
{{ monitor.element.name }}
</p>
</div>
<div class="extra-info">
<div
v-if="showCertificateExpiry && monitor.element.certExpiryDaysRemaining"
>
<Tag
:item="{
name: $t('Cert Exp.'),
value: formattedCertExpiryMessage(monitor),
color: certExpiryColor(monitor),
}"
:size="'sm'"
<!-- Monitor List -->
<!-- animation is not working, no idea why -->
<Draggable
v-model="group.element.monitorList"
class="monitor-list"
group="same-group"
:disabled="!editMode"
:animation="100"
item-key="id"
>
<template #item="monitor">
<div class="item" data-testid="monitor">
<div class="row">
<div class="col-9 col-xl-6 small-padding">
<div class="info">
<font-awesome-icon
v-if="editMode"
icon="arrows-alt-v"
class="action drag me-3"
/>
</div>
<div v-if="showTags">
<Tag
v-for="tag in monitor.element.tags"
:key="tag"
:item="tag"
:size="'sm'"
data-testid="monitor-tag"
<font-awesome-icon
v-if="editMode"
icon="times"
class="action remove me-3"
@click="removeMonitor(group.index, monitor.index)"
/>
<font-awesome-icon
v-if="editMode"
icon="cog"
class="action me-3 ms-0"
:class="{ 'link-active': true, 'btn-link': true }"
data-testid="monitor-settings"
@click="$refs.monitorSettingDialog.show(group, monitor)"
/>
<Status
v-if="showOnlyLastHeartbeat"
:status="statusOfLastHeartbeat(monitor.element.id)"
/>
<Uptime v-else :monitor="monitor.element" type="24" :pill="true" />
<a
v-if="showLink(monitor)"
:href="monitor.element.url"
class="item-name"
target="_blank"
rel="noopener noreferrer"
data-testid="monitor-name"
>
{{ monitor.element.name }}
</a>
<p v-else class="item-name" data-testid="monitor-name">
{{ monitor.element.name }}
</p>
</div>
<div class="extra-info">
<div
v-if="
showCertificateExpiry && monitor.element.certExpiryDaysRemaining
"
>
<Tag
:item="{
name: $t('Cert Exp.'),
value: formattedCertExpiryMessage(monitor),
color: certExpiryColor(monitor),
}"
:size="'sm'"
/>
</div>
<div v-if="showTags">
<Tag
v-for="tag in monitor.element.tags"
:key="tag"
:item="tag"
:size="'sm'"
data-testid="monitor-tag"
/>
</div>
</div>
</div>
</div>
<div :key="$root.userHeartbeatBar" class="col-3 col-xl-6">
<HeartbeatBar size="mid" :monitor-id="monitor.element.id" />
<div :key="$root.userHeartbeatBar" class="col-3 col-xl-6">
<HeartbeatBar size="mid" :monitor-id="monitor.element.id" />
</div>
</div>
</div>
</div>
</template>
</Draggable>
</div>
</template>
</Draggable>
</div>
</transition>
</div>
</template>
</Draggable>
@ -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;

Loading…
Cancel
Save