230 lines
7.2 KiB
Vue
230 lines
7.2 KiB
Vue
<script setup lang="ts">
|
|
import {useNodesStore} from "@/stores/nodes";
|
|
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 redactFieldsByDefault = ref(true);
|
|
const nodesRedactFieldsMap = ref({} as NodesRedactFieldsMap)
|
|
|
|
async function refresh(page: number): Promise<void> {
|
|
await nodes.refresh(page, 50);
|
|
}
|
|
|
|
function redactAllFields(shallRedactFields: boolean): void {
|
|
redactFieldsByDefault.value = shallRedactFields;
|
|
nodesRedactFieldsMap.value = {};
|
|
}
|
|
|
|
function shallRedactField(node: EnhancedNode, field: NodeRedactField): boolean {
|
|
const redactFieldsMap = nodesRedactFieldsMap.value[node.mac];
|
|
if (!redactFieldsMap) {
|
|
return redactFieldsByDefault.value;
|
|
}
|
|
const redactField = redactFieldsMap[field];
|
|
return redactField === undefined ? redactFieldsByDefault.value : redactField;
|
|
}
|
|
|
|
function setRedactField(node: EnhancedNode, field: NodeRedactField, value: boolean): void {
|
|
let redactFieldsMap = nodesRedactFieldsMap.value[node.mac];
|
|
if (!redactFieldsMap) {
|
|
redactFieldsMap = {};
|
|
nodesRedactFieldsMap.value[node.mac] = redactFieldsMap;
|
|
}
|
|
redactFieldsMap[field] = value;
|
|
}
|
|
|
|
onMounted(async () => await refresh(1));
|
|
</script>
|
|
|
|
<template>
|
|
<h2>Knoten</h2>
|
|
|
|
<div>
|
|
<span>Gesamt: {{nodes.getTotalNodes}}</span>
|
|
<button
|
|
v-if="redactFieldsByDefault"
|
|
@click="redactAllFields(false)">
|
|
Sensible Daten einblenden
|
|
</button>
|
|
<button
|
|
v-if="!redactFieldsByDefault"
|
|
@click="redactAllFields(true)">
|
|
Sensible Daten ausblenden
|
|
</button>
|
|
</div>
|
|
|
|
<Pager
|
|
:page="nodes.getPage"
|
|
:itemsPerPage="nodes.getNodesPerPage"
|
|
:totalItems="nodes.getTotalNodes"
|
|
@changePage="refresh" />
|
|
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>Name</th>
|
|
<th>Besitzer*in</th>
|
|
<th>E-Mail</th>
|
|
<th>Token</th>
|
|
<th>MAC</th>
|
|
<th>VPN</th>
|
|
<th>Site</th>
|
|
<th>Domäne</th>
|
|
<th>GPS</th>
|
|
<th>Status</th>
|
|
<th>Monitoring</th>
|
|
</tr>
|
|
</thead>
|
|
|
|
<tbody>
|
|
<tr
|
|
v-for="node in nodes.getNodes"
|
|
:class="[node.onlineState ? node.onlineState.toLowerCase() : 'online-state-unknown']">
|
|
<td>{{node.hostname}}</td>
|
|
<td v-if="shallRedactField(node, 'nickname')">
|
|
<span
|
|
class="redacted"
|
|
@click="setRedactField(node, 'nickname', false)">
|
|
nickname
|
|
</span>
|
|
</td>
|
|
<td v-if="!shallRedactField(node, 'nickname')">
|
|
<span
|
|
class="redactable"
|
|
@click="setRedactField(node, 'nickname', true)">
|
|
{{node.nickname}}
|
|
</span>
|
|
</td>
|
|
<td v-if="shallRedactField(node, 'email')">
|
|
<span
|
|
class="redacted"
|
|
@click="setRedactField(node, 'email', false)">
|
|
email@example.com
|
|
</span>
|
|
</td>
|
|
<td v-if="!shallRedactField(node, 'email')">
|
|
<span
|
|
class="redactable"
|
|
@click="setRedactField(node, 'email', true)">
|
|
{{node.email}}
|
|
</span>
|
|
</td>
|
|
<td v-if="shallRedactField(node, 'token')">
|
|
<span
|
|
class="redacted"
|
|
@click="setRedactField(node, 'token', false)">
|
|
0123456789abcdef
|
|
</span>
|
|
</td>
|
|
<td v-if="!shallRedactField(node, 'token')">
|
|
<span
|
|
class="redactable"
|
|
@click="setRedactField(node, 'token', true)">
|
|
{{node.token}}
|
|
</span>
|
|
</td>
|
|
<td>{{node.mac}}</td>
|
|
<td class="icon">
|
|
<i
|
|
v-if="node.key"
|
|
class="fa fa-lock"
|
|
aria-hidden="true"
|
|
title="Hat VPN-Schlüssel" />
|
|
<i
|
|
v-if="!node.key"
|
|
class="fa fa-times not-available"
|
|
aria-hidden="true"
|
|
title="Hat keinen VPN-Schlüssel" />
|
|
</td>
|
|
<td>{{node.site}}</td>
|
|
<td>{{node.domain}}</td>
|
|
<td class="icon">
|
|
<i
|
|
v-if="node.coords"
|
|
class="fa fa-map-marker"
|
|
aria-hidden="true"
|
|
title="Hat Koordinaten" />
|
|
<i
|
|
v-if="!node.coords"
|
|
class="fa fa-times not-available"
|
|
aria-hidden="true"
|
|
title="Hat keinen Koordinaten" />
|
|
</td>
|
|
<td v-if="node.onlineState !== undefined">{{node.onlineState.toLowerCase()}}</td>
|
|
<td v-if="node.onlineState === undefined">unbekannt</td>
|
|
<td class="icon">
|
|
<i
|
|
v-if="node.monitoring && node.monitoringConfirmed"
|
|
class="fa fa-heartbeat"
|
|
aria-hidden="true"
|
|
title="Monitoring aktiv" />
|
|
<i
|
|
v-if="node.monitoring && !node.monitoringConfirmed"
|
|
class="fa fa-envelope"
|
|
aria-hidden="true"
|
|
title="Monitoring nicht bestätigt" />
|
|
<i
|
|
v-if="!node.monitoring"
|
|
class="fa fa-times not-available"
|
|
aria-hidden="true"
|
|
title="Monitoring deaktiviert" />
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
|
|
<Pager
|
|
:page="nodes.getPage"
|
|
:itemsPerPage="nodes.getNodesPerPage"
|
|
:totalItems="nodes.getTotalNodes"
|
|
@changePage="refresh" />
|
|
</template>
|
|
|
|
<style lang="scss" scoped>
|
|
@import "../scss/variables";
|
|
table {
|
|
border-collapse: collapse;
|
|
|
|
th, td {
|
|
padding: 0.5em 0.25em;
|
|
}
|
|
|
|
td {
|
|
border-top: 1px solid $gray-light;
|
|
}
|
|
|
|
.online {
|
|
color: $variant-color-success;
|
|
}
|
|
|
|
.offline {
|
|
color: $variant-color-danger;
|
|
}
|
|
|
|
.online-state-unknown {
|
|
color: $variant-color-warning;
|
|
}
|
|
|
|
.icon {
|
|
text-align: center;
|
|
}
|
|
|
|
.redacted, .redactable {
|
|
cursor: pointer;
|
|
}
|
|
|
|
.redacted {
|
|
filter: blur(0.2em);
|
|
}
|
|
|
|
.not-available {
|
|
color: $gray-dark
|
|
}
|
|
}
|
|
</style>
|