From 36be1779c3945753e77d68d58b892145e9ae8a39 Mon Sep 17 00:00:00 2001 From: cheveguerra Date: Sun, 2 Aug 2026 04:00:25 -0600 Subject: [PATCH] feat(ui/polling): monitoreo de desarmadas en Grupo 0, tooltips explicativos y fecha compacta (v06.08.02) - Monitoreo de Desarmadas & Grupo 0:^ --- CONTEXT.md | 13 ++- index.py | 237 +++++++++++++++++++++++++++++++++++++++++++--- totalconnect.exe | Bin 18115755 -> 18118791 bytes update_github.bat | 32 +++---- 4 files changed, 247 insertions(+), 35 deletions(-) diff --git a/CONTEXT.md b/CONTEXT.md index 525aa89..f38e01d 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -1,7 +1,7 @@ # Estado del Proyecto - TotalConnect 2.0 **Última actualización:** 2026-08-02 -**Versión actual:** v06.08.01 +**Versión actual:** v06.08.02 --- @@ -11,15 +11,20 @@ El proyecto **TotalConnect 2.0** es un panel web centralizado de alto rendimient ### 🛠️ Características Principales e Infraestructura 1. **Motor Híbrido Dinámico de Monitoreo (`index.py`)**: - - **Grupo 0**: Prioridad absoluta para sucursales con alarmas activas (particiones, zonas y estado ALARMING) o eventos SOAP pendientes (`needs_full_update = True`). + - **Grupo 0**: Prioridad absoluta para sucursales con alarmas activas, eventos SOAP pendientes y sucursales desarmadas bajo el modo *"Desarmadas revisión constante"*. + - **Control de Protección por Límite**: Si el número de sucursales desarmadas supera la variable `max_disarmed_count` configurada en `credentials.json`, la función se deshabilita automáticamente para evitar cuellos de botella y *rate-limiting*. - **Esquema 2:1 (SOAP + REST)**: 2 consultas ligeras vía SOAP por 1 consulta completa por REST para evitar el *rate-limiting* de Honeywell (máximo 35 consultas REST por ciclo). - **Resiliencia de Sesión**: Purgado automático de tokens SOAP obsoletos y auto-recuperación ante errores de red (`ConnectionResetError`). 2. **Dashboard Web e Interfaz en Tiempo Real**: + - **Switch "Desarmadas revisión constante"**: Control checkbox interactivo en la cabecera del dashboard con sincronización en vivo. + - **Tooltips Flotantes Explicativos**: Ventanas emergentes al pasar el cursor (`mouseover`) sobre los controles de la cabecera (*Cuentas*, *Desarmadas revisión constante* y *Polling: Activo*), detallando sus funciones y parámetros técnicos. + - **Formato Compacto de Fecha/Hora**: Fecha de última actualización optimizada con días/meses abreviados a 3 letras, sin año y hora en formato de 24 horas (ej. `dom, 2 ago - 02:36:18`). - **Badge de Escaneo en Vivo**: Muestra en tiempo real la sucursal actual en proceso de carga/sondeo (`Cargando sucursal: 6001`). - **Visor de Bitácora Interactiva**: Modal visual en vivo para consultar las últimas 150 líneas del archivo de log (`activity.log`). - **Hard Reset**: Mecanismo de purga de caché y reinicio por clic prolongado (3s) en el botón de actualización. - **Kill-Switch / Polling Toggle**: Control de inicio/pausa de sondeo en segundo plano con *Circuit Breaker* ante errores `401`/`429`. 3. **Portabilidad y Parámetros**: + - Persistencia de `monitor_disarmed_enabled` y `max_disarmed_count` en `credentials.json`. - Eliminación de rutas absolutas en duro (`Z:\`, `E:\`), operando de manera 100% relativa a la ruta de ejecución actual. - Binario autónomo `totalconnect.exe` para Windows y soporte para servicio Systemd en Linux. @@ -27,11 +32,13 @@ El proyecto **TotalConnect 2.0** es un panel web centralizado de alto rendimient ## 🏷️ Historial de Tags / Versiones +* **`v06.08.02`**: Implementado switch "Desarmadas revisión constante" en UI con tooltip explicativo, persistencia en `credentials.json`, endpoint `/toggle-disarmed-monitor` y autodesactivación por límite `max_disarmed_count`. * **`v06.08.01`**: Prioridad en Grupo 0 para alarmas y eventos SOAP, indicador de escaneo en vivo en UI, eliminación de rutas absolutas en duro. --- ## 📋 Tareas Pendientes / Próximos Pasos -- [ ] Monitorear estabilidad del motor híbrido en producción tras despliegue de `v06.08.01`. +- [ ] Monitorear estabilidad del motor híbrido y la función de desarmadas en producción tras despliegue de `v06.08.02`. - [ ] Validar rendimiento en entornos con elevado número de cuentas Total Connect. + diff --git a/index.py b/index.py index 7780f8c..d542f0f 100644 --- a/index.py +++ b/index.py @@ -177,7 +177,7 @@ CACHE_FILE = os.path.join(BASE_DIR, 'locations_cache.json') LAST_STATE_FILE = os.path.join(BASE_DIR, 'last_state.json') LAST_EVENT_IDS_FILE = os.path.join(BASE_DIR, 'last_event_ids.json') LOG_FILE = os.path.join(BASE_DIR, 'activity.log') -APP_VERSION = "06.08.01" +APP_VERSION = "06.08.02" polling_trigger_event = threading.Event() soap_disabled_accounts = set() soap_session_ids = {} # dict: username -> GUID SessionId para SOAP GetLiveEvents @@ -228,7 +228,37 @@ def save_polling_state(enabled): except Exception as e: log_msg(f"⚠️ No se pudo actualizar polling_enabled en {CREDENTIALS_FILE}: {e}") +def load_disarmed_monitor_config(): + """Carga el estado del monitoreo de desarmadas y su límite máximo exclusivamente desde credentials.json.""" + enabled = False + max_count = 50 + if os.path.exists(CREDENTIALS_FILE): + try: + with open(CREDENTIALS_FILE, 'r', encoding='utf-8') as f: + data = json.load(f) + if isinstance(data, dict): + enabled = data.get("monitor_disarmed_enabled", False) + max_count = data.get("max_disarmed_count", 50) + except Exception: + pass + return enabled, max_count + +def save_disarmed_monitor_state(enabled): + """Guarda el estado del monitoreo de desarmadas exclusivamente en credentials.json de la ruta actual.""" + if os.path.exists(CREDENTIALS_FILE): + try: + with open(CREDENTIALS_FILE, 'r', encoding='utf-8') as f: + creds_data = json.load(f) + if isinstance(creds_data, dict): + creds_data["monitor_disarmed_enabled"] = enabled + with open(CREDENTIALS_FILE, 'w', encoding='utf-8') as f: + json.dump(creds_data, f, ensure_ascii=False, indent=2) + log_msg(f"💾 Estado de monitoreo de desarmadas ({enabled}) guardado en: {CREDENTIALS_FILE}") + except Exception as e: + log_msg(f"⚠️ No se pudo actualizar monitor_disarmed_enabled en {CREDENTIALS_FILE}: {e}") + polling_enabled = load_polling_state() +monitor_disarmed_enabled, max_disarmed_count = load_disarmed_monitor_config() def log_msg(msg): """Escribe un mensaje de log con timestamp tanto en la consola (con flush) como en activity.log.""" @@ -579,10 +609,11 @@ def _get_location_sort_priority(loc_item): def generate_html_report(data): """Genera un archivo HTML premium, limpio, claro y compacto con el estatus consolidado.""" now = datetime.datetime.now(ZoneInfo("America/Mexico_City")) - dias = ["lunes", "martes", "miércoles", "jueves", "viernes", "sábado", "domingo"] - meses = ["enero", "febrero", "marzo", "abril", "mayo", "junio", "julio", "agosto", "septiembre", "octubre", "noviembre", "diciembre"] + dias = ["lun", "mar", "mié", "jue", "vie", "sáb", "dom"] + meses = ["ene", "feb", "mar", "abr", "may", "jun", "jul", "ago", "sep", "oct", "nov", "dic"] - timestamp = f"{dias[now.weekday()]}, {now.day} de {meses[now.month - 1]} de {now.year} - {now.strftime('%I:%M:%S %p')}" + timestamp = f"{dias[now.weekday()]}, {now.day} {meses[now.month - 1]} - {now.strftime('%H:%M:%S')}" + disarmed_checked_attr = "checked" if monitor_disarmed_enabled else "" # Generar checkboxes dinámicos para las cuentas de credentials.json checkboxes_html = "" @@ -1233,6 +1264,64 @@ def generate_html_report(data): .btn-log-refresh:hover {{ background: #2563eb; }} + /* Tooltip Desarmadas Revisión Constante */ + .disarmed-tooltip-wrapper {{ + position: relative; + display: inline-flex; + align-items: center; + }} + + .disarmed-tooltip-trigger {{ + color: #475569; + font-weight: 600; + font-size: 0.75rem; + cursor: help; + text-decoration: underline dotted #94a3b8; + transition: color 0.15s; + }} + + .disarmed-tooltip-trigger:hover {{ + color: #1e293b; + text-decoration-color: #2563eb; + }} + + .disarmed-tooltip-box {{ + visibility: hidden; + opacity: 0; + position: absolute; + top: 130%; + left: 50%; + transform: translateX(-50%) translateY(-4px); + width: 320px; + background: #0f172a; + color: #f8fafc; + padding: 12px 14px; + border-radius: 10px; + box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.4), 0 8px 10px -6px rgba(0, 0, 0, 0.3); + border: 1px solid #334155; + font-size: 0.75rem; + line-height: 1.45; + z-index: 1000; + transition: all 0.2s cubic-bezier(0.16, 1, 0.3, 1); + pointer-events: none; + }} + + .disarmed-tooltip-box::after {{ + content: ''; + position: absolute; + bottom: 100%; + left: 50%; + transform: translateX(-50%); + border-width: 6px; + border-style: solid; + border-color: transparent transparent #0f172a transparent; + }} + + .disarmed-tooltip-wrapper:hover .disarmed-tooltip-box {{ + visibility: visible; + opacity: 1; + transform: translateX(-50%) translateY(0); + }} @@ -1249,14 +1338,51 @@ def generate_html_report(data):
-
- Cuentas: +
+ Cuentas: {checkboxes_html} +
+
+ 👤 Selección de Cuentas TotalConnect +
+
+ Permite elegir qué cuentas de usuario (configuradas en credentials.json) serán procesadas al presionar el botón "Actualizar" para consultar sus sucursales. +
+
+
+
+ +
+
+ 🔄 Monitoreo de Desarmadas en Revisión Constante +
+
+ Al activar esta casilla, las sucursales desarmadas se agregan al Grupo 0 de alta prioridad para detectar en tiempo real el instante exacto en que son armadas. +
+
+ ⚡ Protección por Límite: Si el número total de desarmadas supera el límite configurado (max_disarmed_count:{max_disarmed_count} en credentials.json), esta función se desactivará automáticamente para evitar sobrecargar la API de Honeywell. +
+
+
+
+ +
+
+ 🛑/🟢 Control de Polling Automático (Kill-Switch) +
+
+ Activa o pausa las consultas automáticas en segundo plano (cada 30s).

+ - Activo: El motor monitorea eventos SOAP en tiempo real y realiza sondeos por lotes.
+ - Pausado: Detiene las peticiones automáticas a Honeywell para congelar el rastreo o conservar ancho de banda. +
+
-