1
0
mirror of https://github.com/amir20/dozzle.git synced 2025-12-21 13:23:07 +01:00

feat: adds table sort to homepage (#2330)

* feat: adds table sort to homepage

* feat: adds sorting to dashboard

* fixes colors
This commit is contained in:
Amir Raminfar
2023-07-31 09:54:27 -07:00
committed by GitHub
parent ac0b9b157e
commit 40c259b1be
4 changed files with 77 additions and 15 deletions

View File

@@ -37,8 +37,6 @@ declare module 'vue' {
LogViewer: typeof import('./components/LogViewer/LogViewer.vue')['default'] LogViewer: typeof import('./components/LogViewer/LogViewer.vue')['default']
LogViewerWithSource: typeof import('./components/LogViewer/LogViewerWithSource.vue')['default'] LogViewerWithSource: typeof import('./components/LogViewer/LogViewerWithSource.vue')['default']
'Mdi:arrowUp': typeof import('~icons/mdi/arrow-up')['default'] 'Mdi:arrowUp': typeof import('~icons/mdi/arrow-up')['default']
'Mdi:chevronLeft': typeof import('~icons/mdi/chevron-left')['default']
'Mdi:chevronRight': typeof import('~icons/mdi/chevron-right')['default']
'Mdi:dotsVertical': typeof import('~icons/mdi/dots-vertical')['default'] 'Mdi:dotsVertical': typeof import('~icons/mdi/dots-vertical')['default']
'Mdi:lightChevronDoubleDown': typeof import('~icons/mdi-light/chevron-double-down')['default'] 'Mdi:lightChevronDoubleDown': typeof import('~icons/mdi-light/chevron-double-down')['default']
'Mdi:lightChevronLeft': typeof import('~icons/mdi-light/chevron-left')['default'] 'Mdi:lightChevronLeft': typeof import('~icons/mdi-light/chevron-left')['default']
@@ -50,7 +48,6 @@ declare module 'vue' {
'Octicon:container24': typeof import('~icons/octicon/container24')['default'] 'Octicon:container24': typeof import('~icons/octicon/container24')['default']
'Octicon:download24': typeof import('~icons/octicon/download24')['default'] 'Octicon:download24': typeof import('~icons/octicon/download24')['default']
'Octicon:trash24': typeof import('~icons/octicon/trash24')['default'] 'Octicon:trash24': typeof import('~icons/octicon/trash24')['default']
OrugaIcon: typeof import('./components/OrugaIcon.vue')['default']
Popup: typeof import('./components/Popup.vue')['default'] Popup: typeof import('./components/Popup.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink'] RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView'] RouterView: typeof import('vue-router')['RouterView']

View File

@@ -2,12 +2,22 @@
<template> <template>
<table class="table is-fullwidth"> <table class="table is-fullwidth">
<thead> <thead>
<tr> <tr :data-direction="direction > 0 ? 'asc' : 'desc'">
<th>{{ $t("label.container-name") }}</th> <th
<th>{{ $t("label.status") }}</th> v-for="(label, field) in headers"
<th>{{ $t("label.last-started") }}</th> :key="field"
<th>{{ $t("label.avg-cpu") }}</th> @click.prevent="sort(field)"
<th>{{ $t("label.avg-mem") }}</th> :class="{ 'selected-sort': field === sortField }"
>
<a>
<span class="icon-text">
<span>{{ $t(label) }}</span>
<span class="icon">
<mdi:arrow-up />
</span>
</span>
</a>
</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@@ -31,13 +41,22 @@
<nav class="pagination is-right" role="navigation" aria-label="pagination" v-if="isPaginated"> <nav class="pagination is-right" role="navigation" aria-label="pagination" v-if="isPaginated">
<ul class="pagination-list"> <ul class="pagination-list">
<li v-for="i in totalPages"> <li v-for="i in totalPages">
<a class="pagination-link" :class="{ 'is-current': i === currentPage }" @click="currentPage = i">{{ i }}</a> <a class="pagination-link" :class="{ 'is-current': i === currentPage }" @click.prevent="currentPage = i">{{
i
}}</a>
</li> </li>
</ul> </ul>
</nav> </nav>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
const headers = {
name: "label.container-name",
state: "label.status",
created: "label.last-started",
cpu: "label.avg-cpu",
mem: "label.avg-mem",
};
const { containers, perPage = 15 } = defineProps<{ const { containers, perPage = 15 } = defineProps<{
containers: { containers: {
movingAverage: { cpu: number; memory: number }; movingAverage: { cpu: number; memory: number };
@@ -48,16 +67,62 @@ const { containers, perPage = 15 } = defineProps<{
}[]; }[];
perPage?: number; perPage?: number;
}>(); }>();
const sortField: Ref<keyof typeof headers> = ref("created");
const direction = ref<1 | -1>(1);
const sortedContainers = computedWithControl(
() => [containers.length, sortField.value, direction.value],
() => {
console.log("sorting");
return containers.sort((a, b) => {
if (sortField.value === "name") {
return direction.value * a.name.localeCompare(b.name);
} else if (sortField.value === "created") {
return direction.value * (a.created.getTime() - b.created.getTime());
} else if (sortField.value === "cpu") {
return direction.value * (a.movingAverage.cpu - b.movingAverage.cpu);
} else if (sortField.value === "mem") {
return direction.value * (a.movingAverage.memory - b.movingAverage.memory);
} else if (sortField.value === "state") {
return direction.value * a.state.localeCompare(b.state);
}
throw new Error("Invalid sort field");
});
},
);
const totalPages = computed(() => Math.ceil(containers.length / perPage)); const totalPages = computed(() => Math.ceil(sortedContainers.value.length / perPage));
const isPaginated = computed(() => totalPages.value > 1); const isPaginated = computed(() => totalPages.value > 1);
const currentPage = ref(1); const currentPage = ref(1);
const paginated = computed(() => { const paginated = computed(() => {
const start = (currentPage.value - 1) * perPage; const start = (currentPage.value - 1) * perPage;
const end = start + perPage; const end = start + perPage;
return containers.slice(start, end); return sortedContainers.value.slice(start, end);
}); });
function sort(field: keyof typeof headers) {
if (sortField.value === field) {
direction.value *= -1;
} else {
sortField.value = field;
direction.value = 1;
}
}
</script> </script>
<style lang="scss" scoped></style> <style lang="scss" scoped>
.icon {
display: none;
transition: transform 0.2s ease-in-out;
[data-direction="desc"] & {
transform: rotate(180deg);
}
}
.selected-sort {
font-weight: bold;
border-color: var(--primary-color);
.icon {
display: inline-block;
}
}
</style>

View File

@@ -44,7 +44,7 @@ const memoryData = computedWithControl(
value: formatBytes(stat.snapshot.memoryUsage), value: formatBytes(stat.snapshot.memoryUsage),
})); }));
return points; return points;
} },
); );
</script> </script>

View File

@@ -1,4 +1,4 @@
<!DOCTYPE html> <!doctype html>
<html> <html>
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />