webapp: Implement BasePage component for default views

This commit is contained in:
Thomas Basler 2022-10-18 18:25:50 +02:00
parent 93512e2e0c
commit 03066af1c4
14 changed files with 795 additions and 864 deletions

View File

@ -0,0 +1,28 @@
<template>
<div class="container-xxl" role="main">
<div class="page-header">
<h1>{{ title }}</h1>
</div>
<div class="text-center" v-if="isLoading">
<div class="spinner-border" role="status">
<span class="visually-hidden">Loading...</span>
</div>
</div>
<template v-if="!isLoading">
<slot />
</template>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
props: {
title: { type: String, required: true },
isLoading: { type: Boolean, required: false, default: false }
},
});
</script>

View File

@ -1,10 +1,5 @@
<template> <template>
<div class="container-xxl" role="main"> <BasePage :title="'About OpenDTU'">
<div class="page-header">
<h1>About OpenDTU</h1>
</div>
<div class="accordion" id="accordionExample"> <div class="accordion" id="accordionExample">
<div class="accordion-item"> <div class="accordion-item">
<h2 class="accordion-header" id="headingOne"> <h2 class="accordion-header" id="headingOne">
@ -96,12 +91,13 @@
</div> </div>
</div> </BasePage>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import BasePage from '@/components/BasePage.vue';
import { import {
BIconInfoCircle, BIconInfoCircle,
BIconActivity, BIconActivity,
@ -111,6 +107,7 @@ import {
export default defineComponent({ export default defineComponent({
components: { components: {
BasePage,
BIconInfoCircle, BIconInfoCircle,
BIconActivity, BIconActivity,
BIconBug, BIconBug,

View File

@ -1,121 +1,110 @@
<template> <template>
<div class="container-xxl" role="main"> <BasePage :title="'Config Management'" :isLoading="loading">
<div class="page-header">
<h1>Config Management</h1>
</div>
<BootstrapAlert v-model="showAlert" dismissible :variant="alertType"> <BootstrapAlert v-model="showAlert" dismissible :variant="alertType">
{{ alertMessage }} {{ alertMessage }}
</BootstrapAlert> </BootstrapAlert>
<div class="text-center" v-if="loading"> <div class="card">
<div class="spinner-border" role="status"> <div class="card-header text-white bg-primary">Backup: Configuration File Backup</div>
<span class="visually-hidden">Loading...</span> <div class="card-body text-center">
Backup the configuration file
<button class="btn btn-primary" @click="downloadConfig">Backup
</button>
</div> </div>
</div> </div>
<template v-if="!loading"> <div class="card mt-5">
<div class="card"> <div class="card-header text-white bg-primary">Restore: Restore the Configuration File</div>
<div class="card-header text-white bg-primary">Backup: Configuration File Backup</div> <div class="card-body text-center">
<div class="card-body text-center">
Backup the configuration file <div v-if="!uploading && UploadError != ''">
<button class="btn btn-primary" @click="downloadConfig">Backup <p class="h1 mb-2">
<BIconExclamationCircleFill />
</p>
<span style="vertical-align: middle" class="ml-2">
{{ UploadError }}
</span>
<br />
<br />
<button class="btn btn-light" @click="clear">
<BIconArrowLeft /> Back
</button> </button>
</div> </div>
</div>
<div class="card mt-5"> <div v-else-if="!uploading && UploadSuccess">
<div class="card-header text-white bg-primary">Restore: Restore the Configuration File</div> <span class="h1 mb-2">
<div class="card-body text-center"> <BIconCheckCircle />
</span>
<div v-if="!uploading && UploadError != ''"> <span> Upload Success </span>
<p class="h1 mb-2"> <br />
<BIconExclamationCircleFill /> <br />
</p> <button class="btn btn-primary" @click="clear">
<span style="vertical-align: middle" class="ml-2"> <BIconArrowLeft /> Back
{{ UploadError }}
</span>
<br />
<br />
<button class="btn btn-light" @click="clear">
<BIconArrowLeft /> Back
</button>
</div>
<div v-else-if="!uploading && UploadSuccess">
<span class="h1 mb-2">
<BIconCheckCircle />
</span>
<span> Upload Success </span>
<br />
<br />
<button class="btn btn-primary" @click="clear">
<BIconArrowLeft /> Back
</button>
</div>
<div v-else-if="!uploading">
<div class="form-group pt-2 mt-3">
<input class="form-control" type="file" ref="file" accept=".json" @change="uploadConfig" />
</div>
</div>
<div v-else-if="uploading">
<div class="progress">
<div class="progress-bar" role="progressbar" :style="{ width: progress + '%' }"
v-bind:aria-valuenow="progress" aria-valuemin="0" aria-valuemax="100">
{{ progress }}%
</div>
</div>
</div>
<div class="alert alert-danger mt-3" role="alert">
<b>Note:</b> This operation replaces the configuration file with the restored configuration and
restarts OpenDTU to apply all settings.
</div>
</div>
</div>
<div class="card mt-5">
<div class="card-header text-white bg-primary">Initialize: Perform Factory Reset</div>
<div class="card-body text-center">
<button class="btn btn-danger" @click="onFactoryResetModal">Restore Factory-Default Settings
</button> </button>
</div>
<div class="alert alert-danger mt-3" role="alert"> <div v-else-if="!uploading">
<b>Note:</b> Click Restore Factory-Default Settings to restore and initialize the <div class="form-group pt-2 mt-3">
factory-default settings and reboot. <input class="form-control" type="file" ref="file" accept=".json" @change="uploadConfig" />
</div> </div>
</div> </div>
</div>
</template>
<div class="modal" id="factoryReset" tabindex="-1"> <div v-else-if="uploading">
<div class="modal-dialog"> <div class="progress">
<div class="modal-content"> <div class="progress-bar" role="progressbar" :style="{ width: progress + '%' }"
<div class="modal-header"> v-bind:aria-valuenow="progress" aria-valuemin="0" aria-valuemax="100">
<h5 class="modal-title">Factory Reset</h5> {{ progress }}%
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> </div>
</div>
<div class="modal-body">
Are you sure you want to delete the current configuration and reset all settings to their
factory defaults?
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" @click="onFactoryResetCancel"
data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-danger" @click="onFactoryResetPerform">Factory
Reset!</button>
</div> </div>
</div> </div>
<div class="alert alert-danger mt-3" role="alert">
<b>Note:</b> This operation replaces the configuration file with the restored configuration and
restarts OpenDTU to apply all settings.
</div>
</div> </div>
</div> </div>
<div class="card mt-5">
<div class="card-header text-white bg-primary">Initialize: Perform Factory Reset</div>
<div class="card-body text-center">
<button class="btn btn-danger" @click="onFactoryResetModal">Restore Factory-Default Settings
</button>
<div class="alert alert-danger mt-3" role="alert">
<b>Note:</b> Click Restore Factory-Default Settings to restore and initialize the
factory-default settings and reboot.
</div>
</div>
</div>
</BasePage>
<div class="modal" id="factoryReset" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Factory Reset</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
Are you sure you want to delete the current configuration and reset all settings to their
factory defaults?
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" @click="onFactoryResetCancel"
data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-danger" @click="onFactoryResetPerform">Factory
Reset!</button>
</div>
</div>
</div>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import BasePage from '@/components/BasePage.vue';
import { import {
BIconExclamationCircleFill, BIconExclamationCircleFill,
BIconArrowLeft, BIconArrowLeft,
@ -126,6 +115,7 @@ import BootstrapAlert from "@/components/BootstrapAlert.vue";
export default defineComponent({ export default defineComponent({
components: { components: {
BasePage,
BIconExclamationCircleFill, BIconExclamationCircleFill,
BIconArrowLeft, BIconArrowLeft,
BIconCheckCircle, BIconCheckCircle,

View File

@ -1,68 +1,59 @@
<template> <template>
<div class="container-xxl" role="main"> <BasePage :title="'DTU Settings'" :isLoading="dataLoading">
<div class="page-header">
<h1>DTU Settings</h1>
</div>
<BootstrapAlert v-model="showAlert" dismissible :variant="alertType"> <BootstrapAlert v-model="showAlert" dismissible :variant="alertType">
{{ alertMessage }} {{ alertMessage }}
</BootstrapAlert> </BootstrapAlert>
<div class="text-center" v-if="dataLoading"> <form @submit="saveDtuConfig">
<div class="spinner-border" role="status"> <div class="card">
<span class="visually-hidden">Loading...</span> <div class="card-header text-white bg-primary">DTU Configuration</div>
</div> <div class="card-body">
</div> <div class="row mb-3">
<label for="inputDtuSerial" class="col-sm-2 col-form-label">Serial:</label>
<template v-if="!dataLoading"> <div class="col-sm-10">
<form @submit="saveDtuConfig"> <input type="number" class="form-control" id="inputDtuSerial" min="1" max="99999999999"
<div class="card"> placeholder="DTU Serial" v-model="dtuConfigList.dtu_serial" />
<div class="card-header text-white bg-primary">DTU Configuration</div>
<div class="card-body">
<div class="row mb-3">
<label for="inputDtuSerial" class="col-sm-2 col-form-label">Serial:</label>
<div class="col-sm-10">
<input type="number" class="form-control" id="inputDtuSerial" min="1" max="99999999999"
placeholder="DTU Serial" v-model="dtuConfigList.dtu_serial" />
</div>
</div> </div>
</div>
<div class="row mb-3"> <div class="row mb-3">
<label for="inputPollInterval" class="col-sm-2 col-form-label">Poll Interval:</label> <label for="inputPollInterval" class="col-sm-2 col-form-label">Poll Interval:</label>
<div class="col-sm-10"> <div class="col-sm-10">
<div class="input-group"> <div class="input-group">
<input type="number" class="form-control" id="inputPollInterval" min="1" max="86400" <input type="number" class="form-control" id="inputPollInterval" min="1" max="86400"
placeholder="Poll Interval in Seconds" v-model="dtuConfigList.dtu_pollinterval" placeholder="Poll Interval in Seconds" v-model="dtuConfigList.dtu_pollinterval"
aria-describedby="pollIntervalDescription" /> aria-describedby="pollIntervalDescription" />
<span class="input-group-text" id="pollIntervalDescription">seconds</span> <span class="input-group-text" id="pollIntervalDescription">seconds</span>
</div>
</div>
</div>
<div class="row mb-3">
<label for="inputTimezone" class="col-sm-2 col-form-label">PA Level:</label>
<div class="col-sm-10">
<select class="form-select" v-model="dtuConfigList.dtu_palevel">
<option v-for="palevel in palevelList" :key="palevel.key" :value="palevel.key">
{{ palevel.value }}
</option>
</select>
</div> </div>
</div> </div>
</div> </div>
<div class="row mb-3">
<label for="inputTimezone" class="col-sm-2 col-form-label">PA Level:</label>
<div class="col-sm-10">
<select class="form-select" v-model="dtuConfigList.dtu_palevel">
<option v-for="palevel in palevelList" :key="palevel.key" :value="palevel.key">
{{ palevel.value }}
</option>
</select>
</div>
</div>
</div> </div>
<button type="submit" class="btn btn-primary mb-3">Save</button> </div>
</form> <button type="submit" class="btn btn-primary mb-3">Save</button>
</template> </form>
</div> </BasePage>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import BasePage from '@/components/BasePage.vue';
import BootstrapAlert from "@/components/BootstrapAlert.vue"; import BootstrapAlert from "@/components/BootstrapAlert.vue";
import type { DtuConfig } from "@/types/DtuConfig"; import type { DtuConfig } from "@/types/DtuConfig";
export default defineComponent({ export default defineComponent({
components: { components: {
BasePage,
BootstrapAlert, BootstrapAlert,
}, },
data() { data() {

View File

@ -1,9 +1,5 @@
<template> <template>
<div class="container-xxl" role="main"> <BasePage :title="'Firmware Upgrade'">
<div class="page-header">
<h1>Firmware Upgrade</h1>
</div>
<div class="position-relative" v-if="loading"> <div class="position-relative" v-if="loading">
<div class="position-absolute top-50 start-50 translate-middle"> <div class="position-absolute top-50 start-50 translate-middle">
<div class="spinner-border" role="status"> <div class="spinner-border" role="status">
@ -68,11 +64,12 @@
</div> </div>
</div> </div>
</div> </div>
</div> </BasePage>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import BasePage from '@/components/BasePage.vue';
import SparkMD5 from "spark-md5"; import SparkMD5 from "spark-md5";
import { import {
BIconExclamationCircleFill, BIconExclamationCircleFill,
@ -83,6 +80,7 @@ import {
export default defineComponent({ export default defineComponent({
components: { components: {
BasePage,
BIconExclamationCircleFill, BIconExclamationCircleFill,
BIconArrowLeft, BIconArrowLeft,
BIconArrowRepeat, BIconArrowRepeat,

View File

@ -1,9 +1,5 @@
<template> <template>
<div class="container-xxl" role="main"> <BasePage :title="'Inverter Settings'" :isLoading="dataLoading">
<div class="page-header">
<h1>Inverter Settings</h1>
</div>
<BootstrapAlert v-model="showAlert" dismissible :variant="alertType"> <BootstrapAlert v-model="showAlert" dismissible :variant="alertType">
{{ alertMessage }} {{ alertMessage }}
</BootstrapAlert> </BootstrapAlert>
@ -72,80 +68,79 @@
</div> </div>
</div> </div>
</div> </div>
</BasePage>
<div class="modal" id="inverterEdit" tabindex="-1"> <div class="modal" id="inverterEdit" tabindex="-1">
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title">Edit Inverter</h5> <h5 class="modal-title">Edit Inverter</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<form> <form>
<div class="mb-3"> <div class="mb-3">
<label for="inverter-serial" class="col-form-label">Serial:</label> <label for="inverter-serial" class="col-form-label">Serial:</label>
<input v-model="editInverterData.serial" type="number" id="inverter-serial" <input v-model="editInverterData.serial" type="number" id="inverter-serial"
class="form-control" /> class="form-control" />
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label for="inverter-name" class="col-form-label">Name:</label> <label for="inverter-name" class="col-form-label">Name:</label>
<input v-model="editInverterData.name" type="text" id="inverter-name" <input v-model="editInverterData.name" type="text" id="inverter-name" class="form-control"
class="form-control" maxlength="31" /> maxlength="31" />
</div>
<div class="mb-3" v-for="(max, index) in editInverterData.max_power" :key="`${index}`">
<label :for="`inverter-max_${index}`" class="col-form-label">Max power string {{ index +
1
}}:</label>
<div class="input-group">
<input type="number" class="form-control" :id="`inverter-max_${index}`" min="0"
v-model="editInverterData.max_power[index]"
:aria-describedby="`inverter-maxDescription_${index} inverter-maxHelpText_${index}`" />
<span class="input-group-text" :id="`inverter-maxDescription_${index}`">W</span>
</div> </div>
<div :id="`inverter-maxHelpText_${index}`" class="form-text">This value is used to
calculate the Irradiation.</div>
</div>
</form>
<div class="mb-3" v-for="(max, index) in editInverterData.max_power" :key="`${index}`"> </div>
<label :for="`inverter-max_${index}`" class="col-form-label">Max power string {{ index + <div class="modal-footer">
1 <button type="button" class="btn btn-secondary" @click="onCancel"
}}:</label> data-bs-dismiss="modal">Cancel</button>
<div class="input-group"> <button type="button" class="btn btn-primary" @click="onEditSubmit(editId)">Save
<input type="number" class="form-control" :id="`inverter-max_${index}`" min="0" changes</button>
v-model="editInverterData.max_power[index]"
:aria-describedby="`inverter-maxDescription_${index} inverter-maxHelpText_${index}`" />
<span class="input-group-text" :id="`inverter-maxDescription_${index}`">W</span>
</div>
<div :id="`inverter-maxHelpText_${index}`" class="form-text">This value is used to
calculate the Irradiation.</div>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" @click="onCancel"
data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary" @click="onEditSubmit(editId)">Save
changes</button>
</div>
</div> </div>
</div> </div>
</div> </div>
</div>
<div class="modal" id="inverterDelete" tabindex="-1"> <div class="modal" id="inverterDelete" tabindex="-1">
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title">Delete Inverter</h5> <h5 class="modal-title">Delete Inverter</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
Are you sure you want to delete the inverter "{{ deleteInverterData.name }}" with serial number Are you sure you want to delete the inverter "{{ deleteInverterData.name }}" with serial number
{{ deleteInverterData.serial }}? {{ deleteInverterData.serial }}?
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-secondary" @click="onDeleteCancel" <button type="button" class="btn btn-secondary" @click="onDeleteCancel"
data-bs-dismiss="modal">Cancel</button> data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-danger" <button type="button" class="btn btn-danger" @click="onDelete(deleteId.toString())">Delete</button>
@click="onDelete(deleteId.toString())">Delete</button>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import BasePage from '@/components/BasePage.vue';
import { import {
BIconTrash, BIconTrash,
BIconPencil BIconPencil
@ -163,6 +158,7 @@ declare interface Inverter {
export default defineComponent({ export default defineComponent({
components: { components: {
BasePage,
BootstrapAlert, BootstrapAlert,
BIconTrash, BIconTrash,
BIconPencil, BIconPencil,
@ -177,6 +173,7 @@ export default defineComponent({
editInverterData: {} as Inverter, editInverterData: {} as Inverter,
deleteInverterData: {} as Inverter, deleteInverterData: {} as Inverter,
inverters: [] as Inverter[], inverters: [] as Inverter[],
dataLoading: true,
alertMessage: "", alertMessage: "",
alertType: "info", alertType: "info",
showAlert: false, showAlert: false,
@ -198,9 +195,13 @@ export default defineComponent({
}, },
methods: { methods: {
getInverters() { getInverters() {
this.dataLoading = true;
fetch("/api/inverter/list") fetch("/api/inverter/list")
.then((response) => response.json()) .then((response) => response.json())
.then((data) => (this.inverters = data.inverter)); .then((data) => {
this.inverters = data.inverter;
this.dataLoading = false;
});
}, },
onSubmit() { onSubmit() {
const formData = new FormData(); const formData = new FormData();

View File

@ -1,237 +1,228 @@
<template> <template>
<div class="container-xxl" role="main"> <BasePage :title="'MqTT Settings'" :isLoading="dataLoading">
<div class="page-header">
<h1>MqTT Settings</h1>
</div>
<BootstrapAlert v-model="showAlert" dismissible :variant="alertType"> <BootstrapAlert v-model="showAlert" dismissible :variant="alertType">
{{ alertMessage }} {{ alertMessage }}
</BootstrapAlert> </BootstrapAlert>
<div class="text-center" v-if="dataLoading"> <form @submit="saveMqttConfig">
<div class="spinner-border" role="status"> <div class="card">
<span class="visually-hidden">Loading...</span> <div class="card-header text-white bg-primary">MqTT Configuration</div>
<div class="card-body">
<div class="row mb-3">
<label class="col-sm-4 form-check-label" for="inputMqtt">Enable MqTT</label>
<div class="col-sm-8">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" id="inputMqtt"
v-model="mqttConfigList.mqtt_enabled" />
</div>
</div>
</div>
<div class="row mb-3" v-show="mqttConfigList.mqtt_enabled">
<label class="col-sm-4 form-check-label" for="inputMqttHass">Enable Home Assistant MQTT Auto
Discovery</label>
<div class="col-sm-8">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" id="inputMqttHass"
v-model="mqttConfigList.mqtt_hass_enabled" />
</div>
</div>
</div>
</div>
</div> </div>
</div>
<template v-if="!dataLoading"> <div class="card mt-5" v-show="mqttConfigList.mqtt_enabled">
<form @submit="saveMqttConfig"> <div class="card-header text-white bg-primary">
<div class="card"> MqTT Broker Parameter
<div class="card-header text-white bg-primary">MqTT Configuration</div>
<div class="card-body">
<div class="row mb-3">
<label class="col-sm-4 form-check-label" for="inputMqtt">Enable MqTT</label>
<div class="col-sm-8">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" id="inputMqtt"
v-model="mqttConfigList.mqtt_enabled" />
</div>
</div>
</div>
<div class="row mb-3" v-show="mqttConfigList.mqtt_enabled">
<label class="col-sm-4 form-check-label" for="inputMqttHass">Enable Home Assistant MQTT Auto
Discovery</label>
<div class="col-sm-8">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" id="inputMqttHass"
v-model="mqttConfigList.mqtt_hass_enabled" />
</div>
</div>
</div>
</div>
</div> </div>
<div class="card-body">
<div class="card mt-5" v-show="mqttConfigList.mqtt_enabled"> <div class="row mb-3">
<div class="card-header text-white bg-primary"> <label for="inputHostname" class="col-sm-2 col-form-label">Hostname:</label>
MqTT Broker Parameter <div class="col-sm-10">
<input type="text" class="form-control" id="inputHostname" maxlength="128"
placeholder="Hostname or IP address" v-model="mqttConfigList.mqtt_hostname" />
</div>
</div> </div>
<div class="card-body">
<div class="row mb-3"> <div class="row mb-3">
<label for="inputHostname" class="col-sm-2 col-form-label">Hostname:</label> <label for="inputPort" class="col-sm-2 col-form-label">Port:</label>
<div class="col-sm-10"> <div class="col-sm-10">
<input type="text" class="form-control" id="inputHostname" maxlength="128" <input type="number" class="form-control" id="inputPort" min="1" max="65535"
placeholder="Hostname or IP address" v-model="mqttConfigList.mqtt_hostname" /> placeholder="Port number" v-model="mqttConfigList.mqtt_port" />
</div>
</div>
<div class="row mb-3">
<label for="inputUsername" class="col-sm-2 col-form-label">Username:</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="inputUsername" maxlength="32"
placeholder="Username, leave empty for anonymous connection"
v-model="mqttConfigList.mqtt_username" />
</div>
</div>
<div class="row mb-3">
<label for="inputPassword" class="col-sm-2 col-form-label">Password:</label>
<div class="col-sm-10">
<input type="password" class="form-control" id="inputPassword" maxlength="32"
placeholder="Password, leave empty for anonymous connection"
v-model="mqttConfigList.mqtt_password" />
</div>
</div>
<div class="row mb-3">
<label for="inputTopic" class="col-sm-2 col-form-label">Base Topic:</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="inputTopic" maxlength="32"
placeholder="Base topic, will be prepend to all published topics (e.g. inverter/)"
v-model="mqttConfigList.mqtt_topic" />
</div>
</div>
<div class="row mb-3">
<label for="inputPublishInterval" class="col-sm-2 col-form-label">Publish Interval:</label>
<div class="col-sm-10">
<div class="input-group">
<input type="number" class="form-control" id="inputPublishInterval" min="5" max="86400"
placeholder="Publish Interval in Seconds"
v-model="mqttConfigList.mqtt_publish_interval"
aria-describedby="publishIntervalDescription" />
<span class="input-group-text" id="publishIntervalDescription">seconds</span>
</div> </div>
</div> </div>
</div>
<div class="row mb-3"> <div class="row mb-3">
<label for="inputPort" class="col-sm-2 col-form-label">Port:</label> <label class="col-sm-2 form-check-label" for="inputRetain">Enable Retain Flag</label>
<div class="col-sm-10"> <div class="col-sm-10">
<input type="number" class="form-control" id="inputPort" min="1" max="65535" <div class="form-check form-switch">
placeholder="Port number" v-model="mqttConfigList.mqtt_port" /> <input class="form-check-input" type="checkbox" id="inputRetain"
v-model="mqttConfigList.mqtt_retain" />
</div> </div>
</div> </div>
</div>
<div class="row mb-3"> <div class="row mb-3">
<label for="inputUsername" class="col-sm-2 col-form-label">Username:</label> <label class="col-sm-2 form-check-label" for="inputTls">Enable TLS</label>
<div class="col-sm-10"> <div class="col-sm-10">
<input type="text" class="form-control" id="inputUsername" maxlength="32" <div class="form-check form-switch">
placeholder="Username, leave empty for anonymous connection" <input class="form-check-input" type="checkbox" id="inputTls"
v-model="mqttConfigList.mqtt_username" /> v-model="mqttConfigList.mqtt_tls" />
</div> </div>
</div> </div>
</div>
<div class="row mb-3"> <div class="row mb-3" v-show="mqttConfigList.mqtt_tls">
<label for="inputPassword" class="col-sm-2 col-form-label">Password:</label> <label for="inputCert" class="col-sm-2 col-form-label">CA-Root-Certificate (default
<div class="col-sm-10"> Letsencrypt):</label>
<input type="password" class="form-control" id="inputPassword" maxlength="32" <div class="col-sm-10">
placeholder="Password, leave empty for anonymous connection" <textarea class="form-control" id="inputCert" maxlength="2048" rows="10"
v-model="mqttConfigList.mqtt_password" /> placeholder="Root CA Certificate from Letsencrypt"
</div> v-model="mqttConfigList.mqtt_root_ca_cert">
</div>
<div class="row mb-3">
<label for="inputTopic" class="col-sm-2 col-form-label">Base Topic:</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="inputTopic" maxlength="32"
placeholder="Base topic, will be prepend to all published topics (e.g. inverter/)"
v-model="mqttConfigList.mqtt_topic" />
</div>
</div>
<div class="row mb-3">
<label for="inputPublishInterval" class="col-sm-2 col-form-label">Publish Interval:</label>
<div class="col-sm-10">
<div class="input-group">
<input type="number" class="form-control" id="inputPublishInterval" min="5"
max="86400" placeholder="Publish Interval in Seconds"
v-model="mqttConfigList.mqtt_publish_interval"
aria-describedby="publishIntervalDescription" />
<span class="input-group-text" id="publishIntervalDescription">seconds</span>
</div>
</div>
</div>
<div class="row mb-3">
<label class="col-sm-2 form-check-label" for="inputRetain">Enable Retain Flag</label>
<div class="col-sm-10">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" id="inputRetain"
v-model="mqttConfigList.mqtt_retain" />
</div>
</div>
</div>
<div class="row mb-3">
<label class="col-sm-2 form-check-label" for="inputTls">Enable TLS</label>
<div class="col-sm-10">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" id="inputTls"
v-model="mqttConfigList.mqtt_tls" />
</div>
</div>
</div>
<div class="row mb-3" v-show="mqttConfigList.mqtt_tls">
<label for="inputCert" class="col-sm-2 col-form-label">CA-Root-Certificate (default
Letsencrypt):</label>
<div class="col-sm-10">
<textarea class="form-control" id="inputCert" maxlength="2048" rows="10"
placeholder="Root CA Certificate from Letsencrypt"
v-model="mqttConfigList.mqtt_root_ca_cert">
</textarea> </textarea>
</div>
</div> </div>
</div> </div>
</div> </div>
</div>
<div class="card mt-5" v-show="mqttConfigList.mqtt_enabled"> <div class="card mt-5" v-show="mqttConfigList.mqtt_enabled">
<div class="card-header text-white bg-primary">LWT Parameters</div> <div class="card-header text-white bg-primary">LWT Parameters</div>
<div class="card-body"> <div class="card-body">
<div class="row mb-3"> <div class="row mb-3">
<label for="inputLwtTopic" class="col-sm-2 col-form-label">LWT Topic:</label> <label for="inputLwtTopic" class="col-sm-2 col-form-label">LWT Topic:</label>
<div class="col-sm-10"> <div class="col-sm-10">
<div class="input-group"> <div class="input-group">
<span class="input-group-text" id="basic-addon3">{{ <span class="input-group-text" id="basic-addon3">{{
mqttConfigList.mqtt_topic mqttConfigList.mqtt_topic
}}</span> }}</span>
<input type="text" class="form-control" id="inputLwtTopic" maxlength="32" <input type="text" class="form-control" id="inputLwtTopic" maxlength="32"
placeholder="LWT topic, will be append base topic" placeholder="LWT topic, will be append base topic"
v-model="mqttConfigList.mqtt_lwt_topic" aria-describedby="basic-addon3" /> v-model="mqttConfigList.mqtt_lwt_topic" aria-describedby="basic-addon3" />
</div>
</div>
</div>
<div class="row mb-3">
<label for="inputLwtOnline" class="col-sm-2 col-form-label">LWT Online message:</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="inputLwtOnline" maxlength="20"
placeholder="Message that will be published to LWT topic when online"
v-model="mqttConfigList.mqtt_lwt_online" />
</div>
</div>
<div class="row mb-3">
<label for="inputLwtOffline" class="col-sm-2 col-form-label">LWT Offline message:</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="inputLwtOffline" maxlength="20"
placeholder="Message that will be published to LWT topic when offline"
v-model="mqttConfigList.mqtt_lwt_offline" />
</div> </div>
</div> </div>
</div> </div>
</div>
<div class="card mt-5" v-show="mqttConfigList.mqtt_enabled && mqttConfigList.mqtt_hass_enabled"> <div class="row mb-3">
<div class="card-header text-white bg-primary">Home Assistant MQTT Auto Discovery Parameters</div> <label for="inputLwtOnline" class="col-sm-2 col-form-label">LWT Online message:</label>
<div class="card-body"> <div class="col-sm-10">
<div class="row mb-3"> <input type="text" class="form-control" id="inputLwtOnline" maxlength="20"
<label for="inputHassTopic" class="col-sm-2 col-form-label">Prefix Topic:</label> placeholder="Message that will be published to LWT topic when online"
<div class="col-sm-10"> v-model="mqttConfigList.mqtt_lwt_online" />
<input type="text" class="form-control" id="inputHassTopic" maxlength="32"
placeholder="The prefix for the discovery topic"
v-model="mqttConfigList.mqtt_hass_topic" />
</div>
</div> </div>
</div>
<div class="row mb-3"> <div class="row mb-3">
<label class="col-sm-2 form-check-label" for="inputHassRetain">Enable Retain Flag</label> <label for="inputLwtOffline" class="col-sm-2 col-form-label">LWT Offline message:</label>
<div class="col-sm-10"> <div class="col-sm-10">
<div class="form-check form-switch"> <input type="text" class="form-control" id="inputLwtOffline" maxlength="20"
<input class="form-check-input" type="checkbox" id="inputHassRetain" placeholder="Message that will be published to LWT topic when offline"
v-model="mqttConfigList.mqtt_hass_retain" /> v-model="mqttConfigList.mqtt_lwt_offline" />
</div>
</div>
</div> </div>
<div class="row mb-3">
<label class="col-sm-2 form-check-label" for="inputHassExpire">Enable Expiration</label>
<div class="col-sm-10">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" id="inputHassExpire"
v-model="mqttConfigList.mqtt_hass_expire" />
</div>
</div>
</div>
<div class="row mb-3">
<label class="col-sm-2 form-check-label" for="inputIndividualPanels">Individual
Panels:</label>
<div class="col-sm-10">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" id="inputIndividualPanels"
v-model="mqttConfigList.mqtt_hass_individualpanels" />
</div>
</div>
</div>
</div> </div>
</div> </div>
</div>
<button type="submit" class="btn btn-primary mb-3">Save</button> <div class="card mt-5" v-show="mqttConfigList.mqtt_enabled && mqttConfigList.mqtt_hass_enabled">
</form> <div class="card-header text-white bg-primary">Home Assistant MQTT Auto Discovery Parameters</div>
</template> <div class="card-body">
</div> <div class="row mb-3">
<label for="inputHassTopic" class="col-sm-2 col-form-label">Prefix Topic:</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="inputHassTopic" maxlength="32"
placeholder="The prefix for the discovery topic"
v-model="mqttConfigList.mqtt_hass_topic" />
</div>
</div>
<div class="row mb-3">
<label class="col-sm-2 form-check-label" for="inputHassRetain">Enable Retain Flag</label>
<div class="col-sm-10">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" id="inputHassRetain"
v-model="mqttConfigList.mqtt_hass_retain" />
</div>
</div>
</div>
<div class="row mb-3">
<label class="col-sm-2 form-check-label" for="inputHassExpire">Enable Expiration</label>
<div class="col-sm-10">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" id="inputHassExpire"
v-model="mqttConfigList.mqtt_hass_expire" />
</div>
</div>
</div>
<div class="row mb-3">
<label class="col-sm-2 form-check-label" for="inputIndividualPanels">Individual
Panels:</label>
<div class="col-sm-10">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" id="inputIndividualPanels"
v-model="mqttConfigList.mqtt_hass_individualpanels" />
</div>
</div>
</div>
</div>
</div>
<button type="submit" class="btn btn-primary mb-3">Save</button>
</form>
</BasePage>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import BasePage from '@/components/BasePage.vue';
import BootstrapAlert from "@/components/BootstrapAlert.vue"; import BootstrapAlert from "@/components/BootstrapAlert.vue";
import type { MqttConfig } from "@/types/MqttConfig"; import type { MqttConfig } from "@/types/MqttConfig";
export default defineComponent({ export default defineComponent({
components: { components: {
BasePage,
BootstrapAlert, BootstrapAlert,
}, },
data() { data() {

View File

@ -1,168 +1,161 @@
<template> <template>
<div class="container-xxl" role="main"> <BasePage :title="'MqTT Info'" :isLoading="dataLoading">
<div class="page-header"> <div class="card">
<h1>MqTT Info</h1> <div class="card-header text-white bg-primary">Configuration Summary</div>
</div> <div class="card-body">
<div class="table-responsive">
<div class="text-center" v-if="dataLoading"> <table class="table table-hover table-condensed">
<div class="spinner-border" role="status"> <tbody>
<span class="visually-hidden">Loading...</span> <tr>
<th>Status</th>
<td class="badge" :class="{
'bg-danger': !mqttDataList.mqtt_enabled,
'bg-success': mqttDataList.mqtt_enabled,
}">
<span v-if="mqttDataList.mqtt_enabled">enabled</span>
<span v-else>disabled</span>
</td>
</tr>
<tr>
<th>Server</th>
<td>{{ mqttDataList.mqtt_hostname }}</td>
</tr>
<tr>
<th>Port</th>
<td>{{ mqttDataList.mqtt_port }}</td>
</tr>
<tr>
<th>Username</th>
<td>{{ mqttDataList.mqtt_username }}</td>
</tr>
<tr>
<th>Base Topic</th>
<td>{{ mqttDataList.mqtt_topic }}</td>
</tr>
<tr>
<th>Publish Interval</th>
<td>{{ mqttDataList.mqtt_publish_interval }} seconds</td>
</tr>
<tr>
<th>Retain</th>
<td class="badge" :class="{
'bg-danger': !mqttDataList.mqtt_retain,
'bg-success': mqttDataList.mqtt_retain,
}">
<span v-if="mqttDataList.mqtt_retain">enabled</span>
<span v-else>disabled</span>
</td>
</tr>
<tr>
<th>TLS</th>
<td class="badge" :class="{
'bg-danger': !mqttDataList.mqtt_tls,
'bg-success': mqttDataList.mqtt_tls,
}">
<span v-if="mqttDataList.mqtt_tls">enabled</span>
<span v-else>disabled</span>
</td>
</tr>
<tr v-show="mqttDataList.mqtt_tls">
<th>Root CA Certifcate Info</th>
<td>{{ mqttDataList.mqtt_root_ca_cert_info }}</td>
</tr>
</tbody>
</table>
</div>
</div> </div>
</div> </div>
<template v-if="!dataLoading"> <div class="card mt-5">
<div class="card"> <div class="card-header text-white bg-primary">Home Assistant MQTT Auto Discovery Configuration Summary
<div class="card-header text-white bg-primary">Configuration Summary</div> </div>
<div class="card-body"> <div class="card-body">
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-hover table-condensed"> <table class="table table-hover table-condensed">
<tbody> <tbody>
<tr> <tr>
<th>Status</th> <th>Status</th>
<td class="badge" :class="{ <td class="badge" :class="{
'bg-danger': !mqttDataList.mqtt_enabled, 'bg-danger': !mqttDataList.mqtt_hass_enabled,
'bg-success': mqttDataList.mqtt_enabled, 'bg-success': mqttDataList.mqtt_hass_enabled,
}"> }">
<span v-if="mqttDataList.mqtt_enabled">enabled</span> <span v-if="mqttDataList.mqtt_hass_enabled">enabled</span>
<span v-else>disabled</span> <span v-else>disabled</span>
</td> </td>
</tr> </tr>
<tr> <tr>
<th>Server</th> <th>Base Topic</th>
<td>{{ mqttDataList.mqtt_hostname }}</td> <td>{{ mqttDataList.mqtt_hass_topic }}</td>
</tr> </tr>
<tr> <tr>
<th>Port</th> <th>Retain</th>
<td>{{ mqttDataList.mqtt_port }}</td> <td class="badge" :class="{
</tr> 'bg-danger': !mqttDataList.mqtt_hass_retain,
<tr> 'bg-success': mqttDataList.mqtt_hass_retain,
<th>Username</th> }">
<td>{{ mqttDataList.mqtt_username }}</td> <span v-if="mqttDataList.mqtt_hass_retain">enabled</span>
</tr> <span v-else>disabled</span>
<tr> </td>
<th>Base Topic</th> </tr>
<td>{{ mqttDataList.mqtt_topic }}</td> <tr>
</tr> <th>Expire</th>
<tr> <td class="badge" :class="{
<th>Publish Interval</th> 'bg-danger': !mqttDataList.mqtt_hass_expire,
<td>{{ mqttDataList.mqtt_publish_interval }} seconds</td> 'bg-success': mqttDataList.mqtt_hass_expire,
</tr> }">
<tr> <span v-if="mqttDataList.mqtt_hass_expire">enabled</span>
<th>Retain</th> <span v-else>disabled</span>
<td class="badge" :class="{ </td>
'bg-danger': !mqttDataList.mqtt_retain, </tr>
'bg-success': mqttDataList.mqtt_retain, <tr>
}"> <th>Individual Panels</th>
<span v-if="mqttDataList.mqtt_retain">enabled</span> <td class="badge" :class="{
<span v-else>disabled</span> 'bg-danger': !mqttDataList.mqtt_hass_individualpanels,
</td> 'bg-success': mqttDataList.mqtt_hass_individualpanels,
</tr> }">
<tr> <span v-if="mqttDataList.mqtt_hass_individualpanels">enabled</span>
<th>TLS</th> <span v-else>disabled</span>
<td class="badge" :class="{ </td>
'bg-danger': !mqttDataList.mqtt_tls, </tr>
'bg-success': mqttDataList.mqtt_tls, </tbody>
}"> </table>
<span v-if="mqttDataList.mqtt_tls">enabled</span>
<span v-else>disabled</span>
</td>
</tr>
<tr v-show="mqttDataList.mqtt_tls">
<th>Root CA Certifcate Info</th>
<td>{{ mqttDataList.mqtt_root_ca_cert_info }}</td>
</tr>
</tbody>
</table>
</div>
</div> </div>
</div> </div>
</div>
<div class="card mt-5"> <div class="card mt-5">
<div class="card-header text-white bg-primary">Home Assistant MQTT Auto Discovery Configuration Summary</div> <div class="card-header text-white bg-primary">Runtime Summary</div>
<div class="card-body"> <div class="card-body">
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-hover table-condensed"> <table class="table table-hover table-condensed">
<tbody> <tbody>
<tr> <tr>
<th>Status</th> <th>Connection Status</th>
<td class="badge" :class="{ <td class="badge" :class="{
'bg-danger': !mqttDataList.mqtt_hass_enabled, 'bg-danger': !mqttDataList.mqtt_connected,
'bg-success': mqttDataList.mqtt_hass_enabled, 'bg-success': mqttDataList.mqtt_connected,
}"> }">
<span v-if="mqttDataList.mqtt_hass_enabled">enabled</span> <span v-if="mqttDataList.mqtt_connected">connected</span>
<span v-else>disabled</span> <span v-else>disconnected</span>
</td> </td>
</tr> </tr>
<tr> </tbody>
<th>Base Topic</th> </table>
<td>{{ mqttDataList.mqtt_hass_topic }}</td>
</tr>
<tr>
<th>Retain</th>
<td class="badge" :class="{
'bg-danger': !mqttDataList.mqtt_hass_retain,
'bg-success': mqttDataList.mqtt_hass_retain,
}">
<span v-if="mqttDataList.mqtt_hass_retain">enabled</span>
<span v-else>disabled</span>
</td>
</tr>
<tr>
<th>Expire</th>
<td class="badge" :class="{
'bg-danger': !mqttDataList.mqtt_hass_expire,
'bg-success': mqttDataList.mqtt_hass_expire,
}">
<span v-if="mqttDataList.mqtt_hass_expire">enabled</span>
<span v-else>disabled</span>
</td>
</tr>
<tr>
<th>Individual Panels</th>
<td class="badge" :class="{
'bg-danger': !mqttDataList.mqtt_hass_individualpanels,
'bg-success': mqttDataList.mqtt_hass_individualpanels,
}">
<span v-if="mqttDataList.mqtt_hass_individualpanels">enabled</span>
<span v-else>disabled</span>
</td>
</tr>
</tbody>
</table>
</div>
</div> </div>
</div> </div>
</div>
<div class="card mt-5"> </BasePage>
<div class="card-header text-white bg-primary">Runtime Summary</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover table-condensed">
<tbody>
<tr>
<th>Connection Status</th>
<td class="badge" :class="{
'bg-danger': !mqttDataList.mqtt_connected,
'bg-success': mqttDataList.mqtt_connected,
}">
<span v-if="mqttDataList.mqtt_connected">connected</span>
<span v-else>disconnected</span>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</template>
</div>
</template> </template>
<script lang="ts"> <script lang="ts">
import type { MqttStatus } from '@/types/MqttStatus';
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import BasePage from '@/components/BasePage.vue';
import type { MqttStatus } from '@/types/MqttStatus';
export default defineComponent({ export default defineComponent({
components: {
BasePage,
},
data() { data() {
return { return {
dataLoading: true, dataLoading: true,

View File

@ -1,123 +1,114 @@
<template> <template>
<div class="container-xxl" role="main"> <BasePage :title="'Network Settings'" :isLoading="dataLoading">
<div class="page-header">
<h1>Network Settings</h1>
</div>
<BootstrapAlert v-model="showAlert" dismissible :variant="alertType"> <BootstrapAlert v-model="showAlert" dismissible :variant="alertType">
{{ alertMessage }} {{ alertMessage }}
</BootstrapAlert> </BootstrapAlert>
<div class="text-center" v-if="dataLoading"> <form @submit="saveNetworkConfig">
<div class="spinner-border" role="status"> <div class="card">
<span class="visually-hidden">Loading...</span> <div class="card-header text-white bg-primary">WiFi Configuration</div>
<div class="card-body">
<div class="row mb-3">
<label for="inputSSID" class="col-sm-2 col-form-label">WiFi SSID:</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="inputSSID" maxlength="32" placeholder="SSID"
v-model="networkConfigList.ssid" />
</div>
</div>
<div class="row mb-3">
<label for="inputPassword" class="col-sm-2 col-form-label">WiFi Password:</label>
<div class="col-sm-10">
<input type="password" class="form-control" id="inputPassword" maxlength="64"
placeholder="PSK" v-model="networkConfigList.password" />
</div>
</div>
<div class="row mb-3">
<label for="inputHostname" class="col-sm-2 col-form-label">Hostname:</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="inputHostname" maxlength="32"
placeholder="Hostname" v-model="networkConfigList.hostname" />
<div class="alert alert-secondary" role="alert">
<b>Hint:</b> The text <span class="font-monospace">%06X</span> will be replaced
with the last 6 digits of the ESP ChipID in hex format.
</div>
</div>
</div>
<div class="row mb-3">
<label class="col-sm-2 form-check-label" for="inputDHCP">Enable DHCP</label>
<div class="col-sm-10">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" id="inputDHCP"
v-model="networkConfigList.dhcp" />
</div>
</div>
</div>
</div>
</div> </div>
</div>
<template v-if="!dataLoading"> <div class="card" v-show="!networkConfigList.dhcp">
<form @submit="saveNetworkConfig"> <div class="card-header text-white bg-primary">
<div class="card"> Static IP Configuration
<div class="card-header text-white bg-primary">WiFi Configuration</div> </div>
<div class="card-body"> <div class="card-body">
<div class="row mb-3"> <div class="row mb-3">
<label for="inputSSID" class="col-sm-2 col-form-label">WiFi SSID:</label> <label for="inputIP" class="col-sm-2 col-form-label">IP Address:</label>
<div class="col-sm-10"> <div class="col-sm-10">
<input type="text" class="form-control" id="inputSSID" maxlength="32" placeholder="SSID" <input type="text" class="form-control" id="inputIP" maxlength="32" placeholder="IP address"
v-model="networkConfigList.ssid" /> v-model="networkConfigList.ipaddress" />
</div>
</div> </div>
</div>
<div class="row mb-3"> <div class="row mb-3">
<label for="inputPassword" class="col-sm-2 col-form-label">WiFi Password:</label> <label for="inputNetmask" class="col-sm-2 col-form-label">Netmask:</label>
<div class="col-sm-10"> <div class="col-sm-10">
<input type="password" class="form-control" id="inputPassword" maxlength="64" <input type="text" class="form-control" id="inputNetmask" maxlength="32"
placeholder="PSK" v-model="networkConfigList.password" /> placeholder="Netmask" v-model="networkConfigList.netmask" />
</div>
</div> </div>
</div>
<div class="row mb-3"> <div class="row mb-3">
<label for="inputHostname" class="col-sm-2 col-form-label">Hostname:</label> <label for="inputGateway" class="col-sm-2 col-form-label">Default Gateway:</label>
<div class="col-sm-10"> <div class="col-sm-10">
<input type="text" class="form-control" id="inputHostname" maxlength="32" <input type="text" class="form-control" id="inputGateway" maxlength="32"
placeholder="Hostname" v-model="networkConfigList.hostname" /> placeholder="Default Gateway" v-model="networkConfigList.gateway" />
<div class="alert alert-secondary" role="alert">
<b>Hint:</b> The text <span class="font-monospace">%06X</span> will be replaced
with the last 6 digits of the ESP ChipID in hex format.
</div>
</div>
</div> </div>
</div>
<div class="row mb-3"> <div class="row mb-3">
<label class="col-sm-2 form-check-label" for="inputDHCP">Enable DHCP</label> <label for="inputDNS1" class="col-sm-2 col-form-label">DNS Server 1:</label>
<div class="col-sm-10"> <div class="col-sm-10">
<div class="form-check form-switch"> <input type="text" class="form-control" id="inputDNS1" maxlength="32"
<input class="form-check-input" type="checkbox" id="inputDHCP" placeholder="DNS Server 1" v-model="networkConfigList.dns1" />
v-model="networkConfigList.dhcp" /> </div>
</div> </div>
</div>
<div class="row mb-3">
<label for="inputDNS2" class="col-sm-2 col-form-label">DNS Server 2:</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="inputDNS2" maxlength="32"
placeholder="DNS Server 2" v-model="networkConfigList.dns2" />
</div> </div>
</div> </div>
</div> </div>
</div>
<div class="card" v-show="!networkConfigList.dhcp"> <button type="submit" class="btn btn-primary mb-3">Save</button>
<div class="card-header text-white bg-primary"> </form>
Static IP Configuration </BasePage>
</div>
<div class="card-body">
<div class="row mb-3">
<label for="inputIP" class="col-sm-2 col-form-label">IP Address:</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="inputIP" maxlength="32"
placeholder="IP address" v-model="networkConfigList.ipaddress" />
</div>
</div>
<div class="row mb-3">
<label for="inputNetmask" class="col-sm-2 col-form-label">Netmask:</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="inputNetmask" maxlength="32"
placeholder="Netmask" v-model="networkConfigList.netmask" />
</div>
</div>
<div class="row mb-3">
<label for="inputGateway" class="col-sm-2 col-form-label">Default Gateway:</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="inputGateway" maxlength="32"
placeholder="Default Gateway" v-model="networkConfigList.gateway" />
</div>
</div>
<div class="row mb-3">
<label for="inputDNS1" class="col-sm-2 col-form-label">DNS Server 1:</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="inputDNS1" maxlength="32"
placeholder="DNS Server 1" v-model="networkConfigList.dns1" />
</div>
</div>
<div class="row mb-3">
<label for="inputDNS2" class="col-sm-2 col-form-label">DNS Server 2:</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="inputDNS2" maxlength="32"
placeholder="DNS Server 2" v-model="networkConfigList.dns2" />
</div>
</div>
</div>
</div>
<button type="submit" class="btn btn-primary mb-3">Save</button>
</form>
</template>
</div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import BasePage from '@/components/BasePage.vue';
import BootstrapAlert from "@/components/BootstrapAlert.vue"; import BootstrapAlert from "@/components/BootstrapAlert.vue";
import type { NetworkConfig } from "@/types/NetworkkConfig"; import type { NetworkConfig } from "@/types/NetworkkConfig";
export default defineComponent({ export default defineComponent({
components: { components: {
BasePage,
BootstrapAlert, BootstrapAlert,
}, },
data() { data() {

View File

@ -1,29 +1,19 @@
<template> <template>
<div class="container-xxl" role="main"> <BasePage :title="'Network Info'" :isLoading="dataLoading">
<div class="page-header"> <WifiStationInfo :networkStatus="networkDataList" />
<h1>Network Info</h1> <div class="mt-5"></div>
</div> <WifiApInfo :networkStatus="networkDataList" />
<div class="text-center" v-if="dataLoading"> <div class="mt-5"></div>
<div class="spinner-border" role="status"> <InterfaceNetworkInfo :networkStatus="networkDataList" />
<span class="visually-hidden">Loading...</span> <div class="mt-5"></div>
</div> <InterfaceApInfo :networkStatus="networkDataList" />
</div> <div class="mt-5"></div>
</BasePage>
<template v-if="!dataLoading">
<WifiStationInfo :networkStatus="networkDataList" />
<div class="mt-5"></div>
<WifiApInfo :networkStatus="networkDataList" />
<div class="mt-5"></div>
<InterfaceNetworkInfo :networkStatus="networkDataList" />
<div class="mt-5"></div>
<InterfaceApInfo :networkStatus="networkDataList" />
<div class="mt-5"></div>
</template>
</div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import BasePage from '@/components/BasePage.vue';
import WifiStationInfo from "@/components/WifiStationInfo.vue"; import WifiStationInfo from "@/components/WifiStationInfo.vue";
import WifiApInfo from "@/components/WifiApInfo.vue"; import WifiApInfo from "@/components/WifiApInfo.vue";
import InterfaceNetworkInfo from "@/components/InterfaceNetworkInfo.vue"; import InterfaceNetworkInfo from "@/components/InterfaceNetworkInfo.vue";
@ -32,6 +22,7 @@ import type { NetworkStatus } from '@/types/NetworkStatus';
export default defineComponent({ export default defineComponent({
components: { components: {
BasePage,
WifiStationInfo, WifiStationInfo,
WifiApInfo, WifiApInfo,
InterfaceNetworkInfo, InterfaceNetworkInfo,

View File

@ -1,97 +1,85 @@
<template> <template>
<div class="container-xxl" role="main"> <BasePage :title="'NTP Settings'" :isLoading="dataLoading || timezoneLoading">
<div class="page-header">
<h1>NTP Settings</h1>
</div>
<BootstrapAlert v-model="showAlert" dismissible :variant="alertType"> <BootstrapAlert v-model="showAlert" dismissible :variant="alertType">
{{ alertMessage }} {{ alertMessage }}
</BootstrapAlert> </BootstrapAlert>
<div class="text-center" v-if="dataLoading || timezoneLoading"> <form @submit="saveNtpConfig">
<div class="spinner-border" role="status">
<span class="visually-hidden">Loading...</span>
</div>
</div>
<template v-if="!dataLoading && !timezoneLoading">
<form @submit="saveNtpConfig">
<div class="card">
<div class="card-header text-white bg-primary">NTP Configuration</div>
<div class="card-body">
<div class="row mb-3">
<label for="inputNtpServer" class="col-sm-2 col-form-label">Time Server:</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="inputNtpServer" maxlength="32"
placeholder="Time Server" v-model="ntpConfigList.ntp_server" />
</div>
</div>
<div class="row mb-3">
<label for="inputTimezone" class="col-sm-2 col-form-label">Timezone:</label>
<div class="col-sm-10">
<select class="form-select" v-model="timezoneSelect">
<option v-for="(config, name) in timezoneList" :key="name + '---' + config"
:value="name + '---' + config">
{{ name }}
</option>
</select>
</div>
</div>
<div class="row mb-3">
<label for="inputTimezoneConfig" class="col-sm-2 col-form-label">Timezone Config:</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="inputTimezoneConfig" maxlength="32"
placeholder="Timezone" v-model="ntpConfigList.ntp_timezone" disabled />
</div>
</div>
</div>
</div>
<button type="submit" class="btn btn-primary mb-3">Save</button>
</form>
</template>
<template v-if="!dataLoading && !timezoneLoading">
<div class="card"> <div class="card">
<div class="card-header text-white bg-primary">Manual Time Synchronization</div> <div class="card-header text-white bg-primary">NTP Configuration</div>
<div class="card-body"> <div class="card-body">
<div class="row mb-3"> <div class="row mb-3">
<label for="currentMcuTime" class="col-sm-2 col-form-label">Current OpenDTU Time:</label> <label for="inputNtpServer" class="col-sm-2 col-form-label">Time Server:</label>
<div class="col-sm-10"> <div class="col-sm-10">
<input type="text" class="form-control" id="currentMcuTime" v-model="mcuTime" disabled /> <input type="text" class="form-control" id="inputNtpServer" maxlength="32"
placeholder="Time Server" v-model="ntpConfigList.ntp_server" />
</div> </div>
</div> </div>
<div class="row mb-3">
<label for="currentLocalTime" class="col-sm-2 col-form-label">Current Local Time:</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="currentLocalTime" v-model="localTime"
disabled />
</div>
</div>
<div class="text-center mb-3">
<button type="button" class="btn btn-danger" @click="setCurrentTime()"
title="Synchronize Time">Synchronize Time
</button>
</div>
<div class="alert alert-secondary" role="alert">
<b>Hint:</b> You can use the manual time synchronization to set the current time of OpenDTU if
no NTP server is available. But be aware, that in case of power cycle the time gets lost. Also
the time accurancy can be very bad as it is not resynchronised regularly.
</div>
<div class="row mb-3">
<label for="inputTimezone" class="col-sm-2 col-form-label">Timezone:</label>
<div class="col-sm-10">
<select class="form-select" v-model="timezoneSelect">
<option v-for="(config, name) in timezoneList" :key="name + '---' + config"
:value="name + '---' + config">
{{ name }}
</option>
</select>
</div>
</div>
<div class="row mb-3">
<label for="inputTimezoneConfig" class="col-sm-2 col-form-label">Timezone Config:</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="inputTimezoneConfig" maxlength="32"
placeholder="Timezone" v-model="ntpConfigList.ntp_timezone" disabled />
</div>
</div>
</div> </div>
</div> </div>
</template> <button type="submit" class="btn btn-primary mb-3">Save</button>
</div> </form>
<div class="card">
<div class="card-header text-white bg-primary">Manual Time Synchronization</div>
<div class="card-body">
<div class="row mb-3">
<label for="currentMcuTime" class="col-sm-2 col-form-label">Current OpenDTU Time:</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="currentMcuTime" v-model="mcuTime" disabled />
</div>
</div>
<div class="row mb-3">
<label for="currentLocalTime" class="col-sm-2 col-form-label">Current Local Time:</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="currentLocalTime" v-model="localTime" disabled />
</div>
</div>
<div class="text-center mb-3">
<button type="button" class="btn btn-danger" @click="setCurrentTime()"
title="Synchronize Time">Synchronize Time
</button>
</div>
<div class="alert alert-secondary" role="alert">
<b>Hint:</b> You can use the manual time synchronization to set the current time of OpenDTU if
no NTP server is available. But be aware, that in case of power cycle the time gets lost. Also
the time accurancy can be very bad as it is not resynchronised regularly.
</div>
</div>
</div>
</BasePage>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import BasePage from '@/components/BasePage.vue';
import BootstrapAlert from "@/components/BootstrapAlert.vue"; import BootstrapAlert from "@/components/BootstrapAlert.vue";
import type { NtpConfig } from "@/types/NtpConfig"; import type { NtpConfig } from "@/types/NtpConfig";
export default defineComponent({ export default defineComponent({
components: { components: {
BasePage,
BootstrapAlert, BootstrapAlert,
}, },
data() { data() {

View File

@ -1,75 +1,66 @@
<template> <template>
<div class="container-xxl" role="main"> <BasePage :title="'NTP Info'" :isLoading="dataLoading">
<div class="page-header"> <div class="card">
<h1>NTP Info</h1> <div class="card-header text-white bg-primary">Configuration Summary</div>
</div> <div class="card-body">
<div class="table-responsive">
<div class="text-center" v-if="dataLoading"> <table class="table table-hover table-condensed">
<div class="spinner-border" role="status"> <tbody>
<span class="visually-hidden">Loading...</span> <tr>
<th>Server</th>
<td>{{ ntpDataList.ntp_server }}</td>
</tr>
<tr>
<th>Timezone</th>
<td>{{ ntpDataList.ntp_timezone }}</td>
</tr>
<tr>
<th>Timezone Description</th>
<td>{{ ntpDataList.ntp_timezone_descr }}</td>
</tr>
</tbody>
</table>
</div>
</div> </div>
</div> </div>
<template v-if="!dataLoading"> <div class="card mt-5">
<div class="card"> <div class="card-header text-white bg-primary">Current Time</div>
<div class="card-header text-white bg-primary">Configuration Summary</div> <div class="card-body">
<div class="card-body"> <div class="table-responsive">
<div class="table-responsive"> <table class="table table-hover table-condensed">
<table class="table table-hover table-condensed"> <tbody>
<tbody> <tr>
<tr> <th>Status</th>
<th>Server</th> <td class="badge" :class="{
<td>{{ ntpDataList.ntp_server }}</td> 'bg-danger': !ntpDataList.ntp_status,
</tr> 'bg-success': ntpDataList.ntp_status,
<tr> }">
<th>Timezone</th> <span v-if="ntpDataList.ntp_status">synced</span>
<td>{{ ntpDataList.ntp_timezone }}</td> <span v-else>not synced</span>
</tr> </td>
<tr> </tr>
<th>Timezone Description</th> <tr>
<td>{{ ntpDataList.ntp_timezone_descr }}</td> <th>Local Time</th>
</tr> <td>{{ ntpDataList.ntp_localtime }}</td>
</tbody> </tr>
</table> </tbody>
</div> </table>
</div> </div>
</div> </div>
</div>
<div class="card mt-5"> </BasePage>
<div class="card-header text-white bg-primary">Current Time</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover table-condensed">
<tbody>
<tr>
<th>Status</th>
<td class="badge" :class="{
'bg-danger': !ntpDataList.ntp_status,
'bg-success': ntpDataList.ntp_status,
}">
<span v-if="ntpDataList.ntp_status">synced</span>
<span v-else>not synced</span>
</td>
</tr>
<tr>
<th>Local Time</th>
<td>{{ ntpDataList.ntp_localtime }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</template>
</div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import BasePage from '@/components/BasePage.vue';
import type { NtpStatus } from "@/types/NtpStatus"; import type { NtpStatus } from "@/types/NtpStatus";
export default defineComponent({ export default defineComponent({
components: {
BasePage,
},
data() { data() {
return { return {
dataLoading: true, dataLoading: true,

View File

@ -1,60 +1,51 @@
<template> <template>
<div class="container-xxl" role="main"> <BasePage :title="'Security Settings'" :isLoading="dataLoading">
<div class="page-header">
<h1>Security Settings</h1>
</div>
<BootstrapAlert v-model="showAlert" dismissible :variant="alertType"> <BootstrapAlert v-model="showAlert" dismissible :variant="alertType">
{{ alertMessage }} {{ alertMessage }}
</BootstrapAlert> </BootstrapAlert>
<div class="text-center" v-if="dataLoading"> <form @submit="savePasswordConfig">
<div class="spinner-border" role="status"> <div class="card">
<span class="visually-hidden">Loading...</span> <div class="card-header text-white bg-primary">Admin password</div>
</div> <div class="card-body">
</div> <div class="row mb-3">
<label for="inputPassword" class="col-sm-2 col-form-label">Password:</label>
<template v-if="!dataLoading"> <div class="col-sm-10">
<form @submit="savePasswordConfig"> <input type="password" class="form-control" id="inputPassword" maxlength="64"
<div class="card"> placeholder="Password" v-model="securityConfigList.password" />
<div class="card-header text-white bg-primary">Admin password</div>
<div class="card-body">
<div class="row mb-3">
<label for="inputPassword" class="col-sm-2 col-form-label">Password:</label>
<div class="col-sm-10">
<input type="password" class="form-control" id="inputPassword" maxlength="64"
placeholder="Password" v-model="securityConfigList.password" />
</div>
</div> </div>
<div class="row mb-3">
<label for="inputPasswordRepeat" class="col-sm-2 col-form-label">Repeat Password:</label>
<div class="col-sm-10">
<input type="password" class="form-control" id="inputPasswordRepeat" maxlength="64"
placeholder="Password" v-model="passwordRepeat" />
</div>
</div>
<div class="alert alert-secondary" role="alert">
<b>Hint:</b>
The administrator password is used to connect to the device when in AP mode.
It must be 8..64 characters.
</div>
</div> </div>
<div class="row mb-3">
<label for="inputPasswordRepeat" class="col-sm-2 col-form-label">Repeat Password:</label>
<div class="col-sm-10">
<input type="password" class="form-control" id="inputPasswordRepeat" maxlength="64"
placeholder="Password" v-model="passwordRepeat" />
</div>
</div>
<div class="alert alert-secondary" role="alert">
<b>Hint:</b>
The administrator password is used to connect to the device when in AP mode.
It must be 8..64 characters.
</div>
</div> </div>
<button type="submit" class="btn btn-primary mb-3">Save</button> </div>
</form> <button type="submit" class="btn btn-primary mb-3">Save</button>
</template> </form>
</div> </BasePage>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import BasePage from '@/components/BasePage.vue';
import BootstrapAlert from "@/components/BootstrapAlert.vue"; import BootstrapAlert from "@/components/BootstrapAlert.vue";
import type { SecurityConfig } from '@/types/SecurityConfig'; import type { SecurityConfig } from '@/types/SecurityConfig';
export default defineComponent({ export default defineComponent({
components: { components: {
BasePage,
BootstrapAlert, BootstrapAlert,
}, },
data() { data() {

View File

@ -1,30 +1,19 @@
<template> <template>
<div class="container-xxl" role="main"> <BasePage :title="'System Info'" :isLoading="dataLoading">
<div class="page-header"> <FirmwareInfo :systemStatus="systemDataList" />
<h1>System Info</h1> <div class="mt-5"></div>
</div> <HardwareInfo :systemStatus="systemDataList" />
<div class="mt-5"></div>
<div class="text-center" v-if="dataLoading"> <MemoryInfo :systemStatus="systemDataList" />
<div class="spinner-border" role="status"> <div class="mt-5"></div>
<span class="visually-hidden">Loading...</span> <RadioInfo :systemStatus="systemDataList" />
</div> <div class="mt-5"></div>
</div> </BasePage>
<template v-if="!dataLoading">
<FirmwareInfo :systemStatus="systemDataList" />
<div class="mt-5"></div>
<HardwareInfo :systemStatus="systemDataList" />
<div class="mt-5"></div>
<MemoryInfo :systemStatus="systemDataList" />
<div class="mt-5"></div>
<RadioInfo :systemStatus="systemDataList" />
<div class="mt-5"></div>
</template>
</div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import BasePage from '@/components/BasePage.vue';
import HardwareInfo from "@/components/HardwareInfo.vue"; import HardwareInfo from "@/components/HardwareInfo.vue";
import FirmwareInfo from "@/components/FirmwareInfo.vue"; import FirmwareInfo from "@/components/FirmwareInfo.vue";
import MemoryInfo from "@/components/MemoryInfo.vue"; import MemoryInfo from "@/components/MemoryInfo.vue";
@ -33,6 +22,7 @@ import type { SystemStatus } from '@/types/SystemStatus';
export default defineComponent({ export default defineComponent({
components: { components: {
BasePage,
HardwareInfo, HardwareInfo,
FirmwareInfo, FirmwareInfo,
MemoryInfo, MemoryInfo,