diff --git a/api/server.js b/api/server.js index 87ba374..13c2141 100644 --- a/api/server.js +++ b/api/server.js @@ -184,6 +184,23 @@ app.post('/api/sensors/:type/command', async (req, res) => { }); }); +// --- RUTAS DE GESTIÓN DE ALMACENAMIENTO --- + +// Variable global para simular la configuración de escritura del demonio C++ +let loggingRateSeconds = 1; + +app.post('/api/config/logging', (req, res) => { + loggingRateSeconds = req.body.rate || 1; + console.log(`[SISTEMA] Demonio de escritura configurado a: 1 registro cada ${loggingRateSeconds}s`); + res.json({ success: true, rate: loggingRateSeconds }); +}); + +app.post('/api/history/clear', (req, res) => { + console.log(`[SISTEMA] Purgado de base de datos histórico solicitado por el usuario.`); + // En el hardware real, aquí se ejecutaría 'fs.unlink()' o se truncarían los archivos CSV en /logs/ + res.json({ success: true, message: "Archivos de registro truncados correctamente." }); +}); + app.listen(PORT, () => { console.log(`[MOCK SERVER] Backend Node.js corriendo en http://localhost:${PORT}`); }); \ No newline at end of file diff --git a/frontend/dashboard.js b/frontend/dashboard.js index 1586569..8e0c583 100644 --- a/frontend/dashboard.js +++ b/frontend/dashboard.js @@ -1,8 +1,11 @@ const POLLING_INTERVAL_MS = 1000; -const HISTORY_POLLING_INTERVAL_MS = 10000; const ALARM_CONFIG_FILE = "../config/alarms.json"; const WARNING_MARGIN_RATIO = 0.1; +// Variables dinámicas para el control de históricos +let currentHistoryInterval = 10000; +let historyIntervalId = null; + // Diccionario para traducir estados en la UI sin romper las clases CSS const stateTranslations = { "NORMAL": "NORMAL", @@ -144,7 +147,7 @@ function setSensorState(result) { valueElement.textContent = result.value; // Traducir el estado para mostrar en pantalla, manteniendo la clase en inglés stateElement.textContent = stateTranslations[state] || state; - + ["online", "normal", "warning", "critical", "offline"].forEach((className) => { stateElement.classList.toggle(className, className === stateClass); cardElement.classList.toggle(className, className === stateClass); @@ -586,11 +589,75 @@ async function updateHistoricalTrends() { }); } -updateDashboard(); -setInterval(updateDashboard, POLLING_INTERVAL_MS); +// --- MOTOR DE INTERVALOS DINÁMICOS --- +function startHistoryPolling() { + if (historyIntervalId) { + clearInterval(historyIntervalId); + } + historyIntervalId = setInterval(updateHistoricalTrends, currentHistoryInterval); +} + +// Inicialización de renderizado updateHistoricalTrends(); -setInterval(updateHistoricalTrends, HISTORY_POLLING_INTERVAL_MS); +startHistoryPolling(); + +// --- FUNCIONES DE CONTROL DE ALMACENAMIENTO --- + +window.updateChartRefreshRate = function () { + const select = document.getElementById("chart-refresh-rate"); + currentHistoryInterval = parseInt(select.value, 10); + + const intervalText = select.options[select.selectedIndex].text.replace("Cada ", "").toLowerCase(); + document.getElementById("history-interval").textContent = intervalText; + + startHistoryPolling(); +}; + +window.updateLoggingRate = async function () { + const select = document.getElementById("data-logging-rate"); + const rate = parseInt(select.value, 10); + + try { + const response = await fetch('/api/config/logging', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ rate }) + }); + + if (!response.ok) throw new Error("Fallo en la sincronización con el servidor"); + alert(`Configuración de hardware actualizada:\nLos demonios C++ guardarán 1 registro cada ${rate} segundos.`); + } catch (error) { + console.error("Error configurando la tasa de registro:", error); + } +}; + +window.clearHistoricalData = async function () { + const confirmacion = confirm("ADVERTENCIA CRÍTICA:\n\n¿Confirma el purgado total de los archivos históricos CSV? Esta acción no se puede deshacer y los datos de tendencias se perderán de forma permanente."); + + if (!confirmacion) return; + + try { + const response = await fetch('/api/history/clear', { method: 'POST' }); + if (!response.ok) throw new Error("Fallo en el purgado de archivos"); + + // Simulación visual de purgado en la interfaz (Destrucción de datos del Canvas) + historicalCharts.forEach(config => { + const existingChart = chartInstances.get(config.id); + if (existingChart) { + existingChart.data.labels = []; + existingChart.data.datasets.forEach(dataset => dataset.data = []); + existingChart.update("none"); + } + setChartAvailability(config, false); + }); + + alert("Purgado de memoria completado exitosamente."); + } catch (error) { + console.error("Error ejecutando el borrado:", error); + alert("Error de sistema al intentar purgar los archivos."); + } +}; const chartCards = { temperature: document.getElementById("chart-card-temperature"), @@ -611,13 +678,13 @@ document.querySelectorAll(".chart-filter").forEach((button) => { if (selected === "all") { Object.values(chartCards).forEach((card) => { - if(card) card.style.display = ""; + if (card) card.style.display = ""; }); return; } Object.entries(chartCards).forEach(([key, card]) => { - if(card) card.style.display = key === selected ? "" : "none"; + if (card) card.style.display = key === selected ? "" : "none"; }); }); }); \ No newline at end of file diff --git a/frontend/index.html b/frontend/index.html index 23e6a63..780b452 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -132,9 +132,43 @@