mirror of
https://github.com/sergeyyarkov/modbus-logger.git
synced 2026-10-11 10:28:51 +03:00
progress
This commit is contained in:
parent
0e10984830
commit
d934e93d93
@ -4,7 +4,7 @@ import pino from "pino-http";
|
||||
/**
|
||||
* Routers
|
||||
*/
|
||||
import { apiRouter } from "#root/routes/api.router.js";
|
||||
import { apiRouter } from "#root/routes/index.js";
|
||||
|
||||
/**
|
||||
* Middlewares
|
||||
|
||||
@ -1,7 +1,4 @@
|
||||
import Modbus from "modbus-serial";
|
||||
|
||||
const modbusClient = new Modbus();
|
||||
|
||||
modbusClient.on("close", () => console.log("Modbus connection closed."));
|
||||
|
||||
export default modbusClient;
|
||||
|
||||
@ -51,6 +51,7 @@ export const modbusController = {
|
||||
if (!modbusClient.isOpen) return res.status(400).json({ message: "Already closed." });
|
||||
modbusClient.close(undefined);
|
||||
modbusClient.destroy(undefined);
|
||||
return res.status(200).json({ message: 'Closed.' });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
@ -77,7 +78,22 @@ export const modbusController = {
|
||||
* @param {import('express').Response} res
|
||||
* @param {import('express').NextFunction} next
|
||||
*/
|
||||
async createSlaveDevice(req, res, next) {},
|
||||
async createDevice(req, res, next) {
|
||||
try {
|
||||
await db.run(`INSERT INTO "modbus_slaves" (
|
||||
"id",
|
||||
"name",
|
||||
"g_display_reg_addr",
|
||||
"g_display_reg_format",
|
||||
"g_y_label",
|
||||
"is_logging")
|
||||
VALUES (?, ?, ?, ?, ?, ?)`,
|
||||
[...req.body]);
|
||||
return res.send(200).json({ message: 'Device created.', data: req.body });
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove modbus slave device
|
||||
@ -85,7 +101,21 @@ export const modbusController = {
|
||||
* @param {import('express').Response} res
|
||||
* @param {import('express').NextFunction} next
|
||||
*/
|
||||
async removeSlaveDevice(req, res, next) {
|
||||
async updateDevice(req, res, next) {
|
||||
try {
|
||||
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove modbus slave device
|
||||
* @param {import('express').Request} req
|
||||
* @param {import('express').Response} res
|
||||
* @param {import('express').NextFunction} next
|
||||
*/
|
||||
async removeDevice(req, res, next) {
|
||||
try {
|
||||
const device = await db.get(`SELECT id FROM "modbus_slaves" WHERE id = ?`, [req.body.id]);
|
||||
if (!device) return res.status(404).json({ message: 'Device not found.' });
|
||||
|
||||
7
index.d.ts
vendored
7
index.d.ts
vendored
@ -10,10 +10,3 @@ export type AppConfig = {
|
||||
log_interval_ms: number;
|
||||
};
|
||||
|
||||
export type SlaveModbusDevice = {
|
||||
id: number;
|
||||
name: string;
|
||||
display_reg_addr: number | null;
|
||||
display_reg_format: number | null;
|
||||
is_logging: boolean;
|
||||
}
|
||||
@ -682,6 +682,18 @@ video {
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
.mb-1 {
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.mb-2 {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.mb-10 {
|
||||
margin-bottom: 2.5rem;
|
||||
}
|
||||
|
||||
.block {
|
||||
display: block;
|
||||
}
|
||||
@ -714,8 +726,12 @@ video {
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.max-h-\[520px\] {
|
||||
max-height: 520px;
|
||||
.max-h-\[400px\] {
|
||||
max-height: 400px;
|
||||
}
|
||||
|
||||
.min-h-\[50px\] {
|
||||
min-height: 50px;
|
||||
}
|
||||
|
||||
.w-1 {
|
||||
|
||||
@ -27,31 +27,34 @@
|
||||
<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-1">
|
||||
<!-- List of devices -->
|
||||
<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
|
||||
>
|
||||
<div class="flex flex-col max-h-[520px] overflow-y-auto space-y-2">
|
||||
<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-[400px] overflow-y-auto space-y-2">
|
||||
<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"
|
||||
class="w-full min-h-[50px] 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>
|
||||
</button>
|
||||
</template>
|
||||
</div>
|
||||
<button class="w-full text-center border-2 border-dashed rounded-sm hover:bg-gray-50 cursor-pointer">
|
||||
<!-- Add new device -->
|
||||
<button class="w-full min-h-[50px] text-center border-2 border-dashed rounded-sm hover:bg-gray-50 cursor-pointer">
|
||||
<span class="text-4xl text-gray-400 select-none">+</span>
|
||||
</button>
|
||||
</div>
|
||||
<hr class="my-4" />
|
||||
<!-- Connection -->
|
||||
<div>
|
||||
<span class="italic text-sm">Connection:</span>
|
||||
<div class="flex flex-col mt-1.5">
|
||||
<span class="text-sm"
|
||||
>Status:
|
||||
<span class="text-sm">
|
||||
Status:
|
||||
<b x-show="$store.app.is_opened" class="text-green-700">Opened</b>
|
||||
<b x-show="!$store.app.is_opened" class="text-red-700">Closed</b>
|
||||
</span>
|
||||
@ -63,20 +66,27 @@
|
||||
<div x-show="selectedDevice != null">
|
||||
<div class="pt-5 border-b flex justify-between items-center pr-6">
|
||||
<h2 x-text="selectedDevice?.name" class="font-bold text-3xl my-2"></h2>
|
||||
<!-- Device create, delete -->
|
||||
<div>
|
||||
<a href="#">Edit</a>
|
||||
<a
|
||||
@click="$store.confirmDialog.open('Remove device', `Delete ${selectedDevice.name}? All logs will be cleared.`, removeDevice, selectedDevice.id)"
|
||||
<a @click="$dispatch('dialog', {
|
||||
title: 'Remove device',
|
||||
body: `Remove ${selectedDevice.name}? All logs will be cleared.`,
|
||||
isOpen: true,
|
||||
onConfirm: async () => await removeDevice(selectedDevice.id)
|
||||
});"
|
||||
class="text-red-500"
|
||||
href="#"
|
||||
>Delete</a
|
||||
>
|
||||
href="#">
|
||||
Delete
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Display Graph -->
|
||||
<div class="mt-4">
|
||||
<div id="div_g" x-ref="graph" style="width: 720px; height: 300px"></div>
|
||||
</div>
|
||||
<hr class="mt-10" />
|
||||
<!-- Display values -->
|
||||
<h2 class="text-xl font-bold mb-3">Display values</h2>
|
||||
</div>
|
||||
<div class="select-none" x-show="selectedDevice === null">
|
||||
@ -90,7 +100,7 @@
|
||||
|
||||
<!-- Configuration content -->
|
||||
<template x-if="$store.app.currentPage === 'configuration'">
|
||||
<main class="mt-6" x-data="configAppPage" x-bind:hidden="$store.app.currentPage !== 'configuration'">
|
||||
<main class="mt-6 mb-2" x-data="configAppPage" x-bind:hidden="$store.app.currentPage !== 'configuration'">
|
||||
<div class="border rounded-sm p-4 max-w-2xl mx-auto">
|
||||
<h2 class="text-2xl font-bold">Setup configuration</h2>
|
||||
<p class="text-gray-700 italic">
|
||||
@ -295,16 +305,17 @@
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<!-- Confirm dialog -->
|
||||
<div x-show="$store.confirmDialog.isOpen" class="backdrop z-50">
|
||||
<!-- Dialog -->
|
||||
<div x-data="dialog">
|
||||
<template @dialog.window="open($event)" x-if="isOpen">
|
||||
<div 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 class="flex justify-between items-center">
|
||||
<h2 x-text="$store.confirmDialog.labels.title" class="font-bold text-lg"></h2>
|
||||
<button @click="$store.confirmDialog.close()" aria-label="Close">
|
||||
<h2 x-text="labels.title" class="font-bold text-lg"></h2>
|
||||
<button @click="close()" aria-label="Close">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||
<path
|
||||
fill="currentColor"
|
||||
@ -314,13 +325,15 @@
|
||||
</button>
|
||||
</div>
|
||||
<hr />
|
||||
<p x-text="$store.confirmDialog.labels.body"></p>
|
||||
<p x-text="labels.body"></p>
|
||||
<div class="mt-4 flex justify-end space-x-2">
|
||||
<button @click="$store.confirmDialog.confirm()" class="btn">Confirm</button>
|
||||
<button @click="$store.confirmDialog.close()" class="btn bg-red-500">Cancel</button>
|
||||
<button x-bind:disabled="isLoading" x-bind:class="isLoading && 'opacity-70'" @click="confirm()" class="btn">Confirm</button>
|
||||
<button @click="close()" class="btn bg-red-500">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -68,7 +68,7 @@ document.addEventListener("alpine:init", async () => {
|
||||
showRoller: false,
|
||||
ylabel: "Y Label",
|
||||
valueRange: [0.0, 1.8],
|
||||
//labels: ["Time", "Random"],
|
||||
// labels: ["Time", "Random"],
|
||||
});
|
||||
this.devices = await api.get("/modbus/devices");
|
||||
window.intervalId = setInterval(() => {
|
||||
@ -86,10 +86,10 @@ document.addEventListener("alpine:init", async () => {
|
||||
graphData = [];
|
||||
this.selectedDevice = device;
|
||||
},
|
||||
async removeDevice(id, ctx) {
|
||||
async removeDevice(id) {
|
||||
try {
|
||||
//await api.post('/modbus/remove_device', { id });
|
||||
this.devices = _this.devices.filter((d) => d.id != id);
|
||||
await api.post('/modbus/remove_device', { id });
|
||||
this.devices = this.devices.filter((d) => d.id != id);
|
||||
this.selectedDevice = null;
|
||||
} catch (error) {
|
||||
console.error("Error while removing device.", error);
|
||||
@ -128,28 +128,37 @@ document.addEventListener("alpine:init", async () => {
|
||||
},
|
||||
}));
|
||||
|
||||
Alpine.store("confirmDialog", {
|
||||
Alpine.data("dialog", () => ({
|
||||
isOpen: false,
|
||||
isLoading: false,
|
||||
labels: {
|
||||
title: "Confirm dialog",
|
||||
body: "Are you sure you want to perform this action?",
|
||||
},
|
||||
cb: {
|
||||
fn: null,
|
||||
args: [],
|
||||
},
|
||||
open(title, body, cb, ...args) {
|
||||
this.isOpen = true;
|
||||
this.labels = { title, body };
|
||||
this.cb.fn = cb;
|
||||
this.cb.args = args;
|
||||
},
|
||||
cb: null,
|
||||
onConfirm: () => undefined,
|
||||
onError: () => undefined,
|
||||
confirm() {
|
||||
this.cb.fn && this.cb.fn(...this.cb.args);
|
||||
if (this.onConfirm.constructor.name === 'AsyncFunction') {
|
||||
this.isLoading = true;
|
||||
this.onConfirm()
|
||||
.then(() => this.close())
|
||||
.catch((e) => this.onError(e))
|
||||
.finally(() => this.isLoading = false)
|
||||
} else {
|
||||
this.onConfirm();
|
||||
this.close();
|
||||
}
|
||||
},
|
||||
close() {
|
||||
this.isOpen = false;
|
||||
this.cb = null;
|
||||
},
|
||||
});
|
||||
open(event) {
|
||||
const { detail } = event;
|
||||
this.isOpen = detail.isOpen;
|
||||
this.onConfirm = detail.onConfirm;
|
||||
this.labels = { title: detail.title, body: detail.body };
|
||||
}
|
||||
}));
|
||||
});
|
||||
|
||||
@ -1,31 +0,0 @@
|
||||
import express from "express";
|
||||
|
||||
/**
|
||||
* Validators
|
||||
*/
|
||||
import { appConfigSchema, removeModbusDeviceSchema } from "#root/validators/index.js";
|
||||
|
||||
/**
|
||||
* Controllers
|
||||
*/
|
||||
import { appController, modbusController } from "#root/controllers/index.js";
|
||||
|
||||
/**
|
||||
* Middlewares
|
||||
*/
|
||||
import { validateBodyMiddleware } from "#root/middlewares/index.js";
|
||||
|
||||
const apiRouter = express.Router();
|
||||
|
||||
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.post('/modbus/remove_device', validateBodyMiddleware(removeModbusDeviceSchema), modbusController.removeSlaveDevice);
|
||||
apiRouter.post("/modbus/connect", modbusController.connect);
|
||||
apiRouter.post("/modbus/close", modbusController.close);
|
||||
apiRouter.get("/modbus/data_stream", modbusController.streamData);
|
||||
apiRouter.get('/modbus/status', modbusController.status);
|
||||
|
||||
export { apiRouter };
|
||||
14
routes/api/index.js
Normal file
14
routes/api/index.js
Normal file
@ -0,0 +1,14 @@
|
||||
import express from "express";
|
||||
import { modbusRouter } from './modbus.router.js'
|
||||
import { appConfigSchema } from "#root/validators/index.js";
|
||||
import { appController } from "#root/controllers/index.js";
|
||||
import { validateBodyMiddleware } from "#root/middlewares/index.js";
|
||||
|
||||
const apiRouter = express.Router();
|
||||
|
||||
apiRouter.use("/modbus", modbusRouter);
|
||||
apiRouter.get("/app/config", appController.getConfig);
|
||||
apiRouter.patch("/app/config", validateBodyMiddleware(appConfigSchema), appController.updateConfig);
|
||||
apiRouter.get("/app/is_configured", appController.isConfigured);
|
||||
|
||||
export { apiRouter };
|
||||
17
routes/api/modbus.router.js
Normal file
17
routes/api/modbus.router.js
Normal file
@ -0,0 +1,17 @@
|
||||
import express from "express";
|
||||
import { modbusController } from "#root/controllers/index.js"
|
||||
import { modbusDeviceSchema, removeModbusDeviceSchema } from "#root/validators/index.js";
|
||||
import { validateBodyMiddleware } from "#root/middlewares/index.js";
|
||||
|
||||
const modbusRouter = express.Router();
|
||||
|
||||
modbusRouter.get("/devices", modbusController.list);
|
||||
modbusRouter.post("/create_device", validateBodyMiddleware(modbusDeviceSchema), modbusController.createDevice)
|
||||
modbusRouter.post('/remove_device', validateBodyMiddleware(removeModbusDeviceSchema), modbusController.removeDevice);
|
||||
modbusRouter.post('/update_device', validateBodyMiddleware(modbusDeviceSchema), modbusController.updateDevice);
|
||||
modbusRouter.post("/connect", modbusController.connect);
|
||||
modbusRouter.post("/close", modbusController.close);
|
||||
modbusRouter.get("/data_stream", modbusController.streamData);
|
||||
modbusRouter.get('/status', modbusController.status);
|
||||
|
||||
export { modbusRouter }
|
||||
@ -1 +1 @@
|
||||
export * from './api.router.js';
|
||||
export * from './api/index.js';
|
||||
|
||||
@ -17,4 +17,4 @@ export const appConfigSchema = object({
|
||||
log_interval_ms: number().min(1000).required(),
|
||||
});
|
||||
|
||||
export const removeModbusDeviceSchema = object({ id: number().max(255).min(1).required() })
|
||||
export * from './modbus-device.schema.js'
|
||||
12
validators/modbus-device.schema.js
Normal file
12
validators/modbus-device.schema.js
Normal file
@ -0,0 +1,12 @@
|
||||
import { object, number, boolean, string } from 'yup';
|
||||
|
||||
export const modbusDeviceSchema = object({
|
||||
id: number().required(),
|
||||
name: string().required(),
|
||||
g_display_reg_addr: number().nullable(),
|
||||
g_display_reg_format: number().oneOf([16, 32]).nullable(),
|
||||
g_y_label: string().max(40).nullable(),
|
||||
is_logging: boolean().required()
|
||||
});
|
||||
|
||||
export const removeModbusDeviceSchema = modbusDeviceSchema.pick(['id']);
|
||||
Loading…
x
Reference in New Issue
Block a user