Se incopora las estadisticas nuevamente
This commit is contained in:
parent
0ab5b4f68c
commit
5fc3437ce2
@ -29,7 +29,8 @@
|
|||||||
"desktop:dev": "node src/tools/nw.cjs dev",
|
"desktop:dev": "node src/tools/nw.cjs dev",
|
||||||
"test:config": "node --test test/configuracion.test.cjs",
|
"test:config": "node --test test/configuracion.test.cjs",
|
||||||
"test:ui": "node test/ui-smoke.cjs",
|
"test:ui": "node test/ui-smoke.cjs",
|
||||||
"test:startup": "node test/ui-startup.cjs"
|
"test:startup": "node test/ui-startup.cjs",
|
||||||
|
"test:estadisticas": "node --test test/estadisticas.test.cjs"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"chai": "^4.3.4",
|
"chai": "^4.3.4",
|
||||||
|
|||||||
@ -1272,139 +1272,10 @@ function VerRegistrosLog(select)
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function graficoEstadisticas()
|
async function graficoEstadisticas() {
|
||||||
{
|
return EstadisticasUI.mount(document.getElementById('estadisticas'), {
|
||||||
// GRAFICO DEL TIEMPO CHARTJS
|
mode: configGeneralEMA.ConfGeneral.Modo,
|
||||||
document.getElementById("grafico").style.display = "none";
|
system: configGeneralEMA.ConfGeneral.SistemaMetrico,
|
||||||
document.getElementById("actualizando").style.display = "block";
|
load: () => new Promise((resolve, reject) => fsconf.readFile('./logs/registro_diario.log', 'utf8', (err, data) => err ? reject(err) : resolve(data)))
|
||||||
|
});
|
||||||
//Se genera el grafico normal
|
|
||||||
if(['Recolector_USB', 'Recolector_IP'].includes(configGeneralEMA.ConfGeneral.Modo)) // se comprueba el modo se encuentra en Recolector
|
|
||||||
{
|
|
||||||
|
|
||||||
fsconf.readFile('./logs/registro_diario.log', {
|
|
||||||
encoding: 'utf8',
|
|
||||||
autoClose: true
|
|
||||||
}, (err, data) => {
|
|
||||||
if (err) {
|
|
||||||
EMA.RegistrarLog("1|ERROR|Error al intentar leer el registro diario para generar el grafico estadistico.");
|
|
||||||
} else {
|
|
||||||
var db = new Date();
|
|
||||||
var mes = db.getMonth() + 1; if(mes <= 9) {mes = '0'+ mes};
|
|
||||||
var dia= db.getDate();if(dia <= 9) {dia = '0'+ dia};
|
|
||||||
var fecha = dia + "/" + mes + "/" + db.getFullYear();
|
|
||||||
db = null;mes=null;dia=null;
|
|
||||||
var paraPendientes = fsconf.readFileSync('./logs/debug.log', 'utf8');
|
|
||||||
var paraPendientes = paraPendientes.split('\n');
|
|
||||||
var paraPendientesALM = paraPendientes.filter(function(datos) {var Resultado = datos.split("|"); return (Resultado[1] == 'PENDIENTE_ALM' && fecha == datos.substring(0,10)); });
|
|
||||||
var paraPendientesPUB = paraPendientes.filter(function(datos) {var Resultado = datos.split("|"); return (Resultado[1] == 'PENDIENTE_PUB' && fecha == Resultado[2].substring(15,25)); });
|
|
||||||
var CantiddPendientesAlmacenados = paraPendientesALM.length;
|
|
||||||
var CantiddPendientesPublicados = paraPendientesPUB.length;
|
|
||||||
paraPendientes = null;paraPendientesPUB = null; paraPendientesALM = null; fecha = null;
|
|
||||||
var RegistroDiarioTmp = JSON.parse(data);
|
|
||||||
var CantRegistros = RegistroDiarioTmp.length;
|
|
||||||
// console.log(CantRegistros);
|
|
||||||
if(CantRegistros > 1)
|
|
||||||
{
|
|
||||||
delete RegistroDiarioTmp['fechaISO'];
|
|
||||||
delete RegistroDiarioTmp['fechaUTC'];
|
|
||||||
delete RegistroDiarioTmp['luxer_intencidad_1hs'];
|
|
||||||
delete RegistroDiarioTmp['viento_direccion_nombre'];
|
|
||||||
delete RegistroDiarioTmp['lluvia_acu_diaria'];
|
|
||||||
delete RegistroDiarioTmp['temperatura_interna_max_dia'];
|
|
||||||
delete RegistroDiarioTmp['temperatura_interna_min_dia'];
|
|
||||||
delete RegistroDiarioTmp['humedad_interna_max_dia'];
|
|
||||||
delete RegistroDiarioTmp['humedad_interna_min_dia'];
|
|
||||||
delete RegistroDiarioTmp['temperatura_externa_max_dia'];
|
|
||||||
delete RegistroDiarioTmp['temperatura_externa_min_dia'];
|
|
||||||
delete RegistroDiarioTmp['humedad_externa_max_dia'];
|
|
||||||
delete RegistroDiarioTmp['humedad_externa_min_dia'];
|
|
||||||
delete RegistroDiarioTmp['punto_de_rocio_max_dia'];
|
|
||||||
delete RegistroDiarioTmp['punto_de_rocio_min_dia'];
|
|
||||||
delete RegistroDiarioTmp['luxer_intencidad_max_dia'];
|
|
||||||
delete RegistroDiarioTmp['luxer_intencidad_min_dia'];
|
|
||||||
delete RegistroDiarioTmp['luxer_uv_max_dia'];
|
|
||||||
delete RegistroDiarioTmp['luxer_uv_min_dia'];
|
|
||||||
delete RegistroDiarioTmp['presion_relativa_max_dia'];
|
|
||||||
delete RegistroDiarioTmp['presion_relativa_min_dia'];
|
|
||||||
delete RegistroDiarioTmp['viento_direccion_prom_dia'];
|
|
||||||
delete RegistroDiarioTmp['viento_velocidad_max_dia'];
|
|
||||||
delete RegistroDiarioTmp['viento_rafagas_max_dia'];
|
|
||||||
RegistroDiarioConCambios = [];
|
|
||||||
for (var j = 0; j < CantRegistros; j++) {
|
|
||||||
if ( RegistroDiarioTmp[j].cambio == true) RegistroDiarioConCambios.push(RegistroDiarioTmp[j]);
|
|
||||||
}
|
|
||||||
|
|
||||||
delete RegistroDiarioConCambios['diferencia'];
|
|
||||||
// Se generan los datos para el grafico
|
|
||||||
var fecha_maxima = RegistroDiarioConCambios[RegistroDiarioConCambios.length-1].fecha;
|
|
||||||
var labels = RegistroDiarioConCambios.map(function(e) { return e.fecha.substring(11, 16) || 0;});
|
|
||||||
var tmp_Ext = RegistroDiarioConCambios.map(function(e) { return e.temperatura_externa || 0;});
|
|
||||||
var hum_Ext = RegistroDiarioConCambios.map(function(e) { return e.humedad_externa || 0;});
|
|
||||||
var tmp_Int = RegistroDiarioConCambios.map(function(e) { return e.temperatura_interna || 0;});
|
|
||||||
var hum_Int = RegistroDiarioConCambios.map(function(e) { return e.humedad_interna || 0;});
|
|
||||||
var vientos = RegistroDiarioConCambios.map(function(e) { return e.viento_velocidad || 0;});
|
|
||||||
var vinetosRaf = RegistroDiarioConCambios.map(function(e) { return e.viento_rafagas || 0;});
|
|
||||||
var UV = RegistroDiarioConCambios.map(function(e) { return e.luxer_uv || 0;});
|
|
||||||
var Luxer = RegistroDiarioConCambios.map(function(e) { return e.luxer_intencidad || 0;});
|
|
||||||
var Lluvia = RegistroDiarioConCambios.map(function(e) { return e.lluvia_acu_hora || 0;});
|
|
||||||
var presionRel = RegistroDiarioConCambios.map(function(e) { return e.presion_relativa || 0;});
|
|
||||||
var validacion = RegistroDiarioConCambios.map(function(e) { return (e.estado_validacion_ant) || 0;});
|
|
||||||
var puntorocio = RegistroDiarioConCambios.map(function(e) { return e.punto_de_rocio || 0;});
|
|
||||||
// GRAFICO RESUMEN CON RGRAPH
|
|
||||||
var resumenLabels = [
|
|
||||||
'Temp.Ext', 'Hum.Ext', 'Temp.Int', 'Hum.Int', 'Viento', 'Lluvia', 'Pres.Rel', 'UV', 'Luxer', 'Pto.Rocío', 'Valid.'
|
|
||||||
];
|
|
||||||
var resumenData = [
|
|
||||||
tmp_Ext[tmp_Ext.length-1] || 0,
|
|
||||||
hum_Ext[hum_Ext.length-1] || 0,
|
|
||||||
tmp_Int[tmp_Int.length-1] || 0,
|
|
||||||
hum_Int[hum_Int.length-1] || 0,
|
|
||||||
vientos[vientos.length-1] || 0,
|
|
||||||
Lluvia[Lluvia.length-1] || 0,
|
|
||||||
presionRel[presionRel.length-1] || 0,
|
|
||||||
UV[UV.length-1] || 0,
|
|
||||||
Luxer[Luxer.length-1] || 0,
|
|
||||||
puntorocio[puntorocio.length-1] || 0,
|
|
||||||
validacion[validacion.length-1] || 0
|
|
||||||
];
|
|
||||||
|
|
||||||
|
|
||||||
window._GraphInicioChart = new RGraph.Bar({
|
|
||||||
id: 'graph_inicio',
|
|
||||||
data: resumenData,
|
|
||||||
options: {
|
|
||||||
colors: ['#1e90ff','#00b894','#fdcb6e','#e17055','#636e72','#00bfff','#6c5ce7','#fd79a8','#ffeaa7','#fab1a0','#636e72'],
|
|
||||||
backgroundGrid: true,
|
|
||||||
xaxisLabels: resumenLabels,
|
|
||||||
yaxis: true,
|
|
||||||
yaxisLabelsCount: 5,
|
|
||||||
marginLeft: 50,
|
|
||||||
marginBottom: 60,
|
|
||||||
marginTop: 40,
|
|
||||||
marginRight: 20,
|
|
||||||
title: 'Resumen Últimos Valores',
|
|
||||||
titleBold: true
|
|
||||||
}
|
|
||||||
}).draw();
|
|
||||||
// document.getElementById("actualizando").style.display = "none";
|
|
||||||
//console.log("Grafico resumen generado con RGraph.");
|
|
||||||
//console.table(resumenLabels);
|
|
||||||
// console.table(resumenData);
|
|
||||||
|
|
||||||
}else{
|
|
||||||
return false
|
|
||||||
//document.getElementById("inicio_estadisticas").innerHTML = "<h2> No hay registros para procesar .. </h2>";
|
|
||||||
//document.getElementById("grafico").innerHTML = "<h2> No hay registros para procesar .. </h2>";
|
|
||||||
//document.getElementById("actualizando").style.display = "none";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
}else{ //MODO Visor
|
|
||||||
return false;
|
|
||||||
//document.getElementById("inicio_estadisticas").innerHTML = "<h2> Las estadisticas solo en modo recolector. </h2>";
|
|
||||||
//document.getElementById("grafico").innerHTML = "<h2> Gráficos de estadisticas solo en modo recolector. </h2>";
|
|
||||||
//document.getElementById("actualizando").style.display = "none";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@ -23,6 +23,7 @@
|
|||||||
<script src="../gui_common/js/configuracion.js"></script>
|
<script src="../gui_common/js/configuracion.js"></script>
|
||||||
<script src="../gui_common/js/tablas.js"></script>
|
<script src="../gui_common/js/tablas.js"></script>
|
||||||
<script src="../gui_common/js/estado.js"></script>
|
<script src="../gui_common/js/estado.js"></script>
|
||||||
|
<script src="../gui_common/js/estadisticas.js"></script>
|
||||||
<!-- Libreria bootstrap select para graficos -->
|
<!-- Libreria bootstrap select para graficos -->
|
||||||
<!-- Datable - Para las tablas dinamicas -->
|
<!-- Datable - Para las tablas dinamicas -->
|
||||||
<script src="../node_modules/datatables.net/js/jquery.dataTables.min.js"></script>
|
<script src="../node_modules/datatables.net/js/jquery.dataTables.min.js"></script>
|
||||||
@ -125,9 +126,9 @@
|
|||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" id="exterior-tab" data-bs-toggle="tab" data-bs-target="#exterior" role="tab" aria-controls="exterior" aria-selected="false"><img height="24px" width="24px" src="../gui_common/img/exterior.webp" aria-controls="exterior"> EXTERIOR</a>
|
<a class="nav-link" id="exterior-tab" data-bs-toggle="tab" data-bs-target="#exterior" role="tab" aria-controls="exterior" aria-selected="false"><img height="24px" width="24px" src="../gui_common/img/exterior.webp" aria-controls="exterior"> EXTERIOR</a>
|
||||||
</li>
|
</li>
|
||||||
<!-- <li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" id="estadisticas-tab" data-bs-toggle="tab" data-bs-target="#estadisticas" role="tab" aria-controls="estadisticas" aria-selected="false"><img height="24px" width="24px" src="../gui_common/img/estadistica.png" aria-controls="estadisticas"> ESTADISTICAS</a>
|
<a class="nav-link" id="estadisticas-tab" data-bs-toggle="tab" data-bs-target="#estadisticas" role="tab" aria-controls="estadisticas" aria-selected="false"><img height="24px" width="24px" src="../gui_common/img/estadistica.png" aria-controls="estadisticas"> ESTADISTICAS</a>
|
||||||
</li> -->
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" id="redes-tab" data-bs-toggle="tab" data-bs-target="#redes" role="tab" aria-controls="redes" aria-selected="false"><img height="24px" width="24px" src="../gui_common/img/redes.webp" aria-controls="redes"> REDES</a>
|
<a class="nav-link" id="redes-tab" data-bs-toggle="tab" data-bs-target="#redes" role="tab" aria-controls="redes" aria-selected="false"><img height="24px" width="24px" src="../gui_common/img/redes.webp" aria-controls="redes"> REDES</a>
|
||||||
</li>
|
</li>
|
||||||
@ -143,7 +144,7 @@
|
|||||||
<div class="tab-pane fade show active" id="inicio" role="tabpanel" aria-labelledby="inicio-tab"></div>
|
<div class="tab-pane fade show active" id="inicio" role="tabpanel" aria-labelledby="inicio-tab"></div>
|
||||||
<div class="tab-pane fade text-center" id="interior" role="tabpanel" aria-labelledby="interior-tab"></div>
|
<div class="tab-pane fade text-center" id="interior" role="tabpanel" aria-labelledby="interior-tab"></div>
|
||||||
<div class="tab-pane fade text-center" id="exterior" role="tabpanel" aria-labelledby="exterior-tab"></div>
|
<div class="tab-pane fade text-center" id="exterior" role="tabpanel" aria-labelledby="exterior-tab"></div>
|
||||||
<!-- <div class="tab-pane fade text-center" id="estadisticas" role="tabpanel" aria-labelledby="estadisticas-tab"></div>-->
|
<div class="tab-pane fade text-center" id="estadisticas" role="tabpanel" aria-labelledby="estadisticas-tab"></div>
|
||||||
<div class="tab-pane fade text-center" id="configuracion" role="tabpanel" aria-labelledby="configuracion-tab"></div>
|
<div class="tab-pane fade text-center" id="configuracion" role="tabpanel" aria-labelledby="configuracion-tab"></div>
|
||||||
<div class="tab-pane fade text-center" id="redes" role="tabpanel" aria-labelledby="redes-tab"></div>
|
<div class="tab-pane fade text-center" id="redes" role="tabpanel" aria-labelledby="redes-tab"></div>
|
||||||
<div class="tab-pane fade" id="contacto" role="tabpanel" aria-labelledby="contacto-tab"></div>
|
<div class="tab-pane fade" id="contacto" role="tabpanel" aria-labelledby="contacto-tab"></div>
|
||||||
|
|||||||
@ -1,38 +1,7 @@
|
|||||||
<!-- Inicio del Gráfico -->
|
<section class="text-start estadisticas-panel" aria-label="Estadísticas del registro diario">
|
||||||
<div >
|
<div class="d-flex justify-content-between align-items-center mb-3 gap-2">
|
||||||
<button id="boton_grafico" class="btn btn-sm btn-success" onclick="Mostrar()" role="button">Gráfico Historial</button>
|
<h2 class="h4 mb-0">Estadísticas del registro diario</h2>
|
||||||
<button id="boton_volver" class="btn btn-sm btn-success" onclick="Volver()" role="button">Volver</button>
|
<button type="button" class="btn btn-primary btn-sm" data-estadisticas-refresh>Actualizar</button>
|
||||||
<br><br>
|
</div>
|
||||||
<div id="actualizando" style="display:block">
|
<div data-estadisticas-content aria-live="polite"></div>
|
||||||
<br><br> <img src='../gui_common/img/animadas/cargando2.gif' width='40' height='40' /> <br> <small>Cargando Gráfico...</small>
|
</section>
|
||||||
</div>
|
|
||||||
<div id="inicio_estadisticas">
|
|
||||||
<canvas id="graph_inicio" width="800" height="400" style="width:100%; height:400px; max-width:80vw; max-height:50vh;"></canvas>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="grafico" style="background-color:#182c3e00; " >
|
|
||||||
<canvas id="graph" style="height:50vh; width:80vw" ></canvas>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<script>
|
|
||||||
function Mostrar()
|
|
||||||
{
|
|
||||||
document.getElementById("grafico").style.display = "block";
|
|
||||||
document.getElementById("inicio_estadisticas").style.display = "none";
|
|
||||||
document.getElementById("inicio_estadisticas").hidden = true;
|
|
||||||
document.getElementById("boton_grafico").hidden = true;
|
|
||||||
document.getElementById("boton_volver").hidden = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function Volver()
|
|
||||||
{
|
|
||||||
document.getElementById("grafico").style.display = "none";
|
|
||||||
document.getElementById("inicio_estadisticas").style.display = "block";
|
|
||||||
document.getElementById("inicio_estadisticas").hidden = false;
|
|
||||||
document.getElementById("boton_grafico").hidden = false;
|
|
||||||
document.getElementById("boton_volver").hidden = true;
|
|
||||||
}
|
|
||||||
document.getElementById("boton_volver").hidden = true;
|
|
||||||
|
|
||||||
</script>
|
|
||||||
<!-- Fin del Gráfico -->
|
|
||||||
|
|||||||
@ -876,6 +876,7 @@ function MenuConfig () { ConfigUI.visibility(); }
|
|||||||
|
|
||||||
async function MenuGUI (elemento) {
|
async function MenuGUI (elemento) {
|
||||||
if (!ConfigUI.canLeave()) return;
|
if (!ConfigUI.canLeave()) return;
|
||||||
|
EstadisticasUI.dispose();
|
||||||
try {
|
try {
|
||||||
document.getElementById("actualizando_inicio").hidden = false;
|
document.getElementById("actualizando_inicio").hidden = false;
|
||||||
document.getElementById("actualizando_inicio").style.display = "block";
|
document.getElementById("actualizando_inicio").style.display = "block";
|
||||||
|
|||||||
@ -568,3 +568,18 @@ table.dataTable.table-bordered {
|
|||||||
}
|
}
|
||||||
.notifyjs-arrow { display: none !important; }
|
.notifyjs-arrow { display: none !important; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Mantener legibles los valores y el historial sobre los fondos de día/noche. */
|
||||||
|
.estadisticas-panel {
|
||||||
|
background: rgba(20, 34, 50, 0.96);
|
||||||
|
color: #f2f5f9;
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
}
|
||||||
|
.estadisticas-panel .table {
|
||||||
|
--bs-table-bg: transparent;
|
||||||
|
--bs-table-color: #f2f5f9;
|
||||||
|
color: #f2f5f9;
|
||||||
|
}
|
||||||
|
.estadisticas-panel caption { color: #d0d9e5; }
|
||||||
|
.estadisticas-panel .form-label { color: #f2f5f9; }
|
||||||
|
|||||||
170
src/gui_common/js/estadisticas.js
Normal file
170
src/gui_common/js/estadisticas.js
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
/* Estadísticas del registro diario, compartidas por NW.js y la interfaz web. */
|
||||||
|
const EstadisticasUI = (() => {
|
||||||
|
const variables = [
|
||||||
|
['temperatura_externa', 'Temperatura exterior', '°C', 'temperature'],
|
||||||
|
['temperatura_interna', 'Temperatura interior', '°C', 'temperature'],
|
||||||
|
['humedad_externa', 'Humedad exterior', '%'],
|
||||||
|
['humedad_interna', 'Humedad interior', '%'],
|
||||||
|
['punto_de_rocio', 'Punto de rocío', '°C', 'temperature'],
|
||||||
|
['viento_velocidad', 'Velocidad del viento', 'km/h', 'speed'],
|
||||||
|
['viento_rafagas', 'Ráfagas', 'km/h', 'speed'],
|
||||||
|
['presion_relativa', 'Presión relativa', 'hPa'],
|
||||||
|
['lluvia_acu_hora', 'Lluvia acumulada en una hora', 'mm'],
|
||||||
|
['lluvia_acu_diaria', 'Lluvia acumulada diaria', 'mm'],
|
||||||
|
['luxer_uv', 'Índice UV', ''],
|
||||||
|
['luxer_intencidad', 'Iluminación', 'lux']
|
||||||
|
];
|
||||||
|
let generation = 0;
|
||||||
|
function numeric(value) {
|
||||||
|
if (typeof value !== 'number' && typeof value !== 'string') return null;
|
||||||
|
if (typeof value === 'string' && !value.trim()) return null;
|
||||||
|
const number = Number(value);
|
||||||
|
return Number.isFinite(number) ? number : null;
|
||||||
|
}
|
||||||
|
function timestamp(record) {
|
||||||
|
const iso = Date.parse(record.fechaISO);
|
||||||
|
if (Number.isFinite(iso)) return iso;
|
||||||
|
const parts = String(record.fecha || '').match(/^(\d{2})\/(\d{2})\/(\d{4})[ T](\d{2}):(\d{2})(?::(\d{2}))?$/);
|
||||||
|
return parts ? new Date(+parts[3], +parts[2] - 1, +parts[1], +parts[4], +parts[5], +(parts[6] || 0)).getTime() : NaN;
|
||||||
|
}
|
||||||
|
function analyze(input, system = 'SI') {
|
||||||
|
const records = typeof input === 'string' ? JSON.parse(input) : input;
|
||||||
|
if (!Array.isArray(records)) throw new Error('El registro diario no contiene una lista de mediciones.');
|
||||||
|
const rows = records.filter(r => r && typeof r === 'object' && !Array.isArray(r))
|
||||||
|
.map(record => ({record, time: timestamp(record)}))
|
||||||
|
.filter(row => Number.isFinite(row.time)).sort((a, b) => a.time - b.time);
|
||||||
|
const series = variables.map(([key, label, unit, kind]) => {
|
||||||
|
const imperial = system !== 'SI';
|
||||||
|
const points = rows.map(({record, time}) => {
|
||||||
|
let value = numeric(record[key]);
|
||||||
|
if (value !== null && imperial) {
|
||||||
|
if (kind === 'temperature') value = value * 9 / 5 + 32;
|
||||||
|
if (kind === 'speed') value *= 0.621371192;
|
||||||
|
}
|
||||||
|
return {time, value, label: String(record.fecha || record.fechaISO)};
|
||||||
|
});
|
||||||
|
const values = points.map(p => p.value).filter(v => v !== null);
|
||||||
|
// No sumar acumulados de lluvia ni convertir ausencias en ceros.
|
||||||
|
const stats = values.reduce((acc, v) => ({min: Math.min(acc.min, v), max: Math.max(acc.max, v), sum: acc.sum + v}), {min: Infinity, max: -Infinity, sum: 0});
|
||||||
|
return {key, label, unit: imperial && kind === 'temperature' ? '°F' : imperial && kind === 'speed' ? 'mph' : unit,
|
||||||
|
points, count: values.length, min: values.length ? stats.min : null,
|
||||||
|
max: values.length ? stats.max : null, mean: values.length ? stats.sum / values.length : null,
|
||||||
|
last: points.length ? points[points.length - 1].value : null};
|
||||||
|
});
|
||||||
|
return {total: records.length, dated: rows.length, discarded: records.length - rows.length,
|
||||||
|
changed: rows.filter(r => r.record.cambio === true).length, series,
|
||||||
|
first: rows[0]?.record.fecha || rows[0]?.record.fechaISO,
|
||||||
|
last: rows.at(-1)?.record.fecha || rows.at(-1)?.record.fechaISO};
|
||||||
|
}
|
||||||
|
const format = value => value === null ? '—' : value.toLocaleString('es-AR', {maximumFractionDigits: 2});
|
||||||
|
function element(tag, text, className) {
|
||||||
|
const node = document.createElement(tag);
|
||||||
|
if (text !== undefined) node.textContent = text;
|
||||||
|
if (className) node.className = className;
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
function draw(target, series) {
|
||||||
|
target.replaceChildren();
|
||||||
|
if (!series.count) { target.append(element('p', 'No hay mediciones válidas para esta variable.')); return; }
|
||||||
|
const svgNS = 'http://www.w3.org/2000/svg';
|
||||||
|
function svgNode(tag, attrs, text) {
|
||||||
|
const node = document.createElementNS(svgNS, tag);
|
||||||
|
for (const [key, value] of Object.entries(attrs)) node.setAttribute(key, value);
|
||||||
|
if (text !== undefined) node.textContent = text;
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
const svg = svgNode('svg', {viewBox: '0 0 900 320', role: 'img', 'aria-label': series.label + ' (' + series.unit + ')', style: 'width:100%;max-height:420px;min-height:220px'});
|
||||||
|
svg.append(svgNode('title', {}, series.label + ' · ' + series.unit));
|
||||||
|
const first = series.points[0].time, last = series.points.at(-1).time;
|
||||||
|
const span = series.max - series.min || Math.max(Math.abs(series.max) * 0.1, 1);
|
||||||
|
const low = series.min - span * 0.1, high = series.max + span * 0.1;
|
||||||
|
const x = t => first === last ? 470 : 80 + (t - first) / (last - first) * 780;
|
||||||
|
const y = v => 265 - (v - low) / (high - low) * 240;
|
||||||
|
for (let i = 0; i <= 4; i++) {
|
||||||
|
const value = low + (high - low) * i / 4;
|
||||||
|
svg.append(svgNode('line', {x1:80, x2:860, y1:y(value), y2:y(value), stroke:'currentColor', opacity:0.2}));
|
||||||
|
svg.append(svgNode('text', {x:72, y:y(value) + 4, 'text-anchor':'end', fill:'currentColor', 'font-size':13}, format(value)));
|
||||||
|
}
|
||||||
|
let path = '', gap = true;
|
||||||
|
for (const point of series.points) {
|
||||||
|
if (point.value === null) { gap = true; continue; }
|
||||||
|
path += `${gap ? 'M' : 'L'}${x(point.time).toFixed(2)},${y(point.value).toFixed(2)} `;
|
||||||
|
gap = false;
|
||||||
|
}
|
||||||
|
svg.append(svgNode('path', {d:path, fill:'none', stroke:'#64b5ff', 'stroke-width':2, 'vector-effect':'non-scaling-stroke'}));
|
||||||
|
// Limitar marcadores DOM para históricos grandes, manteniendo la curva completa.
|
||||||
|
const valid = series.points.filter(p => p.value !== null);
|
||||||
|
const step = Math.max(1, Math.ceil(valid.length / 200));
|
||||||
|
valid.forEach((point, i) => {
|
||||||
|
if (i % step && i !== valid.length - 1) return;
|
||||||
|
const dot = svgNode('circle', {cx:x(point.time), cy:y(point.value), r:3, fill:'#64b5ff'});
|
||||||
|
dot.append(svgNode('title', {}, `${point.label}: ${format(point.value)} ${series.unit}`));
|
||||||
|
svg.append(dot);
|
||||||
|
});
|
||||||
|
svg.append(svgNode('text', {x:80, y:300, fill:'currentColor', 'font-size':12}, series.points[0].label));
|
||||||
|
svg.append(svgNode('text', {x:860, y:300, 'text-anchor':'end', fill:'currentColor', 'font-size':12}, series.points.at(-1).label));
|
||||||
|
target.append(svg);
|
||||||
|
}
|
||||||
|
function render(target, result) {
|
||||||
|
target.replaceChildren();
|
||||||
|
if (!result.dated) {
|
||||||
|
target.append(element('p', result.total ? 'No hay registros con fecha válida para procesar.' : 'Todavía no hay registros diarios. Iniciá la recolección y actualizá esta pantalla.', 'alert alert-info'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
target.append(element('p', `${result.dated} registros · ${result.changed} con cambios · ${result.first} — ${result.last}`));
|
||||||
|
if (result.discarded) target.append(element('p', `Se omitieron ${result.discarded} registros sin fecha válida.`, 'alert alert-warning'));
|
||||||
|
const tableBox = element('div', undefined, 'table-responsive');
|
||||||
|
const table = element('table', undefined, 'table table-bordered table-sm');
|
||||||
|
table.append(element('caption', 'Resumen de las muestras disponibles. Los acumulados de lluvia no se suman. — indica un dato ausente.'));
|
||||||
|
const header = element('tr');
|
||||||
|
for (const label of ['Variable', 'Unidad', 'Muestras', 'Mínimo', 'Promedio', 'Máximo', 'Último']) { const th = element('th', label); th.scope = 'col'; header.append(th); }
|
||||||
|
const head = element('thead'); head.append(header); table.append(head);
|
||||||
|
const body = element('tbody');
|
||||||
|
for (const series of result.series) {
|
||||||
|
const row = element('tr');
|
||||||
|
for (const value of [series.label, series.unit, series.count, format(series.min), format(series.mean), format(series.max), format(series.last)]) row.append(element('td', value));
|
||||||
|
body.append(row);
|
||||||
|
}
|
||||||
|
table.append(body); tableBox.append(table); target.append(tableBox);
|
||||||
|
const label = element('label', 'Variable del gráfico', 'form-label'); label.htmlFor = 'estadisticas-variable';
|
||||||
|
const select = element('select', undefined, 'form-select mb-3'); select.id = label.htmlFor;
|
||||||
|
for (const series of result.series) {
|
||||||
|
const option = element('option', `${series.label} (${series.unit || 'índice'})`); option.value = series.key; select.append(option);
|
||||||
|
}
|
||||||
|
select.value = (result.series.find(s => s.count) || result.series[0]).key;
|
||||||
|
const graph = element('div'); graph.dataset.estadisticasGraph = '';
|
||||||
|
select.onchange = () => draw(graph, result.series.find(s => s.key === select.value));
|
||||||
|
target.append(label, select, graph); select.onchange();
|
||||||
|
}
|
||||||
|
async function mount(root, {mode, system = 'SI', load}) {
|
||||||
|
if (!root) return;
|
||||||
|
const id = ++generation;
|
||||||
|
const target = root.querySelector('[data-estadisticas-content]');
|
||||||
|
const button = root.querySelector('[data-estadisticas-refresh]');
|
||||||
|
if (!target || !button) return;
|
||||||
|
button.onclick = () => mount(root, {mode, system, load});
|
||||||
|
if (!['Recolector', 'Recolector_USB', 'Recolector_IP'].includes(mode)) {
|
||||||
|
button.disabled = true;
|
||||||
|
const notice = element('p', 'Las estadísticas del registro diario están disponibles en modo recolector.', 'alert alert-info');
|
||||||
|
notice.style.setProperty('--bs-alert-bg', '#1a606f');
|
||||||
|
notice.style.setProperty('--bs-alert-color', '#fff');
|
||||||
|
target.replaceChildren(notice);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
button.disabled = true;
|
||||||
|
target.setAttribute('aria-busy', 'true');
|
||||||
|
target.replaceChildren(element('p', 'Cargando estadísticas…', 'alert alert-info'));
|
||||||
|
try {
|
||||||
|
const result = analyze(await load(), system);
|
||||||
|
if (id === generation && root.isConnected) render(target, result);
|
||||||
|
} catch (error) {
|
||||||
|
if (id === generation && root.isConnected) target.replaceChildren(element('p', 'No se pudieron cargar las estadísticas. Revisá el registro diario y volvé a intentar.', 'alert alert-danger'));
|
||||||
|
console.warn('Estadísticas:', error.message);
|
||||||
|
} finally {
|
||||||
|
if (id === generation) { button.disabled = false; target.setAttribute('aria-busy', 'false'); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function dispose() { generation++; }
|
||||||
|
return {analyze, mount, dispose};
|
||||||
|
})();
|
||||||
|
if (typeof module === 'object' && module.exports) module.exports = EstadisticasUI;
|
||||||
@ -1257,322 +1257,10 @@ function VerRegistrosLog(select)
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function graficoEstadisticas()
|
async function graficoEstadisticas() {
|
||||||
{
|
return EstadisticasUI.mount(document.getElementById('estadisticas'), {
|
||||||
// GRAFICO DEL TIEMPO CHARTJS
|
mode: configGeneralEMA.ConfGeneral.Modo,
|
||||||
document.getElementById("grafico").style.display = "none";
|
system: configGeneralEMA.ConfGeneral.SistemaMetrico,
|
||||||
document.getElementById("actualizando").style.display = "block";
|
load: () => $.ajax({url: '/VerHistorico', dataType: 'json', timeout: 15000})
|
||||||
if(configGeneralEMA.ConfGeneral.Modo == 'Recolector') // se comprueba el modo se encuentra en Recolector
|
});
|
||||||
{
|
|
||||||
//Se genera el grafico
|
|
||||||
$.get("/verHistorico", function(data, status)
|
|
||||||
{
|
|
||||||
var db = new Date();
|
|
||||||
var mes = db.getMonth() + 1; if(mes <= 9) {mes = '0'+ mes};
|
|
||||||
var dia= db.getDate();if(dia <= 9) {dia = '0'+ dia};
|
|
||||||
var fecha = dia + "/" + mes + "/" + db.getFullYear();
|
|
||||||
db = null;mes=null;dia=null;
|
|
||||||
var RegistroDiarioTmp = JSON.parse(data);
|
|
||||||
var CantRegistros = RegistroDiarioTmp.length;
|
|
||||||
if(CantRegistros > 1)
|
|
||||||
{
|
|
||||||
delete RegistroDiarioTmp['fechaISO'];
|
|
||||||
delete RegistroDiarioTmp['fechaUTC'];
|
|
||||||
delete RegistroDiarioTmp['luxer_intencidad_1hs'];
|
|
||||||
delete RegistroDiarioTmp['viento_direccion_nombre'];
|
|
||||||
delete RegistroDiarioTmp['lluvia_acu_diaria'];
|
|
||||||
delete RegistroDiarioTmp['temperatura_interna_max_dia'];
|
|
||||||
delete RegistroDiarioTmp['temperatura_interna_min_dia'];
|
|
||||||
delete RegistroDiarioTmp['humedad_interna_max_dia'];
|
|
||||||
delete RegistroDiarioTmp['humedad_interna_min_dia'];
|
|
||||||
delete RegistroDiarioTmp['temperatura_externa_max_dia'];
|
|
||||||
delete RegistroDiarioTmp['temperatura_externa_min_dia'];
|
|
||||||
delete RegistroDiarioTmp['humedad_externa_max_dia'];
|
|
||||||
delete RegistroDiarioTmp['humedad_externa_min_dia'];
|
|
||||||
delete RegistroDiarioTmp['punto_de_rocio_max_dia'];
|
|
||||||
delete RegistroDiarioTmp['punto_de_rocio_min_dia'];
|
|
||||||
delete RegistroDiarioTmp['luxer_intencidad_max_dia'];
|
|
||||||
delete RegistroDiarioTmp['luxer_intencidad_min_dia'];
|
|
||||||
delete RegistroDiarioTmp['luxer_uv_max_dia'];
|
|
||||||
delete RegistroDiarioTmp['luxer_uv_min_dia'];
|
|
||||||
delete RegistroDiarioTmp['presion_relativa_max_dia'];
|
|
||||||
delete RegistroDiarioTmp['presion_relativa_min_dia'];
|
|
||||||
delete RegistroDiarioTmp['viento_direccion_prom_dia'];
|
|
||||||
delete RegistroDiarioTmp['viento_velocidad_max_dia'];
|
|
||||||
delete RegistroDiarioTmp['viento_rafagas_max_dia'];
|
|
||||||
RegistroDiarioConCambios = [];
|
|
||||||
for (var j = 0; j < CantRegistros; j++) {
|
|
||||||
if ( RegistroDiarioTmp[j].cambio == true) RegistroDiarioConCambios.push(RegistroDiarioTmp[j]);
|
|
||||||
}
|
|
||||||
|
|
||||||
delete RegistroDiarioConCambios['diferencia'];
|
|
||||||
// Se generan los datos para el grafico
|
|
||||||
var fecha_maxima = RegistroDiarioConCambios[RegistroDiarioConCambios.length-1].fecha;
|
|
||||||
var labels = RegistroDiarioConCambios.map(function(e) { return e.fecha.substring(11, 16) || 0;});
|
|
||||||
var tmp_Ext = RegistroDiarioConCambios.map(function(e) { return e.temperatura_externa || 0;});
|
|
||||||
var hum_Ext = RegistroDiarioConCambios.map(function(e) { return e.humedad_externa || 0;});
|
|
||||||
var tmp_Int = RegistroDiarioConCambios.map(function(e) { return e.temperatura_interna || 0;});
|
|
||||||
var hum_Int = RegistroDiarioConCambios.map(function(e) { return e.humedad_interna || 0;});
|
|
||||||
var vientos = RegistroDiarioConCambios.map(function(e) { return e.viento_velocidad || 0;});
|
|
||||||
var vinetosRaf = RegistroDiarioConCambios.map(function(e) { return e.viento_rafagas || 0;});
|
|
||||||
var UV = RegistroDiarioConCambios.map(function(e) { return e.luxer_uv || 0;});
|
|
||||||
var Luxer = RegistroDiarioConCambios.map(function(e) { return e.luxer_intencidad || 0;});
|
|
||||||
var Lluvia = RegistroDiarioConCambios.map(function(e) { return e.lluvia_acu_hora || 0;});
|
|
||||||
var presionRel = RegistroDiarioConCambios.map(function(e) { return e.presion_relativa || 0;});
|
|
||||||
var validacion = RegistroDiarioConCambios.map(function(e) { return (e.estado_validacion_ant) || 0;});
|
|
||||||
var puntorocio = RegistroDiarioConCambios.map(function(e) { return e.punto_de_rocio || 0;});
|
|
||||||
var totalErrores = 0;
|
|
||||||
for(var i=0;i<validacion.length;i++)
|
|
||||||
{ if(!isNaN(parseInt(validacion[i]),10)) totalErrores += parseInt(validacion[i],10);};
|
|
||||||
// Se genera grafico de resumen
|
|
||||||
new Chart(document.getElementById("graph_inicio"), {
|
|
||||||
type: 'bar',
|
|
||||||
data: {
|
|
||||||
labels: ["Registros Totales \n "+ CantRegistros, "Registros Distintos", "Errores Detectados"],
|
|
||||||
datasets: [
|
|
||||||
{
|
|
||||||
label: "Resumen de Estadisticas",
|
|
||||||
backgroundColor: ["#3e95cd", "#28a745","#c42850"],
|
|
||||||
data: [CantRegistros,RegistroDiarioConCambios.length, totalErrores]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
options: {
|
|
||||||
showAllTooltips: true,
|
|
||||||
hover: {
|
|
||||||
animationDuration: 0
|
|
||||||
},
|
|
||||||
animation: {
|
|
||||||
duration: 1,
|
|
||||||
onComplete: function () {
|
|
||||||
var chartInstance = this.chart,
|
|
||||||
ctx = chartInstance.ctx;
|
|
||||||
ctx.font = Chart.helpers.fontString('14', 'normal', Chart.defaults.global.defaultFontFamily);
|
|
||||||
ctx.fillStyle = 'white';
|
|
||||||
ctx.textAlign = 'center';
|
|
||||||
ctx.textBaseline = 'bottom';
|
|
||||||
ctx.fontColor = 'white';
|
|
||||||
this.data.datasets.forEach(function (dataset, i) {
|
|
||||||
var meta = chartInstance.controller.getDatasetMeta(i);
|
|
||||||
meta.data.forEach(function (bar, index) {
|
|
||||||
var data = dataset.data[index];
|
|
||||||
ctx.fillText(data, bar._model.x, bar._model.y - 5);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
legend: {
|
|
||||||
display: false,
|
|
||||||
labels: {
|
|
||||||
display: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
title: {
|
|
||||||
display: false
|
|
||||||
},
|
|
||||||
scales: {
|
|
||||||
yAxes: [{
|
|
||||||
ticks: {
|
|
||||||
beginAtZero:true,
|
|
||||||
fontColor: 'white'
|
|
||||||
},
|
|
||||||
}],
|
|
||||||
xAxes: [{
|
|
||||||
ticks: {
|
|
||||||
fontColor: 'white'
|
|
||||||
},
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
RegistroDiarioTmp = null;CantRegistros=null;CantiddPendientesAlmacenados=null; CantiddPendientesPublicados = null;RegistroDiarioConCambios = null;
|
|
||||||
//Se genera el grafico
|
|
||||||
Grafico = new Chart(document.getElementById("graph"), {
|
|
||||||
type: 'line',
|
|
||||||
data: {
|
|
||||||
labels: labels,
|
|
||||||
datasets: [{
|
|
||||||
label: 'Temp.Ext',
|
|
||||||
data: tmp_Ext,
|
|
||||||
pointRadius:1,
|
|
||||||
// backgroundColor: ['rgba(200, 0, 0, 0.8)'],
|
|
||||||
borderColor: ['rgba(200,0,0,1)'],
|
|
||||||
borderWidth: 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Hum.Ext',
|
|
||||||
data: hum_Ext,
|
|
||||||
pointRadius:0.2,
|
|
||||||
// backgroundColor: ['rgba(100,0,0, 0.8)'],
|
|
||||||
borderColor: ['rgba(100,0,100,1)'],
|
|
||||||
borderWidth: 3,
|
|
||||||
hidden: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Pto.Rocio',
|
|
||||||
data: puntorocio,
|
|
||||||
pointRadius:0.2,
|
|
||||||
// backgroundColor: ['rgba(100,100,0, 0.8)'],
|
|
||||||
borderColor: ['rgba(100,100,0,1)'],
|
|
||||||
borderWidth: 3,
|
|
||||||
hidden: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Temp.Int',
|
|
||||||
data: tmp_Int,
|
|
||||||
pointRadius:0.2,
|
|
||||||
//backgroundColor: ['rgba(100,100,100, 0.8)'],
|
|
||||||
borderColor: ['rgba(100,100,255,1)'],
|
|
||||||
borderWidth: 3,
|
|
||||||
hidden: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Hum.Int',
|
|
||||||
data: hum_Int,
|
|
||||||
pointRadius:0.2,
|
|
||||||
backgroundColor: ['rgba(100,180,0, 0.8)'],
|
|
||||||
borderColor: ['rgba(100,190,0,1)'],
|
|
||||||
borderWidth: 2,
|
|
||||||
hidden: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'V.Raf',
|
|
||||||
data: vinetosRaf,
|
|
||||||
pointRadius:0.2,
|
|
||||||
// backgroundColor: ['rgba(255,80,220, 0.8)'],
|
|
||||||
borderColor: ['rgba(100,90,0,1)'],
|
|
||||||
borderWidth: 3,
|
|
||||||
hidden: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'V.Vel',
|
|
||||||
data: vientos,
|
|
||||||
pointRadius:0.2,
|
|
||||||
backgroundColor: ['rgba(100,80,0, 1)'],
|
|
||||||
borderColor: ['rgba(100,90,0,1)'],
|
|
||||||
borderWidth: 0.2,
|
|
||||||
hidden: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'UV',
|
|
||||||
data: UV,
|
|
||||||
pointRadius:0.2,
|
|
||||||
backgroundColor: ['rgba(97, 42, 154, 0.9)'],
|
|
||||||
borderColor: ['rgba(97, 42, 154,1)'],
|
|
||||||
borderWidth: 2,
|
|
||||||
hidden: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'L.Intec',
|
|
||||||
data: Luxer,
|
|
||||||
pointRadius:0.2,
|
|
||||||
backgroundColor: ['rgba(255,255,0, 0.9)'],
|
|
||||||
borderColor: ['rgba(255,255,0,1)'],
|
|
||||||
borderWidth: 2,
|
|
||||||
hidden: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Lluvia',
|
|
||||||
data: Lluvia,
|
|
||||||
pointRadius:0.2,
|
|
||||||
backgroundColor: ['rgba(100,80,100, 0.9)'],
|
|
||||||
borderColor: ['rgba(100,90,0,1)'],
|
|
||||||
borderWidth: 2,
|
|
||||||
hidden: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Valid.',
|
|
||||||
data: validacion,
|
|
||||||
pointRadius:0.2,
|
|
||||||
backgroundColor: ['rgba(220,53,69, 1)'],
|
|
||||||
borderColor: ['rgba(220,60,69,1)'],
|
|
||||||
borderWidth: 2,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Pre.R',
|
|
||||||
data: presionRel,
|
|
||||||
pointRadius:0.2,
|
|
||||||
//backgroundColor: ['rgba(100,80,100, 0.8)'],
|
|
||||||
borderColor: ['rgba(255,255,255,1)'],
|
|
||||||
borderWidth: 2,
|
|
||||||
hidden: true,
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
options: {
|
|
||||||
showAllTooltips: true,
|
|
||||||
//elements: { point: { hitRadius: 5, hoverRadius: 5 }},
|
|
||||||
tooltips: {
|
|
||||||
backgroundColor: 'white',
|
|
||||||
titleFontColor: 'black',
|
|
||||||
callbacks: {
|
|
||||||
labelColor: function(tooltipItem, chart) {
|
|
||||||
var datos = chart.config.data.datasets[tooltipItem.datasetIndex];
|
|
||||||
if(typeof datos.backgroundColor === 'undefined') { var colorfondo = 'white';}else{var colorfondo= datos.backgroundColor[0];}
|
|
||||||
return {
|
|
||||||
backgroundColor : colorfondo,
|
|
||||||
borderColor : datos.borderColor[0],
|
|
||||||
borderWidth: 5,
|
|
||||||
labelTextColor: 'black'
|
|
||||||
};
|
|
||||||
},
|
|
||||||
labelTextColor: function(tooltipItem, chart) {
|
|
||||||
return 'black';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
legend: {
|
|
||||||
labels: {
|
|
||||||
fontColor: 'white'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
scales: {
|
|
||||||
yAxes: [{
|
|
||||||
ticks: {
|
|
||||||
autoSkip: true,
|
|
||||||
maxTicksLimit: 30,
|
|
||||||
showXLabels: 10,
|
|
||||||
beginAtZero:true,
|
|
||||||
fontColor: 'white'
|
|
||||||
},
|
|
||||||
gridLines: {
|
|
||||||
display: false,
|
|
||||||
drawBorder: true,
|
|
||||||
color: "white",
|
|
||||||
}
|
|
||||||
}],
|
|
||||||
xAxes: [{
|
|
||||||
ticks: {
|
|
||||||
beginAtZero:true,
|
|
||||||
fontColor: 'white'
|
|
||||||
},
|
|
||||||
gridLines: {
|
|
||||||
display: false,
|
|
||||||
drawBorder: true,
|
|
||||||
color: "white",
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
|
|
||||||
title: {
|
|
||||||
display: true,
|
|
||||||
fontColor: 'white',
|
|
||||||
text: 'Estadísticas del dia hasta '+fecha_maxima
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
document.getElementById("actualizando").style.display = "none";
|
|
||||||
}else{
|
|
||||||
document.getElementById("inicio_estadisticas").innerHTML = "<h2> No hay registros para procesar .. </h2>";
|
|
||||||
document.getElementById("grafico").innerHTML = "<h2> No hay registros para procesar .. </h2>";
|
|
||||||
document.getElementById("actualizando").style.display = "none";
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}else{ //MODO Visor
|
|
||||||
document.getElementById("inicio_estadisticas").innerHTML = "<h2> Las estadisticas solo en modo recolector. </h2>";
|
|
||||||
document.getElementById("grafico").innerHTML = "<h2> Gráficos de estadisticas solo en modo recolector. </h2>";
|
|
||||||
document.getElementById("actualizando").style.display = "none";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@ -21,6 +21,7 @@
|
|||||||
<script src="js/configuracion.js"></script>
|
<script src="js/configuracion.js"></script>
|
||||||
<script src="js/tablas.js"></script>
|
<script src="js/tablas.js"></script>
|
||||||
<script src="js/estado.js"></script>
|
<script src="js/estado.js"></script>
|
||||||
|
<script src="js/estadisticas.js"></script>
|
||||||
<!-- Libreria bootstrap select para graficos -->
|
<!-- Libreria bootstrap select para graficos -->
|
||||||
<!-- Datable - Para las tablas dinamicas -->
|
<!-- Datable - Para las tablas dinamicas -->
|
||||||
|
|
||||||
@ -108,9 +109,9 @@
|
|||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" id="exterior-tab" data-bs-toggle="tab" data-bs-target="#exterior" role="tab" aria-controls="exterior" aria-selected="false"><img height="24px" width="24px" src="gui_common/img/exterior.webp" aria-controls="exterior"> EXTERIOR</a>
|
<a class="nav-link" id="exterior-tab" data-bs-toggle="tab" data-bs-target="#exterior" role="tab" aria-controls="exterior" aria-selected="false"><img height="24px" width="24px" src="gui_common/img/exterior.webp" aria-controls="exterior"> EXTERIOR</a>
|
||||||
</li>
|
</li>
|
||||||
<!-- <li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" id="estadisticas-tab" data-bs-toggle="tab" data-bs-target="#estadisticas" role="tab" aria-controls="estadisticas" aria-selected="false"><img height="24px" width="24px" src="gui_common/img/estadistica.png" aria-controls="estadisticas"> ESTADISTICAS</a>
|
<a class="nav-link" id="estadisticas-tab" data-bs-toggle="tab" data-bs-target="#estadisticas" role="tab" aria-controls="estadisticas" aria-selected="false"><img height="24px" width="24px" src="gui_common/img/estadistica.png" aria-controls="estadisticas"> ESTADISTICAS</a>
|
||||||
</li> -->
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" id="redes-tab" data-bs-toggle="tab" data-bs-target="#redes" role="tab" aria-controls="redes" aria-selected="false"><img height="24px" width="24px" src="gui_common/img/redes.webp" aria-controls="redes"> REDES</a>
|
<a class="nav-link" id="redes-tab" data-bs-toggle="tab" data-bs-target="#redes" role="tab" aria-controls="redes" aria-selected="false"><img height="24px" width="24px" src="gui_common/img/redes.webp" aria-controls="redes"> REDES</a>
|
||||||
</li>
|
</li>
|
||||||
@ -126,7 +127,7 @@
|
|||||||
<div class="tab-pane fade show active" id="inicio" role="tabpanel" aria-labelledby="inicio-tab"></div>
|
<div class="tab-pane fade show active" id="inicio" role="tabpanel" aria-labelledby="inicio-tab"></div>
|
||||||
<div class="tab-pane fade text-center" id="interior" role="tabpanel" aria-labelledby="interior-tab"></div>
|
<div class="tab-pane fade text-center" id="interior" role="tabpanel" aria-labelledby="interior-tab"></div>
|
||||||
<div class="tab-pane fade text-center" id="exterior" role="tabpanel" aria-labelledby="exterior-tab"></div>
|
<div class="tab-pane fade text-center" id="exterior" role="tabpanel" aria-labelledby="exterior-tab"></div>
|
||||||
<!-- <div class="tab-pane fade text-center" id="estadisticas" role="tabpanel" aria-labelledby="estadisticas-tab"></div>-->
|
<div class="tab-pane fade text-center" id="estadisticas" role="tabpanel" aria-labelledby="estadisticas-tab"></div>
|
||||||
<div class="tab-pane fade text-center" id="configuracion" role="tabpanel" aria-labelledby="configuracion-tab"></div>
|
<div class="tab-pane fade text-center" id="configuracion" role="tabpanel" aria-labelledby="configuracion-tab"></div>
|
||||||
<div class="tab-pane fade text-center" id="redes" role="tabpanel" aria-labelledby="redes-tab"></div>
|
<div class="tab-pane fade text-center" id="redes" role="tabpanel" aria-labelledby="redes-tab"></div>
|
||||||
<div class="tab-pane fade" id="contacto" role="tabpanel" aria-labelledby="contacto-tab"></div>
|
<div class="tab-pane fade" id="contacto" role="tabpanel" aria-labelledby="contacto-tab"></div>
|
||||||
|
|||||||
@ -1,42 +1,7 @@
|
|||||||
<!-- Inicio del Gráfico -->
|
<section class="text-start estadisticas-panel" aria-label="Estadísticas del registro diario">
|
||||||
<div >
|
<div class="d-flex justify-content-between align-items-center mb-3 gap-2">
|
||||||
<button id="boton_grafico" class="btn btn-sm btn-success" onclick="Mostrar()" role="button">Gráfico Historial</button>
|
<h2 class="h4 mb-0">Estadísticas del registro diario</h2>
|
||||||
<button id="boton_volver" class="btn btn-sm btn-success" onclick="Volver()" role="button">Volver</button>
|
<button type="button" class="btn btn-primary btn-sm" data-estadisticas-refresh>Actualizar</button>
|
||||||
<br><br>
|
</div>
|
||||||
<div id="actualizando" style="display:block">
|
<div data-estadisticas-content aria-live="polite"></div>
|
||||||
<br><br> <img src='gui_common/img/animadas/cargando2.gif' width='40' height='40' /> <br> <small>Cargando Gráfico...</small>
|
</section>
|
||||||
</div>
|
|
||||||
<div id="inicio_estadisticas">
|
|
||||||
<canvas id="graph_inicio" style="width:100%; height:400px; max-width:80vw; max-height:50vh;"></canvas>
|
|
||||||
</div>
|
|
||||||
<button id="diag_toggle_canvas" class="btn btn-sm btn-warning" onclick="(function(){var c=document.getElementById('graph_inicio'); if(c.style.display==='none'){c.style.display='block';} else {c.style.display='none';}})()">Toggle Canvas</button>
|
|
||||||
<button id="diag_enable_graphs" class="btn btn-sm btn-success" onclick="(function(){ try { window._ENABLE_GRAPHICS = true; console.log('Graphics enabled'); graficoEstadisticas(); } catch(e){console.error(e);} })()">Enable Graphs</button>
|
|
||||||
<button id="diag_paint_test" class="btn btn-sm btn-info" onclick="(function(){ try { if(window._testCanvasPaint){ window._testCanvasPaint(); } else { console.warn('_testCanvasPaint no disponible'); } } catch(e){console.error(e);} })()">Canvas Paint Test</button>
|
|
||||||
|
|
||||||
<div id="grafico" style="background-color:#182c3e00; display:none" >
|
|
||||||
<canvas id="graph" style="height:50vh; width:80vw" ></canvas>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<script>
|
|
||||||
|
|
||||||
function Mostrar()
|
|
||||||
{
|
|
||||||
document.getElementById("grafico").style.display = "block";
|
|
||||||
document.getElementById("inicio_estadisticas").style.display = "none";
|
|
||||||
document.getElementById("inicio_estadisticas").hidden = true;
|
|
||||||
document.getElementById("boton_grafico").hidden = true;
|
|
||||||
document.getElementById("boton_volver").hidden = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function Volver()
|
|
||||||
{
|
|
||||||
document.getElementById("grafico").style.display = "none";
|
|
||||||
document.getElementById("inicio_estadisticas").style.display = "block";
|
|
||||||
document.getElementById("inicio_estadisticas").hidden = false;
|
|
||||||
document.getElementById("boton_grafico").hidden = false;
|
|
||||||
document.getElementById("boton_volver").hidden = true;
|
|
||||||
}
|
|
||||||
document.getElementById("boton_volver").hidden = true;
|
|
||||||
|
|
||||||
</script>
|
|
||||||
<!-- Fin del Gráfico -->
|
|
||||||
|
|||||||
@ -39,3 +39,21 @@
|
|||||||
29/09/2026 01:59:26 - 0|CORE|✅ Modulo node-hid cargado correctamente.
|
29/09/2026 01:59:26 - 0|CORE|✅ Modulo node-hid cargado correctamente.
|
||||||
29/09/2026 01:59:26 - 0|CORE|⏳ Iniciando Ema Libre en Modo gráfico o Web.
|
29/09/2026 01:59:26 - 0|CORE|⏳ Iniciando Ema Libre en Modo gráfico o Web.
|
||||||
29/09/2026 01:59:43 - 0|CORE|🔚 SALIR: Se cerro el core de EMA LIBRE. Hasta luego!!!.
|
29/09/2026 01:59:43 - 0|CORE|🔚 SALIR: Se cerro el core de EMA LIBRE. Hasta luego!!!.
|
||||||
|
29/09/2026 23:41:13 - 0|CORE|🕵️. Buscando Modulo USB para el S.O - linux_x64_gui
|
||||||
|
29/09/2026 23:41:13 - 0|CORE|✅ Modulo node-hid cargado correctamente.
|
||||||
|
29/09/2026 23:41:13 - 0|CORE|⏳ Iniciando Ema Libre en Modo gráfico o Web.
|
||||||
|
29/09/2026 23:41:52 - 0|CORE|⚙️ RECONFIGURANDO: Se reconfigura el recolectorde EMA LIBRE.
|
||||||
|
29/09/2026 23:42:49 - 0|CORE|⚙️ RECONFIGURANDO: Se reconfigura el recolectorde EMA LIBRE.
|
||||||
|
29/09/2026 23:50:04 - 0|CORE|🕵️. Buscando Modulo USB para el S.O - linux_x64_gui
|
||||||
|
29/09/2026 23:50:04 - 0|CORE|✅ Modulo node-hid cargado correctamente.
|
||||||
|
29/09/2026 23:50:04 - 0|CORE|⏳ Iniciando Ema Libre en Modo gráfico o Web.
|
||||||
|
29/09/2026 23:50:16 - 0|CORE|🕵️. Buscando Modulo USB para el S.O - linux_x64_gui
|
||||||
|
29/09/2026 23:50:16 - 0|CORE|✅ Modulo node-hid cargado correctamente.
|
||||||
|
29/09/2026 23:50:16 - 0|CORE|⏳ Iniciando Ema Libre en Modo gráfico o Web.
|
||||||
|
29/09/2026 23:51:10 - 0|CORE|🕵️. Buscando Modulo USB para el S.O - linux_x64_gui
|
||||||
|
29/09/2026 23:51:10 - 0|CORE|✅ Modulo node-hid cargado correctamente.
|
||||||
|
29/09/2026 23:51:10 - 0|CORE|⏳ Iniciando Ema Libre en Modo gráfico o Web.
|
||||||
|
29/09/2026 23:51:19 - 0|CORE|🔚 SALIR: Se cerro el core de EMA LIBRE. Hasta luego!!!.
|
||||||
|
29/09/2026 23:52:16 - 0|CORE|🕵️. Buscando Modulo USB para el S.O - linux_x64_gui
|
||||||
|
29/09/2026 23:52:16 - 0|CORE|✅ Modulo node-hid cargado correctamente.
|
||||||
|
29/09/2026 23:52:16 - 0|CORE|⏳ Iniciando Ema Libre en Modo gráfico o Web.
|
||||||
|
|||||||
65
test/estadisticas.test.cjs
Normal file
65
test/estadisticas.test.cjs
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
const {test} = require('node:test');
|
||||||
|
const assert = require('node:assert/strict');
|
||||||
|
const fs = require('node:fs');
|
||||||
|
const vm = require('node:vm');
|
||||||
|
const stats = require('../src/gui_common/js/estadisticas.js');
|
||||||
|
const row = (time, values = {}) => ({fecha: `29/09/2026 ${time}`, ...values});
|
||||||
|
const metric = (result, key = 'temperatura_externa') => result.series.find(s => s.key === key);
|
||||||
|
|
||||||
|
test('empty history and malformed input', () => {
|
||||||
|
assert.equal(stats.analyze('[]').dated, 0);
|
||||||
|
assert.throws(() => stats.analyze('{}'));
|
||||||
|
assert.throws(() => stats.analyze('invalid'));
|
||||||
|
});
|
||||||
|
test('one unchanged record is enough', () => {
|
||||||
|
const result = stats.analyze([row('12:00:00', {temperatura_externa: 0, cambio: false})]);
|
||||||
|
assert.equal(result.changed, 0);
|
||||||
|
assert.equal(metric(result).mean, 0);
|
||||||
|
assert.equal(metric(result).count, 1);
|
||||||
|
});
|
||||||
|
test('missing values do not become zero and latest remains missing', () => {
|
||||||
|
const result = stats.analyze([row('12:00:00', {temperatura_externa: '-4'}), row('12:01:00', {temperatura_externa: 0}), row('12:02:00', {temperatura_externa: ''}), row('12:03:00', {temperatura_externa: null})]);
|
||||||
|
assert.equal(metric(result).mean, -2);
|
||||||
|
assert.equal(metric(result).count, 2);
|
||||||
|
assert.equal(metric(result).last, null);
|
||||||
|
assert.deepEqual(metric(result).points.map(p => p.value), [-4, 0, null, null]);
|
||||||
|
});
|
||||||
|
test('orders dates and excludes malformed rows without mutating input', () => {
|
||||||
|
const input = [row('13:00:00', {temperatura_externa: 20}), null, {}, row('12:00:00', {temperatura_externa: 10, cambio: true})];
|
||||||
|
const before = JSON.stringify(input);
|
||||||
|
const result = stats.analyze(input);
|
||||||
|
assert.equal(result.discarded, 2);
|
||||||
|
assert.equal(result.changed, 1);
|
||||||
|
assert.equal(metric(result).last, 20);
|
||||||
|
assert.equal(result.first, '29/09/2026 12:00:00');
|
||||||
|
assert.equal(JSON.stringify(input), before);
|
||||||
|
});
|
||||||
|
test('imperial temperature and wind conversion preserves absent measurements', () => {
|
||||||
|
const result = stats.analyze([row('12:00:00', {temperatura_externa: 0, viento_velocidad: 100})], 'SM');
|
||||||
|
assert.equal(metric(result).mean, 32);
|
||||||
|
assert.equal(metric(result).unit, '°F');
|
||||||
|
assert.equal(metric(result, 'viento_velocidad').unit, 'mph');
|
||||||
|
assert.ok(Math.abs(metric(result, 'viento_velocidad').mean - 62.1371192) < 0.000001);
|
||||||
|
assert.equal(metric(result, 'temperatura_interna').mean, null);
|
||||||
|
});
|
||||||
|
test('rain accumulators are not summed', () => {
|
||||||
|
const result = stats.analyze([row('12:00:00', {lluvia_acu_diaria: 10}), row('13:00:00', {lluvia_acu_diaria: 11})]);
|
||||||
|
assert.equal(metric(result, 'lluvia_acu_diaria').last, 11);
|
||||||
|
assert.equal(metric(result, 'lluvia_acu_diaria').max, 11);
|
||||||
|
});
|
||||||
|
test('both production adapters load the proper data source', async () => {
|
||||||
|
for (const mode of ['gui', 'gui_web']) {
|
||||||
|
let request;
|
||||||
|
const sandbox = {
|
||||||
|
configGeneralEMA: {ConfGeneral: {Modo: 'Recolector_IP', SistemaMetrico: 'SI'}},
|
||||||
|
document: {getElementById: id => id},
|
||||||
|
EstadisticasUI: {mount: async (root, options) => {assert.equal(root, 'estadisticas'); assert.equal(options.mode, 'Recolector_IP'); return options.load();}},
|
||||||
|
fsconf: {readFile: (file, encoding, cb) => {request = file; cb(null, '[]');}},
|
||||||
|
$: {ajax: options => {request = options.url; return Promise.resolve('[]');}}
|
||||||
|
};
|
||||||
|
const script = fs.readFileSync(`src/${mode}/controlador/funcionesgenerales.js`, 'utf8').split('async function graficoEstadisticas()')[1];
|
||||||
|
vm.runInNewContext('async function graficoEstadisticas()' + script, sandbox);
|
||||||
|
assert.equal(await sandbox.graficoEstadisticas(), '[]');
|
||||||
|
assert.equal(request, mode === 'gui' ? './logs/registro_diario.log' : '/VerHistorico');
|
||||||
|
}
|
||||||
|
});
|
||||||
@ -36,6 +36,41 @@ async function probe() {
|
|||||||
check(document.querySelector(tab.dataset.bsTarget)?.classList.contains('active'), 'Tab failed: ' + tab.id);
|
check(document.querySelector(tab.dataset.bsTarget)?.classList.contains('active'), 'Tab failed: ' + tab.id);
|
||||||
}
|
}
|
||||||
tests.push('all navigation tabs');
|
tests.push('all navigation tabs');
|
||||||
|
const statsRoot = document.getElementById('estadisticas');
|
||||||
|
statsRoot.innerHTML = window.statisticsHTML;
|
||||||
|
const records = [
|
||||||
|
{fecha:'29/09/2026 12:00:00', temperatura_externa:0, cambio:false},
|
||||||
|
{fecha:'29/09/2026 12:02:00', temperatura_externa:10, cambio:true}
|
||||||
|
];
|
||||||
|
await EstadisticasUI.mount(statsRoot, {mode:'Recolector_IP', load:async () => JSON.stringify(records)});
|
||||||
|
check(statsRoot.querySelectorAll('tbody tr').length === 12, 'Statistics summary missing');
|
||||||
|
check(statsRoot.querySelector('svg path').getAttribute('d').includes('L'), 'History line missing');
|
||||||
|
document.getElementById('estadisticas-tab').click();
|
||||||
|
nw.Window.get().show();
|
||||||
|
statsRoot.classList.add('ema-panel');
|
||||||
|
statsRoot.style.padding = '16px';
|
||||||
|
await wait(400);
|
||||||
|
check(statsRoot.getBoundingClientRect().height > 300, 'Statistics panel not visible');
|
||||||
|
const capture = await new Promise(resolve => nw.Window.get().capturePage(resolve, {format:'png', datatype:'buffer'}));
|
||||||
|
require('fs').writeFileSync(window.resultFile + '.estadisticas.png', capture);
|
||||||
|
const variable = statsRoot.querySelector('select');
|
||||||
|
variable.value = 'humedad_externa'; variable.dispatchEvent(new Event('change'));
|
||||||
|
check(!statsRoot.querySelector('svg'), 'Missing values plotted as zeros');
|
||||||
|
await EstadisticasUI.mount(statsRoot, {mode:'Recolector_USB', load:async () => [records[0]]});
|
||||||
|
check(statsRoot.querySelectorAll('svg circle').length === 1, 'Single unchanged record not plotted');
|
||||||
|
await EstadisticasUI.mount(statsRoot, {mode:'Recolector', load:async () => []});
|
||||||
|
check(statsRoot.textContent.includes('Todavía no hay registros'), 'Empty state missing');
|
||||||
|
await EstadisticasUI.mount(statsRoot, {mode:'Recolector', load:async () => {throw Error('fixture failure');}});
|
||||||
|
check(statsRoot.querySelector('.alert-danger') && !statsRoot.querySelector('button').disabled, 'Error/retry state missing');
|
||||||
|
await EstadisticasUI.mount(statsRoot, {mode:'Visor', load:async () => {throw Error('Visor must not load');}});
|
||||||
|
check(statsRoot.textContent.includes('modo recolector'), 'Visor message missing');
|
||||||
|
let resolveOld;
|
||||||
|
const old = EstadisticasUI.mount(statsRoot, {mode:'Recolector', load:() => new Promise(resolve => {resolveOld = resolve;})});
|
||||||
|
await EstadisticasUI.mount(statsRoot, {mode:'Recolector', load:async () => []});
|
||||||
|
resolveOld(records); await old;
|
||||||
|
check(!statsRoot.querySelector('svg'), 'Stale response overwrote newer statistics');
|
||||||
|
tests.push('statistics summary, chart, selection, single record, empty/error/visor states and stale requests');
|
||||||
|
|
||||||
$('#settings').html(window.settingsHTML);
|
$('#settings').html(window.settingsHTML);
|
||||||
const fixture = window.configFixture;
|
const fixture = window.configFixture;
|
||||||
const interfaces = {eth0: [{family:'IPv4',internal:false,address:'127.0.0.1'}]};
|
const interfaces = {eth0: [{family:'IPv4',internal:false,address:'127.0.0.1'}]};
|
||||||
@ -154,7 +189,7 @@ window.persistConfiguration=async draft => {
|
|||||||
else {const response=await $.ajax({url:'/GuardarConf',method:'POST',contentType:'application/json',data:JSON.stringify({configuracion:draft})});if(!response.ok)throw new Error('Save failed');}
|
else {const response=await $.ajax({url:'/GuardarConf',method:'POST',contentType:'application/json',data:JSON.stringify({configuracion:draft})});if(!response.ok)throw new Error('Save failed');}
|
||||||
};
|
};
|
||||||
window.readConfiguration=()=>{const sandbox={module:{exports:{}}};require('vm').runInNewContext(require('fs').readFileSync(${JSON.stringify(configFile)},'utf8'),sandbox);return sandbox.module.exports;};
|
window.readConfiguration=()=>{const sandbox={module:{exports:{}}};require('vm').runInNewContext(require('fs').readFileSync(${JSON.stringify(configFile)},'utf8'),sandbox);return sandbox.module.exports;};
|
||||||
window.settingsHTML=${JSON.stringify(settings)};window.languageURL=${JSON.stringify(language)};window.resultFile=${JSON.stringify(resultFile)};addEventListener('load',${probe.toString()});</script>`);
|
window.statisticsHTML=${JSON.stringify(read(mode + '/view/modelo_estadisticas.html'))};window.settingsHTML=${JSON.stringify(settings)};window.languageURL=${JSON.stringify(language)};window.resultFile=${JSON.stringify(resultFile)};addEventListener('load',${probe.toString()});</script>`);
|
||||||
const args = JSON.parse(read('package.json'))['chromium-args'].replace('--disable-logging', '');
|
const args = JSON.parse(read('package.json'))['chromium-args'].replace('--disable-logging', '');
|
||||||
fs.writeFileSync(path.join(folder, 'package.json'), JSON.stringify({name:'carpincho-ui-test',main:'index.html','chromium-args':args,window:{show:false,width:1200,height:900}}));
|
fs.writeFileSync(path.join(folder, 'package.json'), JSON.stringify({name:'carpincho-ui-test',main:'index.html','chromium-args':args,window:{show:false,width:1200,height:900}}));
|
||||||
await new Promise((resolve, reject) => {
|
await new Promise((resolve, reject) => {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user