Added paging.
This commit is contained in:
parent
956889be11
commit
87839f4faa
4 changed files with 176 additions and 19 deletions
113
frontend/src/components/Pager.vue
Normal file
113
frontend/src/components/Pager.vue
Normal file
|
@ -0,0 +1,113 @@
|
|||
<script setup lang="ts">
|
||||
import {computed, defineProps} from "vue";
|
||||
|
||||
const props = defineProps({
|
||||
page: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
itemsPerPage: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
totalItems: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const firstItem = computed(() => {
|
||||
console.log(props.totalItems, props.page, props.itemsPerPage);
|
||||
return Math.min(props.totalItems, (props.page - 1) * props.itemsPerPage + 1)
|
||||
});
|
||||
const lastItem = computed(() => Math.min(props.totalItems, firstItem.value + props.itemsPerPage - 1));
|
||||
const lastPage = computed(() => Math.ceil(props.totalItems / props.itemsPerPage));
|
||||
const pages = computed(() => {
|
||||
const pages: number[] = [];
|
||||
if (lastPage.value <= 2) {
|
||||
return pages;
|
||||
}
|
||||
|
||||
let p1 = props.page - 1;
|
||||
if (props.page === lastPage.value) {
|
||||
p1 = props.page - 2;
|
||||
}
|
||||
if (p1 <= 1) {
|
||||
p1 = 2;
|
||||
}
|
||||
|
||||
for (let p = p1; p <= p1 + 2; p += 1) {
|
||||
if (p < lastPage.value) {
|
||||
pages.push(p);
|
||||
}
|
||||
}
|
||||
|
||||
return pages;
|
||||
});
|
||||
|
||||
const showFirstEllipsis = computed(
|
||||
() => pages.value.length > 0 && pages.value[0] > 2
|
||||
);
|
||||
const showLastEllipsis = computed(
|
||||
() => pages.value.length > 0 && pages.value[pages.value.length - 1] < lastPage.value - 1
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "changePage", page: number): void,
|
||||
}>();
|
||||
|
||||
function toPage(page: number): void {
|
||||
emit("changePage", page);
|
||||
}
|
||||
|
||||
// noinspection JSIncompatibleTypesComparison
|
||||
const classes = computed(() => (p: number) => p === props.page ? ["current-page"] : []);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<nav>
|
||||
<span class="total">
|
||||
<strong>{{ firstItem }}</strong>
|
||||
-
|
||||
<strong>{{ lastItem }}</strong>
|
||||
von
|
||||
<strong>{{ totalItems }}</strong>
|
||||
</span>
|
||||
|
||||
<ul>
|
||||
<li v-if="page > 1" @click="toPage(page - 1)">‹</li>
|
||||
<li :class="classes(1)" @click="toPage(1)">1</li>
|
||||
<li v-if="showFirstEllipsis" class="ellipsis">…</li>
|
||||
<li v-for="page in pages" :class="classes(page)" @click="toPage(page)">{{ page }}</li>
|
||||
<li v-if="showLastEllipsis" class="ellipsis">…</li>
|
||||
<li v-if="lastPage > 1" :class="classes(lastPage)" @click="toPage(lastPage)">{{ lastPage }}</li>
|
||||
<li v-if="page < lastPage" @click="toPage(page + 1)">›</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../scss/variables";
|
||||
|
||||
ul {
|
||||
display: inline-block;
|
||||
list-style: none;
|
||||
|
||||
li {
|
||||
display: inline-block;
|
||||
min-width: 2em;
|
||||
height: 1.5em;
|
||||
line-height: 1.5em;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
|
||||
&.current-page {
|
||||
color: $variant-color-info;
|
||||
}
|
||||
|
||||
&.ellipsis {
|
||||
cursor: initial;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -4,6 +4,8 @@ import {internalApi} from "@/utils/Api";
|
|||
|
||||
interface NodesStoreState {
|
||||
nodes: EnhancedNode[];
|
||||
page: number;
|
||||
nodesPerPage: number;
|
||||
totalNodes: number;
|
||||
}
|
||||
|
||||
|
@ -12,6 +14,8 @@ export const useNodesStore = defineStore({
|
|||
state(): NodesStoreState {
|
||||
return {
|
||||
nodes: [],
|
||||
page: 1,
|
||||
nodesPerPage: 20,
|
||||
totalNodes: 0,
|
||||
};
|
||||
},
|
||||
|
@ -22,16 +26,29 @@ export const useNodesStore = defineStore({
|
|||
|
||||
getTotalNodes(state: NodesStoreState): number {
|
||||
return state.totalNodes;
|
||||
}
|
||||
},
|
||||
|
||||
getNodesPerPage(state: NodesStoreState): number {
|
||||
return state.nodesPerPage
|
||||
},
|
||||
|
||||
getPage(state: NodesStoreState): number {
|
||||
return state.page
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
async refresh(): Promise<void> {
|
||||
async refresh(page: number, nodesPerPage: number): Promise<void> {
|
||||
// TODO: Handle paging
|
||||
const result = await internalApi.getPagedList<EnhancedNode>(
|
||||
"nodes",
|
||||
isEnhancedNode
|
||||
isEnhancedNode,
|
||||
page,
|
||||
nodesPerPage,
|
||||
);
|
||||
this.nodes = result.entries;
|
||||
this.totalNodes = result.total;
|
||||
this.page = page;
|
||||
this.nodesPerPage = nodesPerPage;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
@ -23,12 +23,22 @@ class Api {
|
|||
}
|
||||
}
|
||||
|
||||
private toURL(path: string): string {
|
||||
return this.baseURL + this.apiPrefix + path;
|
||||
private toURL(path: string, queryParams?: object): string {
|
||||
let queryString = "";
|
||||
if (queryParams) {
|
||||
const queryStrings: string[] = [];
|
||||
for (const [key, value] of Object.entries(queryParams)) {
|
||||
queryStrings.push(`${encodeURIComponent(key)}=${encodeURIComponent(value)}`);
|
||||
}
|
||||
if (queryStrings.length > 0) {
|
||||
queryString = `?${queryStrings.join("&")}`;
|
||||
}
|
||||
}
|
||||
return this.baseURL + this.apiPrefix + path + queryString;
|
||||
}
|
||||
|
||||
private async doGet<T>(path: string, isT: TypeGuard<T>): Promise<Response<T>> {
|
||||
const url = this.toURL(path);
|
||||
private async doGet<T>(path: string, isT: TypeGuard<T>, queryParams?: object): Promise<Response<T>> {
|
||||
const url = this.toURL(path, queryParams);
|
||||
const result = await fetch(url);
|
||||
const json = await result.json();
|
||||
|
||||
|
@ -48,8 +58,16 @@ class Api {
|
|||
return response.result;
|
||||
}
|
||||
|
||||
async getPagedList<T>(path: string, isT: TypeGuard<T>): Promise<PagedListResult<T>> {
|
||||
const response = await this.doGet(path, toIsArray(isT));
|
||||
async getPagedList<T>(
|
||||
path: string,
|
||||
isT: TypeGuard<T>,
|
||||
page: number,
|
||||
itemsPerPage: number,
|
||||
): Promise<PagedListResult<T>> {
|
||||
const response = await this.doGet(path, toIsArray(isT), {
|
||||
_page: page,
|
||||
_perPage: itemsPerPage,
|
||||
});
|
||||
const totalStr = response.headers.get("x-total-count");
|
||||
const total = parseInteger(totalStr, 10);
|
||||
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
<script setup lang="ts">
|
||||
import {useNodesStore} from "@/stores/nodes";
|
||||
import {ref} from "vue";
|
||||
import {onMounted, ref} from "vue";
|
||||
import type {EnhancedNode, MAC} from "@/types";
|
||||
import Pager from "@/components/Pager.vue";
|
||||
|
||||
type NodeRedactField = "nickname" | "email" | "token";
|
||||
type NodeRedactFieldsMap = Partial<Record<NodeRedactField, boolean>>;
|
||||
type NodesRedactFieldsMap = Partial<Record<MAC, NodeRedactFieldsMap>>;
|
||||
|
||||
const nodes = useNodesStore();
|
||||
const page = ref(0);
|
||||
const redactFieldsByDefault = ref(true);
|
||||
const nodesRedactFieldsMap = ref({} as NodesRedactFieldsMap)
|
||||
|
||||
function refresh(): void {
|
||||
nodes.refresh();
|
||||
async function refresh(page: number): Promise<void> {
|
||||
await nodes.refresh(page, 50);
|
||||
}
|
||||
|
||||
function redactAllFields(shallRedactFields: boolean): void {
|
||||
|
@ -39,11 +39,7 @@ function setRedactField(node: EnhancedNode, field: NodeRedactField, value: boole
|
|||
redactFieldsMap[field] = value;
|
||||
}
|
||||
|
||||
function hasOnlineState(node: EnhancedNode): boolean {
|
||||
return node.onlineState !== undefined;
|
||||
}
|
||||
|
||||
refresh();
|
||||
onMounted(async () => await refresh(1));
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -62,6 +58,13 @@ refresh();
|
|||
Sensible Daten ausblenden
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<Pager
|
||||
:page="nodes.getPage"
|
||||
:itemsPerPage="nodes.getNodesPerPage"
|
||||
:totalItems="nodes.getTotalNodes"
|
||||
@changePage="refresh" />
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
|
@ -82,7 +85,7 @@ refresh();
|
|||
<tbody>
|
||||
<tr
|
||||
v-for="node in nodes.getNodes"
|
||||
:class="[hasOnlineState ? node.onlineState.toLowerCase() : 'online-state-unknown']">
|
||||
:class="[node.onlineState ? node.onlineState.toLowerCase() : 'online-state-unknown']">
|
||||
<td>{{node.hostname}}</td>
|
||||
<td v-if="shallRedactField(node, 'nickname')">
|
||||
<span
|
||||
|
@ -175,6 +178,12 @@ refresh();
|
|||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<Pager
|
||||
:page="nodes.getPage"
|
||||
:itemsPerPage="nodes.getNodesPerPage"
|
||||
:totalItems="nodes.getTotalNodes"
|
||||
@changePage="refresh" />
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
|
Loading…
Reference in a new issue