Aggiunta stili CSS per Kiosk, struttura HTML per la Mappa e Riferimenti ai Sensori
• Creato un nuovo file CSS per gli stili del chiosco (kiosk) con variabili, stili per le schede (card) e animazioni. • Aggiunto un file HTML per l'interfaccia della mappa utilizzando Mapbox, inclusi gli stili e il JavaScript per le funzionalità della mappa. • Introdotto un file JSON per i riferimenti ai sensori, definendo percorsi ed elementi per i dati di temperatura, vento, onde, posizione, batteria, motore e sistema. Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
27
plugin/routes/collection/cloud.js
Normal file
27
plugin/routes/collection/cloud.js
Normal file
@@ -0,0 +1,27 @@
|
||||
const router = require('express').Router();
|
||||
const {
|
||||
FORECAST_CURRENT,
|
||||
FORECAST_HOURLY,
|
||||
MARINE_CURRENT,
|
||||
MARINE_HOURLY,
|
||||
LOG_PATHS
|
||||
} = require('../../rules');
|
||||
|
||||
const api_url = process.env.API_URL || 'http://api-services:3003';
|
||||
|
||||
router.get('/status', (req, res) => {
|
||||
res.json({ cloud: 'active', api: api_url, version: '2.0' });
|
||||
});
|
||||
|
||||
// Ritorna la configurazione statica corrente
|
||||
router.get('/config', (req, res) => {
|
||||
res.json({
|
||||
forecast_current: FORECAST_CURRENT,
|
||||
forecast_hourly: FORECAST_HOURLY,
|
||||
marine_current: MARINE_CURRENT,
|
||||
marine_hourly: MARINE_HOURLY,
|
||||
log_paths: LOG_PATHS
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
16
plugin/routes/collection/dashboard.js
Normal file
16
plugin/routes/collection/dashboard.js
Normal file
@@ -0,0 +1,16 @@
|
||||
const router = require('express').Router();
|
||||
const express = require('express');
|
||||
const path = require('path')
|
||||
|
||||
|
||||
const kioskPath = path.join(__dirname, '../../tools/kiosk');
|
||||
|
||||
router.use('/', express.static(kioskPath));
|
||||
router.get('/', (req, res) => {
|
||||
res.sendFile(path.join(kioskPath, 'dashboard.html'));
|
||||
});
|
||||
|
||||
|
||||
router.get('/api/', (req, res) => {});
|
||||
|
||||
module.exports = router;
|
||||
38
plugin/routes/collection/data.js
Normal file
38
plugin/routes/collection/data.js
Normal file
@@ -0,0 +1,38 @@
|
||||
const router = require('express').Router();
|
||||
const db = require('../../config/skFlow')
|
||||
|
||||
const config = require('../../config/configManager.js')
|
||||
|
||||
router.get('/', (req, res) => {
|
||||
const { path } = req.query;
|
||||
const data = db.get(path);
|
||||
res.json(data);
|
||||
});
|
||||
|
||||
router.get('/', (req, res) => {
|
||||
const { source } = req.query;
|
||||
const data = db.getBySource(source);
|
||||
res.json(data);
|
||||
});
|
||||
|
||||
router.get('/info', (req, res) => {
|
||||
const info = {
|
||||
|
||||
telegram: config.getTelegramToken(),
|
||||
|
||||
sensor: {
|
||||
name: config.getSensorName(),
|
||||
code: config.getSensorCode()
|
||||
},
|
||||
|
||||
other: {
|
||||
api_url: process.env.API_URL,
|
||||
realtime_url: process.env.REALTIME_URL,
|
||||
realtime_socket_url: process.env.REALTIME_SOCKET_URL,
|
||||
reconnect_delay: config.getReconnectDelay()
|
||||
}
|
||||
}
|
||||
res.json(info);
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
34
plugin/routes/collection/kiosk.js
Normal file
34
plugin/routes/collection/kiosk.js
Normal file
@@ -0,0 +1,34 @@
|
||||
const router = require('express').Router();
|
||||
const express = require('express');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const configManager = require('../../config/configManager.js');
|
||||
|
||||
const kioskPath = path.join(__dirname, '../../tools/kiosk');
|
||||
const htmlFile = path.join(kioskPath, 'kiosk.html');
|
||||
|
||||
router.use('/', express.static(kioskPath));
|
||||
|
||||
router.get('/', (req, res) => {
|
||||
const apiUrl = process.env.API_URL || 'https://api.mebboat.it';
|
||||
const realtimeUrl = process.env.REALTIME_URL || 'https://realtime.mebboat.it';
|
||||
const realtimeWsUrl = process.env.REALTIME_SOCKET_URL || 'wss://realtime.mebboat.it';
|
||||
const sensorCode = configManager.getSensorCode();
|
||||
const sensorName = configManager.getSensorName();
|
||||
|
||||
const esc = (s) => String(s || '').replace(/"/g, '"');
|
||||
const metas = `
|
||||
<meta name="api-url" content="${esc(apiUrl)}">
|
||||
<meta name="realtime-url" content="${esc(realtimeUrl)}">
|
||||
<meta name="realtime-ws-url" content="${esc(realtimeWsUrl)}">
|
||||
<meta name="sensor-code" content="${esc(sensorCode)}">
|
||||
<meta name="sensor-name" content="${esc(sensorName)}">
|
||||
`;
|
||||
let html;
|
||||
try { html = fs.readFileSync(htmlFile, 'utf8'); }
|
||||
catch (e) { return res.status(500).send('kiosk.html not found'); }
|
||||
html = html.replace('</head>', metas + '</head>');
|
||||
res.set('Content-Type', 'text/html').send(html);
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
9
plugin/routes/collection/map.js
Normal file
9
plugin/routes/collection/map.js
Normal file
@@ -0,0 +1,9 @@
|
||||
const router = require('express').Router();
|
||||
const path = require('path')
|
||||
//Endpoints per controllare lo stato di un servizio di mappe da implementare poi..
|
||||
|
||||
router.get('/', (req, res) => {
|
||||
res.sendFile(path.join(__dirname, '../tools/map/map.html'));
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
55
plugin/routes/collection/rec.js
Normal file
55
plugin/routes/collection/rec.js
Normal file
@@ -0,0 +1,55 @@
|
||||
const core = require('../../cores/logs.local')
|
||||
const router = require('express').Router();
|
||||
|
||||
|
||||
router.post('/start', async (req, res) => {
|
||||
const { name } = req.body;
|
||||
const session = await core.startRecording(name);
|
||||
res.status(200).send({
|
||||
status: 'Started',
|
||||
session: session
|
||||
});
|
||||
});
|
||||
|
||||
router.get('/', (req, res) => {
|
||||
const session = core.getSession();
|
||||
if (session) {
|
||||
res.status(200).send(session);
|
||||
} else {
|
||||
res.status(404).send({ session: 'Nessuna sessione attiva' });
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/list', async (req, res) => {
|
||||
const logs = await core.listLogs();
|
||||
res.status(200).send(logs);
|
||||
});
|
||||
|
||||
router.get('/:log', async (req, res) => {
|
||||
const { log } = req.params
|
||||
const data = await core.getLog(log);
|
||||
if (data) {
|
||||
res.status(200).send(data);
|
||||
} else {
|
||||
res.status(404).send({ error: 'Log non trovato' });
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/download/:name', (req, res) => {
|
||||
const name = req.params.name;
|
||||
const filePath = core.getLogFile(name);
|
||||
if (filePath) {
|
||||
res.download(filePath);
|
||||
} else {
|
||||
res.status(404).send({ error: 'File non trovato' });
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/stop', async (req, res) => {
|
||||
await core.stopRecording();
|
||||
res.status(200).send({
|
||||
status: 'Stopped'
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
@@ -1,56 +0,0 @@
|
||||
/**
|
||||
* Registers dataset recording control routes.
|
||||
* @param {Object} router - Route wrapper with get/post methods
|
||||
* @param {Object} app - SignalK app instance
|
||||
*/
|
||||
function registerDatasetRoutes(router, app) {
|
||||
router.post("/dataset/start", (req, res) => {
|
||||
try {
|
||||
if (!app.datasetControl) {
|
||||
return res.status(503).json({ error: "Dataset control not available" });
|
||||
}
|
||||
const result = app.datasetControl.start();
|
||||
res.json({ success: result, message: result ? "Recording started" : "Already recording" });
|
||||
} catch (e) {
|
||||
res.status(500).json({ error: e.message });
|
||||
}
|
||||
});
|
||||
|
||||
router.post("/dataset/stop", (req, res) => {
|
||||
try {
|
||||
if (!app.datasetControl) {
|
||||
return res.status(503).json({ error: "Dataset control not available" });
|
||||
}
|
||||
const result = app.datasetControl.stop();
|
||||
res.json({ success: result, message: result ? "Recording stopped" : "No active recording" });
|
||||
} catch (e) {
|
||||
res.status(500).json({ error: e.message });
|
||||
}
|
||||
});
|
||||
|
||||
router.post("/dataset/restart", (req, res) => {
|
||||
try {
|
||||
if (!app.datasetControl) {
|
||||
return res.status(503).json({ error: "Dataset control not available" });
|
||||
}
|
||||
const result = app.datasetControl.restart();
|
||||
res.json({ success: result, message: "Recording restarted" });
|
||||
} catch (e) {
|
||||
res.status(500).json({ error: e.message });
|
||||
}
|
||||
});
|
||||
|
||||
router.get("/dataset/status", (req, res) => {
|
||||
try {
|
||||
if (!app.datasetControl) {
|
||||
return res.status(503).json({ error: "Dataset control not available" });
|
||||
}
|
||||
const status = app.datasetControl.getStatus();
|
||||
res.json(status);
|
||||
} catch (e) {
|
||||
res.status(500).json({ error: e.message });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = registerDatasetRoutes;
|
||||
@@ -1,63 +0,0 @@
|
||||
const { getForecast, getSeaConditions } = require("../api_models/openmeteo.js");
|
||||
|
||||
/**
|
||||
* Registers forecast-related routes.
|
||||
* @param {Object} router - Route wrapper with get/post methods
|
||||
* @param {Object} app - SignalK app instance
|
||||
*/
|
||||
function registerForecastRoutes(router, app) {
|
||||
// Get current forecast data directly from OpenMeteo
|
||||
router.get("/forecasts/data", async (req, res) => {
|
||||
try {
|
||||
const position = app.getSelfPath('navigation.position')?.value;
|
||||
|
||||
if (!position?.latitude || !position?.longitude) {
|
||||
return res.status(503).json({ error: "Position not available" });
|
||||
}
|
||||
|
||||
const mode = req.query.mode || 'both';
|
||||
const [forecastData, wavesData] = await Promise.all([
|
||||
getForecast(position, { mode }),
|
||||
getSeaConditions(position, { mode })
|
||||
]);
|
||||
|
||||
res.status(200).json({
|
||||
forecast: forecastData,
|
||||
sea: wavesData
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('[MEB] Error in /meb/forecasts/data:', error);
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// Force update: fetch fresh hourly data from OpenMeteo
|
||||
router.post("/forecasts/update", async (req, res) => {
|
||||
try {
|
||||
const position = app.getSelfPath('navigation.position')?.value;
|
||||
|
||||
if (!position?.latitude || !position?.longitude) {
|
||||
return res.status(503).json({ error: "Position not available" });
|
||||
}
|
||||
|
||||
const [forecastData, wavesData] = await Promise.all([
|
||||
getForecast(position, { mode: 'both' }),
|
||||
getSeaConditions(position, { mode: 'both' })
|
||||
]);
|
||||
|
||||
if (!forecastData?.hourly || !wavesData?.hourly) {
|
||||
return res.status(500).json({ error: "Hourly data not available from API" });
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
forecast: forecastData,
|
||||
sea: wavesData
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('[MEB] Error in /meb/forecasts/update:', error);
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = registerForecastRoutes;
|
||||
@@ -1,30 +0,0 @@
|
||||
const path = require("path");
|
||||
|
||||
const websPath = path.join(__dirname, "..", "public");
|
||||
|
||||
/**
|
||||
* Registers helm/steering support routes.
|
||||
* @param {Object} router - Route wrapper with get/post methods
|
||||
*/
|
||||
function registerHelmRoutes(router) {
|
||||
router.get("/helm", (req, res) => {
|
||||
try {
|
||||
const side = req.query.side || "destra";
|
||||
const helmPath = path.join(websPath, "steering_support", `helm_steering_${side}.html`);
|
||||
res.status(200).sendFile(helmPath);
|
||||
} catch (e) {
|
||||
res.status(500).json({ error: e.message });
|
||||
}
|
||||
});
|
||||
|
||||
router.get("/helm/support", (req, res) => {
|
||||
try {
|
||||
const indexPath = path.join(websPath, "steering_support", "steering_support.html");
|
||||
res.status(200).sendFile(indexPath);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = registerHelmRoutes;
|
||||
@@ -1,34 +0,0 @@
|
||||
const registerMapRoutes = require("./map");
|
||||
const registerHelmRoutes = require("./helm");
|
||||
const registerDatasetRoutes = require("./dataset");
|
||||
const registerForecastRoutes = require("./forecasts");
|
||||
const registerTelegramRoutes = require("./telegram");
|
||||
|
||||
/**
|
||||
* Registers all plugin routes under the /meb prefix.
|
||||
* @param {Object} app - SignalK app instance
|
||||
* @param {Object} settings - Plugin settings
|
||||
*/
|
||||
module.exports = function (app, settings) {
|
||||
const router = {
|
||||
get: (subPath, handler) => {
|
||||
const fullPath = '/meb' + (subPath.startsWith('/') ? subPath : `/${subPath}`);
|
||||
app.get(fullPath, handler);
|
||||
},
|
||||
post: (subPath, handler) => {
|
||||
const fullPath = '/meb' + (subPath.startsWith('/') ? subPath : `/${subPath}`);
|
||||
app.post(fullPath, handler);
|
||||
}
|
||||
};
|
||||
|
||||
// Health check
|
||||
router.get("/ping", (req, res) => {
|
||||
res.status(200).send("Ping is active!");
|
||||
});
|
||||
|
||||
registerMapRoutes(router, app, settings);
|
||||
registerHelmRoutes(router);
|
||||
registerDatasetRoutes(router, app);
|
||||
registerForecastRoutes(router, app);
|
||||
registerTelegramRoutes(router);
|
||||
};
|
||||
18
plugin/routes/main.js
Normal file
18
plugin/routes/main.js
Normal file
@@ -0,0 +1,18 @@
|
||||
// Il file generale che raggruppa le api
|
||||
const router = require('express').Router();
|
||||
const cloudRoutes = require('./collection/cloud')
|
||||
const mapRoutes = require('./collection/map')
|
||||
const dataRoutes = require('./collection/data')
|
||||
const recRoutes = require('./collection/rec')
|
||||
const dashboard = require('./collection/dashboard')
|
||||
const kiosk = require('./collection/kiosk')
|
||||
|
||||
router.use('/cloud', cloudRoutes)
|
||||
router.use('/map', mapRoutes)
|
||||
router.use('/data', dataRoutes)
|
||||
router.use('/rec', recRoutes)
|
||||
|
||||
router.use('/dashboard', dashboard)
|
||||
router.use('/kiosk', kiosk)
|
||||
|
||||
module.exports = router
|
||||
@@ -1,43 +0,0 @@
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
const websPath = path.join(__dirname, "..", "public");
|
||||
|
||||
/**
|
||||
* Registers map-related routes.
|
||||
* @param {Object} router - Route wrapper with get/post methods
|
||||
* @param {Object} app - SignalK app instance
|
||||
* @param {Object} settings - Plugin settings
|
||||
*/
|
||||
function registerMapRoutes(router, app, settings) {
|
||||
// Serve interactive map with Mapbox token injected
|
||||
router.get('/map', (req, res) => {
|
||||
const filePath = path.join(websPath, "map.html");
|
||||
fs.readFile(filePath, "utf8", (err, html) => {
|
||||
if (err) {
|
||||
res.status(500).send("Error loading map");
|
||||
return;
|
||||
}
|
||||
const token = settings?.mapboxKey ?? "";
|
||||
const finalHtml = html.replace("{{MAPBOX_KEY}}", token);
|
||||
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
||||
res.send(finalHtml);
|
||||
});
|
||||
});
|
||||
|
||||
// Stream boat position and serve latest via API
|
||||
let lastPosition = null;
|
||||
|
||||
app.streambundle.getSelfStream("navigation.position").onValue(pos => {
|
||||
lastPosition = pos;
|
||||
});
|
||||
|
||||
router.get('/map/boat', (req, res) => {
|
||||
if (!lastPosition) {
|
||||
return res.json({ error: "No position data available" });
|
||||
}
|
||||
res.json(lastPosition);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = registerMapRoutes;
|
||||
@@ -1,13 +0,0 @@
|
||||
const { reloadBot } = require("../telegram/telegram.core.js");
|
||||
|
||||
module.exports = function (router) {
|
||||
router.post("/telegram/reload", (req, res) => {
|
||||
try {
|
||||
reloadBot();
|
||||
res.status(200).json({ status: "success", message: "Bot ricaricato." });
|
||||
} catch (error) {
|
||||
console.error("[MEB] Errore nel ricaricamento del bot da API:", error);
|
||||
res.status(500).json({ status: "error", message: "Errore durante il reload del bot." });
|
||||
}
|
||||
});
|
||||
};
|
||||
Reference in New Issue
Block a user