Skip to content

Commit 7b3f83f

Browse files
Add ColumnHeader (WIP)
1 parent 992a5d2 commit 7b3f83f

File tree

4 files changed

+168
-43
lines changed

4 files changed

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

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

Lines changed: 13 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ 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";
@@ -18,7 +18,6 @@ import ConfirmDialog from "@/components/ConfirmDialog.vue";
1818
import LastHeartbeat from "@/components/heartbeats/LastHeartbeat.vue";
1919
import FilterInput from "../FilterInput.vue";
2020
import ResultsCount from "../ResultsCount.vue";
21-
import ColumnHelp from "./ColumnHelp.vue";
2221
2322
enum Operation {
2423
Mute = "mute",
@@ -159,26 +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-
<ColumnHelp>
171-
<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>
172-
</ColumnHelp>
173-
</div>
174-
<StandardColumn columnLabel="Actions">
175-
<ColumnHelp :interactive="true">
176-
<div class="d-flex align-items-center p-1">
177-
<button type="button" class="btn btn-danger btn-ms text-nowrap me-3" @click="deleteAllInstances()"><i class="fa fa-trash text-white" /> Delete</button>
161+
<ColumnHeader :name="ColumnNames.InstanceName" label="Host Name" columnClass="col-6" v-model="sortByInstances" sortable default-ascending />
162+
<ColumnHeader :name="ColumnNames.LastHeartbeat" label="Last Heartbeat" columnClass="col-2" :sortable="true" v-model="sortByInstances" />
163+
<ColumnHeader :name="ColumnNames.MuteToggle" label="Mute Alerts" columnClass="col-2 centre" v-model="sortByInstances">
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" columnClass="col-1" :sortable="false" :interactive-help="true" v-model="sortByInstances">
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>
178170
<span style="text-transform: none">Delete an instance when that instance has been decommissioned.</span>
179-
</div>
180-
</ColumnHelp>
181-
</StandardColumn>
171+
</div>
172+
</template>
173+
</ColumnHeader>
182174
</div>
183175
<no-data v-if="filteredValidInstances.length === 0" message="No endpoint instances found. For untracked endpoints, disconnected instances are automatically pruned.">
184176
<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" columnClass="col-6" sortable v-model="sortByInstances" default-ascending />
46+
<ColumnHeader v-if="columns.includes(ColumnNames.InstancesDown)" :name="ColumnNames.InstancesDown" label="Instances Down" columnClass="col-2" sortable v-model="sortByInstances" default-ascending />
47+
<ColumnHeader v-if="columns.includes(ColumnNames.InstancesTotal)" :name="ColumnNames.InstancesTotal" label="Instances Total" columnClass="col-2" sortable v-model="sortByInstances" default-ascending />
48+
<ColumnHeader v-if="columns.includes(ColumnNames.LastHeartbeat)" :name="ColumnNames.LastHeartbeat" label="Last Heartbeat" columnClass="col-2" sortable v-model="sortByInstances" />
49+
<ColumnHeader v-if="columns.includes(ColumnNames.Tracked)" :name="ColumnNames.Tracked" label="Track Instances" columnClass="col-1 centre" sortable v-model="sortByInstances" />
50+
<ColumnHeader v-if="columns.includes(ColumnNames.TrackToggle)" :name="ColumnNames.TrackToggle" label="Track Instances" columnClass="col-2 centre" sortable v-model="sortByInstances" />
51+
<ColumnHeader v-if="columns.includes(ColumnNames.Muted)" :name="ColumnNames.Muted" label="Instances Muted" columnClass="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)