This commit is contained in:
Sergey Yarkov 2023-11-13 16:26:03 +03:00
parent 4edb0f3a9d
commit e2ad549ed1
4 changed files with 98 additions and 69 deletions

View File

@ -1,4 +1,5 @@
import db from "#root/config/database.config.js"; import db from "#root/config/database.config.js";
import cp from 'child_process'
/** /**
* Services * Services
@ -14,7 +15,7 @@ export const appController = {
*/ */
async getConfig(req, res, next) { async getConfig(req, res, next) {
try { try {
const data = await appService.getConfig(db); const data = await appService.getConfig();
if (!data) return res.status(200).json({ config: null }); if (!data) return res.status(200).json({ config: null });
return res.status(200).json({ config: data }); return res.status(200).json({ config: data });
} catch (error) { } catch (error) {
@ -30,7 +31,7 @@ export const appController = {
*/ */
async updateConfig(req, res, next) { async updateConfig(req, res, next) {
try { try {
const config = await appService.getConfig(db); const config = await appService.getConfig();
const payload = req.body; const payload = req.body;
if (!config) await db.run(`INSERT INTO "app_config" (id) VALUES (?)`, [0]); if (!config) await db.run(`INSERT INTO "app_config" (id) VALUES (?)`, [0]);
@ -81,7 +82,7 @@ export const appController = {
*/ */
async isConfigured(req, res, next) { async isConfigured(req, res, next) {
try { try {
const config = await appService.getConfig(db); const config = await appService.getConfig();
if (!config) return res.status(200).json({ is_configured: false }); if (!config) return res.status(200).json({ is_configured: false });
for (const colPrefix of ['mb_tcp', 'mb_rtu']) { for (const colPrefix of ['mb_tcp', 'mb_rtu']) {
@ -98,5 +99,39 @@ export const appController = {
} catch (error) { } catch (error) {
next(error); next(error);
} }
},
/**
* Application memory usage
* @param {import('express').Request} req
* @param {import('express').Response} res
* @param {import('express').NextFunction} next
*/
memoryUsage(req, res, next) {
const memoryUsage = process.memoryUsage();
const bytesToMB = (bytes) => bytes / 10**6;
Object.keys(memoryUsage).forEach(k => (memoryUsage[k] = `${bytesToMB(memoryUsage[k])}MB`));
return res.status(200).json({ data: memoryUsage });
},
/**
* Ping
* @param {import('express').Request} req
* @param {import('express').Response} res
* @param {import('express').NextFunction} next
*/
ping(req, res, next) {
return res.status(200).send('pong');
},
/**
* Send info about latest commit hash
* @param {import('express').Request} req
* @param {import('express').Response} res
* @param {import('express').NextFunction} next
*/
commitHash(req, res, next) {
const hash = cp.execSync('git rev-parse HEAD').toString().trim();
return res.status(200).json({ hash })
} }
} }

BIN
data.db

Binary file not shown.

View File

@ -24,9 +24,9 @@
<div class="border-r fixed bg-white h-screen min-w-[256px] p-2.5 pl-0 pt-5"> <div class="border-r fixed bg-white h-screen min-w-[256px] p-2.5 pl-0 pt-5">
<div class="flex flex-col space-y-1"> <div class="flex flex-col space-y-1">
<!-- List of devices --> <!-- List of devices -->
<span class="italic text-sm">Devices:</span> <span class="italic text-sm">Устройства:</span>
<span x-show="devices.length === 0" class="text-gray-500 text-sm text-center block"> <span x-show="devices.length === 0" class="text-gray-500 text-sm text-center block">
No devices added. Устройства не добавлены.
</span> </span>
<div <div
@add-device.window="devices = [...devices, $event.detail];" @add-device.window="devices = [...devices, $event.detail];"
@ -83,7 +83,7 @@
/> />
</div> </div>
<div> <div>
<label :for="$id('name')"><span class="text-red-500">*</span>Name:</label> <label :for="$id('name')"><span class="text-red-500">*</span>Название устройства:</label>
<input <input
x-model="data.name" x-model="data.name"
placeholder="Device #1" placeholder="Device #1"
@ -94,15 +94,15 @@
/> />
</div> </div>
<div> <div>
<label :for="$id('graph-display')">Graph Display</label> <label :for="$id('graph-display')">Вывод значения на график</label>
<select x-model="graphView" :id="$id('graph-display')"> <select x-model="graphView" :id="$id('graph-display')">
<option value="none">None</option> <option value="none">Нет</option>
<option value="new-value">New value</option> <option value="new-value">Новое значение</option>
</select> </select>
</div> </div>
<div x-show="graphView === 'new-value'"> <div x-show="graphView === 'new-value'">
<div> <div>
<label :for="$id('g_y_label')"><span class="text-red-500">*</span>Value name:</label> <label :for="$id('g_y_label')"><span class="text-red-500">*</span>Название значения:</label>
<input <input
x-model="data.g_y_label" x-model="data.g_y_label"
:id="$id('g_y_label')" :id="$id('g_y_label')"
@ -114,7 +114,7 @@
</div> </div>
<div> <div>
<label :for="$id('g_display_reg_addr')" <label :for="$id('g_display_reg_addr')"
><span class="text-red-500">*</span>Register address:</label ><span class="text-red-500">*</span>Адрес регистра:</label
> >
<input <input
x-model="data.g_display_reg_addr" x-model="data.g_display_reg_addr"
@ -129,7 +129,7 @@
</div> </div>
<div> <div>
<label :for="$id('g_display_reg_type')" <label :for="$id('g_display_reg_type')"
><span class="text-red-500">*</span>Registers type:</label ><span class="text-red-500">*</span>Тип регистра:</label
> >
<select <select
x-model="data.g_display_reg_type" x-model="data.g_display_reg_type"
@ -143,7 +143,7 @@
</div> </div>
<div> <div>
<label :for="$id('g_display_reg_format')" <label :for="$id('g_display_reg_format')"
><span class="text-red-500">*</span>Format (type):</label ><span class="text-red-500">*</span>Формат значения:</label
> >
<select <select
x-model="data.g_display_reg_format" x-model="data.g_display_reg_format"
@ -181,7 +181,7 @@
<!-- Create new device --> <!-- Create new device -->
<button <button
@click="$dispatch('open-device-modal', { @click="$dispatch('open-device-modal', {
labels: { title: 'Create new device', submit: 'Create', cancel: 'Cancel' }, labels: { title: 'Добавить новое устройство', submit: 'Добавить', cancel: 'Отмена' },
actionType: 'CREATE' actionType: 'CREATE'
})" })"
class="w-full min-h-[50px] text-center border-2 border-dashed rounded-sm hover:bg-gray-50 cursor-pointer" class="w-full min-h-[50px] text-center border-2 border-dashed rounded-sm hover:bg-gray-50 cursor-pointer"
@ -192,14 +192,14 @@
<hr class="my-4" /> <hr class="my-4" />
<!-- Connection --> <!-- Connection -->
<div> <div>
<span class="italic text-sm">Connection:</span> <span class="italic text-sm">Соединение:</span>
<div class="flex flex-col mt-1.5"> <div class="flex flex-col mt-1.5">
<span class="text-sm"> <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-green-700">Открыто</b>
<b x-show="!$store.app.is_opened" class="text-red-700">Closed</b> <b x-show="!$store.app.is_opened" class="text-red-700">Закрыто</b>
</span> </span>
<a class="text-sm" @click="$store.app.setPage('configuration')" href="#"> Reconfigurate </a> <a class="text-sm" @click="$store.app.setPage('configuration')" href="#">Настройки</a>
</div> </div>
</div> </div>
</div> </div>
@ -214,21 +214,21 @@
<div> <div>
<a <a
@click="$dispatch('open-device-modal', { @click="$dispatch('open-device-modal', {
labels: { title: `Edit device ${selectedDevice?.name}`, submit: 'Save', cancel: 'Cancel' }, labels: { title: `Редактировать устройство ${selectedDevice?.name}`, submit: 'Save', cancel: 'Отмена' },
device: selectedDevice, device: selectedDevice,
actionType: 'EDIT' actionType: 'EDIT'
})" })"
href="#">Edit</a> href="#">Редактировать</a>
<a @click="$dispatch('dialog', { <a @click="$dispatch('dialog', {
title: 'Remove device', title: 'Удаление устройства',
body: `Remove ${selectedDevice.name}?`, body: `Удалить ${selectedDevice.name}?`,
isOpen: true, isOpen: true,
onConfirm: async () => await removeDevice(selectedDevice.id) onConfirm: async () => await removeDevice(selectedDevice.id)
});" });"
class="text-red-500" class="text-red-500"
href="#" href="#"
> >
Delete Удалить
</a> </a>
</div> </div>
</div> </div>
@ -241,16 +241,16 @@
<div id="div_g" style="width: 720px; height: 300px"></div> <div id="div_g" style="width: 720px; height: 300px"></div>
<div class="flex justify-evenly space-x-2 mt-3"> <div class="flex justify-evenly space-x-2 mt-3">
<span class="italic text-sm" <span class="italic text-sm"
>Value: <span class="font-bold" x-text="selectedDevice?.g_value"></span >Значение: <span class="font-bold" x-text="selectedDevice?.g_value"></span
></span> ></span>
<span class="italic text-sm"> <span class="italic text-sm">
Register: <span x-text="selectedDevice?.g_display_reg_addr"></span Адрес регистра: <span x-text="selectedDevice?.g_display_reg_addr"></span
></span> ></span>
<span class="italic text-sm" <span class="italic text-sm"
>Format: <span x-text="selectedDevice?.g_display_reg_format"></span >Формат регистра: <span x-text="selectedDevice?.g_display_reg_format"></span
></span> ></span>
<span class="italic text-sm" <span class="italic text-sm"
>Register type: <span x-text="selectedDevice?.g_display_reg_type"></span >Тип регистра: <span x-text="selectedDevice?.g_display_reg_type"></span
></span> ></span>
</div> </div>
<hr class="my-3" /> <hr class="my-3" />
@ -275,12 +275,12 @@
/> />
</svg> </svg>
</div> </div>
<p class="mt-4">Loading...</p> <p class="mt-4">Загрузка...</p>
</div> </div>
</div> </div>
<!-- Display values --> <!-- Display values -->
<div x-show="!isLoading"> <div x-show="!isLoading">
<h2 class="text-xl font-bold mb-3 mt-5">Display values</h2> <h2 class="text-xl font-bold mb-3 mt-5">Мониторинг значений</h2>
<div class="mb-2"> <div class="mb-2">
<div class="grid grid-cols-1 md:sm:grid-cols-2 lg:sm:grid-cols-3 gap-2"> <div class="grid grid-cols-1 md:sm:grid-cols-2 lg:sm:grid-cols-3 gap-2">
<template x-for="value in selectedDevice?.display_values" :key="value.id"> <template x-for="value in selectedDevice?.display_values" :key="value.id">
@ -290,7 +290,7 @@
<div class="flex"> <div class="flex">
<!-- Edit display value --> <!-- Edit display value -->
<button @click="$dispatch('open-display-value-modal', { <button @click="$dispatch('open-display-value-modal', {
labels: { title: `Edit ${value.name}`, submit: 'Edit' }, labels: { title: `Редактировать значение ${value.name}`, submit: 'Сохранить' },
actionType: 'EDIT', actionType: 'EDIT',
selectedDevice, selectedDevice,
displayValue: value displayValue: value
@ -322,13 +322,13 @@
</div> </div>
<hr /> <hr />
<div class="flex flex-col"> <div class="flex flex-col">
<span>Value: <span>Значение:
<span x-show="value?.reg_format === 'BOOL'"><input :value="Boolean(value?.data)" type="checkbox" onclick="return false;" /></span> <span x-show="value?.reg_format === 'BOOL'"><input :value="Boolean(value?.data)" type="checkbox" onclick="return false;" /></span>
<span x-show="value?.reg_format !== 'BOOL'" x-text="Number.isInteger(value?.data) ? value?.data : value?.data?.toFixed(3)"></span> <span x-show="value?.reg_format !== 'BOOL'" x-text="Number.isInteger(value?.data) ? value?.data : value?.data?.toFixed(3)"></span>
</span> </span>
<span>Addr: <span x-text="value?.reg_addr"></span></span> <span>Адрес: <span x-text="value?.reg_addr"></span></span>
<span>Type: <span x-text="$store.app.registersTypesMap[value?.reg_type]"></span></span> <span>Тип: <span x-text="$store.app.registersTypesMap[value?.reg_type]"></span></span>
<span>Format: <span x-text="value?.reg_format"></span></span> <span>Формат: <span x-text="value?.reg_format"></span></span>
<span x-show="value.desc" x-text="value?.desc" class="text-sm text-gray-500 italic"></span> <span x-show="value.desc" x-text="value?.desc" class="text-sm text-gray-500 italic"></span>
</div> </div>
</div> </div>
@ -336,7 +336,7 @@
<!-- Create Display Value --> <!-- Create Display Value -->
<button <button
@click="$dispatch('open-display-value-modal', { @click="$dispatch('open-display-value-modal', {
labels: { title: 'Create new display value', submit: 'Create' }, labels: { title: 'Добавить новое значение', submit: 'Добавить' },
actionType: 'CREATE', actionType: 'CREATE',
selectedDevice, selectedDevice,
})" })"
@ -372,18 +372,18 @@
class="flex flex-col space-y-2" class="flex flex-col space-y-2"
> >
<div> <div>
<label :for="$id('name')"><span class="text-red-500">*</span>Name:</label> <label :for="$id('name')"><span class="text-red-500">*</span>Название:</label>
<input <input
x-model="data.name" x-model="data.name"
:id="$id('name')" :id="$id('name')"
name="name" name="name"
placeholder="Value Name" placeholder="PV"
type="text" type="text"
required required
/> />
</div> </div>
<div> <div>
<label :for="$id('reg_addr')"><span class="text-red-500">*</span>Register Address:</label> <label :for="$id('reg_addr')"><span class="text-red-500">*</span>Адрес регистра:</label>
<input <input
x-model.number="data.reg_addr" x-model.number="data.reg_addr"
:id="$id('reg_addr')" :id="$id('reg_addr')"
@ -396,7 +396,7 @@
/> />
</div> </div>
<div> <div>
<label :for="$id('reg_type')"><span class="text-red-500">*</span>Registers Type:</label> <label :for="$id('reg_type')"><span class="text-red-500">*</span>Тип регистра:</label>
<select <select
x-model="data.reg_type" x-model="data.reg_type"
:id="$id('reg_type')" :id="$id('reg_type')"
@ -410,7 +410,7 @@
</select> </select>
</div> </div>
<div> <div>
<label :for="$id('reg_format')"><span class="text-red-500">*</span>Format (type):</label> <label :for="$id('reg_format')"><span class="text-red-500">*</span>Формат значения:</label>
<select <select
x-model="data.reg_format" x-model="data.reg_format"
:id="$id('reg_format')" :id="$id('reg_format')"
@ -438,7 +438,7 @@
class="btn" class="btn"
> >
</button> </button>
<button @click="close()" class="btn bg-red-500">Cancel</button> <button @click="close()" class="btn bg-red-500">Отмена</button>
</div> </div>
</form> </form>
</div> </div>
@ -449,7 +449,7 @@
</div> </div>
</div> </div>
<div class="select-none" x-show="selectedDevice === null"> <div class="select-none" x-show="selectedDevice === null">
<span class="block text-center italic text-gray-500 mt-10">No device selected.</span> <span class="block text-center italic text-gray-500 mt-10">Устройство не выбрано.</span>
</div> </div>
</div> </div>
<!-- <div class="border-l"></div> --> <!-- <div class="border-l"></div> -->
@ -461,15 +461,12 @@
<template x-if="$store.app.currentPage === 'configuration'"> <template x-if="$store.app.currentPage === 'configuration'">
<main class="mt-6 mb-2" 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-5 max-w-md mx-auto mb-2"> <div class="border rounded-sm p-5 max-w-md mx-auto mb-2">
<h2 class="text-2xl font-bold">Setup configuration</h2> <h2 class="text-2xl font-bold">Настройки соединения</h2>
<p class="text-gray-700 italic">
Fill in the required configuration settings to connect to the Modbus network.
</p>
<hr /> <hr />
<form @submit.prevent="configurateApp()" action="#"> <form @submit.prevent="configurateApp()" action="#">
<div> <div>
<div> <div class="mb-3">
<label class="block" for="connection_type"><span class="text-red-500">*</span>Network type</label> <label class="block" for="connection_type"><span class="text-red-500">*</span>Тип сети</label>
<select <select
x-model="$store.app.config.mb_connection_type" x-model="$store.app.config.mb_connection_type"
name="mb_connection_type" name="mb_connection_type"
@ -483,7 +480,7 @@
<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"><span class="text-red-500">*</span>IP</label> <label for="tcp_ip"><span class="text-red-500">*</span>IP адрес</label>
<input <input
x-model="$store.app.config.mb_tcp_ip" x-model="$store.app.config.mb_tcp_ip"
id="tcp_ip" id="tcp_ip"
@ -495,7 +492,7 @@
</div> </div>
<div> <div>
<label for="rtu_port"><span class="text-red-500">*</span>Port</label> <label for="rtu_port"><span class="text-red-500">*</span>Порт</label>
<input <input
x-model.number="$store.app.config.mb_tcp_port" x-model.number="$store.app.config.mb_tcp_port"
id="rtu_port" id="rtu_port"
@ -509,7 +506,7 @@
<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"><span class="text-red-500">*</span>Path</label> <label for="rtu_path"><span class="text-red-500">*</span>Порт</label>
<input <input
id="rtu_path" id="rtu_path"
name="mb_rtu_path" name="mb_rtu_path"
@ -521,7 +518,7 @@
</div> </div>
<div> <div>
<label for="rtu_baud"><span class="text-red-500">*</span>Baud</label> <label for="rtu_baud"><span class="text-red-500">*</span>Скорость (бит/с)</label>
<select <select
name="mb_rtu_baud" name="mb_rtu_baud"
id="rtu_baud" id="rtu_baud"
@ -538,7 +535,7 @@
</div> </div>
<div> <div>
<label for="rtu_parity"><span class="text-red-500">*</span>Parity</label> <label for="rtu_parity"><span class="text-red-500">*</span>Бит четности</label>
<select <select
name="mb_rtu_parity" name="mb_rtu_parity"
id="rtu_parity" id="rtu_parity"
@ -547,12 +544,12 @@
> >
<option value="even">even</option> <option value="even">even</option>
<option value="odd">odd</option> <option value="odd">odd</option>
<option value="none">none</option> <option value="none">нет</option>
</select> </select>
</div> </div>
<div> <div>
<label for="rtu_data_bits"><span class="text-red-500">*</span>Data bits</label> <label for="rtu_data_bits"><span class="text-red-500">*</span>Кол-во битов данных</label>
<select <select
name="mb_rtu_data_bits" name="mb_rtu_data_bits"
id="rtu_data_bits" id="rtu_data_bits"
@ -567,7 +564,7 @@
</div> </div>
<div> <div>
<label for="rtu_stop_bits"><span class="text-red-500">*</span>Stop bits</label> <label for="rtu_stop_bits"><span class="text-red-500">*</span>Стоповых бит</label>
<select <select
name="mb_rtu_stop_bits" name="mb_rtu_stop_bits"
id="rtu_stop_bits" id="rtu_stop_bits"
@ -582,7 +579,7 @@
</div> </div>
<div class="mt-2"> <div class="mt-2">
<label for="rtu_path"><span class="text-red-500">*</span>Log Interval (ms)</label> <label for="rtu_path"><span class="text-red-500">*</span>Интервал опроса (мс)</label>
<input <input
id="log_interval_ms" id="log_interval_ms"
name="log_interval_ms" name="log_interval_ms"
@ -602,7 +599,7 @@
class="btn ml-auto" class="btn ml-auto"
type="submit" type="submit"
> >
Save Сохранить
</button> </button>
</div> </div>
<div> <div>
@ -636,7 +633,7 @@
/> />
</svg> </svg>
</div> </div>
<p class="mt-4">Loading...</p> <p class="mt-4">Загрузка...</p>
</div> </div>
</main> </main>
</template> </template>
@ -690,9 +687,9 @@
@click="confirm()" @click="confirm()"
class="btn" class="btn"
> >
Confirm Подтвердить
</button> </button>
<button @click="close()" class="btn bg-red-500">Cancel</button> <button @click="close()" class="btn bg-red-500">Отмена</button>
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,5 +1,4 @@
import express from 'express' import express from 'express'
import cp from 'child_process'
import { appConfigSchema } from "#root/validators/index.js"; import { appConfigSchema } from "#root/validators/index.js";
import { appController } from "#root/controllers/index.js"; import { appController } from "#root/controllers/index.js";
import { validateBodyMiddleware } from "#root/middlewares/index.js"; import { validateBodyMiddleware } from "#root/middlewares/index.js";
@ -9,10 +8,8 @@ const appRouter = express.Router();
appRouter.get("/config", appController.getConfig); appRouter.get("/config", appController.getConfig);
appRouter.patch("/config", validateBodyMiddleware(appConfigSchema), appController.updateConfig); appRouter.patch("/config", validateBodyMiddleware(appConfigSchema), appController.updateConfig);
appRouter.get("/is_configured", appController.isConfigured); appRouter.get("/is_configured", appController.isConfigured);
appRouter.get('/ping', (req, res) => res.status(200).send('pong')); appRouter.get('/ping', appController.ping);
appRouter.get('/commit-hash', (req, res) => { appRouter.get('/commit-hash', appController.commitHash)
const hash = cp.execSync('git rev-parse HEAD').toString().trim(); appRouter.get('/memory', appController.memoryUsage)
return res.status(200).json({ hash })
})
export { appRouter } export { appRouter }