Cambios generales
This commit is contained in:
parent
5221b6fb19
commit
6777bf363b
@ -1,4 +1,11 @@
|
||||
** Mejoras de interfaz y configuración (Septiembre 27, 2026)
|
||||
** Versión 2.0.0: (Septiembre 27, 2026)
|
||||
+ Se actualiza la versión de NWJS → 0.107.0
|
||||
+ Node-hid ahora no necesita nw-gyp, se actualiza la compilación
|
||||
+ Se migra escritorio y web a Bootstrap 5 con Popper incluido en el bundle.
|
||||
+ Se actualiza bootstrap-select a 1.14.0-beta3 y se reconstruyen selects conservando el valor para evitar opciones duplicadas.
|
||||
+ Se integran DataTables 1.13.11 y Buttons 2.4.3 con estilos Bootstrap 5.
|
||||
+ Se corrigen atributos, clases, rutas de imágenes y estilos de tablas/formularios.
|
||||
+ Se agrega desktop:dev y test:ui (requiere un binario NW.js SDK como argumento).
|
||||
+ Se actualizó RGraph 7.01 → 7.21
|
||||
+ NW.js 0.117.0 centralizado en runtime.json para desarrollo y empaquetado x64.
|
||||
+ Formulario compartido con controles nativos, validación y aviso de cambios sin guardar.
|
||||
@ -8,15 +15,8 @@
|
||||
+ Estados separados para conexión, última lectura y publicación, con motivos y acciones.
|
||||
+ Pruebas de configuración, interfaz y arranque aisladas de la estación real.
|
||||
+ El empaquetador ARM histórico queda bloqueado: su Node/NW.js no soportan esta versión.
|
||||
|
||||
** Versión 2.0.0: (Septiembre 27, 2026)
|
||||
+ Se actualiza la versión de NWJS → 0.107.0
|
||||
+ Node-hid ahora no necesita nw-gyp, se actualiza la compilación
|
||||
+ Se migra escritorio y web a Bootstrap 5 con Popper incluido en el bundle.
|
||||
+ Se actualiza bootstrap-select a 1.14.0-beta3 y se reconstruyen selects conservando el valor para evitar opciones duplicadas.
|
||||
+ Se integran DataTables 1.13.11 y Buttons 2.4.3 con estilos Bootstrap 5.
|
||||
+ Se corrigen atributos, clases, rutas de imágenes y estilos de tablas/formularios.
|
||||
+ Se agrega desktop:dev y test:ui (requiere un binario NW.js SDK como argumento).
|
||||
+ Se mejora el iconizado.
|
||||
+ Se mejora el boton de guardado.
|
||||
|
||||
** Versión 1.36: (Enero 10 , 2026)
|
||||
+ Se incorporó recolectar por WIFI/IP.
|
||||
|
||||
@ -160,7 +160,7 @@
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
$('.menu').on('click', function(evnt) {
|
||||
var obj = $(evnt.target);
|
||||
var obj = $(evnt.currentTarget);
|
||||
$('#' + obj.attr('aria-controls')).empty();
|
||||
let screens = gui.Screen.screens;
|
||||
let winX = win.x;
|
||||
@ -178,7 +178,7 @@
|
||||
}
|
||||
}
|
||||
//nw.Window.get().moveTo(0);
|
||||
switch (obj.attr('id')) {
|
||||
switch (obj.attr('aria-controls')) {
|
||||
case 'Wzoomin':
|
||||
win.zoomLevel += 0.3;
|
||||
break;
|
||||
@ -190,9 +190,7 @@
|
||||
nw.Window.get().resizeTo(currentScreen.bounds.width, currentScreen.bounds.height);
|
||||
break;
|
||||
case 'Wmin':
|
||||
nw.Window.get().minimize();
|
||||
//win.hide(); // Oculta completamente la ventana
|
||||
//win.setShowInTaskbar(false); // Quita el icono del taskbar
|
||||
ocultarVentana();
|
||||
break;
|
||||
case 'Wmax':
|
||||
nw.Window.get().moveTo(currentScreen.bounds.x, currentScreen.bounds.y);
|
||||
|
||||
@ -32,6 +32,8 @@
|
||||
// y exporta las variables necesarias para el resto del archivo.
|
||||
var configGeneralEMA, configGlobalEMA, funcionesActualizar, EMA, gui, os, win;
|
||||
var _IS_NW = false;
|
||||
let tray = null;
|
||||
let ventanaMinimizada = false;
|
||||
const iconoEncendido = './gui_common/iconos/icono_enc.png'; // Imagen del Icono Encendido
|
||||
const iconoApagado = './gui_common/iconos/icono_off.png'; // Imagen del Icono Apagado
|
||||
const iconoPublicado = './gui_common/iconos/icono_pub.png'; // Imagen del Icono Prublicado --> no utilizada
|
||||
@ -58,85 +60,64 @@ if (_IS_NW) {
|
||||
// **********************Iconizado / Menu Superior *********************************************************
|
||||
gui.App.clearCache(); // Se limpia cache
|
||||
win = gui.Window.get(); // Se obtiene la ventana actual
|
||||
// Crear el icono en el tray
|
||||
function createTray() {
|
||||
if (tray) return; // Evitar duplicados
|
||||
// Mantener el icono y su menú durante toda la vida de la ventana.
|
||||
function mostrarVentana() {
|
||||
win.setShowInTaskbar(true);
|
||||
win.show();
|
||||
if (ventanaMinimizada) {
|
||||
win.restore();
|
||||
ventanaMinimizada = false;
|
||||
}
|
||||
win.focus();
|
||||
}
|
||||
|
||||
// Crear menú contextual (sin 'menubar')
|
||||
const menu = new nw.Menu();
|
||||
menu.append(new nw.MenuItem({ type: 'checkbox', label: 'box1' }));
|
||||
menu.append(new nw.MenuItem({
|
||||
label: 'Iniciar',
|
||||
click: () => { if (typeof INICIAR === 'function') INICIAR(); }
|
||||
}));
|
||||
menu.append(new nw.MenuItem({
|
||||
label: 'Detener',
|
||||
click: () => { if (typeof DETENER === 'function') DETENER(); }
|
||||
}));
|
||||
menu.append(new nw.MenuItem({ type: 'separator' }));
|
||||
menu.append(new nw.MenuItem({
|
||||
function ocultarVentana() {
|
||||
createTray();
|
||||
win.hide();
|
||||
win.setShowInTaskbar(false);
|
||||
}
|
||||
|
||||
function createTray() {
|
||||
if (tray) return;
|
||||
|
||||
const menu = new gui.Menu();
|
||||
menu.append(new gui.MenuItem({
|
||||
label: 'Mostrar ventana',
|
||||
click: () => {
|
||||
win.show();
|
||||
win.restore();
|
||||
win.focus();
|
||||
win.setShowInTaskbar(true);
|
||||
removeTray();
|
||||
}
|
||||
click: mostrarVentana
|
||||
}));
|
||||
menu.append(new nw.MenuItem({
|
||||
menu.append(new gui.MenuItem({
|
||||
label: 'Ocultar ventana',
|
||||
click: () => {
|
||||
win.hide();
|
||||
win.setShowInTaskbar(false);
|
||||
}
|
||||
click: ocultarVentana
|
||||
}));
|
||||
menu.append(new nw.MenuItem({ type: 'separator' }));
|
||||
menu.append(new nw.MenuItem({
|
||||
menu.append(new gui.MenuItem({ type: 'separator' }));
|
||||
menu.append(new gui.MenuItem({ label: 'Iniciar', click: INICIAR }));
|
||||
menu.append(new gui.MenuItem({ label: 'Detener', click: DETENER }));
|
||||
menu.append(new gui.MenuItem({ type: 'separator' }));
|
||||
menu.append(new gui.MenuItem({
|
||||
label: 'Salir',
|
||||
click: () => {
|
||||
if (!ConfigUI.canLeave()) return;
|
||||
if (typeof EMA !== 'undefined' && typeof EMA.Salir === 'function') EMA.Salir();
|
||||
nw.App.closeAllWindows();
|
||||
nw.App.quit();
|
||||
if (typeof EMA.Salir === 'function') EMA.Salir();
|
||||
gui.App.closeAllWindows();
|
||||
gui.App.quit();
|
||||
}
|
||||
}));
|
||||
|
||||
// Crear tray icon
|
||||
tray = new nw.Tray({
|
||||
// Entregar el menú al crear el indicador, también en Linux.
|
||||
tray = new gui.Tray({
|
||||
title: 'EmaLibre Carpincho',
|
||||
icon: iconoApagado,
|
||||
menu: menu
|
||||
});
|
||||
tray.menu = menu;
|
||||
|
||||
// Click izquierdo: mostrar ventana
|
||||
tray.on('click', () => {
|
||||
console.log('Tray icon clicked');
|
||||
win.show();
|
||||
win.restore();
|
||||
win.focus();
|
||||
win.setShowInTaskbar(true);
|
||||
removeTray();
|
||||
if (typeof MuestraRegistroActual === 'function') MuestraRegistroActual(false);
|
||||
if (typeof Graficos === 'function') Graficos();
|
||||
if (typeof ClimaActual === 'function') ClimaActual();
|
||||
});
|
||||
|
||||
// Click derecho: abrir menú manualmente si el entorno no lo hace
|
||||
tray.on('right-click', () => {
|
||||
console.log('Tray icon right-clicked');
|
||||
tray.menu.popup();
|
||||
});
|
||||
|
||||
// Doble click: también mostrar
|
||||
tray.on('double-click', () => {
|
||||
win.show();
|
||||
win.focus();
|
||||
win.setShowInTaskbar(true);
|
||||
removeTray();
|
||||
});
|
||||
tray.tooltip = 'Ema Libre Carpincho';
|
||||
// NW.js abre el menú nativo con clic derecho; solo emite clic izquierdo.
|
||||
tray.on('click', mostrarVentana);
|
||||
}
|
||||
nw.Window.get().on('loaded', createTray);
|
||||
win.on('loaded', createTray);
|
||||
win.on('minimize', () => {
|
||||
ventanaMinimizada = true;
|
||||
ocultarVentana();
|
||||
});
|
||||
|
||||
// Window resizing is handled by responsive CSS; preserve manual zoom.
|
||||
|
||||
@ -171,7 +152,6 @@ if (_IS_NW) {
|
||||
};
|
||||
}
|
||||
// **********************Vairales Generales*********************
|
||||
let tray = null;
|
||||
var UltimoReg;
|
||||
var Ant_UltimoReg;
|
||||
var ModeloVisor; // Variables Generales - Registros recolectados
|
||||
@ -204,7 +184,7 @@ function INICIAR () {
|
||||
HABLA(0, 'Se inicio en modo Recolector', 'success');
|
||||
$('#signal').addClass('parpadea');
|
||||
}
|
||||
if (_IS_NW) tray.icon = iconoEncendido;
|
||||
if (_IS_NW) { createTray(); tray.icon = iconoEncendido; }
|
||||
$('td#barralateral').css('background-color', '#11881161');
|
||||
$('#estado').html('En Linea');
|
||||
$('#mensaje').html('Encendido');
|
||||
@ -268,7 +248,7 @@ function killAllTimers() {
|
||||
function DETENER () {
|
||||
clearInterval(intervaloBarra);
|
||||
killAllTimers();
|
||||
if (_IS_NW) tray.icon = iconoApagado;
|
||||
if (_IS_NW) { createTray(); tray.icon = iconoApagado; }
|
||||
$('td#barralateral').css('background-color', '#ff000030');
|
||||
$('#estado').html('Fuera de Linea');
|
||||
$('#img_estado').attr('src', iconoNeutro);
|
||||
|
||||
@ -5,9 +5,49 @@ const ConfigUI = (() => {
|
||||
const values = () => Object.fromEntries(Object.keys(ConfiguracionModelo.fields).map(id => [id, document.getElementById(id)?.value ?? '']));
|
||||
const snapshot = () => JSON.stringify(values());
|
||||
const dirty = () => !!form() && baseline !== snapshot();
|
||||
function message(text, error = false) {
|
||||
let notification;
|
||||
function notify(text, error) {
|
||||
if (notification) {
|
||||
bootstrap.Toast.getInstance(notification)?.dispose();
|
||||
notification.remove();
|
||||
}
|
||||
const toast = document.createElement('div');
|
||||
toast.className = 'toast position-fixed top-0 end-0 m-3 border-0 shadow text-bg-' + (error ? 'danger' : 'success');
|
||||
toast.style.zIndex = '1090';
|
||||
toast.style.maxWidth = 'calc(100vw - 2rem)';
|
||||
toast.setAttribute('role', error ? 'alert' : 'status');
|
||||
toast.setAttribute('aria-live', error ? 'assertive' : 'polite');
|
||||
toast.setAttribute('aria-atomic', 'true');
|
||||
const row = document.createElement('div');
|
||||
row.className = 'd-flex';
|
||||
const body = document.createElement('div');
|
||||
body.className = 'toast-body';
|
||||
const title = document.createElement('strong');
|
||||
title.className = 'd-block mb-1';
|
||||
title.textContent = error ? 'No se pudo guardar' : 'Configuración guardada';
|
||||
const detail = document.createElement('div');
|
||||
detail.textContent = text;
|
||||
body.append(title, detail);
|
||||
const close = document.createElement('button');
|
||||
close.type = 'button';
|
||||
close.className = 'btn-close btn-close-white me-2 m-auto';
|
||||
close.setAttribute('aria-label', 'Cerrar notificación');
|
||||
close.setAttribute('data-bs-dismiss', 'toast');
|
||||
row.append(body, close);
|
||||
toast.append(row);
|
||||
document.body.append(toast);
|
||||
notification = toast;
|
||||
toast.addEventListener('hidden.bs.toast', () => {
|
||||
bootstrap.Toast.getInstance(toast)?.dispose();
|
||||
toast.remove();
|
||||
if (notification === toast) notification = null;
|
||||
});
|
||||
new bootstrap.Toast(toast, {autohide: !error, delay: 5000}).show();
|
||||
}
|
||||
function message(text, error = false, floating = false) {
|
||||
const status = document.getElementById('config-status');
|
||||
if (status) { status.textContent = text; status.className = error ? 'text-danger' : 'text-white'; }
|
||||
if (floating) notify(text, error);
|
||||
}
|
||||
function canLeave() {
|
||||
if (saving) { message('Esperá a que termine el guardado.'); return false; }
|
||||
@ -72,7 +112,7 @@ const ConfigUI = (() => {
|
||||
const data = values();
|
||||
const invalid = ConfiguracionModelo.validate(data);
|
||||
errors(invalid);
|
||||
if (Object.keys(invalid).length) { message('Revisá los campos marcados.', true); return false; }
|
||||
if (Object.keys(invalid).length) { message('Revisá los campos marcados en el formulario.', true, true); return false; }
|
||||
const draft = ConfiguracionModelo.build(original, data);
|
||||
saving = true;
|
||||
const controls = [...form().querySelectorAll('input, select, button')];
|
||||
@ -81,11 +121,11 @@ const ConfigUI = (() => {
|
||||
message('Guardando…');
|
||||
try {
|
||||
await persist(draft);
|
||||
original = draft; baseline = snapshot(); message('Configuración guardada.');
|
||||
original = draft; baseline = snapshot(); message('Configuración guardada.', false, true);
|
||||
return draft;
|
||||
} catch (error) {
|
||||
if (error.fields) errors(error.fields);
|
||||
message(error.message || 'No se pudo guardar. Tus cambios siguen en el formulario.', true);
|
||||
message(error.message || 'Tus cambios siguen en el formulario. Volvé a intentarlo.', true, true);
|
||||
return false;
|
||||
} finally {
|
||||
saving = false;
|
||||
|
||||
@ -21,7 +21,6 @@ const EstadoUI = (() => {
|
||||
}
|
||||
target.append(box);
|
||||
};
|
||||
const configAction = {label: 'Revisar configuración', run() {document.getElementById('configuracion-tab')?.click();}};
|
||||
if (data.error) {card('Conexión', 'Sin respuesta', data.error, 'danger', {label:'Reintentar', run: refresh}); return;}
|
||||
const visor = data.mode === 'Visor';
|
||||
const running = data.state?.[0] === true;
|
||||
@ -29,7 +28,7 @@ const EstadoUI = (() => {
|
||||
const reason = data.state?.[1];
|
||||
card('Conexión', visor ? 'Fuente remota' : running ? 'Recolector activo' : 'Recolector detenido',
|
||||
reason || (sensorFault ? 'La estación informa un problema de sensores. Revisá alimentación y señal.' : visor ? 'La fecha de lectura indica la vigencia del dato recibido.' : running ? 'El proceso está activo; revisá la última lectura para confirmar recepción.' : 'Iniciá el recolector desde Inicio o revisá la configuración.'),
|
||||
reason || sensorFault ? 'warning' : running ? 'success' : 'secondary', reason || !running ? configAction : null);
|
||||
reason || sensorFault ? 'warning' : running ? 'success' : 'secondary');
|
||||
const record = data.record;
|
||||
const age = Date.now() - date(record?.fecha, data.utc);
|
||||
const stale = Number.isFinite(age) && age > Math.max(120, Number(data.interval || 60) * 3) * 1000;
|
||||
@ -40,7 +39,7 @@ const EstadoUI = (() => {
|
||||
const failed = publications.find(p => p.estado !== true && !['Esperar', 'No enviado'].includes(p.estado));
|
||||
card('Última publicación', last ? `${last.red}: ${last.fecha}` : visor ? 'No corresponde al modo visor' : 'Sin publicaciones',
|
||||
failed ? `${failed.red}: ${failed.error || 'No se pudo publicar'}. Revisá la red y las credenciales.` : last ? (last.estado === true ? 'Envío confirmado por la red.' : 'La red indica que hay que esperar antes de enviar.') : 'Todavía no hay un envío confirmado.',
|
||||
failed ? 'danger' : last?.estado === true ? 'success' : 'secondary', failed ? configAction : null);
|
||||
failed ? 'danger' : last?.estado === true ? 'success' : 'secondary');
|
||||
}
|
||||
async function refresh() {
|
||||
clearTimeout(timer);
|
||||
|
||||
@ -95,25 +95,27 @@
|
||||
</div>
|
||||
<div class="col-12 col-md-6 col-lg-3 col-centered">
|
||||
<label class="control-label" for="Debug">Debug y Tamaño</label><br>
|
||||
<select id='Debug' name="Debug" class="form-select">
|
||||
<optgroup label="Normal">
|
||||
<option value="1">1</option>
|
||||
<option value="2">2</option>
|
||||
<option value="3">3</option>
|
||||
</optgroup>
|
||||
<optgroup label="Alto">
|
||||
<option value="4">4</option>
|
||||
<option value="5">5</option>
|
||||
<option value="6">6</option>
|
||||
</optgroup>
|
||||
<optgroup label="Muy Alto">
|
||||
<option value="7">7</option>
|
||||
<option value="8">8</option>
|
||||
<option value="9">9</option>
|
||||
</optgroup>
|
||||
</select>
|
||||
<input type="text" class="form-control" id="debugMB" placeholder="MB Debug" maxlength="5" size="5">
|
||||
<label class="control-label">MB</label>
|
||||
<div class="input-group flex-nowrap">
|
||||
<select id='Debug' name="Debug" class="form-select">
|
||||
<optgroup label="Normal">
|
||||
<option value="1">1</option>
|
||||
<option value="2">2</option>
|
||||
<option value="3">3</option>
|
||||
</optgroup>
|
||||
<optgroup label="Alto">
|
||||
<option value="4">4</option>
|
||||
<option value="5">5</option>
|
||||
<option value="6">6</option>
|
||||
</optgroup>
|
||||
<optgroup label="Muy Alto">
|
||||
<option value="7">7</option>
|
||||
<option value="8">8</option>
|
||||
<option value="9">9</option>
|
||||
</optgroup>
|
||||
</select>
|
||||
<input type="text" class="form-control" id="debugMB" aria-label="Tamaño máximo del debug en MB" placeholder="MB Debug" maxlength="5" size="5">
|
||||
<span class="input-group-text">MB</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-md-6 col-lg-1 col-centered">
|
||||
<label class="control-label" for="utc">UTC</label><br>
|
||||
|
||||
@ -10,3 +10,23 @@
|
||||
27/09/2026 23:21:09 - 0|CORE|✅ Modulo node-hid cargado correctamente.
|
||||
27/09/2026 23:21:09 - 0|CORE|⏳ Iniciando Ema Libre en Modo gráfico o Web.
|
||||
27/09/2026 23:23:30 - 0|CORE|🔚 SALIR: Se cerro el core de EMA LIBRE. Hasta luego!!!.
|
||||
28/09/2026 21:04:14 - 0|CORE|🕵️. Buscando Modulo USB para el S.O - linux_x64_gui
|
||||
28/09/2026 21:04:14 - 0|CORE|✅ Modulo node-hid cargado correctamente.
|
||||
28/09/2026 21:04:14 - 0|CORE|⏳ Iniciando Ema Libre en Modo gráfico o Web.
|
||||
28/09/2026 21:09:57 - 0|CORE|🔚 SALIR: Se cerro el core de EMA LIBRE. Hasta luego!!!.
|
||||
28/09/2026 21:10:04 - 0|CORE|🕵️. Buscando Modulo USB para el S.O - linux_x64_gui
|
||||
28/09/2026 21:10:04 - 0|CORE|✅ Modulo node-hid cargado correctamente.
|
||||
28/09/2026 21:10:04 - 0|CORE|⏳ Iniciando Ema Libre en Modo gráfico o Web.
|
||||
28/09/2026 21:11:11 - 0|CORE|🔚 SALIR: Se cerro el core de EMA LIBRE. Hasta luego!!!.
|
||||
28/09/2026 21:12:28 - 0|CORE|🕵️. Buscando Modulo USB para el S.O - linux_x64_gui
|
||||
28/09/2026 21:12:28 - 0|CORE|✅ Modulo node-hid cargado correctamente.
|
||||
28/09/2026 21:12:28 - 0|CORE|⏳ Iniciando Ema Libre en Modo gráfico o Web.
|
||||
28/09/2026 21:25:06 - 0|CORE|⚙️ RECONFIGURANDO: Se reconfigura el recolectorde EMA LIBRE.
|
||||
28/09/2026 21:25:10 - 0|CORE|⚙️ RECONFIGURANDO: Se reconfigura el recolectorde EMA LIBRE.
|
||||
28/09/2026 21:25:40 - 0|CORE|⚙️ RECONFIGURANDO: Se reconfigura el recolectorde EMA LIBRE.
|
||||
28/09/2026 21:25:44 - 0|CORE|⚙️ RECONFIGURANDO: Se reconfigura el recolectorde EMA LIBRE.
|
||||
28/09/2026 21:25:55 - 0|CORE|⚙️ RECONFIGURANDO: Se reconfigura el recolectorde EMA LIBRE.
|
||||
28/09/2026 21:27:46 - 0|CORE|⚙️ RECONFIGURANDO: Se reconfigura el recolectorde EMA LIBRE.
|
||||
28/09/2026 21:28:00 - 0|CORE|⚙️ RECONFIGURANDO: Se reconfigura el recolectorde EMA LIBRE.
|
||||
28/09/2026 21:28:39 - 0|CORE|⚙️ RECONFIGURANDO: Se reconfigura el recolectorde EMA LIBRE.
|
||||
28/09/2026 21:28:47 - 0|CORE|⚙️ RECONFIGURANDO: Se reconfigura el recolectorde EMA LIBRE.
|
||||
|
||||
Loading…
Reference in New Issue
Block a user