Statistics styling
This commit is contained in:
parent
6c47c5b178
commit
3ce2ba5013
6 changed files with 198 additions and 19 deletions
|
|
@ -1,7 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
import {RouterLink, RouterView} from "vue-router";
|
||||
import PageHeader from "@/components/PageHeader.vue";
|
||||
import PageFooter from "@/components/PageFooter.vue";
|
||||
import PageHeader from "@/components/page/PageHeader.vue";
|
||||
import PageFooter from "@/components/page/PageFooter.vue";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
|||
113
frontend/src/components/admin/StatisticsCard.vue
Normal file
113
frontend/src/components/admin/StatisticsCard.vue
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
<script setup lang="ts">
|
||||
import {computed, defineProps} from "vue";
|
||||
|
||||
const {
|
||||
title,
|
||||
icon,
|
||||
variant,
|
||||
value,
|
||||
link,
|
||||
filter,
|
||||
} = defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
variant: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
value: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
link: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
filter: {
|
||||
type: Object,
|
||||
required: false,
|
||||
},
|
||||
});
|
||||
|
||||
const linkTarget = computed(() => {
|
||||
if (filter) {
|
||||
const json = JSON.stringify(filter);
|
||||
return `${link}?search=${encodeURIComponent(json)}`;
|
||||
} else {
|
||||
return link;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<RouterLink :to="linkTarget" :class="['statistics-card', 'statistics-card-' + variant]">
|
||||
<i :class="['fa', 'fa-' + icon]" aria-hidden="true" />
|
||||
<dl>
|
||||
<dt>{{ title }}</dt>
|
||||
<dd>{{ value }}</dd>
|
||||
</dl>
|
||||
</RouterLink>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../scss/variables";
|
||||
|
||||
.statistics-card {
|
||||
display: flex;
|
||||
|
||||
min-height: $statistics-card-height;
|
||||
|
||||
margin: $statistics-card-margin;
|
||||
padding: $statistics-card-padding;
|
||||
|
||||
border-radius: $statistics-card-border-radius;
|
||||
|
||||
@each $variant, $color in $variant-colors {
|
||||
&.statistics-card-#{$variant} {
|
||||
background-color: $color;
|
||||
color: map-get($variant-text-colors, $variant);
|
||||
}
|
||||
}
|
||||
|
||||
i {
|
||||
display: inline-block;
|
||||
margin-right: $statistics-card-icon-gap;
|
||||
font-size: $statistics-card-icon-size;
|
||||
width: 1em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
dl {
|
||||
position: relative;
|
||||
flex-grow: 1;
|
||||
|
||||
box-sizing: border-box;
|
||||
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
|
||||
text-align: right;
|
||||
|
||||
dt {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
dd {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
|
||||
font-weight: bold;
|
||||
font-size: $statistics-card-value-font-size;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
56
frontend/src/components/page/PageFooter.vue
Normal file
56
frontend/src/components/page/PageFooter.vue
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
<script setup lang="ts">
|
||||
import {useConfigStore} from "@/stores/config";
|
||||
import {useVersionStore} from "@/stores/version";
|
||||
|
||||
const config = useConfigStore();
|
||||
const version = useVersionStore();
|
||||
|
||||
function refresh(): void {
|
||||
config.refresh();
|
||||
version.refresh();
|
||||
}
|
||||
|
||||
refresh();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<footer v-if="config.getConfig">
|
||||
ffffng ({{ version.getVersion }})
|
||||
<a href="https://github.com/freifunkhamburg/ffffng" target="_blank">
|
||||
<i class="fa fa-code" aria-hidden="true" /> Source Code
|
||||
</a>
|
||||
<a href="https://github.com/freifunkhamburg/ffffng/issues" target="_blank">
|
||||
<i class="fa fa-bug" aria-hidden="true" /> Fehler melden
|
||||
</a>
|
||||
<a
|
||||
v-if="config.getConfig.legal.privacyUrl"
|
||||
:href="config.getConfig.legal.privacyUrl"
|
||||
target="_blank">Datenschutz</a>
|
||||
<a
|
||||
v-if="config.getConfig.legal.imprintUrl"
|
||||
:href="config.getConfig.legal.imprintUrl"
|
||||
target="_blank">Impressum</a>
|
||||
</footer>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@use "sass:math";
|
||||
@import "../../scss/variables";
|
||||
|
||||
footer {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
|
||||
text-align: center;
|
||||
|
||||
background-color: $nav-bar-background-color;
|
||||
padding: $nav-footer-padding;
|
||||
|
||||
a {
|
||||
color: $nav-link-color;
|
||||
padding: 0 math.div($nav-link-spacing, 2);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
76
frontend/src/components/page/PageHeader.vue
Normal file
76
frontend/src/components/page/PageHeader.vue
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
<script setup lang="ts">
|
||||
import { useConfigStore } from "@/stores/config";
|
||||
|
||||
const config = useConfigStore();
|
||||
|
||||
function refresh(): void {
|
||||
config.refresh();
|
||||
}
|
||||
|
||||
refresh();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<header v-if="config.getConfig">
|
||||
<nav>
|
||||
<RouterLink class="logo" to="/">
|
||||
<!--suppress HtmlUnknownTarget -->
|
||||
<img
|
||||
src="icon.svg"
|
||||
alt="Symbol: Gelbes Zahnrad in zwei konzentrischen Kreisen (hellgrau und magenta)"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</RouterLink>
|
||||
|
||||
<h1>
|
||||
<RouterLink to="/">
|
||||
{{ config.getConfig.community.name }} – Knotenverwaltung
|
||||
</RouterLink>
|
||||
</h1>
|
||||
|
||||
<RouterLink class="admin-link" to="/admin">
|
||||
<i class="fa fa-wrench" aria-hidden="true" /> Admin-Panel
|
||||
</RouterLink>
|
||||
</nav>
|
||||
</header>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../scss/variables";
|
||||
|
||||
header {
|
||||
background-color: $nav-bar-background-color;
|
||||
padding: $nav-header-padding;
|
||||
|
||||
* {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.logo {
|
||||
padding: 0;
|
||||
|
||||
img {
|
||||
width: $nav-header-logo-size;
|
||||
height: $nav-header-logo-size;
|
||||
|
||||
margin: $nav-header-logo-margin;
|
||||
}
|
||||
}
|
||||
|
||||
h1 {
|
||||
display: inline;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
a {
|
||||
color: $nav-link-color;
|
||||
padding: 0 $nav-link-spacing 0 0;
|
||||
}
|
||||
|
||||
a.admin-link {
|
||||
display: inline-block;
|
||||
padding: 0;
|
||||
line-height: 2em;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,17 +1,34 @@
|
|||
// Grays
|
||||
$black: #000000;
|
||||
$gray-darkest: #222222;
|
||||
$gray-darker: #333333;
|
||||
$gray-dark: #444444;
|
||||
$gray: #666666;
|
||||
$gray-light: #d6d6d6;
|
||||
$gray-lighter: #ededed;
|
||||
$white: #ffffff;
|
||||
|
||||
// Colors
|
||||
$color-primary: #e5287a;
|
||||
$color-success: #449d44;
|
||||
$color-warning: #fdbc41;
|
||||
$color-danger: #c9302c;
|
||||
$color-info: #009ee0;
|
||||
$variant-colors: (
|
||||
primary: #e5287a,
|
||||
success: #4ba74b,
|
||||
warning: #fdbc41,
|
||||
danger: #ef5652,
|
||||
info: #009ee0,
|
||||
);
|
||||
$variant-text-colors: (
|
||||
// primary: do not use, contrast too low
|
||||
success: $gray-darkest,
|
||||
warning: $gray-darkest,
|
||||
danger: $gray-darkest,
|
||||
info: $gray-darkest,
|
||||
);
|
||||
|
||||
$variant-color-primary: map-get($variant-colors, primary);
|
||||
$variant-color-success: map-get($variant-colors, success);
|
||||
$variant-color-warning: map-get($variant-colors, warning);
|
||||
$variant-color-danger: map-get($variant-colors, danger);
|
||||
$variant-color-info: map-get($variant-colors, info);
|
||||
|
||||
// Page
|
||||
$page-background-color: $gray-darkest;
|
||||
|
|
@ -19,7 +36,7 @@ $page-text-color: $gray-lighter;
|
|||
$page-padding: 0.5em;
|
||||
|
||||
// Links
|
||||
$link-color: $color-warning;
|
||||
$link-color: $variant-color-warning;
|
||||
|
||||
// Navigation
|
||||
$nav-bar-background-color: $gray-darker;
|
||||
|
|
@ -31,3 +48,12 @@ $nav-header-logo-size: 2em;
|
|||
$nav-header-logo-margin: 0 0.5em 0 0;
|
||||
|
||||
$nav-footer-padding: 0.75em;
|
||||
|
||||
// Statistics
|
||||
$statistics-card-height: 4.5em;
|
||||
$statistics-card-margin: 0.5em;
|
||||
$statistics-card-padding: 0.3em 0.5em;
|
||||
$statistics-card-border-radius: 0.5em;
|
||||
$statistics-card-icon-size: 4em;
|
||||
$statistics-card-icon-gap: 0.15em;
|
||||
$statistics-card-value-font-size: 2.5em;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
import StatisticsCard from "@/components/admin/StatisticsCard.vue";
|
||||
import { useStatisticsStore } from "@/stores/statistics";
|
||||
|
||||
const statistics = useStatisticsStore();
|
||||
|
|
@ -13,18 +14,57 @@ refresh();
|
|||
<template>
|
||||
<main>
|
||||
<div v-if="statistics.getStatistics">
|
||||
<h1>Nodes</h1>
|
||||
<h2>Knotenstatistik</h2>
|
||||
|
||||
<div>
|
||||
Registered: {{ statistics.getStatistics.nodes.registered }}<br />
|
||||
With VPN-key: {{ statistics.getStatistics.nodes.withVPN }}<br />
|
||||
With coordinates: {{ statistics.getStatistics.nodes.withCoords }}<br />
|
||||
Monitoring active: {{ statistics.getStatistics.nodes.monitoring.active }}<br />
|
||||
Monitoring pending: {{ statistics.getStatistics.nodes.monitoring.pending }}
|
||||
<div class="statistics">
|
||||
<StatisticsCard
|
||||
title="Registrierte Knoten"
|
||||
icon="circle-o"
|
||||
variant="info"
|
||||
:value="statistics.getStatistics.nodes.registered"
|
||||
link="/admin/nodes"
|
||||
/>
|
||||
<StatisticsCard
|
||||
title="Mit hinterlegtem fastd-Key"
|
||||
icon="lock"
|
||||
variant="warning"
|
||||
:value="statistics.getStatistics.nodes.withVPN"
|
||||
link="/admin/nodes"
|
||||
:filter="{hasKey: true}"
|
||||
/>
|
||||
<StatisticsCard
|
||||
title="Mit Koordinaten"
|
||||
icon="map-marker"
|
||||
variant="success"
|
||||
:value="statistics.getStatistics.nodes.withCoords"
|
||||
link="/admin/nodes"
|
||||
:filter="{hasCoords: true}"
|
||||
/>
|
||||
<StatisticsCard
|
||||
title="Monitoring aktiv"
|
||||
icon="heartbeat"
|
||||
variant="success"
|
||||
:value="statistics.getStatistics.nodes.monitoring.active"
|
||||
link="/admin/nodes"
|
||||
:filter="{monitoringState: 'active'}"
|
||||
/>
|
||||
<StatisticsCard
|
||||
title="Monitoring noch nicht bestätigt"
|
||||
icon="envelope"
|
||||
variant="danger"
|
||||
:value="statistics.getStatistics.nodes.monitoring.pending"
|
||||
link="/admin/nodes"
|
||||
:filter="{monitoringState: 'pending'}"
|
||||
/>
|
||||
</div>
|
||||
<button @click="refresh()">Refresh</button>
|
||||
</div>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
<style lang="scss" scoped>
|
||||
.statistics {
|
||||
display: grid;
|
||||
// TODO: Responsive sizes
|
||||
grid-template-columns: repeat(auto-fill, minmax(25%, 100%));
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
Loading…
Reference in a new issue