CreaRack-SL

Los clientes WiFi que ves son los de ahora (task #225, v1.66.9)

Síntesis

La página Wireless presentaba como clientes actuales los datos de un análisis profundo (deep_snmp_data) realizado hace semanas. En PROD, de 3.219 clientes reportados, 3.203 eran de APs caídos desde el 30 de junio. El “Busiest AP” señalaba hace 6 semanas al mismo punto.

Ahora: solo cuentan los clientes de APs que el MonitoringTarget ve vivos (last_status == "up") en este momento. La cifra cae, pero la UI expresa “N of M APs reporting live” para dejar claro que no es avería.

Evolución v1.85.2 (ver sección más abajo): desde el 25-08-2026 el juez de “vivo” ya no depende solo del ping — también mira SNMP en vivo. Esta sección de Síntesis describe el mecanismo ORIGINAL (v1.66.9); sigue siendo el criterio de respaldo.


Cambio funcional

Nuevas funciones en monitoring/api/wireless.py

_has_current_clients(profile) → bool

  • Propósito: ¿Los clientes de este AP reflejan el AHORA?
  • Lógica: profile.linked_monitoring_target and mt.last_status == "up"
  • Fuente: MonitoringTarget.last_status (señal viva del target)
  • Nota: Más estricto que _ap_status() a propósito — ésta llama “online” a un AP sin target solo por tener deep data, lo que era la puerta por la que entraba la foto vieja.

_current_client_count(profile) → int

  • Propósito: Clientes del AP, o 0 si el dato no es del ahora.
  • Lógica: Cuenta via _count_clients_from_deep() solo si _has_current_clients() es verdad.
  • Fuente: profile.deep_snmp_data (JSON con estructura {wireless: [{name: "clientsPerRadio", value: "N"}]}).

Lectores afectados (5)

Todos reemplazan la lectura directa de deep_snmp_data por _current_client_count(p) / _has_current_clients(p):

  1. get_wireless_summary(): endpoint /api/monitoring/wireless/summary

    • Nuevo: aps_reporting_clients, aps_without_current_data
    • avg_clients_per_ap se calcula sobre los que reportan, no sobre la flota entera
    • Busiest AP solo entre los vivos
  2. get_wireless_status_list(): /api/monitoring/wireless/status-list

    • Zeroing de client_count para APs sin datos vivos
    • Crítico porque el dashboard suma estos valores por su cuenta (WirelessDashboard.computeMetrics)
  3. get_wireless_report(): informe individual por AP

    • client_count: 0 si el AP está caído
  4. get_wireless_fleet_report(): /api/monitoring/wireless/fleet-report

    • Nuevo: aps_reporting_clients, aps_without_current_data
    • Cada AP en fleet trae clients: 0 si es stale
  5. get_wireless_group_summary() — cerrado el 23-08-2026 en task #255 (pieza 3, v1.82.5): la tarjeta de grupo se quedó fuera del arreglo original y seguía sumando la última foto como si fuera de ahora. Ahora usa el mismo juez que el resumen general; la agregación de SSIDs (_aggregate_ssids) también se salta a los APs sin dato vivo.

Frontend

  • Template: templates/monitoring/wireless.html

    • Nuevo div: metric-sublabel bajo “Total Clients”
  • Stylesheet: static/css/pages/wireless.css

    • Clase .metric-sublabel: 10px, color muted, margin-top 2px, line-height 1.3
  • JS: static/js/pages/wireless/WirelessDashboard.js

    • updateSummaryCards() llena #wl-metric-clients-source con “N of M APs reporting live” (interpolated)
    • Solo se pinta desde la API; updateMetrics() (local) no lo toca

Contrato de tests

5 tests en tests/api/test_monitoring.py::TestWirelessClientFreshness:

  1. test_summary_ignores_clients_of_dead_aps: AP up (5 clientes) + AP down (200 en foto) → total = 5
  2. test_busiest_ap_is_never_a_stale_photo: Busiest AP es siempre del que está vivo
  3. test_status_list_zeroes_stale_client_counts: status-list devuelve 0 para dead APs
  4. test_ap_without_monitoring_target_does_not_count: Sin target no hay señal de vida (huérfano → 0)
  5. test_fleet_report_excludes_stale_and_reports_how_many: Fleet report cuenta y expone stales

Deuda — CERRADA

La deuda original decía que deep_snmp_data no lleva fecha en ninguna parte (ni en el JSON del Agente, ni como campo del modelo, ni en last_updated) y que por eso el fix de esta página no podía decir cuándo era la foto, solo dejar de sumarla.

Esa deuda quedó cerrada por v1.66.16 (migración network/0060, campo DeviceProfile.deep_snmp_updated_at) — ver [[feature—wireless—deep-discovery-timestamp]]. El 23-08-2026 (task #255) se corrigió además el comentario del código que seguía afirmando la versión vieja (“no lleva fecha en ninguna parte”), que había quedado rancio desde v1.66.16 sin que nadie lo tocara.


Impacto en observabilidad

  • Métrica que cambia: total_clients (baja dramáticamente de ~3200 a ~16 en este caso)
  • Métrica nueva: aps_reporting_clients / aps_without_current_data (desambigua si es avería o verdad)
  • User feedback: Sublabel “2 of 124 APs reporting live” explica la caída

Evolución — v1.85.2 (ciclos 5 y 6 de la auditoría #261): el juez ahora también mira SNMP vivo

El juez original de esta página (_has_current_clients / _current_client_count) solo confiaba en MonitoringTarget.last_status == "up", es decir, en el ping. En el CCIB, donde la red filtra ICMP hacia los APs, eso hacía que 47 puntos de acceso con SNMP perfectamente vivo salieran en rojo y sin clientes (W-H1, W-H2).

WirelessLive (monitoring/api/wireless.py)

Nueva clase que hace una sola consulta instantánea a VictoriaMetrics por petición (last_over_time(...) sobre snmp_fast_station_count / snmp_extras_connected_clients / snmp_fast_connected_clients, ventana de 2× la cadencia del target + margen, mínimo 3 min) para saber qué targets tienen una muestra SNMP reciente y cuántos clientes reportan ahora mismo.

  • WirelessLive.for_profiles(org, profiles): construye la instancia para un lote de perfiles de una tirada, vía MetricsReader (monitoring/services/metrics_reader_base.py).
  • _ap_status(profile, live): online si el ping ve el AP arriba O si live.is_fresh(profile) (SNMP fresco) — antes solo dependía del ping.
  • _has_current_clients(profile, live) / _current_client_count(profile, live): si hay SNMP fresco, el recuento sale directo de live.clients (dato en vivo); si no, cae al criterio de más arriba en esta página (ping arriba → foto del deep discovery); si tampoco, 0. La foto queda como respaldo, no como fuente principal.
  • Sin VictoriaMetrics o sin targets, WirelessLive queda vacía y el comportamiento es idéntico al descrito en el resto de esta página (juez = ping).

A42 · /wireless/deep-data con el mismo juez

Este endpoint tenía su PROPIO cálculo de client_count (sumaba clientsPerRadio de la foto sin mirar frescura) y el flujo “Deep Data” del frontend lo escribía en ap.client_count: una regresión de la #225 colándose por otra puerta. Ahora llama a _current_client_count(p, live), igual que el resumen.

Otros arreglos del mismo ciclo (misma familia de honestidad de datos)

  • W-H4: firmware_version se rellena desde la foto del deep discovery (firmwareVersion / cdpFirmware) en el ingest — estaba vacío en 249/249 APs.
  • W-H6: la ficha del AP dice “Deep data: undated snapshot” cuando la foto es anterior al sello deep_snmp_updated_at (ver [[feature—wireless—deep-discovery-timestamp]]), en vez de callar justo en las fotos más viejas.
  • A43: el sweep de flota usa el perfil de vendor generic cuando no reconoce el equipo (antes lo saltaba en silencio).

Commit 1195434d (PR #439, v1.85.2). Tests: tests/monitoring/test_ciclos5_6_wireless_seguridad.py (9). Sin verificar en la red del CCIB hasta septiembre.


Evolución — v1.85.9 (deuda #274, B1-#12): el conteo por _count_clients_from_deep ya no sale multiplicado

_count_clients_from_deep() — la función que _current_client_count() llama cuando no hay SNMP fresco (ver la sección “Evolución v1.85.2” de arriba) — caía a contar la lista clients de la foto de deep discovery entrada por entrada cuando el AP no traía clientsPerRadio. Esa lista es plana: una fila por OID × cliente, no una fila por cliente. Con 8 OIDs de cliente declarados, el conteo salía ×8. Ahora cuenta index únicos dentro de clients, que sí es un cliente real por fila. Detalle completo de la tanda: [[feature—monitoring—deuda-274-tanda-a]].


Evolución — v1.164.5 (28-09-2026, visto en PROD durante FABCON26): la agregación de SSIDs también exige que la FOTO sea reciente

El arreglo de task #255 (sección “Lectores afectados” de arriba) hizo que get_wireless_group_summary() dejara fuera de la agregación de SSIDs a los APs sin dato vivo — pero un AP “vivo” (con SNMP o ping recientes) puede llevar semanas sin repetir su Deep Discovery, porque esa foto no se renueva sola. En el grupo “CCIB&Forum” (124 APs) esto dejaba colarse en la tabla de SSIDs redes de eventos ya terminados (MorganMoney, PXP26 WORLD TOUR) por delante de la red del evento en curso: el filtro miraba si el AP estaba vivo, no si la FOTO lo estaba, y 99 de las 124 fotos eran del 16-09.

_ssid_photo_is_recent(profile, now) añade ese segundo filtro: a la agregación solo entra un AP cuya deep_snmp_updated_at tenga menos de SSID_PHOTO_MAX_AGE (60 min, UNVERIFIED — elegido por criterio, no medido). La respuesta de get_wireless_group_summary suma ssids_from_aps (APs cuya foto entró), ssids_old_aps (APs vivos dejados fuera por foto vieja), ssids_as_of (la foto más vieja de las que sí cuentan) y ssids_max_age_minutes; WirelessGroupDetail.js los usa para decir de cuántos APs sale la tabla o, si ninguna foto es reciente, pedir que se relance Deep Discovery en vez de no mostrar nada.

El total de clientes del grupo (total_clients) no cambia — ya salía del dato en vivo desde v1.85.2; este filtro solo afecta a qué fotos entran en la tabla de SSIDs por nombre. Tests: tests/monitoring/test_wireless_grupo_ssids_foto.py (4). Commit 602638f7 (PR #623).


Véase también

  • [[entity—monitoring—model—monitoring-target]]
  • [[entity—network—model—device-profile]]
  • [[concept—monitoring—wireless-observability]]
  • [[feature—wireless—deep-discovery-timestamp]]
  • [[feature—ups—graficas-vivas-walk-first]]
  • [[crearack-tech—architecture—wireless-monitor]]
  • [[feature—monitoring—deuda-274-tanda-a]]