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
8f1404c299
commit
3dd5ce81c9
@ -5,5 +5,6 @@ import { open } from "sqlite";
|
||||
const sqlite = process.env.NODE_ENV == "development" ? sqlite3.verbose() : sqlite3;
|
||||
|
||||
const db = await open({ filename: "./data.db", driver: sqlite.Database });
|
||||
await db.run(`PRAGMA foreign_keys = ON`);
|
||||
|
||||
export default db;
|
||||
|
||||
@ -116,7 +116,19 @@ export const modbusController = {
|
||||
* @param {import('express').Response} res
|
||||
* @param {import('express').NextFunction} next
|
||||
*/
|
||||
async createDisplayValue(req, res, next) {},
|
||||
async createDisplayValue(req, res, next) {
|
||||
try {
|
||||
const { name, slave_id, reg_addr, reg_format, reg_type } = req.body
|
||||
await db.run(`INSERT INTO "display_values"
|
||||
("name", "slave_id", "reg_addr", "reg_format", "reg_type")
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
`, [name, slave_id, reg_addr, reg_format, reg_type]
|
||||
);
|
||||
return res.status(200).json({ message: 'Created.' });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove display value from modbus slave device
|
||||
@ -133,14 +145,6 @@ export const modbusController = {
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Get latest data log from modbus slave device
|
||||
* @param {import('express').Request} req
|
||||
* @param {import('express').Response} res
|
||||
* @param {import('express').NextFunction} next
|
||||
*/
|
||||
async getLatestLog(req, res, next) {},
|
||||
|
||||
/**
|
||||
* Creates event stream aboud modbus connection status
|
||||
* @param {import('express').Request} req
|
||||
|
||||
@ -610,6 +610,10 @@ video {
|
||||
visibility: collapse;
|
||||
}
|
||||
|
||||
.fixed {
|
||||
position: fixed;
|
||||
}
|
||||
|
||||
.absolute {
|
||||
position: absolute;
|
||||
}
|
||||
@ -995,6 +999,14 @@ video {
|
||||
padding-top: 1.25rem;
|
||||
}
|
||||
|
||||
.pl-\[250px\] {
|
||||
padding-left: 250px;
|
||||
}
|
||||
|
||||
.pl-\[260px\] {
|
||||
padding-left: 260px;
|
||||
}
|
||||
|
||||
.text-left {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
@ -21,7 +21,7 @@
|
||||
<template x-if="$store.app.currentPage === 'monitoring'">
|
||||
<main x-data="monitoringPage" class="flex-1" x-bind:hidden="$store.app.currentPage !== 'monitoring'">
|
||||
<div class="flex h-full flex-row space-x-2">
|
||||
<div class="border-r min-w-[256px] p-2.5 pl-0 pt-5">
|
||||
<div class="border-r fixed h-screen min-w-[256px] p-2.5 pl-0 pt-5">
|
||||
<div class="flex flex-col space-y-1">
|
||||
<!-- List of devices -->
|
||||
<span class="italic text-sm">Devices:</span>
|
||||
@ -209,7 +209,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-auto pl-2">
|
||||
<div class="flex-auto pl-[260px]">
|
||||
<div x-show="selectedDevice != null">
|
||||
<div class="pt-5 border-b flex flex-wrap justify-between items-center pr-6">
|
||||
<div class="mb-2">
|
||||
@ -301,11 +301,11 @@
|
||||
<!-- Remove display value -->
|
||||
<button
|
||||
@click="$dispatch('dialog', {
|
||||
title: 'Remove display value',
|
||||
body: `Remove display value ${value.name}?`,
|
||||
isOpen: true,
|
||||
onConfirm: async () => await removeDisplayValue(value.id)
|
||||
});"
|
||||
title: 'Remove display value',
|
||||
body: `Remove display value ${value.name}?`,
|
||||
isOpen: true,
|
||||
onConfirm: async () => await removeDisplayValue(value.id)
|
||||
});"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||
<path
|
||||
@ -328,13 +328,113 @@
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<!-- Create Display -->
|
||||
<div
|
||||
<!-- Create Display Value -->
|
||||
<button
|
||||
@click="$dispatch('open-create-display-value-modal')"
|
||||
class="flex justify-center min-h-[140px] items-center border-4 text-center border-dashed p-2.5 rounded-sm cursor-pointer hover:bg-gray-50"
|
||||
>
|
||||
<span class="text-5xl text-gray-400 font-bold select-none">+</span>
|
||||
</div>
|
||||
</button>
|
||||
<!-- MODAL: Create new display value -->
|
||||
<template x-teleport="body">
|
||||
<div
|
||||
x-data="displayValueModal"
|
||||
@open-create-display-value-modal.window="open()"
|
||||
@keyup.escape.window="close()"
|
||||
class="backdrop z-50"
|
||||
x-show="isOpen"
|
||||
>
|
||||
<div class="modal">
|
||||
<div class="flex justify-between items-center">
|
||||
<h2 class="font-bold text-lg">Create new display value</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"
|
||||
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>
|
||||
</div>
|
||||
<hr />
|
||||
<form @submit.prevent="create(selectedDevice)" action="#">
|
||||
<div
|
||||
x-id="['name', 'reg_addr', 'reg_type', 'reg_format']"
|
||||
class="flex flex-col space-y-2"
|
||||
>
|
||||
<div>
|
||||
<label :for="$id('name')"><span class="text-red-500">*</span>Name:</label>
|
||||
<input
|
||||
x-model="data.name"
|
||||
:id="$id('name')"
|
||||
name="name"
|
||||
placeholder="Value Name"
|
||||
type="text"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label :for="$id('reg_addr')"><span class="text-red-500">*</span>Register Address:</label>
|
||||
<input
|
||||
x-model.number="data.reg_addr"
|
||||
:id="$id('reg_addr')"
|
||||
name="reg_addr"
|
||||
placeholder="0"
|
||||
type="number"
|
||||
min="0"
|
||||
max="65534"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label :for="$id('reg_type')"><span class="text-red-500">*</span>Registers Type:</label>
|
||||
<select
|
||||
x-model="data.reg_type"
|
||||
:id="$id('reg_type')"
|
||||
name="reg_type"
|
||||
required
|
||||
>
|
||||
<option value="IR">Input Registers</option>
|
||||
<option value="HR">Holding Registers</option>
|
||||
<option value="DI">Discrete Inputs</option>
|
||||
<option value="DO">Coils</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label :for="$id('reg_format')"><span class="text-red-500">*</span>Format (type):</label>
|
||||
<select
|
||||
x-model="data.reg_format"
|
||||
:id="$id('reg_format')"
|
||||
name="reg_format"
|
||||
required
|
||||
>
|
||||
<option value="UI16">Unsigned Int 16</option>
|
||||
<option value="I16">Signed Int 16</option>
|
||||
<option value="UI32">Unsigned Int 32</option>
|
||||
<option value="I32">Signed Int 32</option>
|
||||
<option value="FP32">Floating Point 32</option>
|
||||
<option value="BOOL">Boolean</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<span x-text="error?.message" class="text-red-500"></span>
|
||||
</div>
|
||||
<div class="mt-4 flex justify-end space-x-2">
|
||||
<button
|
||||
x-bind:disabled="isLoading"
|
||||
x-bind:class="isLoading && 'opacity-70'"
|
||||
type="submit"
|
||||
class="btn"
|
||||
>
|
||||
Create
|
||||
</button>
|
||||
<button @click="close()" class="btn bg-red-500">Cancel</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -107,6 +107,49 @@ document.addEventListener("alpine:init", async () => {
|
||||
},
|
||||
}));
|
||||
|
||||
Alpine.data('displayValueModal', () => ({
|
||||
isOpen: false,
|
||||
isLoading: false,
|
||||
error: null,
|
||||
data: {
|
||||
name: '',
|
||||
reg_addr: 0,
|
||||
reg_type: 'IR',
|
||||
reg_format: 'UI16'
|
||||
},
|
||||
resetDataFields() {
|
||||
this.data.name = '';
|
||||
this.data.reg_addr = 0;
|
||||
this.data.reg_type = 'IR';
|
||||
this.data.reg_format = 'UI16';
|
||||
},
|
||||
async create(selectedDevice) {
|
||||
try {
|
||||
this.isLoading = true;
|
||||
const data = {
|
||||
...this.data,
|
||||
slave_id: selectedDevice.id
|
||||
}
|
||||
await api.post('/modbus/create_display-value', data);
|
||||
selectedDevice?.display_values?.push(this.data);
|
||||
this.resetDataFields();
|
||||
this.close();
|
||||
} catch (error) {
|
||||
this.error = error;
|
||||
console.error(error);
|
||||
} finally {
|
||||
this.isLoading = false;
|
||||
}
|
||||
},
|
||||
open() {
|
||||
this.isOpen = true;
|
||||
},
|
||||
close() {
|
||||
this.isOpen = false;
|
||||
this.resetDataFields();
|
||||
},
|
||||
}))
|
||||
|
||||
Alpine.data("monitoringPage", () => ({
|
||||
selectedDevice: null,
|
||||
devices: [],
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import express from "express";
|
||||
import { modbusController } from "#root/controllers/index.js"
|
||||
import { modbusDeviceSchema, removeDisplayValueSchema, removeModbusDeviceSchema } from "#root/validators/index.js";
|
||||
import { createDisplayValueSchema, displayValueSchema, modbusDeviceSchema, removeDisplayValueSchema, removeModbusDeviceSchema } from "#root/validators/index.js";
|
||||
import { validateBodyMiddleware } from "#root/middlewares/index.js";
|
||||
|
||||
const modbusRouter = express.Router();
|
||||
@ -9,6 +9,7 @@ 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('/create_display-value', validateBodyMiddleware(createDisplayValueSchema), modbusController.createDisplayValue);
|
||||
modbusRouter.post('/remove_display-value', validateBodyMiddleware(removeDisplayValueSchema), modbusController.removeDisplayValue);
|
||||
modbusRouter.post("/connect", modbusController.connect);
|
||||
modbusRouter.post("/close", modbusController.close);
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { object, number, boolean, string } from 'yup';
|
||||
import { object, number, string } from 'yup';
|
||||
|
||||
export const displayValueSchema = object({
|
||||
id: number().required(),
|
||||
@ -10,3 +10,6 @@ export const displayValueSchema = object({
|
||||
})
|
||||
|
||||
export const removeDisplayValueSchema = displayValueSchema.pick(['id']);
|
||||
|
||||
// TODO: On creating "reg_format" can be BOOL if "reg_type" is "DI" or "DO"
|
||||
export const createDisplayValueSchema = displayValueSchema.omit(['id'])
|
||||
Loading…
x
Reference in New Issue
Block a user