CreaRack-SL

Patrón Monitoring* — bases comunes + descriptor por dominio (serie F1, auditoría s187)

Patrón Monitoring* — bases comunes + descriptor por dominio

Cierre documental de la serie R5-F1 de la auditoría s187 (PRs #242, #243, #249, #253, #265, #266 · v1.40.3 → v1.43.3). Verificado contra código el 03-07-2026.

0 · En dos palabras (para todo el equipo)

CreaRack tiene tres monitores gemelos — UPS, Wireless y Digital Signage — que durante meses evolucionaron como tres copias del mismo frontend (~72% de líneas idénticas medidas en la auditoría). La serie F1 los puso a los tres sobre una única implementación por pieza (dashboard, report, grids de charts, detalle de grupo y fontanería del detalle), donde cada monitor aporta solo un “carnet de identidad” (su descriptor: endpoints, métricas, textos, colores). Resultado: ~−3,3k LOC y, sobre todo, que un fix o mejora futura se hace UNA vez y llega a los tres a la vez — como pasó con el dial de cadencia (task #175), que se cableó en las bases y funcionó en los tres monitores el mismo día.

1 · El patrón

Cada pieza de la familia es una base común (clase o factoría) en static/js/pages/monitoring/ que concentra la MECÁNICA, y un descriptor por dominio (~20-100 LOC) en pages/ups|wireless|signage/ que aporta lo específico:

static/js/pages/monitoring/          ← las bases (mecánica)
├── MonitoringDashboard.js   (632)   PR1/PR2 · overview GridStack
├── MonitoringReport.js      (439)   PR3     · report histórico + CSV + print
├── MonitoringChartGrid.js   (197)   PR4     · grid de paneles + pills + layouts LS
├── MonitoringChartPanel.js  (291)   PR4     · panel de chart individual
├── MonitoringGroupDetail.js (228)   PR5     · detalle de grupo
└── MonitoringDetailPlumbing.js (235) PR6    · deep discovery + WS realtime (mixins)

static/js/pages/ups/UpsDashboard.js  (~135)  ← descriptores (identidad)
                    UpsChartGrid.js  (~21)      «class X extends Base { constructor(o){ super(o, DESCRIPTOR) } }»
                    UpsGroupDetail.js(~57)      «export const X = createBase({ ...descriptor })»
                    ... (y equivalentes wireless/signage)

Dos variantes de composición conviven, elegidas por lo que ya existía en cada sitio:

  • Clase + descriptor (extends con super(opts, DESCRIPTOR)): Dashboard, Report, ChartGrid, ChartPanel.
  • Factoría / mixin + Object.assign (createMonitoringGroupDetail(d), createDeepDiscoveryMixin(d), createRealtimeWsMixin(d)): GroupDetail y la fontanería del Detail — los dominios eran object literals con otros mixins ya fusionados.

Los strings traducibles del descriptor van como thunks () => t('...') (se evalúan en render; el literal queda en el archivo del descriptor para el extractor del .po).

2 · Qué centraliza cada base

BaseMecánica que concentraLo que aporta el descriptor
MonitoringDashboardGridStack (drag/resize/save/reset layout), heatmap 24h, widgets, timers del overviewcharts del registry, stat tiles, layoutKeys EXACTAS, textos, render custom
MonitoringReportRango From/To, stats, charts, deep-tables, Print, Export CSV con sanitización (heredada por los 3 — antes solo UPS la tenía)métricas, columnas, títulos
MonitoringChartGridToolbar con pills 1h/4h/12h/24h, add/remove paneles, persistencia del layout y del rango en localStorage, echarts.connect, realtime WS, destroyregistry, defaultLayout, maxPanels, lsPrefix, pillClass, PanelClass (¡inyectada!), extras de toolbar
MonitoringChartPanelPanel individual: fetch, estilos, dropdown de métrica, removedescriptor de series/ejes (UPS y Signage; WirelessChartPanel se INYECTA intacto — multi-serie, no se fusionó)
MonitoringGroupDetailLayout del tab de grupo, summary con stat cards, tabla de miembros ordenable, binding Port Assign, timers (charts 120s, summary 60s), cleanupstats, columnas/filas de miembros, fuente de devices, endpoint group-summary, extras (SSIDs de Wireless, badge-click) vía hooks
MonitoringDetailPlumbingDeep discovery (despacho al Agente + fallback REST a 127.0.0.1:5050, poll 30×3s, disparo silencioso al abrir, timer 5 min, refresh manual del botón) y WS realtime (/ws/monitoring/, batching de metric_update a 100 ms, indicador .ws-realtime-active, backoff 1→32s, repace() del dial #175)tabPrefix, renderDeep, botón/toast del refresh, onExtraMessage (Wireless: job_progress)

3 · Invariantes SAGRADOS (romperlos = romper a usuarios)

  1. lsPrefix y las claves de localStorage son EXACTAS pre-refactor por dominio (ups_chart_layout_v1_…): cambiarlas borra los layouts guardados de todo el mundo. El rango horario va en clave separada (hours_) para no invalidar el JSON del layout.
  2. Los IDs del DOM se conservan (ups-chart-grid-<id>, wireless-group-summary-<id>…): hay código que los busca por id.
  3. El contrato público de cada módulo no cambia: show*Detail/showGroupDetail/cleanupTab/repace y el Map _chartGrids (los page controllers lo leen para resizeAll() al cambiar de pestaña). Único método con caller externo por nombre: WirelessDetail.refreshApData (bridge Wireless._detail_refresh).
  4. layoutKeys del Dashboard exactas — mismos motivos que el punto 1.
  5. Timers: charts de detalle acompasados a la cadencia del target (max(polling_interval, 15)s, 120s sin dato — task #175); grupos a 120s fijo; deep-data 5 min; summary de grupo 60s.

4 · Cómo añadir un dominio nuevo (receta)

  1. Crea su ChartRegistry (métricas → series/ejes/formatters) y, si su panel es estándar, un descriptor de MonitoringChartPanel; si es especial, inyecta tu clase de panel (patrón WirelessChartPanel).
  2. class NuevoChartGrid extends MonitoringChartGrid con su descriptor (lsPrefix NUEVO, no recicles el de otro dominio).
  3. Dashboard, Report y GroupDetail: un descriptor por pieza calcando cualquiera de los existentes (UPS es el más simple).
  4. Detail: monta tu render por dominio y fúndele createDeepDiscoveryMixin (+ createRealtimeWsMixin si va con WS propio).
  5. Todos los strings visibles con t()/tc() (los del descriptor como thunks) y escape SIEMPRE por imports estáticos de utils/html.js (regla R5a — jamás window.escapeHtml || a nivel top de módulo).

5 · Qué NO se fusiona (decidido en la auditoría, no lo “arregles”)

  • *DetailCharts / render por dominio (tablas de radios/SSIDs vs batería vs playlists): ~13% común — fusionarlos costaría más de lo que ahorra.
  • WirelessChartPanel: multi-serie, se inyecta tal cual en el grid base.
  • El WS de Signage (SignageDetailCharts.js): vive con su grid en funciones de módulo; migrarlo implicaría reescribir ese archivo entero.
  • El refresh manual de Wireless (refreshApData): espera la finalización por push WS (job_progress) con polling de seguridad — se apoya en _dispatchDeepDiscover común pero no se genericiza.
  • ChartRegistries: ya SON descriptores (datos, no mecánica).
  • El CMS de Signage: otro dominio funcional, fuera de la familia.

6 · Historia de la serie (PRs)

PRVersiónPiezaLOC
#242 (PR1)v1.40.3MonitoringDashboard + UPS 789→135base nueva
#243 (PR2)v1.40.4Signage 772→140 · Wireless 794→185−1.529/+336
#249 (PR3)v1.40.5MonitoringReport ×3 (CSV sanitizado para los 3)−754
#253 (PR4)v1.40.6MonitoringChartGrid/Panel ×3−515
#265 (PR5)v1.43.2MonitoringGroupDetail ×3−269
#266 (PR6)v1.43.3MonitoringDetailPlumbing (deep discovery ×3 + WS ×2)−171

Todos con click-test de Edu en PROD. Serie CERRADA el 03-07-2026.

Véase también

  • [[crearack-tech—frontend—centralized-services]]
  • [[crearack-tech—frontend—performance-guidelines]]
  • [[feature—monitoring—dial-cadencia-sondeo]]
  • [[crearack-tech—architecture—sentinel-mode]]