Todos los cambios notables de NetPulse se documentan en este fichero.
El formato se basa en Keep a Changelog, y este proyecto se adhiere a Versionado Semántico.
- Alertas de seguridad WiFi desde los scans que ya existían (#1358, #1359, #1393, #1394): dos detectores nuevos sin nada que instalar ni configurar. El de SSID impostor avisa urgente cuando un BSSID que no es de la flota anuncia un SSID que sí lo es (firma clásica de evil twin); confirma con dos scans reales separados 20 min para no levantar falsos positivos, y se auto-resuelve cuando desaparece. El de SSID vecino nuevo avisa (info) cuando un nombre de red nunca visto aguanta 4 h cerca: un router nuevo enchufado, un IoT en modo pairing, un hotspot fijo; los vecinos de siempre se siembran como conocidos la primera vez para no inundar, y cada SSID avisa una sola vez.
- Alerta cuando un cliente se ve por primera vez (#1323, #1397): el hueco que dejaba "dispositivo desconocido" (que solo cubre clientes sin nombre): un equipo con reserva DHCP o nombre estático entraba en la lista sin ruido. Ahora cualquier MAC que entra al registro por primera vez con nombre genera su alerta (clients, urgente, push), una sola vez por MAC; la siembra del primer arranque no avisa de los ya presentes.
- Los eventos resueltos se conservan en el registro (#1349, #1396): al resolverse una alerta (desactualizado → actualizado, SNMP fallando → recuperado), el evento original ya no desaparece del log: queda marcado con su fecha de cierre y la pestaña Resueltas lo lista. El evento de cierre referencia los que resuelve y, al expandirlo, muestra el desplegable con ellos. Una reincidencia reabre el mismo episodio sin heredar el leído.
- Arrastrar un switch o AP mueve también a sus cableados (#1377, #1390): en el mapa de topología, los clientes cableados siguen a su padre al arrastrarlo, igual que los CTs siguen a su hipervisor. Las posiciones guardadas llevan ahora marca de tiempo: un hijo reubicado a mano después que el padre conserva su sitio hasta la próxima vez que muevas el padre.
- lldpd configurado en puertos físicos al instalar el agente (#1369, #1392): en OpenWrt (qualcommax/nss-dp verificado, DSA probable), un lldpd escuchando en el bridge no ve vecinos nunca y nadie avisa. El instalador lo deja escuchando en
lan*,wan(idempotente, nunca instala paquetes por sí solo) y, si detecta el caso, el agente lo reporta y la ficha del router muestra el aviso con el comando exacto para arreglarlo. - Tabla de enlaces con columna de ping y leyenda a lo ancho (#1387): la leyenda del mapa ocupa una fila completa con muestras de enlaces en tres columnas, la tabla de enlaces lista también los uplinks de switches gestionados con su latencia sondeada, y los cables de hipervisores muestran la velocidad medida por su padre.
- Retirar agentes de la flota desde la UI (#1385): un agente sin pulso (equipo apagado o retirado) ya no ofrece actualización (imposible sin pulso) y su fila se marca en rojo con la acción Retirar, que revoca su token y lo saca de la lista con confirmación en app.
- Topología: evidencia LLDP endurecida (#1371, #1376, #1379, #1381, #1372, #1398): parser compatible con el JSON keyed-array de lldpd 1.0.x (sin él, cero vecinos en OpenWrt 25.x); los uplinks se anclan a la verdad del puerto raíz y los nodos fantasma del switch gestionado se absorben en vez de duplicarse; los CTs de hipervisor ya no salen duplicados; las velocidades medidas cruzan a los uplinks con evidencia LLDP; y cuando varios candidatos de flota llegan por el mismo puerto raíz (switch que inunda la multicast reservada, no 802.1AB), el candidato se corrobora por FDB en vez de ganar por orden de scan.
- Tooltip WAN solo en el gateway (#1386): las bocas
wande routers no-gateway mostraban la IP pública y DNS del gateway sobre un enlace local; ahora solo la unidad principal muestra esa información. - Tabla 802.11r de Itinerancia sin columna MAC (#1391, #1395): un tag JSON duplicado (resto de un renombrado) hacía que el BSSID nunca se serializara y la columna salía siempre "-".
- Scans de vecinos por interfaz con su propio timeout (#1388) y servir la arquitectura de agente correcta o fallar en voz alta (#1367).
- Agente embebido 3.0.17 (#1375, #1389): parser de ping sin redondeo a entero (pings LAN sub-milisegundo ya no salen como 0 ms), sonda LLDP con detección de binding bridge-only y scan de vecinos por interfaz. Los agentes se actualizan solos desde el servidor al detectar la diferencia de versión.
- Documentación: repaso de cobertura de la web y README más ligero (#1365).
- Aviso opt-in cuando un cliente se desconecta (#1354, #1355): check "Avisar si se desconecta" en el editor del dispositivo. Al confirmarse la caída (tres ciclos seguidos offline, sin avisos por flapping corto ni por el arranque del server) entra una alerta urgente con push, y al volver la entrada se retira sola del feed, como la de agente caído.
- Desconexión de túneles WireGuard en el registro (#1348, #1361): el panel ya registraba la conexión de un peer; ahora también su desconexión, con la duración de la sesión. Solo en sondeos correctos: un fallo de lectura nunca marca desconexiones espurias.
- Badge de categoría por fila en el registro de actividad (#1357, #1362): las filas se tiñen por severidad y la categoría solo existía en los chips de filtro; el azul de info coincide con el de Internet y confundía la categoría de un evento. Cada fila muestra ahora su categoría con icono y etiqueta.
- Evidencia real en los enlaces de topología y switches hoja inferidos (#1325, #1356): cada enlace de unidad de flota declara su procedencia (LLDP, FDB, device o manual) en la label; antes el front la deducía con un heurístico del router y un enlace resuelto por FDB podía etiquetarse "LLDP". Los switches no gestionados colgados de un puerto de un switch gestionado con dos o más clientes cableados aparecen como nodos inferidos colgando del switch. Las hover cards de puertos de switch colapsan la línea duplicada cuando ambas vías nombran al mismo vecino.
- Política de seguridad (
SECURITY.md, canal privado de reporte) y Dependabot mensual para Go y npm (#1350, #1351).
- Tabla de clientes más aireada (#1360, #1363): la columna de dispositivo cede peso (3.4 -> 2.8fr) a las de fecha, que suben a 1.0fr con nowrap y cabeceras españolas más cortas ("Primera vez" / "Última vez"); dejan de partir el texto en dos líneas. Recordatorio: el selector de columnas permite ocultar cualquiera y el espacio se reparte entre las visibles.
- Dependencias:
gonzalop/mqal tag oficial v0.9.12 (#1352) y 31 actualizaciones npm minor+patch agrupadas (#1353), ambas vía Dependabot.
- Iconos dedicados para aire acondicionado, caldera y placas SBC (#1340, #1345, discusión #1337): tres tipos nuevos (
clima,caldera,placa) con sus glifos Lucide (AirVent, Heater, CircuitBoard), estrictamente aditivo: los tokens que ya pertenecían a otra regla se quedan donde estaban, así que ningún dispositivo existente cambia de tipo ni de icono. Los tres glifos entran también en el picker manual (mapa del front y allowlist del server en lockstep). - URL local para instalaciones desatendidas de agentes (#1335, #1336): nueva variable
NETPULSE_AGENT_URLque sustituye al dominio público en la URL con la que cada agente conecta (línea de instalación, reinstall, re-emit y auto-reinstall) cuando el server vive detrás de un reverse proxy. Cadena de resolución: AGENT_URL > PUBLIC_URL > host de la petición. - Integración del trabajo de borky (#1332, #1333): el agente lee las reservas DHCP de
/etc/config/dhcpy los clientes con fixed-address ganan su nombre end-to-end; los host records de mDNS completan la mitad discovery de #338; y el matching de fabricantes se afina (OUIs de videovigilancia acamara, purificadores y humidificadores Xiaomi por nombre, tokens cortos con matching whole-word: "gree" ya no traga "greenwave" ni "wiz" casa WIZnet). Agente embebido 3.0.15.
- Bucle de topología de flota de la 2.40.0 (#1279, #1342): la identidad sintetizada desde las descripciones de puerto locales de los switches Omada casaba routers de flota por nombre y LLDP-first fijaba padres falsos: unidades conectadas en bucle y gateway sin hijos. Esa identidad ahora solo etiqueta; cualquier ciclo residual degrada a la evidencia device/FDB, y las labels de los enlaces semánticos se colocan en el punto medio del enlace (antes se amontonaban en la esquina superior izquierda).
- El ciclo de vida del cliente ya no pierde datos (#1315, #1343): una MAC nunca se trata como nombre (tampoco la de una MAC enlazada: el merge promovía la MAC de la alias al canónico, y las filas contaminadas de
device_seense curan al leer, sin migración de datos), y el último tipo inferido sobrevive a la expiración del lease en dispositivos Auto (los overrides manuales siguen mandando, y los fantasmas conservan su icono). - El rail del tablet tiene botón (#1318, #1344): el pie del rail (breakpoint md) estrena el toggle expandir/plegar y el estado colapsado funciona desde md, así que el rail en vertical retrato se comporta como el del portátil. La barra móvil no cambia.
- Botón Editar del detalle arriba (#1341, #1346): en escritorio ancla arriba a la derecha del panel, alineado con la primera fila de detalle (MAC / concesión / primera vez vista); en móvil sigue en su fila al pie.
- La topología obedece a lo que los equipos se anuncian por cable (#1279, #1324): LLDP manda sobre la inferencia FDB al decidir dónde cuelga cada unidad de la flota, con regla de puerto raíz (el vecino que vive en el puerto donde la unidad aprende la MAC bridge del gateway). Los switches TP-Link Omada, que publican una tabla LLDP remota capada, aportan ahora la identidad del vecino desde su tabla de puertos local (las descripciones que el admin les puso). Los switches de la flota estrenan etiqueta en el mapa: se calculaba y no se pintaba.
- Tus etiquetas manuales mueven routers, APs y switches de verdad (#1321, #1326): un etiquetado cuya MAC es la bridge de una unidad de flota fija su uplink por encima de toda inferencia (manual > LLDP > device > FDB > gateway), en los tres formatos de destino: MAC del padre, id de router o
router:puerto(este último fija también la boca). Los enlaces de uplink llevan la velocidad real negociada (EthPort del padre) y el mapa y la tabla muestran "padre · puerto" en vez de un genérico "1G". - Iconos dedicados para cámaras CCTV, aspiradores y videoporteros (#1327, #1328, #1329, #1330): las cámaras pintan el glifo CCTV (y su regla va antes que la de TV: "cctv-salon" ya no cae en TV), los robots aspiradores salen del cubo IoT genérico con tipo propio (
aspirador), y los timbres con cámara tienen el suyo (videoportero) en vez de mezclarse con las cámaras. Los tres glifos también son seleccionables en el picker manual de iconos al editar un cliente, y el botón Editar del detalle flotan 20 px separado de la esquina del panel.
- Campaña: vamos a por las 100 estrellas: el banner de anuncios estrena botón prominente (debajo del texto, fondo acento, fuente mayor) y barra de progreso de campaña (85/100 al publicar). El aviso pide una estrella en GitHub y difundir en comunidades (r/selfhosted, r/foss, r/openwrt, foros de OpenWrt, XDA Developers). Si lo descartaste antes, vuelve a aparecer con el nuevo enfoque.
- Re-emitir connection strings a la flota (#1294): tras activar HTTPS o cambiar su puerto/CA, un botón en Ajustes > HTTPS reescribe
/etc/netpulse-agent.env(URL del server, CA raíz, pin SPKI) en todos los agentes nativos alcanzables por SSH y los reinicia, rotando sus tokens de forma atómica. El despliegue de HTTPS pasa a ser: activar → confirmar → re-emitir. - Barra de navegación móvil deslizable (#1312/#1314): todas las secciones visibles en una fila horizontal que auto-centra la activa; la hoja "Más" desaparece.
- "Canales actuales" decía sin datos con el gateway (#1308): el survey excluía al gateway por una guarda heredada de la antigua pestaña de itinerancia; desde que la lente vive en Canales es un AP más y su agente aporta survey como los demás.
- MACs de radios de la flota en Clientes (#1307): el purge de #1290 no cubría los BSSID por radio; ahora se purgan del registro y se filtran de las listas finales (una fuente viva los re-emitía cada ciclo).
- Detalle de cliente reorganizado (#1305): las MACs enlazadas quedan bajo la MAC canónica, el hostname debajo, y First/Last Seen juntos verticalmente.
- Redes detectadas (#1304): SSID como primera columna, desempate por señal descendente en los ordenados por SSID/canal y filtro persistente de señal mínima (≥ -70/-80/-90 dBm).
- Overscroll del menú en tablets (#1302): el scroll táctil sobre el sidebar ya no salta al contenido principal.
- HTTPS en puertos ≤ 1024 sin root (#1306/#1311): la unidad systemd distribuida concede
CAP_NET_BIND_SERVICEal servicio. - Filtro de señal de Clientes mostraba la clave i18n cruda: las claves vivían mal ubicadas bajo
devices.detail.
- La lente "Canales actuales" sigue al selector superior (#1300): su título y selector internos desaparecen; la banda se elige desde la barra de arriba (2.4/5 GHz) y el survey la sigue.
- Tabla de clientes (#1300, #1298): ordenar por banda o SSID desempata por señal descendente (los vecinos fuertes agrupados arriba), nuevo filtro de señal mínima (≥ -70/-80/-90 dBm) para ocultar el ruido de vecinos débiles, y las MACs enlazadas (#1151) son buscables y visibles en el detalle del cliente.
- Restaurada la banda sticky (#1300): el merge de #1286 solo llevó la mitad de i18n del cambio - la banda volvía a saltar al cambiar de unidad.
- CI (#1271): el snapshot de main se compila en cada push (sin filtro de paths): el updater rolling ya no aplica snapshots viejos en pushes solo-Go.
- Sello de build con versión (#1275): el menú muestra
versión · build <hash>, con badges de navegación en Flota (agentes pendientes de actualizar + unidades con problema) y Clientes (conectados sin identificar), y el sidebar hace scroll en pantallas cortas (portrait). - Banda sticky y lentes con nombre (#1283, parcial): la banda elegida se mantiene al cambiar de unidad, y el selector dice "Análisis / Canales actuales" en vez del críptico "24 h / Ahora".
- Latencia WAN real en OpenWrt (#1287, agente 3.0.14): el parser del ping solo entendía el formato GNU de 4 números; busybox imprime 3 - en OpenWrt la latencia era siempre "—".
- SNMP: puertos 2.5 GE mostrados como "2 Gbps" (#1284): la división entera truncaba el decimal.
- Selector de Canales sin unidades sin WiFi (#1285): los switches OpenWrt cable-only se ofrecían como fuente de análisis.
- HTTPS: el puerto pedido se aplica antes del enable (#1288): activar HTTPS con puerto nuevo intentaba siempre el bind en :443 y fallaba en no-root.
- Clientes: los borrados se quedan borrados (#1290/#1292): el borrado escribe una lápida que filtra el registro Y la lista final (una estación stale del AP re-emitía la MAC en cada ciclo); las MACs base y bridge de la flota se purgan del registro; las IPs no-parseables se descartan.
- Borrar router limpia el stub (#1291): revoca el token del agente, olvida el registro y purga su estado y MAC aprendida. Re-añadir el router exige re-emparejar el agente.
- La lente "Ahora" funciona en flotas solo-agente (#1270, agente 3.0.13): el survey de ocupación iba por SSH y las unidades sin SSH (solo agente) no lo ejecutaban jamás - la lente nacía vacía. Ahora el propio agente ejecuta
iw dev <iface> survey dumpen su sondeo completo (throttle de 5 minutos, forzado por el botón Actualizar) y empuja el texto crudo; el servidor lo cachea 15 minutos y la lente lo fusiona reutilizando el parser existente. Las unidades con SSH siguen igual.
- Análisis de canales rehecho (#1214): espectro estilo LuCI con techo plano y falda curvada, eje dBm adaptativo con rejillas reales (múltiplos de 10 dentro del rango de la vista), colores estables por SSID para todas las redes (propias incluidas), sombreado de la red bajo el cursor con hit-test que respeta campanas anidadas, etiquetas en el pico y en la banda del suelo para todas, franja de ocupación con solapamiento real de anchos y tonos relativos a la vista, y orden persistente de columnas en Redes detectadas (#1267).
- Botón "Escanear ahora" (#1227) y fallback de 30 días (#1243): pensado para flotas con
NETPULSE_SCAN_INTERVAL=0; si la ventana de 24 h está vacía se usan los últimos scans conocidos y se indica la fecha. - Sugerencias de canal conservadoras (#1225): solo se propone cambiar con ganancia clara (margen ≥30 %, con excepción DFS); en caso contrario, "mantén tu canal".
- Lente "Ahora" en Canales (#1229): el survey en vivo se pliega dentro de Canales y desaparece de Itinerancia.
- Sello de build en el menú lateral (#1250): el hash del commit en ejecución, para diagnosticar cachés y service workers de un vistazo.
- SNMP (#1266): nombre completo del dispositivo y temperatura de chasis en MikroTik SwOS.
- Telemetría de radio (#1213/#1216, agente 3.0.11): ruido y BSSID por radio en el detalle de unidad.
- Diálogo de actualización centrado en las novedades (#1223).
- El service worker servía el app shell viejo indefinidamente (#1249): las navegaciones van red-primero (precache solo como fallback offline); un reload normal basta para actualizar.
- El espectro dibujaba todas las redes pegadas al techo (#1251): el clamp del pico usaba
Math.maxen vez deMath.min; con un entorno a -85..-97 todo se aplastaba arriba y solo sobrevivía una etiqueta. - Franja de ocupación (#1255/#1265): la potencia se reparte por los slots que cubre el ancho de cada red (antes solo el canal sintonizado) y la escala es relativa a la vista; una banda sin vecinas se pinta en verde calmado con su nota.
- Clientes (#1210/#1212): export CSV, borrado solo-offline con confirmación y chip OFFLINE; los BSSID de la propia flota dejan de aparecer como clientes. Reportado por @crowedavid.
- Latencia WAN honesta (#1211): objetivo configurable en el reinstalador (
NETPULSE_WAN_TARGET/GW_TARGET) y "—" cuando no hay dato. Reportado por @crowedavid. - Updater y supervisor: normalización del SHA corto antes de comparar (falso
updateAvailableen rolling, #1261); guard del agente embebido contra versiones inyectadas (#1262); el supervisor no rearma SSH de slugs ausentes de la tabla (#1260). - HTTPS (#1226): una instalación anterior a #978 conserva el puerto donde se configuró.
- Varios: survey de Roaming solo con APs y título "Canales actuales" (#1217), fallo de copia de tokens visible (#1220), dependencias Go al día (
gonzalop/mqcon el fix de backoff, #1235).
- Flujos guiados de integraciones en la Ayuda (#1184): MQTT y Home Assistant, ntfy, Telegram y el servidor MCP se explican ahora con un recorrido paso a paso dentro de la app, en castellano y en inglés (#1196, #1198).
- La tarjeta "Actualizar NetPulse" era inutilizable en pantallas cortas (#1197): el diálogo apilaba changelog, requisitos, aviso de tiempo sin servicio y botones de acción en una altura fija sin scroll; ahora se ajusta al viewport en todos los tamaños y el cuerpo es el contenedor de scroll (#1199).
- Nombres de interfaz en OpenWrt (#1200): los netdev 25.12+ del BPI-R4 (sfp-lan, sfp-wan) se mostraban como "sfp-LAN " y "SFP -wan", y la boca "lan" a secas del UniFi 6 Lite dejaba el panel de puertos vacío (0 de 0); un etiquetado único conserva el formato clásico (LAN 1, WAN, ETH 0, SFP 0) y muestra el nombre real del resto (agente 3.0.10) (#1201). Reportado por @pl-komuch, con capturas que dejaron el diagnóstico trivia.
- Retención indefinida de clientes (#1145): los equipos descubiertos ya no desaparecen de la lista al caducar su lease o limpiarse la tabla ARP; permanecen como offline con su último nombre, IP y última vez vistos. Reportado por @crowedavid (#1172).
- Un cliente, varias MAC (#1151): los dispositivos que usan una MAC distinta por red (un reloj, un portátil) se enlazan en una sola fila. Reportado por @crowedavid (#1174).
- Borrar cliente: nueva acción en la hoja de edición, con confirmación; un cliente borrado solo reaparece si se le vuelve a ver en la red.
- Pausa de unidades de flota (#1085): pausa una unidad sin borrarla: deja de sondearse, no alerta y se distingue con su insignia.
- Topología y rack: los uplinks salen del FDB del switch gestionado (adiós a la estrella plana y a los cables inventados); nueva designación de unidad central para el auto-cableado, que se sincroniza solo al cambiar el cableado físico; los montajes muestran los nombres reales de la flota y los equipos no identificados ya no se ofrecen como candidatos. Diseñado con la comunidad en las discussions #1108 y #1109.
- Latencia real (#1148): la tarjeta de latencia del gateway muestra la serie de 24 h medida (antes: un 0 ms falso con gráfica de demo). Reportado por @crowedavid.
- Documentación (#1152): landing y READMEs al día (análisis de canales, MCP, rack).
- MQTT (#1153, #1158, #1162): el publisher usa gonzalop/mq v0.9.11 (adiós a la tormenta de reconexiones que congelaba Home Assistant durante horas), espera el apagado del publisher viejo al reconfigurar en caliente y protege su estado interno con mutex.
- Puertos y LLDP (#1149, #1150): los tooltips de los puertos ya no recortan IP/MAC/detalle y muestran la información del vecino LLDP.
- Canales (#1147, parcial): el desplegable solo ofrece APs (quedan el scan ad hoc y las sugerencias conservadoras).
- Firmware upgrades (#1146): fuera de Labs, con su toggle propio de visibilidad.
- Seguridad y cumplimiento (#1167 a #1173): THIRD_PARTY.md con las licencias de dependencias incluido en los archivos de release, bump de golang.org/x/crypto (2 CVEs SSH) y otras dependencias, fixes de carreras en el sondeo RTL y en los tests.
Gracias a @crowedavid: la retención de clientes, el enlazado de MACs, el LLDP y la latencia de esta release empezaron con sus reportes (los squash de #1172 y #1174 salieron sin el crédito del commit; queda registrado aquí).
- Servidor MCP integrado (labs): servidor MCP de solo lectura en
/mcp: estado de flota, dispositivos, alertas y topología consultables por un asistente IA por HTTP con token de API. Solo en Labs, apagado por defecto, con rate-limit y auditoría. - Topología LLDP-MIB en switches SNMP: los switches gestionados por SNMP ya usan LLDP-MIB para el descubrimiento de topología: enlaces reales en hardware que nunca expuso un bridge OpenWrt. Esta funcionalidad existe gracias a @crowedavid: literalmente, sin él y su hardware esto no habría sido posible.
- Rack canvas (vista previa experimental, labs): una vista física del mueble de 19" con routers, switches y servidores, y detección del cableado desde el FDB (uplinks entre routers incluidos). Vista previa muy temprana: desactivada por defecto (Ajustes → Labs), aún en pleno desarrollo.
- Actualizaciones de firmware (#1121): lista solo dispositivos con OpenWrt vanilla, con nota explicativa, y sale de labs.
- Alertas: la opción "solo no leídas" ahora persiste (#1122) y se olvida la marca de leída al resolverse una alerta (#1133).
- Speedtest: botón adhoc y detección de estado "testing" restaurados (#1132).
- Roaming: el filtro de cliente de los eventos coincide con la vista de detalle y los filtros son multiselección (#1139).
- Interfaz: los marcadores AdGuard y AP ya no se muestran donde no aplican (#1141).
- SNMP: las interfaces virtuales se agrupan en vez de ocultarse (#1142, cierra #1125) y se ignoran ifAlias de solo espacios (#1077).
- Resumen: first/last seen aplicado al snapshot SSE de dispositivos (#1111).
- Diálogo de actualización con tamaño y fondo de estilo Pulse (#1116).
- Alertas que reaparecen leídas tras un reinicio (#1094): una alerta ya leída en una sesión anterior (p. ej. "Agent outdated") se mostraba leída al re-emitirse justo tras arrancar el server; ahora un episodio nuevo tras reinicio vuelve a no-leída (los refreshes de un episodio en curso, como port flapping, conservan su estado).
- "First seen"/"Last seen" siempre con guion (#1095): la marca de visto solo se escribía desde el resumen; la lista de dispositivos que consume la UI ahora también la persiste (write-through).
- Eventos de roaming desordenados dentro del mismo segundo (#1097): los logs de OpenWrt no llevan millis; el desempate ahora usa el orden de inserción (id), que reproduce el orden del log. Nuevos filtros por cliente y por AP en la vista de eventos.
- Tarjeta "Edit client" (#1096): Guardar/Cancelar junto a los campos que editan (nombre, tipo, icono) y título renombrado de "dispositivo" a "cliente".
- Tooltips en los botones web/SSH de la unidad (#1098): muestran la URL y el comando exactos antes de pulsar.
- Agente 3.0.8: una unidad sin clientes asociados en ese momento descartaba toda su sección wireless (radios, scans, BSSIDs propios); radios o BSSIDs propios ya bastan para reportarla.
- Análisis de canales: de labs a primera clase (#1070, #1076, #1079, #1080, #1082, #1084) - la página "Canales" es ahora un analizador de espectro WiFi completo y de solo lectura (nunca escribe en los routers): cascada por radio con campanas sobre un eje de frecuencia real, zonas DFS sombreadas con leyenda, franja continua de ocupación, tira de puntuación de TODOS los canales (13 en 2.4 GHz, todos los bloques a su ancho en 5 GHz, DFS etiquetados) agrupada por congestión, tabla ordenable con aislamiento cruzado en el gráfico y selector de flota sin unidades sin WiFi. Sin insignia de labs ni toggle.
- Motor de congestión inteligente (#1080, #1082): las redes propias nunca congestian un canal (detección por prefijo MAC, transitividad de SSID, datos dawn/usteer y los nuevos BSSIDs propios del agente); las vecinas se ponderan en dominio de potencia (un AP a -50 dBm pesa ~3000x uno a -85); el veredicto de congestión del canal actual es absoluto (umbrales sobre el score), no relativo al resto de la banda. Solo sugiere: recomienda canal y estima la reducción de ruido; aplicarlo sigue en manos del usuario.
- Agente 3.0.7 (#1087): reporta sus propios BSSIDs (
iw dev, cubre MACs aleatorizadas y guests fuera de dawn/usteer) y lee el ancho de canal anunciado (HT/VHT operation) de cada vecina, persistido enwifi_scans.width_mhz; el espectro dibuja cada red a su ancho real en vez de los 20 MHz estimados.
- La notificación push de "Agent outdated" llevaba la hora estática 16:00:00 (epoch en la zona del servidor) al dispararse tras un reinicio: la rama de inserción de
EmitOrUpdatenotificaba sin el default de timestamp (#1074). - El análisis de canales mostraba "este equipo no reporta radios WiFi" cuando el payload del agente estaba momentáneamente fuera de la ventana de frescura; ahora cae al último payload conocido para esa vista de solo lectura (#1080).
- La tarjeta de resumen proponía un bloque DFS como "mejor canal" al empatar a 100 en la normalización; ahora promociona siempre el canal que recomienda el motor (#1082).
- El título del documento ("Tu red, de un vistazo") era español fijo en index.html; ahora sigue el idioma de la interfaz.
- Abiertas las GitHub Discussions del proyecto: sugerencias, preguntas y votos de ideas en https://github.com/gnacho/netpulse/discussions
- Soporte RouterOS (MikroTik) de primera clase (#969, gracias @samex): nuevo tipo de router en la flota, sondeado desde el servidor por la API REST nativa de RouterOS 7+ (vitals reales: CPU/RAM/uptime, métricas WAN, comprobación de actualizaciones), con cliente del API binario (8728) escrito a mano para instalaciones con
wwwcerrado: cero dependencias nuevas, login moderno y solo lectura. Contraseña solo en el servidor (json:"-"), TLS autofirmado opt-in, y agenterouteros-pusheropcional. - M-Lab NDT como proveedor de speedtest por defecto (#1037, idea de @crowedavid): red de medición neutra y sin ánimo de lucro, con política de datos abierta: mejor privacidad que Ookla o Cloudflare. Las instalaciones con proveedor guardado lo conservan. Nuevo modo de planificación "auto" que ejecuta el test a la hora de menos tráfico según el histórico propio de la red (#1066).
- Anclaje de uplinks de flota por evidencia FDB (#1047, #1051, #1060): cada unidad cuelga de su padre real (el switch donde su MAC bridge se aprendió, con etiqueta de puerto); si el puerto tiene un círculo de switch (inferido o gestionado), el uplink sale del círculo: gateway → (switch) → AP. Ya no depende de que las unidades aparezcan como devices.
- Equipo aguas arriba del gateway bajo el nodo Internet (#1042): cuando la IP de un cliente coincide con la puerta de enlace WAN (módem/ONT del ISP), se dibuja colgando de la nube, junto a la línea WAN, en vez de como cliente LAN.
- Granularidad horaria y diaria en Informes (#1032, #1033): el selector ofrece Horario (buckets por hora) y Diario; el endpoint conserva semanal/mensual para API/CSV.
- Retención del registro de alertas y "Vaciar registro" (#1034): "Marcar todo como leído" ya no borra del log (solo del read-set); vaciar es una acción aparte, con confirmación, y
alerts.retentionDays(default 30, poda horaria y de arranque sin reinicio).
- El feed de roaming mostraba solo Disconnect en APs con wpad-mbedtls (OpenWrt 25.12.x), que no emite AP-STA-* (#1038).
- Pantalla en blanco al cambiar el rango de Informes (#1029).
- La tarjeta de edición de flota guardaba y cerraba al elegir el tipo; ahora hay etiquetas visibles, firmware target solo para OpenWrt/GL.iNet y sondeo SNMP disponible en el alta (#1026).
- La tabla de enlaces siempre nombraba al gateway como padre de los uplinks aunque el mapa los dibujara desde un círculo de switch (#1063).
- Literales en español filtrándose a UIs en inglés en las tarjetas de puertos; los pares inferidos llevan icono de red (#1039, #1040).
- El matching LLDP exigía igualdad exacta de nombre: "sw1" (chasis) no casaba con "sw1.lan" (flota) y el uplink caía al gateway (#1041).
- La integración Proxmox duplicaba un switch inferido entre el switch gestionado y los hosts; el inventario PVE (ground truth) lo absorbe (#1053).
- La segunda instancia Proxmox con id vacío o duplicado pisaba la primera en silencio; el diálogo lo rechaza (#1057).
- Previsualizaciones al pasar el cursor mientras se edita el layout de topología (#1045), tooltips que saltaban solos al abrir diálogos y marco de foco en la (x) (#1064, #1068), anillo de foco también al hover (#1069), y los diálogos de config cierran al guardar (#1065).
- El historial de tráfico de un router desbordaba la tarjeta con muchos puertos: selector en desplegable con filtro All/Active/Inactive (#1035).
- Las alertas se componen en inglés nativo y se traducen al idioma del cliente (#1014); las notas de release se muestran en el idioma del cliente (#1011).
- Terminología consolidada: flota/unidad y clientes (#1025, #1027), botón "Detectar" de Proxmox recuperado (#967).
- Actualización completa de dependencias (#1020 tramos 1 y 2, #1022, #1028).
Gracias a @crowedavid por los reportes que formaron la mayor parte de esta release, a @samex por la integración RouterOS, y a @gnulan, @borky y todos los que reportan y discuten ideas.
- Hub de Servicios e Integraciones en Ajustes (#964, #968, #977, #996): AdGuard, WireGuard y OpenVPN como servicios de red y MQTT y Proxmox como integraciones, todos con toggles de activación en el servidor (efecto inmediato, sin reinicio) e icono de configuración que abre diálogos grandes. ntfy y Telegram viven en Notificaciones. Detección de AdGuard (sondea gateway y flota, #964) y descubrimiento de Proxmox en la subred en dos fases (TCP masivo y confirmación TLS en serie, porque pveproxy limita las conexiones simultáneas por IP, #967).
- Puerto HTTPS configurable (#978): se cambia en caliente sin reiniciar, con rollback si el bind falla; por defecto 443 (
NETPULSE_TLS_PORTsigue teniendo prioridad). - Proveedor del test de velocidad configurable (#976, #1001): Ookla (por defecto), Cloudflare, LibreSpeed o un endpoint personalizado con su propia URL; las opciones del test periódico se editan en su diálogo (#997).
- Clientes: filtros de flota y conexión como desplegables multi-selección (#970, #989) con el mismo estilo que el filtro de tipo, selector de columnas visibles de la tabla (#958, la de nombre siempre visible y el espacio liberado se reparte a partes iguales) y banda 6 GHz solo cuando la red la tiene (#991).
- Selector de idioma de las notificaciones y consolidación de canales (#977, #998): push integrado en la tarjeta de notificaciones visuales y bloqueado sin contexto seguro (HTTPS), con el motivo visible al pasar por encima del check.
- Resumen rediseñado (#965, #979, #982, #983, #992): hero compacto a dos mitades (saludo con latencia y clientes / anillo de salud, todo centrado), tarjeta de alertas al lado con acciones solo icono y estado vacío amable, tráfico WAN a todo lo ancho con la gráfica del histórico de velocidad medida y la línea del último test (% del plan, bajada/subida, ping, antigüedad, servidor) sin botón de ejecutar, "Tu flota" como título de la sección de routers y AdGuard antes que WireGuard.
- Informes reescritos para entenderse (#973, #993): cabecera que explica qué se mide y en qué ventana, tiempo sin datos en horas y minutos, tres tarjetas resumen y barra segmentada estilo Uptime Kuma con tooltip de fecha y hora. Los routers sin cobertura suficiente muestran «sin datos suficientes» en vez de cifras inventadas.
- Dieta de textos en Ajustes (#1002, #995, #1000, #999): los discursos largos (modos de AdGuard, creación del token de Proxmox, canales urgentes, topic de ntfy...) pasan a tooltips de información o a la página de Ayuda; la tarjeta HTTPS queda en toggle + puerto; dispositivos de confianza y tokens Bearer quedan en añadir + listado; tokens de API movidos a Administración; historial de actualizaciones movido a Acerca de, bajo Sistema. Sin párrafos en la UI.
- Navegación y tema (#974, #980, #981): Ajustes sobre Ayuda, toggle claro/oscuro en la cabecera junto a refrescar (y funcionando de verdad: la paleta inline ganaba a la clase CSS), indicador «En vivo» al pie del menú junto al de plegar, y diálogo de actualización rediseñado estilo Pulse (tarjeta de versión, prerrequisitos, aviso ámbar, progreso con pasos).
- Detalle de cliente aligerado (#985, #984): sin tráfico por cliente ni datos de presencia, botón de editar a la derecha y grid de datos sin párrafos; eliminado el texto largo de NetGrip/agente nativo.
- Topología (#986, #990): colores de banda wifi claramente diferenciados (2.4 naranja, 5 cian, 6 fucsia) y botón de etiquetas junto al de etiquetar dispositivos.
- Labs en rojo (#975): el check de Labs y sus servicios (orquestación, canales, actualizaciones) se acentúan en rojo porque escriben en los routers; nuevo toggle maestro «Limitar historial» (activado por defecto) con diálogo de retención de presencia e intervalo de itinerancia.
- «Marcar todo como leído» no limpiaba las alertas (#971): nuevo endpoint
POST /api/alerts/clearque vacía el feed de verdad y sobrevive a recargas. La alerta de caída de agente es ahora volátil por router (no se acumula en el log y desaparece al recuperar) y MarkRead ya no reactiva alertas visibles como no leídas (#966). - Disponibilidad de Informes inventada (#987): la fórmula asumía sondeo de 5 s y marcaba como «caído» el tiempo entre muestras (hasta 17 días fantasma en routers online). Se calcula ahora con la cobertura real de buckets de 5 minutos y el día en curso se mide con los datos crudos.
- El toggle de tema no cambiaba nada (#980): el arranque aplicaba la paleta como variables CSS inline que ganaban a la clase del tema.
- Los desplegables de filtros de Clientes no abrían (#1006): el trigger personalizado no reenviaba el ref a Radix.
- Routers renombrados aparecían como
router-xx(#988): Orchestration e Informes muestran el nombre dado por el usuario con el slug entre paréntesis cuando difieren (reporte de gnulan). - El fondo se desplazaba al abrir diálogos: se reserva la canal del scrollbar (
scrollbar-gutter: stable) para que el ancho no cambie. - Diálogos con fondo blanco en tema oscuro y datos por defecto al abrir: el DialogContent usaba una clase stock ajena a los tokens del tema, y el diálogo de AdGuard mostraba el formulario vacío mientras cargaba la config real.
- Umbral de señal débil duplicado (#1003): el chip y el filtro de Clientes usaban -70 dBm fijos; ahora siguen el ajuste global (reporte de crowedavid).
- Sin cambios en el agente (sigue en la 3.0.5).
- El botón de actualizar quedaba bloqueado en instalaciones sin clone git (#955): la comprobación «working tree limpio» se ejecutaba en cualquier instalación en modo rolling (detectado por la presencia de
deploy/update.sh) aunque el directorio no fuera un clone git; fallaba con «no se pudo leer el estado de git» y el botón de aplicar quedaba bloqueado para siempre, pese a queupdate.shsí soporta ese layout. La comprobación se omite cuando no hay.git, igual que en el layout estable. - El chequeo de conectividad agotaba la cuota anónima de la API de GitHub (#956): el sondeo llamaba a
api.github.comen cada cómputo (caché de solo 30 s); con la interfaz de actualización abierta podía superar las 60 peticiones/hora del acceso anónimo y la aplicación de la actualización fallaba después con el error de límite de peticiones. El sondeo usa ahora la web pública de GitHub, que prueba la conectividad igual y no consume cuota de API. - Tarjeta «Protegidos por AdGuard» en rojo al 100 % (#959): la barra aplicaba los umbrales de las métricas de carga (rojo por encima del 90 %) a una métrica de cobertura, donde el 100 % es el mejor caso. Rediseño: con cobertura total muestra un escudo verde grande con el porcentaje; con cobertura parcial muestra el contador de protegidos y la tarjeta es clicable, filtrando la tabla para ver solo los clientes sin proteger.
- Clientes SNMP duplicados por las mayúsculas de la MAC (#960): el parser FDB de SNMP emitía las MACs en minúsculas mientras el resto de fuentes (sondeo del agente, wireless, DHCP, atributos) usan mayúsculas, así que el mismo dispositivo aparecía dos veces en la lista de clientes; también se rompía la exclusión de la MAC del puente en el uplink. El parser emite ahora la forma canónica en mayúsculas.
- Sin cambios en el agente (sigue en la 3.0.5).
- Serie WAN / VLANs / alertas del fork borky (#946): soporte multi-WAN, política de uplink en el agente, alerta cuando cambia la conexión de salida a internet, el detalle del router muestra sus conexiones, VLANs leídas del router y panel de bocas enlazado por el puerto real. Atribuido a borky.
- Salud del sondeo SNMP por dispositivo (#930): contadores de aciertos/fallos por router SNMP, ping ICMP de respaldo sin privilegios, alerta
snmp-failingtras 3 fallos seguidos solo si el host responde a ping (pista de community/puerto mal configurados), alerta de recuperación y contadores visibles en el detalle del router y en la tarjeta de puertos. - Last seen en clientes (#954): nueva tabla
device_seenque registra la primera y la última vez que el server vio online a cada MAC (todas las fuentes, no solo wireless). El detalle del dispositivo muestra ambas y la tabla de clientes gana columnas ordenables en pantallas xl+. El server manda epochs y la app formatea el relativo con el locale activo.
- Switches SNMP sin clientes pese a tener el FDB poblado (#928): dos causas encadenadas, confirmadas con el snmpwalk del reporter. (1) El fallback a la tabla Q-BRIDGE solo se probaba cuando el walk dot1d devolvía cero PDUs crudos; si devolvía datos no utilizables, nunca se intentaba (#948). (2) Los TP-Link Omada (SG3428X-M2, SG3210X-M2) indexan la tabla dot1d por
<vlan>.<mac>(7 octetos) en vez de la MAC desnuda que manda RFC 1493, y el parser exigía exactamente 6: descartaba todas las entradas (#950). La línea de log del FDB muestra además los PDUs crudos de cada walk para acortar diagnósticos futuros. - "First seen" nunca se poblaba fuera del demo (#954): el campo existía en el contrato pero ninguna fuente live lo rellenaba; ahora sale de
device_seen. - Slugs autogenerados invisibles en la UI (#951): los slugs (router-lan, gateway...) se generan en el alta y salían en logs sin aparecer en la web. Las líneas de log del poller SNMP y de "router inalcanzable" usan ahora el nombre del dispositivo (con el slug entre paréntesis) y la tarjeta de información del detalle muestra la fila ID.
- Agente 3.0.5: la serie WAN/VLANs toca el agente (política de uplink); el resto de cambios son de servidor y web.
- El selector de columnas visibles de la tabla de clientes (tercera sugerencia de #954) queda como follow-up en #958.
- Gracias a crowedavid por el reporte detallado de #928, las pruebas con sus TP-Link Omada y las sugerencias de #954, y a borky por la serie WAN/VLANs.
- HTTPS con autoridad certificadora privada (#937): opt-in, con los defaults intactos. Servidor HTTPS con certificado renovable desde Ajustes, CA privada, prueba de clave del servidor al emparejar el agente (pinning de CA, varias claves), cookies separadas HTTP/HTTPS, HSTS solo en respuestas seguras, timeout de escritura SSE y descargas a través del service worker. Adaptado del fork comunitario borky (17 commits, atribuidos).
- Filtro All / Online / Offline en Clients, persistente (#923): el interruptor "solo online" se convierte en selector de tres estados, guardado junto al resto de preferencias de la página.
- Panel en inglés por defecto (#938): el idioma por defecto para navegadores nuevos pasa a ser el inglés; el selector sigue igual. Adaptado del fork borky.
- A qué AP o switch cuelga cada cliente (#938): visible en la lista de dispositivos.
- Serie Proxmox del fork borky (#936): test de conexión que dice qué ve el token, CTs nombrados con su nombre real, host encontrado por la dirección que el cluster conoce, distinción VM/contenedor (badge "VM" en el mapa), re-clasificación al recibir nombre.
- Serie routers/topología/dispositivos del fork borky (#945): vecinos ARP no confirmados por el kernel, re-atribución de clientes al borrar un router, conteo de clientes igual que el overview, cada enlace nombra la caja de la que sale y con velocidades medidas, alerta de dispositivo desconocido que dice dónde está y permite identificarlo, emparejamiento de agentes por más que su slug, bocas fantasma DSA eliminadas, uplink marcado en la boca real.
- La pill "Reconnecting" parpadeaba con TLS (#925): ahora solo aparece tras 5 s de caída sostenida del SSE; las reconexiones transitorias se resuelven sin llamar la atención.
- Fila de puertos fuera de pantalla en switches grandes (#929): el chasis hace wrap cuando hay más de 16 bocas.
- Clientes FDB/LLDP no aparecían según la boca (#928, #943): el emparejamiento se hacía por id de boca en vez de por interfaz; cualquier boca cuyo id no era su interfaz (WAN "wan" sobre "eth1", PPPoE sobre "lan1") quedaba vacía aunque el vecino estuviera aprendido. Fix del fork borky, atribuido.
- SNMP FDB sin diagnóstico (#928): el walk del FDB reporta ahora su fuente (dot1d/dot1q), devuelve error real si ambos fallan y el journal registra el conteo de entradas cuando cambia.
- "Dejar anónimo" decía "Dispositivo identificado" (#938): el toast refleja ahora la elección real.
- Clave "Find this device" propia (#938): el texto correcto gana sobre "View device".
- Agente 3.0.4: la serie de emparejamiento FDB/LLDP y slugs toca el agente; sin cambios funcionales para routers sin esos casos.
- Adaptado de los forks comunitarios con atribución: borky (Proxmox, UI/i18n, HTTPS/CA, routers/topología), más invitaciones abiertas a contribuciones (UniFi #944, RouterOS #939).
Crédito: la serie completa de este release procede del fork de borky (borky-git borky-git@protonmail.com): Proxmox (#940), UI/i18n (#941), HTTPS/CA (#942), FDB/LLDP por interfaz (#943), routers/topología/dispositivos (#945) y WAN/VLANs (#946). Los merges #940-#944 y #946 llevan trailer Co-authored-by; el #945 lo omitió por error - el crédito se reconoce aquí nominalmente.
- No se podía seleccionar °F en Ajustes (#898): la unidad se guardaba en localStorage con comillas y la lectura nunca coincidía. Ahora persiste y se aceptan los valores antiguos.
- Notación decimal europea en los totales WAN (#899): las tarjetas "TOTAL 24H" y "TOTAL TODAY" formateaban server-side con coma fija. Ahora el servidor manda bytes crudos y el cliente formatea con el locale activo, igual que las tasas.
- La leyenda de la matriz de señal no coincidía con los umbrales reales (#905): reescrita sin operadores sobre valores negativos ("-65 dBm o mejor", etc.), exacta respecto al código.
- El filtro DAWN en la tabla de eventos con redes usteer (#909): la opción DAWN solo aparece cuando la red tiene routers con DAWN.
- Los eventos de conexión no aparecían en la tabla (#908): el parser de logread no aceptaba el formato más común de hostapd ("wlan0: AP-STA-CONNECTED ", con la iface una sola vez). Ahora cubre todas las variantes observadas, con sufijos de motivo incluidos.
- Los pushes llevaban una hora estática (16:00:00) (#914): el notifier recibía el evento con timestamp 0 (epoch en la zona del usuario). Emit/EmitNoDedup ahora rellenan Ts antes de notificar, con test de regresión.
- La alerta "Agente desactualizado" se repetía cada 30 s (#910): se reenviaba con cada push del agente y refrescaba su timestamp. Ahora solo se reemite cuando la versión observada o la de referencia cambian, como la alerta de caída.
- La contraseña inicial del instalador tenía caracteres ambiguos (#895): el alfabeto base64 incluía 0/O y 1/l/I, indistinguibles en muchas fuentes de terminal. Ahora se genera sin ellos (~94 bits de entropía mantenidos).
- Umbral "Weak signal" server-wide (#904): el ajuste de Ajustes ahora vive en el servidor (kv, default -70 dBm) y alimenta la alerta de señal débil, igual para todos los clientes. Antes solo pintaba un caption local.
- El filtro "Solo señal débil" de la matriz usa ese umbral (#906) y el rótulo muestra el valor efectivo ("peor que X dBm"), evitando la ambigüedad del "<" sobre valores negativos.
- Intervalo de ingesta de eventos de roaming configurable (#907): nuevo ajuste (Ajustes > Datos y umbrales) con la cadencia del collector de logread (default 60 s), aplicado sin reiniciar. La cabecera de la tabla de eventos muestra los valores reales configurados (retención e intervalo) en vez de texto fijo.
- Toggle "Solo no leídas" en la tarjeta de Alertas del Overview (#916): mantiene la vista limpia en operación normal; la preferencia persiste en el navegador.
- "Marcar todo como leído" no persistía (#891): un snapshot tomado antes de que la petición llegara al servidor podía devolver las alertas a no leído hasta el siguiente ciclo. Ahora el estado leído se re-aplica al confirmarse la operación, y los eventos de alerta por SSE se deduplican por ID (ya no duplican filas ni inflan el contador).
- Los pushes salían siempre en español (#888): las alertas se componen en español y la web las traduce por usuario, pero ntfy, Telegram y webhook publicaban el texto en crudo. Ahora se traducen en el servidor al publicar, con los mismos catálogos de la app.
- Banner de "agente caído" demasiado drástico tras un reinicio (#887): justo después de reiniciar el servidor todos los agentes tardan unos segundos en volver a empujar, y cada tarjeta mostraba "Agent down - reinstálalo". Durante los primeros 90 s tras el arranque se muestra un aviso neutro de espera; el banner drástico se reserva para fallos persistentes.
- El parser del env del agente no recortaba comentarios en línea (#883):
NETPULSE_SCAN_INTERVAL=0 # notatomaba el valor literal y caía en el default de 30 m en vez de desactivar el sondeo. Agente 3.0.3. - La actualización fallaba en instalaciones sin clone git (#897): en el layout estable (install.sh / CT) el árbol de fuentes no tiene
.gity la actualización moría en el paso de fetch. Ahora se resuelve el SHA de main víagit ls-remotey el flujo continúa.
- Idioma por defecto del servidor para las notificaciones (#889): nuevo ajuste server-wide
alerts.lang(Ajustes > Notificaciones, por defecto inglés) que define en qué idioma se envían los avisos de ntfy, Telegram y webhook, independientemente del idioma de cada cliente. - Visibilidad de la recuperación de agentes (#890): todo el proceso de recuperación automática (expiración del TTL, reinicio por SSH, espera de push, rotación de token, escalado a reinstalación y cooldowns) queda registrado en el log con prefijo
supervisor:/rearmer:, y la estrategia completa (cadena de escalado, timeouts y anti-martilleo) está documentada endocs/AGENTE-OPENWRT.md.
- Gráfica de historial de estrellas en el README (#881, #882).
- El reinstall del agente pisaba tu configuración (#851): el botón "reinstall" de la consola y la rotación de token reescribían
/etc/netpulse-agent.envdesde cero y perdían ajustos añadidos a mano (comoNETPULSE_SCAN_INTERVAL=0). Ahora conservan cualquier opción que no gestionan, y el env generado documenta las disponibles como valores por defecto comentados. - Watchdog cron retirado de la instalación (#851): un reinicio a ciegas cada 2 min no arregla un agente en bucle de crash, solo ensucia el log; la detección efectiva es la del servidor (dead man's switch + alertas). El respawn de procd sigue cubriendo crashes puntuales y el init restaura el binario tras un sysupgrade. Al actualizar se limpia la entrada de cron y el fichero del watchdog de versiones anteriores.
- Entrada de cron del watchdog huérfana tras sysupgrade (#879):
/etcsobrevive al sysupgrade pero el binario no, y cron logueaba un comando inexistente cada 2 minutos. El init del agente auto-repara la entrada al arrancar si el watchdog ya no existe. - Las acciones de las alertas no existían en táctil (#862): dependían del hover; en móvil y tablet ahora se muestran siempre, con su etiqueta y en su propia línea para no aplastar el título.
- El CI inyectaba el tag del agente como versión del servidor (#869): los builds de desarrollo reportaban
agent/vX.Y.Zcomo versión; ahora solo se consideran tags de release.
- Toggle "Sondeo por consola" por switch (#863): la consola web del firmware RTLPlayground admite una sola sesión y cada sondeo expulsaba a quien estuviera dentro. Ahora se puede desactivar por switch (Ajustes > Routers); con el beacon v1.3+ los datos de firmware, uptime y MAC siguen llegando por UDP.
- Verbosidad por canal de notificación (#874): cada canal (ntfy, y la misma palanca para Telegram, webhook y Web Push) decide si recibe solo alertas urgentes (por defecto, sin cambios) o todo lo que pasa el filtro del feed. La tarjeta de ntfy tiene el interruptor.
- Canales de notificación documentados (#871): las tarjetas de ntfy y Telegram y el README explican que los canales solo llevan alertas urgentes, separado del nivel del feed.
- Reinstalar agente desde la consola contra un servidor HTTPS (#851): el script de reinstall (manual y el auto-reinstall del supervisor) generaba el
/etc/netpulse-agent.envsinNETPULSE_SERVER_FP, así que el agente seguía cayendo en el bucle fatal de pinning aunque el servidor usara TLS válido. El servidor deriva ahora el pin SPKI por sí mismo (handshake TLS contra su propia URL pública, con caché) y el env generado lo lleva; la rotación de token en caliente también lo conserva en lugar de perderlo. - Acciones de alertas más visibles (#862): las acciones por fila del feed crecen (icono mayor y área de clic más generosa) y muestran su etiqueta de texto al pasar el cursor; el resumen de Inicio reduce cada alerta al título con las mismas acciones, y "Actualizar agente" navega a la sección de agentes de la flota.
- El icono personalizado de un cliente no se veía en la tabla de Clientes (#864): la tabla de clientes conectados usaba el tipo deducido e ignoraba
iconOverride; ahora usa el mismo criterio que el resto del panel.
- Firmware, uptime y MAC por beacon (#865): el datagrama de estado del firmware RTLPlayground lleva ahora versión de firmware, uptime y MAC de la CPU, y NetPulse los ingiere como fuente principal para el switch (con fallback a la consola), de modo que el panel los muestra aunque el sondeo por consola esté deshabilitado.
- Routers solo-agente que flapeaban offline con cada roam (#852): un push de evento inalámbrico (roam de un cliente, payload sin sección system) sobrescribía el estado del agente en el servidor y rompía el emparejamiento por hostname/MAC hasta el próximo push completo; en routers sin slug exacto cada poll caía en la ventana y marcaba "sin cliente". El registry conserva ahora la última identidad conocida del agente (también persistida y restaurada).
- La caída del agente no notificaba en routers solo-agente (#850): el poll sigue sirviendo el último payload cacheado, así que la tarjeta nunca marcaba offline y el aviso del Dead Man's Switch era system/no urgente: jamás pasaba los filtros de notificación "solo urgentes del topic router". Ahora emite como category router + urgent + critical (con SSH disponible sigue siendo el aviso system no urgente de siempre).
- Instalación de agente contra un servidor HTTPS (#851): el agente fallaba en bucle exigiendo
NETPULSE_SERVER_FPy el one-liner de la UI no lo incluía.install-agent.shderiva el fingerprint SPKI del propio certificado cuando no se pasa--server-fp, el one-liner lo incluye si el servidor lo conoce, y el error del agente imprime el comando openssl exacto. - Velocidades de puerto truncadas y conduits DSA visibles (#847): los enlaces de 2500 Mbps se mostraban como "2 Gbps" (entero truncado) y el puerto CPU interno del switch (conduit DSA, marcado por
/sys/class/net/<i>/dsa) aparecía como boca falsa con velocidades sin sentido ("150 Mbps" en un BPI-R4). Gbps con decimales ("2.5 Gbps") y los conduits se excluyen del panel y del fallback de WAN. Requiere agente actualizado en los routers (el probe corre en el router). - La velocidad WAN contratada no se veía en la topología (#849): el plan del enlace WAN salía "—" en live aunque estuviera configurado en Ajustes; ahora se deriva del par contratado ("Fibra 1000/1000 Mbps") sin pisar un plan ya presente.
- El icono personalizado de un cliente no llegaba a la topología (#845): el chip del mapa usaba solo el tipo deducido e ignoraba
iconOverride; ahora usa el mismo helper que las filas de Clientes. - Una alerta offline por caída (#846): mientras una caída está abierta, el flapping ya no re-emite la alerta crítica ni re-notifica; la recuperación debe mantenerse 3 sondeos seguidos y al estabilizarse la alerta desaparece del feed en lugar de quedar leída, y sobrevive a reinicios del servidor.
- Emparejamiento de agentes en la tabla de flota (#848): las filas emparejaban solo por slug y perdían agentes cuyo slug no coincide con el id del router; ahora también por routerId.
- Traducciones de la landing ar/hi/zh/ja (#841): repaso nativo de las cadenas traducidas en bloque; "topology" ya no es topografía en árabe, "health" ya no es salud humana en hindi, puntuación CJK normalizada en chino, y restaurado el inciso "(o la tarjeta MQTT en Ajustes)" que el inglés ganó después.
- Rutas prerenderizadas por idioma en la web (#842): la landing genera una ruta estática por idioma (
/de/,/ja/features/,/ar/home-assistant/, 10 idiomas x 3 páginas) conlang, canonical, hreflang yog:localecorrectos, indexable de verdad para los crawlers./sigue siendo el shell en español con el selector JS;/es/canonicaliza a/. El sitemap lista las 30 URLs. - Contador de instancias documentado (#822): el README describe el ping diario anónimo (id aleatorio, versión, OS/arquitectura; nada más) y cómo desactivarlo (
NETPULSE_TELEMETRY=0).
- Ajustes de MQTT y propagación a NetGrip (#838): nueva tarjeta en Ajustes para configurar el publisher MQTT (activar, broker, puerto, credenciales, instancia e intervalo) sin tocar el entorno ni reiniciar: se guarda en la base de datos y se aplica en caliente. Incluye prueba de conexión contra el broker y propagación a la flota: con un clic envía esa configuración a cada router NetGrip (op
mqtt.configurecon rollback si el broker nuevo no responde), para tener toda la flota en Home Assistant sin configurar router a router. - NetPulse cede el paso a los routers que se exponen solos (#832): cuando un router lleva NetGrip con su MQTT activado (se expone a Home Assistant con sus propias entidades y comandos), el publisher de flota deja de publicar su dispositivo por router y publica la configuración de descubrimiento vacía para que Home Assistant la borre, en lugar de duplicarla. Los routers que no se exponen (sin NetGrip, o con el MQTT de NetGrip apagado) siguen publicando las seis entidades de siempre, así que la cobertura no depende de NetGrip. El dato lo informa el propio agente del router en su payload.
- Acciones en las alertas (#833): cada fila del feed gana acciones con tooltip: "Identificar dispositivo" (dispositivo desconocido, va al flujo de identificación), "Actualizar agente" (agente desactualizado, va a orquestación) y "Descartar alerta" en todas. El descarte se persiste (
POST /api/alerts/dismiss) y sobrevive a reinicios; la alerta de agente desactualizado se resuelve sola cuando el agente informa de una versión al día. - Tarjeta de NetGrip en Orquestación (#836): aviso al pie de la sección de que escribe en los routers y recomendación de NetGrip para OpenWrt (el rol de agente cuesta ~10 MB de disco y ~15 MB de RAM), con enlace a netgrip.cloudless.club.
- Acerca de (#837): la entrada de Ko-fi se sustituye por "Danos una estrella en GitHub" y "Código fuente" por "Informar de un problema", que apunta a las incidencias de GitHub.
- Home Assistant por MQTT (#825): NetPulse puede publicar el estado de la flota en un broker MQTT y Home Assistant lo descubre solo (MQTT Discovery). Crea un dispositivo con la salud de la instalación (puntuación, clientes online y totales, routers y alertas sin leer), un dispositivo por router (estado, salud, CPU, memoria, temperatura y clientes) y publica un evento por cada alerta cuando se dispara. Es opcional y viene desactivado; se configura con las variables
NETPULSE_MQTT_*(o las opciones UCImqtt_*en modo on-box). Publica, no escucha: desde Home Assistant se lee la red. La conexión al broker no usa TLS, así que está pensada para un broker de tu red local. Con NetGrip activado en los routers, además se controlan desde Home Assistant el WiFi de invitados, banIP, IPv6, SQM y el reinicio. El detalle (topics, entidades y límites) está en el manual y en la página NetPulse y Home Assistant.
- La orientación de la PWA la gestiona el sistema operativo (#808): el manifest declaraba
orientation: "any", que en una PWA instalada anula el bloqueo de orientación del propio sistema y además permite el giro invertido (180°): la pantalla rotaba y "se daba la vuelta" aunque el auto-rotado estuviera bloqueado. Se elimina el campo, de modo que el sistema vuelve a decidir y el bloqueo de rotación se respeta. (Una PWA ya instalada puede necesitar reinstalarse para recoger el manifest nuevo.) - El updater deja de acumular snapshots de la base de datos (#809): cada actualización dejaba una copia completa de la BD en
server/data/.update-backup-*sin límite alguno (18 copias / ~2,2 GB en dos semanas en una instancia viva). Ahora se conservan las tres más recientes y el resto se poda al terminar un update correcto; durante un rollback no se toca ninguna. - Índices duplicados en
port_series_raw(#817): la tabla ya tenía su clave primaria(router_id, port_id, ts)y cargaba además dos índices idénticos a esa misma clave, creados por error en dos sitios distintos (~33 MB de más en una base de 266 MB). Se dejan de crear y una migración los elimina en las bases existentes.
- El rollup cubre toda la retención de muestras (#812): el rollup nocturno raw→5m usaba una ventana fija de 48 h, así que tras una caída de más de dos días las muestras entre 48 h y 7 días nunca se agregaban y se perdían al purgar el raw, dejando un hueco permanente en las series de 5 min y diarias. La ventana pasa a ser la retención del raw (7 días) en métricas, ancho de banda por cliente y series por puerto.
- Backups automáticos comprimidos (#818): los backups diarios de la base de datos se guardan ahora comprimidos (
netpulse-<fecha>.db.gz, aproximadamente 4× menos espacio) y la retención reconoce tanto el formato nuevo como los backups antiguos sin comprimir.
- AdGuard se puede desactivar de verdad desde Ajustes > Servicios (#813): desmarcar un servicio en "Servicios" solo ocultaba su tarjeta en el panel; el servidor seguía sondeando AdGuard y restando 5 puntos de salud con la etiqueta "AdGuard inactivo". Peor aún, la tarjeta de configuración de AdGuard estaba condicionada por ese mismo checkbox, así que al desmarcarlo desaparecía también la única acción para desactivarlo. Ahora el toggle de Servicios se persiste en el servidor (
GET/PUT /api/settings/services) y, al desactivar AdGuard, el sondeo se detiene y no se aplica la penalización. La tarjeta de AdGuard gana además un botón Desactivar que borra su configuración (DELETE /api/config/adguard), la tarjeta del panel se oculta cuando no hay configuración y el texto de la sección aclara que los servicios marcados "se muestran y se monitorizan".
- El feed de anuncios se sirve desde
netpulse.cloudless.club(#810): el aviso del panel deja de depender del propio binario y pasa a leer el feed publicado en el dominio del proyecto.
- El historial de alertas sobrevive a reinicios y actualizaciones (#798): el registro de alertas era solo memoria (tope 100, sin tabla), así que un self-update lo borraba por completo. Las alertas se guardan ahora en una tabla SQLite (esquema idempotente, poda a 500 filas) y se recargan al arrancar, reconstruyendo la ventana de deduplicación y el estado de leídas. El modo demo sigue siendo solo memoria, para no re-sementar el dataset sintético.
- El nombre del dispositivo se puede aplicar como reserva DHCP (#800): renombrar un cliente solo cambiaba el nombre visible dentro de NetPulse, y el router seguía mostrando el hostname original (p. ej. en LuCI). La tarjeta de reserva DHCP del editor ofrece ahora "Aplicar nombre en el router": si la MAC ya tiene reserva se actualiza solo su hostname (la IP no se toca); si no, se crea la reserva con la IP actual. El hostname debe ser DNS válido (la acción se deshabilita con una pista si el nombre visible no lo es), los conflictos de IP con otras MAC se rechazan y, como toda escritura en el router, se muestra primero el plan de comandos y solo se ejecuta tras la confirmación explícita.
- Nombre y tipo editables en la ficha del cliente (#797): el editor de dispositivos pasaba a solo dejar cambiar el icono. Ahora
device_overridesguarda también nombre y tipo, con un campo de nombre y un selector de tipo (los 12 conocidos o de vuelta a Auto): el nombre propio manda sobre el alias DHCP y el tipo sobre el clasificador, y los tipos inválidos se rechazan. De paso se corrigen las búsquedas de overrides con MAC en mayúsculas (causa probable de los iconos que "desaparecían") y la imposibilidad de volver a Auto. - Orden estable y columna de tráfico en la lista de clientes (#799): el orden por defecto ya no "baila" entre refrescos (tráfico cuantizado en buckets de 0.1 Mbps con desempate por nombre), la lista de escritorio tiene columna de Tráfico ordenable y el móvil gana un selector de orden, así que ambas vistas muestran lo mismo.
- El botón de comprobar actualizaciones hace un check real (#795): el botón de Ajustes solo leía el último estado cacheado (arranque o tick de 24 h), así que podía seguir mostrando un "latest" caducado. Ahora dispara primero el check real (
POST /api/update/check) y solo recurre al estado cacheado como respaldo. - La alerta de auto-actualización respeta el idioma de la interfaz (#796): la descripción de la alerta interpolaba una frase en español fija del backend aunque la UI estuviera en inglés. El programador emite ahora códigos de resultado estables (applied / check-failed / cannot-apply / apply-busy, con versión y motivo) y los locales EN/ES resuelven una plantilla propia por resultado antes que la descripción genérica.
- El botón «Rearmar» ya no se ofrece en agentes externos (#792): la cabecera del detalle de un router mostraba el rearme para cualquier agente caído, incluidos los externos sin SSH (p. ej. un switch gestionado por beacon) y cualquier scraper no nativo: el backend ya los rechaza, pero el botón no debería aparecer. Ahora el componente lo oculta para
kindexterno en todos los sitios donde se use.
- Versionado del agente desacoplado del server (#790): hasta ahora cada release del server rebuildaba el agente con el número del tag, así que todos los agentes se marcaban como desactualizados en cada release aunque el código del agente no hubiera cambiado. El agente pasa a tener versión propia en
agent/VERSION(arranca en 3.0.0, por encima de las versiones heredadas del tag, de modo que la flota existente se actualiza una vez a la nueva línea y desde entonces solo pide actualización cuando el agente cambia de verdad). El CI la inyecta igual en el binario del agente y en el servidor, y la release falla siagent/cambió desde el tag anterior sin bump deagent/VERSION. Nombres de los assets del agente sin cambios (siguen ligados al tag del release).
- Flujo completo de actualizaciones de firmware con owut/ASU (#761): la pantalla de actualizaciones se rediseña en torno al attended sysupgrade. Modelo y versión actuales pasan a solo lectura con lo detectado del board info; la versión objetivo es un desplegable con las versiones estables que ASU puede construir para el router (aviso inline en saltos de rama mayor); URL y checksum quedan como vía avanzada. owut se puede instalar desde la UI, "Comprobar" pasa a la fila de acciones junto a Guardar / Actualizar ahora / Programar, y Actualizar ahora lanza el upgrade desatendido (conserva los paquetes instalados) con confirmación clara: el estado sigue la secuencia construcción → descarga → reinicio, con vista de proceso (fase, barra de progreso y consola) y vista de éxito al terminar. Programación con recurrencia una vez / semanal (empezando en lunes) / mensual, idempotente (un router al día no hace nada) y con aviso del resultado por alerta (feed, push, webhook, Telegram). Los routers con firmware de fabricante (GL.iNet, detectado por sus repositorios) quedan fuera del flujo con un panel informativo: sus actualizaciones las gestiona el fabricante. Los paquetes del propio stack (netgrip, agente) sobreviven al flasheo: NetPulse asegura sus ficheros en /etc/sysupgrade.conf antes de actualizar, y los paquetes locales sin feed se detectan en el check y se ofrecen para excluir con sus nombres.
- Notificaciones vía ntfy (#766): nuevo canal en la cadena de notificaciones (push + webhook + Telegram + ntfy), para ntfy.sh público o un servidor propio: basta un topic (funciona como contraseña, se recomienda largo y aleatorio) y opcionalmente un token de acceso para topics reservados. Publicación con título y prioridad, cola con reintentos y fail-silent, activable desde la UI sin reiniciar, y botón de prueba que publica de verdad y muestra el error si algo falla. El token nunca sale del servidor.
- Proxmox VE multi-instancia (#764): la integración pasa de un único endpoint a una lista de instancias (clusters o nodos sueltos), cada una con su URL y token de solo lectura; la config anterior migra sola a una instancia "default". El inventario consulta todas las instancias y fusiona VMs/CTs y nodos (con claves por instancia, para que dos clusters con nodos homónimos no se pisen), la topología sella como siempre por MAC, y los nodos de hipervisor llevan su instancia para distinguirlos en la UI. Ajustes muestra la lista con añadir, editar y quitar; los secretos nunca salen del servidor.
- Auto-actualización programada del servidor (#759): nueva programación en Ajustes → Administración con modo desactivado (por defecto) / diaria / semanal / mensual y hora. El disparo comprueba novedades y aplica solo el binario cuando el readiness lo permite (nunca en modo demo ni con el check fallido), con la verificación de salud y el rollback automático del helper root; el reloj deriva del último disparo persistido, así que reinicios y la propia actualización no re disparan la ventana. Cada intento (aplicada, sin novedades, comprobación fallida, no aplicable) queda en Ajustes y avisa por alerta.
- Recarga del certificado TLS sin reiniciar (#769): el listener HTTPS con certificados propios (NETPULSE_TLS_CERT/KEY, p. ej. renovados por acme.sh/certbot) cargaba el par una sola vez al arrancar y la renovación solo se servía tras reiniciar el proceso. Ahora el servidor repuebla el par del disco cuando cambia (chequeo de mtime en cada handshake) y la renovación se sirve en el siguiente handshake sin reinicio. Si la renovación está a medio escribir, sigue sirviendo el último par válido y reintenta como mucho cada 30 s. El path on-box autofirmado no cambia: el agente fija el SPKI en el pairing.
- Alta guiada de dispositivos desconocidos (#772): la alerta "dispositivo desconocido" ahora notifica de verdad por los canales configurados (push/ntfy/Telegram, con router, banda y señal) y enlaza a una tarjeta de alta en Clientes: se asigna un nombre (que además silencia futuros avisos para esa MAC), se elige un icono y se puede reservar la IP con el plan de comandos antes de ejecutar; o dejar el dispositivo explícitamente como anónimo, que silencia el aviso sin nombrarlo. Cuando hay clientes conectados sin identificar, la página de Clientes lo anuncia con un banner.
- Timeline de presencia por cliente (#771): el detalle de cada cliente muestra las bandas de presencia de los últimos 7 días (una pista de 24 h por día, coloreada por AP) y el feed de eventos recientes, construidas a partir de los eventos de conexión/desconexión del sondeo y de los AP-STA-CONNECTED de hostapd: un salto a otro AP cierra el tramo y abre otro, así el roaming se ve como un cambio de color. La tarjeta "Roaming (24 h)" de Clientes rankea los equipos que más rebotan entre APs. La retención del historial es configurable (Ajustes → Datos, por defecto 30 días) con una poda horaria que acota ambas tablas de eventos, que crecían sin límite.
- La vista de la topología recuerda el zoom entre sesiones (#777): el nivel de escala (y el desplazamiento) elegido en el modo edición o con los botones/rueda se guarda y se restaura al volver a abrir la topología; "Restablecer vista" vuelve a la vista inicial.
- La lista de clientes recuerda la vista y el orden (#778): el modo lista/tarjetas y la columna y dirección de orden activos se guardan y se restauran al volver a abrir la sección; la búsqueda y los filtros siguen siendo de sesión.
- Fugas del topic en el canal ntfy (#773): auditoría de seguridad del canal (petición para reutilizarlo en NetGrip): el topic ya no aparece en el log de arranque ni en los errores de red (enmascarado), los reintentos solo cubren errores transitorios (5xx/429/red; los 4xx no reintentan), el mensaje se trunca por límite de runa UTF-8 sin cortar caracteres, el botón de prueba exige el canal activado, la publicación en vuelo se cancela al cerrar y el cierre es idempotente.
- El chip "Servicios" y el fabricante "Desconocido" se quedaban en español con la UI en inglés (#770): ambas cadenas venían del backend como etiquetas canónicas en español; ahora se traducen al idioma activo como el resto de etiquetas del health score. El chip además solapaba ligeramente la barra en anchos estrechos.
- Interfaces AmneziaWG invisibles en el panel WireGuard (#776): la detección automática solo consultaba la herramienta estándar
wg, que no ve las interfaces AmneziaWG (familia netlink propia, herramientaawg). Ahora el descubrimiento lista con ambas y el sondeo cae aawg showcuandowgno puede leer la interfaz; los routers WireGuard puros no se ven afectados (awgausente = "sin interfaces AmneziaWG"). - El uptime de la consola RTL fallaba con el nuevo formato de
time(#785): firmwares RTLPlayground recientes (KP-9000 v0.1.0-ad9e2ee) respondenTick counter: 0x… Sec Counter: 0x…en vez del contador hex único, y cada sondeo fallaba dejando firmware, uptime y MAC congelados en la UI tras el backoff. El parser acepta ambos formatos (el uptime es el Sec Counter).
- Avisos externos para todas las instancias: el servidor consulta
announcements.jsondel repositorio (raw de GitHub, refresco cada 6 h, fail-silent) y expone el aviso vigente en/api/announcement; la UI lo pinta con el mismo estilo que el aviso de actualización, con enlace opcional y descarte persistente por identificador. Primer aviso publicado: el programa de beta testers de NetGrip está abierto. Fuente configurable conNETPULSE_ANNOUNCEMENTS_URL.
- Previsualización de los comandos antes de escribir en el router (#754): reservar una IP o bloquear un dispositivo ya no aplica directo. Al confirmar la acción, NetPulse pide primero el plan al servidor (
?dry_run=1: construye los mismos comandos uci sin ejecutarlos) y lo muestra en la ficha del dispositivo -comandos que se ejecutarán y, plegables, los de rollback-, con el router identificado. Nada se aplica hasta pulsar "Ejecutar y aplicar"; desbloquear y eliminar reserva siguen siendo directos. Estilo LuCI "Unsaved Changes", pedido por la comunidad.
- Fuera las afirmaciones de "solo lectura" aplicadas al producto entero (#751): ya no son ciertas (reservas DHCP, bloqueo de dispositivos, orquestación, actualizaciones de firmware escriben en los routers). README, web (metas, JSON-LD, hero, FAQ, tour de funciones, footer, 10 idiomas) y manuales pasan a decir la verdad con el matiz correcto: el sondeo es de solo lectura; lo que escribe es siempre explícito, lo lanza un admin y va con snapshot y rollback. La alerta "firmware disponible" también mentía ("NetPulse es de solo lectura: usa LuCI") y ahora señala a Actualizaciones de firmware.
- Terminología de firmware unificada en inglés (#752): el menú lateral y el toggle de Laboratorio decían "Updates" mientras la página decía "Firmware upgrades"; todo el UI en inglés usa ahora "Firmware upgrades".
- "Comprobar actualizaciones" ya no dice que estás en la última versión cuando no pudo comprobar (#743): si el fetch a GitHub fallaba (límite de la API anónima, red, timeout), el botón devolvía el estado del último check exitoso -posiblemente de hace 24 h- sin ninguna señal, y la UI lo pintaba como "estás en la última versión". El reportero lo clavó: una release publicada dos horas antes no aparecía. Ahora el estado lleva
checkFailedcon el motivo (límite de GitHub sin token, sin conexión) y la UI muestra "No se pudo comprobar ahora (motivo); inténtalo de nuevo en unos minutos" en lugar de un falso todo-ok.
- El test de velocidad ya no mide basura cuando la URL del servidor apunta a la web de Ookla (#744): la tarjeta de ajustes traía
https://speedtest.netcomo valor por defecto, placeholder y botón "Restaurar" (no es un servidor de pruebas, es la web), y cualquier guardado escribía esa URL en la config: el resultado era una bajada capada, una subida ~0 y un servidor sin nombre. Ahora el campo nace vacío (autoselección), el botón pasa a "Automático", el servidor rechaza la trampa con un mensaje claro, un saneado al arranque limpia la URL trampa de instalaciones existentes, y una medición con bajada o subida <= 0 se descarta con error en vez de ensuciar la serie.
- El botón de test de velocidad vive en la tarjeta Tráfico WAN del Overview (#744): la franja de velocidad medida (último test, histórico de 7 días, % del plan y botón de ejecutar) pasa del detalle del gateway a la portada, donde ya vive el resto del tráfico.
- Lista de últimos tests (#744): en la tarjeta del test, un desplegable "Ver últimos tests" con las últimas mediciones (3/5/10, configurable): cuándo, origen (auto/manual), bajada, subida, ping, jitter y servidor.
- Programación con día y hora para el test (#744): además de cada N horas, se puede elegir semanal (día de la semana + hora) o mensual (día del mes + hora), con el vencimiento derivado del último resultado persistido (reinicios y actualizaciones no resetean el reloj). Las instalaciones con los antiguos "semanal/mensual" por horas (168/720) migran solas al selector nuevo.
- Los respaldos automáticos ahora se ejecutan de verdad (#741): la configuración (activado, frecuencia, retención) existía y se mostraba en la UI, pero nada en el servidor la leía para disparar el respaldo; el único camino que copiaba la base de datos era el botón manual, así que un respaldo inicial y nada más era el comportamiento esperado del código. Ahora un bucle periódico (patrón del scheduler de speedtest) decide en cada pasada si toca, con el vencimiento derivado SIEMPRE del
last_runpersistido: los reinicios y el auto-updater no resetean el reloj. El run manual y el programado comparten el mismo código con exclusión mutua.
- Hora fija para el respaldo diario (#741): campo opcional
Hora(HH:MM, hora local) en la tarjeta de Respaldos; vacío mantiene el comportamiento "cada N horas" y con hora fija el respaldo dispara una vez al día a esa hora (ventana de mantenimiento). ValidaciónHH:MMen el servidor. - Los routers con NetGrip se etiquetan como "NetGrip" en toda la flota (#746): el badge de agente mostraba el genérico "Agente" para todos, también para los que llevan el agente embebido de NetGrip. Ahora el badge (flota, tarjetas, tabla y detalle) muestra "NetGrip" con tooltip propio ("NetGrip v{versión} · agente embebido"); la tabla de agentes ya lo distinguía y el resto de la UI queda consistente.
- La vista de tarjetas de dispositivos aprovecha mejor las pantallas grandes (#711): el grid se quedaba en 3 columnas y dejaba mucho espacio sin usar en monitores anchos (el reporte veía solo un par de tarjetas a 1920x990). Ahora añade 4 columnas a partir de
xly 5 a partir de2xl, con un gap algo menor, para mostrar más dispositivos por pantalla sin cambiar el diseño de la tarjeta. - El intervalo del sondeo SSH del servidor pasa a ser configurable y sube de 5 s a 30 s (#715): el poller sondeara los routers por SSH cada
NETPULSE_POLL_INTERVALsegundos (entero > 0, 30 por defecto) en vez de cada 5 s fijos, que añadía una carga base visible en routers pequeños e inesperada para un monitor de solo lectura. El ajuste de UI "Intervalo de refresco" ya no aparenta controlar el sondeo del servidor: ahora gobierna de verdad la cadencia con la que la interfaz reconsulta los datos cuando pierde la conexión en tiempo real (3 s, 5 s, 10 s o pausado), y su etiqueta lo aclara.
-
Unidad de temperatura configurable (Celsius o Fahrenheit) (#717): las temperaturas se mostraban siempre en Celsius. Ahora Ajustes → "Datos y umbrales" permite elegir °C o °F, una preferencia de solo presentación que se aplica a tarjetas, tabla comparativa, detalle de router, gráfico de rendimiento, topología y temperatura SFP, así como a las descripciones de las alertas de temperatura en vivo (que ahora muestran el umbral efectivo del router). El servidor y el almacenamiento siguen en Celsius, y los umbrales por router se editan en Celsius con etiqueta explícita. Las notificaciones push y el feed de la demo conservan el Celsius.
-
El umbral de alerta por temperatura alta pasa a ser por router (#716): antes era un único valor fijo de 65 °C para todos, así que un router que corre más caliente obligaba a subir el umbral global y ocultaba problemas reales en los más frescos. Ahora cada router puede fijar el suyo (columna
temp_thresholdenrouters, vacío = 65 °C por defecto); la alerta de temperatura y el estado "hot" del dashboard lo usan. En la UI se configura en Ajustes → Dispositivos, y se retira el slider global de "Datos y umbrales" que no gobernaba la alerta real. -
El panel WireGuard y la topología ya ven los peers de todos los túneles, no solo
wg0(#713): el servidor sondeaba una única interfaz (WG_INTERFACE, por defectowg0), así que un gateway con varios túneles solo mostraba los peers dewg0. AhoraWG_INTERFACEadmiteauto(nuevo default) o vacío para descubrir y agregar los peers de todas las interfaces del router (wg show interfaces), y un valor explícito (una interfaz o una lista separada por comas) sigue actuando como filtro, conservando el comportamiento del caso de un solo túnel. -
Detección y check de
owutpara upgrades de OpenWrt, Fase 1 (#695): el servidor puede detectar si un router tiene disponibleowut(attended sysupgrade) y ejecutarowut checkpor SSH, exponiendo el resultado para revisión en la UI de Actualizaciones por router: si hay una actualización disponible y si la infraestructura ASU (sysupgrade.openwrt.org) puede construir la imagen. Es solo detección + check;owut download/owut installllegarán en una fase posterior con confirmación explícita. El flujo sysupgrade actual se mantiene intacto como fallback (owut requiere OpenWrt 24.10+; en 23.05 y anteriores no existe). -
HTTPS nativo opcional para el servidor (#696): con
NETPULSE_TLS_ENABLED=1el servidor escucha HTTPS en un puerto adicional (NETPULSE_TLS_PORT, por defecto 3443) sin tocar el HTTP dePORT, de modo que los agentes y el updater siguen hablando por HTTP y solo la UI/PWA pasa a usar HTTPS. Si configurasNETPULSE_TLS_CERTyNETPULSE_TLS_KEYse cargan esos certificados (HTTPS válido, necesario para instalar la PWA); si no, se genera un certificado autofirmado enDATA_DIRal arrancar para que HTTPS esté disponible sin configurar nada, asumiendo el aviso del navegador (un autofirmado no permite instalar la PWA). Sin ACME. -
El override manual
attachya puede colgar un dispositivo de un router o de un puerto concreto (#690): elparentsolo admitía la MAC de otro device, así que no había forma de anclar un equipo a un AP ni a una boca determinada cuando la inferencia no podía saberlo (NAT, VM sin OUI de hipervisor, cliente callado en un switch que reporta todas las MAC en una boca, recableados). Ahoraparentacepta tres formas: la MAC de un device (como hasta ahora, backwards-compatible), el id de un router, orouterId:puertopara fijar la boca exacta. En la UI el input libre de MAC se sustituye por un selector (routers, sus puertos y dispositivos) que conserva la edición de los overrides existentes conparent= MAC.
-
Los mensajes de rearm y de rotación de token explican ya el motivo real en routers sin agente ni SSH (#718, #719): al rearmar, la UI colapsaba cualquier error en un "No se pudo rearmar" genérico; ahora distingue entre sin agente registrado, sin acceso SSH, cooldown, router desconocido, dispositivo no OpenWrt, NetGrip y fallo SSH, con mensajes específicos en el idioma del usuario. Y al regenerar el token de un router sin agente (o gestionado por NetGrip), el aviso ya no dice "reinstala el agente": el servidor indica por qué no se pudo aplicar en caliente y la UI aclara que el token queda guardado y se aplicará cuando el agente se instale o se recupere.
-
El sondeo de WireGuard ya no se cae entero si falla una interfaz o el listado (#714): con el sondeo multi-interfaz (#713) un solo
wg show <iface> dumpque salía con status 1 (interfaz caída o inaccesible) abortaba toda la agregación y dejaba el panel WireGuard en "inactivo" permanente, logueando "no disponible" en cada tick. Ahora el fallo de una interfaz se salta y se agregan los peers de las que responden; siwg show interfacesno está soportado (o falla), se cae al default históricowg0; y si ninguna interfaz responde, el panel muestra el túnel inactivo sin loguear error en cada tick. -
El feed de alertas ya no muestra español para los tipos que faltaban por migrar (#712): los emisores que seguían generando literales sin
type/vars(recuperaciones de agente, router y puerto, acceso SSH perdido/recuperado, handshake de WireGuard, los eventos del beacon del switch, el diagnóstico SFP y el auto-rearme) llevan ahora su slug estable y sus variables, y el frontend traduce el título y la descripción en el idioma del usuario. Los literales se mantienen como respaldo y el dedup/silence (que usan el título) no cambian. -
Los clientes cableados y callados de un AP puente ya no vuelven a colgarse del router principal (#694): la clasificación de los puertos de subida (uplink) se calculaba cada tick solo con las MACs de bridge presentes en ese sondeo, así que un cliente callado cuya brMac ya no estaba en el FDB del gateway acababa atribuido al principal por dos vías transitorias (el FDB del gateway y el fallback ARP, que gana por orden alfabético). Ahora el servidor recuerda los puertos que alguna vez aprendieron la MAC de bridge de otro router (la clasificación de uplink persiste entre ticks y sobrevive a sondeos fallidos), el gateway no pisa la memoria de boca de un satélite en un puerto de uplink, y la memoria de puerto se refresca mientras el dispositivo siga online por ARP/lease aunque no hable por FDB. Además,
GetBridgeFdbdevuelvenilante un fallo de sonda para no sustituir la caché anti-parpadeo por un FDB vacío. -
Las notificaciones push de alertas se traducen al idioma del dispositivo (#689): el payload viajaba con los literales en español, así que un usuario con la app en inglés recibía los avisos en español. Ahora el server añade el slug del tipo y sus variables (
type/vars) y el service worker traduce con el catálogo de la app (alerts.types.*/alerts.hints.*) según el idioma del navegador, manteniendo los literales como fallback. -
Los nombres de la topología ya no se montan sobre los iconos (#692): los labels de los APs se dibujaban a los lados y chocaban con el abanico de clientes cableados (que sale al oeste) y con los chips wifi; los de los peers WireGuard se montaban sobre su propio icono y el del peer vecino. Ahora ambos van centrados bajo el nodo: el modelo ya excluye el ángulo del label al repartir los chips wifi, así que ese sector queda libre por construcción.
-
El resumen de alertas de la Home ya no muestra "ahora mismo" para todo (#698):
AlertItem(y el aviso de temperatura del detalle de router) pintaban el campotimelegacy, que el backend rellena con "ahora mismo" fijo en las alertas en vivo; una alerta de ayer seguía diciendo "ahora mismo". Ahora usanalertRelTime, que calcula el relativo real desdetsy solo cae al string legacy si falta el timestamp. -
El "Visto" de la flota de agentes ya no oscila (#691): el contador de "último push" solo se recalculaba en renders esporádicos (con la llegada de snapshots), así que se congelaba entre actualizaciones y saltaba hacia atrás con cada push del agente (oscilación tipo 39-44-18-23 s). Un único reloj compartido de 1 s alimenta ahora toda la tabla: la celda avanza segundo a segundo y solo cae cuando llega un push realmente nuevo.
-
El hostname de las reservas DHCP se valida y se escapa (#693): el
hostnamedel endpointPUT /api/devices/{mac}/reservationse interpolaba sin sanitizar en el comandouci setque corre por SSH como root en el router; una comilla simple rompía el quoting e inyectaba comandos arbitrarios. Ahora solo se aceptan hostnames DNS (letras, dígitos, guiones y puntos, hasta 253 caracteres; el resto recibe 400invalid_hostname) y todos los valores interpolados en comandos uci -incluidos los rollbacks que re-leen config del router- viajan escapados. La UI manda el hostname solo cuando el nombre del dispositivo es un hostname DNS válido.
/api/healthcuenta ahora los SSE de los agentes (#681):sseConnectionssolo reflejaba las pestañas del navegador; los agentes conectan por su propio hub y quedaban invisibles (con toda la flota empujando, el health leía 0-1). Se añadeagentSseConnections(el contador de UI conserva su significado).
- Los clientes callados de un AP puente ya no se cuelgan del router principal (#678): cuando un cliente cableado de un dumb AP deja de hablar, su entrada en el FDB del AP caduca y caía al fallback ARP, donde el gateway gana por orden alfabético. La memoria sticky de puerto ahora solo registra bocas locales (nunca el tránsito que el gateway ve por su uplink) y corrige también el router de un dispositivo ya online, sin fabricar presencia; la evidencia fresca de una boca local del gateway sigue mandando.
- Las curvas de los túneles WireGuard siguen a los nodos (#677): las dos primeras curvas peer→Internet estaban fijadas para la posición canónica de Internet, así que al reordenar el mapa (modo edición o el reposicionamiento automático) dejaban de aterrizar en el nodo y cruzaban lo que hubiera cerca. Ahora la curva se calcula siempre con las posiciones reales del peer y de Internet.
- El agente ya no se atasca con payloads viejos en el buffer (#680): tras un corte más largo que la ventana anti-replay, la cabeza de la cola no se podía entregar (401
stale_payload) y bloqueaba todo lo que venía detrás durante horas. El agente descarta los payloads caducados en el flush y se recupera en el primer ciclo con conectividad.
- Paquetes OpenWrt también para x86/64 (#672): el agente y el servidor on-router se publican ahora para x86/64 (
.ipk24.10 y.apk25.12), y la arquitectura del.ipkse deriva del SDK en vez de estar fijada; instalar los paquetes en un OpenWrt x86_64 dejaba de fallar con "Unsupported architecture".
-
El traductor del navegador ya no destroza los hostnames (#666): la página declaraba siempre
lang="es"aunque la UI estuviera en inglés, así que el navegador ofrecía traducirla y el traductor reescribía también los datos (p. ej. "crowed-pixeltab" → "crowded-pixeltab"). El documento declara ahora el idioma activo y los valores de red (hostnames, IPs) van marcadostranslate="no"para que queden intactos aunque la traducción esté activada. -
Las alertas se muestran en el idioma del usuario (#671): los textos del feed (título, descripción y sugerencia) los generaba el servidor en español y un usuario con la app en inglés los veía mezclados. Los eventos llevan ahora el slug de su tipo y sus variables, y el frontend los traduce por clave en el idioma activo (con los literales del servidor como respaldo). Migrados los 13 tipos con slug: dispositivo desconocido, router offline, agente caído (y su variante SSH), agente desactualizado, firmware, Internet caído, velocidad WAN lenta, temperatura alta, señal débil, port flapping, ghost port y enlace degradado. Las notificaciones push de las alertas urgentes siguen en español por ahora (seguimiento aparte).
- La topología se puede editar para ajustar las distancias (#656): un botón Editar (solo administrador) entra en modo edición que permite arrastrar routers/APs, switches o bridges inferidos y dispositivos para acercar o alejar los satélites (lo que cuelga de un nodo lo acompaña). Al guardar, las posiciones se persisten en el navegador y el layout queda congelado (ya no se reordena al refrescar); los nodos nuevos se colocan con el auto-layout. Un botón Restablecer vuelve al layout automático por defecto.
- Los hosts de Proxmox se muestran como nodos con sus CTs/VMs anidados (#561, #656): el sellado contra la API del cluster creaba los metadatos pero el mapa no anidaba los contenedores bajo su host. Ahora cada host PVE genera su nodo de hipervisor: los CTs se agrupan en grid bajo él con badge +N y enlaces host→CT, y el nodo se dibuja redondo (icono de servidor) con etiqueta "Proxmox · "; al arrastrarlo, sus CTs lo acompañan. La tarjeta de un dispositivo gana además un botón discreto "Etiquetar" (admin) que abre el panel de overrides con la MAC precargada, y persiste ~1,2 s al viajar hacia ella para dar tiempo a pulsarlo.
- Los hosts por cable de un punto de acceso puente (dumb AP) ya se atribuyen al AP, no al router principal (#656): cuando un nodo como un dumb AP (sin WAN,
br-lanpuentado a la LAN principal) tiene clientes conectados por cable a sus bocas, el router principal también los ve en su FDB porque comparten el mismo segmento L2. La reconciliación descartaba esas MACs del satélite y las colgaba del router principal. Ahora una MAC que el satélite aprende en una boca local que no es su uplink se atribuye a ese satélite (la boca física es la evidencia más específica); el uplink sigue excluido como tránsito y los routers con agente propio conservan su fuente específica. Además, el orden de los dispositivos es ahora determinista (por MAC), lo que evita que los iconos del mapa salten de posición al refrescar. - Los dispositivos callados ya no saltan del switch inferido al que están cableados (#656): las entradas del FDB caducan a los ~5 min sin tráfico, así que los equipos de AV (TV, receptor, shield…) perdían su evidencia de puerto entre refrescos y el mapa los re-anclaba al router principal hasta que volvían a hablar. El servidor recuerda ahora la última boca real donde se vio cada MAC (30 min de memoria, solo como respaldo del FDB actual) y conserva su atribución de puerto mientras el dispositivo siga conectado.
- El tráfico del tooltip de un dispositivo en la topología ya no muestra todos los decimales (#656): se formatea con la misma convención que el resto de la app (1 decimal a partir de 1 Mbps, 2 por debajo). El toggle "Flujo" del header desaparece: la animación sigue el ajuste "Reducir animaciones" (y el reduce-motion del sistema).
- Un router OpenWrt ya no cae en falso "host key changed" al sondearlo (#651): el descubrimiento (openssh con
accept-new) registraba enknown_hostssolo la host key que negociaba (ed25519), pero el sondeo Go negocia otra (ecdsa/rsa) con el mismo servidor; un host con varias host keys daba un falso "ssh key changed" y exigía un re-onboard sin motivo. Ahora el descubrimiento pinea todas las host keys del router (ed25519, ecdsa y rsa), de forma que el sondeo siempre encuentra la suya sin debilitar la protección anti-MITM: si el router cambia todas sus claves (reflash), la detección de "host key changed" sigue intacta.
- Los peers WireGuard se etiquetan por su nombre configurado en OpenWrt (#663): en la topología, los peers WireGuard que no estaban ya mapeados a un dispositivo NetPulse se muestran ahora con el nombre legible de la descripción UCI del peer (prioridad: nombre del dispositivo NetPulse → descripción UCI → IP del túnel), en lugar de la IP cruda del túnel. El sondeo lee
wg show dumpyuci show networken una sola sesión SSH (con marcador de separación) y conserva el estado de un túnel caído.
- Un router OpenWrt nativo ya no aparece como "Managed switch" en la tabla de dispositivos (#660): la columna Role distinguía solo gateway y "solo agente", y el resto caía en "Managed switch"; ahora se basa en el tipo del dispositivo y muestra "OpenWrt" para los routers nativos (agente SSH), dejando "Managed switch"/External para los sondeados por SNMP.
- El switch sondeado por SNMP ya no pierde el historial de tráfico ni los dispositivos conectados (#661): el path SNMP calculaba los contadores de bytes y la tasa por puerto pero nunca los persistía, así que la "historia de tráfico" del switch quedaba vacía aunque el sondeo leyera los datos (vía
recordPortSamples, la misma ruta que SSH/agente). Además, el FDB ya no descarta las MACs cuyo índice no resolvía a un puerto conocido (antes el switch mostraba 0 dispositivos pese a tener clientes) y se añade un fallback a la tabla Q-BRIDGE para los switches que solo la exponen. La tarjeta de la flota pinta ahora el tráfico agregado del switch en bps, y la gráfica de puertos de un switch SNMP usa bps (los beacons sin contadores de bytes siguen en fps).
- La gráfica de tráfico de los puertos muestra frames/s cuando el dispositivo no mide bytes (#641): los agentes beacon de switches RTLPlayground (KP-9000) solo reportan contadores acumulados de tramas, no bytes. El servidor deriva ahora frames/s de esos contadores en los tres niveles (raw, 5m, diario) y la tarjeta de Puertos del detalle los pinta como fps en lugar de bps para las fuentes sin contadores de bytes. Una gráfica de puerto de un switch gestionado deja de aparecer "sin datos" y muestra su tráfico real.
- El detalle y la flota muestran el desglose real de clientes por banda (#645): cada tarjeta de router (y el detalle) indica sus clientes online por banda (2.4/5/6 GHz y cable) calculado en el servidor con la misma fuente que el total. Solo se pintan las bandas con clientes, así un switch sin radios wifi ya no muestra bandas a 0.
- El servidor sondea la consola del switch RTLPlayground para firmware, uptime y MAC (#639): los agentes beacon de tipo RTLPlayground exponen ahora la versión de firmware real, el tiempo de actividad y la MAC del switch, leídos de su consola HTTP, y se muestran en la tarjeta del router.
- El tráfico de los puertos de un switch beacon ya no se ensucia con ceros (#642): el push del agente intercalaba muestras a 0 con las buenas del beacon. Ahora las fuentes externas solo persisten sus contadores reales, así las series de los puertos quedan limpias.
- El detalle de un switch gestionado ya no pinta sus puertos dos veces (#644): la tarjeta de chasis con LEDs de salud (front panel) duplicaba a la de Puertos Ethernet con las bocas RJ45. Se eliminó el front panel del detalle: la tarjeta de puertos ya muestra el estado, la salud y el historial por puerto.
- La tarjeta de un switch sin throughput en bps ya no muestra la línea de tráfico 24h plana (#648): para fuentes que solo reportan tramas por puerto, el mini-gráfico de tráfico de la tarjeta se deriva ahora de los frames/s de sus puertos en lugar de la tabla de bps (que siempre estaba vacía para ellos).
- El plan de canales no contaba la propia malla como interferencia ni sugería un bloque que cruzara a DFS (#631): los BSSID de los APs de la propia red ya no se puntúan como vecinos (se excluyen por prefijo MAC de los routers monitorizados), y los canales candidatos se validan contra el ancho de la radio: a 40/80/160 MHz solo se ofrecen bloques que no entran en canales DFS (52-64, 100-144). Evita recomendar un canal que ya usan los satélites de tu malla o un bloque de 80 MHz que cruce a banda DFS.
- El rearm ahora recupera un agente en 401 por token desincronizado (#630): si reiniciar el proceso no hace que el agente vuelva a empujar, NetPulse regenera el token y lo aplica en el propio router (reescribe el
.env+ reinicia) sin reinstalar. Además, la rotación de token en un reinstall es atómica: si el SSH falla, el servidor restaura el hash anterior, así el agente no se queda empujando un token que ya no se acepta (401 eterno).
- Botón de desinstalación del agente (#624): en routers con poco espacio libre (p. ej. UniFi 6 Lite) el
reinstallno cabía; ahora hay una acción "Desinstalar" que retira el agente limpio (detiene/deshabilita el init, borra binario,.env, watchdog y cron). - Rotación del token del agente en caliente: al regenerar el token de un dispositivo en Ajustes, NetPulse ahora lo aplica en el propio router por SSH (reescribe
/etc/netpulse-agent.envy reinicia el servicio), así el dispositivo sigue reportando sin reinstalar. El one-liner de instalación solo se necesita cuando el router no es alcanzable por SSH. - Autodetección de la imagen de firmware (#629): en Actualizaciones, NetPulse resuelve la imagen del router a partir de su propio firmware (
board_name+target+version) consultando el índice de descargas de OpenWrt y prerrellena la URL y el checksum, con un botón "Autodetectar imagen". Si no se puede resolver, se mantiene la entrada manual.
- Ajustes rediseñados a una columna (#627): la página de Ajustes pasa a una columna única con un índice fijo que te sigue al hacer scroll (scroll-spy); cada tarjeta (Apariencia, Datos y umbrales, Servicios, Red, Integraciones, Administración, Cuenta, Acerca de) ocupa el ancho completo y es más manejable. La tarjeta Apariencia muestra previews reales del tema (claro/oscuro/sistema) y la paleta en dos columnas; el test de velocidad WAN se ejecuta real desde el servidor y rellena descarga/subida en su sitio. Los toggles de Laboratorio se muestran individuales (Orquestación, Canales, Actualizaciones) al activar Labs.
- Los botones de copiar mostraban la clave i18n cruda (#628): los botones de copiar token/huella de la tarjeta de adopción usaban
common.copy, una clave inexistente, así que el tooltip mostraba literalmentecommon.copy. Añadida la clavecommon.copy("Copiar"/"Copy").
- Puerto SSH configurable por router (#605): varios routers exponen su dropbear en un puerto distinto de 22. Ahora cada router admite un
ssh_port(formulario de alta/edición en Ajustes) y todo el camino SSH del servidor (pool, sondeo, acciones, orquestación, rearm, instalación del agente y descubrimiento del gateway) usahost:puerto. Sin puerto configurado se sigue usando 22. - Matriz de itinerancia agrupada por dispositivo (#600): en WiFi Roaming → Matriz, las columnas se agrupan por punto de acceso (nombre) con una fila de cabecera que etiqueta cada banda (2.4G/5G), en lugar de una columna suelta por AP-banda.
- 802.11r: nombres y exclusión (#601): el título de la sección pasa de "Por router" a "Por AP", la columna "Router" a "Dispositivo", y los equipos sin radios (p. ej. el gateway) ya no aparecen en el detalle por router. Además, al añadir un router por descubrimiento ya no se usa el modelo de hardware como nombre (se usa el hostname), así la UI muestra nombres de dispositivo en lugar de modelos en toda la app.
- Análisis de canales: ancho del canal propio (#602): la fila de la red propia en la tabla muestra el ancho de canal (
· N MHz) junto al número de canal, cruzando con el channel-plan del router.
- La UI se quedaba en negro al conectarse un cliente WireGuard (#592): el servidor envía el tipo de peer como texto libre y usa
desconocidocuando no hay tipo mapeado; la tarjeta de WireGuard de la Home construía el icono conPEER_ICONS[peer.type]sin fallback, así que un peerdesconocido(o no mapeado) producía un iconoundefinedy React crasheaba ("Element type is invalid") en cuanto un cliente WireGuard se conectaba (o al abrir la UI desde él). Se añadedesconocidoa los mapas de iconos y un fallback.
- El agente degradaba el WiFi de los APs (#591): lanzaba un
iw dev scan(scan activo que saca la radio del canal) en cada sondeo completo, es decir cada ~30-37 s por dispositivo (medido en la tabla de scans). En puntos de acceso eso provoca desconexiones de dispositivos IoT y pérdida de señal. Ahora el scan se ejecuta como mucho cada 10 minutos, tras el arranque, o de inmediato cuando el servidor pide un refresh por SSE. La versión de agente embebida sube a 2.26.12 para que la flota se actualice. Se corrige además un test flaky de orden de mapa enTestParseUsteerque rompía el CI. - El re-onboard SSH auto-aceptaba claves cambiadas (#603): ante una clave de host distinta en un host conocido, el pool renovaba
known_hostsen silencio (accept-new con refresh), lo que anulaba la protección MITM. Ahora la conexión se rechaza y el router se marca comounreachable/hostKeyChanged; un admin debe confirmar el re-onboard explícitamente (nuevo endpointPOST /api/routers/{id}/accept-host-keyy botón en el detalle del router), que borra la entrada y vuelve a hacer TOFU en el siguiente sondeo. El tipo de clave registrado es el que presente el dropbear del router (ssh-rsa en nuestra flota OpenWrt), no se fuerza RSA.
- El total de RAM del detalle de router siempre salía como 512 MB (#590): el caption "X MB usados de Y MB" se calculaba con el resolver de extras de demo (cuyo fallback para cualquier router desconocido es la entrada demo con 512 MB) aunque se estuviera en live. Ahora usa el
ramMbreal que devuelve el backend (/api/routers/{id}).
- Panel de rendimiento con historia real en live: el detalle de router ya mostraba las series reales (1h/24h/7d de cpu/ram/temp agregadas por el server) pero el front las ignoraba y pintaba la serie sintética de demo (siempre "picaba" a las 21:00 y partía del valor actual con ruido). Ahora, en live, las tres curvas usan la historia real por rango; la demo conserva su serie canónica. Sin historia todavía (router recién dado de alta) se muestra un punto con el valor actual sin romper el panel.
- La tarjeta del gateway en "Dispositivos" se identifica por rol, no por id de demo (#597): antes detectaba el gateway con el id demo
flint2, así que en modo live la tarjeta nunca recibía el tratamiento de router principal. Ahora usaroleBadge Principal, igual que el detalle del router, y el subtítulo muestra elmodelreal del dispositivo (se elimina el literal "GL.iNet Flint 2 · GL-MT6000" tanto de la tarjeta como de la cabecera de detalle). Se elimina también la clave de traducciónmsToGateway(quedó sin uso tras quitar el footer en #593).
- Las tarjetas de "Dispositivos" marcaban a todos los routers como "Gateway principal" (#593): el footer de cada
FleetCardpintaba la etiqueta del gateway siempre que la latencia al gateway no estuviera disponible, y en modo live esos extras nunca llegan (solo la demo los rellena), así que la etiqueta salía en todas las tarjetas, incluidos APs y switches. Ahora la indicación es una pill verde "Puerta de enlace" junto al nombre, visible solo en la tarjeta del router conroleBadge Principal(el gateway real), y se elimina la fila del footer con el texto y el enlace "Ver detalle" (toda la tarjeta ya es clicable).
- Copy en español sin anglicismos: las cadenas visibles pasan de "gateway" a "puerta de enlace" (pill de tarjeta, "Estado de la puerta de enlace", enlaces y latencia en el detalle de AP, captions de AdGuard/reserva/aviso, copy de Orchestration y tooltip vía Internet). El texto en inglés no cambia.
- Colisión de nombres de los
.apken la release (#573, fix #586): se construía el.apkcon dos targets (qualcommax/ipq807xyarmsr/armv8) que generaban el mismo nombre de fichero (sin el arch), así que en el upload (--clobber) solo sobrevivía uno. Ahora se publica un único.apkaarch64_generic(targetarmsr/armv8), instalable en aarch64 genérico y en cortex-a53; el.ipkcortex-a53 se mantiene para opkg. (Nota: fix al workflow de empaquetado OpenWrt, sin cambio de código runtime.)
- Los instaladores no parseaban el tag de la release correctamente (#564):
install.sh,install-agent.shyinstall-collector.shresolvíanreleases/latestcon un corte posicional (cut -d'"' -f4); cuando GitHub devuelve el JSON minificado en una línea, el campo 4 es la URL de la release, no el tag, y la descarga apuntaba a una URL inválida. Ahora extraentag_namepor nombre (grep -oE). El instalador además informa de su propia versión. - El servidor no encontraba el fichero de leases de dnsmasq en ubicaciones no estándar (#568): el fichero se leía siempre de
/tmp/dhcp.leases; ahora se resuelve vía UCI (dhcp.@dnsmasq[0].leasefile) con fallback, y aplica al agente y al sondeo SSH. - El SSH al gateway fallaba por host-key (#567): el probe de discovery (ssh del sistema) y el pool (librería Go) negocian un algoritmo de host-key distinto y comparten
known_hosts; ante una clave conocida pero distinta el pool rechazaba (protección MITM) y dropbear veía "Exit before auth". Ahora re-onboarda con aviso (accept-new con refresh), acorde al modelo de confianza de una LAN self-hosted. - Se mostraba un timezone hardcodeado y firmware "actualizado" en falso (#566): la tarjeta de información del router mostraba
Europe/Madridy una pill "actualizado" antes de tener datos reales. Ahora usa el timezone del router si existe (si no,—) y oculta la pill de firmware sin datos reales.
- Apk genérico aarch64 (#573): se publica un
.apkcon archaarch64_generic(targetarmsr/armv8) además delaarch64_cortex-a53, para routers aarch64 fuera de cortex-a53 (antesapk addfallaba conuninstallable arch). - Elección honesta agente nativo vs NetGrip (#570): la sección de agentes explica ambas opciones (descripción corta, puerto 8090 de NetGrip, recomendación) para routers OpenWrt sin agente. NetPulse no instala NetGrip: la elección se hace al alta.
- Mensaje honesto de "sin tráfico por cliente" (#574): el detalle de dispositivo indica que los clientes por cable necesitan nlbwmon (los WiFi usan hostapd), y se documenta el caveat de RAM de nlbwmon.
- #569: rearmado de un agente NetGrip stale reiniciando el servicio netgrip (PR de la otra sesión, mergeada).
- El "copy command" para instalar un agente no era ejecutable (#571): la app generaba el one-liner con opciones separadas por espacio (
--binary /tmp/netpulse-agent ...), pero el instalador solo aceptaba--opt=value, así que abortaba en la primera opción conopción desconocida: --binaryy nunca llegaba al paso SSH. El instalador ahora acepta tanto--opt valuecomo--opt=value. - El instalador no usaba la llave SSH del servidor (#572):
install-agent.shhacíassh root@IPsin identidad explícita; si el router solo autoriza la llave que genera NetPulse (la que muestra Ajustes), el SSH fallaba aunquessh -i <llave> root@IPfuncionara. Ahora se acepta--ssh-key <ruta>, se reenvía con-ia todos losssh/scp, y el copy-command la incluye automáticamente.
- Identificación de hosts Proxmox y sus contenedores vía la API del cluster (#561): el detalle de Dispositivos y la topología ahora sellan correctamente los hosts de Proxmox VE como hipervisor y sus CTs/VMs como contenedores colgados de su host, con nombres e IPs reales. La heurística de red (FDB/OUI) no podía deducir la relación contenedor→host cuando un puerto del router mezcla dispositivos o los CTs usan MACs no estándar; la relación vive en el cluster. La integración es de solo lectura (token API con privilegios de auditoría,
PVEAuditor) y se configura en Ajustes > Proxmox VE (URL del cluster + token). Nota: en PVE 9.x los privilegios de un token no se editan desde la UI; se asignan por CLI (pvesh set /access/acl -path / -roles PVEAuditor -tokens "usuario@realm!token").
- CI: Node 24 (Active LTS Krypton) y
.nvmrc/enginesfijados (#542, #560): los workflows usan Node 24 y el repo declara la versión para reproducibilidad.
- Tráfico por cliente en vivo (#551): el detalle de un dispositivo ahora muestra su tráfico real (actual, total y gráfico por rangos 24 h / 7 d / 30 d) cuando la fuente del router lo reporta. Por router la fuente preferente es nlbwmon (cubre cableados y todo lo que enruta); si no está disponible se usan los contadores por estación de
ubus hostapd get_clients(bytes.rx/tx, clientes wifi asociados a ese AP), que el agente ya reporta. La store de ancho de banda por cliente (creada en el #337/#357) queda cableada de extremo a extremo: ingesta por deltas con rollups que calculan tasas reales (antes dejaban los bps a 0), APIGET /api/devices/{mac}/trafficy tasa actual por cliente en el overview. Nota: los clientes cableados del gateway no tendrán tráfico hasta que su pusher (NetGrip) emita la secciónclientBw; en un router con agente NetPulse y nlbwmon instalado queda cubierto todo.
- Análisis de canales rediseñado al estilo LuCI (#542, #545, #557): una gráfica por banda (2.4 GHz y 5 GHz) para que el hueco de frecuencia y el solape de etiquetas de canal desaparezcan; eje X con slots de canal uniformes por banda; el selector de router muestra todos los disponibles (primero los que escanean) y avisa cuando uno no reporta escaneos (NetGrip); se ignora el ruido por debajo de -90 dBm. Consolidado aquí al revisar el histórico entre v2.28.2 y main.
- Análisis de canales: eje X con todos los canales de la banda y red propia visible (#542, #554): la gráfica muestra cada canal de la banda activa (2.4 GHz: 1-13; 5 GHz: UNII-1..4) aunque esté vacío, como el análisis de LuCI; la red gestionada se dibuja como una montaña violeta en su canal en uso a -25 dBm (no aparece en los escaneos externos); y se muestra una sola banda a la vez (selector 2.4 GHz / 5 GHz, sin "Todos").
- La reserva DHCP se escribe en el router que sirve el lease, no siempre en el gateway (#537): las acciones de reserva (GET/PUT/DELETE /api/devices/{mac}/reservation) apuntaban siempre al gateway global. En una LAN separada cuyo DHCP lo concede otro router (p. ej. un AP/lab con su propio dnsmasq, como rt-lab en 192.168.2.x) la reserva acababa en el gateway equivocado. Ahora el servidor resuelve el router que reportó el lease de esa MAC (el dnsmasq que se la concedió) y escribe allí; si no hay lease conocido cae al gateway, como antes. El bloqueo de dispositivo ya apuntaba al router de atache.
- El sondeo SSH ya no satura APs de bajas prestaciones (#550): el servidor, cuando sondea un router por SSH (sin agente), seguía usando el par de comandos previo al #373: clientes por
iwinfo assoclisty resumen de radios poriwinfoen cada tick de 5 s. En un AP de un solo núcleo (p. ej. Archer A7 v5) eso disparaba la carga. Ahora el sondeo SSH replica el fix que el agente tiene desde #373: clientes víaubus hostapd get_clients(autoritativo incluso con un AP vacío, sin caer a iwinfo), iwinfo solo en una pasada combinada como fallback, y el resumen de radios cacheado 5 minutos en vez de refrescarse en cada poll.
Nota de versionado: las releases 2.26.13 y 2.26.14 no llegaron a publicarse como releases de GitHub (un symlink
app/node_modulescommiteado por error rompía goreleaser con "dirty state"). Su código está en main y se consolida aquí, en la v2.28.0.
- Upgrades de firmware programados y desatendidos (#494): además de lanzar un upgrade al momento, ahora se puede programar para una hora concreta (p. ej. de madrugada). El router muestra "Programada para " con opción de cancelar; el servidor la dispara solo cuando toca, reutilizando exactamente el mismo flujo validado del manual (validación de imagen ASU, backup pre-upgrade y flash, todo en el agente). El flujo manual no cambia.
- Descubrimiento de dispositivos cableados por tabla ARP (#507): los hosts con IP estática (sin lease DHCP) que no se ven por WiFi ni por FDB ahora aparecen como dispositivos cableados mientras el router los tiene en su tabla ARP (el agente ya la recogía; antes solo servía para rellenar la IP de dispositivos conocidos). Solo IPv4 por ahora; aparecen mientras están activos y desaparecen al envejecer la entrada (sin persistencia, lista sin huérfanos).
- Manual de usuario por área y menú (#418):
docs/manual.es.mdydocs/manual.en.md, una sección por pantalla de la app con qué muestra, qué datos necesita, cómo leerla, las decisiones de diseño relevantes y procedimientos paso a paso, enlazados desde el README.
- El apagado se colgaba hasta TimeoutStopSec cuando un sondeo SNMP/SSH lento bloqueaba el tick del poller (#481):
Stop()esperaba indefinidamente al tick en curso. Ahora espera como mucho 15 s (loggea si se rinde) y el proceso continúa el cierre. - Acciones de dispositivo (reserva IP + bloqueo) endurecidas (#439): resolución del router objetivo fiable (basada en configuración), las reservas detectan conflictos de IP (409) y son idempotentes, el bloqueo usa una regla de firewall con nombre determinista y las escrituras son transaccionales con rollback si el reinicio del servicio falla. Validado en producción y documentado en el manual.
- Release pipeline: se elimina el symlink
app/node_modulesdel repositorio (rompía goreleaser en cada tag) y se ignora explícitamente.
- Acciones de dispositivo (reserva IP + bloqueo) endurecidas y con tests (#439): la funcionalidad (reservar IP estática en el DHCP del gateway y bloquear/desbloquear el acceso de un dispositivo por MAC desde su ficha de edición) se ha endurecido: la resolución del router objetivo es ahora fiable (basada en la configuración, no en heurísticas); las reservas detectan y rechazan conflictos de IP (409) y son idempotentes; el bloqueo usa una regla de firewall con nombre determinista; y todas las escrituras son transaccionales con
uci commitcomo límite atómico y rollback si el reinicio del servicio (dnsmasq/firewall) falla - nunca se deja una configuración a medias. 23 tests nuevos con SSH simulado.
- Upgrades de firmware programados y desatendidos (#494): además de lanzar un upgrade al momento, ahora se puede programar para una hora concreta (p. ej. de madrugada). El router muestra "Programada para " con opción de cancelar; el servidor la dispara solo cuando toca, reutilizando exactamente el mismo flujo validado del manual (validación de imagen ASU, backup pre-upgrade y flash, todo en el agente). El flujo manual no cambia.
- Descubrimiento de dispositivos cableados por tabla ARP (#507): los hosts con IP estática (sin lease DHCP) que no se ven por WiFi ni por FDB ahora aparecen como dispositivos cableados mientras el router los tiene en su tabla ARP (el agente ya la recogía; antes solo servía para rellenar la IP de dispositivos conocidos). Solo IPv4 por ahora; aparecen mientras están activos y desaparecen al envejecer la entrada (decisión: sin persistencia, lista sin huérfanos).
- El apagado se colgaba hasta TimeoutStopSec cuando un sondeo SNMP/SSH lento bloqueaba el tick del poller (#481):
Stop()esperaba indefinidamente al tick en curso. Ahora espera como mucho 15 s (loggea si se rinde) y el proceso continúa el cierre; el tick ya termina solo por los timeouts del cliente SNMP.
- El plan de canales mostraba un score absurdo (9223372036854775807) o nada (#518): cuando el canal actual del router era DFS (52/100/112/116...), no estaba en la lista de canales candidatos (solo no-DFS) y el score del canal actual quedaba sin calcular, pintando el valor máximo de un entero de 64 bits. Ahora el score del canal actual se calcula siempre (es informativo), aunque el canal no sea recomendable. Además, el mensaje de "sin escaneos" explica ahora que los routers con NetGrip no escanean vecinos y orienta a seleccionar un router con el agente NetPulse.
- El aviso de un intento de actualización de firmware fallido quedaba para siempre y no se podía cerrar (#519): un fallo antiguo (p. ej. "agent not connected" de un incidente pasado) seguía mostrándose aunque el router ya estuviera sano y en la versión objetivo. Nuevo botón "Descartar aviso" en la tarjeta (endpoint
DELETE /api/firmware-upgrades/{routerId}/failure, que solo borra intentos terminados; un upgrade en curso nunca se toca). De paso, se arregló un fallo latente por el que los upgrades en curso no aparecían en la lista (el escaneo de columnas NULL fallaba en silencio).
- Test de velocidad WAN periódico (#511): medición real de bajada, subida, latencia y jitter (pérdida de paquetes donde el servidor la aporta) contra speedtest.net desde el host del servidor, con programación configurable (6/12/24 h o ejecución manual), historial en SQLite con retención anual, porcentaje frente al plan contratado (#151), gráfica de los últimos 7 días y botón "Ejecutar test" con progreso en vivo en la tarjeta WAN. Alerta con silencio hasta recuperación cuando la bajada medida queda por debajo de un porcentaje configurable del plan. Configuración en Ajustes, Datos y umbrales.
- Falso "la actualización falló" en cada actualización con reinicio (#512):
update.shescribe el marcador de éxito con el SHA completo (40 caracteres,git rev-parse HEAD) mientras el comprobador lo comparaba literalmente con el objetivo corto (7 caracteres), así que el reinicio esperado del script (systemd mata su cgroup al aplicar) se registraba siempre comoupdate_exit_-1aunque el binario nuevo arrancara bien. La comparación pasa a ser por prefijo y el marcador se trata como la verdad del terreno: si el swap se hizo y pidió reinicio, es éxito; si main avanzó entre la comprobación y la aplicación, se rectifican el historial y el marcador de confirmación post-arranque al SHA realmente instalado. - El diálogo de actualización explica los errores en lenguaje humano (#512): códigos como
update_exit_-1se traducen a un texto claro (castellano e inglés); el código técnico queda como detalle de depuración. - El porcentaje de RAM del router sobreestimaba el consumo (#513): se calculaba con
(total - MemAvailable) / totalyMemAvailablees pesimista en sistemas de ficheros ubifs/overlay (cuenta poca caché como reclamable a corto plazo), de modo que un punto de acceso descargado podía marcar en torno al 80%. El agente nativo lee ahora/proc/meminfopara enviar la caché de ficheros, y el servidor usa la fórmula clásica que no la cuenta como consumida (paridad con LuCI/htop). El ajuste llega a los routers al actualizar el agente de la flota.
- Descubrimiento de clientes cableados tras puertos con nombres no estándar (#506): el agente filtraba las MACs aprendidas en el FDB del bridge con una lista cerrada de nombres de puerto (
lan1,wan,eth0...), pensada para excluir los puertos wireless; cualquier puerto físico fuera de esa lista se descartaba en silencio. En el Banana Pi BPI-R4 las jaulas SFP se llamansfp-lan/sfp-wan(naming de OpenWrt 25.12+), así que los dispositivos cableados tras el SFP-LAN (p. ej. detrás de un switch no gestionado, con IP estática y sin lease DHCP) jamás aparecían como clientes. El filtro pasa a ser una lista de exclusión: vale cualquier puerto miembro del bridge salvo wireless (phy*-ap*,wlan*), interfaces virtuales (bridges, túneles, veth, wg, ppp...) y subinterfaces VLAN. - La vía
bridge fdb showfiltraba mal las entradas locales (#506): cuando el router no tienebrctl, el fallback dejaba pasar las entradas locales del bridge (su propia MAC registrada en cada puerto, flagpermanent); el servidor interpretaba esa MAC propia como un uplink y descartaba TODOS los clientes cableados de ese puerto. Ahora se excluyen las entradaspermanent, en paridad con la vía brctl. - AGENT_VERSION alineada con la release (2.26.10): los agentes embebidos en el servidor reportaban 0.2.0, menor que los agentes de la flota instalados desde tarball (2.26.x), por lo que nunca se ofrecía su actualización; a partir de aquí el agente embebido ofrece update a toda la flota anterior.
- La sección de ayuda ocupa el mismo ancho y tamaño que el resto de la app (#503): se limitaba a sí misma a 768px y usaba letra pequeña (13px) en pasos, intros y pies; ahora sigue el contenedor estándar (1160px a 1440px de viewport, igual que Inicio) y el cuerpo sube al text-sm de la aplicación.
- Aplicar el canal recomendado desde el plan de canales (#500): el agente reporta la sección UCI de cada radio (radio0/radio1) y la página del plan deja de ser informativa: botón de aplicar por radio con confirmación (canal actual a objetivo y aviso del reinicio de red), progreso y resultado en vivo, y vuelta al canal anterior con un clic en la misma sesión. Los planes viajan por el motor existente (diff explícito: uci_set + commit + reinicio de red), con delegación al executor de NetGrip o SSE al agente. Traducción completa ES/EN de la página (cabecera "Señal", badge de puerta de enlace) y puntuación actual/mejor por radio como contexto.
- POST /api/plans con diff explícito respondía 201 sin body: el plan releído de BD llevaba un desired vacío (json.RawMessage("")) cuyo marshal falla y el error se silenciaba; los clientes JSON recibían EOF. El desired vacío se queda nil y writeJSON loguea los errores de codificación.
- El reporte del apply moría al reiniciar la red del propio router: aplicar ops como "service network restart" cortaba el enlace del agente justo cuando tocaba reportar el resultado y el plan quedaba "applying" eterno; ahora el agente reintenta el apply-result con backoff durante varios minutos.
- Sección de ayuda dentro de la app (#484): nueva página de guía rápida con recorridos guiados para los cinco flujos que un admin necesita el primer día: primer arranque y contraseña, alta de un router, instalación del agente (el botón de un clic primero y el one-liner con token de pairing como alternativa manual), lectura del plan de canales y upgrade de firmware. Los pasos citan las etiquetas reales de la UI renderizadas como chips y escritas en los mismos ficheros de i18n que la interfaz, de modo que la ayuda deriva junto con ella; sin capturas bitmap, que envejecen mal. Paridad completa ES/EN, ítem de navegación y entrada en la paleta de comandos.
- CSP permite estilos inline para Radix y framer-motion (#485): cada apertura de un diálogo (paleta de comandos incluida) logueaba una violación de Content Security Policy y las transiciones de entrada/salida no se ejecutaban. Radix (posicionado) y framer-motion (transform/opacity) escriben atributos style con valores dinámicos imposibles de hashear, así que
style-srcgana'unsafe-inline';script-srcsigue estricto ('self') y la app no renderiza HTML controlado por el usuario.
- Página de actualizaciones de firmware más segura (#477, con #494 aplazado): la lista solo incluye routers OpenWrt/GL.iNet con agente (los switches gestionados y los dispositivos solo-beacon desaparecen de la página), el formulario se prefiere con el modelo y target detectados por el propio agente y arrancar un upgrade exige confirmar el objetivo (versión actual a objetivo, URL de la imagen, verificación sha256 y aviso de reinicio). Los errores de upgrade antiguos de la tarjeta llevan marca de tiempo relativa para que un fallo viejo no se lea como agente caído. Las actualizaciones programadas quedan como seguimiento (#494).
- Vecinos LLDP en el payload del agente (#489): el agente sondea
lldpclien su ciclo completo e incluye los vecinos (puerto, chasis, IP de gestión, capacidades, puerto remoto) en el push. Hasta ahora la recolección LLDP solo existía en la vía SSH del server, muerta para routers sondeados por agente (el camino recomendado de instalación), de modo que la topología nunca promovía switches gestionados ni identificaba vecinos en esas flotas. El contrato es explícito:available:falsecuando lldpd no está instalado (alimenta el hint de la UI), vecinos ausentes si la sonda falla esa ronda (anti-parpadeo como las vitales) y lista vacía honesta cuando no hay vecinos. El parser delldpclipasa a vivir enagent/probecompartido por ambas vías. Nota de red: un switch conforme 802.1D filtra el multicast LLDP, así que los vecinos solo aparecen entre equipos directamente cableados o a través de switches no gestionados.
- Agente para dispositivos MIPS (#488):
netpulse-agentse compila ahora paramipsle(little-endian: MT7621 y el resto de ramips) ymips(big-endian: ath79 y demás targets BE), ambos soft-float porque estos SoC no llevan FPU. Llega a los tres caminos: tarballs de release, binarios embebidos en el server (el botón Instalar agente y el reintegro POST-flash sirven el binario correcto) y el instalador de una línea. Comouname -mdevuelve "mips" para ambos endianness, la detección lee el byte EI_DATA del ELF del sistema (truco busybox-safe sin depender deod -t). Los paquetes .apk/.ipk del agente siguen siendo aarch64-only de momento; el one-liner y el tarball cubren MIPS.
- Changelog humano en el asistente de actualización (#490): cuando hay una actualización disponible, el updater consulta el compare de GitHub entre tu versión y la última, y el asistente muestra la lista de commits que entran (sha corto + asunto, lo más reciente primero), cuántos cambios son y un enlace a la comparación completa. Funciona en ambos modos (rolling por SHA, estable por tag), cachea el resultado por par de versiones para no repetir la consulta en cada chequeo de 24 h y degrada sin romper nada si el compare falla: vuelve al cuerpo del commit o a las notas del release, como antes. Las notas de release autogeneradas con un único commit dejan de ser lo único que se ve al actualizar.
- Instalación del agente en un clic desde la tabla de agentes (#483): todo router OpenWrt tiene ahora fila en la página Routers aunque aún no tenga agente, con botón Instalar agente que lo registra (crea su token al vuelo) y lo despliega por SSH (binario, config, servicio y watchdog), igual que ya hacía Reinstalar. El listado de agentes expone el hostname del board para no duplicar filas en routers legacy cuyo id de overview difiere del de tabla. El README deja de señalar el menú "Ajustes → Agentes" que no existía.
- Self-update estable del updater (#482) y escalado del supervisor: un rearme que no recupera el agente termina en reinstalación completa (#476).
- Plan de canales WiFi de punta a punta (#475): cuatro defectos apilados. El server no cableaba el store (la ruta nunca se registraba:
not_foundy scans descartados); el parser del agente leíafreq: 5260.0como entero y tiraba todos los BSS; cada push reinsertaba los mismos vecinos y el score desbordaba (recomendaciones con MaxInt64) hasta que la lectura deduplica por BSSID; y la página mandaba el id del overview (flint2) mientras los datos viven bajo el slug (gateway). Además,wifi_scanslleva purga (48 h) y la UI trataradios/scansnulos sin romperse. - Updater: el kill por reinicio diferido cuenta como éxito (#474) y el asset
go-latestse construye en cada commit de main, cerrando el falso "update_exit_-1". - Auditación de Dependabot: las 4 alertas high eran
fast-uri; cerradas con su bump (#460).
- Cambio de la propia contraseña (#465): el formulario de Ajustes llamaba a
PUT /api/auth/password, un endpoint que no existía en el servidor, así que el cambio nunca funcionaba. Ahora está implementado: verifica la contraseña actual, aplica la política 10..128, invalida el resto de sesiones del usuario y conserva la sesión que pide el cambio. El formulario alinea el mínimo a 10 y lo indica. - Botones de copiar sobre HTTP sin HTTPS (#466):
navigator.clipboardno existe en orígenes no seguros (NetPulse se suele servir porhttp://<ip>:3000), así que copiar la clave SSH no hacía nada y el error se tragaba. Helper compartido con fallback a textarea +execCommand('copy')para todos los botones de copia, con aviso visible si ambas vías fallan. - El desinstalador dejaba el grupo
netpulse(#467): la reinstalación moría conuseradd: group netpulse exists. Los instaladores reutilizan el grupo si ya existe (-g) y los desinstaladores lo borran (groupdel), en el server y el collector. - El .apk de luci-app-netpulse era un ipk renombrado (#468): apk-tools v3 en 24.12+/25.x lo rechazaba con "v2 package format error". El SDK 25.12 genera ahora un paquete v3 firmado real (mismo patrón source-less que el agente); verificado en un router 25.12.5. El asset pasa a llamarse
luci-app-netpulse-<versión>-r1.apky el README refleja el patrón nuevo. - Las releases desde v2.25.0 no publicaban ningún asset (#469):
release.ymlcorría los tests Go antes de compilar los binarios de agente embebidos y moría antes de goreleaser (el mismo desorden que #458 arregló en go.yml), así que no había tarballs ni paquetes y los instaladores fallaban. Además, el job package-server deopenwrt-package.ymlleía el tag de un contexto vacío. Con este tag vuelven los tarballs, checksums y paquetes OpenWrt.
- Apply seguro de configuración con rollback y ownership UCI (#451): el orquestador aplica cambios UCI marcando propiedad por integración; si el health check post-apply falla, revierte automáticamente a la configuración previa.
- Planificación de canales WiFi (#452): nuevo panel de channel planning con escaneos pasivos de vecinos (agente) y recomendaciones de canal por radio; endpoints de channel plan e inventario de radios.
- Actualización de firmware de routers OpenWrt (#453): el servidor define un target de firmware por router (URL + SHA256) y envía el comando al agente vía SSE; el agente descarga y verifica la imagen, hace backup de la config con
sysupgrade -b, flashea y reporta el resultado tras el reinicio mediante un fichero pendiente. UI con progreso y resultado. Validado E2E en un router real (imagen oficial ASU).
- Flujo del upgrade de firmware (#453): el comando SSE viaja como objeto JSON (antes base64 y el agente no podía parsearlo);
finished_atnullable en el store; inyección del reloj enNewLive(panic enpollRouter); los endpointsfirmware-progressyfirmware-resultaceptan Bearer del agente. - Watchdog del agente reiniciaba cada 2 min:
pgrep -xde BusyBox compara la línea de comando completa y nunca matcheaba/usr/sbin/netpulse-agent; sustituido porpidof. - CI go-server roto desde v2.25.0 (#458): los binarios de agente se construyen ahora antes de los tests (
TestOpenArmVariantslos abre) y el binario arm se llamanetpulse-agent-arm, el nombre que resuelvenormalizeArch(antesarmv7era imposible de servir vía/api/agents/{slug}/binary).
- Acciones de router en la tabla y ocultar vitales inexistentes (#445): la tabla de routers añade acciones para abrir la web UI genérica y NetGrip; los routers sin métricas de sistema (SNMP/switches/beacon) ya no muestran CPU/RAM/temperatura en 0; las vitales anteriores se reciclan entre pushes parciales del agente.
- Timeline compacta de actualización de agentes (#446): el panel de agentes muestra el progreso del upgrade en una sola línea por agente, con desaparición rápida tras completarse (10 s) o fallar (60 s), evitando que rompa la tabla.
- Botón "Actualizar agente" persistente tras upgrade (#447):
vercmpahora compara también el build del sufijo-N(CmpBuild), de modo que el frontend y el ciclo de upgrade usan el mismo criterio. Un agente con build más reciente ya no se considera desactualizado. - Upgrade de agentes OpenWrt fallaba con 404 (#449):
agentbin.Opennormalizaarmv7/armv7l/armhfaarm,aarch64aarm64yx86_64aamd64, coincidiendo con los nombres de los binarios embebidos.
- Asesor de re-anclaje WiFi (#403): nuevo panel "Reanclar" en
/roamingque lista clientes conectados a un AP subóptimo y sugiere un AP mejor según el hearing map. Soporta usteer (preferido) y DAWN como fallback. Incluye endpoints/api/wifi-reanchor/recommendationsy/api/wifi-reanchor/{mac}/move, y ejecutadel_clientconban_timeal aprobar el movimiento.
- Intervalo de polling SNMP configurable por switch (#414): los routers con SNMP activado ahora usan un
snmp_poll_intervalconfigurable (10-3600 s, default 60) en lugar de sondear en cada tick de 5 s. Se expone en el formulario de edicion del router (Ajustes > Routers) y enPUT /api/config/routers/:id.
- Falsos positivos ghost-port en switches SNMP (#414): el
PortMonitoraplica una histeresis temporal de 5 minutos de silencio real antes de declarar ghost-port en puertos procedentes de SNMP. Ademas, cuando un router SNMP se cachea entre polls, no se re-observa el mismo snapshot, evitando que el contador de polls sin trafico crezca artificialmente. - Métricas duplicadas para routers SNMP (#414): el poller ya no inserta filas repetidas en
metricscuando reutiliza el snapshot cacheado de un switch SNMP.
- El updater ahora protege claves SSH, known_hosts y base de datos durante las actualizaciones (#425):
deploy/update.shhace copia de seguridad dedata/.ssh/y de la BD antes degit reset --hard; restaura.ssh/si git lo elimino; conserva el binario anterior como.prevhasta pasar un healthcheck post-reinicio; si el servidor no ve routers/agentes, aplica rollback al binario, claves y BD anteriores y reinicia. Ademas,sshkey.EnsureKeypairrestaura el ultimo backup disponible si el par de claves desaparece, evitando regeneraciones accidentales. - Falso positivo de ghost port tras reinicio del servidor (#405, #406):
PortMonitorinicializa el estado de un puerto desconocido con el trafico observado en la primera muestra, por lo que un contador constante > 0 tras un reinicio ya no se interpreta como "se callo".
- Aviso cuando aun se usa DAWN para roaming (#426, #427): la vista
/roamingmuestra una advertencia si algun router sigue reportando DAWN como daemon de roaming activo, ayudando a completar la migracion a usteer. - Deteccion del daemon de roaming activo y anomalias de configuracion (#428, #429): el backend detecta si cada router usa
none,dawn,usteero ambos, y senala inconsistencias en la configuracion 802.11r (sin mobility domain, sin R0/R1 key, SSID sin roaming, o mobility domain distinta por SSID). La UI presenta estas anomalias como tarjetas de aviso en/roaming.
- Migracion del daemon de roaming de DAWN a usteer (#410): el backend, agente y frontend ahora usan usteer, el daemon oficial de OpenWrt para client steering. Reemplaza las sondas DAWN por
local_info,remote_infoyconnected_clientsde usteer; actualiza las vistas/roamingy/orchestrationy los textos i18n ES/EN; renombraDawnPanelaUsteerPanely elimina helpers y tests de DAWN obsoletos.
- Regresion: modo glinet de AdGuard rechazaba host vacio (#423): tras el fix de #421,
PUT /api/config/adguardexigia un host no vacio tambien en modoglinet, lo que impedia guardar la configuracion por defecto (router gestionado por SSH sin host remoto). Ahoraglinetacepta host vacio y devuelveport: 0.
- Migracion del daemon de roaming de DAWN a usteer (#410): el backend, agente y frontend ahora usan usteer, el daemon oficial de OpenWrt para client steering. Reemplaza las sondas DAWN por
local_info,remote_infoyconnected_clientsde usteer; actualiza las vistas/roamingy/orchestrationy los textos i18n ES/EN; renombraDawnPanelaUsteerPanely elimina helpers y tests de DAWN obsoletos.
- Puerto de AdGuard Home remoto vuelve a 3000 tras guardar (#420):
PUT /api/config/adguardignoraba el campoportdel JSON y solo extraía puerto de la cadenahost. Como el modo standard envía host y puerto por separado, cualquier guardado terminaba usando 3000. Ahora el puerto explícito tiene prioridad y se persiste correctamente. - Clientes sin dirección IP en el panel (#421): cuando un dispositivo no aparecía en las leases DHCP del router sondeado (porque el dnsmasq vive en otro equipo), la IP quedaba vacía. El agente ahora incluye la tabla ARP (
/proc/net/arp) ybuildDevicesla usa como último fallback tras leases ygl-clients. - Mensajes confusos al expulsar cliente en roaming (#411):
KickUsteerClientahora recorre todos los routers y continua si undel_clientfalla, en lugar de devolver el error crudo de SSH en el primer intento. Tambien corrige la comparacion de MAC, ya que usteer devuelve las MAC en minusculas enconnected_clients. El mensaje de error ahora escould not disconnect from <router>en vez deProcess exited with status 4.
- Interruptor para desactivar alertas de ghost port (#419): nueva variable de entorno
GHOST_PORT_ENABLED(default0). Cuando esta desactivada, elPortMonitorno genera alertas de puerto fantasma ni de recuperacion, eliminando los falsos positivos actuales mientras se afinan los agentes.
- Agente no repita contadores de /proc/net/dev congelados (#408): cuando
CmdNetDevfalla o tarda en un ciclo, el prober enviaba la muestra anterior comoNetIf, con un timestamp nuevo. El servidor calculaba deltas de 0 bps y disparaba alertas Ghost port en el único puerto activo. AhoraNetIfsolo se incluye en el payload cuando el ciclo actual refresca los contadores.
- Alerta de agente desactualizado (#400): nueva alerta consolidada
alert-agent-outdated-<routerID>cuando un agente (native o netgrip) está por debajo de la última versión disponible. Se recupera automáticamente cuando el agente se actualiza.
- Cierre del ciclo de upgrade (#401): el tracker de upgrades ahora cierra el paso
donecuando el agente pushea la versión objetivo (antes quedaba enrestartinghasta el TTL de 3 minutos). La UI refleja el estadodoneinmediatamente.
- Módulo de orquestación DAWN (#402): nueva tarjeta "DAWN" en
/orchestrationpara desplegar y alinear la configuración de roaming (802.11k/v/r, mobility domain, shared key, broadcast IP, random_bssid=0) en los APs. El backend probea el estado actual vía SSH, genera un plan declarativo con las ops necesarias, y verifica la malla DAWN tras el apply (ubus call dawn get_network). Incluye rollback y i18n ES/EN.
- Rollback de planes delegado a NetGrip (#397):
POST /api/plans/{id}/rollbackahora delega las ops inversas al executor de NetGrip cuando el router tiene executor token (mismo criterio que el apply), asentando el plan comorolled_backvía la máquina de estados existente. El fallback al agente embebido por SSE se mantiene.
- Delegación de planes a NetGrip (#339): si un router tiene un executor token de NetGrip (enviado con el snapshot en #340),
POST /api/plans/{id}/applydelega la ejecución de las ops a NetGrip víaPOST /api/executor/apply. Si NetGrip no responde, se mantiene el fallback al agente embebido por SSE. - Backups centralizados de snapshots UCI (#340): el endpoint
POST /api/config-backupahora recibe snapshots gzip desde NetGrip autenticados con el token de agente. Los endpoints de lectura/borrado (GET /api/config-backup,GET /api/config-backup/{id},DELETE /api/config-backup/{id}) requieren rol admin.
- Texto seleccionable en el feed de alertas (#393): las filas del feed de
/alertsrenderizaban todo su contenido dentro de un<button>, y los navegadores desactivan la selección de texto dentro de los botones. La fila pasa a ser undiv role="button"(Enter/Espacio siguen funcionando) con un guard por movimiento de puntero: arrastrar para seleccionar no despliega la alerta, un click normal sí. Corrige además el anidado inválido de botones (silenciar dentro de la fila). - Banner de actualización sincronizado con el check manual (#392): el ribbon de nueva versión y la comprobación de Ajustes comparten el mismo estado; ya no divergen tras un check manual.
- El servidor rechaza al arrancar valores placeholder en
AUTH_PASSySESSION_SECRET(#391).
- Añade el tracker de Umami a la landing para registrar visitas (#384).
- AdGuard Home remoto (#379): el ajuste de AdGuard ahora permite elegir entre el modo GL.iNet (en el router) y el modo estándar (instancia remota). En modo estándar se configura FQDN/IP + puerto, y NetPulse se conecta directamente por HTTP a la API /control.
- NetGrip embedded agents are told apart (kind netgrip) in the agents view: Estado (active/inactive/not installed) and a new Agente column (NetPulse/ NetGrip/Externo). NetPulse can update them centrally via the SSE upgrade event (NetGrip runs its own self-updater); rearm/reinstall are refused.
- install-agent.sh and the agent apk hand off to NetGrip when detected instead of installing the standalone binary; --update-netgrip deploys the latest NetGrip release with a downgrade guard.
- Public
agent/runtimepackage so the agent can run embedded (used by NetGrip). - Releases now attach the on-box server package (netpulse .apk/.ipk).
- Caché de DNS con TTL (#376): el agente cachea las resoluciones respetando el TTL real, reduciendo latencia y consultas repetidas.
- UI en inglés sin cadenas españolas (#378): el feed de alertas demo ahora es bilingüe y no muestra textos en español cuando la interfaz está en inglés.
- Documentación del descubrimiento (#375): el README explica qué fuentes usa NetPulse para descubrir dispositivos (ARP, DHCP leases, WiFi, LLDP, DNS, OUI).
- Agent no longer burns CPU on iwinfo shell-outs (reported 25% on MT7621): clients come from ubus hostapd get_clients, single-pass iwinfo fallback, radios cached out of the event path.
- Pre-existing frontend build errors on main (unused vars/imports, possibly-undefined palettes) that broke the dist CI job.
- Alertas inteligentes de puerto (#303):
PortMonitorrastrea transiciones de estado y detecta flapping (>5 transiciones en 10 min). Integrado en las rutas de agente, SSH y SNMP. - Alerta puerto fantasma (#307): alerta cuando un puerto con trafico estable se queda sin trafico durante 12+ muestras consecutivas. Requiere historico previo para evitar falsos positivos.
- Alerta enlace degradado (#308): alerta cuando la velocidad negociada baja por debajo de la mitad del historico dominante durante 3+ polls consecutivos.
- Health score por puerto (#299): puntuacion 0-100 por puerto basada en estado de enlace (30%), utilizacion (25%), errores (25%) y flapping (20%). Expuesto en la API de detalle del router.
- Widget panel frontal del switch (#306): LEDs por puerto (verde/ambar/rojo/gris) con etiquetas de velocidad e indicadores PoE. Tooltips al pasar el raton. Activo para switches con 8+ puertos. Colapso responsive en movil.
- Poller SNMP para switches gestionados (#309): nuevo paquete
server-go/internal/snmp/con gosnmp. Sondeo de ifTable/ifXTable (estado, velocidad, contadores, errores), dot1dTpFdbTable (tabla MAC-puerto), sysUpTime/sysDescr. Config SNMP por router (toggle + community + puerto). Integrado en el ciclo de polling junto a SSH/agente. - Series temporales por puerto (#302): paquete
server-go/internal/portseries/. Tres tablas SQLite: raw (7d), buckets 5min (1 año), diario (indefinido). Job nocturno de rollup + purga. APIGET /api/routers/:id/ports/:portId/series. Sparkline SVG en el panel de puerto con selector 24h/7d/30d.
- Base OUI embebida (#314): 39769 prefijos MAC para resolver fabricante de dispositivos en vivo.
Device.Manufacturerahora se rellena desde el prefijo OUI en lugar de "Desconocido". - Sugerencias accionables en alertas (#310): campo
hintopcional con sugerencia estática por tipo de alerta (agente caído, temperatura alta, WAN caído, etc.). Visible en el feed, push notifications y service worker. - Topología LLDP como ground truth (#300): los enlaces de infraestructura (router-switch, switch-switch en cadena) usan LLDP como fuente primaria. FDB queda como fallback cuando lldpd no está instalado.
- Vista VLAN de solo lectura (#315): panel colapsable en el detalle del router con tabla de VLANs (ID, puertos, tagged/untagged, PVID) parseada de
bridge vlan show. - Monitorización SFP/DDM (#313): sonda
ethtool -men el agente + parsing de campos SFP del beacon. Temperatura, voltaje y potencia óptica TX/RX en el detalle de puerto. Alertas por RX < -14 dBm y temperatura > 70 °C. - Clasificación por huella DHCP/LLDP (#301): el clasificador de dispositivos amplía las reglas de hostname con DHCP vendor class, client-id y capacidades LLDP.
- Descubrimiento de tipo de dispositivo (#304): filtros por tipo y badges en el inventario de dispositivos. La API devuelve
typeCountsagrupado.
- Switches gestionados como agentes beacon UDP (#291, #292, #294): los switches RTL8372/8373 con firmware RTLPlayground (p. ej. el KP-9000) se integran por un canal UDP propio: beacon de estado cada 30 s (velocidad de enlace y contadores de tramas por boca), datagrama FDB cada 5 min (~600 B, MACs normalizadas al formato canónico) y eventos inmediatos (loop, port up/down, disabled/recovered) que alimentan las alertas. El token del agente se valida igual que el ingest HTTP (kv sha256), con rate limit por IP y estampado de hora en el servidor (el emisor no tiene RTC).
- Descubrimiento y pareado de switches (#291): un switch sin parear anuncia su identidad (modelo + versión de firmware) por broadcast; NetPulse lo lista como candidato y el admin lo configura con un slug y el comando
beacon <ip> <slug> <token>. - Detección de reinicio del switch (#291): el contador seq del beacon vuelve a 1 tras un boot; el servidor lo detecta y publica una alerta informativa "Switch reiniciado".
- Atribución de bocas consciente de la infraestructura (#291): los clientes WiFi atribuyen la boca a su AP; los prefijos OUI de virtualización marcan hipervisores con su VM/CT detrás; las etiquetas curadas nombran dispositivos o infraestructura; los satélites ya no adoptan dispositivos aprendidos por su uplink (el switch deja de atribuirse toda la red vista por lan1).
- Documentación de onboarding (#292/#293):
docs/AGENTE-RTL-SWITCH.mdcon el procedimiento completo para incorporar switches embebidos.
- Test beacon con race en CI lenta (#295): la consulta a la kv podía ganar al write del goroutine del listener; ahora se espera con deadline.
- Ventana sin atribución tras reinicios: el beacon persiste la última tabla MAC conocida, así el arranque restaura los nombres de bocas y clientes al instante.
- Panel de detalle del AP: la fila de backhaul/latencia ahora usa la IP real del gateway (192.168.1.1) en vez de un literal del canon demo (192.168.8.1) y ocupa una fila compacta a ancho completo.
- Las bocas de un switch cableado ya no muestran secciones WiFi vacías y se mantienen en una única fila horizontal con scroll si no caben.
- Asistente de actualización multi-paso (#280): aplicar una actualización ya no es un botón ciego. El asistente confirma con el changelog real (cuerpo del commit en rolling, notas del release en estable) y un aviso explícito de caída del servicio que hay que marcar; ejecuta con pasos visibles (obtener código, descargar binario, verificar integridad, instalar, reiniciar) alimentados por hitos
STEP:/PROGRESS:del script de update, transmitidos por SSE (GET /api/update/stream, admin) con fallback a polling; y recarga la app solo cuando responde un proceso distinto (uptime baseline). La descarga del binario de CI se verifica contra el digest sha256 publicado antes del swap. - Sección de flota de agentes en Dispositivos (#284): la vista de agentes pasa a ser una sección al final de la página de dispositivos (la ruta
/agentsredirige), con rearmar, copiar comando, reinstalar y actualizar agente por fila. - Actualización de agentes con progreso en vivo (#284): el agente reporta pasos con timestamps (descargando con % cada 5%, comprobando integridad, instalando, reiniciando) que el servidor expone como historia (
upgrade.steps) y la UI pinta como línea de tiempo; los runs rápidos se resumen ("Actualizado · N pasos · X s"). - Cola de actualizaciones para agentes desconectados (#284): si un agente no tiene su stream SSE conectado, el comando de upgrade se encola y se envía en cuanto reconecta (flush on-connect del hub); la UI lo muestra como "esperando conexión" y lo sigue hasta que resuelve.
- Verificación de integridad del binario de agente (#284): el servidor sirve el binario embebido con cabecera
X-Checksum-Sha256y el agente comprueba el sha256 antes de hacer el swap (paso "comprobando integridad"); si no cuadra, aborta sin tocar el binario en marcha. - Menús renombrados (#284): "Routers" pasa a "Dispositivos" y "Dispositivos" a "Clientes" (más preciso: la página lista routers, switches y APs), alineando paleta de comandos, resumen y ajustes.
- Backoff de reconexión del agente que nunca se reseteaba (#284): tras varios reinicios del servidor, los agentes acababan reintentando su stream de comandos cada 5 minutos para siempre, con lo que el servidor solo alcanzaba a los que habían reconectado por casualidad ("upgrade enviado a 1 de 5"). El backoff ahora se resetea tras una conexión establecida.
- Sección "Novedades" vacía (#280): cuando el cuerpo del commit solo contiene trailers de git (Co-authored-by, etc.), la sección de novedades del asistente ya no se renderiza.
- Upgrades de flota no muestreados (#284): al lanzar "Actualizar agentes" se dispara un refresco inmediato de agentes que engancha el sondeo rápido (2 s), en vez de esperar al ciclo de reposo de 30 s con el que un upgrade en LAN ocurría entero sin verse.
- Consolidación de alertas de auto-rearme (#271): el supervisor emitía una alerta "Auto-rearme sin recuperación" por cada reintento (cada 10 min), llenando el feed con decenas de copias del mismo incidente. Ahora la primera falla crea la alerta y los reintentos la actualizan (ts/descripción) en su sitio; al recuperarse el agente se cierra el incidente y el próximo fallo crea una alerta nueva.
- Boca WAN con datos reales de conexión (#276): el detalle del gateway muestra en el tooltip de la boca WAN el protocolo (PPPoE), IP pública, puerta de enlace y servidores DNS, sondados en vivo vía
ubus call network.interface.wan status(cache 60 s). - Botón atrás fijo en el detalle de router (#275): el botón "Atrás" vivía en la cabecera del detalle y se perdía al hacer scroll. Ahora es una flecha icon-only fija en el topbar (desktop) y en el header móvil, siempre visible en
/routers/:id. - Tooltips de bocas LAN/WAN estilo tarjeta (#276): al pasar el cursor sobre una boca se muestra una tarjeta con el dispositivo conectado (nombre, velocidad, MAC e IP); las libres muestran "libre".
- Topología sin hint engañoso (#276): eliminado el texto "clic para ver el dispositivo" de la tarjeta de dispositivo (muchos chips no se pueden clicar porque otros se interponen).
- Reenvío DNS del módulo AdGuard al puerto correcto (#270): dnsmasq reenviaba a la UI HTTP (puerto 3000) en vez del puerto DNS real; ahora usa un puerto DNS dedicado (5353) fuera del 53 para no colisionar con dnsmasq.
- Test de informe semanal independiente de la fecha (#273): los tests usaban semanas ISO fijas y rompían el CI al avanzar la semana actual; ahora calculan las fechas relativas a hoy.
- Resumen reorganizado (#265): tarjeta hero en una sola columna centrada: saludo con el estado debajo ("Tu red está perfecta" cuando todo va bien, o contador dinámico "N alertas importantes" cuando hay penalizaciones), donut de salud a 220 px centrado y fila de stats (latencia, dispositivos) debajo. El contador sale del breakdown existente del healthScore, sin cambios de backend.
- Módulo WireGuard de orquestación (Fase 10.3, #92): despliegue declarativo de peers WireGuard (interfaz wg0 + secciones wgpeer) vía plan/apply/state, healthcheck con
wg show, rollback automático si el túnel no queda up y protección anti-lockout (nunca borra el peer del admin). - Recuperación de agentes caídos desde la app (#245): nueva vista de flota de agentes con estados fresh/stale/offline y last-seen, acción de reinstall vía SSH y comando one-liner como fallback.
- Labels de puertos/VLAN de LuCI como fuente de nombres en topología (#258): el agente lee
port_labels/vlan_labelsde/etc/config/luciy la topología las usa como nombre preferente. - Demo: tracker GoatCounter opcional (
VITE_GC_COUNT) y nombre propio por defecto en inglés para primeros visitantes.
- CSP endurecida (#212): el script anti-black-screen y el splash se externalizan y se elimina
unsafe-inlinedescript-src/style-src. - Landing: animaciones de lightbox y botones (press feedback), reveals restringidos a cards/paneles.
- Topología: rings wifi más compactos y cable del host alejado de los APs.
- SSE (#199, #200): limpieza de conexiones por identidad (no mata streams nuevos con el mismo slug) y broadcast no bloqueado por clientes lentos.
- Backend auditoría (#202-#206, #209): race en notifier Close, reintentos de webhook con backoff, fugas/derribos en la pool SSH, poda de presencia sin límite y hash bcrypt dummy válido (sin oráculo de enumeración de usuarios).
- Paginación (#201):
startnegativo ya no causa panic con queries enormes. - Reports (#207): UpPct semanal clampeado a 100 igual que availability.
- LLDP (#208):
lldpDownUntilprotegido con mutex. - Frontend (#141, #172, #183, #187): snackbar de instalación PWA, layout mobile de topología, transición con slide entre vistas y rings de topología compactos.
- i18n de demo (#237, #238): idioma forzable en build y nombres propios traducidos al idioma activo.
- Test de availability: clampeado el valor esperado para que no dependa de la hora del día.
- TLS híbrido post-quantum y auto-actualización de agentes destacados en la landing (X25519MLKEM768, Go 1.24+; self-update por flota).
- Detalle del router: campo
lldpAvailablepara señalar cuándo faltalldpden el dispositivo sondeado (requisito para detección de switches gestionados). - Empaquetado OpenWrt: los releases ahora adjuntan
.ipk/.apkdel agente y.ipkdeluci-app-netpulse(el workflow no se disparaba por el GITHUB_TOKEN). - Alertas de demo localizadas al idioma activo (estático y modo demo).
- Mensaje amistoso "solo con backend en vivo" en Reports/Roaming cuando la demo estática sirve HTML para
/api/*.
- Seguridad:
http.Servercon ReadTimeout/WriteTimeout (SSE exento);login_attemptsse resetea al expirar el bloqueo; verificación de Origin en mutaciones (CSRF defensa en profundidad); el token de pairing ya no se loguea en claro;413real para bodies sobredimensionados;AUTH_PASSmínimo 10; el webhook se loguea solo por host; el backup descargable documenta que contiene credenciales. - Alerta de dispositivo desconocido: ya no se re-dispara por cada reconexión (memoria por MAC) ni para dispositivos con hostname conocido.
- Alertas offline de dispositivos conocidos se limpian al recuperar la conexión (flapping IPv6).
- Descubrimiento de links LLDP por identidad de router (fallback mgmt-IP/nombre) cuando la chassis-MAC difiere de la del bridge.
- Gateway sin SSH ya no se muestra como "offline": se distingue de "sin acceso".
- Topología: el popup de detalle ya no desaparece antes de clicar.
- WAN: el jitter simulado solo actúa en modo demo (no corrompe el chart en vivo).
- Frontend: fetch races canceladas (Roaming/Reports), polling con cleanup (applyPlan, banner), NaN en AdGuard card, claves estables en Home, unread coherente, timers limpiados, 401 de
/api/dawnredirige a login,noUncheckedIndexedAccesshabilitado. - A11y: tabs WAI-ARIA con navegación por teclado.
- El release v2.11.1 ajustó
AGENT_VERSIONpara que derive del tag yhttpapi.Versionsea inyectable; este release lo consolida.
- Firmware objetivo por router: versión configurable en Ajustes; si el firmware instalado no la cumple, NetPulse emite una alerta no urgente, marca el router en la lista y penaliza ligeramente su salud. #241
- Rotación de la clave SSH del servidor:
POST /api/config/sshkey/rotate(admin) con confirmación en la UI; respalda el par anterior y regenera uno nuevo. #242 - Actualización del agente desde la app:
updateAvailableen la lista de agentes y botón "Actualizar agente" en el detalle del router; el agente descarga el binario nuevo del servidor, lo intercambia y se reinicia solo. #243 - Reinstalación del agente desde la app: botón junto a LuCI/SSH que repara un agente borrado (p. ej. por una actualización de firmware del router) vía SSH: descarga el binario, escribe la config y reactiva el servicio, rotando el token. #246
- Estados de agente diferenciados: "Agente caído" (registrado pero sin
respuesta) frente a "Agente no instalado" (router agent-only sin agente),
ambos en rojo; se expone
router.agentOnlyen el view-model. - Roadmap: Fase 17.12 "Firmware update in-app (labs)".
- Copiar comando SSH en LAN HTTP: fallback a
execCommandcuando la Clipboard API no está disponible (HTTP), y el toast solo confirma si la copia tuvo éxito. updateAvailablecoherente: la versión del binario embebido ahora se inyecta por CI con el mismo valor que la versión del agente (antes una constante fija que nunca coincidía).
- Dispositivos de confianza: nueva allowlist
known_macsgestionable desde Ajustes ("Dispositivos de confianza"). Una MAC registrada nunca dispara la alerta "Dispositivo desconocido" y su nombre se usa como alias. Endpoints adminGET/PUT/DELETE /api/settings/known-macs. #196
- Falsos positivos de "Dispositivo desconocido": la alerta se disparaba
con los propios routers de la red y con dispositivos ya conocidos cuando el
sondeo del gateway era lento (su bridgeMAC se colaba como cliente de un AP)
o el lease DHCP no se resolvía en ese tick. Ahora la bridgeMAC de cada
router se persiste en
routers.macy se excluye siempre, y la alerta de desconocido deja de ser urgente (warn informativo, categoríaclientspasa aall). #196
-
Aviso de actualización sin versión duplicada: cuando GitHub publica una release cuyo nombre coincide con el tag (p. ej.
v2.10.0), el banner ya no muestra "Nueva versión disponible: v2.10.0 — v2.10.0". Ahora solo aparece la versión una vez. #191 -
Hint visible en instalaciones estables: en despliegues que usan
install.sh(modo estable), el banner ahora muestra debajo del mensaje "Instalación estable: re-ejecuta install.sh para actualizar", haciendo explícito por qué no hay botón "Actualizar" y clarificando el enlace "Ver en GitHub" como siguiente paso. #191
- Eventos offline/online de dispositivos: el poller registra las
transiciones de presencia wireless (un dispositivo que deja de verse 3
ticks seguidos =
offline; al reaparecer =online) con timestamp, router y última señal. Nueva tabladevice_events(retención 30 días) y endpointGET /api/device-eventscon filtroslimit/since/router/mac/state. Permite correlacionar incidentes (qué cayó, cuándo, cuánto tardó en volver). #184
-
Historial de actualizaciones: nueva tabla
update_historyy endpointGET /api/updates/history(admin) con el registro de cada apply (cuándo, SHA desde/hacia, iniciador, duración, estado). Visible en Ajustes. #159 -
Velocidad WAN contratada: campo en Ajustes para declarar el ancho de bajada/subida contratado (Mbps). Se persiste y el gráfico de tráfico WAN muestra una línea de referencia al nivel contratado y la utilización ("Pico hoy X % del contratado").
GET/PUT /api/settings/wanspeed(admin) ywan.contractDownMbps/contractUpMbpsen el overview. #151
-
Readiness checks antes de aplicar actualizaciones:
GET /api/update/statusexpone pre-flight checks (disco, git limpio, red a GitHub, sin update en curso); la UI bloquea el botón de aplicar si no están listos. #160 -
Confirmación post-update: tras aplicar y reiniciar, si el commit cambió se muestra un aviso de una sola vez "Actualizado a ". #161
- Readiness git: el check de working tree limpio ignoraba los ficheros
untracked (
.env,data/, binarios, backups), bloqueando el apply para siempre en un layout real. Ahora usa--untracked-files=no(ungit reset --hardno toca untracked). #160
- Ingesta externa en modo demo: los colectores/scrapers externos (p. ej.
un scraper de switch vía timer) recibían
409 demo_read_onlyal empujar datos en modo demo y reportaban fallos de un estado esperado. Ahora la ingesta se acepta como no-op benigno (202+demo:true) sin tocar la BD, manteniendo la validación de token y firma HMAC. #168
- Resumen en móvil: las tarjetas de servicios forzaban columnas
implícitas en la rejilla de 1 columna, dejando el saludo y el tráfico
montados en la misma línea horizontal (el saludo colapsaba a ancho 0).
Ahora usan clases de columna estáticas solo a partir de
lg. #173
- Resumen WAN en modo live: "Pico hoy", "Media" y "Total 24h" se calculaban solo en el modo demo; en live quedaban siempre a 0 / "—". Ahora se calculan desde las métricas almacenadas del gateway (pico del día con su hora, media de bajada 24h y volumen total 24h). #169
-
Orquestación Fase 18 (17.2–17.4): módulos de WiFi invitado (SSID aislado con subred propia), DDNS (ddns-scripts vía UCI) y QoS/SQM (sqm-scripts con cake) con UI unificada en
/orchestration. El motor gana los Kindsuci_add,uci_set_named,uci_delete_sectionytcp_check(healthcheck de puerto con reintentos). #131 / #133 / #136 -
Topología portable: refactor del layout (anillos densos, resolver de colisiones, círculo virtual) sin coordenadas hardcodeadas de una red concreta. #138 / #140
-
Ajustes → Acerca de: los cuatro tiles enlazan ahora a su destino (GitHub, la web del proyecto, Ko-fi y el Club Cloudless). #162
-
AdGuard solo en el gateway: el plan se rechaza (422) si el router destino no es el gateway, ya está el fork de fábrica o no hay RAM suficiente, salvo opt-in explícito. #120 / #130
-
Orquestación oculta por defecto: el menú
/orchestrationsolo se muestra si el admin lo activa en Ajustes. #121 / #129
- Fase 17.1 — Módulo AdGuard Home (despliegue completo): el motor de
orquestación (Fase 10) pasa de configurar UCI a desplegar un servicio
entero. Un probe SSH detecta el escenario del router (firmware GL.iNet
con fork propio → abort; apk/opkg en feeds; binario ya presente; binario
oficial de GitHub) y genera el plan correspondiente. Nuevos Kinds del
executor:
apk_install,download(allowlist URL),write_file,extract_tarball,chmod,service start/stop,mv. El endpointPOST /api/plansdevuelve 422managed_by_firmwarepara routers con AdGuard de fabricante. UI/orchestrationmuestra el método detectado. #116 / #122
-
Discover responde a la cancelación del cliente: el scan de la LAN propagaba el
context.Contextde la request HTTP, así que cancelar la petición (cerrar la pestaña, corte de red) dejaba el barrido corriendo con el lock de caché tomado. Ahorapool,tcpOpen,postUbus,getRootyprobeSshrespetanctxy el scan aborta en cuanto el cliente se desconecta. #107 / #123 -
Home: el tráfico WAN ya no aparece duplicado: HeroStrip mostraba
↓ down / ↑ upinstantáneos junto a WanTraffic, que ya pinta el gráfico down/up y las métricas 24h. HeroStrip pasa a mostrar solo saludo + HealthRing + latencia (y nº de dispositivos en móvil). #108 / #124
-
Fase 14.3 — Estado 802.11r en /roaming: nueva pestaña que muestra por SSID y por router si Fast BSS Transition está activo, con mobility_domain, modo FT (over-the-air/over-the-DS), tipo de auth (PSK local vs RADIUS externo) y banderas 802.11k/v/w (PMF). Datos leídos por SSH de
uci show wireless. EndpointGET /api/dot11r(503 si ningún router lo soporta). #105 / #106 -
Fase 14.4 — Utilización por canal WiFi en /roaming: nueva pestaña Survey con noise floor y % de ocupación por canal, leído de
iw dev wlanX survey dump. Los canales congestionados son la causa más habitual de WiFi lento en casa. EndpointGET /api/survey. #111 / #113 -
Fase 14.5 — Eventos de roaming con histórico en /roaming: nueva pestaña Eventos con feed persistente de 30 días de conexiones, desconexiones y decisiones DAWN. Ingesta continua cada 60s vía SSH
logread | grep, deduplicación por hash. EndpointGET /api/roam-events. #114 / #115 -
Editar configuración de router: nuevo endpoint
PUT /api/config/routers/{id}que permite editar host, nombre, tipo, gateway yagent_onlysin borrar y recrear (lo cual perdía el ID y el histórico de métricas). UI de edición inline en Ajustes. #109 / #110
-
GetDawn timeout en routers agent-only: el handler intentaba SSH a switches sin SSH (switch16), perdiendo 4s por cada llamada. Ahora se saltan los routers
agent_only. -
Survey filtraba interfaces no-wlan: el parser solo aceptaba
wlan0/wlan1, descartando silenciosamente todos los radios de los Redmi AX6 (driver mt76, interfacesphy0-ap0/phy1-ap0). Acepta cualquier interfaz listada poriw dev.
- Routers agent-only (Phase 14.3 bundled): mergeada la rama limpia
fix/agent-only-switchesconRouterConfig.AgentOnly,StalePayload, rol Switch, gate en topología y migración de BD. Antes no estaba en main y bloqueaba la compilación de la Fase 14.3.
- Single-flight de GetOverview: un panic en buildOverview congelaba el poller para siempre (sfInFlight sin reset, canal nunca cerrado); los seguidores 2..N recibían (nil, nil) del canal cerrado. Reescrito con estructura compartida sfCall, close(done) como pura señal para N seguidores, y recover del líder transformando panic en error.
- Cliente SSE zombi bloqueaba el broadcast: un peer desaparecido sin cerrar TCP llenaba el buffer del kernel y fmt.Fprint bloqueaba el Broadcast síncrono de todos los clientes (~15 min). Añadido write deadline 10s vía ResponseController en Hub y AgentHub. Añadido ReadHeaderTimeout: 10s al http.Server.
- Carrera en SSHPool.dial: dos dialers concurrentes al mismo host filtraban una conexión SSH. Añadido single-flight por host (canal dialing) y chequeo de p.closed en el mutex.
- %v → %w en dos envolturas de error (config.go y rearmer.go) que rompían errors.Is/As aguas arriba.
- Panic del poller por notifier nil-encapsulado: un puntero
nilempaquetado en la interfazalerts.Notifier(webhook inactivo) no esnilparaif n != nil, yNotifypaniqueaba al emitir una alerta URGENTE de "dispositivo desconocido". Los notifiers se filtran ahora en su tipo concreto antes de empaquetar en la interfaz, con defensa adicional víareflectennotifierChain.Notify.
- Fase 12 en el ROADMAP: auditoría de seguridad y robustez (release bug-hunting).
TRUST_PROXY: cuando el servidor va detrás de un proxy/reverse, la IP del cliente se toma deX-Forwarded-Fory el tráfico HTTPS deX-Forwarded-Proto. Por defecto no se confía en esas cabeceras.- Ventana de frescura del
tsdel agente en la ingesta (AGENT_MAX_TS_DRIFT_S, default 5 min): rechaza pushes con marca de tiempo fuera de rango.
- Bypass de rate-limit por
X-Forwarded-Forfalso: sinTRUST_PROXY, la IP del cliente es la del socket; ya no se puede falsear para evadir el bloqueo de fuerza bruta del login ni el rate-limit de la ingesta. - Anti-replay en la ingesta de agentes: un token robado ya no permite reinyectar payloads antiguos capturados.
- Body sin límite en endpoints JSON (login, usuarios, config, alertas, push): techo de 64 KB por petición.
- Password mínima 10 caracteres en alta y cambio de contraseña (antes 6).
- Fase 7 (agente a fondo): iw events en tiempo real, SSE bidireccional
(AgentHub + refresh),
.ipkvía opkg en gateway, profiling, CI de empaquetado.ipk/.apk, landing web con posicionamiento OpenWrt nativo. - Fase 8 (consolidación): métricas en
/api/health, registry de agentes persistente, escalera de retención, recharts v3, webhook saliente con DLQ. - Actualización de stack: TS 7, Tailwind 4, Vite 8, react-router 8, sqlite 1.56.