webapp: Use volar formatter

This commit is contained in:
Thomas Basler 2022-06-21 20:32:43 +02:00
parent cd35261570
commit 5100c44c23
24 changed files with 1759 additions and 2077 deletions

View File

@ -1,25 +1,25 @@
<template> <template>
<NavBar /> <NavBar />
<main class="container-fluid"> <main class="container-fluid">
<router-view /> <router-view />
</main> </main>
</template> </template>
<script> <script>
import NavBar from "./components/NavBar.vue"; import NavBar from "./components/NavBar.vue";
export default { export default {
name: "App", name: "App",
components: { components: {
NavBar, NavBar,
}, },
}; };
</script> </script>
<style> <style>
/* Show it is fixed to the top */ /* Show it is fixed to the top */
body { body {
min-height: 75rem; min-height: 75rem;
padding-top: 4.5rem; padding-top: 4.5rem;
} }
</style> </style>

View File

@ -1,11 +1,10 @@
<template> <template>
<div class="container" role="main"> <div class="container" role="main">
<div class="page-header"> <div class="page-header">
<h1>About</h1> <h1>About</h1>
This project was started from This project was started from
<a href="https://www.mikrocontroller.net/topic/525778" target="_blank" <a href="https://www.mikrocontroller.net/topic/525778" target="_blank">this discussion.
>this discussion. (Mikrocontroller.net)</a (Mikrocontroller.net)</a>
> </div>
</div> </div>
</div>
</template> </template>

View File

@ -1,76 +1,50 @@
<template> <template>
<div class="container" role="main"> <div class="container" role="main">
<div class="page-header"> <div class="page-header">
<h1>DTU Settings</h1> <h1>DTU Settings</h1>
</div>
<BootstrapAlert v-model="showAlert" dismissible :variant="alertType">
{{ this.alertMessage }}
</BootstrapAlert>
<form @submit="saveDtuConfig">
<div class="card">
<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 class="row mb-3">
<label for="inputPollInterval" class="col-sm-2 col-form-label"
>Poll Interval:</label
>
<div class="col-sm-10">
<div class="input-group">
<input
type="number"
class="form-control"
id="inputPollInterval"
min="1"
max="86400"
placeholder="Poll Interval in Seconds"
v-model="dtuConfigList.dtu_pollinterval"
aria-describedby="pollIntervalDescription"
/>
<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> <BootstrapAlert v-model="showAlert" dismissible :variant="alertType">
<button type="submit" class="btn btn-primary mb-3">Save</button> {{ this.alertMessage }}
</form> </BootstrapAlert>
</div> <form @submit="saveDtuConfig">
<div class="card">
<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 class="row mb-3">
<label for="inputPollInterval" class="col-sm-2 col-form-label">Poll Interval:</label>
<div class="col-sm-10">
<div class="input-group">
<input type="number" class="form-control" id="inputPollInterval" min="1" max="86400"
placeholder="Poll Interval in Seconds" v-model="dtuConfigList.dtu_pollinterval"
aria-describedby="pollIntervalDescription" />
<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>
<button type="submit" class="btn btn-primary mb-3">Save</button>
</form>
</div>
</template> </template>
<script> <script>
@ -78,61 +52,61 @@ import { defineComponent } from 'vue';
import BootstrapAlert from "@/components/partials/BootstrapAlert.vue"; import BootstrapAlert from "@/components/partials/BootstrapAlert.vue";
export default defineComponent({ export default defineComponent({
components: { components: {
BootstrapAlert, BootstrapAlert,
},
data() {
return {
dtuConfigList: [],
palevelList: [
{ key: 0, value: "Minimum (-18 dBm)" },
{ key: 1, value: "Low (-12 dBm)" },
{ key: 2, value: "High (-6 dBm)" },
{ key: 3, value: "Maximum (0 dBm)" },
],
alertMessage: "",
alertType: "info",
showAlert: false,
};
},
created() {
this.getDtuConfig();
},
methods: {
getDtuConfig() {
fetch("/api/dtu/config")
.then((response) => response.json())
.then(
function (data) {
this.dtuConfigList = data;
}.bind(this)
);
}, },
saveDtuConfig(e) { data() {
e.preventDefault(); return {
dtuConfigList: [],
const formData = new FormData(); palevelList: [
formData.append("data", JSON.stringify(this.dtuConfigList)); { key: 0, value: "Minimum (-18 dBm)" },
{ key: 1, value: "Low (-12 dBm)" },
fetch("/api/dtu/config", { { key: 2, value: "High (-6 dBm)" },
method: "POST", { key: 3, value: "Maximum (0 dBm)" },
body: formData, ],
}) alertMessage: "",
.then(function (response) { alertType: "info",
if (response.status != 200) { showAlert: false,
throw response.status; };
} else { },
return response.json(); created() {
} this.getDtuConfig();
}) },
.then( methods: {
function (response) { getDtuConfig() {
this.alertMessage = response.message; fetch("/api/dtu/config")
this.alertType = response.type; .then((response) => response.json())
this.showAlert = true; .then(
}.bind(this) function (data) {
); this.dtuConfigList = data;
}.bind(this)
);
},
saveDtuConfig(e) {
e.preventDefault();
const formData = new FormData();
formData.append("data", JSON.stringify(this.dtuConfigList));
fetch("/api/dtu/config", {
method: "POST",
body: formData,
})
.then(function (response) {
if (response.status != 200) {
throw response.status;
} else {
return response.json();
}
})
.then(
function (response) {
this.alertMessage = response.message;
this.alertType = response.type;
this.showAlert = true;
}.bind(this)
);
},
}, },
},
}); });
</script> </script>

View File

@ -1,82 +1,74 @@
<template> <template>
<div class="container" role="main"> <div class="container" role="main">
<div class="page-header"> <div class="page-header">
<h1>Firmware Upgrade</h1> <h1>Firmware Upgrade</h1>
</div>
<div class="position-relative" v-if="loading">
<div class="position-absolute top-50 start-50 translate-middle">
<div class="spinner-border" role="status">
<span class="visually-hidden">Loading...</span>
</div> </div>
</div>
</div>
<div v-if="!loading && !uploading && OTAError !== null" class="card"> <div class="position-relative" v-if="loading">
<div class="card-header text-white bg-danger">OTA Error</div> <div class="position-absolute top-50 start-50 translate-middle">
<div class="card-body text-center"> <div class="spinner-border" role="status">
<p class="h1 mb-2"><BIconExclamationCircleFill /></p> <span class="visually-hidden">Loading...</span>
</div>
<span style="vertical-align: middle" class="ml-2"> </div>
{{ OTAError }}
</span>
<br />
<br />
<button class="btn btn-light" @click="clear">
<BIconArrowLeft /> Back
</button>
<button class="btn btn-primary" @click="retryOTA">
<BIconArrowRepeat /> Retry
</button>
</div>
</div>
<div v-else-if="!loading && !uploading && OTASuccess" class="card">
<div class="card-header text-white bg-success">OTA Status</div>
<div class="card-body text-center">
<span class="h1 mb-2"><BIconCheckCircle /></span>
<span> OTA Success </span>
<br />
<br />
<button class="btn btn-primary" @click="clear">
<BIconArrowLeft /> Back
</button>
</div>
</div>
<div v-else-if="!loading && !uploading" class="card">
<div class="card-header text-white bg-primary">Firmware Upload</div>
<div class="card-body text-center">
<div class="form-group pt-2 mt-3">
<input
class="form-control"
type="file"
ref="file"
accept=".bin,.bin.gz"
@change="uploadOTA"
/>
</div> </div>
</div>
</div>
<div v-else-if="!loading && uploading" class="card"> <div v-if="!loading && !uploading && OTAError !== null" class="card">
<div class="card-header text-white bg-primary">Upload Progress</div> <div class="card-header text-white bg-danger">OTA Error</div>
<div class="card-body text-center"> <div class="card-body text-center">
<div class="progress"> <p class="h1 mb-2">
<div <BIconExclamationCircleFill />
class="progress-bar" </p>
role="progressbar"
:style="{ width: this.progress + '%' }" <span style="vertical-align: middle" class="ml-2">
v-bind:aria-valuenow="this.progress" {{ OTAError }}
aria-valuemin="0" </span>
aria-valuemax="100" <br />
> <br />
{{ progress }}% <button class="btn btn-light" @click="clear">
</div> <BIconArrowLeft /> Back
</button>
<button class="btn btn-primary" @click="retryOTA">
<BIconArrowRepeat /> Retry
</button>
</div>
</div>
<div v-else-if="!loading && !uploading && OTASuccess" class="card">
<div class="card-header text-white bg-success">OTA Status</div>
<div class="card-body text-center">
<span class="h1 mb-2">
<BIconCheckCircle />
</span>
<span> OTA Success </span>
<br />
<br />
<button class="btn btn-primary" @click="clear">
<BIconArrowLeft /> Back
</button>
</div>
</div>
<div v-else-if="!loading && !uploading" class="card">
<div class="card-header text-white bg-primary">Firmware Upload</div>
<div class="card-body text-center">
<div class="form-group pt-2 mt-3">
<input class="form-control" type="file" ref="file" accept=".bin,.bin.gz" @change="uploadOTA" />
</div>
</div>
</div>
<div v-else-if="!loading && uploading" class="card">
<div class="card-header text-white bg-primary">Upload Progress</div>
<div class="card-body text-center">
<div class="progress">
<div class="progress-bar" role="progressbar" :style="{ width: this.progress + '%' }"
v-bind:aria-valuenow="this.progress" aria-valuemin="0" aria-valuemax="100">
{{ progress }}%
</div>
</div>
</div>
</div> </div>
</div>
</div> </div>
</div>
</template> </template>
<script> <script>
@ -84,101 +76,101 @@ import { defineComponent } from 'vue';
import SparkMD5 from "spark-md5"; import SparkMD5 from "spark-md5";
export default defineComponent({ export default defineComponent({
data() { data() {
return { return {
loading: true, loading: true,
uploading: false, uploading: false,
progress: 0, progress: 0,
OTAError: null, OTAError: null,
OTASuccess: false, OTASuccess: false,
type: "firmware", type: "firmware",
file: null, file: null,
};
},
methods: {
fileMD5(file) {
return new Promise((resolve, reject) => {
const blobSlice =
File.prototype.slice ||
File.prototype.mozSlice ||
File.prototype.webkitSlice;
const chunkSize = 2097152; // Read in chunks of 2MB
const chunks = Math.ceil(file.size / chunkSize);
const spark = new SparkMD5.ArrayBuffer();
const fileReader = new FileReader();
let currentChunk = 0;
fileReader.onload = (e) => {
spark.append(e.target.result); // Append array buffer
currentChunk += 1;
if (currentChunk < chunks) {
loadNext();
} else {
const md5 = spark.end();
resolve(md5);
}
}; };
fileReader.onerror = (e) => {
reject(e);
};
const loadNext = () => {
const start = currentChunk * chunkSize;
const end =
start + chunkSize >= file.size ? file.size : start + chunkSize;
fileReader.readAsArrayBuffer(blobSlice.call(file, start, end));
};
loadNext();
});
}, },
uploadOTA(event) { methods: {
this.uploading = true; fileMD5(file) {
const formData = new FormData(); return new Promise((resolve, reject) => {
if (event !== null) { const blobSlice =
[this.file] = event.target.files; File.prototype.slice ||
} File.prototype.mozSlice ||
const request = new XMLHttpRequest(); File.prototype.webkitSlice;
request.addEventListener("load", () => { const chunkSize = 2097152; // Read in chunks of 2MB
// request.response will hold the response from the server const chunks = Math.ceil(file.size / chunkSize);
if (request.status === 200) { const spark = new SparkMD5.ArrayBuffer();
this.OTASuccess = true; const fileReader = new FileReader();
} else if (request.status !== 500) { let currentChunk = 0;
this.OTAError = `[HTTP ERROR] ${request.statusText}`; fileReader.onload = (e) => {
} else { spark.append(e.target.result); // Append array buffer
this.OTAError = request.responseText; currentChunk += 1;
} if (currentChunk < chunks) {
this.uploading = false; loadNext();
this.progress = 0; } else {
}); const md5 = spark.end();
// Upload progress resolve(md5);
request.upload.addEventListener("progress", (e) => { }
this.progress = Math.trunc((e.loaded / e.total) * 100); };
}); fileReader.onerror = (e) => {
request.withCredentials = true; reject(e);
this.fileMD5(this.file) };
.then((md5) => { const loadNext = () => {
formData.append("MD5", md5); const start = currentChunk * chunkSize;
formData.append("firmware", this.file, "firmware"); const end =
request.open("post", "/api/firmware/update"); start + chunkSize >= file.size ? file.size : start + chunkSize;
request.send(formData); fileReader.readAsArrayBuffer(blobSlice.call(file, start, end));
}) };
.catch(() => { loadNext();
this.OTAError = });
"Unknown error while upload, check the console for details."; },
this.uploading = false; uploadOTA(event) {
this.progress = 0; this.uploading = true;
}); const formData = new FormData();
if (event !== null) {
[this.file] = event.target.files;
}
const request = new XMLHttpRequest();
request.addEventListener("load", () => {
// request.response will hold the response from the server
if (request.status === 200) {
this.OTASuccess = true;
} else if (request.status !== 500) {
this.OTAError = `[HTTP ERROR] ${request.statusText}`;
} else {
this.OTAError = request.responseText;
}
this.uploading = false;
this.progress = 0;
});
// Upload progress
request.upload.addEventListener("progress", (e) => {
this.progress = Math.trunc((e.loaded / e.total) * 100);
});
request.withCredentials = true;
this.fileMD5(this.file)
.then((md5) => {
formData.append("MD5", md5);
formData.append("firmware", this.file, "firmware");
request.open("post", "/api/firmware/update");
request.send(formData);
})
.catch(() => {
this.OTAError =
"Unknown error while upload, check the console for details.";
this.uploading = false;
this.progress = 0;
});
},
retryOTA() {
this.OTAError = null;
this.OTASuccess = false;
this.uploadOTA(null);
},
clear() {
this.OTAError = null;
this.OTASuccess = false;
},
}, },
retryOTA() { mounted() {
this.OTAError = null; this.loading = false;
this.OTASuccess = false;
this.uploadOTA(null);
}, },
clear() {
this.OTAError = null;
this.OTASuccess = false;
},
},
mounted() {
this.loading = false;
},
}); });
</script> </script>

View File

@ -1,72 +1,48 @@
<template> <template>
<div class="container" role="main"> <div class="container" role="main">
<div class="page-header"> <div class="page-header">
<h1>Live Data</h1> <h1>Live Data</h1>
<template v-if="waitForData == true">Waiting for data... </template> <template v-if="waitForData == true">Waiting for data... </template>
<template v-else> <template v-else>
<div class="d-flex align-items-start"> <div class="d-flex align-items-start">
<div <div class="nav flex-column nav-pills me-3" id="v-pills-tab" role="tablist"
class="nav flex-column nav-pills me-3" aria-orientation="vertical">
id="v-pills-tab" <button v-for="inverter in inverterData" :key="inverter.serial" class="nav-link"
role="tablist" :id="'v-pills-' + inverter.serial + '-tab'" data-bs-toggle="pill"
aria-orientation="vertical" :data-bs-target="'#v-pills-' + inverter.serial" type="button" role="tab"
> aria-controls="'v-pills-' + inverter.serial" aria-selected="true">
<button {{ inverter.name }}
v-for="inverter in inverterData" </button>
:key="inverter.serial" </div>
class="nav-link"
:id="'v-pills-' + inverter.serial + '-tab'" <div class="tab-content" id="v-pills-tabContent">
data-bs-toggle="pill" <div v-for="inverter in inverterData" :key="inverter.serial" class="tab-pane fade show"
:data-bs-target="'#v-pills-' + inverter.serial" :id="'v-pills-' + inverter.serial" role="tabpanel"
type="button" :aria-labelledby="'v-pills-' + inverter.serial + '-tab'" tabindex="0">
role="tab" <div class="card">
aria-controls="'v-pills-' + inverter.serial" <div class="card-header text-white bg-primary" :class="{
aria-selected="true" 'bg-danger': inverter.age_critical,
> 'bg-primary': !inverter.age_critical,
{{ inverter.name }} }">
</button> {{ inverter.name }} (Inverter Serial Number:
</div> {{ inverter.serial }}) (Data Age:
{{ inverter.data_age }} seconds)
<div class="tab-content" id="v-pills-tabContent"> </div>
<div <div class="card-body">
v-for="inverter in inverterData" <div class="row row-cols-1 row-cols-md-3 g-4">
:key="inverter.serial" <div v-for="channel in 5" :key="channel">
class="tab-pane fade show" <InverterChannelInfo v-if="inverter[channel - 1]"
:id="'v-pills-' + inverter.serial" :channelData="inverter[channel - 1]" :channelNumber="channel - 1" />
role="tabpanel" </div>
:aria-labelledby="'v-pills-' + inverter.serial + '-tab'" </div>
tabindex="0" </div>
> </div>
<div class="card"> </div>
<div
class="card-header text-white bg-primary"
:class="{
'bg-danger': inverter.age_critical,
'bg-primary': !inverter.age_critical,
}"
>
{{ inverter.name }} (Inverter Serial Number:
{{ inverter.serial }}) (Data Age:
{{ inverter.data_age }} seconds)
</div>
<div class="card-body">
<div class="row row-cols-1 row-cols-md-3 g-4">
<div v-for="channel in 5" :key="channel">
<InverterChannelInfo
v-if="inverter[channel - 1]"
:channelData="inverter[channel - 1]"
:channelNumber="channel - 1"
/>
</div> </div>
</div>
</div> </div>
</div> </template>
</div>
</div>
</div> </div>
</template>
</div> </div>
</div>
</template> </template>
<script> <script>
@ -75,84 +51,83 @@ import InverterChannelInfo from "@/components/partials/InverterChannelInfo";
import bootstrap from "bootstrap/dist/js/bootstrap.js"; import bootstrap from "bootstrap/dist/js/bootstrap.js";
export default defineComponent({ export default defineComponent({
components: { components: {
InverterChannelInfo, InverterChannelInfo,
}, },
data() { data() {
return { return {
socket: null, socket: null,
heartInterval: null, heartInterval: null,
waitForData: true, waitForData: true,
inverterData: [], inverterData: [],
isFirstFetchAfterConnect: true, isFirstFetchAfterConnect: true,
}; };
}, },
created() { created() {
this.initSocket(); this.initSocket();
}, },
unmounted() { unmounted() {
this.closeSocket();
},
updated() {
// Select first tab
if (this.isFirstFetchAfterConnect) {
this.isFirstFetchAfterConnect = false;
const firstTabEl = this.$el.querySelector(
"#v-pills-tab:first-child button"
);
if (firstTabEl != null) {
const firstTab = new bootstrap.Tab(firstTabEl);
firstTab.show();
}
}
},
methods: {
initSocket() {
console.log("Starting connection to WebSocket Server");
const { protocol, host } = location;
const webSocketUrl = `${
protocol === "https" ? "wss" : "ws"
}://${host}/livedata`;
this.socket = new WebSocket(webSocketUrl);
this.socket.onmessage = function (event) {
console.log(event);
this.inverterData = JSON.parse(event.data);
this.waitForData = false;
this.heartCheck(); // Reset heartbeat detection
}.bind(this);
this.socket.onopen = function (event) {
console.log(event);
console.log("Successfully connected to the echo websocket server...");
};
// Listen to window events , When the window closes , Take the initiative to disconnect websocket Connect
window.onbeforeunload = () => {
this.closeSocket(); this.closeSocket();
};
}, },
// Send heartbeat packets regularly * 59s Send a heartbeat updated() {
heartCheck() { // Select first tab
this.heartInterval && clearTimeout(this.heartInterval); if (this.isFirstFetchAfterConnect) {
this.heartInterval = setInterval(() => { this.isFirstFetchAfterConnect = false;
if (this.socket.readyState === 1) {
// Connection status const firstTabEl = this.$el.querySelector(
this.socket.send("ping"); "#v-pills-tab:first-child button"
} else { );
this.initSocket(); // Breakpoint reconnection 5 Time if (firstTabEl != null) {
const firstTab = new bootstrap.Tab(firstTabEl);
firstTab.show();
}
} }
}, 59 * 1000);
}, },
/** To break off websocket Connect */ methods: {
closeSocket() { initSocket() {
this.socket.close(); console.log("Starting connection to WebSocket Server");
this.heartInterval && clearTimeout(this.heartInterval);
this.isFirstFetchAfterConnect = true; const { protocol, host } = location;
const webSocketUrl = `${protocol === "https" ? "wss" : "ws"
}://${host}/livedata`;
this.socket = new WebSocket(webSocketUrl);
this.socket.onmessage = function (event) {
console.log(event);
this.inverterData = JSON.parse(event.data);
this.waitForData = false;
this.heartCheck(); // Reset heartbeat detection
}.bind(this);
this.socket.onopen = function (event) {
console.log(event);
console.log("Successfully connected to the echo websocket server...");
};
// Listen to window events , When the window closes , Take the initiative to disconnect websocket Connect
window.onbeforeunload = () => {
this.closeSocket();
};
},
// Send heartbeat packets regularly * 59s Send a heartbeat
heartCheck() {
this.heartInterval && clearTimeout(this.heartInterval);
this.heartInterval = setInterval(() => {
if (this.socket.readyState === 1) {
// Connection status
this.socket.send("ping");
} else {
this.initSocket(); // Breakpoint reconnection 5 Time
}
}, 59 * 1000);
},
/** To break off websocket Connect */
closeSocket() {
this.socket.close();
this.heartInterval && clearTimeout(this.heartInterval);
this.isFirstFetchAfterConnect = true;
},
}, },
},
}); });
</script> </script>

View File

@ -1,112 +1,95 @@
<template> <template>
<div class="container" role="main"> <div class="container" role="main">
<div class="page-header"> <div class="page-header">
<h1>Inverter Settings</h1> <h1>Inverter Settings</h1>
</div> </div>
<BootstrapAlert v-model="showAlert" dismissible :variant="alertType"> <BootstrapAlert v-model="showAlert" dismissible :variant="alertType">
{{ this.alertMessage }} {{ this.alertMessage }}
</BootstrapAlert> </BootstrapAlert>
<div class="card"> <div class="card">
<div class="card-header text-white bg-primary">Add a new Inverter</div> <div class="card-header text-white bg-primary">Add a new Inverter</div>
<div class="card-body"> <div class="card-body">
<form class="form-inline" v-on:submit.prevent="onSubmit"> <form class="form-inline" v-on:submit.prevent="onSubmit">
<div class="form-group"> <div class="form-group">
<label>Serial</label> <label>Serial</label>
<input <input v-model="inverterData.serial" type="number" class="form-control ml-sm-2 mr-sm-4 my-2"
v-model="inverterData.serial" required />
type="number" </div>
class="form-control ml-sm-2 mr-sm-4 my-2" <div class="form-group">
required <label>Name</label>
/> <input v-model="inverterData.name" type="text" class="form-control ml-sm-2 mr-sm-4 my-2"
</div> maxlength="31" required />
<div class="form-group"> </div>
<label>Name</label> <div class="ml-auto text-right">
<input <button type="submit" class="btn btn-primary my-2">Add</button>
v-model="inverterData.name" </div>
type="text" </form>
class="form-control ml-sm-2 mr-sm-4 my-2" </div>
maxlength="31" </div>
required
/> <div class="card mt-5">
</div> <div class="card-header text-white bg-primary">Inverter List</div>
<div class="ml-auto text-right"> <div class="card-body">
<button type="submit" class="btn btn-primary my-2">Add</button> <div class="table-responsive">
</div> <table class="table">
</form> <thead>
</div> <tr>
</div> <th scope="col">Serial</th>
<th>Name</th>
<div class="card mt-5"> <th>Type</th>
<div class="card-header text-white bg-primary">Inverter List</div> <th>Action</th>
<div class="card-body"> </tr>
<div class="table-responsive"> </thead>
<table class="table"> <tbody>
<thead> <tr v-for="inverter in sortedInverters" v-bind:key="inverter.id">
<tr> <template v-if="editId == inverter.id">
<th scope="col">Serial</th> <td>
<th>Name</th> <input v-model="editInverterData.serial" type="number" class="form-control" />
<th>Type</th> </td>
<th>Action</th> <td>
</tr> <input v-model="editInverterData.name" type="text" class="form-control"
</thead> maxlength="31" />
<tbody> </td>
<tr v-for="inverter in sortedInverters" v-bind:key="inverter.id"> <td>
<template v-if="editId == inverter.id"> {{ editInverterData.type }}
<td> </td>
<input <td>
v-model="editInverterData.serial" <a href="#" class="icon">
type="number" <BIconCheck v-on:click="onEditSubmit(inverter.id)" />
class="form-control" </a>
/> <a href="#" class="icon">
</td> <BIconX v-on:click="onCancel" />
<td> </a>
<input </td>
v-model="editInverterData.name" </template>
type="text" <template v-else>
class="form-control" <td>
maxlength="31" {{ inverter.serial }}
/> </td>
</td> <td>
<td> {{ inverter.name }}
{{ editInverterData.type }} </td>
</td> <td>
<td> {{ inverter.type }}
<a href="#" class="icon"> </td>
<BIconCheck v-on:click="onEditSubmit(inverter.id)" /> <td>
</a> <a href="#" class="icon">
<a href="#" class="icon"> <BIconTrash v-on:click="onDelete(inverter.id)" />
<BIconX v-on:click="onCancel" /> </a>
</a> <a href="#" class="icon">
</td> <BIconPencil v-on:click="onEdit(inverter)" />
</template> </a>
<template v-else> </td>
<td> </template>
{{ inverter.serial }} </tr>
</td> </tbody>
<td> </table>
{{ inverter.name }} </div>
</td> </div>
<td>
{{ inverter.type }}
</td>
<td>
<a href="#" class="icon">
<BIconTrash v-on:click="onDelete(inverter.id)" />
</a>
<a href="#" class="icon">
<BIconPencil v-on:click="onEdit(inverter)" />
</a>
</td>
</template>
</tr>
</tbody>
</table>
</div> </div>
</div>
</div> </div>
</div>
</template> </template>
<script> <script>
@ -114,137 +97,137 @@ import { defineComponent } from 'vue';
import BootstrapAlert from "@/components/partials/BootstrapAlert.vue"; import BootstrapAlert from "@/components/partials/BootstrapAlert.vue";
export default defineComponent({ export default defineComponent({
components: { components: {
BootstrapAlert, BootstrapAlert,
},
data() {
return {
editId: "-1",
inverterData: {
id: "",
serial: "",
name: "",
},
editInverterData: {
id: "",
serial: "",
name: "",
type: "",
},
inverters: [],
alertMessage: "",
alertType: "info",
showAlert: false,
};
},
created() {
this.getInverters();
},
computed: {
sortedInverters() {
return this.inverters.slice().sort((a, b) => {
return a.serial - b.serial;
});
}, },
}, data() {
methods: { return {
getInverters() { editId: "-1",
fetch("/api/inverter/list") inverterData: {
.then((response) => response.json()) id: "",
.then((data) => (this.inverters = data.inverter)); serial: "",
name: "",
},
editInverterData: {
id: "",
serial: "",
name: "",
type: "",
},
inverters: [],
alertMessage: "",
alertType: "info",
showAlert: false,
};
}, },
onSubmit() { created() {
const formData = new FormData(); this.getInverters();
formData.append("data", JSON.stringify(this.inverterData)); },
computed: {
sortedInverters() {
return this.inverters.slice().sort((a, b) => {
return a.serial - b.serial;
});
},
},
methods: {
getInverters() {
fetch("/api/inverter/list")
.then((response) => response.json())
.then((data) => (this.inverters = data.inverter));
},
onSubmit() {
const formData = new FormData();
formData.append("data", JSON.stringify(this.inverterData));
fetch("/api/inverter/add", { fetch("/api/inverter/add", {
method: "POST", method: "POST",
body: formData, body: formData,
}) })
.then(function (response) { .then(function (response) {
if (response.status != 200) { if (response.status != 200) {
throw response.status; throw response.status;
} else { } else {
return response.json(); return response.json();
} }
}) })
.then( .then(
function (response) { function (response) {
this.alertMessage = response.message; this.alertMessage = response.message;
this.alertType = response.type; this.alertType = response.type;
this.showAlert = true; this.showAlert = true;
}.bind(this) }.bind(this)
) )
.then(this.getInverters()); .then(this.getInverters());
this.inverterData.serial = ""; this.inverterData.serial = "";
this.inverterData.name = ""; this.inverterData.name = "";
}, },
onDelete(id) { onDelete(id) {
const formData = new FormData(); const formData = new FormData();
formData.append("data", JSON.stringify({ id: id })); formData.append("data", JSON.stringify({ id: id }));
fetch("/api/inverter/del", { fetch("/api/inverter/del", {
method: "POST", method: "POST",
body: formData, body: formData,
}) })
.then(function (response) { .then(function (response) {
if (response.status != 200) { if (response.status != 200) {
throw response.status; throw response.status;
} else { } else {
return response.json(); return response.json();
} }
}) })
.then( .then(
function (response) { function (response) {
this.alertMessage = response.message; this.alertMessage = response.message;
this.alertType = response.type; this.alertType = response.type;
this.showAlert = true; this.showAlert = true;
}.bind(this) }.bind(this)
) )
.then(this.getInverters()); .then(this.getInverters());
}, },
onEdit(inverter) { onEdit(inverter) {
this.editId = inverter.id; this.editId = inverter.id;
this.editInverterData.serial = inverter.serial; this.editInverterData.serial = inverter.serial;
this.editInverterData.name = inverter.name; this.editInverterData.name = inverter.name;
this.editInverterData.type = inverter.type; this.editInverterData.type = inverter.type;
}, },
onCancel() { onCancel() {
this.editId = "-1"; this.editId = "-1";
this.editInverterData.serial = ""; this.editInverterData.serial = "";
this.editInverterData.name = ""; this.editInverterData.name = "";
}, },
onEditSubmit(id) { onEditSubmit(id) {
const formData = new FormData(); const formData = new FormData();
this.editInverterData.id = id; this.editInverterData.id = id;
formData.append("data", JSON.stringify(this.editInverterData)); formData.append("data", JSON.stringify(this.editInverterData));
fetch("/api/inverter/edit", { fetch("/api/inverter/edit", {
method: "POST", method: "POST",
body: formData, body: formData,
}) })
.then(function (response) { .then(function (response) {
if (response.status != 200) { if (response.status != 200) {
throw response.status; throw response.status;
} else { } else {
return response.json(); return response.json();
} }
}) })
.then( .then(
function (response) { function (response) {
this.alertMessage = response.message; this.alertMessage = response.message;
this.alertType = response.type; this.alertType = response.type;
this.showAlert = true; this.showAlert = true;
}.bind(this) }.bind(this)
) )
.then(this.getInverters()); .then(this.getInverters());
this.editId = "-1"; this.editId = "-1";
this.editInverterData.serial = ""; this.editInverterData.serial = "";
this.editInverterData.name = ""; this.editInverterData.name = "";
this.editInverterData.type = ""; this.editInverterData.type = "";
},
}, },
},
}); });
</script> </script>

View File

@ -1,213 +1,135 @@
<template> <template>
<div class="container" role="main"> <div class="container" role="main">
<div class="page-header"> <div class="page-header">
<h1>MqTT Settings</h1> <h1>MqTT Settings</h1>
</div>
<BootstrapAlert v-model="showAlert" dismissible :variant="alertType">
{{ this.alertMessage }}
</BootstrapAlert>
<form @submit="saveMqttConfig">
<div class="card">
<div class="card-header text-white bg-primary">MqTT Configuration</div>
<div class="card-body">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" id="inputMqtt"
v-model="mqttConfigList.mqtt_enabled" />
<label class="form-check-label" for="inputMqtt">Enable MqTT</label>
</div>
</div>
</div>
<div class="card mt-5" v-show="mqttConfigList.mqtt_enabled">
<div class="card-header text-white bg-primary">
MqTT Broker Parameter
</div>
<div class="card-body">
<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 or IP address" v-model="mqttConfigList.mqtt_hostname" />
</div>
</div>
<div class="row mb-3">
<label for="inputPort" class="col-sm-2 col-form-label">Port:</label>
<div class="col-sm-10">
<input type="number" class="form-control" id="inputPort" min="1" max="65535"
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="text" 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 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>
</div>
<div class="card mt-5" v-show="mqttConfigList.mqtt_enabled">
<div class="card-header text-white bg-primary">LWT Parameters</div>
<div class="card-body">
<div class="row mb-3">
<label for="inputLwtTopic" class="col-sm-2 col-form-label">LWT Topic:</label>
<div class="col-sm-10">
<div class="input-group">
<span class="input-group-text" id="basic-addon3">{{
mqttConfigList.mqtt_topic
}}</span>
<input type="text" class="form-control" id="inputLwtTopic" maxlength="32"
placeholder="LWT topic, will be append base topic"
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>
<button type="submit" class="btn btn-primary mb-3">Save</button>
</form>
</div> </div>
<BootstrapAlert v-model="showAlert" dismissible :variant="alertType">
{{ this.alertMessage }}
</BootstrapAlert>
<form @submit="saveMqttConfig">
<div class="card">
<div class="card-header text-white bg-primary">MqTT Configuration</div>
<div class="card-body">
<div class="form-check form-switch">
<input
class="form-check-input"
type="checkbox"
id="inputMqtt"
v-model="mqttConfigList.mqtt_enabled"
/>
<label class="form-check-label" for="inputMqtt">Enable MqTT</label>
</div>
</div>
</div>
<div class="card mt-5" v-show="mqttConfigList.mqtt_enabled">
<div class="card-header text-white bg-primary">
MqTT Broker Parameter
</div>
<div class="card-body">
<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 or IP address"
v-model="mqttConfigList.mqtt_hostname"
/>
</div>
</div>
<div class="row mb-3">
<label for="inputPort" class="col-sm-2 col-form-label">Port:</label>
<div class="col-sm-10">
<input
type="number"
class="form-control"
id="inputPort"
min="1"
max="65535"
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="text"
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 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>
</div>
<div class="card mt-5" v-show="mqttConfigList.mqtt_enabled">
<div class="card-header text-white bg-primary">LWT Parameters</div>
<div class="card-body">
<div class="row mb-3">
<label for="inputLwtTopic" class="col-sm-2 col-form-label"
>LWT Topic:</label
>
<div class="col-sm-10">
<div class="input-group">
<span class="input-group-text" id="basic-addon3">{{
mqttConfigList.mqtt_topic
}}</span>
<input
type="text"
class="form-control"
id="inputLwtTopic"
maxlength="32"
placeholder="LWT topic, will be append base topic"
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>
<button type="submit" class="btn btn-primary mb-3">Save</button>
</form>
</div>
</template> </template>
<script> <script>
@ -215,51 +137,51 @@ import { defineComponent } from 'vue';
import BootstrapAlert from "@/components/partials/BootstrapAlert.vue"; import BootstrapAlert from "@/components/partials/BootstrapAlert.vue";
export default defineComponent({ export default defineComponent({
components: { components: {
BootstrapAlert, BootstrapAlert,
},
data() {
return {
mqttConfigList: [],
alertMessage: "",
alertType: "info",
showAlert: false,
};
},
created() {
this.getMqttConfig();
},
methods: {
getMqttConfig() {
fetch("/api/mqtt/config")
.then((response) => response.json())
.then((data) => (this.mqttConfigList = data));
}, },
saveMqttConfig(e) { data() {
e.preventDefault(); return {
mqttConfigList: [],
const formData = new FormData(); alertMessage: "",
formData.append("data", JSON.stringify(this.mqttConfigList)); alertType: "info",
showAlert: false,
fetch("/api/mqtt/config", { };
method: "POST", },
body: formData, created() {
}) this.getMqttConfig();
.then(function (response) { },
if (response.status != 200) { methods: {
throw response.status; getMqttConfig() {
} else { fetch("/api/mqtt/config")
return response.json(); .then((response) => response.json())
} .then((data) => (this.mqttConfigList = data));
}) },
.then( saveMqttConfig(e) {
function (response) { e.preventDefault();
this.alertMessage = response.message;
this.alertType = response.type; const formData = new FormData();
this.showAlert = true; formData.append("data", JSON.stringify(this.mqttConfigList));
}.bind(this)
); fetch("/api/mqtt/config", {
method: "POST",
body: formData,
})
.then(function (response) {
if (response.status != 200) {
throw response.status;
} else {
return response.json();
}
})
.then(
function (response) {
this.alertMessage = response.message;
this.alertType = response.type;
this.showAlert = true;
}.bind(this)
);
},
}, },
},
}); });
</script> </script>

View File

@ -1,112 +1,103 @@
<template> <template>
<div class="container" role="main"> <div class="container" role="main">
<div class="page-header"> <div class="page-header">
<h1>MqTT Info</h1> <h1>MqTT Info</h1>
</div>
<div class="card">
<div class="card-header text-white bg-primary">Configuration Summary</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': !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 Tnterval</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>
</tbody>
</table>
</div> </div>
</div>
</div>
<div class="card mt-5"> <div class="card">
<div class="card-header text-white bg-primary">Runtime Summary</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>Connection Status</th> <th>Status</th>
<td <td class="badge" :class="{
class="badge" 'bg-danger': !mqttDataList.mqtt_enabled,
:class="{ 'bg-success': mqttDataList.mqtt_enabled,
'bg-danger': !mqttDataList.mqtt_connected, }">
'bg-success': mqttDataList.mqtt_connected, <span v-if="mqttDataList.mqtt_enabled">enabled</span>
}" <span v-else>disabled</span>
> </td>
<span v-if="mqttDataList.mqtt_connected">connected</span> </tr>
<span v-else>disconnected</span> <tr>
</td> <th>Server</th>
</tr> <td>{{ mqttDataList.mqtt_hostname }}</td>
</tbody> </tr>
</table> <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 Tnterval</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>
</tbody>
</table>
</div>
</div>
</div>
<div class="card mt-5">
<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> </div>
</div>
</div> </div>
</div>
</template> </template>
<script> <script>
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
export default defineComponent({ export default defineComponent({
data() { data() {
return { return {
mqttDataList: [], mqttDataList: [],
}; };
}, },
created() { created() {
this.getNtpInfo(); this.getNtpInfo();
}, },
methods: { methods: {
getNtpInfo() { getNtpInfo() {
fetch("/api/mqtt/status") fetch("/api/mqtt/status")
.then((response) => response.json()) .then((response) => response.json())
.then((data) => (this.mqttDataList = data)); .then((data) => (this.mqttDataList = data));
},
}, },
},
}); });
</script> </script>

View File

@ -1,109 +1,73 @@
<template> <template>
<nav class="navbar navbar-expand-md navbar-dark fixed-top bg-dark"> <nav class="navbar navbar-expand-md navbar-dark fixed-top bg-dark">
<div class="container-fluid"> <div class="container-fluid">
<a class="navbar-brand" href="#">OpenDTU</a> <a class="navbar-brand" href="#">OpenDTU</a>
<button <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNavAltMarkup"
class="navbar-toggler" aria-controls="navbarNavAltMarkup" aria-expanded="false" aria-label="Toggle navigation">
type="button" <span class="navbar-toggler-icon"></span>
data-bs-toggle="collapse" </button>
data-bs-target="#navbarNavAltMarkup" <div class="collapse navbar-collapse" id="navbarNavAltMarkup">
aria-controls="navbarNavAltMarkup" <div class="navbar-nav">
aria-expanded="false" <li class="nav-item">
aria-label="Toggle navigation" <router-link class="nav-link" to="/">Live Data</router-link>
> </li>
<span class="navbar-toggler-icon"></span> <li class="nav-item dropdown">
</button> <a class="nav-link dropdown-toggle" href="#" id="navbarScrollingDropdown" role="button"
<div class="collapse navbar-collapse" id="navbarNavAltMarkup"> data-bs-toggle="dropdown" aria-expanded="false">
<div class="navbar-nav"> Settings
<li class="nav-item"> </a>
<router-link class="nav-link" to="/">Live Data</router-link> <ul class="dropdown-menu" aria-labelledby="navbarScrollingDropdown">
</li> <li>
<li class="nav-item dropdown"> <router-link class="dropdown-item" to="/settings/network">Network Settings</router-link>
<a </li>
class="nav-link dropdown-toggle" <li>
href="#" <router-link class="dropdown-item" to="/settings/ntp">NTP Settings</router-link>
id="navbarScrollingDropdown" </li>
role="button" <li>
data-bs-toggle="dropdown" <router-link class="dropdown-item" to="/settings/mqtt">MqTT Settings</router-link>
aria-expanded="false" </li>
> <li>
Settings <router-link class="dropdown-item" to="/settings/inverter">Inverter Settings
</a> </router-link>
<ul class="dropdown-menu" aria-labelledby="navbarScrollingDropdown"> </li>
<li> <li>
<router-link class="dropdown-item" to="/settings/network" <router-link class="dropdown-item" to="/settings/dtu">DTU Settings</router-link>
>Network Settings</router-link </li>
> <li>
</li> <hr class="dropdown-divider" />
<li> </li>
<router-link class="dropdown-item" to="/settings/ntp" <li>
>NTP Settings</router-link <router-link class="dropdown-item" to="/firmware/upgrade">Firmware Upgrade</router-link>
> </li>
</li> </ul>
<li> </li>
<router-link class="dropdown-item" to="/settings/mqtt" <li class="nav-item dropdown">
>MqTT Settings</router-link <a class="nav-link dropdown-toggle" href="#" id="navbarScrollingDropdown" role="button"
> data-bs-toggle="dropdown" aria-expanded="false">
</li> Info
<li> </a>
<router-link class="dropdown-item" to="/settings/inverter" <ul class="dropdown-menu" aria-labelledby="navbarScrollingDropdown">
>Inverter Settings</router-link <li>
> <router-link class="dropdown-item" to="/info/system">System</router-link>
</li> </li>
<li> <li>
<router-link class="dropdown-item" to="/settings/dtu" <router-link class="dropdown-item" to="/info/network">Network</router-link>
>DTU Settings</router-link </li>
> <li>
</li> <router-link class="dropdown-item" to="/info/ntp">NTP</router-link>
<li><hr class="dropdown-divider" /></li> </li>
<li> <li>
<router-link class="dropdown-item" to="/firmware/upgrade" <router-link class="dropdown-item" to="/info/mqtt">MqTT</router-link>
>Firmware Upgrade</router-link </li>
> </ul>
</li> </li>
</ul> <li class="nav-item">
</li> <router-link class="nav-link" to="/about">About</router-link>
<li class="nav-item dropdown"> </li>
<a </div>
class="nav-link dropdown-toggle" </div>
href="#"
id="navbarScrollingDropdown"
role="button"
data-bs-toggle="dropdown"
aria-expanded="false"
>
Info
</a>
<ul class="dropdown-menu" aria-labelledby="navbarScrollingDropdown">
<li>
<router-link class="dropdown-item" to="/info/system"
>System</router-link
>
</li>
<li>
<router-link class="dropdown-item" to="/info/network"
>Network</router-link
>
</li>
<li>
<router-link class="dropdown-item" to="/info/ntp"
>NTP</router-link
>
</li>
<li>
<router-link class="dropdown-item" to="/info/mqtt"
>MqTT</router-link
>
</li>
</ul>
</li>
<li class="nav-item">
<router-link class="nav-link" to="/about">About</router-link>
</li>
</div> </div>
</div> </nav>
</div>
</nav>
</template> </template>
<script> <script>

View File

@ -1,170 +1,100 @@
<template> <template>
<div class="container" role="main"> <div class="container" role="main">
<div class="page-header"> <div class="page-header">
<h1>Network Settings</h1> <h1>Network Settings</h1>
</div>
<BootstrapAlert v-model="showAlert" dismissible :variant="alertType">
{{ this.alertMessage }}
</BootstrapAlert>
<form @submit="saveNetworkConfig">
<div class="card">
<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="32"
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>
</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 class="card" v-show="!networkConfigList.dhcp">
<div class="card-header text-white bg-primary">
Static IP Configuration
</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>
</div> </div>
<BootstrapAlert v-model="showAlert" dismissible :variant="alertType">
{{ this.alertMessage }}
</BootstrapAlert>
<form @submit="saveNetworkConfig">
<div class="card">
<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="32"
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>
</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 class="card" v-show="!networkConfigList.dhcp">
<div class="card-header text-white bg-primary">
Static IP Configuration
</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>
</div>
</template> </template>
<script> <script>
@ -172,51 +102,51 @@ import { defineComponent } from 'vue';
import BootstrapAlert from "@/components/partials/BootstrapAlert.vue"; import BootstrapAlert from "@/components/partials/BootstrapAlert.vue";
export default defineComponent({ export default defineComponent({
components: { components: {
BootstrapAlert, BootstrapAlert,
},
data() {
return {
networkConfigList: [],
alertMessage: "",
alertType: "info",
showAlert: false,
};
},
created() {
this.getNetworkConfig();
},
methods: {
getNetworkConfig() {
fetch("/api/network/config")
.then((response) => response.json())
.then((data) => (this.networkConfigList = data));
}, },
saveNetworkConfig(e) { data() {
e.preventDefault(); return {
networkConfigList: [],
const formData = new FormData(); alertMessage: "",
formData.append("data", JSON.stringify(this.networkConfigList)); alertType: "info",
showAlert: false,
fetch("/api/network/config", { };
method: "POST", },
body: formData, created() {
}) this.getNetworkConfig();
.then(function (response) { },
if (response.status != 200) { methods: {
throw response.status; getNetworkConfig() {
} else { fetch("/api/network/config")
return response.json(); .then((response) => response.json())
} .then((data) => (this.networkConfigList = data));
}) },
.then( saveNetworkConfig(e) {
function (response) { e.preventDefault();
this.alertMessage = response.message;
this.alertType = response.type; const formData = new FormData();
this.showAlert = true; formData.append("data", JSON.stringify(this.networkConfigList));
}.bind(this)
); fetch("/api/network/config", {
method: "POST",
body: formData,
})
.then(function (response) {
if (response.status != 200) {
throw response.status;
} else {
return response.json();
}
})
.then(
function (response) {
this.alertMessage = response.message;
this.alertType = response.type;
this.showAlert = true;
}.bind(this)
);
},
}, },
},
}); });
</script> </script>

View File

@ -1,17 +1,17 @@
<template> <template>
<div class="container" role="main"> <div class="container" role="main">
<div class="page-header"> <div class="page-header">
<h1>Network Info</h1> <h1>Network Info</h1>
</div>
<WifiStationInfo />
<div class="mt-5"></div>
<WifiApInfo />
<div class="mt-5"></div>
<InterfaceStationInfo />
<div class="mt-5"></div>
<InterfaceApInfo />
<div class="mt-5"></div>
</div> </div>
<WifiStationInfo />
<div class="mt-5"></div>
<WifiApInfo />
<div class="mt-5"></div>
<InterfaceStationInfo />
<div class="mt-5"></div>
<InterfaceApInfo />
<div class="mt-5"></div>
</div>
</template> </template>
<script> <script>
@ -22,11 +22,11 @@ import InterfaceStationInfo from "./partials/InterfaceStationInfo.vue";
import InterfaceApInfo from "./partials/InterfaceApInfo.vue"; import InterfaceApInfo from "./partials/InterfaceApInfo.vue";
export default defineComponent({ export default defineComponent({
components: { components: {
WifiStationInfo, WifiStationInfo,
WifiApInfo, WifiApInfo,
InterfaceStationInfo, InterfaceStationInfo,
InterfaceApInfo, InterfaceApInfo,
}, },
}); });
</script> </script>

View File

@ -1,69 +1,47 @@
<template> <template>
<div class="container" role="main"> <div class="container" role="main">
<div class="page-header"> <div class="page-header">
<h1>NTP Settings</h1> <h1>NTP Settings</h1>
</div>
<BootstrapAlert v-model="showAlert" dismissible :variant="alertType">
{{ this.alertMessage }}
</BootstrapAlert>
<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>
</div> <BootstrapAlert v-model="showAlert" dismissible :variant="alertType">
<button type="submit" class="btn btn-primary mb-3">Save</button> {{ this.alertMessage }}
</form> </BootstrapAlert>
</div> <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>
</div>
</template> </template>
<script> <script>
@ -71,73 +49,73 @@ import { defineComponent } from 'vue';
import BootstrapAlert from "@/components/partials/BootstrapAlert.vue"; import BootstrapAlert from "@/components/partials/BootstrapAlert.vue";
export default defineComponent({ export default defineComponent({
components: { components: {
BootstrapAlert, BootstrapAlert,
},
data() {
return {
ntpConfigList: [],
timezoneList: {},
timezoneSelect: "",
alertMessage: "",
alertType: "info",
showAlert: false,
};
},
watch: {
timezoneSelect: function (newValue) {
this.ntpConfigList.ntp_timezone = newValue.split("---")[1];
this.ntpConfigList.ntp_timezone_descr = newValue.split("---")[0];
}, },
}, data() {
created() { return {
this.getTimezoneList(); ntpConfigList: [],
this.getNtpConfig(); timezoneList: {},
}, timezoneSelect: "",
methods: { alertMessage: "",
getTimezoneList() { alertType: "info",
fetch("/zones.json") showAlert: false,
.then((response) => response.json()) };
.then((data) => (this.timezoneList = data));
}, },
getNtpConfig() { watch: {
fetch("/api/ntp/config") timezoneSelect: function (newValue) {
.then((response) => response.json()) this.ntpConfigList.ntp_timezone = newValue.split("---")[1];
.then( this.ntpConfigList.ntp_timezone_descr = newValue.split("---")[0];
function (data) { },
this.ntpConfigList = data;
this.timezoneSelect =
this.ntpConfigList.ntp_timezone_descr +
"---" +
this.ntpConfigList.ntp_timezone;
}.bind(this)
);
}, },
saveNtpConfig(e) { created() {
e.preventDefault(); this.getTimezoneList();
this.getNtpConfig();
},
methods: {
getTimezoneList() {
fetch("/zones.json")
.then((response) => response.json())
.then((data) => (this.timezoneList = data));
},
getNtpConfig() {
fetch("/api/ntp/config")
.then((response) => response.json())
.then(
function (data) {
this.ntpConfigList = data;
this.timezoneSelect =
this.ntpConfigList.ntp_timezone_descr +
"---" +
this.ntpConfigList.ntp_timezone;
}.bind(this)
);
},
saveNtpConfig(e) {
e.preventDefault();
const formData = new FormData(); const formData = new FormData();
formData.append("data", JSON.stringify(this.ntpConfigList)); formData.append("data", JSON.stringify(this.ntpConfigList));
fetch("/api/ntp/config", { fetch("/api/ntp/config", {
method: "POST", method: "POST",
body: formData, body: formData,
}) })
.then(function (response) { .then(function (response) {
if (response.status != 200) { if (response.status != 200) {
throw response.status; throw response.status;
} else { } else {
return response.json(); return response.json();
} }
}) })
.then( .then(
function (response) { function (response) {
this.alertMessage = response.message; this.alertMessage = response.message;
this.alertType = response.type; this.alertType = response.type;
this.showAlert = true; this.showAlert = true;
}.bind(this) }.bind(this)
); );
},
}, },
},
}); });
</script> </script>

View File

@ -1,82 +1,79 @@
<template> <template>
<div class="container" role="main"> <div class="container" role="main">
<div class="page-header"> <div class="page-header">
<h1>NTP Info</h1> <h1>NTP Info</h1>
</div>
<div class="card">
<div class="card-header text-white bg-primary">Configuration Summary</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover table-condensed">
<tbody>
<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 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 <td>{{ ntpDataList.ntp_server }}</td>
class="badge" </tr>
:class="{ <tr>
'bg-danger': !ntpDataList.ntp_status, <th>Timezone</th>
'bg-success': ntpDataList.ntp_status, <td>{{ ntpDataList.ntp_timezone }}</td>
}" </tr>
> <tr>
<span v-if="ntpDataList.ntp_status">synced</span> <th>Timezone Description</th>
<span v-else>not synced</span> <td>{{ ntpDataList.ntp_timezone_descr }}</td>
</td> </tr>
</tr> </tbody>
<tr> </table>
<th>Local Time</th> </div>
<td>{{ ntpDataList.ntp_localtime }}</td> </div>
</tr> </div>
</tbody>
</table> <div class="card mt-5">
<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> </div>
</div>
</div> </div>
</div>
</template> </template>
<script> <script>
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
export default defineComponent({ export default defineComponent({
data() { data() {
return { return {
ntpDataList: [], ntpDataList: [],
}; };
}, },
created() { created() {
this.getNtpInfo(); this.getNtpInfo();
}, },
methods: { methods: {
getNtpInfo() { getNtpInfo() {
fetch("/api/ntp/status") fetch("/api/ntp/status")
.then((response) => response.json()) .then((response) => response.json())
.then((data) => (this.ntpDataList = data)); .then((data) => (this.ntpDataList = data));
},
}, },
},
}); });
</script> </script>

View File

@ -1,15 +1,15 @@
<template> <template>
<div class="container" role="main"> <div class="container" role="main">
<div class="page-header"> <div class="page-header">
<h1>System Info</h1> <h1>System Info</h1>
</div>
<FirmwareInfo />
<div class="mt-5"></div>
<HardwareInfo />
<div class="mt-5"></div>
<MemoryInfo />
<div class="mt-5"></div>
</div> </div>
<FirmwareInfo />
<div class="mt-5"></div>
<HardwareInfo />
<div class="mt-5"></div>
<MemoryInfo />
<div class="mt-5"></div>
</div>
</template> </template>
<script> <script>
@ -19,10 +19,10 @@ import FirmwareInfo from "@/components/partials/FirmwareInfo.vue";
import MemoryInfo from "@/components/partials/MemoryInfo.vue"; import MemoryInfo from "@/components/partials/MemoryInfo.vue";
export default defineComponent({ export default defineComponent({
components: { components: {
HardwareInfo, HardwareInfo,
FirmwareInfo, FirmwareInfo,
MemoryInfo, MemoryInfo,
}, },
}); });
</script> </script>

View File

@ -1,21 +1,9 @@
<template> <template>
<div <div v-if="isAlertVisible" ref="element" class="alert" role="alert" :class="classes">
v-if="isAlertVisible" <slot />
ref="element" <button v-if="dismissible" type="button" class="btn-close" data-bs-dismiss="alert" :aria-label="dismissLabel"
class="alert" @click="dismissClicked" />
role="alert" </div>
:class="classes"
>
<slot />
<button
v-if="dismissible"
type="button"
class="btn-close"
data-bs-dismiss="alert"
:aria-label="dismissLabel"
@click="dismissClicked"
/>
</div>
</template> </template>
<script> <script>
@ -23,105 +11,105 @@ import { computed, defineComponent, onBeforeUnmount, ref, watch } from "vue";
import Alert from "bootstrap/js/dist/alert"; import Alert from "bootstrap/js/dist/alert";
export const toInteger = (value, defaultValue = NaN) => { export const toInteger = (value, defaultValue = NaN) => {
return Number.isInteger(value) ? value : defaultValue; return Number.isInteger(value) ? value : defaultValue;
}; };
export default defineComponent({ export default defineComponent({
name: "BAlert", name: "BAlert",
props: { props: {
dismissLabel: { type: String, default: "Close" }, dismissLabel: { type: String, default: "Close" },
dismissible: { type: Boolean, default: false }, dismissible: { type: Boolean, default: false },
fade: { type: Boolean, default: false }, fade: { type: Boolean, default: false },
modelValue: { type: [Boolean, Number], default: false }, modelValue: { type: [Boolean, Number], default: false },
show: { type: Boolean, default: false }, show: { type: Boolean, default: false },
variant: { type: String, default: "info" }, variant: { type: String, default: "info" },
}, },
emits: ["dismissed", "dismiss-count-down", "update:modelValue"], emits: ["dismissed", "dismiss-count-down", "update:modelValue"],
setup(props, { emit }) { setup(props, { emit }) {
const element = undefined; const element = undefined;
let instance = undefined; let instance = undefined;
const classes = computed(() => ({ const classes = computed(() => ({
[`alert-${props.variant}`]: props.variant, [`alert-${props.variant}`]: props.variant,
show: props.modelValue, show: props.modelValue,
"alert-dismissible": props.dismissible, "alert-dismissible": props.dismissible,
fade: props.modelValue, fade: props.modelValue,
})); }));
let _countDownTimeout = 0; let _countDownTimeout = 0;
const parseCountDown = (value) => { const parseCountDown = (value) => {
if (typeof value === "boolean") { if (typeof value === "boolean") {
return 0; return 0;
} }
const numberValue = toInteger(value, 0); const numberValue = toInteger(value, 0);
return numberValue > 0 ? numberValue : 0; return numberValue > 0 ? numberValue : 0;
}; };
const clearCountDownInterval = () => { const clearCountDownInterval = () => {
if (_countDownTimeout === undefined) return; if (_countDownTimeout === undefined) return;
clearTimeout(_countDownTimeout); clearTimeout(_countDownTimeout);
_countDownTimeout = undefined; _countDownTimeout = undefined;
}; };
const countDown = ref(parseCountDown(props.modelValue)); const countDown = ref(parseCountDown(props.modelValue));
const isAlertVisible = computed(() => props.modelValue || props.show); const isAlertVisible = computed(() => props.modelValue || props.show);
onBeforeUnmount(() => { onBeforeUnmount(() => {
clearCountDownInterval(); clearCountDownInterval();
instance?.dispose(); instance?.dispose();
instance = undefined; instance = undefined;
}); });
const parsedModelValue = computed(() => { const parsedModelValue = computed(() => {
if (props.modelValue === true) { if (props.modelValue === true) {
return true; return true;
} }
if (props.modelValue === false) return false; if (props.modelValue === false) return false;
if (toInteger(props.modelValue, 0) < 1) { if (toInteger(props.modelValue, 0) < 1) {
// Boolean will always return false for the above comparison // Boolean will always return false for the above comparison
return false; return false;
} }
return !!props.modelValue; return !!props.modelValue;
}); });
const handleShowAndModelChanged = () => { const handleShowAndModelChanged = () => {
countDown.value = parseCountDown(props.modelValue); countDown.value = parseCountDown(props.modelValue);
if ((parsedModelValue.value || props.show) && !instance) if ((parsedModelValue.value || props.show) && !instance)
instance = new Alert(element); instance = new Alert(element);
}; };
const dismissClicked = () => { const dismissClicked = () => {
if (typeof props.modelValue === "boolean") { if (typeof props.modelValue === "boolean") {
emit("update:modelValue", false); emit("update:modelValue", false);
} else { } else {
emit("update:modelValue", 0); emit("update:modelValue", 0);
} }
emit("dismissed"); emit("dismissed");
}; };
watch(() => props.modelValue, handleShowAndModelChanged); watch(() => props.modelValue, handleShowAndModelChanged);
watch(() => props.show, handleShowAndModelChanged); watch(() => props.show, handleShowAndModelChanged);
watch(countDown, (newValue) => { watch(countDown, (newValue) => {
clearCountDownInterval(); clearCountDownInterval();
if (typeof props.modelValue === "boolean") return; if (typeof props.modelValue === "boolean") return;
emit("dismiss-count-down", newValue); emit("dismiss-count-down", newValue);
if (newValue === 0 && props.modelValue > 0) emit("dismissed"); if (newValue === 0 && props.modelValue > 0) emit("dismissed");
if (props.modelValue !== newValue) emit("update:modelValue", newValue); if (props.modelValue !== newValue) emit("update:modelValue", newValue);
if (newValue > 0) { if (newValue > 0) {
_countDownTimeout = setTimeout(() => { _countDownTimeout = setTimeout(() => {
countDown.value--; countDown.value--;
}, 1000); }, 1000);
} }
}); });
return { return {
dismissClicked, dismissClicked,
isAlertVisible, isAlertVisible,
element, element,
classes, classes,
}; };
}, },
}); });
</script> </script>

View File

@ -1,85 +1,85 @@
<template> <template>
<div class="card"> <div class="card">
<div class="card-header text-white bg-primary"> <div class="card-header text-white bg-primary">
Firmware Information Firmware Information
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover table-condensed">
<tbody>
<tr>
<th>Hostname</th>
<td>{{ systemDataList.hostname }}</td>
</tr>
<tr>
<th>SDK Version</th>
<td>{{ systemDataList.sdkversion }}</td>
</tr>
<tr>
<th>Firmware Version</th>
<td>{{ systemDataList.firmware_version }}</td>
</tr>
<tr>
<th>Git Hash</th>
<td>{{ systemDataList.git_hash }}</td>
</tr>
<tr>
<th>Reset Reason CPU 0</th>
<td>{{ systemDataList.resetreason_0 }}</td>
</tr>
<tr>
<th>Reset Reason CPU 1</th>
<td>{{ systemDataList.resetreason_1 }}</td>
</tr>
<tr>
<th>Config save count</th>
<td>{{ systemDataList.cfgsavecount }}</td>
</tr>
<tr>
<th>Uptime</th>
<td>{{ timeInHours(systemDataList.uptime) }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div> </div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover table-condensed">
<tbody>
<tr>
<th>Hostname</th>
<td>{{ systemDataList.hostname }}</td>
</tr>
<tr>
<th>SDK Version</th>
<td>{{ systemDataList.sdkversion }}</td>
</tr>
<tr>
<th>Firmware Version</th>
<td>{{ systemDataList.firmware_version }}</td>
</tr>
<tr>
<th>Git Hash</th>
<td>{{ systemDataList.git_hash }}</td>
</tr>
<tr>
<th>Reset Reason CPU 0</th>
<td>{{ systemDataList.resetreason_0 }}</td>
</tr>
<tr>
<th>Reset Reason CPU 1</th>
<td>{{ systemDataList.resetreason_1 }}</td>
</tr>
<tr>
<th>Config save count</th>
<td>{{ systemDataList.cfgsavecount }}</td>
</tr>
<tr>
<th>Uptime</th>
<td>{{ timeInHours(systemDataList.uptime) }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</template> </template>
<script> <script>
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
export default defineComponent({ export default defineComponent({
data() { data() {
return { return {
systemDataList: [], systemDataList: [],
}; };
},
created() {
this.getSystemInfo();
},
computed: {
timeInHours() {
return (value) => {
const days = parseInt(Math.floor(value / 3600 / 24));
const hours = parseInt(Math.floor((value - days * 3600 * 24) / 3600));
const minutes = parseInt(Math.floor((value - days * 3600 * 24 - hours * 3600) / 60));
const seconds = parseInt((value - days * 3600 * 24 - hours * 3600 + minutes * 60) % 60);
const dHours = hours > 9 ? hours : "0" + hours;
const dMins = minutes > 9 ? minutes : "0" + minutes;
const dSecs = seconds > 9 ? seconds : "0" + seconds;
return days + " days " + dHours + ":" + dMins + ":" + dSecs;
};
}, },
}, created() {
methods: { this.getSystemInfo();
getSystemInfo() { },
fetch("/api/system/status") computed: {
.then((response) => response.json()) timeInHours() {
.then((data) => (this.systemDataList = data)); return (value) => {
const days = parseInt(Math.floor(value / 3600 / 24));
const hours = parseInt(Math.floor((value - days * 3600 * 24) / 3600));
const minutes = parseInt(Math.floor((value - days * 3600 * 24 - hours * 3600) / 60));
const seconds = parseInt((value - days * 3600 * 24 - hours * 3600 + minutes * 60) % 60);
const dHours = hours > 9 ? hours : "0" + hours;
const dMins = minutes > 9 ? minutes : "0" + minutes;
const dSecs = seconds > 9 ? seconds : "0" + seconds;
return days + " days " + dHours + ":" + dMins + ":" + dSecs;
};
},
},
methods: {
getSystemInfo() {
fetch("/api/system/status")
.then((response) => response.json())
.then((data) => (this.systemDataList = data));
},
}, },
},
}); });
</script> </script>

View File

@ -1,42 +1,36 @@
<template> <template>
<tr> <tr>
<th>{{ name }}</th> <th>{{ name }}</th>
<td> <td>
<div class="progress"> <div class="progress">
<div <div class="progress-bar" role="progressbar" :style="{ width: this.getPercent() + '%' }"
class="progress-bar" v-bind:aria-valuenow="this.getPercent()" aria-valuemin="0" aria-valuemax="100">
role="progressbar" {{ this.getPercent() }}%
:style="{ width: this.getPercent() + '%' }" </div>
v-bind:aria-valuenow="this.getPercent()" </div>
aria-valuemin="0" </td>
aria-valuemax="100" <td class="rightCell">
> {{ Math.round((total - used) / 1024) }}
{{ this.getPercent() }}% KByte
</div> </td>
</div> <td class="rightCell">{{ Math.round(used / 1024) }} KByte</td>
</td> <td class="rightCell">{{ Math.round(total / 1024) }} KByte</td>
<td class="rightCell"> </tr>
{{ Math.round((total - used) / 1024) }}
KByte
</td>
<td class="rightCell">{{ Math.round(used / 1024) }} KByte</td>
<td class="rightCell">{{ Math.round(total / 1024) }} KByte</td>
</tr>
</template> </template>
<script> <script>
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
export default defineComponent({ export default defineComponent({
props: { props: {
name: String, name: String,
total: Number, total: Number,
used: Number, used: Number,
}, },
methods: { methods: {
getPercent() { getPercent() {
return Math.round((this.used / this.total) * 100); return Math.round((this.used / this.total) * 100);
},
}, },
},
}); });
</script> </script>

View File

@ -1,53 +1,53 @@
<template> <template>
<div class="card"> <div class="card">
<div class="card-header text-white bg-primary"> <div class="card-header text-white bg-primary">
Hardware Information Hardware Information
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover table-condensed">
<tbody>
<tr>
<th>Chip Model</th>
<td>{{ systemDataList.chipmodel }}</td>
</tr>
<tr>
<th>Chip Revision</th>
<td>{{ systemDataList.chiprevision }}</td>
</tr>
<tr>
<th>Chip Cores</th>
<td>{{ systemDataList.chipcores }}</td>
</tr>
<tr>
<th>CPU Frequency</th>
<td>{{ systemDataList.cpufreq }} MHz</td>
</tr>
</tbody>
</table>
</div>
</div>
</div> </div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover table-condensed">
<tbody>
<tr>
<th>Chip Model</th>
<td>{{ systemDataList.chipmodel }}</td>
</tr>
<tr>
<th>Chip Revision</th>
<td>{{ systemDataList.chiprevision }}</td>
</tr>
<tr>
<th>Chip Cores</th>
<td>{{ systemDataList.chipcores }}</td>
</tr>
<tr>
<th>CPU Frequency</th>
<td>{{ systemDataList.cpufreq }} MHz</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</template> </template>
<script> <script>
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
export default defineComponent({ export default defineComponent({
data() { data() {
return { return {
systemDataList: [], systemDataList: [],
}; };
}, },
created() { created() {
this.getSystemInfo(); this.getSystemInfo();
}, },
methods: { methods: {
getSystemInfo() { getSystemInfo() {
fetch("/api/system/status") fetch("/api/system/status")
.then((response) => response.json()) .then((response) => response.json())
.then((data) => (this.systemDataList = data)); .then((data) => (this.systemDataList = data));
},
}, },
},
}); });
</script> </script>

View File

@ -1,45 +1,45 @@
<template> <template>
<div class="card"> <div class="card">
<div class="card-header text-white bg-primary"> <div class="card-header text-white bg-primary">
Network Interface (Access Point) Network Interface (Access Point)
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover table-condensed">
<tbody>
<tr>
<th>IP Address</th>
<td>{{ networkDataList.ap_ip }}</td>
</tr>
<tr>
<th>MAC Address</th>
<td>{{ networkDataList.ap_mac }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div> </div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover table-condensed">
<tbody>
<tr>
<th>IP Address</th>
<td>{{ networkDataList.ap_ip }}</td>
</tr>
<tr>
<th>MAC Address</th>
<td>{{ networkDataList.ap_mac }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</template> </template>
<script> <script>
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
export default defineComponent({ export default defineComponent({
data() { data() {
return { return {
networkDataList: [], networkDataList: [],
}; };
}, },
created() { created() {
this.getNetworkInfo(); this.getNetworkInfo();
}, },
methods: { methods: {
getNetworkInfo() { getNetworkInfo() {
fetch("/api/network/status") fetch("/api/network/status")
.then((response) => response.json()) .then((response) => response.json())
.then((data) => (this.networkDataList = data)); .then((data) => (this.networkDataList = data));
},
}, },
},
}); });
</script> </script>

View File

@ -1,61 +1,61 @@
<template> <template>
<div class="card"> <div class="card">
<div class="card-header text-white bg-primary"> <div class="card-header text-white bg-primary">
Network Interface (Station) Network Interface (Station)
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover table-condensed">
<tbody>
<tr>
<th>IP Address</th>
<td>{{ networkDataList.sta_ip }}</td>
</tr>
<tr>
<th>Netmask</th>
<td>{{ networkDataList.sta_netmask }}</td>
</tr>
<tr>
<th>Default Gateway</th>
<td>{{ networkDataList.sta_gateway }}</td>
</tr>
<tr>
<th>DNS 1</th>
<td>{{ networkDataList.sta_dns1 }}</td>
</tr>
<tr>
<th>DNS 2</th>
<td>{{ networkDataList.sta_dns2 }}</td>
</tr>
<tr>
<th>MAC Address</th>
<td>{{ networkDataList.sta_mac }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div> </div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover table-condensed">
<tbody>
<tr>
<th>IP Address</th>
<td>{{ networkDataList.sta_ip }}</td>
</tr>
<tr>
<th>Netmask</th>
<td>{{ networkDataList.sta_netmask }}</td>
</tr>
<tr>
<th>Default Gateway</th>
<td>{{ networkDataList.sta_gateway }}</td>
</tr>
<tr>
<th>DNS 1</th>
<td>{{ networkDataList.sta_dns1 }}</td>
</tr>
<tr>
<th>DNS 2</th>
<td>{{ networkDataList.sta_dns2 }}</td>
</tr>
<tr>
<th>MAC Address</th>
<td>{{ networkDataList.sta_mac }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</template> </template>
<script> <script>
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
export default defineComponent({ export default defineComponent({
data() { data() {
return { return {
networkDataList: [], networkDataList: [],
}; };
}, },
created() { created() {
this.getNetworkInfo(); this.getNetworkInfo();
}, },
methods: { methods: {
getNetworkInfo() { getNetworkInfo() {
fetch("/api/network/status") fetch("/api/network/status")
.then((response) => response.json()) .then((response) => response.json())
.then((data) => (this.networkDataList = data)); .then((data) => (this.networkDataList = data));
},
}, },
},
}); });
</script> </script>

View File

@ -1,41 +1,41 @@
<template> <template>
<div class="col"> <div class="col">
<div class="card"> <div class="card">
<div class="card-header">Channel {{ channelNumber }}</div> <div class="card-header">Channel {{ channelNumber }}</div>
<div class="card-body"> <div class="card-body">
<table class="table table-striped table-hover"> <table class="table table-striped table-hover">
<thead> <thead>
<tr> <tr>
<th scope="col">Property</th> <th scope="col">Property</th>
<th scope="col">Value</th> <th scope="col">Value</th>
<th scope="col">Unit</th> <th scope="col">Unit</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr v-for="(property, key) in channelData" :key="`prop-${key}`"> <tr v-for="(property, key) in channelData" :key="`prop-${key}`">
<th scope="row">{{ key }}</th> <th scope="row">{{ key }}</th>
<td style="text-align: right">{{ formatNumber(property.v) }}</td> <td style="text-align: right">{{ formatNumber(property.v) }}</td>
<td>{{ property.u }}</td> <td>{{ property.u }}</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
</div> </div>
</div>
</div> </div>
</div>
</template> </template>
<script> <script>
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
export default defineComponent({ export default defineComponent({
props: { props: {
channelData: Object, channelData: Object,
channelNumber: Number, channelNumber: Number,
}, },
methods: { methods: {
formatNumber(num) { formatNumber(num) {
return parseFloat(num).toFixed(2); return parseFloat(num).toFixed(2);
},
}, },
},
}); });
</script> </script>

View File

@ -1,27 +1,28 @@
<template> <template>
<div class="card"> <div class="card">
<div class="card-header text-white bg-primary">Memory Information</div> <div class="card-header text-white bg-primary">Memory Information</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">
<thead> <thead>
<tr> <tr>
<th>Type</th> <th>Type</th>
<th>Usage</th> <th>Usage</th>
<th class="rightCell">Free</th> <th class="rightCell">Free</th>
<th class="rightCell">Used</th> <th class="rightCell">Used</th>
<th class="rightCell">Size</th> <th class="rightCell">Size</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<FsInfo name="Heap" :total="systemDataList.heap_total" :used="systemDataList.heap_used" /> <FsInfo name="Heap" :total="systemDataList.heap_total" :used="systemDataList.heap_used" />
<FsInfo name="LittleFs" :total="systemDataList.littlefs_total" :used="systemDataList.littlefs_used" /> <FsInfo name="LittleFs" :total="systemDataList.littlefs_total"
<FsInfo name="Sketch" :total="systemDataList.sketch_total" :used="systemDataList.sketch_used" /> :used="systemDataList.littlefs_used" />
</tbody> <FsInfo name="Sketch" :total="systemDataList.sketch_total" :used="systemDataList.sketch_used" />
</table> </tbody>
</div> </table>
</div>
</div>
</div> </div>
</div>
</template> </template>
<script> <script>
@ -29,23 +30,23 @@ import { defineComponent } from 'vue';
import FsInfo from "@/components/partials/FsInfo.vue"; import FsInfo from "@/components/partials/FsInfo.vue";
export default defineComponent({ export default defineComponent({
components: { components: {
FsInfo, FsInfo,
}, },
data() { data() {
return { return {
systemDataList: [], systemDataList: [],
}; };
}, },
created() { created() {
this.getSystemInfo(); this.getSystemInfo();
}, },
methods: { methods: {
getSystemInfo() { getSystemInfo() {
fetch("/api/system/status") fetch("/api/system/status")
.then((response) => response.json()) .then((response) => response.json())
.then((data) => (this.systemDataList = data)); .then((data) => (this.systemDataList = data));
},
}, },
},
}); });
</script> </script>

View File

@ -1,58 +1,55 @@
<template> <template>
<div class="card"> <div class="card">
<div class="card-header text-white bg-primary"> <div class="card-header text-white bg-primary">
WiFi Information (Access Point) WiFi Information (Access Point)
</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': !networkDataList.ap_status,
'bg-success': networkDataList.ap_status,
}">
<span v-if="networkDataList.ap_status">enabled</span>
<span v-else>disabled</span>
</td>
</tr>
<tr>
<th>SSID</th>
<td>{{ networkDataList.ap_ssid }}</td>
</tr>
<tr>
<th># Stations</th>
<td>{{ networkDataList.ap_stationnum }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div> </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': !networkDataList.ap_status,
'bg-success': networkDataList.ap_status,
}"
>
<span v-if="networkDataList.ap_status">enabled</span>
<span v-else>disabled</span>
</td>
</tr>
<tr>
<th>SSID</th>
<td>{{ networkDataList.ap_ssid }}</td>
</tr>
<tr>
<th># Stations</th>
<td>{{ networkDataList.ap_stationnum }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</template> </template>
<script> <script>
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
export default defineComponent({ export default defineComponent({
data() { data() {
return { return {
networkDataList: [], networkDataList: [],
}; };
}, },
created() { created() {
this.getNetworkInfo(); this.getNetworkInfo();
}, },
methods: { methods: {
getNetworkInfo() { getNetworkInfo() {
fetch("/api/network/status") fetch("/api/network/status")
.then((response) => response.json()) .then((response) => response.json())
.then((data) => (this.networkDataList = data)); .then((data) => (this.networkDataList = data));
},
}, },
},
}); });
</script> </script>

View File

@ -1,75 +1,72 @@
<template> <template>
<div class="card"> <div class="card">
<div class="card-header text-white bg-primary"> <div class="card-header text-white bg-primary">
WiFi Information (Station) WiFi Information (Station)
</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': !networkDataList.sta_status,
'bg-success': networkDataList.sta_status,
}">
<span v-if="networkDataList.sta_status">enabled</span>
<span v-else>disabled</span>
</td>
</tr>
<tr>
<th>SSID</th>
<td>{{ networkDataList.sta_ssid }}</td>
</tr>
<tr>
<th>Quality</th>
<td>{{ this.getRSSIasQuality(networkDataList.sta_rssi) }} %</td>
</tr>
<tr>
<th>RSSI</th>
<td>{{ networkDataList.sta_rssi }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div> </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': !networkDataList.sta_status,
'bg-success': networkDataList.sta_status,
}"
>
<span v-if="networkDataList.sta_status">enabled</span>
<span v-else>disabled</span>
</td>
</tr>
<tr>
<th>SSID</th>
<td>{{ networkDataList.sta_ssid }}</td>
</tr>
<tr>
<th>Quality</th>
<td>{{ this.getRSSIasQuality(networkDataList.sta_rssi) }} %</td>
</tr>
<tr>
<th>RSSI</th>
<td>{{ networkDataList.sta_rssi }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</template> </template>
<script> <script>
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
export default defineComponent({ export default defineComponent({
data() { data() {
return { return {
networkDataList: [], networkDataList: [],
}; };
},
created() {
this.getNetworkInfo();
},
methods: {
getNetworkInfo() {
fetch("/api/network/status")
.then((response) => response.json())
.then((data) => (this.networkDataList = data));
}, },
getRSSIasQuality(rssi) { created() {
let quality = 0; this.getNetworkInfo();
},
if (rssi <= -100) { methods: {
quality = 0; getNetworkInfo() {
} else if (rssi >= -50) { fetch("/api/network/status")
quality = 100; .then((response) => response.json())
} else { .then((data) => (this.networkDataList = data));
quality = 2 * (rssi + 100); },
} getRSSIasQuality(rssi) {
let quality = 0;
return quality;
if (rssi <= -100) {
quality = 0;
} else if (rssi >= -50) {
quality = 100;
} else {
quality = 2 * (rssi + 100);
}
return quality;
},
}, },
},
}); });
</script> </script>