Add Rulesets page with HTML structure and CSS styles

- Created a new HTML file for the Rulesets page, including a header, toolbar, rules grid, and rule detail popup.
- Implemented JavaScript functionality for loading, filtering, sorting, and managing rules.
- Added CSS styles for the layout, components, and responsive design of the Rulesets page.
This commit is contained in:
Giuseppe Raffa
2026-04-15 08:06:29 +02:00
parent c9402de2e4
commit 3094c06467
13 changed files with 2010 additions and 27 deletions

View File

@@ -3,8 +3,9 @@ const { decode } = require('@msgpack/msgpack');
const { consumeConnectionToken, appendAsConnection, query, hset, del } = require('../store/redis');
const { writeSensorData, queryHistory } = require('../store/influx');
// In-memory map: sensorName → Set<WebSocket>
const sensorWatchers = new Map();
// In-memory registries
const sensorWatchers = new Map(); // sensorName → Set<WebSocket> (watchers)
const connectedSensors = new Map(); // sensorName → WebSocket (sensor clients)
function generateSessionId() {
const num = Math.floor(1000 + Math.random() * 9000);
@@ -71,13 +72,13 @@ function setup(server) {
wss.handleUpgrade(req, socket, head, (ws) => {
ws.sensorName = sensor;
ws.sessionId = generateSessionId();
ws.sessionLabel = ws.sessionId; // default label = sessionId
ws.connectedAt = new Date().toISOString();
ws.rulesVersions = null; // populated by _t:init message
handleSensorConnection(ws);
});
} else if (path === '/live') {
// Accept upgrade without requiring query params.
// The console sends { action: 'watch', sensorId } after connecting.
wss.handleUpgrade(req, socket, head, (ws) => {
handleWatcherConnection(ws);
});
@@ -90,11 +91,14 @@ function setup(server) {
}
function handleSensorConnection(ws) {
const { sensorName, sessionId, connectedAt } = ws;
const { sensorName, sessionId, sessionLabel, connectedAt } = ws;
console.log(`Sensor connected: ${sensorName} (session: ${sessionId})`);
// Register in global registry
connectedSensors.set(sensorName, ws);
appendAsConnection(sensorName, 'connected', connectedAt);
hset(`sensors:${sensorName}`, 'session', sessionId);
hset(`sensors:${sensorName}`, 'session', sessionId, 'sessionLabel', sessionLabel);
const pingInterval = setInterval(() => {
if (ws.readyState === ws.OPEN) ws.ping();
@@ -103,11 +107,28 @@ function handleSensorConnection(ws) {
ws.on('message', (data) => {
try {
const packet = decode(data);
// Messaggio di inizializzazione con versioni rulesets
if (packet._t === 'init') {
ws.rulesVersions = packet.rules || {};
console.log(`[${sensorName}] Rules versions:`, ws.rulesVersions);
// Salva in Redis
const rulesFields = [];
for (const [type, ver] of Object.entries(ws.rulesVersions)) {
rulesFields.push(`rules_${type}`, ver);
}
if (rulesFields.length > 0) {
hset(`sensors:${sensorName}`, ...rulesFields);
}
return; // non scrivere su InfluxDB
}
const { ts, _m, ...fields } = packet;
writeSensorData(fields, sensorName, sessionId, ts);
// Usa sessionLabel (puo' cambiare a runtime dalla console)
writeSensorData(fields, sensorName, ws.sessionLabel, ts);
// Broadcast to watchers as JSON messages grouped by measurement
// Broadcast to watchers
const watchers = sensorWatchers.get(sensorName);
if (watchers && watchers.size > 0) {
const messages = transformPacket(packet);
@@ -128,6 +149,7 @@ function handleSensorConnection(ws) {
ws.on('close', () => {
console.log(`Sensor disconnected: ${sensorName}`);
clearInterval(pingInterval);
connectedSensors.delete(sensorName);
appendAsConnection(sensorName, 'disconnected', new Date().toISOString());
del(`sensors:${sensorName}`);
});
@@ -145,7 +167,6 @@ function handleWatcherConnection(ws) {
const msg = JSON.parse(data.toString());
if (msg.action === 'watch' && msg.sensorId) {
// Unwatch previous sensor if any
if (ws.sensorName) {
sensorWatchers.get(ws.sensorName)?.delete(ws);
if (sensorWatchers.get(ws.sensorName)?.size === 0) {
@@ -155,7 +176,6 @@ function handleWatcherConnection(ws) {
ws.sensorName = msg.sensorId;
// Register as watcher
if (!sensorWatchers.has(msg.sensorId)) {
sensorWatchers.set(msg.sensorId, new Set());
}
@@ -163,14 +183,12 @@ function handleWatcherConnection(ws) {
console.log(`Watcher now watching sensor: ${msg.sensorId}`);
// Send history since sensor connected
try {
const sensorInfo = await query(msg.sensorId, 'sensors');
if (sensorInfo && sensorInfo.timestamp && sensorInfo.session) {
const history = await queryHistory(msg.sensorId, sensorInfo.session, sensorInfo.timestamp);
for (const row of history) {
const ts = new Date(row._time).getTime();
// Send each historical row as individual messages grouped by measurement
const rebuilt = { ts };
for (const [short, { key }] of Object.entries(fieldMapping)) {
const influxField = { t: 'temperature', h: 'humidity', spd: 'speed', cog: 'cog', sog: 'sog', hdg: 'headingTrue', lat: 'latitude', lon: 'longitude' }[short];
@@ -217,4 +235,4 @@ function handleWatcherConnection(ws) {
});
}
module.exports = { setup };
module.exports = { setup, connectedSensors };