Skip to content

Commit ae2ce8b

Browse files
Merge pull request #2490 from Particular/new-column-header-component
New column header component
2 parents a06422d + 5916181 commit ae2ce8b

File tree

9 files changed

+193
-321
lines changed

9 files changed

+193
-321
lines changed
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
<script setup lang="ts">
2+
import { computed } from "vue";
3+
import type { SortInfo } from "./SortInfo";
4+
5+
const props = withDefaults(
6+
defineProps<{
7+
name: string;
8+
label: string;
9+
unit?: string;
10+
sortable?: boolean;
11+
sortBy?: string;
12+
sortState?: SortInfo;
13+
defaultAscending?: boolean;
14+
interactiveHelp?: boolean;
15+
}>(),
16+
{
17+
sortable: false,
18+
defaultAscending: false,
19+
interactiveHelp: false,
20+
}
21+
);
22+
23+
const sortByColumn = computed(() => props.sortBy || props.name);
24+
const activeSortColumn = defineModel<SortInfo>();
25+
const isSortActive = computed(() => activeSortColumn?.value?.property === sortByColumn.value);
26+
const sortIcon = computed(() => (activeSortColumn?.value?.isAscending ? "sort-up" : "sort-down"));
27+
28+
function toggleSort() {
29+
activeSortColumn.value = { property: sortByColumn.value, isAscending: isSortActive.value ? !activeSortColumn?.value?.isAscending : props.defaultAscending };
30+
}
31+
</script>
32+
33+
<template>
34+
<div role="columnheader" :aria-label="props.name">
35+
<div class="box-header">
36+
<button v-if="props.sortable" @click="toggleSort" class="column-header-button" :aria-label="props.name">
37+
<span>
38+
{{ props.label }}
39+
<span v-if="props.unit" class="table-header-unit">{{ props.unit }}</span>
40+
<span v-if="isSortActive">
41+
<i role="img" :class="sortIcon" :aria-label="sortIcon"></i>
42+
</span>
43+
<tippy v-if="$slots.help" max-width="400px" :interactive="props.interactiveHelp">
44+
<i class="fa fa-sm fa-info-circle text-primary ps-1" />
45+
<template #content>
46+
<slot name="help" />
47+
</template>
48+
</tippy>
49+
</span>
50+
</button>
51+
<div v-else class="column-header">
52+
<span>
53+
{{ props.label }}
54+
<span v-if="props.unit" class="table-header-unit">{{ props.unit }}</span>
55+
</span>
56+
<tippy v-if="$slots.help" max-width="400px" :interactive="props.interactiveHelp">
57+
<i class="fa fa-sm fa-info-circle text-primary ps-1" />
58+
<template #content>
59+
<slot name="help" />
60+
</template>
61+
</tippy>
62+
</div>
63+
</div>
64+
</div>
65+
</template>
66+
67+
<style scoped>
68+
.column-header {
69+
background: none;
70+
border: none;
71+
padding: 0;
72+
cursor: default;
73+
max-width: 100%;
74+
display: flex;
75+
flex-wrap: wrap;
76+
}
77+
.column-header span,
78+
.column-header-button span {
79+
text-transform: uppercase;
80+
display: inline-block;
81+
text-align: left;
82+
}
83+
.column-header-button {
84+
background: none;
85+
border: none;
86+
padding: 0;
87+
cursor: pointer;
88+
max-width: 100%;
89+
display: flex;
90+
flex-wrap: wrap;
91+
align-items: end;
92+
}
93+
94+
.column-header-button:hover span {
95+
text-decoration: underline;
96+
}
97+
98+
.column-header-button div {
99+
display: inline-block;
100+
}
101+
102+
.sort-up,
103+
.sort-down {
104+
background-position: center;
105+
background-repeat: no-repeat;
106+
width: 8px;
107+
height: 14px;
108+
padding: 0;
109+
margin-left: 10px;
110+
}
111+
112+
.sort-up {
113+
background-image: url("@/assets/sort-up.svg");
114+
}
115+
116+
.sort-down {
117+
background: url("@/assets/sort-down.svg");
118+
}
119+
120+
.sort-up,
121+
.sort-down {
122+
background-repeat: no-repeat;
123+
display: inline-block;
124+
vertical-align: middle;
125+
}
126+
</style>

src/Frontend/src/components/SortableColumn.vue

Lines changed: 0 additions & 101 deletions
This file was deleted.

src/Frontend/src/components/heartbeats/EndpointInstances.vue

Lines changed: 14 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,12 @@ import { storeToRefs } from "pinia";
44
import { useRoute, useRouter } from "vue-router";
55
import { computed, onMounted, ref } from "vue";
66
import { EndpointStatus } from "@/resources/Heartbeat";
7-
import SortableColumn from "@/components/SortableColumn.vue";
7+
import ColumnHeader from "@/components/ColumnHeader.vue";
88
import DataView from "@/components/DataView.vue";
99
import OnOffSwitch from "../OnOffSwitch.vue";
1010
import routeLinks from "@/router/routeLinks";
1111
import { useShowToast } from "@/composables/toast";
1212
import { TYPE } from "vue-toastification";
13-
import { Tippy } from "vue-tippy";
1413
import { useHeartbeatInstancesStore, ColumnNames } from "@/stores/HeartbeatInstancesStore";
1514
import { EndpointsView } from "@/resources/EndpointView";
1615
import endpointSettingsClient from "@/components/heartbeats/endpointSettingsClient";
@@ -159,41 +158,19 @@ async function toggleAlerts(instance: EndpointsView) {
159158
<section role="table" aria-label="endpoint-instances">
160159
<!--Table headings-->
161160
<div role="row" aria-label="column-headers" class="row table-head-row" :style="{ borderTop: 0 }">
162-
<div role="columnheader" :aria-label="ColumnNames.InstanceName" class="col-6">
163-
<SortableColumn :sort-by="ColumnNames.InstanceName" v-model="sortByInstances" :default-ascending="true">Host Name</SortableColumn>
164-
</div>
165-
<div role="columnheader" :aria-label="ColumnNames.LastHeartbeat" class="col-2">
166-
<SortableColumn :sort-by="ColumnNames.LastHeartbeat" v-model="sortByInstances">Last Heartbeat</SortableColumn>
167-
</div>
168-
<div role="columnheader" :aria-label="ColumnNames.MuteToggle" class="col-2 centre">
169-
<SortableColumn :sort-by="ColumnNames.MuteToggle" v-model="sortByInstances">Mute Alerts</SortableColumn>
170-
<tippy max-width="400px">
171-
<i :style="{ fontSize: '1.1em', marginLeft: '0.25em' }" class="fa fa-info-circle text-primary" />
172-
<template #content>
173-
<span>Mute an instance when you are planning to take the instance offline to do maintenance or some other reason. This will prevent alerts on the dashboard.</span>
174-
</template>
175-
</tippy>
176-
</div>
177-
<div role="columnheader" aria-label="actions" class="col-1">
178-
<div>
179-
Actions
180-
<tippy max-width="400px">
181-
<i :style="{ fontSize: '1.1em' }" class="fa fa-info-circle text-primary" />
182-
<template #content>
183-
<table>
184-
<tbody>
185-
<tr>
186-
<td style="padding: 3px; width: 6em; text-align: end; align-content: center">
187-
<button type="button" class="btn btn-danger btn-sm"><i class="fa fa-trash text-white" /> Delete</button>
188-
</td>
189-
<td style="padding: 3px">Delete an instance when that instance has been decommissioned.</td>
190-
</tr>
191-
</tbody>
192-
</table>
193-
</template>
194-
</tippy>
195-
</div>
196-
</div>
161+
<ColumnHeader :name="ColumnNames.InstanceName" label="Host Name" class="col-6" v-model="sortByInstances" sortable default-ascending />
162+
<ColumnHeader :name="ColumnNames.LastHeartbeat" label="Last Heartbeat" class="col-2" v-model="sortByInstances" sortable />
163+
<ColumnHeader :name="ColumnNames.MuteToggle" label="Mute Alerts" class="col-2 centre">
164+
<template #help>Mute an instance when you are planning to take the instance offline to do maintenance or some other reason. This will prevent alerts on the dashboard.</template>
165+
</ColumnHeader>
166+
<ColumnHeader name="actions" label="Actions" class="col-1" interactive-help>
167+
<template #help>
168+
<div class="d-flex align-items-center p-1">
169+
<button type="button" class="btn btn-danger btn-ms text-nowrap me-3" @click="deleteAllInstances()"><i class="fa fa-trash text-white" /> Delete</button>
170+
<span>Delete an instance when that instance has been decommissioned.</span>
171+
</div>
172+
</template>
173+
</ColumnHeader>
197174
</div>
198175
<no-data v-if="filteredValidInstances.length === 0" message="No endpoint instances found. For untracked endpoints, disconnected instances are automatically pruned.">
199176
<div v-if="totalValidInstances.length === 0" class="delete-all">

src/Frontend/src/components/heartbeats/HeartbeatsList.vue

Lines changed: 8 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
<script setup lang="ts">
22
import { useHeartbeatsStore, ColumnNames } from "@/stores/HeartbeatsStore";
33
import { storeToRefs } from "pinia";
4-
import SortableColumn from "@/components/SortableColumn.vue";
54
import DataView from "@/components/DataView.vue";
65
import OnOffSwitch from "../OnOffSwitch.vue";
76
import routeLinks from "@/router/routeLinks";
@@ -11,6 +10,7 @@ import { LogicalEndpoint } from "@/resources/Heartbeat";
1110
import { useShowToast } from "@/composables/toast";
1211
import { TYPE } from "vue-toastification";
1312
import LastHeartbeat from "@/components/heartbeats/LastHeartbeat.vue";
13+
import ColumnHeader from "../ColumnHeader.vue";
1414
1515
defineProps<{
1616
data: LogicalEndpoint[];
@@ -42,27 +42,13 @@ function endpointHealth(endpoint: LogicalEndpoint) {
4242
<section role="table" aria-label="endpoint-instances">
4343
<!--Table headings-->
4444
<div role="row" aria-label="column-headers" class="row table-head-row" :style="{ borderTop: 0 }">
45-
<div v-if="columns.includes(ColumnNames.Name)" role="columnheader" :aria-label="ColumnNames.Name" class="col-6">
46-
<SortableColumn :sort-by="ColumnNames.Name" v-model="sortByInstances" :default-ascending="true">Name</SortableColumn>
47-
</div>
48-
<div v-if="columns.includes(ColumnNames.InstancesDown)" role="columnheader" :aria-label="ColumnNames.InstancesDown" class="col-2">
49-
<SortableColumn :sort-by="ColumnNames.InstancesDown" v-model="sortByInstances" :default-ascending="true">Instances</SortableColumn>
50-
</div>
51-
<div v-if="columns.includes(ColumnNames.InstancesTotal)" role="columnheader" :aria-label="ColumnNames.InstancesTotal" class="col-2">
52-
<SortableColumn :sort-by="ColumnNames.InstancesTotal" v-model="sortByInstances" :default-ascending="true">Instances</SortableColumn>
53-
</div>
54-
<div v-if="columns.includes(ColumnNames.LastHeartbeat)" role="columnheader" :aria-label="ColumnNames.LastHeartbeat" class="col-2">
55-
<SortableColumn :sort-by="ColumnNames.LastHeartbeat" v-model="sortByInstances">Last Heartbeat</SortableColumn>
56-
</div>
57-
<div v-if="columns.includes(ColumnNames.Tracked)" role="columnheader" :aria-label="ColumnNames.Tracked" class="col-1 centre">
58-
<SortableColumn :sort-by="ColumnNames.Tracked" v-model="sortByInstances">Track Instances</SortableColumn>
59-
</div>
60-
<div v-if="columns.includes(ColumnNames.TrackToggle)" role="columnheader" :aria-label="ColumnNames.Tracked" class="col-2 centre">
61-
<SortableColumn :sort-by="ColumnNames.TrackToggle" v-model="sortByInstances">Track Instances</SortableColumn>
62-
</div>
63-
<div v-if="columns.includes(ColumnNames.Muted)" role="columnheader" :aria-label="ColumnNames.Muted" class="col-1 centre">
64-
<SortableColumn :sort-by="ColumnNames.Muted" v-model="sortByInstances">Instances Muted</SortableColumn>
65-
</div>
45+
<ColumnHeader v-if="columns.includes(ColumnNames.Name)" :name="ColumnNames.Name" label="Name" class="col-6" sortable v-model="sortByInstances" default-ascending />
46+
<ColumnHeader v-if="columns.includes(ColumnNames.InstancesDown)" :name="ColumnNames.InstancesDown" label="Instances Down" class="col-2" sortable v-model="sortByInstances" default-ascending />
47+
<ColumnHeader v-if="columns.includes(ColumnNames.InstancesTotal)" :name="ColumnNames.InstancesTotal" label="Instances" class="col-2" sortable v-model="sortByInstances" default-ascending />
48+
<ColumnHeader v-if="columns.includes(ColumnNames.LastHeartbeat)" :name="ColumnNames.LastHeartbeat" label="Last Heartbeat" class="col-2" sortable v-model="sortByInstances" />
49+
<ColumnHeader v-if="columns.includes(ColumnNames.Tracked)" :name="ColumnNames.Tracked" label="Track Instances" class="col-1 centre" sortable v-model="sortByInstances" />
50+
<ColumnHeader v-if="columns.includes(ColumnNames.TrackToggle)" :name="ColumnNames.TrackToggle" label="Track Instances" class="col-2 centre" sortable v-model="sortByInstances" />
51+
<ColumnHeader v-if="columns.includes(ColumnNames.Muted)" :name="ColumnNames.Muted" label="Instances Muted" class="col-1 centre" sortable v-model="sortByInstances" />
6652
</div>
6753
<!--Table rows-->
6854
<DataView :data="data" :show-items-per-page="true" :items-per-page="itemsPerPage" @items-per-page-changed="store.setItemsPerPage">

0 commit comments

Comments
 (0)