Files
signalk-plugin/plugin/tools/kiosk/server-layout-store.js
Giuseppe Raffa c2c1598226 feat: Implement rulesets and layout management for kiosk plugin
- Added rulesets manager to handle various data types and updates via HTTP and WebSocket.
- Introduced layout store for managing kiosk layouts with caching and server synchronization.
- Enhanced dashboard and data routes to support new layout and ruleset features.
- Updated kiosk HTML and JavaScript to utilize new layout rendering and data binding.
- Removed obsolete map route and integrated map functionality into the new tile renderer.
- Improved Telegram commands to reflect changes in data structure and logging.
- Refactored weather fetching intervals to prevent multiple instances.
- Added SSE stream for real-time layout updates in the kiosk.
2026-05-12 10:17:54 +02:00

81 lines
2.9 KiB
JavaScript

/**
* Store del kiosk layout lato plugin.
*
* - cache su disco: data/kiosk-layout.json
* - bootstrap HTTP: GET {API_URL}/kiosklayouts/sensor/<SENSOR_ID>/active
* - apply push WS: invocato da socket.js quando arriva _t:'kiosk_layout_update'
* - SSE stream: espone un emitter consumato dalla route /meb/kiosk/stream
*
* Il plugin NON sceglie il layout: lo riceve dal server e lo mostra.
*/
const fs = require('fs').promises;
const path = require('path');
const EventEmitter = require('events');
const configManager = require('../../config/configManager');
const cacheFile = path.join(__dirname, '../../../data/kiosk-layout.json');
const emitter = new EventEmitter();
let current = null;
async function ensureDir() {
try { await fs.mkdir(path.dirname(cacheFile), { recursive: true }); } catch {}
}
async function loadCache() {
try {
const raw = await fs.readFile(cacheFile, 'utf-8');
return JSON.parse(raw);
} catch { return null; }
}
async function saveCache() {
if (!current) return;
try { await ensureDir(); await fs.writeFile(cacheFile, JSON.stringify(current, null, 2)); }
catch (err) { console.warn('[KIOSK|STORE] save cache failed:', err.message); }
}
async function init() {
current = await loadCache();
if (current) {
console.log(`[KIOSK|STORE] cache caricata v=${current.version || '?'} (${current.content?.tiles?.length || 0} tiles)`);
} else {
console.log('[KIOSK|STORE] nessun layout in cache');
}
}
async function bootstrapFromServer() {
const API_URL = process.env.API_URL;
if (!API_URL) { console.warn('[KIOSK|STORE] API_URL non configurato'); return; }
const sensorId = configManager.getSensorName() || process.env.SENSOR_ID;
if (!sensorId) { console.warn('[KIOSK|STORE] SENSOR_ID non configurato'); return; }
try {
const r = await fetch(`${API_URL}/kiosklayouts/sensor/${encodeURIComponent(sensorId)}/active`, {
signal: AbortSignal.timeout(8000)
});
if (!r.ok) { if (r.status !== 404) console.warn('[KIOSK|STORE] bootstrap', r.status); return; }
const layout = await r.json();
await applyRemote(layout);
} catch (err) {
console.warn('[KIOSK|STORE] bootstrap err:', err.message);
}
}
/**
* Applica un layout (dedup per id+version), salva cache, emette 'update'.
*/
async function applyRemote(layout) {
if (!layout || !layout.content) return false;
if (current && current.id === layout.id && current.version === layout.version) return false;
current = layout;
await saveCache();
console.log(`[KIOSK|STORE] applicato v=${layout.version || '?'} (${layout.content.tiles?.length || 0} tiles)`);
emitter.emit('update', current);
return true;
}
function get() { return current; }
function onUpdate(fn) { emitter.on('update', fn); return () => emitter.off('update', fn); }
module.exports = { init, bootstrapFromServer, applyRemote, get, onUpdate };