Gráficas variables ajustables y borrado de datos csv

main
EMOTIONS-HUNTER 6 days ago
parent 2f33e59fe8
commit 0ba3e59fb6

@ -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, () => { app.listen(PORT, () => {
console.log(`[MOCK SERVER] Backend Node.js corriendo en http://localhost:${PORT}`); console.log(`[MOCK SERVER] Backend Node.js corriendo en http://localhost:${PORT}`);
}); });

@ -1,8 +1,11 @@
const POLLING_INTERVAL_MS = 1000; const POLLING_INTERVAL_MS = 1000;
const HISTORY_POLLING_INTERVAL_MS = 10000;
const ALARM_CONFIG_FILE = "../config/alarms.json"; const ALARM_CONFIG_FILE = "../config/alarms.json";
const WARNING_MARGIN_RATIO = 0.1; 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 // Diccionario para traducir estados en la UI sin romper las clases CSS
const stateTranslations = { const stateTranslations = {
"NORMAL": "NORMAL", "NORMAL": "NORMAL",
@ -586,11 +589,75 @@ async function updateHistoricalTrends() {
}); });
} }
updateDashboard(); // --- MOTOR DE INTERVALOS DINÁMICOS ---
setInterval(updateDashboard, POLLING_INTERVAL_MS);
function startHistoryPolling() {
if (historyIntervalId) {
clearInterval(historyIntervalId);
}
historyIntervalId = setInterval(updateHistoricalTrends, currentHistoryInterval);
}
// Inicialización de renderizado
updateHistoricalTrends(); 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 = { const chartCards = {
temperature: document.getElementById("chart-card-temperature"), temperature: document.getElementById("chart-card-temperature"),
@ -611,13 +678,13 @@ document.querySelectorAll(".chart-filter").forEach((button) => {
if (selected === "all") { if (selected === "all") {
Object.values(chartCards).forEach((card) => { Object.values(chartCards).forEach((card) => {
if(card) card.style.display = ""; if (card) card.style.display = "";
}); });
return; return;
} }
Object.entries(chartCards).forEach(([key, card]) => { Object.entries(chartCards).forEach(([key, card]) => {
if(card) card.style.display = key === selected ? "" : "none"; if (card) card.style.display = key === selected ? "" : "none";
}); });
}); });
}); });

@ -132,9 +132,43 @@
<section class="trends-panel" aria-label="Tendencias históricas"> <section class="trends-panel" aria-label="Tendencias históricas">
<div class="panel-heading"> <div class="panel-heading">
<h2>Tendencias Históricas</h2> <h2>Tendencias Históricas</h2>
<p>Las gráficas se actualizan cada <span id="history-interval">10 segundos</span></p>
</div> </div>
<div class="card config-card" style="margin-bottom: 20px; padding: 20px; border-left: 4px solid #9c27b0;">
<h3>Configuración de Almacenamiento</h3>
<div style="display: flex; gap: 20px; align-items: center; flex-wrap: wrap;">
<div>
<label style="display: block; margin-bottom: 5px; font-size: 0.9em; color: #ccc;">Actualización
de Gráficas (UI):</label>
<select id="chart-refresh-rate" onchange="updateChartRefreshRate()"
style="padding: 8px; background-color: #222; color: white; border: 1px solid #444; border-radius: 4px;">
<option value="5000">Cada 5 segundos</option>
<option value="10000" selected>Cada 10 segundos</option>
<option value="30000">Cada 30 segundos</option>
<option value="60000">Cada 1 minuto</option>
</select>
</div>
<div>
<label style="display: block; margin-bottom: 5px; font-size: 0.9em; color: #ccc;">Frecuencia de
Guardado (Log):</label>
<select id="data-logging-rate" onchange="updateLoggingRate()"
style="padding: 8px; background-color: #222; color: white; border: 1px solid #444; border-radius: 4px;">
<option value="1">1 lectura / segundo</option>
<option value="5">1 lectura / 5 segundos</option>
<option value="10">1 lectura / 10 segundos</option>
<option value="60">1 lectura / 1 minuto</option>
</select>
</div>
<div style="margin-top: auto;">
<button class="btn-command" onclick="clearHistoricalData()"
style="padding: 8px 15px; background-color: #ff4444; color: white; border: none; border-radius: 4px; cursor: pointer; font-weight: bold;">
Borrar Historial de Datos
</button>
</div>
</div>
</div>
<div class="card export-card"> <div class="card export-card">
<h3>Exportar Historial (CSV / Excel)</h3> <h3>Exportar Historial (CSV / Excel)</h3>
<div class="button-group"> <div class="button-group">
@ -214,14 +248,16 @@
<button class="btn-command" onclick="sendCommand('status')" <button class="btn-command" onclick="sendCommand('status')"
style="padding: 5px 15px; cursor: pointer;">Estado</button> style="padding: 5px 15px; cursor: pointer;">Estado</button>
<button class="btn-command" onclick="sendCommand('r')" <button class="btn-command" onclick="sendCommand('r')"
style="padding: 5px 15px; background-color: #4f46e5; color: white; border: none; border-radius: 4px; cursor: pointer;">Leer (r)</button> style="padding: 5px 15px; background-color: #4f46e5; color: white; border: none; border-radius: 4px; cursor: pointer;">Leer
(r)</button>
<span style="color: #666; margin: 0 10px;">|</span> <span style="color: #666; margin: 0 10px;">|</span>
<input type="text" id="custom-command" placeholder="Ej: factory, t,25.0, led,1" <input type="text" id="custom-command" placeholder="Ej: factory, t,25.0, led,1"
style="padding: 5px; width: 200px; background-color: #222; color: #00ffcc; border: 1px solid #444;"> style="padding: 5px; width: 200px; background-color: #222; color: #00ffcc; border: 1px solid #444;">
<button class="btn-command" onclick="sendCustomCommand()" <button class="btn-command" onclick="sendCustomCommand()"
style="padding: 5px 15px; background-color: #00ffcc; color: black; font-weight: bold; border: none; border-radius: 4px; cursor: pointer;">Enviar ↵</button> style="padding: 5px 15px; background-color: #00ffcc; color: black; font-weight: bold; border: none; border-radius: 4px; cursor: pointer;">Enviar
</button>
</div> </div>
<div id="terminal-output" <div id="terminal-output"
@ -245,18 +281,26 @@
<div style="margin-bottom: 15px;"> <div style="margin-bottom: 15px;">
<label style="display: block; margin-bottom: 5px;">Comandos Globales de Calibración:</label> <label style="display: block; margin-bottom: 5px;">Comandos Globales de Calibración:</label>
<button class="btn-command" onclick="sendCalibrationCmd('cal,?')" style="padding: 5px 10px; margin-right: 5px; cursor: pointer;">Verificar Estado (?)</button> <button class="btn-command" onclick="sendCalibrationCmd('cal,?')"
<button class="btn-command" onclick="sendCalibrationCmd('cal,clear')" style="padding: 5px 10px; background-color: #ff4444; color: white; border: none; border-radius: 4px; cursor: pointer;">Borrar Memoria (Clear)</button> style="padding: 5px 10px; margin-right: 5px; cursor: pointer;">Verificar Estado
(?)</button>
<button class="btn-command" onclick="sendCalibrationCmd('cal,clear')"
style="padding: 5px 10px; background-color: #ff4444; color: white; border: none; border-radius: 4px; cursor: pointer;">Borrar
Memoria (Clear)</button>
</div> </div>
</div> </div>
<div style="background-color: #f5f5f5; padding: 15px; border-radius: 4px;"> <div style="background-color: #f5f5f5; padding: 15px; border-radius: 4px;">
<h4 style="margin-top: 0; color: #333;">Calibración Multipunto</h4> <h4 style="margin-top: 0; color: #333;">Calibración Multipunto</h4>
<p style="font-size: 0.9em; color: #666;">Ingresa el valor de referencia físico (ej. solución pH, o temperatura del agua).</p> <p style="font-size: 0.9em; color: #666;">Ingresa el valor de referencia físico (ej. solución
pH, o temperatura del agua).</p>
<div style="display: flex; gap: 10px; align-items: center;"> <div style="display: flex; gap: 10px; align-items: center;">
<input type="number" id="cal-value" placeholder="Ej: 7.00" step="0.01" style="padding: 8px; width: 100px; border: 1px solid #ccc; border-radius: 4px;"> <input type="number" id="cal-value" placeholder="Ej: 7.00" step="0.01"
<button class="btn-command" onclick="calibratePoint()" style="padding: 8px 15px; background-color: #00ffcc; color: black; font-weight: bold; border: none; border-radius: 4px; cursor: pointer;">Calibrar Punto</button> style="padding: 8px; width: 100px; border: 1px solid #ccc; border-radius: 4px;">
<button class="btn-command" onclick="calibratePoint()"
style="padding: 8px 15px; background-color: #00ffcc; color: black; font-weight: bold; border: none; border-radius: 4px; cursor: pointer;">Calibrar
Punto</button>
</div> </div>
</div> </div>
</div> </div>

Loading…
Cancel
Save