This commit is contained in:
Sergey Yarkov 2023-10-26 23:03:09 +03:00
parent 8932402d3b
commit 0e10984830
No known key found for this signature in database
GPG Key ID: E91CE1FABC619B6F
4 changed files with 80 additions and 51 deletions

BIN
data.db

Binary file not shown.

View File

@ -39,6 +39,21 @@ CREATE TABLE IF NOT EXISTS "display_values" (
FOREIGN KEY("slave_id") REFERENCES modbus_slaves("id") ON DELETE CASCADE FOREIGN KEY("slave_id") REFERENCES modbus_slaves("id") ON DELETE CASCADE
); );
-- CREATE TABLE IF NOT EXISTS "modbus_log" (
-- "id" INTEGER,
-- "date" DATE NOT NULL,
-- "slave_id" INTEGER NOT NULL,
-- -- JSON Format:
-- -- {
-- -- graph: [{ reg_addr: 1 value: 4 }],
-- -- display_values: [{ reg_addr: 1, name: "CV1" }]
-- -- }
-- "data" TEXT NOT NULL,
-- PRIMARY KEY("id"),
-- FOREIGN KEY("slave_id") REFERENCES modbus_slaves("id") ON DELETE CASCADE
-- )
INSERT INTO "modbus_slaves" ( INSERT INTO "modbus_slaves" (
"id", "name", "g_display_reg_addr", "g_display_reg_format", "g_y_label", "is_logging" "id", "name", "g_display_reg_addr", "g_display_reg_format", "g_y_label", "is_logging"
) VALUES ) VALUES

View File

@ -28,30 +28,35 @@
<div class="border-r min-w-[256px] p-2.5 pt-5"> <div class="border-r min-w-[256px] p-2.5 pt-5">
<div class="flex flex-col space-y-1"> <div class="flex flex-col space-y-1">
<span class="italic text-sm">Devices:</span> <span class="italic text-sm">Devices:</span>
<span x-show="devices.length === 0" class="text-gray-500 text-sm text-center block">No devices added.</span> <span x-show="devices.length === 0" class="text-gray-500 text-sm text-center block"
>No devices added.</span
>
<div class="flex flex-col max-h-[520px] overflow-y-auto space-y-2"> <div class="flex flex-col max-h-[520px] overflow-y-auto space-y-2">
<template x-for="device in devices"> <template x-for="device in devices">
<button @click="selectDevice(device)" :class="{'bg-gray-100': selectedDevice?.id === device.id}" class="w-full select-none border px-2.5 py-3 text-left rounded-sm hover:bg-gray-100 cursor-pointer"> <button
@click="selectDevice(device)"
:class="{'bg-gray-100': selectedDevice?.id === device.id}"
class="w-full select-none border px-2.5 py-3 text-left rounded-sm hover:bg-gray-100 cursor-pointer"
>
<span x-text="device.name" class="text-md font-bold"></span> <span x-text="device.name" class="text-md font-bold"></span>
</button> </button>
</template> </template>
</div> </div>
<button class="w-full text-center border-2 border-dashed rounded-sm hover:bg-gray-50 cursor-pointer text-left"> <button class="w-full text-center border-2 border-dashed rounded-sm hover:bg-gray-50 cursor-pointer">
<span class="text-4xl text-gray-400 select-none">+</span> <span class="text-4xl text-gray-400 select-none">+</span>
</button> </button>
</div> </div>
<hr class="my-4" /> <hr class="my-4" />
<div> <div>
<span class="italic text-sm">Connection:</span> <span class="italic text-sm">Connection:</span>
<div class="flex flex-col mt-1.5"> <div class="flex flex-col mt-1.5">
<span class="text-sm">Status: <span class="text-sm"
<b x-show="$store.app.is_opened" class="text-green-700">Opened</b> >Status:
<b x-show="!$store.app.is_opened" class="text-red-700">Closed</b> <b x-show="$store.app.is_opened" class="text-green-700">Opened</b>
</span> <b x-show="!$store.app.is_opened" class="text-red-700">Closed</b>
<a class="text-sm" @click="$store.app.setPage('configuration')" href="#"> </span>
Reconfigurate <a class="text-sm" @click="$store.app.setPage('configuration')" href="#"> Reconfigurate </a>
</a> </div>
</div>
</div> </div>
</div> </div>
<div class="flex-auto pl-2"> <div class="flex-auto pl-2">
@ -60,7 +65,12 @@
<h2 x-text="selectedDevice?.name" class="font-bold text-3xl my-2"></h2> <h2 x-text="selectedDevice?.name" class="font-bold text-3xl my-2"></h2>
<div> <div>
<a href="#">Edit</a> <a href="#">Edit</a>
<a @click="$store.confirmDialog.open('Remove device', `Delete ${selectedDevice.name}? All logs will be cleared.`, removeDevice, selectedDevice.id)" class="text-red-500" href="#">Delete</a> <a
@click="$store.confirmDialog.open('Remove device', `Delete ${selectedDevice.name}? All logs will be cleared.`, removeDevice, selectedDevice.id)"
class="text-red-500"
href="#"
>Delete</a
>
</div> </div>
</div> </div>
<div class="mt-4"> <div class="mt-4">
@ -101,7 +111,7 @@
<option value="RTU">RTU</option> <option value="RTU">RTU</option>
</select> </select>
</div> </div>
<div x-show="$store.app.config.mb_connection_type === 'TCP'" class="flex flex-col space-y-3"> <div x-show="$store.app.config.mb_connection_type === 'TCP'" class="flex flex-col space-y-3">
<div> <div>
<label for="tcp_ip">IP</label> <label for="tcp_ip">IP</label>
@ -114,7 +124,7 @@
x-bind:required="$store.app.config.mb_connection_type === 'TCP'" x-bind:required="$store.app.config.mb_connection_type === 'TCP'"
/> />
</div> </div>
<div> <div>
<label for="rtu_port">Port</label> <label for="rtu_port">Port</label>
<input <input
@ -127,7 +137,7 @@
/> />
</div> </div>
</div> </div>
<div x-show="$store.app.config.mb_connection_type === 'RTU'" class="flex flex-col space-y-3"> <div x-show="$store.app.config.mb_connection_type === 'RTU'" class="flex flex-col space-y-3">
<div> <div>
<label for="rtu_path">Path</label> <label for="rtu_path">Path</label>
@ -140,7 +150,7 @@
x-bind:required="$store.app.config.mb_connection_type === 'RTU'" x-bind:required="$store.app.config.mb_connection_type === 'RTU'"
/> />
</div> </div>
<div> <div>
<label for="rtu_baud">Baud</label> <label for="rtu_baud">Baud</label>
<select <select
@ -157,7 +167,7 @@
<option value="1200">1200</option> <option value="1200">1200</option>
</select> </select>
</div> </div>
<div> <div>
<label for="rtu_parity">Parity</label> <label for="rtu_parity">Parity</label>
<select <select
@ -171,7 +181,7 @@
<option value="none">none</option> <option value="none">none</option>
</select> </select>
</div> </div>
<div> <div>
<label for="rtu_data_bits">Data bits</label> <label for="rtu_data_bits">Data bits</label>
<select <select
@ -186,7 +196,7 @@
<option value="5">5</option> <option value="5">5</option>
</select> </select>
</div> </div>
<div> <div>
<label for="rtu_stop_bits">Stop bits</label> <label for="rtu_stop_bits">Stop bits</label>
<select <select
@ -201,7 +211,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="mt-2"> <div class="mt-2">
<label for="rtu_path">Log Interval (ms)</label> <label for="rtu_path">Log Interval (ms)</label>
<input <input
@ -213,7 +223,7 @@
required required
/> />
</div> </div>
<div class="mt-3 flex flex-row justify-between items-center"> <div class="mt-3 flex flex-row justify-between items-center">
<div class="mr-1"> <div class="mr-1">
<button <button
@ -285,15 +295,25 @@
</main> </main>
</template> </template>
<!-- Confirm dialog -->
<div x-show="$store.confirmDialog.isOpen" class="backdrop z-50"> <div x-show="$store.confirmDialog.isOpen" class="backdrop z-50">
<div role="dialog" @click.away="$store.confirmDialog.close()" class="px-4 py-3.5 ml-auto mr-auto mt-20 bg-white max-w-md border rounded-sm"> <div
role="dialog"
@click.away="$store.confirmDialog.close()"
class="px-4 py-3.5 ml-auto mr-auto mt-20 bg-white max-w-md border rounded-sm"
>
<div class="flex justify-between items-center"> <div class="flex justify-between items-center">
<h2 x-text="$store.confirmDialog.labels.title" class="font-bold text-lg"></h2> <h2 x-text="$store.confirmDialog.labels.title" class="font-bold text-lg"></h2>
<button @click="$store.confirmDialog.close()" aria-label="Close"> <button @click="$store.confirmDialog.close()" aria-label="Close">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M6.4 19L5 17.6l5.6-5.6L5 6.4L6.4 5l5.6 5.6L17.6 5L19 6.4L13.4 12l5.6 5.6l-1.4 1.4l-5.6-5.6L6.4 19Z"/></svg> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path
fill="currentColor"
d="M6.4 19L5 17.6l5.6-5.6L5 6.4L6.4 5l5.6 5.6L17.6 5L19 6.4L13.4 12l5.6 5.6l-1.4 1.4l-5.6-5.6L6.4 19Z"
/>
</svg>
</button> </button>
</div> </div>
<hr> <hr />
<p x-text="$store.confirmDialog.labels.body"></p> <p x-text="$store.confirmDialog.labels.body"></p>
<div class="mt-4 flex justify-end space-x-2"> <div class="mt-4 flex justify-end space-x-2">
<button @click="$store.confirmDialog.confirm()" class="btn">Confirm</button> <button @click="$store.confirmDialog.confirm()" class="btn">Confirm</button>

View File

@ -4,13 +4,7 @@ import api from "./api.js";
import * as utils from "./utils/index.js"; import * as utils from "./utils/index.js";
const pages = ["loading", "monitoring", "configuration", "404"]; const pages = ["loading", "monitoring", "configuration", "404"];
let graphData = []; let graphData = [[0, 0]];
const t = new Date();
for (var i = 10; i >= 0; i--) {
var x = new Date(t.getTime() - i * 1000);
graphData.push([x, Math.random()]);
}
document.addEventListener("alpine:init", async () => { document.addEventListener("alpine:init", async () => {
Alpine.store("app", { Alpine.store("app", {
@ -31,7 +25,7 @@ document.addEventListener("alpine:init", async () => {
error: null, error: null,
async init() { async init() {
try { try {
const connStatusEventSource = new EventSource('/api/modbus/status'); const connStatusEventSource = new EventSource("/api/modbus/status");
const { config } = await api.get("/app/config"); const { config } = await api.get("/app/config");
const { is_configured } = await api.get("/app/is_configured"); const { is_configured } = await api.get("/app/is_configured");
@ -39,7 +33,7 @@ document.addEventListener("alpine:init", async () => {
if (config) this.config = config; if (config) this.config = config;
if (is_configured) { if (is_configured) {
connStatusEventSource.onmessage = (e) => (this.is_opened = e.data === 'true'); connStatusEventSource.onmessage = (e) => (this.is_opened = e.data === "true");
await utils.wait(500); await utils.wait(500);
this.currentPage = "monitoring"; this.currentPage = "monitoring";
return; return;
@ -63,28 +57,28 @@ document.addEventListener("alpine:init", async () => {
}, },
}); });
Alpine.data('monitoringPage', () => ({ Alpine.data("monitoringPage", () => ({
selectedDevice: null, selectedDevice: null,
devices: [], devices: [],
async init() { async init() {
try { try {
this.$store.app.currentPage = 'loading' this.$store.app.currentPage = "loading";
var g = new Dygraph(document.getElementById("div_g"), graphData, { var g = new Dygraph(document.getElementById("div_g"), graphData, {
drawPoints: false, drawPoints: false,
showRoller: false, showRoller: false,
ylabel: 'Y Label', ylabel: "Y Label",
valueRange: [0.0, 1.8], valueRange: [0.0, 1.8],
//labels: ["Time", "Random"], //labels: ["Time", "Random"],
}); });
this.devices = await api.get('/modbus/devices'); this.devices = await api.get("/modbus/devices");
window.intervalId = setInterval(() => { window.intervalId = setInterval(() => {
graphData.push([new Date(), Math.random()]); graphData.push([new Date(), Math.random()]);
g.updateOptions({ file: graphData }) g.updateOptions({ file: graphData });
}, 500) }, 500);
} catch (error) { } catch (error) {
this.$store.app.error = error; this.$store.app.error = error;
} finally { } finally {
this.$store.app.currentPage = 'monitoring' this.$store.app.currentPage = "monitoring";
} }
}, },
selectDevice(device) { selectDevice(device) {
@ -95,13 +89,13 @@ document.addEventListener("alpine:init", async () => {
async removeDevice(id, ctx) { async removeDevice(id, ctx) {
try { try {
//await api.post('/modbus/remove_device', { id }); //await api.post('/modbus/remove_device', { id });
this.devices = _this.devices.filter(d => d.id != id); this.devices = _this.devices.filter((d) => d.id != id);
this.selectedDevice = null; this.selectedDevice = null;
} catch (error) { } catch (error) {
console.error('Error while removing device.', error) console.error("Error while removing device.", error);
} }
} },
})) }));
Alpine.data("configAppPage", () => ({ Alpine.data("configAppPage", () => ({
selectedConnectionType: "TCP", selectedConnectionType: "TCP",
@ -137,12 +131,12 @@ document.addEventListener("alpine:init", async () => {
Alpine.store("confirmDialog", { Alpine.store("confirmDialog", {
isOpen: false, isOpen: false,
labels: { labels: {
title: 'Confirm dialog', title: "Confirm dialog",
body: 'Are you sure you want to perform this action?', body: "Are you sure you want to perform this action?",
}, },
cb: { cb: {
fn: null, fn: null,
args: [] args: [],
}, },
open(title, body, cb, ...args) { open(title, body, cb, ...args) {
this.isOpen = true; this.isOpen = true;
@ -156,6 +150,6 @@ document.addEventListener("alpine:init", async () => {
}, },
close() { close() {
this.isOpen = false; this.isOpen = false;
} },
}) });
}); });