- Created a new CSS file for kiosk styles, defining variables, typography, and layout for cards and toolbars. - Implemented new routes for data anlaysis page
137 lines
4.2 KiB
JavaScript
137 lines
4.2 KiB
JavaScript
const { InfluxDB, Point } = require('@influxdata/influxdb-client');
|
|
|
|
const client = new InfluxDB({
|
|
url: process.env.INFLX_URL,
|
|
token: process.env.INFLX_TOKEN,
|
|
});
|
|
|
|
const bucket = process.env.INFLX_BUCKET || 'logs';
|
|
const org = process.env.INFLX_ORG;
|
|
|
|
const writeApi = client.getWriteApi(org, bucket, 'ms', {
|
|
flushInterval: 100,
|
|
batchSize: 50,
|
|
});
|
|
|
|
/**
|
|
* Scrive dati generici su InfluxDB senza mapping.
|
|
* @param {string} measurement - nome della measurement (es. 'logs', 'weather')
|
|
* @param {Object} fields - campi { key: value }
|
|
* @param {string} sensor - nome del sensore
|
|
* @param {string} session - id sessione (tag immutabile)
|
|
* @param {number} timestamp - timestamp unix ms
|
|
*/
|
|
function writeGenericData(measurement, fields, sensor, session, timestamp) {
|
|
const point = new Point(measurement)
|
|
.tag('sensor', sensor)
|
|
.tag('session', session)
|
|
.timestamp(timestamp);
|
|
|
|
for (const [key, value] of Object.entries(fields)) {
|
|
if (value === null || value === undefined) continue;
|
|
if (typeof value === 'number') {
|
|
point.floatField(key, value);
|
|
} else if (typeof value === 'string') {
|
|
point.stringField(key, value);
|
|
}
|
|
}
|
|
|
|
writeApi.writePoint(point);
|
|
}
|
|
|
|
/**
|
|
* Scrive un batch di punti forecast (previsioni orarie).
|
|
* @param {Array} points - array di [timestamp_ms, { key: value, ... }]
|
|
* @param {string} sensor - nome del sensore
|
|
* @param {string} session - id sessione
|
|
*/
|
|
function writeForecastBatch(points, sensor, session) {
|
|
for (const [ts, fields] of points) {
|
|
writeGenericData('weather_forecast', fields, sensor, session, ts);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Forza il flush del buffer di scrittura.
|
|
*/
|
|
async function flush() {
|
|
try {
|
|
await writeApi.flush();
|
|
} catch (err) {
|
|
console.error('[INFLUX] Flush error:', err.message);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Query storica per una sessione: ritorna righe pivotate con tutti i campi.
|
|
* @param {string} sensor - nome sensore
|
|
* @param {string} session - session_id (tag InfluxDB)
|
|
* @param {string} since - ISO timestamp o duration (es. "-30d")
|
|
* @returns {Array<Object>}
|
|
*/
|
|
async function queryHistory(sensor, session, since) {
|
|
const queryApi = client.getQueryApi(org);
|
|
const fluxQuery = `
|
|
from(bucket: "${bucket}")
|
|
|> range(start: ${since})
|
|
|> filter(fn: (r) => r._measurement == "logs")
|
|
|> filter(fn: (r) => r.sensor == "${sensor}")
|
|
|> filter(fn: (r) => r.session == "${session}")
|
|
|> pivot(rowKey:["_time"], columnKey: ["_field"], valueColumn: "_value")
|
|
|> sort(columns: ["_time"])
|
|
`;
|
|
|
|
const rows = [];
|
|
return new Promise((resolve, reject) => {
|
|
queryApi.queryRows(fluxQuery, {
|
|
next(row, tableMeta) {
|
|
rows.push(tableMeta.toObject(row));
|
|
},
|
|
error: reject,
|
|
complete() { resolve(rows); },
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Esporta tutti i dati di una sessione come CSV.
|
|
* @param {string} sensor - nome sensore
|
|
* @param {string} session - session_id
|
|
* @param {string} since - ISO timestamp inizio (opzionale, default -30d)
|
|
* @returns {string} CSV content
|
|
*/
|
|
async function exportSessionCSV(sensor, session, since) {
|
|
const start = since || '-30d';
|
|
const rows = await queryHistory(sensor, session, start);
|
|
|
|
if (rows.length === 0) return '';
|
|
|
|
// Raccogli tutti i field names (esclusi meta InfluxDB)
|
|
const metaKeys = new Set(['result', 'table', '_start', '_stop', '_measurement', 'sensor', 'session', '']);
|
|
const fieldNames = new Set();
|
|
for (const row of rows) {
|
|
for (const key of Object.keys(row)) {
|
|
if (!metaKeys.has(key) && key !== '_time') {
|
|
fieldNames.add(key);
|
|
}
|
|
}
|
|
}
|
|
|
|
const fields = Array.from(fieldNames).sort();
|
|
const header = ['timestamp', ...fields].join(',');
|
|
|
|
const csvRows = rows.map(row => {
|
|
const ts = row._time || '';
|
|
const values = fields.map(f => {
|
|
const v = row[f];
|
|
if (v === null || v === undefined) return '';
|
|
return v;
|
|
});
|
|
return [ts, ...values].join(',');
|
|
});
|
|
|
|
return header + '\n' + csvRows.join('\n') + '\n';
|
|
}
|
|
|
|
module.exports = { writeGenericData, writeForecastBatch, flush, queryHistory, exportSessionCSV };
|