Add checkbox for monitoring when creating node.
This commit is contained in:
parent
f80657e4e0
commit
ac6fb7b57a
7 changed files with 338 additions and 58 deletions
95
frontend/src/components/BaseCard.vue
Normal file
95
frontend/src/components/BaseCard.vue
Normal file
|
@ -0,0 +1,95 @@
|
|||
<script setup lang="ts">
|
||||
import { onMounted, ref } from "vue";
|
||||
import type { ComponentVariant } from "@/types";
|
||||
|
||||
interface Props {
|
||||
variant: ComponentVariant;
|
||||
icon: string;
|
||||
scrollIntoView: boolean;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
const element = ref<HTMLElement>();
|
||||
|
||||
function scrollIntoView() {
|
||||
element.value?.scrollIntoView({
|
||||
behavior: "smooth",
|
||||
block: "nearest",
|
||||
});
|
||||
}
|
||||
|
||||
if (props.scrollIntoView) {
|
||||
onMounted(scrollIntoView);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div ref="element" :class="['card', props.variant]">
|
||||
<i :class="['icon', 'fa', `fa-${props.icon}`]" aria-hidden="true" />
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import "../scss/variables";
|
||||
|
||||
.card {
|
||||
position: relative;
|
||||
clear: both;
|
||||
|
||||
min-height: $base-card-min-height;
|
||||
|
||||
margin: $base-card-margin;
|
||||
padding: $base-card-padding;
|
||||
|
||||
border: {
|
||||
width: $base-card-border-width;
|
||||
style: $base-card-border-style;
|
||||
radius: $base-card-border-radius;
|
||||
}
|
||||
|
||||
font-weight: $base-card-font-weight;
|
||||
|
||||
*:nth-child(2) {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
*:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.icon {
|
||||
float: left;
|
||||
margin: $base-card-icon-margin;
|
||||
font-size: $base-card-icon-size;
|
||||
}
|
||||
|
||||
a:focus {
|
||||
outline: {
|
||||
width: $base-card-link-focus-outline-width;
|
||||
style: $base-card-link-focus-outline-style;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@each $variant, $color in $variant-colors {
|
||||
.card.#{$variant} {
|
||||
border-color: $color;
|
||||
|
||||
background-color: darken($color, 30%);
|
||||
color: lighten($color, 30%);
|
||||
|
||||
a {
|
||||
color: darken($color, 30%);
|
||||
|
||||
&:hover {
|
||||
color: darken($color, 30%);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline-color: darken($color, 30%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,49 +1,16 @@
|
|||
<script setup lang="ts">
|
||||
import { onMounted, ref } from "vue";
|
||||
|
||||
const element = ref<HTMLElement>();
|
||||
|
||||
function scrollIntoView() {
|
||||
element.value?.scrollIntoView({
|
||||
behavior: "smooth",
|
||||
block: "nearest",
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(scrollIntoView);
|
||||
import BaseCard from "@/components/BaseCard.vue";
|
||||
import { ComponentVariant } from "@/types";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div ref="element" class="error-card">
|
||||
<slot></slot>
|
||||
</div>
|
||||
<BaseCard
|
||||
:variant="ComponentVariant.DANGER"
|
||||
icon="warning"
|
||||
:scrollIntoView="true"
|
||||
>
|
||||
<slot />
|
||||
</BaseCard>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import "../scss/variables";
|
||||
|
||||
.error-card {
|
||||
margin: $error-card-margin;
|
||||
padding: $error-card-padding;
|
||||
|
||||
border: $error-card-border;
|
||||
border-radius: $error-card-border-radius;
|
||||
|
||||
font-weight: $error-card-font-weight;
|
||||
|
||||
background-color: $error-card-background-color;
|
||||
color: $error-card-color;
|
||||
|
||||
a {
|
||||
color: $error-card-link-color;
|
||||
|
||||
&:hover {
|
||||
color: $error-card-link-hover-color;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: $error-card-link-focus-outline;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<style lang="scss"></style>
|
||||
|
|
16
frontend/src/components/InfoCard.vue
Normal file
16
frontend/src/components/InfoCard.vue
Normal file
|
@ -0,0 +1,16 @@
|
|||
<script setup lang="ts">
|
||||
import BaseCard from "@/components/BaseCard.vue";
|
||||
import { ComponentVariant } from "@/types";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<BaseCard
|
||||
:variant="ComponentVariant.INFO"
|
||||
icon="info-circle"
|
||||
:scrollIntoView="false"
|
||||
>
|
||||
<slot />
|
||||
</BaseCard>
|
||||
</template>
|
||||
|
||||
<style lang="scss"></style>
|
135
frontend/src/components/form/CheckboxInput.vue
Normal file
135
frontend/src/components/form/CheckboxInput.vue
Normal file
|
@ -0,0 +1,135 @@
|
|||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
import ExpandableHelpBox from "@/components/ExpandableHelpBox.vue";
|
||||
|
||||
interface Props {
|
||||
name: string;
|
||||
modelValue?: boolean;
|
||||
label: string;
|
||||
help?: string;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
const emit = defineEmits<{
|
||||
(e: "update:modelValue", value: boolean): void;
|
||||
}>();
|
||||
|
||||
const input = ref<HTMLInputElement>();
|
||||
const focussed = ref(false);
|
||||
|
||||
function onChange() {
|
||||
const element = input.value;
|
||||
if (!element) {
|
||||
console.warn("Could not get referenced input element.");
|
||||
return;
|
||||
}
|
||||
emit("update:modelValue", element.checked);
|
||||
}
|
||||
|
||||
function onFocus() {
|
||||
focussed.value = true;
|
||||
}
|
||||
|
||||
function onBlur() {
|
||||
focussed.value = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
:class="{
|
||||
'checkbox-input': true,
|
||||
checked: modelValue,
|
||||
focussed,
|
||||
}"
|
||||
>
|
||||
<label>
|
||||
<input
|
||||
ref="input"
|
||||
type="checkbox"
|
||||
:name="props.name"
|
||||
:checked="props.modelValue"
|
||||
@change="onChange"
|
||||
@focus="onFocus"
|
||||
@blur="onBlur"
|
||||
/>
|
||||
{{ props.label }}
|
||||
<ExpandableHelpBox v-if="props.help" :text="props.help" />
|
||||
</label>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "../../scss/variables";
|
||||
|
||||
.checkbox-input {
|
||||
margin: $validation-form-input-margin;
|
||||
}
|
||||
|
||||
input {
|
||||
// hide the default checkbox and then rebuild a new one via label::before
|
||||
z-index: -1;
|
||||
|
||||
position: absolute;
|
||||
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
width: 0;
|
||||
height: 0;
|
||||
|
||||
opacity: 0;
|
||||
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
label {
|
||||
position: relative;
|
||||
clear: both;
|
||||
|
||||
display: block;
|
||||
width: 100%;
|
||||
font-weight: $label-font-weight;
|
||||
cursor: pointer;
|
||||
|
||||
&::before {
|
||||
float: left;
|
||||
|
||||
display: block;
|
||||
vertical-align: top;
|
||||
|
||||
content: "";
|
||||
|
||||
margin: $checkbox-margin;
|
||||
|
||||
width: $checkbox-size;
|
||||
height: $checkbox-size;
|
||||
|
||||
background-color: $input-background-color;
|
||||
border-radius: $checkbox-border-radius;
|
||||
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.checked &::before {
|
||||
// TODO: Fallback if font does not load?
|
||||
content: "\f00c";
|
||||
|
||||
//noinspection CssNoGenericFontName
|
||||
font-family: ForkAwesome;
|
||||
|
||||
text-align: center;
|
||||
line-height: $checkbox-size;
|
||||
|
||||
font-weight: bold;
|
||||
|
||||
color: $input-text-color;
|
||||
}
|
||||
|
||||
.focussed &::before {
|
||||
outline: $input-outline;
|
||||
outline-offset: $input-outline-offset;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -3,11 +3,13 @@ import { computed, getCurrentInstance, onMounted, ref } from "vue";
|
|||
import { type Constraint, forConstraint } from "@/shared/validation/validator";
|
||||
import ExpandableHelpBox from "@/components/ExpandableHelpBox.vue";
|
||||
|
||||
type InputType = "text" | "number" | "password" | "email" | "tel" | "url";
|
||||
|
||||
interface Props {
|
||||
name: string;
|
||||
modelValue?: string;
|
||||
label?: string;
|
||||
type?: string;
|
||||
type?: InputType;
|
||||
placeholder: string;
|
||||
constraint: Constraint;
|
||||
validationError: string;
|
||||
|
|
|
@ -27,6 +27,8 @@ import { route, RouteName } from "@/router";
|
|||
import RouteButton from "@/components/form/RouteButton.vue";
|
||||
import { ApiError } from "@/utils/Api";
|
||||
import NodeCoordinatesInput from "@/components/nodes/NodeCoordinatesInput.vue";
|
||||
import CheckboxInput from "@/components/form/CheckboxInput.vue";
|
||||
import InfoCard from "@/components/InfoCard.vue";
|
||||
|
||||
const configStore = useConfigStore();
|
||||
const nodeStore = useNodeStore();
|
||||
|
@ -211,8 +213,57 @@ async function onSubmit() {
|
|||
/>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<h3>TODO: Monitoring</h3>
|
||||
<fieldset v-if="configStore.getConfig.monitoring.enabled">
|
||||
<h3>Möchtest Du automatisiert Status-E-Mails bekommen?</h3>
|
||||
|
||||
<i class="monitoring-icon fa fa-heartbeat" aria-hidden="true" />
|
||||
|
||||
<p class="help-block">
|
||||
Du kannst Dich automatisiert benachrichtigen lassen, sobald
|
||||
Dein Knoten längere Zeit offline ist. Die erste E-Mail
|
||||
bekommst Du nach 3 Stunden, nach einem Tag gibt es dann
|
||||
nochmal eine Erinnerung. Sollte Dein Knoten nach einer Woche
|
||||
immernoch offline sein, bekommst Du eine letzte
|
||||
Status-E-Mail.
|
||||
</p>
|
||||
|
||||
<p class="help-block">
|
||||
Du kannst den automatisierten Versand von Status-E-Mails
|
||||
hier selbstverständlich jederzeit wieder deaktivieren.
|
||||
</p>
|
||||
|
||||
<CheckboxInput
|
||||
v-model="monitoringModel"
|
||||
name="monitoring"
|
||||
label="Informiert mich, wenn mein Knoten offline ist"
|
||||
/>
|
||||
|
||||
<InfoCard v-if="monitoringModel">
|
||||
<p>
|
||||
Zur Bestätigung Deiner E-Mail-Adresse schicken wir Dir
|
||||
nach dem Speichern Deiner Knotendaten eine E-Mail mit
|
||||
einem Bestätigungs-Link. Erst nach der Bestätigung
|
||||
deiner E-Mail-Adresse wirst Du informiert, falls Dein
|
||||
Knoten längere Zeit offline ist.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Die Inbetriebnahme Deines Knotens kannst Du
|
||||
selbstverständlich unabhängig von der Bestätigung immer
|
||||
sofort duchführen.
|
||||
</p>
|
||||
|
||||
<p v-if="emailModel">
|
||||
<strong>
|
||||
<i
|
||||
class="fa fa-envelope-o"
|
||||
aria-hidden="true"
|
||||
title="E-Mail-Adresse"
|
||||
/>
|
||||
{{ emailModel }}
|
||||
</strong>
|
||||
</p>
|
||||
</InfoCard>
|
||||
</fieldset>
|
||||
|
||||
<h1>TODO: Check community bounds</h1>
|
||||
|
@ -243,4 +294,13 @@ async function onSubmit() {
|
|||
</ValidationForm>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
<style lang="scss" scoped>
|
||||
@import "../../scss/variables";
|
||||
|
||||
.monitoring-icon {
|
||||
float: left;
|
||||
margin: 0 0.25em 0.125em 0;
|
||||
font-size: 3em;
|
||||
color: $variant-color-primary;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -99,6 +99,10 @@ $input-border-radius: 0.5em;
|
|||
$input-outline: 0.1em solid $variant-color-info;
|
||||
$input-outline-offset: 0.125em;
|
||||
|
||||
$checkbox-size: 1.25em;
|
||||
$checkbox-margin: 0 0.5em 0 0;
|
||||
$checkbox-border-radius: 0.25em;
|
||||
|
||||
$button-margin: 0.3em;
|
||||
$button-padding: 0.25em 0.5em;
|
||||
$button-border-radius: $input-border-radius;
|
||||
|
@ -115,17 +119,18 @@ $button-sizes: (
|
|||
$help-icon-color: $variant-color-info;
|
||||
$help-text-margin: 0.5em 0 0.75em 0;
|
||||
|
||||
// Error cards
|
||||
$error-card-margin: 1em 0;
|
||||
$error-card-padding: 0.5em;
|
||||
$error-card-border: 0.1em solid $variant-color-danger;
|
||||
$error-card-border-radius: 0.5em;
|
||||
$error-card-font-weight: 600;
|
||||
$error-card-background-color: lighten($variant-color-danger, 10%);
|
||||
$error-card-color: $page-background-color;
|
||||
$error-card-link-color: darken($variant-color-danger, 30%);
|
||||
$error-card-link-hover-color: $error-card-link-color;
|
||||
$error-card-link-focus-outline: 0.1em solid $error-card-link-hover-color;
|
||||
// Cards
|
||||
$base-card-margin: 1em 0;
|
||||
$base-card-padding: 1em 0.5em;
|
||||
$base-card-border-width: 0.1em;
|
||||
$base-card-border-style: solid;
|
||||
$base-card-border-radius: 0.5em;
|
||||
$base-card-font-weight: normal;
|
||||
$base-card-link-focus-outline-width: 0.1em;
|
||||
$base-card-link-focus-outline-style: solid;
|
||||
$base-card-icon-margin: 0 0.25em 0.125em 0;
|
||||
$base-card-icon-size: 2em;
|
||||
$base-card-min-height: $base-card-icon-size;
|
||||
|
||||
// Node map
|
||||
$node-map-aspect-ratio: math.div(16, 10);
|
||||
|
|
Loading…
Reference in a new issue