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:
@@ -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 };
|
||||
|
||||
Reference in New Issue
Block a user