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:
@@ -1,9 +1,9 @@
|
||||
const router = require('express').Router();
|
||||
const { queryAll, query } = require('../store/redis');
|
||||
const { queryAll, query, hset } = require('../store/redis');
|
||||
const { connectedSensors } = require('../ws/handler');
|
||||
|
||||
/**
|
||||
* GET /sessions — Lista tutte le sessioni attive dei sensori.
|
||||
* Legge da Redis le chiavi sensors:* (scritte da handler.js alla connessione)
|
||||
* GET /sessions — Lista tutte le sessioni dei sensori con metadata e rules versions
|
||||
*/
|
||||
router.get('/', async (req, res) => {
|
||||
try {
|
||||
@@ -16,7 +16,13 @@ router.get('/', async (req, res) => {
|
||||
name,
|
||||
connectedAt: info.timestamp || null,
|
||||
session: info.session || null,
|
||||
sessionLabel: info.sessionLabel || info.session || null,
|
||||
status: info.status || 'unknown',
|
||||
rules: {
|
||||
weather: info.rules_weather || null,
|
||||
data: info.rules_data || null,
|
||||
logs: info.rules_logs || null,
|
||||
}
|
||||
};
|
||||
}
|
||||
res.json(sessions);
|
||||
@@ -27,8 +33,7 @@ router.get('/', async (req, res) => {
|
||||
});
|
||||
|
||||
/**
|
||||
* GET /sessions/pending — Lista token di connessione pendenti.
|
||||
* Legge da Redis le chiavi sensors_pending:* (create da createConnectionToken)
|
||||
* GET /sessions/pending — Lista token di connessione pendenti
|
||||
*/
|
||||
router.get('/pending', async (req, res) => {
|
||||
try {
|
||||
@@ -41,8 +46,7 @@ router.get('/pending', async (req, res) => {
|
||||
});
|
||||
|
||||
/**
|
||||
* GET /sessions/connected — Lista sensori attualmente connessi.
|
||||
* Legge da Redis le chiavi sensor:* (scritte da appendAsConnection in handler.js)
|
||||
* GET /sessions/connected — Lista sensori attualmente connessi
|
||||
*/
|
||||
router.get('/connected', async (req, res) => {
|
||||
try {
|
||||
@@ -63,7 +67,7 @@ router.get('/connected', async (req, res) => {
|
||||
});
|
||||
|
||||
/**
|
||||
* GET /sessions/connected/:id — Verifica se un sensore specifico è connesso.
|
||||
* GET /sessions/connected/:id — Verifica se un sensore specifico è connesso
|
||||
*/
|
||||
router.get('/connected/:id', async (req, res) => {
|
||||
const { id } = req.params;
|
||||
@@ -79,4 +83,38 @@ router.get('/connected/:id', async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* POST /sessions/:id/label — Cambia il label della sessione per un sensore connesso.
|
||||
* Non interrompe il flusso dati. I nuovi punti InfluxDB avranno il nuovo tag.
|
||||
*/
|
||||
router.post('/:id/label', async (req, res) => {
|
||||
const { id } = req.params;
|
||||
const { label } = req.body;
|
||||
|
||||
if (!label || typeof label !== 'string' || label.trim().length === 0) {
|
||||
return res.status(400).json({ error: 'label is required' });
|
||||
}
|
||||
|
||||
const trimmedLabel = label.trim();
|
||||
|
||||
// Trova il WS client connesso
|
||||
const ws = connectedSensors.get(id);
|
||||
if (!ws) {
|
||||
return res.status(404).json({ error: 'sensor not connected' });
|
||||
}
|
||||
|
||||
// Aggiorna in memoria (effetto immediato sui prossimi punti InfluxDB)
|
||||
ws.sessionLabel = trimmedLabel;
|
||||
|
||||
// Aggiorna in Redis per persistenza
|
||||
try {
|
||||
await hset(`sensors:${id}`, 'sessionLabel', trimmedLabel);
|
||||
} catch (err) {
|
||||
console.error('Error updating session label in Redis', err);
|
||||
}
|
||||
|
||||
console.log(`[${id}] Session label changed to: ${trimmedLabel}`);
|
||||
res.json({ status: 'ok', label: trimmedLabel });
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
||||
@@ -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