Files
OLD-server-architecture/api/src/storage/influx.js
Giuseppe Raffa 0ce879aa44 feat: Add new API endpoints and HTML pages for ML model management
- Implemented HTML pages for datasets, models, training, testing, and results.
- Created API endpoints for managing repositories, results, tests, and training sessions.
- Added functionality for streaming training progress via Server-Sent Events (SSE).
- Introduced a Dockerfile for the ML runner with necessary dependencies.
- Developed an SDK for user code execution within the runner container.
- Enhanced CSS styles for improved UI layout and navigation.
- Established a layout template for consistent HTML structure across pages.
- Added JavaScript for dynamic interactions on the models page.
- Implemented WebSocket handling for real-time communication with kiosk devices and controllers.
- Implemented model registration and management API at /api/models
- Added Gitea proxy API for repository interactions at /api/repos
- Created results API for listing and comparing training results at /api/results
- Developed training management API for enqueueing and retrieving training jobs at /api/trainings
- Introduced SSE endpoint for live training progress updates
- Added HTML pages for models, datasets, and training management
- Created a Dockerfile for the ML runner with necessary dependencies
- Developed SDK for user code execution within the runner container
- Enhanced CSS styles for improved UI/UX
- Implemented WebSocket communication for real-time device and controller interactions in the kiosk system
2026-04-28 09:24:38 +02:00

194 lines
6.2 KiB
JavaScript

const { InfluxDB, Point } = require('@influxdata/influxdb-client');
const url = process.env.INFLX_URL;
const token = process.env.INFLX_TOKEN;
const org = process.env.INFLX_ORG;
const boatTelemetry = "boat"
const client = new InfluxDB({ url, token })
const write = client.getWriteApi(org, boatTelemetry);
const querying = client.getQueryApi(org);
console.log("InfluxDB client initialized with config:", { url, org, token });
async function append(measurement, sensor, data) {
const point = new Point(measurement)
.tag("sensor", sensor)
.floatField('temperature', data.temperature)
.floatField('humidity', data.humidity)
write.writePoint(point);
await write.flush();
}
async function writeBatch(datas) {
datas.forEach(data => {
append(data.measurement, data.sensor, data.data);
})
}
async function query(bucket, relativeTime, measurement, sensor, field) {
const fluxTimeRange = relativeTime || "-1h";
let fluxQuery = `
from(bucket: "${bucket}")
|> range(start: ${fluxTimeRange})
|> filter(fn: (r) => r._measurement == "${measurement}")`;
if (sensor) {
fluxQuery += `\n |> filter(fn: (r) => r.sensor == "${sensor}")`;
}
if (field) {
fluxQuery += `\n |> filter(fn: (r) => r._field == "${field}")`;
}
fluxQuery += `\n |> yield(name: "data")`;
try {
const data = [];
for await (const { values, tableMeta } of querying.iterateRows(fluxQuery)) {
data.push(tableMeta.toObject(values));
}
return data;
} catch (error) {
console.error("Error in query:", error);
return [];
}
}
// Sorgente di verità per i logs di sessione: stesso bucket usato da
// realtime/store/influx.js. Sovrascrivibile via env per ambiente.
const sessionBucket = process.env.INFLX_BUCKET_LOGS || process.env.INFLX_BUCKET || 'logs';
/**
* Query storica per una sessione di registrazione.
* @param {string} sensor - nome sensore
* @param {string} session - session_id (tag InfluxDB)
* @param {string} since - ISO timestamp o duration (es. "-30d")
* @param {string|null} until - ISO timestamp fine (opzionale)
* @returns {Promise<Array<Object>>}
*/
async function querySessionHistory(sensor, session, since, until = null) {
const rangeStr = until ? `start: ${since}, stop: ${until}` : `start: ${since}`;
const fluxQuery = `
from(bucket: "${sessionBucket}")
|> range(${rangeStr})
|> 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) => {
querying.queryRows(fluxQuery, {
next(row, tableMeta) { rows.push(tableMeta.toObject(row)); },
error: reject,
complete() { resolve(rows); },
});
});
}
/**
* Esporta i dati di una sessione come stringa CSV.
* @param {string} sensor
* @param {string} session
* @param {string} since
* @param {string|null} until
* @returns {Promise<string>}
*/
async function exportSessionCSV(sensor, session, since, until = null) {
const rows = await querySessionHistory(sensor, session, since, until);
if (rows.length === 0) return '';
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 values = fields.map(f => { const v = row[f]; return (v == null) ? '' : v; });
return [row._time || '', ...values].join(',');
});
return header + '\n' + csvRows.join('\n') + '\n';
}
/**
* Utility interna: esegue una Flux query e restituisce le righe come array di oggetti.
*/
function runFlux(fluxQuery) {
const rows = [];
return new Promise((resolve, reject) => {
querying.queryRows(fluxQuery, {
next(row, tableMeta) { rows.push(tableMeta.toObject(row)); },
error: reject,
complete() { resolve(rows); },
});
});
}
/**
* Elenca tutte le sessioni presenti in InfluxDB, con primo e ultimo timestamp.
* Sorgente di verità: tag sensor + session sul measurement "logs".
* @param {string} [lookback='-5y'] - range di ricerca (es. '-365d', '-5y')
* @returns {Promise<Array<{session, sensor, startTime, endTime}>>}
*/
async function listInfluxSessions(lookback = '-5y') {
const base = `
from(bucket: "${sessionBucket}")
|> range(start: ${lookback})
|> filter(fn: (r) => r._measurement == "logs")
|> group(columns: ["sensor", "session"])
`;
const [firstRows, lastRows] = await Promise.all([
runFlux(base + '|> first() |> keep(columns: ["_time", "sensor", "session"])'),
runFlux(base + '|> last() |> keep(columns: ["_time", "sensor", "session"])'),
]);
const map = {};
firstRows.forEach(r => {
if (!r.session) return;
map[r.session] = { session: r.session, sensor: r.sensor, startTime: r._time };
});
lastRows.forEach(r => {
if (map[r.session]) map[r.session].endTime = r._time;
});
return Object.values(map).sort((a, b) => new Date(b.startTime) - new Date(a.startTime));
}
async function checkInflux() {
try {
const result = await querying.collectRows(
`from(bucket: "boat") |> range(start: -1s) |> limit(n:1)`
);
console.log('InfluxDB: OK');
return { ok: true };
} catch (error) {
console.error('InfluxDB check failed:', {
message: error.message,
statusCode: error.statusCode ?? 'N/A',
body: error.body ?? 'N/A'
});
return false
}
}
module.exports = {
write: append,
writeBatch,
query,
listInfluxSessions,
querySessionHistory,
exportSessionCSV,
checkInflux
}