mirror of
https://github.com/sergeyyarkov/modbus-logger.git
synced 2026-10-11 02:18:51 +03:00
progress
This commit is contained in:
parent
8e1481016e
commit
1cdf164fba
@ -13,9 +13,12 @@ export function validateBodyMiddleware(schema) {
|
||||
await schema.validate(req.body);
|
||||
next();
|
||||
} catch (error) {
|
||||
return res.status(400).json({ error: {
|
||||
message: error.errors[0]
|
||||
}});
|
||||
return res.status(400).json({
|
||||
error: {
|
||||
status: 400,
|
||||
message: error.errors[0],
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@ -21,8 +21,8 @@ CREATE TABLE IF NOT EXISTS "app_config" (
|
||||
CREATE TABLE IF NOT EXISTS "modbus_slaves" (
|
||||
"id" INTEGER CHECK (id >= 1 AND id <= 255),
|
||||
"name" VARCHAR(256) NOT NULL,
|
||||
"display_addr" INTEGER CHECK(display_addr >= 0 AND display_addr <= 65534),
|
||||
-- "display_val_format" INTEGER CHECK IN(16, 32) NOT NULL,
|
||||
"display_reg_addr" INTEGER CHECK(display_reg_addr >= 0 AND display_reg_addr <= 65534),
|
||||
"display_reg_format" INTEGER CHECK(display_reg_format IN(16, 32)),
|
||||
"is_logging" BOOLEAN NOT NULL,
|
||||
PRIMARY KEY("id")
|
||||
);
|
||||
|
||||
@ -603,15 +603,21 @@ video {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.m-2 {
|
||||
margin: 0.5rem;
|
||||
}
|
||||
|
||||
.mx-auto {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.my-4 {
|
||||
margin-top: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.my-2 {
|
||||
margin-top: 0.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.ml-auto {
|
||||
margin-left: auto;
|
||||
}
|
||||
@ -640,6 +646,10 @@ video {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.mt-10 {
|
||||
margin-top: 2.5rem;
|
||||
}
|
||||
|
||||
.block {
|
||||
display: block;
|
||||
}
|
||||
@ -664,10 +674,18 @@ video {
|
||||
height: 5rem;
|
||||
}
|
||||
|
||||
.h-full {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.h-screen {
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.max-h-\[520px\] {
|
||||
max-height: 520px;
|
||||
}
|
||||
|
||||
.w-1 {
|
||||
width: 0.25rem;
|
||||
}
|
||||
@ -676,32 +694,12 @@ video {
|
||||
width: 5rem;
|
||||
}
|
||||
|
||||
.w-48 {
|
||||
width: 12rem;
|
||||
}
|
||||
|
||||
.w-72 {
|
||||
width: 18rem;
|
||||
}
|
||||
|
||||
.w-full {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.w-24 {
|
||||
width: 6rem;
|
||||
}
|
||||
|
||||
.w-16 {
|
||||
width: 4rem;
|
||||
}
|
||||
|
||||
.w-12 {
|
||||
width: 3rem;
|
||||
}
|
||||
|
||||
.w-64 {
|
||||
width: 16rem;
|
||||
.min-w-\[256px\] {
|
||||
min-width: 256px;
|
||||
}
|
||||
|
||||
.max-w-2xl {
|
||||
@ -712,6 +710,14 @@ video {
|
||||
max-width: 72rem;
|
||||
}
|
||||
|
||||
.max-w-5xl {
|
||||
max-width: 64rem;
|
||||
}
|
||||
|
||||
.flex-1 {
|
||||
flex: 1 1 0%;
|
||||
}
|
||||
|
||||
.flex-auto {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
@ -730,6 +736,16 @@ video {
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
.cursor-pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.select-none {
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.resize {
|
||||
resize: both;
|
||||
}
|
||||
@ -756,12 +772,22 @@ video {
|
||||
margin-left: calc(0.5rem * calc(1 - var(--tw-space-x-reverse)));
|
||||
}
|
||||
|
||||
.space-y-2 > :not([hidden]) ~ :not([hidden]) {
|
||||
--tw-space-y-reverse: 0;
|
||||
margin-top: calc(0.5rem * calc(1 - var(--tw-space-y-reverse)));
|
||||
margin-bottom: calc(0.5rem * var(--tw-space-y-reverse));
|
||||
}
|
||||
|
||||
.space-y-3 > :not([hidden]) ~ :not([hidden]) {
|
||||
--tw-space-y-reverse: 0;
|
||||
margin-top: calc(0.75rem * calc(1 - var(--tw-space-y-reverse)));
|
||||
margin-bottom: calc(0.75rem * var(--tw-space-y-reverse));
|
||||
}
|
||||
|
||||
.overflow-y-auto {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.rounded-sm {
|
||||
border-radius: 0.125rem;
|
||||
}
|
||||
@ -770,6 +796,14 @@ video {
|
||||
border-width: 1px;
|
||||
}
|
||||
|
||||
.border-2 {
|
||||
border-width: 2px;
|
||||
}
|
||||
|
||||
.border-b {
|
||||
border-bottom-width: 1px;
|
||||
}
|
||||
|
||||
.border-l {
|
||||
border-left-width: 1px;
|
||||
}
|
||||
@ -778,6 +812,10 @@ video {
|
||||
border-right-width: 1px;
|
||||
}
|
||||
|
||||
.border-dashed {
|
||||
border-style: dashed;
|
||||
}
|
||||
|
||||
.fill-blue-600 {
|
||||
fill: #2563eb;
|
||||
}
|
||||
@ -786,6 +824,14 @@ video {
|
||||
padding: 2.5rem;
|
||||
}
|
||||
|
||||
.p-2 {
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
.p-2\.5 {
|
||||
padding: 0.625rem;
|
||||
}
|
||||
|
||||
.p-20 {
|
||||
padding: 5rem;
|
||||
}
|
||||
@ -816,19 +862,13 @@ video {
|
||||
padding-right: 0.625rem;
|
||||
}
|
||||
|
||||
.py-1 {
|
||||
padding-top: 0.25rem;
|
||||
padding-bottom: 0.25rem;
|
||||
.py-3 {
|
||||
padding-top: 0.75rem;
|
||||
padding-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.py-1\.5 {
|
||||
padding-top: 0.375rem;
|
||||
padding-bottom: 0.375rem;
|
||||
}
|
||||
|
||||
.py-8 {
|
||||
padding-top: 2rem;
|
||||
padding-bottom: 2rem;
|
||||
.pt-5 {
|
||||
padding-top: 1.25rem;
|
||||
}
|
||||
|
||||
.text-center {
|
||||
@ -845,6 +885,11 @@ video {
|
||||
line-height: 2.25rem;
|
||||
}
|
||||
|
||||
.text-5xl {
|
||||
font-size: 3rem;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.text-sm {
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.25rem;
|
||||
@ -863,6 +908,11 @@ video {
|
||||
color: rgb(229 231 235 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.text-gray-400 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(156 163 175 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.text-gray-700 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(55 65 81 / var(--tw-text-opacity));
|
||||
@ -921,6 +971,11 @@ button:disabled {
|
||||
pointer-events: all !important;
|
||||
}
|
||||
|
||||
.hover\:bg-gray-50:hover {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(249 250 251 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.dark\:text-gray-600 {
|
||||
--tw-text-opacity: 1;
|
||||
|
||||
@ -5,34 +5,55 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Modbus Logger App</title>
|
||||
<link rel="stylesheet" href="/css/dist.css" />
|
||||
<script defer src="/js/dygraphs.min.js"></script>
|
||||
<script defer type="module" src="/js/app.js"></script>
|
||||
</head>
|
||||
<body class="m-2">
|
||||
<body>
|
||||
<!-- <div class="border p-4" x-data>
|
||||
<span>app state:</span>
|
||||
<pre x-text="JSON.stringify($store.app, undefined, 2)"></pre>
|
||||
</div> -->
|
||||
|
||||
<!-- Root element -->
|
||||
<div x-data x-cloak class="mx-auto max-w-6xl m-2 py-8">
|
||||
<div x-data x-cloak class="mx-auto max-w-5xl flex flex-col h-screen">
|
||||
<!-- Monitoring content -->
|
||||
<main x-bind:hidden="$store.app.currentPage !== 'monitoring'">
|
||||
<main class="flex-1" x-bind:hidden="$store.app.currentPage !== 'monitoring'">
|
||||
<!-- <div>
|
||||
<h1 class="text-3xl font-bold">Monitoring page</h1>
|
||||
<button @click="$store.app.setPage('configuration')" class="btn">Config</button>
|
||||
</div> -->
|
||||
<div class="flex flex-auto flex-row h-screen space-x-2">
|
||||
<div class="border-r w-64 p-4">
|
||||
<div class="w-full border px-2.5 py-1.5 rounded-sm">
|
||||
<h2 class="text-sm">Modbus slave #1</h2>
|
||||
<div class="flex h-full flex-row space-x-2">
|
||||
<div class="border-r min-w-[256px] p-2.5 pt-5">
|
||||
<div class="flex flex-col space-y-2">
|
||||
<span class="italic text-sm">Devices list:</span>
|
||||
<div class="flex flex-col max-h-[520px] overflow-y-auto space-y-2">
|
||||
<div class="w-full select-none border px-2.5 py-3 rounded-sm hover:bg-gray-50 cursor-pointer">
|
||||
<h2 class="text-md font-bold">Device #1</h2>
|
||||
</div>
|
||||
<div class="w-full select-none border px-2.5 py-3 rounded-sm hover:bg-gray-50 cursor-pointer">
|
||||
<h2 class="text-md font-bold">Device #2</h2>
|
||||
</div>
|
||||
<div class="w-full select-none border px-2.5 py-3 rounded-sm hover:bg-gray-50 cursor-pointer">
|
||||
<h2 class="text-md font-bold">Device #3</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full">
|
||||
graph...
|
||||
<div class="w-full text-center border-2 border-dashed rounded-sm hover:bg-gray-50 cursor-pointer">
|
||||
<span class="text-5xl text-gray-400 select-none">+</span>
|
||||
</div>
|
||||
<div class="border-l w-48 p-4">
|
||||
|
||||
</div>
|
||||
<hr class="my-4" />
|
||||
<div><span class="italic text-sm">Connection:</span><br /></div>
|
||||
</div>
|
||||
<div class="flex-auto">
|
||||
<div class="pt-5 border-b">
|
||||
<h2 class="font-bold text-3xl my-2">Device #1</h2>
|
||||
</div>
|
||||
<div class="mt-4">
|
||||
<div id="div_g" style="width: 720px; height: 300px"></div>
|
||||
</div>
|
||||
<hr class="mt-10" />
|
||||
</div>
|
||||
<!-- <div class="border-l"></div> -->
|
||||
</div>
|
||||
</main>
|
||||
|
||||
@ -222,8 +243,7 @@
|
||||
<p>Refresh page to try again.</p>
|
||||
<br />
|
||||
<div class="border p-4">
|
||||
<pre x-text="JSON.stringify($store.app.error, undefined, 2)" x-show="$store.app.error">
|
||||
</pre>
|
||||
<pre x-text="JSON.stringify($store.app.error, undefined, 2)" x-show="$store.app.error"></pre>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
@ -1,9 +1,33 @@
|
||||
// @ts-nocheck
|
||||
import "./alpine.js";
|
||||
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"];
|
||||
|
||||
var data = [];
|
||||
var t = new Date();
|
||||
|
||||
for (var i = 10; i >= 0; i--) {
|
||||
var x = new Date(t.getTime() - i * 1000);
|
||||
data.push([x, Math.random()]);
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
var g = new Dygraph(document.getElementById("div_g"), data, {
|
||||
drawPoints: false,
|
||||
showRoller: false,
|
||||
// valueRange: [0.0, 6.8],
|
||||
labels: ["Time", "Random"],
|
||||
});
|
||||
|
||||
window.intervalId = setInterval(function () {
|
||||
var x = new Date(); // current time
|
||||
var y = Math.random();
|
||||
data.push([x, y]);
|
||||
g.updateOptions({ file: data });
|
||||
}, 1000);
|
||||
});
|
||||
|
||||
document.addEventListener("alpine:init", async () => {
|
||||
Alpine.store("app", {
|
||||
@ -31,7 +55,7 @@ document.addEventListener("alpine:init", async () => {
|
||||
|
||||
if (config) this.config = config;
|
||||
if (is_configured) {
|
||||
const devices = await api.get('/modbus/devices');
|
||||
const devices = await api.get("/modbus/devices");
|
||||
this.devices = devices;
|
||||
this.currentPage = "monitoring";
|
||||
return;
|
||||
@ -39,19 +63,19 @@ document.addEventListener("alpine:init", async () => {
|
||||
|
||||
this.currentPage = "configuration";
|
||||
} catch (error) {
|
||||
this.currentPage = 'error'
|
||||
this.currentPage = "error";
|
||||
this.error = error;
|
||||
}
|
||||
},
|
||||
|
||||
setPage(page) {
|
||||
if (!pages.includes(page)) {
|
||||
this.currentPage = '404'
|
||||
this.currentPage = "404";
|
||||
return;
|
||||
}
|
||||
|
||||
this.currentPage = page;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
Alpine.data("configAppPage", () => ({
|
||||
@ -64,9 +88,15 @@ document.addEventListener("alpine:init", async () => {
|
||||
this.errorMessage = "";
|
||||
const data = { ...this.$store.app.config };
|
||||
const body = {
|
||||
RTU: utils.objKeysExclude(data, ['mb_tcp_ip', 'mb_tcp_port']),
|
||||
TCP: utils.objKeysExclude(data, ['mb_rtu_path', 'mb_rtu_baud', 'mb_rtu_parity', 'mb_rtu_data_bits', 'mb_rtu_stop_bits'])
|
||||
}
|
||||
RTU: utils.objKeysExclude(data, ["mb_tcp_ip", "mb_tcp_port"]),
|
||||
TCP: utils.objKeysExclude(data, [
|
||||
"mb_rtu_path",
|
||||
"mb_rtu_baud",
|
||||
"mb_rtu_parity",
|
||||
"mb_rtu_data_bits",
|
||||
"mb_rtu_stop_bits",
|
||||
]),
|
||||
};
|
||||
await api.patch("/app/config", body[data.mb_connection_type]);
|
||||
await api.post("/modbus/connect");
|
||||
this.$store.app.currentPage = "monitoring";
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import express from "express";
|
||||
import modbusClient from "#root/config/modbus-client.config.js";
|
||||
|
||||
/**
|
||||
* Validators
|
||||
@ -9,7 +8,7 @@ import { appConfigSchema } from "#root/validators/index.js";
|
||||
/**
|
||||
* Controllers
|
||||
*/
|
||||
import { appController, modbusController } from '#root/controllers/index.js'
|
||||
import { appController, modbusController } from "#root/controllers/index.js";
|
||||
|
||||
/**
|
||||
* Middlewares
|
||||
@ -22,7 +21,7 @@ apiRouter.get("/app/config", appController.getConfig);
|
||||
apiRouter.patch("/app/config", validateBodyMiddleware(appConfigSchema), appController.updateConfig);
|
||||
apiRouter.get("/app/is_configured", appController.isConfigured);
|
||||
|
||||
apiRouter.get('/modbus/devices', modbusController.list);
|
||||
apiRouter.get("/modbus/devices", modbusController.list);
|
||||
apiRouter.post("/modbus/connect", modbusController.connect);
|
||||
apiRouter.post("/modbus/close", modbusController.close);
|
||||
apiRouter.get("/modbus/data_stream", modbusController.streamData);
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
import db from '#root/config/database.config.js'
|
||||
import db from "#root/config/database.config.js";
|
||||
|
||||
async function run() {
|
||||
try {
|
||||
await db.migrate({ force: false })
|
||||
console.log('Database initialized.');
|
||||
await db.migrate({ force: false });
|
||||
console.log("Database initialized.");
|
||||
} catch (error) {
|
||||
throw error;
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user