Ctrl K
ComunidadGuías…

CHANGELOG.md

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# Changelog

All notable changes to this project will be documented in this file.

## [0.13.12] - 2026-11-04

### Security

- Bump `@hono/node-server` 2.1.1 to 2.1.6 (via `@modelcontextprotocol/sdk`): fixes the advisories affecting versions below 2.1.3
- Bump `serialize-javascript` 7.1.0 to 7.1.4 (via `vite-plugin-pwa` and `workbox-build`, build time only): fixes the advisory affecting versions below 7.1.3
- Lockfile only, both stay within their declared ranges. Replaces Dependabot PR #107

---

## [0.13.11] - 2026-11-04

### Security

- Bump `fast-uri` 4.2.1 to 4.2.3 (via `@modelcontextprotocol/sdk` and `ajv`), override raised to `>=4.2.3`; replaces Dependabot PR #105
- Server network stats: removed the shell `exec` fallback that ran `cat` on the same three `/proc/net/dev` paths the `fs.readFile` loop already reads. No behaviour change, one less shell call

### Changed

- `.gitignore` and `.dockerignore`: the broad `*secret*` glob is replaced by specific patterns (`*.secret`, `*.secret.*`, `*_secret`, `*_secret.*`, `.secrets`, `secrets/`) so source files with "secret" in their name are never silently excluded

---

## [0.13.10] - 2026-10-23

### Fixed

- Network scan: the console showed a stream of `429 Too Many Requests` on `/api/network-scan/progress`, `port-scan-progress`, `stats` and `history` during a scan started from the UI. The page polls up to about 120 requests per minute during a scan while `scanLimiter` allowed 60. Read requests (GET and the read-only `POST /traffic/batch`) now use the general API limit (300 per minute); scan triggers and other writes keep the 60 per minute limit
- Network scan: the auto-scan progress poller no longer runs at the same time as the manual scan/refresh poller, which made `/progress` be requested twice every 2 seconds

---

## [0.13.9] - 2026-10-23

### Security

- Bump `dompurify` 3.4.16 to 3.4.17 (via `jspdf`): fixes GHSA-cg2g-xh64-v4q7 (critical, SAFE_FOR_TEMPLATES bypass), GHSA-mrv2-3pqr-j5jr (high, IN_PLACE detached subtree handlers) and GHSA-c6gr-qvv4-5v3f (low)
- Bump `body-parser` 2.3.0 to 2.4.0 (via `express`): fixes CVE-2026-111181, a process crash on a compressed request body with trailing bytes
- Bump `ip-address` 10.7.3 to 10.7.4 (via `express-rate-limit`): fixes GHSA-rxp7-6hxx-x8q5, quadratic-time zone identifier matching on long IPv6 strings

---

## [0.13.8] - 2026-10-23

### Fixed

- Home Assistant Ingress: `index.html` is now served with its asset, icon and manifest paths prefixed by the `X-Ingress-Path` value, and without the PWA service worker registration (its scope `/` would land on the Home Assistant host). Before, the browser requested `/assets/...` from the Home Assistant root and got a blank page. Only applied in Ingress mode with a valid `/api/hassio_ingress/<token>` header; other deployments are unchanged
- Home Assistant Ingress: lazy-loaded pages failed with `Unable to preload CSS for /assets/...` because the Vite preload helper used absolute `/assets/` URLs. Asset URLs inside JS and CSS are now built relative to the importing file (`experimental.renderBuiltUrl`)
- Home Assistant Ingress: `/api/system/environment` and `/api/settings/theme` were fetched from the Home Assistant root (404); they now go through the Ingress base path like the other API calls

---

## [0.13.7] - 2026-10-22

### Security

- Rebuild the Docker image on Node.js 22.23.4 (`node:22-alpine`) to fix CVE-2026-111497, an out-of-bounds write in the Brotli decoder reachable through `fetch()` and the Express 5 body parser; 0.13.x images shipped Node.js 22.23.3

---

## [0.13.6] - 2026-10-21

### Added

- `GET /api/plugins/unifi/card-devices`: each port row now carries `rx_errors`, `tx_errors`, `rx_dropped` and `tx_dropped` (cumulative controller counters since the device booted); a port that does not report them leaves them out instead of sending 0

---

## [0.13.5] - 2026-10-21

### Security

- Bump `@modelcontextprotocol/sdk` from 1.31.0 to 1.32.1 and `vite` from 6.4.3 to 6.4.5 (replaces Dependabot PR #97, whose security check cannot read the repository secret)

---

## [0.13.4] - 2026-10-21

### Changed

- UniFi per-client traffic history (background poll every 30 s) now reuses a `stat/sta` answer that another reader (live tab, stats, MyServices, MCP) fetched in the last 25 s instead of sending its own request. Counters are cumulative, so the recorded totals stay exact; fewer `GET stat/sta` calls reach the controller

---

## [0.13.3] - 2026-10-20

### Security

- Bump `hono` from 4.13.7 to 4.13.13 (transitive dependency of the MCP SDK) to fix GHSA-5r4p-p66f-jhc7 (`serveStatic` decoded the request path twice, which could bypass middleware on static paths). The application does not use `serveStatic`

---

## [0.13.2] - 2026-10-20

### Added

- UniFi SFP modules: temperature and optical power (RX / TX) per transceiver, graded orange from 70 °C and red from 85 °C, optical RX orange below -14 dBm and red below -18 dBm. Shown in the front-panel card port detail and in a "SFP modules" list under each device on the Overview page
- Electrical (copper) and optical modules are told apart: a copper transceiver reports placeholder optical power, so only its temperature is graded
- Freebox page: SFP card (column 3) with the fibre module (model, vendor, serial, link, signal, module power, RX / TX in dBm, orange below -24 dBm and red below -27 dBm) and the SFP LAN port (link, speed, connected device). New `GET /api/settings/sfp` (cached 2 min). The box reports no temperature for either
- Freebox page: "Latest devices" card (column 4) listing the newest and the last disconnected devices from the LAN browser timestamps
- MCP: `sfp_modules` in the UniFi device summaries, SFP readings in `card-devices` (serial numbers left out), module identity in the Freebox FTTH summary

### Changed

- Home page: an SFP icon appears in the "system state" frame of the UniFi card (hot module, or optic without signal / weak RX / fault) and of the Freebox card (fibre module without signal or weak RX; the box reports no module temperature), with a hover detail. Nothing is shown while the modules are fine
- Freebox page: the SFP and latest devices cards sit in columns 3 and 4, so column 1 is no longer the only loaded one
- Home Network scan card: the "Offline IPs" and "Top worst latency" titles are links to the scan page with the matching filter and sort (`/network-scan?status=offline`, `?status=online&sortBy=ping_latency&sortOrder=desc`); the offline list shows 5 entries at most; the stat rectangles are flatter; the data volume block sits before the offline list in a fixed 8-row frame, and the data volume, offline and latency rows have no frame; the last scan line fits on one line, with a single separator above the data volume

---

## [0.13.1] - 2026-10-20

### Added

- Port errors on the home cards and UniFi pages: a total badge with an hover detail (which port, RX / TX, usual causes). UniFi: System info on the Overview page, a badge in the home card system state, an "Err." column in the switches table of the Analysis tab and an exact-counter tooltip in the Switch tab (RX / TX errors, dropped packets)
- Freebox: port error badge on the home card, by cause (bad CRC, receive errors, fragments, oversized / undersized frames, collisions). New `GET /api/settings/switch/errors` reads `/switch/port/{id}/stats` (one call per port) and keeps the result 5 minutes
- UniFi home card: DHCP client count over the pool size, like Freebox
- Switch tab: the switch badges in the title filter the table to one switch (click again to show all)

### Changed

- Clients and Switch tables share the same zebra and row height
- Home cards: Freebox firmware / LAN header cells stay on a single line (the model name drops the word "Freebox"), the UniFi DHCP range fits on one line, and the system state frames of both cards have the same minimum height. The Freebox Wi-Fi access points list is now a separate block under it

---

## [0.13.0] - 2026-10-20

### Changed

- UniFi Overview: the Access points, Switches and Sites sections are replaced by one Devices card showing the live front-panel cards (gateway, then switches, then APs by default, drag to reorder as before) with the status, system and network info back under each card. Client / AP / switch totals are now tags on the card header
- UniFi Overview: System info is compact and holds the update status chips (up to date, update available, critical update, total devices); it is aligned with Network alerts at equal height
- UniFi tabs reordered: Overview, Switch, Clients, then Traffic, Threats, Analyse
- Reset layout button restyled and only shown once the card layout was changed
- Search page: for a wired client, the fake 8-square port strip is replaced by the real front-panel card of its switch (real ports and LEDs) with the client's port selected

### Removed

- UniFi CARD tab, merged into Overview (`/unifi/card` redirects to `/unifi/overview`)
- Hardware front panel view toggle (cards always show the ports view)
- Threats tab is hidden when there is no compatible gateway (no gateway device, or a controller type without IPS events)

---

## [0.12.4] - 2026-10-19

### Changed

- `GET /api/connection/summary`: the rarely changing extra blocks (LAN, DHCP and WiFi configuration, disks, FTTH optics, VM disk sizes, VM host info) are cached 5 minutes and dropped by any write to the box; live counts (hosts, WiFi stations, VM states, switch links) stay at 30 s. Disk temperature and usage can be up to 5 minutes old in this summary
- `GET /api/auth/check` (permissions refresh of every open tab) no longer asks the Freebox for its session status when an authenticated request succeeded in the last minute; the keep-alive still checks it

---

## [0.12.3] - 2026-10-19

### Security

- `undici` 6.28.1 to 6.29.1: fixes SNYK-JS-UNDICI-21181005 (TLS options passed as `tls` ignored by `Agent`; MyNetwork passes them through `connect`, so it was not exposed) and four denial-of-service issues (SNYK-JS-UNDICI-21181013, 21181015, 21181019, 21185768)

---

## [0.12.2] - 2026-10-19

### Changed

- Freebox LAN devices are no longer polled every 10 s on every settings page: the parental control and port forwarding dialogs load them when they open
- Freebox WiFi status refreshes every 15 s on the Freebox page and every 60 s elsewhere (header and plugin summary)
- App-wide plugin stats refresh every 30 s on the dashboard and every 60 s on other pages

---

## [0.12.1] - 2026-10-19

### Added

- Upstream request counter: every request sent to the Freebox and the UniFi controller is counted per endpoint, with the reads answered without a request (cache hit or shared in-flight request). Shown in Administration > Debug ("Freebox and UniFi requests": last minute, average per minute over the last hour, total, share saved) and available to admins at `GET /api/debug/upstream-calls`, reset with `DELETE /api/debug/upstream-calls`. Counters live in memory and restart with the server

---

## [0.12.0] - 2026-10-19

Request optimisation release: far fewer calls to the UniFi controller and the Freebox, with the same data on screen and in the API.

### Added

- Per-token rate limits for API and MCP tokens: 300 requests per minute, 30 per minute for expensive calls (ip-details, `scan_get_machine_details`, rescans, live top traffic). Answers `429 RATE_LIMITED` with `Retry-After`
- Opt-in compact output for `GET /api/lan/devices`, `/api/wifi/stations` and `/api/dhcp/leases`: `?compact=1`, with `active=1`, `limit=N` and `search=`. Default output unchanged
- MCP: optional `search` and `limit` on `freebox_get_dhcp_leases` and `freebox_get_switch_ports`, optional `compact` on `scan_get_device_by_ip`

### Changed

- UniFi: short shared cache with in-flight sharing for controller reads (devices, clients, dashboard 1.5 s; sysinfo and configuration 60 s), cleared after every write
- UniFi: live WebSocket polls every 2 s (was 1 s); with no client connected the background sampler runs every 30 s (was 1 s, around the clock). No tick starts while the previous one is still running
- UniFi: WAN sampling asks the controller for the gateway only, one client list per tick instead of two, stats cached 10 s, bandwidth reports cached 60 s, threat lookups remember the endpoint that answered
- UniFi: no forced re-login every 15 minutes, a single login at startup, and the connection test no longer logs the live session out
- Freebox: short shared cache for GET requests (per endpoint, 0.9 s to 30 s), cleared after every write; API version fetched once; login and session checks shared between concurrent callers; stats cached 10 s; RRD history cached according to its range; network summary cached 30 s
- Freebox: an expired session is renewed once and the request replayed, instead of waiting for the keep-alive
- Freebox: the native WebSocket (host and VM events) now starts with the plugin and uses the configured box address
- Network scan: plugin data is read once per scan instead of twice per host
- ip-details: one UniFi stats read instead of two, sources fetched in parallel; IPs outside the scan ranges are no longer pinged, unless already recorded by the scanner (for example added by hand)
- MCP `scan_get_machine_details` and `scan_rescan_ip` also accept IPs added by hand outside the scan ranges
- Frontend: a single app-wide stats poller paused on hidden tabs; Freebox devices, downloads, VMs and history only polled on the pages that show them; Speedtest widget reads the live connection stream instead of polling every 2 s; CARD tab is the only UniFi stats poller while open
- Frontend: WebSockets close after 2 minutes on a hidden tab and reopen when it is visible again; the UniFi live chart is refilled from the server so it shows no gap; one shared UniFi socket for all components
- Network scan page: lighter polling during scans, latency and traffic data refreshed only when needed
- SQLite: one transaction per traffic poll, two redundant indexes removed

### Fixed

- Freebox: concurrent requests to the same endpoint with different bodies got the same answer (temperature history showed network data, every VM showed the first VM's disk size, a second concurrent write was never sent)
- Freebox: a timed-out write could be sent twice on Freebox Revolution
- Freebox WebSocket gave up after one failed reconnect in production and stayed on 5 s HTTP polling until reload
- `GET /api/network-scan/history`: `offset` returned an empty page and `total` was the page size
- `GET /api/plugins/:id` logged the shared UniFi session out

### Security

- Removed the debug route `GET /api/plugins/unifi/threats/raw`, which relayed any controller path to any logged-in user
- MCP `raw=true` on UniFi devices, clients and system info no longer returns device secrets (`x_authkey`, SSH and SSO fields)

---

## [0.11.2] - 2026-10-18

### Added

- `GET /api/plugins/unifi/top-traffic`, open to read-only API tokens: clients using the most traffic, `mode=live` (rate), `today` or `alltime` (volume), `limit` 1 to 50
- `GET /api/network-scan/stats`, open to read-only API tokens: total, online, offline and unknown hosts, last scan date
- MCP `scan_get_devices`: `sort_by` (`last_seen`, `latency`, `ip`, `hostname`) and `order`, so the worst latencies come first with `status=online`, `sort_by=latency`, `order=desc`

### Changed

- `unifi_get_top_traffic` and the new REST route share one implementation

---

## [0.11.1] - 2026-10-18

### Added

- `GET /api/plugins/unifi/card-devices`, open to read-only API tokens: switches, gateways and access points with the per-port detail the front-panel cards read (port rows with link, speed, PoE, live RX/TX, uplink flag, LLDP neighbours that are known UniFi devices; uplink port, WAN links without addresses, firmware update). Explicit field list, numbers are real numbers, a gateway only exposes its LAN address, cached 3 s server side
- CARD tab: per-card uplink selector next to the title, with the real uplink port of switches and gateways detected from the controller (uplink object, LLDP, child devices) and selected when the card opens

### Changed

- CARD tab: port RX/TX shown as bit/s, kbit/s, Mbit/s or Gbit/s with the unit picked automatically

### Fixed

- CARD tab: special ports (WAN, SFP+, 10G RJ45) showed no link and no speed because their rows were dropped before the card rebuilt them
- CARD tab: link status shown untranslated ("no_link"), and ports with no controller data now say so

---

## [0.11.0] - 2026-10-18

### Added

- UniFi page, new **CARD** tab: realistic live cards for UniFi switches, gateways and access points (front panel, ports, link speed, PoE, uptime, clients), refreshed every 5 s, read-only. Built on the MIT-licensed [unifi-device-card](https://github.com/bluenazgul/unifi-device-card) (vendored in `src/vendor/unifi-device-card`, local changes listed in `VENDOR.md`) with a MyNetwork adapter feeding it UniFi controller data instead of Home Assistant entities
- Each card shows the model code received and whether a dedicated front panel exists for it or a generic one is drawn
- Cards follow the application theme, optional hardware front panel view (off by default)
- Port LEDs blink slowly when the link is up and faster when the port carries traffic
- Cards can be reordered by drag and drop, and resized with the mouse (right edge for width, lower border for height with proportional reduction, bottom-right corner for both). The layout is kept in the browser (localStorage), with a reset button

### Changed

- `src/vendor/**` is excluded from SonarCloud analysis

---

## [0.10.75] - 2026-10-18

### Added

- MCP tool `scan_get_machine_details(ip)`: everything known about a machine in one call (scan with open ports and protocol, last port scan, Freebox host and DHCP lease, port forwarding, UniFi client with switch name, IP and port, or AP). Same compact projection as `GET /api/search/ip-details/:ip`. Read-only, the IP must be inside the scan ranges (one ping is sent)

### Changed

- Per-IP aggregation moved from the Search route to `ipDetailsService`, shared by the route and the MCP tool (no behaviour change)

---

## [0.10.74] - 2026-10-18

### Added

- `GET /api/search/ip-details/:ip`, open to read-only API tokens: the machine panel of the Search page in one call (scan with open ports and last port scan, Freebox host and DHCP lease, port forwarding, UniFi client, switch, AP). Compact projection for tokens, raw Freebox and UniFi objects are never returned. Existing tokens work without being recreated
- `GET /api/plugins/unifi/clients` and the `unifi_get_clients` MCP tool: `switch_name` and `switch_ip` for wired clients, resolved from the switch MAC

---

## [0.10.73] - 2026-10-17

### Added

- `GET /api/connection/summary`: `hosts` and `hosts_active` (devices known and active on the box) and `switch` (ports up, per-port link and speed, no MAC lists)
- `GET /api/updates/status`, open to read-only API tokens: installed and latest MynetworK version from the shared cached check (no forced refresh possible). `update_available` is `null` when the check is disabled
- `GET /api/plugins/unifi/system`: `gateway.wan` (per WAN link up, speed, latency, no public IP) and `gateway.speedtest` (last speed test of the gateway)

---

## [0.10.72] - 2026-10-17

### Added

- `GET /api/connection/summary`, four new optional blocks for MyServices:
  - `firmware_update`: Freebox Server and Player firmware (installed, latest, date, changelog, blog URL) from the dev.freebox.fr check. `null` until the check has run
  - `dhcp`: enabled, address range, gateway, netmask, DNS (static leases are not included)
  - `wifi.networks`: SSID, enabled, hidden, state and band of the main networks. The guest network, keys and passphrases are never returned
  - `health`: temperatures, fans, disk status, highest partition usage, line and fibre state, grouped in one block (existing fields unchanged)

### Removed

- `system.box_authenticated`: it was always dropped by the API token credential filter (name contains "auth")

---

## [0.10.71] - 2026-10-17

### Changed

- Docs: `docs/API-TOKENS.md` describes the new `GET /api/connection/summary` blocks (`storage`, `ftth`, `wifi`, `vms`, `vm_system`, `missing_rights`, `system.lan_ip`)
- `GET /api/connection/summary`: `vm_system` gains `memory_pct` and `cpu_pct` (share of memory and vCPUs allocated to VMs)

---

## [0.10.70] - 2026-10-17

### Fixed

- API tokens settings: the list of reachable routes now shows `/api/plugins/unifi/system` (missing since 0.10.68)

### Security

- Dependencies: `qs` 6.16.1 (SNYK-JS-QS-20943482) and `zod` 4.6.7 (SNYK-JS-ZOD-20510278), both fixed upstream

---

## [0.10.69] - 2026-10-17

### Added

- `GET /api/connection/summary`, optional and backward compatible blocks for MyServices (unknown values are `null`, never `false`):
  - `system.lan_ip`, plus `system.board_name`, `system.user_main_storage`, `system.box_authenticated`
  - `storage`: disks (model, state, `smart_ok`, temperature, size) and partitions (label, fstype, sizes, computed `used_pct`)
  - `ftth` (only when media is `ftth`): SFP presence, signal, link, optical power in dBm. Line IPs are deliberately not exposed
  - `wifi`: `enabled`, access points (name, state, band, channel, width), `stations_count`
  - `vms` (Delta / Ultra only): status, vCPUs, memory, disk type and disk sizes, and `vm_system` (total/used memory and CPUs)
  - `missing_rights`: Freebox permissions the paired app lacks. A denied block is `null`, the rest of the response stays valid
- Explicit field allowlists: serial, MAC, keys and raw Freebox payloads are never returned. Extra blocks share a 30 s cache

---

## [0.10.68] - 2026-10-13

### Added

- `GET /api/plugins/unifi/system`, open to read-only API tokens: UniFi Network version and pending update (`version`, `update_available`), UniFi OS console version, and gateway health (firmware and update, LAN IP, uptime, CPU, memory, temperature). Compact projection only: SSO secrets and controller IPs from `stat/sysinfo` are never returned
- `GET /api/plugins/unifi/devices`: new `upgrade_to_firmware` field, and `lan_ip` for gateways (their `ip` is often the WAN address)
- `GET /api/connection/summary`: new `system.latest_firmware` and `system.update_available` fields, from the dev.freebox.fr blog check
- MCP: `freebox_get_system_info` adds the same two firmware fields; `unifi_get_system_info` now returns the compact summary (`raw=true` for the full object)

### Changed

- UniFi devices: `upgradable` is always present, `true`, `false`, or `null` when the controller does not report it (it was only sent when `true`). Unknown values are `null`, never `false`
- UniFi devices: `cpu_pct` and `mem_pct` are numbers instead of strings, `temperature` is the highest reading of `temperatures[]` with `general_temperature` as fallback (UniFi OS gateways often only fill the former)

---

## [0.10.67] - 2026-10-13

### Added

- `GET /api/health` now returns the app `version` read from `package.json`. The route stays public (no token required), so API token permissions are unchanged

---

## [0.10.66] - 2026-10-12

### Added

- `GET /api/connection/summary`, open to read-only API tokens: Freebox line state, rates and box health (temperatures, fans, uptime, firmware, disk) in one call. Identifiers are left out: no public IP, serial number, MAC or remote-access domain

### Fixed

- `GET /api/wifi/stations` called `/wifi/stations/` on the Freebox, which does not exist (404). It now gathers the stations of every access point and adds an `ap_id` field. This also fixes the MCP tool that lists WiFi stations

---

## [0.10.65] - 2026-10-11

### Fixed

- `scripts/update-version.sh` no longer pre-fills the CHANGELOG and `commit-message.txt` with commits from previous releases. The base lookup missed scoped subjects such as `fix(security): Version 0.10.63` and fell back to a months-old merge; it now starts from the last `v*` tag reachable from HEAD

---

## [0.10.64] - 2026-10-11

### Added

- `MCP_TRUSTED_PROXIES` (optional, IPv4 or CIDR list, comma-separated): the LAN-only check used by MCP, REST API tokens and the Prometheus export reads `X-Forwarded-For` only when the TCP peer is one of these proxies, so the allowlist protects installs exposed behind a reverse proxy. The header is walked from the right, skipping trusted proxies; a missing or invalid header is refused. Unset by default: behavior unchanged

---

## [0.10.63] - 2026-10-10

### Security

- `GET /api/system` and `GET /api/system/version` now require a logged-in user: they exposed the Freebox serial number, MAC addresses, remote-access domain and HTTPS port to anyone reaching the port. The UI only calls them after login
- `GET /api/fs/share` and `GET /api/fs/share/:token` are now admin only: each Freebox share link grants direct access to files (creating links was already admin only). Non-admins see an empty share list
- `GET /api/metrics/prometheus` now returns 404 when the Prometheus export is disabled (it answered regardless), and only accepts the local network and Tailscale, like MCP and API tokens
- The default CORS configuration no longer allows `*.myoueb.fr` (the author's domain, hardcoded for every install). Defaults are now localhost and private networks only; the UI is served from the same origin and never needs CORS. Extra origins can be added in Administration > Security > CORS
- The Freebox native WebSocket (self-signed certificate, verification disabled) now applies the same LAN-only safeguard as the HTTP client: it refuses to connect without certificate verification to a public IP literal in `FREEBOX_HOST`

---

## [0.10.62] - 2026-10-09

### Added

- Read-only REST API tokens (Administration > API tokens, or `scripts/mcp-token.ts --api`) so external LAN services such as MyServices can read the network inventory without a user account. Tokens use a `mwk_api_` prefix, are hashed at rest, can expire and be revoked, and record their last use. See `docs/API-TOKENS.md` for routes and response formats
- `GET /api/dhcp/leases`: Freebox dynamic DHCP leases (no DHCP configuration)
- `GET /api/plugins/unifi/clients` and `GET /api/plugins/unifi/devices`: compact UniFi inventory (same summaries as the MCP tools, never raw controller objects), with `type`, `search` and `limit` filters
- `npm test`: first automated tests (`node:test`, no new dependency) covering the API token auth matrix

### Security

- API tokens only work on a GET/HEAD allowlist (inventory routes), only from the LAN and Tailscale, and are always refused on admin routes and on `/api/mcp`. MCP tokens are refused on the REST API. Credential-like fields are stripped from every API token response
- `@modelcontextprotocol/sdk` 1.30.0 to 1.31.0 (Dependabot alert #74, high: the SDK's OAuth client could send credentials to an authorization server chosen by the MCP server). MyNetwork only uses the server side, but the fix is applied anyway. Supersedes PR #63 (Dependabot) and PR #65 (Snyk), which made the same bump
- Fixed token expiry check: `expires_at` (ISO 8601) was compared as text against SQLite `datetime('now')`, so an MCP token expiring today stayed valid until the end of the UTC day


### CI

- Docker builds pull the official `node:22-alpine` image through Google's Docker Hub mirror (`mirror.gcr.io`), and Snyk scans run the Snyk CLI directly instead of Docker-based actions: Docker Hub's anonymous pull rate limit was failing builds and scans on shared GitHub runners
- Removed the Snyk Code (SAST) job: Snyk Code is not enabled on the Snyk organization, so it never scanned anything. CodeQL and SonarCloud still cover static analysis

---

## [0.10.61] - 2026-10-09

### Security

- Removed the public `POST /api/users/register` route: anyone reaching the port could create an account with no restriction (it was never used by the UI). Accounts are created by admins via `POST /api/users`
- Credentials are now masked for non-admin users: Freebox WiFi and guest keys (`/api/wifi/bss`, `/api/wifi/full`, `/api/wifi/guest/keys`, `/api/wifi/guest/config`), DynDNS password (`/api/connection/ddns/:provider`), FTP password (`/api/settings/ftp`), and the UniFi controller password and API key in plugin settings (`/api/plugins`, `/api/plugins/:id`). Admin responses are unchanged
- `GET /api/plugins/:id/token` (UniFi API key) is now admin only
- WebSocket connections now enforce the same rules as the REST API: revoked (logged out) tokens and disabled accounts are rejected. `/ws/logs` (server logs) is admin only, matching `GET /api/logs`
- The user role and username are now read from the database on every request instead of the token, so demoting or renaming a user takes effect immediately rather than when their token expires (up to 7 days)

---

## [0.10.60] - 2026-10-09

### Security

- `/api/settings/*` had no authentication (except `/theme`): anyone reaching the port could change the Freebox DHCP, port forwarding/DMZ, VPN (users, server config, start/stop), FTP, connection, IPv6 and LAN settings. All routes now require a logged-in user, and every write requires the admin role
- VPN reads that may expose credentials (`/vpn/users`, `/vpn/servers/:id/config`, `/vpn/client`) are now admin only
- `POST /api/system/reboot` and `POST /api/system/reboot/schedule` had no authentication (unauthenticated Freebox reboot). Both now require the admin role; `GET /api/system/reboot/schedule` requires a logged-in user

---

## [0.10.59] - 2026-10-06

### Security

- `source-map-js` 1.2.1 to 1.2.2 (GHSA-68fv-2mgg-jv7q / CVE-2026-93749, DoS via indexed source-map offsets), via a new `>=1.2.2` override. Pulled in by `postcss`, build-time only
- `postcss-selector-parser` 6.1.4 to 7.1.6 (GHSA-rj75-hqrm-r3gf, quadratic selector parsing), via a new `>=7.1.6` override. Pulled in by `tailwindcss` 3; generated CSS checked byte-identical to the 6.x build
- Replaces Dependabot PR #59, which fixed the same advisories by jumping `tailwindcss` 3 to 4 (major, breaks the PostCSS integration and the build)
- Known and accepted: `braces` 3.0.3 (GHSA-vfj7-8cjw-p6xm) has no patched release; it only reaches the build through `tailwindcss` 3 (chokidar/micromatch) with glob patterns from our own `tailwind.config.js`, never user input. Goes away with a future Tailwind 4 migration

---

## [0.10.58] - 2026-10-06

### Changed

- MCP: the IP allowlist now also accepts Tailscale IPv4 addresses (100.64.0.0/10), so MCP clients connecting directly over Tailscale (without a reverse proxy in front) are no longer rejected with 403. Tailscale IPv6 (fd7a:115c:a1e0::/48) was already accepted. The shared `isPrivateNetworkIp()` is unchanged, so the TLS bypass in `insecureAgent` does not extend to this range

---

## [0.10.57] - 2026-10-06

### Security

- MCP: disruptive write tools now carry `destructiveHint: true` (`freebox_reboot`, `freebox_set_wifi_enabled`, `freebox_toggle_wifi_bss`, `unifi_block_client`, `unifi_set_wlan_enabled`, `unifi_restart_device`, `scan_blacklist_add`), so MCP clients ask the user before running them. Limits prompt injection planted in device names, DHCP hostnames or call logs from chaining into a disruptive action
- MCP: `scan_rescan_ip` and `scan_add_manual_ip` only accept IPs inside the configured scan ranges or the host's local /24 networks, so the scanner can no longer be pointed at arbitrary external hosts
- MCP: changing a token's access level or tool overrides now applies immediately to its open sessions (clients get `tools/list_changed`), instead of only on the next session

---

## [0.10.56] - 2026-10-02

### Security

- `shell-quote` 1.10.0 to 1.12.0 (CVE-2026-102422 / GHSA-pqg4-j6r4-53mv, command injection in `quote()`), override raised to `>=1.11.0`. Pulled in by `concurrently`, no user input reaches it

### Changed

- `concurrently` and `cross-env` moved to `devDependencies`: the container runs `tsx` directly, so neither is needed at runtime and both are now pruned from the Docker image

---

## [0.10.55] - 2026-10-02

### Security

- `fast-uri` 4.1.4 to 4.2.1 (GHSA-jvvf-x445-j334) and `ip-address` 10.5.0 to 10.7.3 (GHSA-2vr4-cq9g-pvrc), Dependabot PR #55
- `brace-expansion` 2.1.4 to 2.1.7 and 5.0.9 to 5.0.12 (GHSA-q2hr-2g5m-vwhr and related DoS advisories), build-time only via `vite-plugin-pwa`
- `dompurify` 3.4.13 to 3.4.16 (GHSA-p98j-92pf-mc4p), pulled in by `jspdf`

---

## [0.10.54] - 2026-09-26

### Changed

- MCP tool responses are now compact JSON (no indentation), for every tool
- `unifi_get_clients`, `unifi_get_devices`, `freebox_get_lan_hosts`, `freebox_get_wifi_stations` and `scan_get_devices` return a compact per-item summary instead of the raw controller objects (e.g. ~100 KB down to ~10 KB for 35 UniFi clients), `raw: true` returns the full objects
- New list filters: `type` (wifi/wired, ap/switch/gateway), `search` (name, IP or MAC), `limit`, `status` for the scanner, `activeOnly` (default true) for Freebox LAN hosts
- `freebox_get_call_log` (most recent first, default 50) and `freebox_get_contacts` (default 100) gain `search`/`limit`, the call log also a `type` filter
- `unifi_get_bandwidth_report` range capped at 7 days
- List responses keep the `{success, result}` envelope and add `total` (matches before `limit`)

---

## [0.10.53] - 2026-09-26

### Changed

- Last 6 frontend `catch (error: any)` migrated to `unknown` (UsersPage, SettingsPage, LogsPage, SearchPage), frontend and backend now have no `catch any` left
- Duplicated network/socket/timeout error handling of UsersPage and SettingsPage factored into `getRequestErrorMessage()` (`src/utils/errorMessage.ts`)

---

## [0.10.52] - 2026-09-26

### CI

- Bump ossf/scorecard-action from v2.4.1 to v2.4.4 (SHA-pinned), Scorecard engine v5.5.0

---

## [0.10.51] - 2026-09-25

### CI

- Bump all GitHub Actions still running on the deprecated Node.js 20 runtime to their Node.js 24 releases (SHA-pinned): checkout v7, upload-artifact v7, download-artifact v8, dependency-review v5, docker build-push v7, login v4, metadata v6, setup-buildx v4, codeql upload-sarif v4, sonarqube-scan v8

---

## [0.10.50] - 2026-09-25

### Changed

- Dashboard scanner widget, "Data volume" list: the row numbers are replaced by an arrow showing each device's dominant traffic direction (down arrow when download dominates, up arrow when upload dominates), in live, today and all-time modes

---

## [0.10.49] - 2026-09-24

### Refactor

- Network scanner deduplication. Routes: the 38 identical catch bodies (log + 500 `{ success: false, error: { message, code } }`) go through a single `sendScanError()` helper. Service: the Freebox/UniFi lookup pairs are merged into `getMacFromPlugin()`, `getHostnameFromPlugin()` and `getVendorFromPlugin()`, and `scanNetwork()` / `refreshExistingIps()` share `prepareScanRun()`. About 400 lines removed, same messages, codes and lookup order.
- Typed catch blocks finished: the 21 `catch (error: any)` left in the scanner's duplicated blocks now use `unknown`, so the backend has none left. Frontend: 46 of 52 converted through a new `src/utils/errorMessage.ts` (`getErrorMessage()` returns '' for non-Errors so `getErrorMessage(e) || fallback` keeps the old fallback behavior, plus `getApiErrorPayload()` for `{ error: { message, code } }` shapes). The 6 left sit inside duplicated blocks (UsersPage, LogsPage, SearchPage, SettingsPage).

---

## [0.10.48] - 2026-09-24

### Refactor

- Typed catch blocks in the network scanner (`server/services/networkScanService.ts`, `server/routes/network-scan.ts`): 68 of the 89 remaining `catch (error: any)` / `.catch((err: any) => ...)` now use `unknown`, narrowed through the shared `getErrorMessage()` helper (same pattern as v0.10.35). Stack traces read via `instanceof Error`, ping exec errors (`signal`/`stderr`) through an explicit shape. No functional change: same messages and error codes. The other 21 sit inside legacy copy-pasted blocks (config/purge route handlers, Freebox/UniFi lookup pairs) and are left for a dedicated deduplication pass, since touching them makes SonarCloud count the whole duplicated block as new code.

---

## [0.10.47] - 2026-09-24

### Fixed

- SonarCloud S1082 (8 Minor) and the S6819 accessibility smells (5 existing + the ones a first `role="presentation"` attempt introduced): no more click handlers on non-interactive elements, same behavior for the user.
  - Modal backdrops (custom domain modal, network-scan insights overlay, top traffic overlay): closing on backdrop click now goes through an invisible real `<button>` behind the content (new shared `ModalBackdropButton`), so the content no longer needs `stopPropagation`.
  - Button containers inside clickable rows (parental control profile, files page download task): `stopPropagation` moved into each button's own handler.
  - Menus: the files page ellipsis menus and context menu are excluded by a ref check in the row click / window "click outside" handlers instead of blocking propagation; the user menu (already closed by `useClickOutside`) just drops its redundant handler.
  - Network-scan status filter group uses a `<fieldset>` instead of `role="group"`.
  - Files page: the ellipsis button + dropdown menu, duplicated between grid and list views, is now defined once and shared.

---

## [0.10.46] - 2026-09-24

### Fixed

- SonarCloud: all 17 Major bugs addressed.
  - TV channel list: a channel numbered `0` would have rendered a stray "0" (S6439).
  - "Elegant" theme input glow: `!important` inside `@keyframes` is invalid and made the browser drop the declaration (css:S4655).
  - Explicit null/undefined checks on the in-flight request/login promises used for deduplication (S6544 x6, no behavior change).
  - `reduce()` in latency min/max seeded with an initial value (S6959 x2).

### Removed

- Dead code: two never-rendered `{false && ...}` blocks on the search page (S6638 x2), identical-branch ternaries/conditionals in the theme picker, ping timeout and Prometheus URL builder (S3923 x4).

---

## [0.10.45] - 2026-09-24

### Fixed

- Freebox WiFi card: connected devices per network now come from the BSS's own `sta_count` (exact per network) instead of the per-band LAN total, which gave the same count to every network sharing a band (the Ultra's two 5 GHz radios, guest network). The estimated load follows. Falls back to the per-band estimate when the API doesn't report it.

---

## [0.10.44] - 2026-09-24

### Fixed

- Dashboard "Network summary" card, Freebox DHCP badge: now says "Inactive (bridge mode)" when the Freebox is in bridge mode (it doesn't serve DHCP even if its config still says enabled, and the IP usage rows are hidden), and "Unavailable" when the DHCP config couldn't be read, instead of a misleading "Inactive".

### Removed

- Dead `NetworkSummaryWidget.tsx` (never imported, superseded by `NetworkSummaryDashboardWidget.tsx`).

---

## [0.10.43] - 2026-09-24

### Fixed

- Freebox WiFi card empty: with a different SSID per band, every BSS reports `is_main_bss: false` and they were all filtered out. A non-main BSS is now only skipped when a main one exists on the same radio (guest network case). Also fixes the access point list on the dashboard Freebox card.
- Freebox plugin WiFi stats: band now read from `status.band` (was always "WiFi"), and `enabled` reflects the real state (enabled in config and radio active) instead of being hardcoded to true. Fixes the Prometheus/InfluxDB WiFi network metrics.
- 6 GHz filter for models without 6 GHz (Pop, Revolution) read non-existent fields and never filtered anything, now reads `config.band` / `status.band`.
- Freebox VMs card stuck on "VMs not available" while the VMs page listed them: the VM store error was never cleared after a successful fetch, and the card checked the error before the VM list.

### Changed

- Freebox sub-pages moved under `/freebox`: `/freebox/tv`, `/freebox/phone`, `/freebox/files`, `/freebox/vms` (like `/freebox/analytics`). Old URLs (`/tv/guide` etc.) redirect to the new ones, keeping the tab and query string.

---

## [0.10.42] - 2026-09-24

### Added

- Full per-device traffic overlay, opened from a new expand button on the dashboard "Data volume" section: wide sortable table (#, device with vendor icon, IP, MAC, download, upload, total) with its own Live / Today / All-time switch. Live shows current rates, Today/All-time show accumulated volumes.

### Changed

- The network-scan insights "view all" top-traffic overlay now uses the same shared component (previously a page-local, today-only table), so it also gains the period switch and the IP/MAC columns.
- Live UniFi top clients raised from 10 to 50 devices (WebSocket feed), so the overlay and the network-scan live pulse cover more devices. Compact views (dashboard widget, UniFi traffic tab) still show the top 10.
- `GET /api/plugins/unifi/top-clients-history` limit cap raised from 50 to 500.

---

## [0.10.41] - 2026-09-24

### Added

- Opt-in "Live traffic" toggle on the network-scan table (off by default, never persisted across reloads): reuses the existing UniFi WebSocket feed to pulse the Download/Upload cells (with an amber dot) for whichever devices are currently in the live top 10 by throughput, with the live rate shown in the cell's tooltip alongside the existing "today" total. Icons added to the Download/Upload columns in the top-traffic overlay's sortable table.

### Changed

- Online/Offline counts moved from separate badges into the status filter buttons themselves ("Online 40", "Offline 5"), removing the now-redundant standalone badges.

### Refactor

- Deduplicated the Download/Upload table cell rendering (today's total, live pulse, tooltip) into a single `renderTrafficCell()` helper.

---

## [0.10.40] - 2026-09-24

### Added

- Per-device traffic (download/upload) on the `/network-scan` page: two new sortable columns backed by UniFi's daily traffic accumulation, matched by MAC (`--` for devices that aren't a known UniFi client, or when the UniFi plugin is disabled). New `POST /api/network-scan/traffic/batch` route and `UniFiClientTrafficRepository.getTodayByMacs()`.
- Third "Top traffic (today)" column in the network-scan insights card, alongside "New devices" and "Frequently disconnected". Each category now previews 5 devices with a "view all" overlay for the rest; the top traffic overlay is a dedicated sortable table (device / download / upload / total).

### Changed

- Removed the `Avg1h`/`Max` latency columns from the network-scan table; their values now show as tooltips on the latency cell and the monitoring icon (only when latency monitoring is enabled for that device), alongside the live ping.
- UniFi per-client traffic accumulation now polls every 30s instead of 60s, matching the network-scan page's own refresh cadence.

### Refactor

- Deduplicated the network-scan insights UI: shared `InsightPreviewColumn`/`InsightOverlay` components and row-building, and shared `renderLatencyStats`/`getTrafficEntry` helpers for the latency tooltips and traffic cells.

---

## [0.10.39] - 2026-09-23

### Changes

- chore: remove dead disk/docker code and validate wifi.ts route IDs
- fix: extract shared chart axes/tooltip in TrafficTab to fix SonarCloud duplication

---

## [0.10.38] - 2026-09-23

### Changes

- feat: UniFi traffic/overview UX pass and no-gateway empty states
- fix: round kb/s speed display in unifi traffic table

---

## [0.10.37] - 2026-09-23

### Added

- Per-device traffic monitoring for UniFi clients, across the home dashboard, `unifi/traffic` and `unifi/analyse`: live top consumers, plus "today" and "all-time" accumulated volume backed by two new SQLite tables (`unifi_client_traffic_daily`, purged after 30 days; `unifi_client_traffic_totals`, never purged) fed by a new background poller independent of the dashboard being open.
- Live device icons on the UniFi bandwidth graph: a small cluster of the current top consumers anchored near the live edge, click-to-pause (freezes the graph so there's time to hover a tooltip), and a toggle to hide them.
- Sortable columns on the traffic tables in `unifi/analyse` and `unifi/traffic` (shared `useSortableTable` hook).
- Device vendor icons on every UniFi client table that was missing one.
- 5/10 result toggle for "Top Pire Latence" on the home dashboard.
- Graph source/period choices (Freebox/UniFi, Live/1h/6h/24h/7j, Live/Today/All-time) now persist per browser across reloads.
- Auto-refresh (30s) for the `unifi/traffic` page's historical bandwidth ranges — previously required a manual refresh click.

### Fixed

- UniFi per-client throughput was read from the wrong fields (`rx_rate`/`tx_rate`/`phy_*_rate`/`sw_*_rate`), which are the negotiated WiFi PHY rate or Ethernet port speed, not actual traffic — confirmed by two unrelated IoT devices showing the exact same fabricated rate simultaneously. Now reads the controller's real instantaneous rate fields (`rx_bytes-r`/`tx_bytes-r`) everywhere this was used (live ranking, "Clients by throughput", "Analyse trafic UniFi").
- UniFi controller session could be destabilized (temporary lockout) by too-frequent client-list polling from the live WebSocket feed; throttled to every 3rd tick.

### Changed

- Live bandwidth graph window shortened from 5 minutes to 60 seconds for both Freebox and UniFi, for a more readable "what's happening right now" view.
- "Volume de données" section moved above "Top Pire Latence" on the home dashboard.

---

## [0.10.36] - 2026-09-22

### Fixed

- Hotfix: `insecureAgent`'s LAN-only check (added in 0.10.33) DNS-resolved hostnames like `mafreebox.freebox.fr` to validate them, but Docker's DNS resolver isn't the Freebox itself — the hostname resolved to a public IP (or failed to resolve), so every connection was refused as "non-private", breaking Freebox connectivity entirely in production. Only IP literals are validated against private ranges now; hostnames (admin-configured, not attacker input) are trusted as-is.

---

## [0.10.35] - 2026-09-22

### Changed

- Backend code quality cleanup, no functional change:
  - `catch (error: any)` replaced with `catch (error: unknown)` across 14 backend files, narrowing with a new shared `getErrorMessage()` helper instead of unchecked `.message` access
  - Duplicated route ID parsing/validation (`parseInt` + `isNaN` + throw, or `Number.isInteger` + manual 400 response) extracted into shared `requireIntParam()`/`parseStrictIntParam()` helpers, applied across `downloads.ts`, `mcpTokens.ts`, `auth.ts`, `users.ts`, and `vm.ts`
  - Same error messages/codes and validation semantics preserved everywhere

---

## [0.10.34] - 2026-09-22

### Fixed

- The Wireshark/IEEE vendor database was never actually refreshed from the remote source after the very first successful download: `updateDatabase()` always reused the existing local file as long as it was structurally valid, even when called specifically because the weekly refresh interval had elapsed. Now always attempts a fresh download when called. Since this path now runs on every scheduled refresh instead of effectively once, the download is written to a temp file and only swapped in atomically once validated, so a failed/partial transfer can no longer destroy the previously-good local database.
- Port scanning had no guard against concurrent runs, letting two overlapping scans corrupt each other's shared progress/abort tracking. Added a reentrance guard (closing a TOCTOU in the check itself) wrapped in try/finally, so a scan can no longer get permanently stuck as "in progress" after an unexpected error.
- `GET /api/system/server`: reduced cognitive complexity (it was blocking SonarCloud's quality gate) by extracting host hostname/uptime detection into dedicated helpers; also dropped a redundant `fs.access` check before `fs.readFile` and parallelized the two lookups.

---

## [0.10.33] - 2026-09-22

### Security

- Fixed a major authentication gap: 16 route files (`auth`, Wi-Fi, LAN, VM, downloads, file manager, parental controls, TV, DHCP, calls, contacts, connection, speedtest, capabilities, latency monitoring, notifications, system info) had no `requireAuth` check at all. Anyone reachable on the API (LAN, or the directly-published Docker port) could manage files on the Freebox storage, disable parental controls, start/stop/delete VMs, change Wi-Fi config and guest keys, etc. without any token. GET routes now require `requireAuth`, write/destructive routes require `requireAuth` + `requireAdmin`, matching the convention already used elsewhere in the codebase.
- `authService`: fail fast if `JWT_SECRET` is not set instead of silently falling back to a hardcoded default secret.
- `insecureAgent`: the shared TLS-bypass agent (used for Freebox/UniFi self-signed certs) now refuses to connect, before the TLS handshake, unless the resolved host is private/LAN — closing a possible misuse path if a call site were ever miswired, and a DNS-rebinding TOCTOU window in that same check.
- `bruteForceProtection`: switched from a fixed window to a sliding window, so pacing login attempts around the window reset no longer avoids the lockout.
- `mcpAuthMiddleware`/`rateLimiter`: no longer trust the spoofable `X-Forwarded-For`/`CF-Connecting-IP`/`X-Real-IP` headers on the directly-published Docker port.
- `downloads`: validate `fileBase64`/`filename`/`downloadDir` before use — unescaped quotes/CRLF could break out of the multipart body sent to the Freebox.
- `networkValidation`: private-IP detection now also covers IPv4 link-local (169.254.0.0/16) and IPv6 (link-local, unique local).
- Pinned `actions/checkout` in the security-review CI workflow to a commit SHA (GitHub code-scanning alert, OSSF Scorecard Pinned-Dependencies check).

---

## [0.10.32] - 2026-09-20

### Added

- Topology: new "Connection" filter (Ethernet / Wi-Fi) to show only wired or only Wi-Fi clients. Infra devices (gateway/switch/AP/repeater) are exempt from this filter — only client-ish cards (client, VM, grouped-devices) are affected.

---

## [0.10.31] - 2026-09-20

### Changed

- Topology: Wi-Fi and VM client cards inside an accordion (AP or VM-host stack) are now ordered by ascending IP address instead of an arbitrary insertion order. Wired clients keep their existing physical-port ordering; IP is only used as a tie-break when there's no port to sort by.

---

## [0.10.30] - 2026-09-20

### Fixed

- Topology: a switch/gateway port could show more than one duplicate link card for the same physical connection (e.g. Proxmox VMs with randomized MACs that the OUI-based VM detection doesn't recognize as VMs, landing as separate raw client cards on the same port). Any port now caps at 2 link cards; the rest fold into a single "+N devices" summary card, independent of the existing VM-host grouping.

---

## [0.10.29] - 2026-09-16

### Added

- Vendor icon fallbacks for Microsoft, Amazon (generic catch-all), Nintendo, Logitech and Ecobee OUI vendor strings, and for Anker (label-only, no registered OUI block).
- Extended the Philips Hue matcher to also cover "Philips Lighting BV" (Signify's legal name before its 2018 rebrand), still seen on older device OUI registrations.

---

## [0.10.28] - 2026-09-16

### Fixed

- Freebox firmware check missed general releases shipped to all models (e.g. "Freebox Server 4.13.0"): the blog scraper's regex required a parenthetical model list ("(Pop)", "(Ultra)", ...) that is omitted on those posts, silently skipping them.

---

## [0.10.27] - 2026-09-16

### Security

- Override `proxy-addr` (transitive dependency of Express) to 2.0.8, fixing a critical user impersonation vulnerability (CVE-2026-90711 / GHSA-jqcg-44mw-7w3h) flagged by Snyk code scanning.

---

## [0.10.26] - 2026-09-16

### Added

- Freebox OS API version displayed in the Freebox plugin card, next to the Server/Player firmware versions.

### Fixed

- Forcing a Freebox firmware check ("Check now") displayed nothing until navigating away and back: the `force` endpoint returned the scrape result without merging it with the currently installed firmware, so `currentVersion`/`updateAvailable` stayed empty.

---

## [0.10.25] - 2026-09-15

### Added

- Real brand logos for Netatmo, VMware, Debian, Veeam, Freebox (generic + Ultra/Revolution/Pop models) and UniFi (+ dedicated UniFi AP icon), via a new `local:<slug>` bundled-icon tier alongside the existing simple-icons/lucide/custom tiers.
- Generic device-family icons selectable in the vendor-icon picker: switch stack, rack bay, network hub, Wi-Fi antenna, wired cable/Ethernet port, alarm siren, sensor probe, solar panel.
- Selectable black/white color variants for near-black brand marks (Apple, Linksys, LIFX, Bose, Sonos, Corsair, DJI) that were nearly invisible against this app's dark-only themes; auto-detection defaults to white.

### Fixed

- `/network-scan` column tooltips (MAC, IP, Actions, monitoring, First seen) are now consistently bottom-anchored to the hovered row, like the existing Latency/Ports tooltips, instead of floating at an inconsistent height above cell-wide hover targets.
- Vendor-icon picker crash when previewing a `local:` icon (rendered as a Lucide component instead of an `<img>`), which broke the vendor edit overlay on `/network-scan`.
- Server-side `vendor_icon` validation rejected the new `local:` icon id prefix.
- Several `/topology` port and VM tooltips were hardcoded in French or English instead of following the selected app language.

---

## [0.10.24] - 2026-09-15

### Added

- MCP admin panel: connected sessions now show token name, client IP, user-agent, connection time and last activity, instead of a bare count.
- MCP Overview tab now polls status every 15s so connected sessions update without a manual page refresh.

### Changed

- Session tracking (`server/mcp/sessionRegistry.ts`) moved from a simple counter to per-session metadata, resolved against the owning MCP token.

---

## [0.10.23] - 2026-09-14

### Added

- MCP tokens: per-token access level (`full`/`read_only`) with per-tool overrides, so a token can be restricted to read-only tools by default while still allowing (or blocking) specific tools individually.
- Admin UI: each token row in the MCP Tokens tab now expands inline into a Capabilities panel (per-category and per-tool toggles, override reset), replacing the previous standalone "Capabilities" tab and its token selector dropdown. Category badges on each token row show at a glance whether all, some, or none of a category's tools are enabled.
- New tokens are now created with full access by default, then the newly created token's Capabilities panel opens automatically so the admin can validate or restrict it as part of the creation flow.

### Changed

- Access level and tool-override changes made from the Capabilities panel now refresh the Tokens list immediately, so its category badges never go stale.

### Security

- MCP Streamable HTTP sessions are now bound to the token that created them: every request against an existing session (`Mcp-Session-Id`) must present that same token. Previously any active MCP token could reuse another token's session id (e.g. one leaked via server logs) and inherit its permissions instead of its own.

### Removed

- Dropped the standalone `GET /api/mcp/tools` endpoint (unused global tool catalog view) and its "Capabilities" quick-link, now that permissions are managed per-token from the Tokens tab.

---

## [0.10.22] - 2026-09-14

### Added

- Vendor icon catalog: added logos for Amazon Alexa/Fire TV, Denon, JBL, Tesla, Corsair, Elgato, Steam Deck, DJI, Bambu Lab and AVM/Fritz!Box, plus fallback icons for Canon, Brother, Xerox, Lexmark, Nanoleaf, Govee, SwitchBot, Broadlink, Yeelight, Eufy, Western Digital, Buffalo, Asustor, D-Link, Zyxel and Eero.
- Vendor icon picker: icons are now grouped by category (Network, Computers & OS, Mobile, Storage & NAS, Smart Home, Audio & Media, Cameras & Security, Printing, Gaming, Servers & Self-hosted, Vehicles & Other, Generic) instead of a flat brands/generic split, and the popover is wider to fit the larger catalog.

### Changed

- Vendor icon column: the whole cell now opens the icon picker on click (with a hover overlay), including on rows with no detected vendor, replacing the small pencil badge that was only visible once a vendor name was set.
- `POST /api/network-scan/:id/vendor`: a manually picked icon is no longer discarded when the vendor name is empty, decoupling icon selection from vendor name detection.
- Docker runtime image: removed npm/npx/corepack (unused, the container runs `tsx` directly), eliminating the vulnerable dependencies npm bundles internally (`tar`, `brace-expansion`) from the final image. The `mcp-token` CLI script is now run via `docker exec ... node_modules/.bin/tsx scripts/mcp-token.ts` instead of `npm run mcp:token` (documented in README.md/README.fr.md).

---

## [0.10.21] - 2026-09-14

### Fixed

- Vendor icon detection: added VMware brand icon, Reolink and Devolo fallback icons, and fixed Shelly detection (devices run on Espressif chips with no dedicated OUI block, so the icon was matched via hostname instead of the OUI vendor string).

---

## [0.10.20] - 2026-09-14

### Changed

- Network scan insights: extracted the shared `InsightDeviceName` component for the "new devices" and "flaky devices" clickable link rendering, removing duplication.
- `NetworkScanRepository.update()`: refactored the per-field update branches into a data-driven loop to reduce cognitive complexity.
- `VendorIconPicker`: extracted the trigger button's resolved-icon preview into a dedicated component, and replaced `autoFocus` on the search input with an imperative focus effect for accessibility.
- Replaced `JSON.parse(JSON.stringify(...))` deep-clone with `structuredClone()` in `NetworkScanConfigModal`.
- Replaced array-index React keys with content-derived keys in `BarChart.tsx`.

### Fixed

- `VendorIconPicker`: popover now also stops propagation of keyboard events, fixing a SonarCloud reliability issue (click handler without a matching keyboard listener).
- Removed an unused `MiniBarChart` import in `NetworkScanPage.tsx`.
- Simplified `slugifyIconTitle()`'s trailing-dash regex to remove a redundant quantifier.

---

## [0.10.19] - 2026-09-14

### Changed

- Network scan overview: merged the Total/Online/Offline cards into a single stacked bar chart card, and merged the insights card with the empty placeholder card, reducing the stats row from 4 cards to 3.
- Network scan overview: darkened the stacked bar chart colors (online/offline) for a less saturated look.
- Network scan insights: "New devices" and "Frequently disconnecting" lists now render in 2 columns to avoid scrolling, and device names are clickable, navigating to the search page with the device's IP.
- Network scan table: vendor name editing is now fully independent from vendor icon editing; renaming a vendor never affects its icon, and the icon's own hover overlay is always available regardless of the vendor-name edit state.
- ThreatMapTab: switched map tiles from CARTO (now requiring an API key) to Esri's keyless World Dark Gray Canvas basemap.

### Fixed

- StackedMiniBarChart: hover tooltips are now rendered through a portal, fixing an issue where they were invisible due to being clipped by an ancestor's `overflow-hidden`. Tooltips now include the hour, total IPs, online and offline counts per bar.
- Fixed vendor icon picker translations (`networkScan.vendorIconPicker.*`) that were nested under the wrong top-level locale section in both `en` and `fr`.

---

## [0.10.18] - 2026-09-13

### Fixed

- Network scan table: latency column tooltip now anchors to the bottom of the hovered row like the ports tooltip, so it always grows upward instead of shifting when its content spans multiple lines.
- Network scan table: offline devices now show a red circle-cross icon with a compact last-seen date in the latency column, and the tooltip adds a "down since" duration line (hours/days/months/years).

### Added

- Vendor icon detection: Home Assistant, TrueNAS, Unraid, OpenMediaVault, Portainer, Pi-hole, AdGuard Home, Plex, Jellyfin, Nextcloud, Grafana, Nginx Proxy Manager, Homebridge, ESPHome, Tasmota, Zigbee2MQTT, IKEA, iRobot, Wemo, Bose, SmartThings, LIFX.

---

## [0.10.17] - 2026-09-13

### Fixed

- BandwidthHistoryWidget: removed duplicated Freebox/UniFi range-selector markup flagged by SonarCloud, now rendered from a single mapped list of ranges.
- NetworkScanPage: removed duplicated tooltip-reset and bounding-rect logic across row/action hover handlers flagged by SonarCloud, factored into shared `hideAllTooltips()` and `getEventRect()` helpers.

---

## [0.10.16] - 2026-09-13

### Fixed

- Live bandwidth chart interpolation now renders smoothly for Freebox and UniFi widgets.
- Network scan table: MAC address column now truncates via CSS ellipsis matching its actual cell width instead of a hardcoded 8-character cutoff.
- Network scan table: switched to `table-layout: fixed` with explicit column widths, removing the unpredictable space redistribution that `table-auto` caused on wide screens.
- Network scan table: the Open Ports hover tooltip is no longer height-capped or scrollable — it always renders its full content and grows upward from the hovered row so it never covers it.

### Added

- Network scan table: the IP address hover tooltip now shows the full address (IPv4 or IPv6) with a copy-to-clipboard button, matching the MAC tooltip style.
- Network scan config: warns when `nmap` is not installed on the server and disables the "port scan after full scan" option accordingly (new `GET /api/network-scan/nmap-status` endpoint).
- Port scanning now runs with a concurrency pool (5 hosts in parallel) instead of sequentially, speeding up full-network port scans.

### Changed

- Network scan table: adjusted column widths (IP, Hostname, Vendor, MAC, Open Ports) for better readability on large screens.
- PWA service worker: `index.html` navigation now uses a network-first strategy instead of serving a possibly stale cached app shell, preventing users from being stuck on an outdated build after a deploy.

---

## [0.10.15] - 2026-09-12

### Fixed
- Removed duplicate GHCR badge on the settings info page

## [0.10.14] - 2026-09-10

### Fixed

- Network scan table: extracted a reusable `CopyButton` component to remove the duplicated copy-to-clipboard state/handler pairs behind the MAC address and Open Ports tooltip buttons, fixing SonarCloud's new-code duplication alert on `NetworkScanPage.tsx`.

---

## [0.10.13] - 2026-09-10

### Fixed

- App: recover automatically from `Failed to fetch dynamically imported module` errors caused by a deploy replacing the built assets while a tab still holds an older chunk manifest, by reloading the page once when Vite's `vite:preloadError` event fires.

### Added

- Network scan table: Open Ports tooltip is now wider (640px, up from 420px) with a 5-column port grid instead of 3, showing more ports per row and reducing the tooltip's height.
- Network scan table: Avg1h and Max latency values are now rounded to whole milliseconds (no decimals), and their column padding was tightened, freeing horizontal space for the Vendor column.
- Network scan table: MAC address tooltip now has a copy-to-clipboard button.
- Network scan table: Open Ports tooltip now has a "copy all" button to copy the full port list at once.

### Changed

- Network scan table: Vendor and Hostname source badges are temporarily hidden (display only, easily re-enabled) to give more room to the Vendor and Hostname columns while testing.

---

## [0.10.12] - 2026-09-10

### Fixed

- Network scan table: hover tooltips (Status, Latency, Actions, First Seen, IP, Open ports, scatter icon) had a mismatch between the hardcoded width/height used to compute their position and their actual rendered size, causing them to overlap the cursor or neighboring rows. Added explicit CSS width classes matching the position calculation, and corrected height estimates for wrapped/localized text.
- Network scan table: Latency column tooltip position shifted inconsistently row to row because its own content could wrap to one or two lines depending on the value's width. The tooltip now always renders as two fixed lines (date, then latency), and the in-cell latency value no longer wraps either.
- Network scan table: fixed a missing separating space between label text and interpolated values in the Latency tooltip, introduced by trimming translated strings instead of using a stable placeholder to split them.
- Dev server: added the LAN IP used to reach the dev server over the local network to Vite's `allowedHosts`.

### Added

- Analytics: uptime history is now a GitHub-style weekly contribution heatmap (one square per day) instead of a bar chart, with a status legend (Up / Reboot detected / Down / No data).
- Analytics: fan speed gauge bar, and an empty state for the temperature history chart while data is still being collected.
- Analytics page is now nested under Freebox (`/freebox/analytics`) and reachable from the Search/Topology icons on the network scan page.
- Vendor icons: added Ecovacs, a Tapo fallback resolving to the TP-Link icon, and Freebox/Aqara fallback icons.

---

## [0.10.11] - 2026-09-09

### Fixed

- Topology: Freebox is its own Wi-Fi AP but never got a dedicated `ap` node (its AP MAC is merged onto the gateway box), so its wifi clients never qualified for the accordion layout and piled up in the same flat grid as wired clients. Added a `gateway-mixed` placement that splits a parent's children by edge medium instead of node kind: wired clients cascade diagonally to the left of the Freebox card, wifi clients cascade diagonally to the right, each card stepping further from the axis instead of stacking in a strict column so no card sits behind another link. UniFi AP / VM-host accordions are untouched.
- Topology: the "Stale" status filter is renamed "Ghost" (label only) and is now mutually exclusive with "Online". Combining both mixed live devices with long-gone Freebox DHCP cache entries that have no reliable medium info, producing an unreadable graph. Offline still combines freely with either.

### Added

- Vendor brand icons (simple-icons) across Topology, Devices list, Network scan, Search and UniFi Clients: known manufacturers (Apple, Samsung, TP-Link, Ubiquiti, Synology, Proxmox...) now show their real logo instead of a generic device-type icon, resolved from the OUI vendor string or hostname for software-only brands with no MAC block (Veeam, LaMetric).

---

## [0.10.10] - 2026-09-09

### Fixed

- Network scan, Latency Monitoring modal: opening it for an IP monitored over several days could freeze the tab. Pings run every ~15s, so 3-30 days meant 17k-170k raw rows returned by `/api/latency-monitoring/measurements/:ip` with no aggregation, plus a broken client-side downsampling step that could return up to 3x its stated cap instead of reducing point count.
- The measurements endpoint now aggregates in SQL once the raw row count exceeds a threshold, bucketing by time window and keeping the min/max latency per bucket so spikes stay visible while the payload is capped at a few thousand points regardless of the selected period.
- Replaced the Recharts scatter chart (one SVG node per data point) with a canvas-based renderer (`LatencyCanvasChart`) for this view, removing the per-point DOM/fiber overhead that made large point counts unusable.

---

## [0.10.9] - 2026-09-08

### Fixed

- CI: the arm64 leg of the multi-arch Docker build hung silently under QEMU emulation on the last two releases (`v0.10.7`, `v0.10.8`), hitting the 45-minute job timeout and leaving those tags amd64-only on GHCR. Rewrote `docker-publish.yml` to build amd64 and arm64 as separate jobs on their own native GitHub-hosted runners (`ubuntu-latest` / `ubuntu-24.04-arm`, no QEMU), each pushing by digest, merged into one multi-arch manifest by a final job.

---

## [0.10.8] - 2026-09-08

### Fixed

- Topology: `SWITCH_INLINE_PORTS_MAX`, `PORT_CELL_WIDTH`, infra/vm-host/client card widths, and `FAN_OUT_COUNT` were each duplicated across two or three of `topologyLayout.ts` / `TopologyNodeCard.tsx` / `TopologyGraph.tsx`, kept in sync only by comments. Centralized into a new `topologyConstants.ts`, removing the drift risk.
- Topology: `nudgeSelected()` called `api.post()` from inside the `setManualPositions()` state updater, which can run twice under React StrictMode in dev. The network call now runs once, after the state update.
- Topology: the `nodes` useMemo rebuilt a new object for all graph nodes (~125 on a typical scanned network) on every click just to toggle the `selected` ring on 1-2 of them, forcing React Flow to re-render every card. Split into a `baseNodes` memo (position/editingMode, independent of selection) and a `nodes` memo that only creates a new object for the selected node.
- Topology, Tree mode: increased dagre `nodesep` (vertical spacing between sibling nodes) from 24 to 40px.

---

## [0.10.7] - 2026-09-08

### Added

- Admin UI, MCP tab: instant on/off kill switch for MCP access, independent of the `MCP_ENABLED` env var (which only takes effect on the next restart). Backed by a new DB flag (`mcp_runtime_disabled`), checked first in `mcpAuthMiddleware` before the IP allowlist and token check. Lets you cut access immediately if a client machine holding the token is suspected compromised, without touching `docker-compose.yml` or restarting the container.
- Admin UI, MCP tab: connected clients count, using the existing (already wired, previously unused by the UI) `sessionRegistry` active-session counter.
- Admin UI, MCP tab, Setup: contextual hint next to the token-generation command showing whether a token is already configured (rotates it) or not (generates a new one), and a combined warning when access is on but no token is configured yet.
- `Docs/MCP_DEV_SETUP.md`: moved the detailed `-u node` / Docker capabilities explanation out of the Setup tab (kept short there) into a dedicated dev reference doc.

### Fixed

- Admin UI, MCP Setup tab and README (EN/FR): the `<LAN-IP>:<PORT>` placeholders in the Claude Code / Claude Desktop / other-client examples are now filled in automatically from the `HOST_IP` and `DASHBOARD_PORT` env vars when set (same vars and fallback already used by `scripts/mcp-token.ts` and the server startup banner), instead of always showing a placeholder the user had to fill in by hand.
- `scripts/mcp-token.ts`: the printed URL used the container's internal port (`config.port`, always 3000 in Docker) instead of the host-mapped external port, so following it literally produced an unreachable URL for anyone using the default `docker-compose.yml`. Now uses `HOST_IP`/`DASHBOARD_PORT` with the same fallback as the rest of the app.
- Admin UI, MCP tab, Setup: clarified that the Claude Code `.mcp.json` block is an auto-generated result of the `claude mcp add` command (written to `~/.claude.json` by default, not a project `.mcp.json`, unless `--scope project` is passed), not a file the user needs to create by hand.
- Copy-to-clipboard buttons: `navigator.clipboard` requires a secure context (HTTPS or localhost) and is undefined when this LAN-only app is reached over plain HTTP on a LAN IP, a common case here, silently breaking every copy button. Added the same `document.execCommand` fallback already used elsewhere in the app, plus a visible "Copied" / "Copy failed" label instead of only a small icon swap.

---

## [0.10.6] - 2026-09-08

### Fixed

- `scripts/mcp-token.ts` / `server/services/mcpAuthService.ts`: `npm run mcp:token` could print "MCP token generated" and a working-looking token even when persisting it to the database silently failed (e.g. a read-only SQLite file) — the token was never actually saved and would never authenticate. `generateToken()` now throws when the hash can't be persisted, and the CLI script exits non-zero with a clear error instead of a false success.
- README (EN/FR) and the admin MCP Setup tab: the documented `docker exec -it mynetwork npm run mcp:token` command fails with a SQLite "readonly database" error on the default hardened compose (`cap_drop: ALL` without `CAP_DAC_OVERRIDE`) — `docker exec` without `-u` attaches as a different user than the app's own process, which owns the database file. Fixed the documented command to `docker exec -it -u node mynetwork npm run mcp:token`, with an explanation of why `-u node` is required.

---

## [0.10.5] - 2026-09-08

### Fixed

- `src/components/topology/TopologyGraph.tsx`: last remaining `tsc` error, `handleNodeDragStop` was typed with React's synthetic `React.MouseEvent` while React Flow's `OnNodeDrag` type expects the native `MouseEvent | TouchEvent`. `npx tsc` is now fully clean (0 errors).
- `scripts/update-version.sh`: the auto-generated `CHANGELOG.md` entry and `commit-message.txt` were pulling in stale, already-released commit messages on every version bump. Two compounding bugs: (1) the commit range lookup only matched `git log --grep="^feat: Version"`, missing every `fix: Version` bump (the majority of recent releases); (2) even fixed, it fell back to dumping the last 10 raw commits whenever the range was empty — which is always the case in this project's workflow (one commit per version bump, no intermediate commits). Both fixed: the grep now matches any conventional-commit prefix, and the fallback was removed so the script produces an honest empty stub instead of stale content when there's nothing new to report yet.

---

## [0.10.4] - 2026-09-08

### Changed

- README (EN/FR): reworked the MCP setup section into a clear two-step flow (generate the token, then connect a client) and added the missing `docker exec -it mynetwork npm run mcp:token` command for the default Docker deployment (previously only the bare `npm run mcp:token`, which doesn't apply when the app runs in a container).
- Admin UI: split the MCP tab into two sub-tabs, "General" (status, token state, endpoint, timestamps) and "Setup" (the same step-by-step token generation and client connection instructions as the README, with copy-to-clipboard code blocks and a per-client selector for Claude Code / Claude Desktop / other Streamable HTTP clients).

---

## [0.10.3] - 2026-09-08

### Fixed

- `require()` does not work in this ESM project (`"type": "module"` since the initial commit) and has silently failed on every call since day one, always falling through to a try/catch fallback. Two remaining active occurrences (beyond the `undici` case already fixed in 0.10.2) are now fixed:
  - `server/config.ts`: `getPublicUrl()` never actually read the `public_url` value configured via the admin UI database, since `require('./database/models/AppConfig.js')` always threw — silently falling back to the `PUBLIC_URL` env var despite the "Database > Environment variable" priority documented in the code. Replaced with a static ESM import.
  - `server/services/networkScanService.ts`: `isDockerEnv()` / `getHostPathPrefix()` used `require('fs')` (redundant on top of being broken, since `fs` was already statically imported in the same file) for the `/proc/self/cgroup` and `/.dockerenv` detection. Replaced with the existing static import.
- Removed `server/plugins/freebox/FreeboxApiService.ts`: dead code carrying the same broken `require('undici')` pattern as the case fixed in 0.10.2, with no import anywhere in the codebase (the active `FreeboxApiService` class lives in `server/services/freeboxApi.ts`).

### Docs

- README (EN/FR): documented how to connect an MCP client to the built-in MCP server — Claude Code CLI (`claude mcp add --transport http`), Claude Code `.mcp.json`, Claude Desktop (native Connectors on paid plans, or the `mcp-remote` stdio bridge otherwise), and any other Streamable HTTP-capable client.
- `scripts/update-version.sh`: now also syncs the version badge in `README.fr.md` (previously only `README.md` was updated, leaving the French badge stale).

---

## [0.10.2] - 2026-09-08

### Security

- **Global TLS certificate verification was silently disabled for the entire process since day one.** `freeboxApi.ts` and `UniFiApiService.ts` tried to build a scoped `undici` `Agent` (`rejectUnauthorized: false`) for Freebox/UniFi's self-signed certificates only, via `require('undici')`. That call always failed — `require` doesn't exist in this ESM project, and `undici` wasn't even an installed dependency — so the fallback path ran on every boot, setting `NODE_TLS_REJECT_UNAUTHORIZED=0` for the whole Node process (every outbound HTTPS request, not just Freebox/UniFi). Fixed: added `undici` as a real dependency, pinned to `^6.21.0` to match the undici version bundled inside Node 22's own `fetch()` (`process.versions.undici` = `6.24.1` as of Node 22.22.3) — a newer major (8.x) breaks the internal dispatcher protocol (`invalid onRequestStart method`) since Node's global `fetch()` doesn't accept a dispatcher built by a different undici major. Switched from `require()` to a static ESM `import`, and factored the agent into a shared `server/utils/insecureAgent.ts` (previously duplicated in both files). Verified: `NODE_TLS_REJECT_UNAUTHORIZED` stays `undefined` for the process lifetime, Freebox/UniFi logins still succeed (real device tests).

### Fixed

- Docker production image was missing `scripts/mcp-token.ts` entirely — `.dockerignore` excluded the whole `scripts/` folder from the build context, and the Dockerfile never copied it into either stage. `npm run mcp:token` (documented as the only way to generate the MCP token) would have failed inside a production container with a module-not-found error. Fixed both layers (`.dockerignore` now keeps `scripts/mcp-token.ts` via a negation pattern; `Dockerfile` copies `scripts/` in the builder and runtime stages) and verified end-to-end with a real `docker build` + `docker run npm run mcp:token`, token generated successfully.

---

## [0.10.1] - 2026-09-08

### Fixed

- 108 preexisting TypeScript errors (`Argument of type 'string | string[]' is not assignable to parameter of type 'string'`) across 18 route files, caused by Express 5's `ParamsDictionary` type now allowing `string[]` for route params (to support new path-to-regexp v6+ wildcard patterns). This project has no such routes, so every param is always a plain string at runtime — the error was type-level only, no logic bug. Added `server/utils/params.ts` (`param(req, key)`), a small helper that extracts a route param as a string, used at each of the 108 call sites instead of `req.params.xxx` directly. Investigated a single-point fix via TypeScript module augmentation first (redeclaring `ParamsDictionary`); not possible, TypeScript rejects merging two incompatible index signatures for the same interface.

---

## [0.10.0] - 2026-09-08

### Added

- Native MCP (Model Context Protocol) server, mounted at `/api/mcp`, LAN-only (IP allowlist + dedicated bearer token, `npm run mcp:token`). Lets an MCP client (e.g. Claude Desktop) query and control the dashboard directly
- Freebox MCP tools: read-only (system info, connection status, WiFi, LAN hosts, DHCP, switch ports, call log, contacts) plus non-destructive actions (reboot, toggle WiFi/BSS, add a DHCP static lease)
- UniFi MCP tools: read-only (devices, clients, WLANs, network config, port forwarding, bandwidth report, system info, sites) plus new write actions (block/unblock a client, enable/disable a WLAN, restart a device) — these write paths did not exist before and required new `UniFiApiService` methods (local controller API only, not the Site Manager cloud API)
- Network scanner MCP tools: read-only (stats, devices, blacklist) plus non-destructive actions (trigger/rescan, add a manual IP, rename a host, blacklist add/remove)
- Read-only "MCP" admin tab (status, token configured, endpoint, last used) — token generation stays CLI-only since the admin panel is internet-exposed while the MCP endpoint must stay LAN-only

### Fixed

- WiFi dashboard: the Freebox Ultra's two 5GHz radios share the same SSID, which was collapsed into a single entry by an SSID-based dedup key. Now deduped by physical radio (`phy_id`) + band instead
- Scheduled reboot silently failing (or throwing) when the Freebox session had expired; now checks `isLoggedIn()` first and logs the actual success/failure result
- `/api/mcp/status` always showing "Error loading MCP status" in the admin UI regardless of actual status: the route wasn't wrapping its response in the `{success, result}` envelope the frontend API client expects

### Security

- `fast-uri` (transitive via `@modelcontextprotocol/sdk` → `ajv`): high-severity advisory, pinned `>=3.1.6` via override
- `qs` (transitive via `express`): medium-severity advisory, pinned `>=6.16.0` via override
- `npm run security:cve`: 0 vulnerabilities

---

## [0.9.11] - 2026-08-15

### Fixed

- `npm ci` failing in CI (Docker build, Snyk scan) with `package.json and package-lock.json ... not in sync`. The deeply-nested `overrides` added in 0.9.10 (`vite-plugin-pwa` → `workbox-build` → `ajv`/`glob`/`@trickfilm400/rollup-plugin-off-main-thread` chains pinning `fast-uri` and `brace-expansion`) produced a lockfile that `npm install` accepted but `npm ci` rejected — a known npm limitation with multi-level nested overrides on optional/transitive deps. Removed the nested overrides; kept the flat ones (`tough-cookie`, `tar`, `rollup`, `shell-quote`). `npm audit` and `npm run security:cve` remain at 0 vulnerabilities without them.

---

## [0.9.10] - 2026-08-15

### Security

- Closed 30 Dependabot alerts. Direct/parent bumps: `postcss` `^8.5.10` to `^8.5.23`, `vite` `^6.4.2` to `^6.4.3`, `ws` `^8.20.1` to `^8.21.0`, `react-router-dom` `^7.16.0` to `^7.18.2`, `concurrently` `^9.2.1` to `^9.2.4`, `express-rate-limit` `^8.3.2` to `^8.5.1` — closes high-severity postcss path-traversal, vite `server.fs.deny` bypass, react-router CSRF/DoS/open-redirect/XSS, and ip-address SSRF bypass alerts
- Overrides raised: `shell-quote` `>=1.8.4` to `>=1.9.0` (quadratic-complexity DoS), plus nested overrides pinning `fast-uri` (`>=3.1.5`) and `brace-expansion` (`>=5.0.9` / `>=2.1.4`) deep inside `vite-plugin-pwa` → `workbox-build` (host-confusion and ReDoS, build-time only)
- Lockfile refresh closed `dompurify` (via `jspdf`, supersedes Dependabot PR #21), `nanoid` (via `postcss`), `body-parser`/`@babel/core` (via `express`/`@vitejs/plugin-react`)
- `npm audit` and `npm run security:cve`: 0 vulnerabilities. `vite build`: OK

---

## [0.9.9] - 2026-05-27

### Added

- Topology: dynamic vertical gap between a switch and its clients, scales `50 → 130 px` with the client count above 3, so dense fan-outs don't mask the switch label
- Topology: new `vm-accordion` placement for vm-host (Proxmox / KVM) stacks, mirrors `wifi-accordion` (two columns flanking the parent), VMs no longer overflow into the neighbouring wired clients
- Topology: virtual edges (vm-host → VM) now share the wifi spine routing (central bottom handle + bezier curve), the layout reads "non-cabled" at a glance like wifi

### Changed

- CI: Docker build multi-arch (amd64 + arm64) now runs **only on `v*.*.*` tag pushes**. Push to `main` builds amd64 only, saves ~15 min/build during day-to-day iteration. ARM64 users still get a multi-arch image on every release tag

---

## [0.9.8] - 2026-05-27

### Security

- CORS: when admin sets `allowedOrigins = "*"`, force `credentials:false`. Resolves CodeQL alert #228 (permissive CORS misconfig on `server/index.ts:194`). The wildcard+credentials combo was already browser-rejected, this aligns the server config with the spec

---

## [0.9.7] - 2026-05-27

### Fixed

- PWA stale chunks after deploy: enable `skipWaiting` + `clientsClaim` + `cleanupOutdatedCaches`, deny SPA fallback for `/assets/*`, stop runtime-caching scripts. Resolves `Failed to load module script` / MIME mismatch on lazy chunks when users had a tab open before the deploy
- Router: silence noisy `[App] Invalid currentPage, falling back to dashboard` warning for plugin-gated routes (`freebox`, `unifi`) while plugin connection status is still loading
- iOS PWA: add `<meta name="mobile-web-app-capable">` alongside the deprecated `apple-mobile-web-app-capable` (W3C standard, removes deprecation warning in Chrome/Edge)

---

## [0.9.6] - 2026-05-27

### Fixed

- Sonar bug S3923 on `server/index.ts:1072`: version label color ternary had two identical branches, simplified
- Sonar `S6606` on `server/index.ts:196`: use `??` nullish coalescing instead of `!== undefined ? : true` for `allowCredentials`
- JWT secret warning now uses top-level `await` instead of a promise chain (sonar S7059)

### Removed

- Unused locals `displayIP` and `containerPadding` in `server/index.ts`

### Refactored

- Extracted nested ternary `ingressBlock` from `urlBlockLines` for readability

---

## [0.9.5] - 2026-05-27

### Fixed

- CSP violation on favicon: decode inlined data: SVGs without `fetch()` in `useFavicon` hook (Vite inlines small SVGs as data: URLs, which broke `connect-src`)
- Add `data:` to CSP `connect-src` as defense-in-depth

### Added

- npm scripts `security:cve` and `security:cve:strict` (cve-lite OSV scan)

---

## [0.9.4] - 2026-05-25

### Security

- Pinned `shell-quote` to `>=1.8.4` via `overrides` (transitive via `concurrently`, devDependency only) to fix the Snyk-reported critical Arbitrary Command Injection in `quote` (object-token shape validation, released 2026-05-22). Closes Snyk Open Source alert #223 and Snyk Container alert #224.

---

## [0.9.3] - 2026-05-25

### Security

- Bumped `ws` from 8.18.3 to 8.20.1 to fix GHSA-q8mj-m7cp-5q26 (uninitialized memory disclosure in `websocket.close()`).
- Bumped `qs` from 6.14.2 to 6.15.2 (transitive via express) to fix GHSA-58qx-3vcg-4xpx (remotely triggerable DoS in `qs.stringify` with comma-format arrays).

Resolves Dependabot PR #18, alerts #14 and #15, and Scorecard alert #222.

---

## [0.9.2] - 2026-05-19

### Fixed

- Network scan: removed hardcoded Docker IP exclusions (`172.17–31.x.x` and `10.10.x.x` ranges that were always skipped). These collided with legitimate LAN ranges on hosts that use `172.16/12` as their working network (fixes #17). Container and virtual interfaces are still filtered for auto-detection, but by interface name (`docker*`, `br-*`, `veth*`, `tun*`, `tap*`) instead of by IP range. What the user configures is now authoritative.

### Added

- Network scan: multi-VLAN support. Scan configuration now accepts a primary `defaultRange` plus up to 10 `additionalRanges`. The history filter, the refresh job, and `isScanRangeAuthorized()` all honour the full list. UI editor added to the Network Scan settings modal (add/remove rows, EN/FR i18n).
- Auto-detect now also skips `tun*` and `tap*` interfaces (VPN tunnels) in addition to the existing `docker*` / `br-*` / `veth*` filters.

### Changed

- Removed `isDockerIp()` and the now-dead `getConfiguredRange()` (singular). All call sites read the full configured range list via `getConfiguredRanges()`.
- Scan error messages updated: no more references to the removed `172.17–31` / `10.10` exclusions; messages now point users to the scan configuration modal.

---

## [0.9.1] - 2026-05-14

### Fixed

- Restored `dagre@0.8.5` in `package-lock.json`. The `v0.9.0` Docker image never built because `update-version.sh`'s non-anchored `sed` replaced `dagre`'s `"version": "0.8.5"` field along with the project's own version, leaving the lockfile with `dagre@0.9.0` while the resolved URL still pointed at `dagre-0.8.5.tgz` — `npm ci` then rejected the inconsistent lockfile. `v0.9.1` is functionally identical to the intended `v0.9.0`; use this tag instead. Follow-up: anchor the `sed` in `update-version.sh` to the top-level workspace version fields only, or switch to `jq` for the structured edit.

---

## [0.9.0] - 2026-05-14

### Added

- Topology: custom React Flow edge (`AvoidingEdge`) draws orthogonal V-H-V (or H-V-H) paths whose horizontal segment is positioned to avoid crossing any infra card (gateway / switch / AP / repeater / VM-host). The path geometry is computed in the edge component itself; obstacle bboxes are passed via `data.obstacles` from the edges memo. Replaces `smoothstep` as the default edge type for the topology graph.
- Topology: L-bend deconfliction. When multiple cables share the same natural mid-Y (typical case: gateway → multiple siblings at the same rank), their bends are fanned vertically with `BEND_STEP = 16 px` per cable. The cable reaching the furthest-right target gets the topmost slot so its long H segment runs above the shorter cables.
- Topology: wired-client speed is shown as a top-right badge on the client card and the inline connection chip below the IP is tier-coloured (sky for gigabit / good RSSI, amber for fair, orange for slow, red for very slow). Same gradient as the `/unifi/traffic` "Clients by throughput" badges.
- Topology: Wi-Fi cables now draw as a bezier curve (vs orthogonal smoothstep for wired) so the wireless link is unambiguous at a glance. Wi-Fi cable colour follows the RSSI tier gradient (sky → amber → orange → red).
- Topology: ethernet cables to UniFi wired clients now show the negotiated port speed. When `cli.sw_port_speed` is missing (some UniFi controller versions don't expose it), we fall back to the parent switch's `port_table[sw_port].speed` — the authoritative source on the switch itself.
- PWA install support via `vite-plugin-pwa`. Chrome / Edge now show the "Install app" button in the URL bar. Manifest + service worker are generated at build time. Online-first design: `/api` and `/ws` are explicitly excluded from the cache so the app always hits the live backend; only the static app shell is precached. 3 PNG icons (192 / 512 / maskable-512) generated from `favicon-racine.svg` with a slate background. `index.html` gets `theme-color`, `apple-touch-icon`, and an SVG favicon link.
- Reboot button in the Freebox page footer now shows an amber pulsing dot when a firmware update is available, with a `title` tooltip clarifying that a reboot is required to apply the update. Driven by `useFreeboxFirmwareStore` via a boolean selector — re-renders only when the update flag actually flips, not on every periodic firmware-check refresh.
- Dashboard Freebox card firmware notification is now click-to-pin: clicking the amber alert pins its changelog/blog tooltip open so the blog link inside stays reachable. Pinned state shows a brighter amber border + ring and adds an `×` close button. Dismissed by clicking the `×` or anywhere outside the bubble. Plain hover behaviour is preserved when not pinned.
- i18n: new `freebox.firmwareUpdate.rebootToApply` and `freebox.firmwareUpdate.close` keys (EN + FR).

### Changed

- Topology: wired clients on a switch are now laid out as a HORIZONTAL row to the right of the switch, sorted left-to-right by physical port number (port 1 closest to the switch). Replaces the previous vertical `wired-column` / `wired-split-columns` placements. The row height (`CLIENT_H`) is small enough that dagre can stack additional infra ranks below it; an extra 90 px reserved height (`WIRED_ROW_BOTTOM_PAD`) is added under the row so the next rank's cables don't graze the client cards.
- Topology: infra siblings (sub-switches / APs hanging off the same parent) are now reordered to match the parent's physical port order — the AP wired on port 1 sits leftmost, the sub-switch on port 5 to its right, etc. Dagre still picks the X coordinates; we only permute the assignment among siblings using `portByChild` (from edge `portIndex`).
- Topology: cables to an infra target (sub-switch / AP / VM-host) use an asymmetric V-H-V (`INFRA_TARGET_BIAS = 0.78`) so V_1 (above the L) is longer than V_2 (below) — keeps the H segment well clear of the wired-client row above and close to the target's top.
- Topology: port-aware edges (`p${portIndex}` source on a switch) now always route to the target's TOP handle so the cable shape stays V-H-V and the obstacle-avoidance machinery can deflect it. Source port handle is preserved — the physical port mapping is intact.
- Topology: horizontal-mode spacing bumped from `nodesep=100 / ranksep=130` to `nodesep=160 / ranksep=180` for more breathing room between sibling infras and rank levels. Editable mode unchanged (`nodesep=60 / ranksep=90`, denser).
- Extracted the click-outside `useEffect` pattern (3 inline copies in `UserMenu.tsx`, `ThemeSection.tsx`, and the new firmware notification) into a shared `useClickOutside(ref, handler, enabled)` hook in `src/hooks/`. Pure refactor — same behaviour, single source of truth. The mousedown-over-click rationale (so a toggle button's own `onClick` can still fire after the handler closes the popup) is documented in the hook.

### Fixed

- SonarCloud quality gate fixes: hooks ordering in `PluginSummaryCard` (4 hooks were called after the `if (!plugin) return null` early return — violates Rules of Hooks); cognitive complexity of `buildUniFiClientEdge` (19 → 7, via `resolveUniFiLinkSpeed` extraction) and `buildHierarchicalLayout` (21 → 12, via `reorderInfraSiblingsByPort` extraction); merged back-to-back `candidates.push()` calls in `findClearMidY`; replaced redundant `event.target as Node` cast in `useClickOutside` with an `instanceof Node` type guard.

---

## [0.8.5] - 2026-05-12

### Added

- Topology side panel now shows ping latency and open ports for the selected device. Data is fetched lazily from the scan-reseau plugin (`GET /api/network-scan/:ip`); the section is hidden when the device is unknown to the scanner.

### Changed

- Renamed topology layout mode `grouped` to `editable`. It is the only mode where manual drag/lock positions persist; the new name reflects that capability directly. UI label: "Editable" / "Éditable".
- Increased vertical gap between wired client cards under a switch from 24 to 36 px to match the Wi-Fi stack spacing.

### Fixed

- Freebox Player POP no longer appears twice in the topology graph. Its wired and Wi-Fi interfaces are listed as separate hosts in `/lan/browser` with consecutive MACs sharing a 5-octet prefix; the collector now groups them and keeps the entry with `access_point` info.

---

## [0.8.4] - 2026-05-12

### Added

- UniFi model catalogue mapping ~50 controller model codes (US24P250, UDMPRO, USPL24P, …) to display name, port spec (rj45/sfp/sfpPlus), PoE budget, and a `hasDedicatedUplink` flag. Generic across all UniFi setups since codes are globally consistent.
- Port-table snapshot cache (`unifi_device_snapshots` SQLite table) keyed by device MAC. When a UniFi switch/gateway is offline at scan time, topology replays the cached port grid greyed-out so the infra card retains its physical port layout. New "cached" badge + dimmed/grayscale styling on the affected grid.
- Wi-Fi accordion placement: AP clients flank the AP in two staggered columns. Spine gap is dynamic (`wifiSpineGap`) so the AP card sits centred inside the channel between columns.
- Wired-column placement: ≤6 wired clients in a single column right of the switch; 7-12 split across right + left columns.
- Sibling Y equalisation (`equalizeSiblings`): every AP / sub-switch / VM-host hanging off the same parent now lands on a single horizontal line, regardless of how many children each has.

### Changed

- `wrappedTreeLayout` and `groupedLayout` now share a single `buildHierarchicalLayout` helper (parameterised by dagre `nodesep`/`ranksep`). The grouped view keeps tighter spacing for a denser dashboard look.
- Uplink chip rule split by device kind via shared `shouldRenderUplinkChip` helper (single source of truth for the card renderer and the edge picker): switches always get a chip on any uplink port; gateways with a dedicated WAN slot (UDM-Pro, USG, USG-Pro-4, UDM-SE, UDM-Pro-Max, UXG-Pro) get a chip; gateways with no dedicated WAN (UDR, UDM basic, UCG-Ultra, UXG-Lite) keep the uplink port inside the regular grid coloured mauve.
- Wi-Fi connection chip on client cards now uses the sky palette (sky-500/200/400) to match the dashed Wi-Fi edge colour (`#7dd3fc`), replacing the previous pink.
- Backend resolves `modelDisplay` from the catalogue with hostname / family + port count fallback for unknown models, included in topology metadata for the frontend.

### Fixed

- Freebox node de-duplication: the box reported itself as an `access_point`, producing both `freebox:box` (no MAC) and a duplicate `mac:XX:…` "Freebox" gateway. `ensureFreeboxAp` now merges non-repeater APs into `freebox:box` (sets its MAC); `collectScanReseauOverlay` indexes existing nodes by MAC field so scan-reseau records also merge into `freebox:box` instead of creating a stray duplicate.
- `CLIENT_NODE_WIDTH` mismatch between `topologyLayout.ts` (170) and `TopologyNodeCard.tsx` (220) — client cards were overflowing their reserved layout slot. Aligned to 220.
- Pre-fetch all UniFi snapshots once in `collectUniFi` (`findAll()`) instead of one SELECT per device in `replayCachedPorts`.
- Replaced O(N²) `clientNodes.find()` / `positionedInfra.find()` inside the four placement helpers with O(1) `Map` lookups built once per layout pass.

### Removed

- ~200 lines of dead code in `topologyLayout.ts`: `buildGroupNode`, `placeChildrenInGroup`, `buildGroupedDagre`, `positionGroupedInfra`, `positionGroupedGroups`, `GroupedDim`, `computeGroupedDims`, and the unused constants `CLIENT_W`, `CLIENT_GAP_X`, `CLIENT_GAP_Y`, `GROUP_PAD`, `GROUP_PAD_TOP`, `CLUSTER_VGAP`, `WIFI_CLUSTER_VGAP`, `WIFI_CLUSTER_COLS`, `WIRED_FLAT_MAX_COLS`, `ORPHAN_PARENT`. Also dropped `GroupNodeData` interface and `getGroupParentId` helper.

---

## [0.8.3] - 2026-05-11

### Changes

- chore(release): version-bump only. Same code as v0.8.02 — the docker-publish workflow uses `docker/metadata-action` with `type=semver,pattern={{version}}`, which strictly rejects leading zeros (`0.8.02` is not valid SemVer). The tag-triggered build produced no image tag and failed with `tag is needed when pushing to registry`. Renaming to `0.8.3` fixes the CI pipeline.
- docs: future patch versions will follow plain SemVer (`0.8.4`, `0.8.5`, … `0.8.10`) — no zero padding.

---

## [0.8.02] - 2026-05-11

### Changes

- refactor(topology): `tagVMClients` no longer spreads an empty object — falls back to a fresh `{}` when metadata is absent, then mutates in place.
- refactor(topology): `buildHypervisorAnchorMap` cognitive complexity 19 → ~6 by extracting `pickAnchorCandidateFromEdge` (flat guard chain) for the per-edge filtering.
- fix(topology): replace `String(metadata.hypervisor ?? '?')` with a `typeof === 'string'` guard so an accidental object value can't render as `'[object Object]'` in the VM detection log line.
- refactor(topology): `TopologyNodeCard` cognitive complexity 17 → under 15 by extracting `VmHostInfoRow`, `StatusDot`, `pickRingClass` and the `INFRA_KINDS_SET` constant. Pure refactor — no behaviour change.

---

## [0.8.01] - 2026-05-11

### Changes

- feat(topology): detect virtual machines by MAC OUI and group them into a synthetic `vm-host` node. Known prefixes: Proxmox / KVM (`bc:24:11`, `52:54:00`), VMware, VirtualBox, Xen, Hyper-V, Docker bridge. Matching OUIs are also added to the wireshark vendor defaults so other parts of the app benefit.
- feat(topology): hypervisor anchoring — when UniFi sees ANY VM of a hypervisor on a switch port, ALL VMs of that hypervisor are reparented to that same UniFi port. The Freebox shadow edges to those VMs are dropped. Result: one stack card appears on the real UniFi port, not on `freebox:box`. Anchor is skipped when UniFi sees the hypervisor on multiple ports (ambiguous multi-host cluster).
- feat(topology): physical host fold-in — when a vm-host is synthesized and exactly one non-VM client sits on the same port, fold its label / IP / MAC / vendor into the vm-host card so the user doesn't see a phantom duplicate next to the stack.
- feat(topology): vm-host card is larger (340 × 120) and carries an info row with active / inactive VM counts, hypervisor label, and the physical host MAC. Vendor of the physical NIC is shown under the IP.
- feat(topology): clear status dot in the top-right of every client card — emerald = active, rose = offline / inactive. Makes the deactivated-in-Freebox state visually unambiguous.
- feat(topology): bucket VMs by `(parent, port)` when port info is available, otherwise fallback to `(parent, hypervisor)` so Freebox-only views (no port reporting) still group correctly.
- chore(i18n): add `vm-host` kind + `virtual` edge legend keys to the en + fr locale bundles.
- chore(topology): filter-state migration — `loadPersistedFilters` now backfills missing entries from `ALL_KINDS` / `ALL_SOURCES`, so adding a new kind (like `vm-host`) doesn't silently hide it for users with older localStorage state.
- `SCHEMA_VERSION` 13 → 18 (covers all intermediate steps and forces a fresh rebuild on existing deployments).

---

## [0.8.00] - 2026-05-11

### Changes

- refactor(topology): split `pruneFreeboxNodesDuplicatingUniFiInfra` to drop SonarCloud cognitive complexity from 23 to under 15. Extracted four file-local helpers: `collectUnifiInfraIps` (returns `Set | null` so the caller can bail on "no UniFi infra"), `isFreeboxOrphanClient`, `looksLikeUnifiInfraClone`, and `dropEdgesTouching`. Pure refactor — same two-pass algorithm, no behaviour change.

---

## [0.7.99] - 2026-05-11

### Changes

- feat(topology): hide offline devices by default — only the `online` status chip is selected on first load; offline + stale remain one click away.
- feat(topology): persist source / kind / status filter chips in `localStorage` (key `topology.filters.v1`) so the user's selection survives page navigation and reloads. Stored values are whitelisted on load to ignore corrupted state.
- fix(topology): drop duplicate UCG node visible via Freebox DMZ. The Freebox sees the UniFi gateway as a LAN client with its WAN-side MAC while UniFi reports the LAN-side MAC, so MAC-based dedup misses it. Backend now prunes client / unknown nodes that Freebox sees but UniFi doesn't, when either (a) IP matches a UniFi-sourced infra node's IP or (b) vendor contains "Ubiquiti" while UniFi infra is present. `SCHEMA_VERSION` bumped 11 → 13 to invalidate stale snapshots.
- feat(topology): enlarge gateway / switch / AP / repeater cards (240 → 300 px, taller header, bigger icon and label, larger inline port cells). Port handle math recalibrated so ethernet edges still land on the centre of each port. `dagre` layout constants synced (NODE_WIDTH, PORT_CELL_WIDTH, row height) so nodes don't overlap.
- feat(topology): legend now includes a "Ports" section explaining the port-cell colours (wired client = emerald, fibre / SFP+ = cyan, uplink = mauve, down = slate) and the PoE active dot.
- chore(i18n): add port-legend keys to the `en` and `fr` locale bundles.

---

## [0.7.98] - 2026-05-10

### Changes

- refactor(topology): SonarCloud cleanup — extract `WifiSummaryBlock` + `SsidRow` components from the side-panel JSX (was nested 5 levels deep), flatten the SSID-line nested template literal via a `bandSuffix` variable.
- refactor(topology): SonarCloud cleanup (already on `main` since v0.7.97) — split `collectScanReseauOverlay` / `stats` useMemo into named helpers (`mergeScanReseauIntoExisting`, `buildScanReseauNode`, `dedupeNodesByMac`, `buildEdgeMediumSets`, `classifyClientMedium`, `computeTopologyStats`), extract `EdgeRow` component, switch `bands.sort()` to `localeCompare`. Pure no-behaviour-change.
- docs(readme): add the topology screenshot to the "other screenshots" gallery.

---

## [0.7.97] - 2026-05-10

### Changes

- fix(topology): scan-reseau ICMP success now ORs `metadata.active=true` onto existing Freebox/UniFi nodes (per MAC). Devices the scanner just pinged are now shown online even when the Freebox ARP cache claims otherwise. Snapshot schema bumped to v11 to invalidate stale caches.
- fix(topology): header counters dedupe by canonical MAC and skip stale Freebox-only offline entries — the inflated "wired clients" count caused by Freebox DHCP cache + double attribution is gone.
- fix(topology): wired/wifi classification prefers wifi when a client has both attributions (UniFi wifi authoritative over Freebox ethernet default).
- feat(topology): side panel — switch links list shows `[Pn]` port badge + friendly client name (was showing raw MAC), sorted by port number. AP/repeater cards get a new "Wi-Fi summary" block (client count, aggregate speed, breakdown by SSID and band) and per-link rows show SSID · band · signal · speed on a second line.
- feat(topology): side panel widened to `w-96` (`w-[28rem]` on lg+) so the new wifi columns fit without truncation.
- feat(topology): auto fitView on first data load and on layout-mode change (Grouped / Tree / Horizontal). Periodic poll updates no longer steal pan/zoom.

---

## [0.7.96] - 2026-05-10

### Changes

- refactor(topology): SonarCloud fixes — split `extractSwitchPorts` into `parsePoePower` / `isPoeActive` / `buildSwitchPort` to bring cognitive complexity back under 15, switch to `Number.parseFloat` / `Number.NaN`, drop the nested template literal in the Uplink chip tooltip

---

## [0.7.95] - 2026-05-10

### Changes

- feat(topology): multi-uplink (LAG) support — every member port gets its own mauve "Uplink" chip on top of the switch card, with its own React Flow target handle so each cable lands on the right port
- feat(topology): PoE indicator switched from full-cell colour to a small amber dot in the corner — a PoE port now keeps its real category colour (uplink mauve, fibre cyan, client green) instead of being masked
- fix(topology): port-aware uplink edges fall back to the centre-top handle when the target switch has no chip for that port (≤12-port grid required) — was causing React Flow to silently drop the edge
- fix(topology): UniFi PoE detection accepts firmware variants where `poe_power` is a string ("5.234") and `poe_enable` / `poe_mode` carry the active state
- refactor(topology): card-card derivation memoised; per-edge target lookup switched from O(N×M) `find` to a `Map` for large topologies; bumped snapshot schema to v10 to invalidate the singular-uplink-port cache
- chore: remove Rybbit analytics — no more telemetry, ever
- docs(readme): add Dependencies & supply-chain section
- fix(header): use globalSearch label on /search to match other pages

---

## [0.7.94] - 2026-05-10

### Changes

- refactor(topology): split pruneRedundantFreeboxEdges to bring cognitive complexity under 15
- fix(topology): port-aware uplink edges + per-mode handles + sonar refactors
- feat(topology): grouped-only edit mode, side-routed Wi-Fi, port grids, page compaction, image export
- feat(topology): persistent manual positions + uplink port detection + Freebox/UniFi edge dedup
- feat(topology): replace duplicate side-panel port grid with stat tiles + DMZ cascade + uplink fixes
- feat(topology): wifi cluster border animation + broader uplink MAC detection
- feat(topology): mauve uplink edges with wider side routing
- feat(topology): UniFi switch port grid (front-panel view)
- feat(topology): animated dashed Wi-Fi edges + offline visible by default
- feat(topology): retry on missing plugin + online-only default + clearer stats
- fix(topology): schema versioning + connection badges + sonar refactors
- fix(topology): move legend to bottom-right and minimap to top-right to avoid overlap with React Flow controls
- fix(topology): correct edge direction (parent->child) + sonar refactors + UX
- docs(changelog): rewrite 0.7.93 entry with the actual topology feature scope

---

## [0.7.93] - 2026-05-10

### Added

- **Network topology page** (`/topology`): interactive graph of devices, links and bandwidth, aggregated from active plugins (Freebox + UniFi + scan-reseau). Three layout modes — Grouped clusters (graphviz-style), Tree (left-right hierarchy) and Horizontal (top-down with multi-row client wrapping). Filter panel (sources / types / status) is collapsible. Brand SVG logos for Freebox and UniFi infrastructure nodes, side panel with full device details, fit-view button, and a minimap that appears only when zoomed in.
- **Topology snapshot model** in SQLite (`topology_snapshots`, single row): `GET /api/topology` returns the cached snapshot instantly; `POST /api/topology/refresh` (admin) recomputes from plugins. No realtime polling — updates run via daily cron at 04:00 plus a one-shot initial build at boot when no snapshot exists.
- **WAN edge**: automatic link from the UniFi gateway(s) to the Freebox box so the diagram reflects the real ISP → Freebox → UCG → switches/APs → clients hierarchy.
- **Stale Freebox cache filter**: devices reported only by Freebox while inactive (typically migrated to UniFi DHCP and still cached on the box) are classified as `stale` and hidden by default. Toggle to show them is in the filter panel.
- **Header navigation**: Search and Topology badges on the dashboard and topology pages; Topology entry alongside Search/Administration in the footer (navigation, not a plugin shortcut).

### Changed

- `UniFiPlugin` exposes `getTopologyData()` returning raw devices + clients without the bandwidth/sysinfo bundle from `getStats()`.
- `Header` accepts a new `onTopologyClick` prop; `Footer` filters tabs and plugin shortcuts on the topology page.
- Added `@xyflow/react` and `dagre` for the interactive graph viewer.

---

## [0.7.92] - 2026-05-07

### Changed

- **Dependencies**: bumped `express-rate-limit` `^8.3.2` → `^8.5.1` and transitive `ip-address` `10.1.0` → `10.2.0` (Dependabot group update, PR #16). Both are minor bumps with no API changes.

### CI

- Skip SonarCloud job on Dependabot PRs (`if: github.actor != 'dependabot[bot]'`). Dependabot's restricted token can't access `SONAR_TOKEN`, which was producing a spurious red check on every dependency bump.

---

## [0.7.91] - 2026-05-02

### Fixed

- Update check: dedupe CI check-runs by name in build validation. Orphaned `in_progress` reruns were invalidating already-successful `build-and-push` runs, causing newer versions to be skipped from the update check (e.g. v0.7.90 not surfaced despite the GHCR image being published).

---

## [0.7.90] - 2026-05-02

### Security

- Bumped `postcss` to `^8.5.10` (installed: 8.5.13) to address GHSA-qx2v-qp2m-jg93 (XSS via unescaped `</style>` sequences in CSS AST stringification). PostCSS is build-time only (Tailwind/Vite); runtime exposure was nil but the OSV alert is now resolved.

### Changed

- `package.json`: direct `postcss` dependency `^8.4.47` → `^8.5.10`. `package-lock.json` regenerated; transitive consumers (autoprefixer, tailwindcss/postcss-\*, vite) dedupe to 8.5.13.

## [0.7.89] - 2026-04-18

### Added

- **Collapsible changelog viewer** — Shared `ChangelogBlock` component reused in Info tab (expanded) and Update tab (collapsible, closed by default)
- `update-version.sh` now captures commits since last `feat: Version X.Y.Z` and populates new CHANGELOG entry automatically

### Changed

- Backfilled CHANGELOG entries 0.7.83-0.7.88 and 0.2.1 from git history
- Removed 18 empty placeholder entries (including malformed `## [0.]`)
- SonarCloud code smells: `await` on async tasks + dataset typing

### Fixed

- **Dashboard React #185 on widget mount** — Force `isAnimationActive={false}` on `BandwidthHistoryWidget` Areas to break the recharts animation/polling data-identity loop

---

## [0.7.88] - 2026-04-17

### Fixed

- **fix(dashboard): React #185 crash on tab resume** — Infinite render loop in `BandwidthHistoryWidget` when returning to an inactive tab
- **unifiRealtimeStore**: skip history growth while `document.hidden`, mirroring the `connectionStore` guard
- **BandwidthHistoryWidget**: added `debounce={100}` on `ResponsiveContainer` to break the recharts `ResizeObserver` loop on visibility change

---

## [0.7.87] - 2026-04-17

### Changed

- **SonarCloud code smells** (minor):
  - `UniFiApiService`: `charCodeAt` → `codePointAt` in `stripTrailingSlashes`
  - `speedtest`: `numGroup` uses `String.raw`; rtt/mac matches use `RegExp.exec`
  - `SettingsPage`: changelog parser uses `RegExp.exec` instead of `String.match`

---

## [0.7.86] - 2026-04-17

### Changed

- **SonarCloud S5852 false positives** — Flagged regex patterns are either linear-time (disjoint character classes, no nested quantifiers) or use bounded `{n,m}` quantifiers. Annotated with `NOSONAR` + justification.
- Affected: `networkScanService`, `speedtest`, `freeboxFirmwareCheckService`, `plugins` route, `searchService`

---

## [0.7.85] - 2026-04-17

### Added

- **UniFi gateway temperature badge with tooltip**
- Backend extracts temperature from UDM gateway device (`general_temperature` or `temperatures[]`), exposes `systemStats.temperature` + `temperatureSensors`
- Dashboard card: system state block with temp badge below mini-graphs
- Header badges: purple CPU badge on dashboard, emerald badge on `/unifi` — hover tooltip listing individual sensors

### Changed

- Removed redundant flickering "Temperature" block from Freebox card footer (duplicated info; inconsistent `sys.temp_cpum` mapping on Ultra)

---

## [0.7.84] - 2026-04-17

### Fixed

- **SonarCloud S5852 ReDoS hotspots** — Replace potentially super-linear regex patterns with bounded equivalents or non-regex string operations
- `UniFiApiService`: extracted `stripTrailingSlashes()` helper (5 occurrences)
- `networkScanService`: `/[\d.]+/` → `/\d+(?:\.\d+)?/` in ping output parsers
- `NetworkScanPage`: `/\s*$/` → `trimEnd()`; `/\s*:\s*$/` → `endsWith + slice`
- `SettingsPage.validateEmail`: regex replaced with `indexOf/slice` + 254-char cap
- `network-scan` route: IP octet extraction via `split()` instead of regex
- `speedtest` route: bounded `\d+(?:\.\d+)?` via shared `numGroup` helper
- `freeboxFirmwareCheckService`: bounded `\d{1,5}(?:\.\d{1,5}){1,3}` for versions

---

## [0.7.83] - 2026-04-17

### Added

- **Sonner toast notifications with customization panel**
- Installed `sonner` for toast notifications (bottom-right, above footer)
- Toast feedback wired on reboot, VM toggle, WiFi toggle, login, logout
- `notificationStore` (zustand+persist) for user prefs: position, offsets, duration, theme, visibleToasts, richColors, closeButton, expand, sound
- `NotificationToaster` wrapper that reactively applies prefs
- `NotificationSettingsCard` in Theme section with live test buttons, Web Audio beep when sound enabled

### Changed

- Reuse existing `Toggle` UI component; sliders commit on pointer release to avoid localStorage thrashing during drag

---

## [0.7.82] - 2026-04-16

### Fixed

- **fix(critical): usePolling closure leak** — Two independent useEffect sharing intervalRef caused stale closures on visibility change. Merged into a single effect with startInterval/stopInterval helpers to prevent orphaned intervals (×6 active polling hooks)
- **fix(critical): NetworkScanPage interval accumulation** — Scan/refresh polling intervals stored in React state (async) allowed double-click race conditions creating parallel intervals. Migrated to useRef with systematic clear-before-create
- **fix: useConnectionWebSocket orphaned setTimeout** — Fallback fetch timeout in ws.onopen was not stored in a ref, causing orphaned timers on unmount. Now tracked via fallbackFetchTimeoutRef and cleared on disconnect
- **fix: connectionStore unbounded RRD data** — extendedHistory and temperatureHistory from Freebox RRD could contain thousands of points with 7d duration. Capped both to 500 points max
- **fix: tvStore epgCache unbounded growth** — EPG cache Map grew indefinitely without cleanup. Limited to 50 entries with oldest-first eviction

---

## [0.7.81] - 2026-04-16

### Fixed

- **fix(critical): Memory leak in usePolling** — Tab visibility change handler created new intervals without clearing existing ones, causing interval accumulation (×6 polling hooks) on each hidden/visible cycle. Root cause of "Out of Memory" crash when staying on a tab
- **fix: Redundant HTTP fallback polling** — Connection status was polled every 1s via HTTP even when WebSocket was active, doubling history writes. Fallback now only activates when WebSocket is disconnected, interval reduced to 5s

---

## [0.7.80] - 2026-04-15

### BREAKING CHANGES (Docker)

- **Docker volumes changed** — `/:/host:ro` and `/var/run/docker.sock` mounts removed. Replace with targeted mounts. See updated docker-compose.yml example below.
- **Capabilities changed** — Added `cap_drop: ALL` + `SETUID`/`SETGID`. Remove `no-new-privileges` if present (incompatible with su-exec).
- **Disk usage and Docker version removed** — These features required unsafe mounts and have been permanently removed from the dashboard.

**Migration:** Update your docker-compose volumes section:

```yaml
volumes:
  - ./data:/app/data
  - /proc:/host/proc:ro
  - /sys:/host/sys:ro
  - /etc/hostname:/host/etc/hostname:ro
  - /etc/hosts:/host/etc/hosts:ro
cap_add:
  - NET_RAW
  - NET_ADMIN
  - SETUID
  - SETGID
cap_drop:
  - ALL
```

### Security

- **fix(critical): Command injection prevention** — All `exec()` calls replaced with `execFile()` (array arguments, no shell) across networkScanService, portScanService, speedtest, wiresharkVendorService
- **feat: JWT blacklist persistence** — Revoked tokens stored in SQLite, survive container restarts. New `POST /api/users/logout` route
- **feat: WebSocket rate limiting** — 50 msg/10s per connection on all 3 WS services
- **feat: RBAC on network scan** — 15 scan routes restricted to admin role
- **fix: Helmet HSTS/upgrade-insecure-requests disabled** — App runs behind reverse proxy (NPM), these headers caused ERR_SSL_PROTOCOL_ERROR on LAN HTTP access
- **fix: CodeQL incomplete string escaping** — `escapeRegex()` utility replaces partial dot-only escaping

### Code Quality

- **refactor: Shared validation utilities** — `server/utils/networkValidation.ts` with `isValidIp`, `isValidPingTarget`, `isValidPortRange`, `extractMac`, `normalizeMac`, `escapeRegex`
- **refactor: Reduced duplication** — MAC extraction (5x), MAC normalization (10x), hostname validation (6x) deduplicated via shared helpers
- **refactor: NumericInput component** — Extracted from SecuritySection.tsx to reduce UI duplication

### Cleanup

- **Removed** `docker-compose.dev.yml`, `Dockerfile.dev` — simplified to 2 compose files
- **Removed** 7 obsolete Doc_Dev files
- **Updated** README.md, README.fr.md, SECURITY.md, CLAUDE.md, all documentation

---

## [0.7.79] - 2026-04-10

### Technical

- **fix(duplication): last mile** — Unique boundary prefix in FreeboxApiService.ts, extracted `validateIpBatch()` helper in latency-monitoring.ts to eliminate last duplicated blocks
- **fix(code-quality): ~30 code smells resolved** — `CorsListEditor` extracted as standalone component, `looksLikeMac()` helper, `getColorHex()` helper, `VALID_*_TABS` arrays replaced with `Set` + `.has()` across 5 pages, `node:crypto` imports, `replaceAll()` over `replace(/g)`, `for-of` loops, regex simplifications
- **fix(security): Sonar exclusions expanded** — S1313 (IPs), S5332 (HTTP/ip-api.com), S4721 (validated exec) exclusions, `sonar.cpd.exclusions` for version-managed files
- **fix(regex): Bounded remaining DoS patterns** — freeboxFirmwareCheckService, wiresharkVendorService, speedtest, UniFiApiService, ClientsTab

---

## [0.7.73] - 2026-04-10

### Technical

- **fix(security): SSRF prevention** — Added hostname allowlist validation in `setBaseUrl()` (only Freebox hosts and private IPs allowed) + URL format validation in auth route (CodeQL #211)
- **fix(security): HTML sanitization** — Replaced regex HTML stripping with state-machine parser for safe tag removal (CodeQL #209-#214)
- **fix(security): Helmet hardening** — Removed `'unsafe-inline'` from `scriptSrc`, re-enabled `frameguard` with dynamic X-Frame-Options (CodeQL #207, #215)
- **fix(reliability): sort comparator** — Refactored `.sort()` calls into `arraysEqual()` helper with explicit `localeCompare` (SonarCloud S2871)
- **fix(duplication): SecuritySection.tsx** — Extracted repeated sorted-array comparison into `arraysEqual()` helper to reduce code duplication

---

## [0.7.70] - 2026-04-10

### For users

- **SonarCloud quality gate fixes** — Resolved all 3 failing conditions: reliability bugs, code duplication, and security hotspot reduction
- **Dockerfile security** — Fixed zlib CVE-2026-27171 vulnerability (Snyk)
- **Configurable network defaults** — Ping target (`DEFAULT_PING_TARGET`) and scan range (`DEFAULT_SCAN_RANGE`) are now configurable via environment variables

---

### Technical

#### Backend

- **Removed debug instrumentation** — Cleaned up 6 leftover debug `fetch()` calls to `127.0.0.1:7243` in freeboxApi.ts, modelDetection.ts, and FreeboxPlugin.ts
- **Crypto-safe random** — Replaced `Math.random()` with `crypto.randomUUID()` / `crypto.randomInt()` in server code (boundary generation, log sampling)
- **Hardened regex patterns** — Fixed 27 regex patterns vulnerable to ReDoS (S5852): bounded quantifiers, non-capturing groups, removed nested backtracking across 16 files
- **Configurable defaults** — `8.8.8.8` and `192.168.1.0/24` moved from hardcoded values to `config.defaultPingTarget` / `config.defaultScanRange` with env var support

#### Frontend

- **SecuritySection.tsx** — Fixed 2 reliability bugs (unused `XCircle` import, unused `user` variable), refactored 7 duplicate `setCorsConfig()` calls into single `updateCorsConfig()` helper, fixed `.sort()` mutating React state, replaced deprecated `onKeyPress` with `onKeyDown`
- **Regex hardening** — IPv4 validation, MAC address, color parsing, and domain validation patterns hardened across ThemeSection, SettingsPage, SearchPage, ExporterSection, and others

#### CI/Quality

- **sonar-project.properties** — Added `sonar.issue.ignore.multicriteria` exclusions for false positive S2245 (Math.random in visual animations) and S1313 (hardcoded IPs expected in network monitoring tool)
- **Dockerfile** — Added `apk upgrade --no-cache` to fix zlib CVE-2026-27171

---

## [0.7.68] - 2026-04-10

### For users

- **Iframe embed configuration** — New UI in Settings > Security > Network to manage allowed iframe origins. MyNetwork can now be embedded in iframes from authorized domains via `Content-Security-Policy: frame-ancestors`
- **URL-driven Settings sub-tabs** — Security, Database, and Backup sections now use URL-based sub-tabs (e.g. `/settings/security/network`), so refreshing or sharing a link keeps the active sub-tab

---

### Technical

#### Backend

- **server/index.ts** — CSP `frame-ancestors` directive is now dynamically read from database config (`iframe_origins`) on each request instead of once at startup. Disabled `X-Frame-Options` header (`frameguard: false`) so CSP `frame-ancestors` takes full control
- **server/routes/system.ts** — Added `iframe_origins` field to `GET/PUT /api/system/general` endpoint for reading/writing allowed iframe origins

#### Frontend

- **SecuritySection.tsx** — Added iframe embed configuration UI (origin list management with add/remove, test button, and snippet display)
- **SettingsPage.tsx** — Security, Database, and Backup sections now derive active sub-tab from 3rd URL path segment via `useNavigate`

#### i18n

- **en/admin.json + fr/admin.json** — Added 9 translation keys for iframe embed configuration

---

## [0.7.67] - 2026-04-09

### For users

- **Sub-route refresh fixed** — Pages like `/settings/debug` or `/unifi/traffic` no longer show a blank page on browser refresh
- **Network scan loads instantly** — Fixed rate limiter blocking the initial page load of the network scan page

---

### Technical

#### Frontend

- **vite.config.ts** — Changed `base: './'` to `base: '/'` so asset paths are absolute. Relative paths caused CSS/JS 404s on multi-segment URLs (`/settings/assets/` instead of `/assets/`)

#### Backend

- **server/index.ts** — Excluded `/api/network-scan` from global `apiLimiter` (300 req/min) since it has its own dedicated limiter
- **rateLimiter.ts** — Increased `scanLimiter` from 10 to 60 req/min (page fires 7+ requests on mount, 10 was too low)

---

## [0.7.66] - 2026-04-09

### For users

- **Network scan stability** — Fixed a polling loop that spammed the server with progress requests, causing 429 rate limit errors and blank pages

---

### Technical

#### Frontend

- **NetworkScanPage.tsx** — Removed `scanProgress` from useEffect dependencies to prevent infinite interval recreation loop (was spamming `/api/network-scan/progress` every render cycle instead of every 2s)
- **update-version.sh** — Commit messages and changelog labels switched from French to English

---

## [0.7.65] - 2026-04-09

### Technical

#### Documentation

- **CHANGELOG.md** — Backfilled missing changelog entries for v0.7.63 (disable Rybbit session replay) and v0.7.64 (Scorecard Token-Permissions fix)

---

## [0.7.64] - 2026-04-09

### Technical

#### CI

- **snyk.yml** — Replaced top-level `permissions: { contents: read, security-events: write }` with `permissions: {}` to fix Scorecard Token-Permissions alert #206 (permissions already defined at job level)

---

## [0.7.63] - 2026-04-09

### Technical

#### Frontend

- **App.tsx** — Disabled Rybbit session replay (`data-disable-session-replay="true"`) to fix 413 Content Too Large errors flooding the console

---

## [0.7.62] - 2026-04-09

### For users

> Every page and tab now has its own URL — better browser history and more detailed analytics.

- **URL-based tab navigation** — Each tab on every page (UniFi, Analytics, Settings, TV, Files, Phone) now updates the browser URL. You can bookmark or share a direct link to any tab (e.g. `/unifi/traffic`, `/settings/plugins`). Browser back/forward buttons work between tabs.
- **Snyk badge fixed** — The Snyk security badge on the README now displays correctly.

---

### Technical

#### Frontend — URL sub-tab routing

- **App.tsx** — `currentPage` derivation now handles sub-paths (`/unifi/traffic` → `'unifi'`) via fallback to first URL segment
- **UniFiPage.tsx** — 8 tabs + overview sub-tab (`/unifi/overview/events`) derived from `location.pathname`, `setActiveTab` uses `navigate()`
- **AnalyticsPage.tsx** — 4 tabs (bandwidth, temperature, wifi, system) derived from URL
- **SettingsPage.tsx** — Freebox tabs (7) and Admin tabs (9) both derived from URL, sessionStorage fallback preserved for backward compat
- **TvPage.tsx** — 3 tabs (recordings, guide, programmed) derived from URL
- **FilesPage.tsx** — 3 tabs (files, downloads, shares) derived from URL, `initialTab` prop still respected as fallback
- **PhonePage.tsx** — 2 tabs (calls, contacts) derived from URL
- All tab changes trigger `pushState` via react-router `navigate()`, which Rybbit analytics script captures automatically

#### CI / README

- **README.md** — Snyk badge switched from `shields.io/snyk/vulnerabilities` (requires snyk.io web monitoring) to GitHub Actions workflow status badge

---

## [0.7.61] - 2026-04-09

### Technical

#### CI

- **snyk.yml** — Added SARIF upload condition (`hashFiles`), pinned actions to SHA
- **sonarcloud.yml** — Pinned action to SHA, added permissions
- **README.md** — Added SonarCloud quality gate + Snyk vulnerability badges

---

## [0.7.60] - 2026-04-09

### Technical

#### CI

- **sonarcloud.yml** — Re-triggered SonarCloud after disabling automatic analysis in project settings

---

## [0.7.59] - 2026-04-09

### For users

- **Security scanning** — SonarCloud (code quality) and Snyk (vulnerability scanning) now run automatically on every push

---

### Technical

#### CI

- **sonarcloud.yml** — New workflow for SonarCloud SAST analysis via `sonarqube-scan-action@v6`
- **snyk.yml** — New workflow with 3 jobs: Snyk Code (SAST), Snyk Open Source (dependencies), Snyk Container (Docker image)
- **sonar-project.properties** — Created with project key `Erreur32_MynetworK`

---

## [0.7.58] - 2026-04-09

### Technical

#### Backend

- **rateLimiter.ts** — Custom `keyGenerator` using `CF-Connecting-IP` → `X-Real-IP` → `req.ip` for correct client IP behind Cloudflare/nginx. API limit raised from 100 → 300 req/min

---

## [0.7.57] - 2026-04-09

### Technical

#### Backend

- **server/index.ts** — Changed `trust proxy: true` → `trust proxy: 1` to fix `ERR_ERL_PERMISSIVE_TRUST_PROXY` causing 429 errors
- **Helmet CSP** — Added `https://way.myoueb.fr` to `scriptSrc` + `connectSrc` for Rybbit, `https://static.cloudflareinsights.com` to `scriptSrc` for Cloudflare beacon

---

## [0.7.56] - 2026-04-09

### For users

- **Analytics fixed** — Rybbit analytics now works reliably with self-hosted server

---

### Technical

#### Frontend

- **App.tsx** — Replaced `@rybbit/js` SDK with script tag injection (`/api/script.js`). SDK v0.6.2 was incompatible with self-hosted Rybbit (wrong API paths, CORS errors)

---

## [0.7.55] - 2026-04-09

### Technical

#### Backend

- **system.ts** — Fixed header showing `v0.1.0` instead of actual version — replaced broken `path.join(__dirname)` with `import pkg from '../../package.json'`

#### CI

- **docker-publish.yml** — Added `timeout-minutes: 45` to prevent hanging builds
- **README.md** — Fixed broken Features anchor link

---

## [0.7.54] - 2026-04-09

### Technical

#### Documentation

- **README.md** — Added analytics privacy section explaining Rybbit opt-in, data collected, and opt-out

#### CI

- **Dockerfile** — Isolated npm cache per architecture (`id=npm-${TARGETPLATFORM}`) to prevent `ETXTBSY` error on arm64 QEMU builds

---

## [0.7.53] - 2026-04-09

### For users

- **Usage analytics** — Optional anonymous analytics via Rybbit (self-hosted). Only active when `VITE_ANALYTICS_HOST` and `VITE_ANALYTICS_SITE_ID` are set

---

### Technical

#### Security

- **OpenSSF Scorecard** — Improved score: added SAST workflow, pinned dependencies to SHA, added SECURITY.md
- **scorecard.yml** — Added `workflow_dispatch` trigger, removed env vars blocked by OSSF verification

#### Frontend

- **App.tsx** — Rybbit analytics integration via `@rybbit/js` SDK (later replaced in v0.7.56)
- **Dockerfile** — Analytics config moved to build ARGs

---

## [0.7.52] - 2026-04-08

### Fixed

- **Update check error messages**: show actual GitHub rate limit error instead of misleading "Registry requires authentication" — includes reset time and GITHUB_TOKEN hint

---

## [0.7.51] - 2026-04-08

### Added

- **Update check frequency selector**: configurable check interval (1h/6h/12h/24h/7d) in administration settings
- **Docker GHCR status**: shows build availability (green/orange) when update available, with `docker compose pull` command
- **Release notes**: display changelog excerpt and link when update available

### Changed

- **Bandwidth chart toggle**: Freebox/UniFi source switch moved to period filter bar, blue for both (was orange for Freebox)
- **Bandwidth legend**: removed "Scale: history (RRD)" label — period label now shown for both sources
- **Plugin Status cards**: removed borders from firmware/last scan sub-cards for cleaner look
- **CodeQL badge**: switched to shields.io static badge (consistent style)

---

## [0.7.50] - 2026-04-08

### Security

- **CORS restriction**: replaced permissive `origin: true` with whitelist — `*.myoueb.fr`, localhost, and private networks (192.168.x.x, 10.x.x.x, 172.16-31.x.x)

### Fixed

- **CodeQL badge**: use GitHub native badge URL for dynamic workflow (shields.io doesn't support workflow IDs)

---

## [0.7.49] - 2026-04-08

### Added

- **OSSF Scorecard** workflow — weekly supply chain security scoring with public dashboard
- **Dependency Review** workflow — blocks PRs introducing high-severity vulnerable dependencies
- **Security badges** on both READMEs (CodeQL, OSSF Scorecard)

---

## [0.7.48] - 2026-04-08

### Security

- **Rate limiting**: `express-rate-limit` middleware — 100 req/min API, 10 req/min network scans
- **Regex injection fix**: full regex character escape in dynamic `new RegExp()` (networkScanService avahi-browse)
- **ReDoS prevention**: truncate user input before regex match in network-scan route
- **URL validation fix**: replace `url.includes('unifi.ui.com')` with proper `new URL(url).hostname` check in UniFi plugin

### Changed

- **`.gitignore` cleanup**: removed 13 redundant entries, reorganized sections, added AI tools patterns (Cursor, Copilot, Gemini)
- **GitHub repo**: disabled unused Projects feature, added 20 SEO topics
- **CodeQL alerts**: resolved 21/24 alerts (dismissed false positives with documented reasons)

---

## [0.7.47] - 2026-04-08

### Added

- **URL-based routing**: React Router with clean URLs (`/settings`, `/network-scan`, `/unifi`, etc.) — browser back/forward, bookmarkable pages
- **WebSocket authentication**: JWT token verification on all WS upgrade requests (`/ws/connection`, `/ws/logs`, `/ws/unifi`)
- **Security headers**: Helmet.js middleware (CSP, X-Frame-Options, X-Content-Type-Options, HSTS)
- **Gzip compression**: `compression` middleware for API responses and static assets
- **Graceful SIGINT shutdown**: proper cleanup handler for Ctrl+C (matches existing SIGTERM handler)
- **CLAUDE.md**: project context file for Claude Code

### Changed

- **JWT hardening**: explicit `algorithm: 'HS256'` in `jwt.sign()` and `algorithms: ['HS256']` in `jwt.verify()` to prevent algorithm confusion attacks
- **Register endpoint**: role whitelist (`user`, `viewer` only) — prevents passing arbitrary role values
- **Console cleanup**: replaced 60+ raw `console.log/error/warn` calls with structured `logger` service across all backend files
- **Source maps disabled** in production build — prevents exposing TypeScript source code

### Security

- **Command injection fixes**: strict IP validation before all shell commands in `networkScanService`, `portScanService`, `speedtest` routes
- **Input validation**: `isValidPingTarget()` for speedtest target, `isValidPortRange()` for nmap port ranges
- **Sensitive files**: added `CLAUDE.md`, `.claude/` to `.gitignore`

### Removed

- Unused `flag-icons` dependency
- Stale `App.tsx.backup` file

---

## [0.7.46] - 2026-04-07

### Added

- **Smart history purge**: new "Keep first & last per IP" option — retains oldest and most recent history entry per device, deletes everything in between (massive DB size reduction while preserving initial and current state)
- **Compound index** `network_scans(status, last_seen)` for faster offline IP purge queries

### Changed

- **getStats() optimization**: replaced 4 separate COUNT queries with a single aggregated query (4x faster)
- **getStatisticsBatch() optimization**: replaced N+1 per-IP loop (3 queries × N IPs) with a single batch query using GROUP BY (100x faster for large networks)
- **History insert**: stopped writing redundant `ping_latency` to `network_scan_history` (already stored in `latency_measurements`)

### Fixed

- **Database health toast**: fixed broken i18n keys `admin.db.*` → `admin.database.*` (critical/warning toasts were showing raw key names)

---

## [0.7.45] - 2026-04-07

### Added

- **Uptime badges**: color-coded uptime badges on Freebox, UniFi and Server cards (green 30d+, blue 7d+, orange 1d+, red <24h)
- **Uptime RichTooltip**: structured tooltip explaining the color code on hover
- **Plugin config modal**: clicking a plugin in "Plugin Status" opens its config modal directly
- **UniFi historical bandwidth**: 1h/6h/24h/7d periods fetch from UniFi controller report API
- **Freebox mode badge**: orange badge for bridge mode, green for router in Network Summary

### Changed

- **Database Performance tab**: removed dangerous SQLite options (WAL mode, sync, cache, busy timeout), kept only Docker toggle + read-only config info
- **Database tabs**: moved data stats to "Health & Actions" tab, renamed "Data & Retention" to "Retention"
- **Database card (Server System)**: replaced WAL/Cache/Mode with health status badge (OK/Warning/Critical)
- **Plugin Status**: removed "OK" badge next to "Active" — only shows error badge when stats unavailable
- **Vendors IEEE**: added clear description explaining MAC address → manufacturer mapping (i18n EN+FR)
- **i18n**: full internationalization of Database admin tabs (health, actions, buttons, messages) — EN + FR

---

## [0.7.44] - 2026-04-07

### Added

- **UniFi real-time mini graph**: dashboard card now shows live bandwidth immediately on page load (no more 5s cold start)
- **UniFi historical bandwidth**: 1h/6h/24h/7d periods now fetch data from the UniFi controller's built-in report API (persistent data, like Freebox RRD)
- **HTTP bandwidth-realtime endpoint**: new `/api/plugins/unifi/bandwidth-realtime` for lightweight polling fallback

### Changed

- UniFi live graph: removed animation fade effect in live mode for both Freebox and UniFi (clean real-time updates like Freebox)
- UniFi WebSocket polling interval reduced from 3s to 1s for true real-time bandwidth
- `fetchWanBandwidth()` now fetches fresh device data each call instead of using stale cached gateway object
- `_pushToHistory()` skips duplicate entries when controller hasn't refreshed counters yet (eliminates zero-rate noise)
- Background bandwidth sampling starts at server boot (data ready before first client connects)
- WebSocket reconnect attempts increased from 1 to 5 in production

### Fixed

- UniFi mini graph always showing 0: was using stale cached gateway device with frozen byte counters
- UniFi live bandwidth alternating real/zero values: caused by background and WebSocket polling interleaving

---

## [0.7.43] - 2026-04-07

### Changed

- Reduce Docker log noise: downgrade verbose WebSocket, polling, and keep-alive logs from info to debug
- Remove RRD console.log sample dumps from connection routes
- Remove JSON.stringify config dumps from network-scan and scheduler logs
- Downgrade UniFi IPS per-endpoint attempt logs to debug (only final result stays info)
- Downgrade Freebox session keep-alive routine logs to debug (errors/renewals still logged)

---

## [0.7.42] - 2026-04-07

### Added

- **Threats table**: new columns — Action, Service, Direction, Signature, Dst Port, Flow Count (matching UniFi OS layout)
- **Threat Map**: Leaflet dark map with clustered attack origin markers, GeoIP resolution, country sidebar filter
- **Threat Map Live mode**: animated attack arcs with pulsing origin dots, color-coded by severity
- **Time range filters**: added 12h, 48h, 30d periods (was only 1h/24h/7d)
- **GeoIP endpoints**: `/api/plugins/unifi/threats/geo/:ip`, batch resolve, server-geo for attack arcs
- **Table/Map toggle**: switch between table and map views within the Threats tab

### Changed

- v2 traffic-flows API now paginates automatically (up to 20k entries for 30d, was capped at 500)
- recentFlows limit scales with time range (200 for 1h → 2000 for 30d)
- Backend extracts service, direction, signature, ports, flowCount from UniFi API responses

---

## [0.7.41] - 2026-04-06

### Fixed

- Update check: properly authenticate with GHCR anonymous token before checking manifest (was false positive on 401)
- Update check: require ALL CI check runs to be completed before showing update notification (was triggering while build still in progress)

---

## [0.7.40] - 2026-04-06

### Changed

- Bump vite from 6.4.1 to 6.4.2 (security fix: path traversal + server.fs check)

---

## [0.7.39] - 2026-04-06

### Added

- Dedicated `/ws/unifi` WebSocket for real-time bandwidth (3s polling, independent of Freebox)
- Lightweight `fetchWanBandwidth()` on UniFiPlugin (stat/dashboard only)
- UniFi realtime Zustand store + `useUnifiWebSocket` hook
- Time range selector on TrafficTab (Live / 1h / 6h / 24h / 7d)
- Clickable legend toggle on bandwidth charts (show/hide download/upload)
- Y-axis unit labels on bandwidth charts
- LIVE badge indicator on TrafficTab when WebSocket is connected

### Changed

- PluginSummaryCard, BandwidthHistoryWidget, TrafficTab now use WebSocket real-time data
- Header displays UniFi realtime bandwidth when WebSocket connected
- Plugin Status card and Bandwidth chart aligned on same dashboard row
- Plugin Status card: fixed height, compact margins, scrollable content
- Docker build optimized: drop arm/v7, improve layer caching, npm cache mount

### Fixed

- Removed duplicate "Analyse trafic UniFi" from Overview events sub-tab
- BandwidthHistoryWidget defaults to Live mode when UniFi-only
- Fix update-version.sh: scan-reseau name mismatch and README badge regex

---

## [0.7.28] - 2026-04-06

### Added

- **UniFi — Bandwidth**: new _Bandwidth_ tab with AreaChart graph (download/upload) and multi-WAN support for CloudGateway (UCG Ultra, UDM Pro) — automatic detection of WAN interfaces, WAN selector integrated into the graph header.
- **UniFi — WAN bytes CloudGateway**: extraction of cumulative counters from the gateway device (`wan1_rx_bytes`, `uplink.rx_bytes`, `network_table`, `wan_stats`) as fallback for the `stat/dashboard` endpoint which returns no WAN data on UniFiOS.
- **12h/24h time format**: new option in General settings (next to language) — preference persisted in localStorage, applied to the header clock.
- **Footer — Freebox & UniFi pages**: _Search_ and _Administration_ buttons always visible (icons-only on Freebox to save space).

### Changed

- **UniFi Debug tab**: hidden in production, visible only in development mode (`import.meta.env.DEV`).
- **Freebox footer**: removed the duplicate _Options_ button (Administration icon replaces the redundant gear).
- **Logger — extended redaction**: added `key` (Freebox/UniFi WiFi password), `psk`, `secret`, `passphrase`, `wpa*`, `app_token`, `private_key`, `credential` to sensitive patterns — these fields are now masked `[REDACTED]` in all logs including debug mode.
- **Log security**: replaced all `console.log/warn/error` calls in `freeboxNativeWebSocket.ts`, `FreeboxPlugin.ts`, `freeboxApi.ts`, `FreeboxApiService.ts`, `routes/wifi.ts` and `routes/plugins.ts` with the centralized logger — verbose logs only show in debug mode.
- **Docker logs**: server log timestamps in local time (respecting the `TZ` variable) instead of UTC.

### Fixed

- **UniFi — per-WAN bandwidth history**: each WAN interface has its own history buffer; switching WAN reloads the corresponding graph.

---

## [0.7.26] - 2026-03-30

### Security

- **path-to-regexp**: updated `8.3.0 → 8.4.0` — fixes **CVE-2026-4926** (GHSA-j3q9-mxjg-w52f) and **CVE-2026-4923** (GHSA-27v5-c462-wpq7): restricted backtracking on repeated wildcards and rejection of too-wide optional route combinations (> 256).

---

## [0.7.25] - 2026-03-26

### Fixed

- **CVE-2026-33671** , **CVE-2026-33672** corrected fix

---

## [0.7.24] - 2026-03-25

### Fixed again

- **Docker**: removed `FROM --platform=$TARGETPLATFORM` (redundant with Buildx) to reduce `RedundantTargetPlatform` warnings during multi-arch build.

---

## [0.7.23] - 2026-03-25

### Fixed

- **Docker**: removed `FROM --platform=$TARGETPLATFORM` (redundant with Buildx) to reduce `RedundantTargetPlatform` warnings during multi-arch build.

---

## [0.7.22] - 2026-03-25

### Fixed

- **UniFi — 429 errors (Too Many Requests)**: prevent login bursts on parallel calls (authentication sharing) + cooldown after rate-limit (`Retry-After` if present, otherwise default backoff) to avoid triggering the "login attempt" limit on the UniFi side.
- **Logs**: debug/verbose level **disabled by default**; added centralized redaction to automatically mask sensitive data in logs (password, apiKey, tokens, `Authorization` headers, cookies / session); removed overly verbose UniFi logs (username, key preview, `console.*`).

---

## [0.7.21] - 2026-03-24

### Fixed

- **Logs (buffer)**: `Error` serialization with `name`, `message` and `stack` in `logBuffer` — UniFi / plugin errors no longer show as `[{}]` in the log stream.
- **Latency monitoring**: stats min/max computed with `reduce` instead of `Math.max(...)` / `Math.min(...)` on large arrays, removing the "Maximum call stack size exceeded" error on the batch stats endpoint (`LatencyMonitoring`).

### Added

- **Network scan — authorized ranges**: `isScanRangeAuthorized()` — scan limited to subnets where the server has a detected LAN address (excluding Docker / overlay interfaces) **or** to the range saved in options (`network_scan_default`); `10.10.x.x` not considered as auto-LAN (explicit configuration required); `resolveScanRangeFromAppConfig()` for the scheduler (same logic as the API: `defaultRange` + `defaultAutoDetect`); **403** response with code `SCAN_RANGE_NOT_ALLOWED` on `POST /scan` if the range is refused; explicit error message when the IP list to scan is empty after exclusions.

### Changed

- **`NetworkScanService`**: eligible /24 collection via `collectEligibleLanSlash24s()`; `getMachineLanRanges()`; `getNetworkRange()` and IPv4 detection (`family` `IPv4` or `4`).
- **`isDockerIp`**: `10.10.x.x` treated as overlay (excluded from refresh / list generation) unless the address is covered by the range configured in Admin.

---

## [0.7.20] - 2026-03-24

### Fixed

- **TypeScript (`tsc --noEmit`)**: aligned `api.get` / `api.post` generics on the **payload** type `result` (not the `{ success, result }` envelope) — network summary widgets, `updateStore`, admin database section and related calls.
- **Server**: `NetworkScanService.isDockerIp` / `isIpInRange` exposed for routes; `UnifiedAutoScanConfig.fullScan.scanType` optional (old config migration); `metricsCollector`: `databaseMetrics` state initialized; `NetworkScanRepository.getDatabaseStats`: `offlineScans` counter; DB purge: use of `scansCount` / `historyCount` / `offlineScans`; `wiresharkVendorService`: removal of duplicate OUI keys in the default set, `String()` normalization for `unknown` fields before `.trim()`.
- **UI**: `AreaChart` without `isAnimationActive` (animation on the `Area`); `MultiSourceWidget`: `onPluginClick(plugin.id)`; `SystemNetworkWidget` / `SystemServerWidget`: history mapped to `NetworkStat` (`time` from `timestamp`) for `BarChart`; `PhonePage`: `TFunction` signatures (i18next); `TvPage`: `useTranslation` in `ProgramTooltip` / `EpgProgramCard`, `RecordingFormModal` with optional channel `number`; `SettingsPage`: typed API responses (DB stats, retention, estimate, Wireshark vendors, plugin priorities, SQLite perf), admin `users` → `general` tab normalization, `admin.debug.warningManyLogs` interpolation with numeric `count`; `Button`: `title` prop; plugins refresh indicator as `aria-label`.

---

## [0.7.19] - 2026-03-24

### Added

- **Release notes 0.7.19**: added version entry to changelog and prepared the associated commit message via `commit.txt`.

---

## [0.7.18] - 2026-03-24

### Added

- **Release notes 0.7.18**: added version entry to changelog and prepared the associated commit message via `commit.txt`.

---

## [0.7.17] - 2026-03-24

### Added

- **Update check — CI build validation**: the check service no longer reports a version as available without verifying its GitHub CI build is validated; new `isBuildValidated()` function that queries the GitHub Check Runs API (`/commits/{sha}/check-runs`) — a tag is kept only if its commit has at least one check run with `conclusion: success` and no `failure`/`cancelled`; if no check run exists → version ignored; on network error or rate limit → permissive fallback to avoid blocking detection

### Fixed

- **Build — Oversized chunks (> 600 kB)**: reworked Rollup `manualChunks` in `vite.config.ts`; new `vendor-markdown` chunk for `react-markdown` + `remark-gfm` and their transitive dependencies (`unified`, `mdast`, `hast`, `micromark`, `vfile`); new `vendor-i18n` chunk for `i18next` / `react-i18next` / `i18next-browser-languagedetector`; `vendor-charts` chunk extended to `d3-*` sub-packages; fixed React exclusion (full `/react/` path filter to avoid excluding `react-markdown` and `react-i18next`)

---

## [0.7.15] - 2026-03-24

### Fixed

- **Scanner plugin — Vendor detection**: reorganized MAC resolution methods to improve detection reliability in Docker bridge mode; the ARP table (`/host/proc/net/arp`) is now consulted **first** (instant, populated after each ping) instead of `ip neigh get` (3s timeout, unusable in Docker bridge)

### Added

- **Scanner plugin — `arping`**: added `arping` to the Dockerfile (Alpine) and as a new MAC detection method (Method 2); sends a direct ARP request to obtain the MAC without relying on the passive ARP table — reliable in native mode and `network_mode: host`, fails silently in Docker bridge (expected behavior)
- **Scanner plugin — Hostname vendor fallback**: new `getVendorFromHostname()` method with 60+ recognized patterns (FR boxes: Freebox/Livebox/Bbox/SFR; Apple, Google/Android, Amazon, Samsung, Ubiquiti, Cisco, TP-Link, ASUS, Netgear, D-Link, Synology, QNAP, consoles, IoT Shelly/ESP32/Tasmota, Raspberry Pi, etc.); activates only if `scanner` is in `vendorPriority` and only when vendor is empty after all OUI/MAC lookups
- **`docker-compose.yml`**: added a comment documenting the `network_mode: host` option to enable maximum MAC detection via `arping` on the physical LAN

- **Scanner — Status column**: for **offline** devices, compact display of the **last seen** date next to the red icon (`XCircle 14px` + date at `10px/60%` opacity); ultra-short adaptive format: `< 1m` → `5m` → `3h` → `2d` → `27/03` → `2025`; column widened from `w-16` to `w-20`; existing tooltip unchanged (full date on hover)

### Notes

- MAC method order is now: ARP table (passive) → arping (active L2) → ip neigh → arp-scan → arp -n
- The hostname fallback respects the `vendorPriority` configuration: only activates if `scanner` is in the configured priority list
- Wireshark vendor DB: 38,625 entries loaded from `oui.txt` (unchanged)

---

## [0.7.12] - 2026-02-28

### Fixed

- **Security (Dependabot)**: Rollup vulnerability (high) — Arbitrary File Write via Path Traversal (GHSA-mw96-cpmx-2vgc); `rollup` override `>=4.59.0` in `package.json` to force the fixed version as a transitive dependency (Vite)

---

## [0.7.11] - 2026-02-28

### Added

- **Administration > General > Updates**: "Check now" button under "Automatic update check" to force a check (bypass the 12h cache); same-page notification: in progress ("Checking…"), up to date (green), update available with version (amber), or error (red)
- **Backend**: `GET /api/updates/check?force=1` (or `force=true`) to force a check without cache; `getCheckResultForce()` in `updateCheckService`
- **Update store**: `checkForUpdatesForce()` calls the force API and returns the result for notification display
- i18n: `admin.updateCheck.checking`, `admin.updateCheck.upToDate`, `admin.updateCheck.updateAvailableVersion` (FR/EN)

---

## [0.7.10] - 2026-02-28

### Added

- **Administration > Plugins**: vertical card layout (one card per row, stacked); wider cards with more padding and options directly in each card
- **Freebox plugin**: "Freebox firmware check" section integrated into the Freebox card (firmware options in the card), collapsible section (collapsed by default) with clickable header and chevron (▶ / ▼)
- **Scanner**: "Stop" button during a full scan (replaces "Scan") to stop the current scan; API `POST /api/network-scan/scan-stop` and `requestStopScan` flag on the backend
- **Scanner**: status filters as colored badges (All / Online / Offline) with border and ring on the active one, reinforced transparency for inactive ones; explanatory tooltips on totals (total, online, offline) in the table header
- **Scanner**: manual rescan disabled for offline IPs (grayed button + tooltip); success toast + CheckCircle icon after rescan; background list refresh to avoid "server unavailable"; toasts instead of alerts for errors
- i18n: `admin.logsUserLogin`, `networkScan.tooltips.headerTotal/headerOnline/headerOffline`, `networkScan.success.rescanDone/rescanDoneRefreshFailed/scanStopRequested`, `networkScan.buttons.stop`, `networkScan.tooltips.stopScan/rescanOfflineDisabled`, `networkScan.filters.statusFilterLabel`, `scanStop` errors (FR/EN)

### Modified

- **Administration > General**: Localization (timezone + UI language) grouped in the "My profile" section (same frame); subtitle "Region and language" (instead of "Localization") with reinforced style (text-sm, font-semibold, theme-secondary)
- **Administration > General**: "User management" section moved to 2nd column under "Network configuration" and renamed "User login logs"
- **Administration > General**: removed separate "Localization" and "Interface language" sections; removed the dedicated "Freebox firmware check" section from the General tab (moved to Plugins > Freebox card)
- **Scanner**: status filter dropdown replaced with three badge-buttons (All, Online, Offline); online/offline totals displayed on the filter line with tooltips; tooltip on the total header badge
- **Scanner**: rescan logic (immediate UI success, background refresh, errors in toast)
- i18n: `admin.localization` updated ("Region & language" / "Région et langue")
- `FreeboxFirmwareCheckSection` component extracted into `src/components/FreeboxFirmwareCheckSection.tsx` and reused in the Freebox plugin card
- Backend: `networkScanService` — `scanStopRequested` flag and loop exit between batches for on-demand scan stop

---

## [0.7.9] - 2026-02-24

### Added

**Network scanner (NetworkScanPage) – MAC-style tooltips**

- **IP** column: hover tooltip "Click to learn more about this IP" (floating style identical to other columns)
- **Monit** column: explanatory tooltip (what monitoring is for, Latency scatter graph activation) + actions depending on state (enable / disable, view graph)
- **Scatter** icon (when monitoring is enabled): tooltip "Opens the Latency scatter table" + "Click to open"
- **Actions** column: independent per-button tooltips (Rescan, Ban, Delete) instead of a single tooltip on the whole cell
- **Scan Info** card: **ON** / **OFF** badge next to the title based on scan-reseau plugin state (active/inactive); accessibility tooltip "Plugin active" / "Plugin inactive"

### Modified

- **Scan Info**: removed the "Auto" label and its associated ON/OFF badge next to the title (only the plugin badge remains)
- **Scanner header**: removed the "Active" indicator (green dot + text) next to the Configuration button
- i18n (networkScan): `stats.pluginActive`, `stats.pluginInactive`, `tooltips.monitoringDescription`, `tooltips.openScatterTable`, `tooltips.clickToOpenScatter`, `tooltips.clickToKnowMoreIp` (FR/EN)

---

## [0.7.8] - 2026-02-24

### Added

**Update check (12h)**

- `updateCheckService` backend service: 12h cache, scheduler every 12h when enabled
- API `GET /api/updates/check` returns `lastCheckAt` (ISO); cached result
- `POST /api/updates/config` starts or stops the scheduler based on `enabled`
- On server startup, the scheduler launches if "update check" config is enabled in DB
- Update banner in the Header: visible when a new version is available and check is enabled, dismissible (close button); dismissal persisted per version in `localStorage` (`mynetwork_update_banner_dismissed`)

**Administration > Info**

- Info tab redesigned: project card (96×96 logo, name, description, version | license), real GitHub (shields.io) badges: MynetworK (dynamic version), Status, Docker, GHCR, Build, React, TypeScript, License; links on GHCR and Build
- Repo stats badges (Stars, Forks, Issues) as small badges under the "View on GitHub" button, no "Repository stats" frame
- "About" section with icon and long description (i18n)
- "Technologies" section (same cyan style as About)
- i18n: `admin.updateCheck.lastCheck`, `admin.repoStatsTitle`, `admin.aboutTitle`, `admin.aboutDescription`, `admin.issuesCount` (FR/EN)

### Modified

- **Administration > General**: "Automatic update check" section now only shows the **last check** (date/time) when enabled; removed version display and "manual check disabled" block
- **Update store**: `UpdateInfo` type with `lastCheckAt`; `checkForUpdates` saves `result.lastCheckAt` and updates `lastCheck`
- **Update routes**: removed orphan code (old GET /check logic); only GET /check, GET /config, POST /config

---

## [0.7.7] - 2026-02-20

### Fixed

- **Security (Dependabot):** Updated `qs` 6.14.1 → 6.14.2 (CVE arrayLimit bypass, DoS via memory exhaustion)
- **scripts/update-version.sh:** Added version update in `package-lock.json` (root + packages."")

---

## [0.7.6] - 2026-02-20

### Added

**Freebox – Firmware update notifications**

- Backend service that periodically scrapes dev.freebox.fr/blog for the latest versions (Server + Player)
- API: `GET /api/plugins/freebox/firmware-check`, config routes (get/post), force check
- Dashboard: single-line firmware badge with version and changelog on hover (PluginSummaryCard)
- Freebox page: notification banner with expandable changelog (expanded by default)
- Header: update indicator (↑) next to the firmware version on the Freebox page
- Administration > General: "Freebox firmware check" section (toggle, 1h–24h interval, check now)
- i18n: `freebox.firmwareUpdate.*` and `admin.freeboxFirmwareCheck.*` keys (FR/EN)
- HTML entity decoding in the changelog (`&rsquo;` → `'`, accents, etc.) — backend + `src/utils/textUtils.ts`
- Shared `compareVersions()` utility extracted into `server/utils/version.ts`

### Fixed

- **PluginsPage:** added `onNavigateToSettings` to the `PluginsPageProps` interface
- **Header:** added `onSearchClick` to the `HeaderProps` interface
- **SpeedtestWidget:** removed invalid props (`downloadSpeed`, `uploadSpeed`, etc.) — the component takes no props

---

## [0.7.5] - 2026-02-09

### Fixed

- **UniFi (Events):** Duplicate text removed on the "No event" message (line with typographic apostrophe `’` U+2019) — display only via `t('unifi.noEventDetected')`.

### Modified

- **UniFi i18n – terminology:** Aligned on "Appareil(s)" in French: `equipments`, `totalEquipments`, `offlineDevices`, `offlineDevicesMessage` keys in FR (instead of "Équipements"); EN unchanged (Devices).
- **PluginSummaryCard (UniFi Analysis tab):** Hardcoded "Équipements" label replaced with `t('unifi.equipments')` for i18n consistency.
- **UniFi – Clients:** Signal quality (Excellent, Good, Average, Weak) moved to i18n keys (`unifi.excellent`, `unifi.good`, `unifi.average`, `unifi.weak`).
- **Versions:** All version references updated to 0.7.5 (package.json, version.ts, main.tsx, Freebox, UniFi, Network Scan plugins).

---

## [0.7.3] - 2026-02-07

### Fixed

- **SearchPage:** `Card` requires `title` prop — added `title=""` to all result-state cards (loading, error, no results, filtered empty, IP details)
- **SearchPage:** Lucide `Activity` does not accept `title` — wrapped in `<span title="...">` for tooltip
- **NetworkScanPage:** `refresh.config` and `AutoStatus` — added missing `scanType: 'quick'` in default state and `lastScan: null` in fallback state; typed `api.get` for `/api/network-scan/progress` so `setScanProgress(result)` is type-safe
- **NetworkScanPage:** Lucide icons (Loader2, CheckCircle, XCircle, Clock) — removed invalid `title` prop, wrapped in `<span title="...">` for tooltips
- **NetworkScanConfigModal:** `api.get` responses typed for `wireshark-vendor-stats` and `plugin-priority-config` so setState accepts `result`; refresh fallback object now includes `scanType: 'quick'`

### Modified

- Typed API responses (`api.get<T>`) where result was assigned to typed state to resolve "Argument of type 'unknown' is not assignable" errors

---

## [0.7.2] - 2026-02-07

### Added

**Administration > Info tab**

- Changelog rendered as visual markdown (react-markdown + remark-gfm): headings (h1–h4), lists, code blocks, links with proper styling
- Colored markdown titles in changelog (teal, cyan, amber, emerald) and improved code blocks (border, background)
- App logo (MynetworK) in project block; all GitHub links use central `GITHUB_REPO_URL` constant; changelog links resolve to repo (relative → blob/main, #anchor → repo#anchor)
- i18n keys for Info page: projectName, projectDescription, licenseLabel, licensePublic, authorTitle, authorBy, technologiesTitle, databaseLabel, authLabel
- More color on Info page: colored left borders per block (teal, blue, amber, purple, cyan), colored section titles and repo stats boxes

### Fixed

- **General (Administration):** "key 'admin.database' returned an object instead of string" — added `admin.databaseLabel` and `admin.authLabel` so tab labels use string keys (admin.database is an object for the Database section)
- **Vite:** `base: './'` moved to top-level of config; it was incorrectly inside `build` and ignored by Vite (assets used absolute paths and broke Ingress)
- **Ingress:** `src/utils/ingress.ts` regex updated from `/api_ingress/` to `/api/hassio_ingress/` to match Home Assistant Ingress path

### Modified

- `src/utils/constants.ts`: added `GITHUB_REPO_URL` and `GITHUB_RAW_BASE` for centralised repo URLs
- `vite.config.ts`: base at top-level; removed invalid `build.base`
- `src/utils/ingress.ts`: path pattern and comment for hassio_ingress

---

## [0.7.0] - 2026-02-07

### Added

**Home Assistant Ingress support**

- App works when served via Home Assistant Ingress (no more white page)
- Public `GET /api/config` (no auth): returns `ingress`, `basePath` (from `X-Ingress-Path`), `showPorts` for frontend
- Frontend: `src/utils/ingress.ts` with `getBasePath()` to detect path prefix (e.g. `/api_ingress/<token>/`)
- API client and WebSocket URL use base path when under Ingress so requests stay same-origin
- Vite build `base: './'` so assets load correctly under a path prefix
- Backend: CORS allows any origin when `INGRESS_MODE=1` or `ADDON_INGRESS=1`; startup banner hides port URLs and shows "UI: served via Ingress" when Ingress or `SHOW_PORTS=false`

**Internationalization (i18n)**

- Application fully internationalized with English as default and French as second language
- Language switcher (EN/FR) in header; choice persisted in `localStorage` (`mynetwork_lang`)
- Translation files: `src/locales/en.json`, `src/locales/fr.json` with namespaced keys
- i18n stack: `i18next`, `react-i18next`, `i18next-browser-languagedetector`
- UI strings in App, Footer, Header, UserMenu, ThemeSection, Network Summary widgets, and others use `useTranslation()` and `t()`
- Documentation: `Docs/INTERNATIONALIZATION.md` (English)

**Documentation**

- README in English with link to French version; `README.fr.md` (full French README)
- Main Docs translated to English; French versions (`.fr.md`) for CONFIGURATION_UNIFI, CONNEXION_FREEBOX, VARIABLES_ENVIRONNEMENT, TROUBLESHOOTING_PROD, RESET_DOCKER_PROD, NGINX_WEBSOCKET_CONFIG
- `Docs/README.md` index listing docs with EN/FR links

### Modified

- `main.tsx`: import and init i18n before render
- `tsconfig.json`: `resolveJsonModule: true` for locale JSON imports
- `package.json`: added dependencies for i18n
- Server: banner and CORS conditional on Ingress env vars; new `/api/config` route

---

## [0.6.0] - 2026-02-07

### Added

**Docker - Multi-arch build (GHCR / Home Assistant 2026)**

- ✅ Workflow GitHub Actions : build et push pour `linux/amd64`, `linux/arm64`, `linux/arm/v7` (manifest list)
- ✅ Même tag d’image (`ghcr.io/.../mynetwork:0.6.0`) résolu automatiquement selon l’architecture (compatible Raspberry / HA add-on)
- ✅ Dockerfile : ARG `TARGETPLATFORM` / `BUILDPLATFORM`, stage builder et runtime en `FROM --platform=$TARGETPLATFORM` pour une image finale cohérente par arch
- ✅ Compilation des modules natifs (ex. better-sqlite3) pour l’arch cible (builder sur TARGETPLATFORM), plus de risque « wrong ELF class » sur ARM

### Changed

**Docker - Workflow**

- 🔧 `docker-publish.yml` : `platforms: linux/amd64,linux/arm64,linux/arm/v7` (QEMU + Buildx déjà en place)

**Docker - Dockerfile**

- 🔧 Stage builder : `FROM --platform=$TARGETPLATFORM` (au lieu de BUILDPLATFORM) pour compiler les natives pour la bonne arch
- 🔧 Stage runtime : `FROM --platform=$TARGETPLATFORM` + re-déclaration `ARG TARGETPLATFORM` avant le 2ᵉ stage

---

## [0.5.6] - 2026-02-07

### Added

**UniFi - NAT / Gateway & Ports**

- ✅ Résumé gateway dans les stats système : `gatewaySummary` (IP, nom, modèle, WAN/LAN ports, `portCount`) et `natRulesCount`
- ✅ Extraction des ports WAN/LAN depuis `network_table` du gateway (quand exposé par l’API UniFi)
- ✅ Carte « Gateway & Ports » dans l’onglet NAT : blocs WAN (cyan) et LAN (emerald) avec liste des interfaces
- ✅ Colonne NAT (Info Système) enrichie : ports WAN avec IP/statut, ports LAN, nombre de règles NAT

**Freebox - Administration plugins**

- ✅ Avertissement dans la modale de configuration du plugin Freebox lorsque le plugin est désactivé : inviter à activer le plugin pour la découverte automatique

### Changed

**UniFi - Backend**

- 🔧 Correction TypeScript : `getNetworkConfig().catch()` retourne un objet avec `dhcpRange: undefined` pour garder un type cohérent
- 🔧 Récupération des règles NAT en parallèle dans `getStats()` (`getPortForwardingRules`) pour exposer le nombre et le résumé gateway

---

## [0.5.5] - 2026-02-04

### Added

**Thème / Réglages - Animation "All"**

- ✅ Option pour choisir les animations incluses dans le cycle (cases à cocher par animation)
- ✅ Durée par animation étendue : de 5 s à 1 h (3600 s), affichage adapté (s / min / h)
- ✅ Paramètres "All" toujours affichés en réglages (réglages de transition, pas ceux de l'animation diffusée)

**Thème / Réglages - Opacité des blocs**

- ✅ Application de l'opacité au chargement de l'app (themeManager) : prise en compte sur toutes les pages (dashboard, réglages, etc.), pas seulement après passage par la page Réglages
- ✅ Chargement de l'opacité depuis l'API thème si disponible (cohérence multi-onglets / serveur)

### Changed

**Thème / Réglages - Organisation**

- 🔧 Personnalisation des couleurs du thème déplacée au-dessus de la section Animation
- 🔧 Titre clarifié : "Personnalisation des couleurs du thème" avec sous-texte explicite (interface, pas animation)

**Thème / Réglages - Animation "All"**

- 🔧 Suppression de l'option "Pause entre animations"
- 🔧 Vitesse d'animation globale masquée pour le mode "All" (chaque animation garde sa propre vitesse)
- 🔧 Durée par animation : relecture des paramètres à chaque tick (1 s) pour prise en compte immédiate du réglage

**Thème / Réglages - Options animation**

- 🔧 Paramètres affichés en grille multi-colonnes (1 / 2 / 3 colonnes selon écran)
- 🔧 Curseurs (sliders) plus fins, valeurs en gros avec unités (s, min, h, ms selon le paramètre)
- 🔧 Choix des animations (cycle "All") : chips et cases plus compacts

**Thème / Réglages - Bouton Réinitialiser**

- 🔧 Libellé raccourci : "Réinitialiser" (au lieu de "Réinitialiser cette animation")
- 🔧 Style ambre pour meilleure visibilité (bordure et fond ambre)

**App / Contexte animation**

- 🔧 Contexte des paramètres d'animation basé sur fullAnimationId (choix utilisateur) : en mode "All", les réglages affichés sont toujours ceux du défilement (durée, aléatoire, liste d'animations)

---

### Added

**UniFi - Onglet NAT**

- ✅ Nouvel onglet "NAT" dans la page UniFi (placé après "Vue d'ensemble")
- ✅ Affichage de toutes les règles NAT/port forwarding configurées dans UniFi
- ✅ Détails des règles : nom, statut (Actif/Inactif), protocole, port destination, redirection (IP:port), source
- ✅ Filtre par statut : boutons "Tous" et "Actifs" pour filtrer les règles
- ✅ Compteur de règles : affiche "X règle(s) affichée(s) sur Y"
- ✅ Design cohérent avec les autres onglets UniFi (même style de cartes et badges)

**UniFi - API Backend NAT**

- ✅ Nouvelle méthode `getPortForwardingRules()` dans UniFiApiService pour récupérer les règles NAT depuis UniFi (`/api/s/<site>/rest/portforward`)
- ✅ Route API `GET /api/plugins/unifi/nat` pour exposer les règles NAT au frontend
- ✅ Normalisation des données : mapping des champs UniFi vers un format standardisé (id, name, enabled, protocol, dst_port, fwd_port, fwd_host, src, comment)

### Changed

**UniFi - Optimisation du Refresh NAT**

- 🔧 Séparation du chargement initial (`isInitialLoading`) et du refresh périodique (`isRefreshing`)
- 🔧 Polling conditionnel : le refresh ne s'active que quand l'onglet NAT est actif (`isActive`)
- 🔧 Indicateur de refresh discret : petite icône `RefreshCw` qui tourne dans le header (au lieu de recharger toute la page)
- 🔧 Suppression du scintillement : les refreshes périodiques sont silencieux et n'interrompent plus l'affichage
- 🔧 Chargement initial uniquement quand l'onglet devient actif (optimisation des performances)

---

## [0.5.3] - 2026-02-03

### Added

**Dashboard - Récapitulatif Réseau Séparé**

- ✅ Création de deux widgets distincts : `NetworkSummaryDashboardWidget` (dashboard principal) et `NetworkSummaryFreeboxWidget` (page Freebox)
- ✅ Widget dashboard : affiche UniFi + DHCP UniFi détaillé (statut, clients connectés, plage IP, gateway) + Freebox si présent
- ✅ Widget Freebox : affiche uniquement les informations Freebox (pas d'UniFi)
- ✅ Section DHCP UniFi détaillée dans le widget dashboard avec plage IP et nombre de clients

**UniFi - InfoSystème Enrichi**

- ✅ Ajout des informations DHCP UniFi dans InfoSystème : statut (Actif/Inactif), plage IP, IP utilisées (clients)
- ✅ Ajout des informations NAT UniFi dans InfoSystème : statut (Actif/Inactif), Gateway IP, nom du gateway
- ✅ Réorganisation de InfoSystème en 4 colonnes pour une meilleure compacité : Système, DHCP, NAT, Controller

**UniFi - API Backend DHCP**

- ✅ Extension de `getNetworkConfig()` dans UniFiApiService pour récupérer la plage DHCP (`dhcpd_start` et `dhcpd_stop`)
- ✅ Exposition de `dhcpRange` dans les stats système du plugin UniFi
- ✅ Transmission de la plage DHCP au frontend via l'API `/api/dashboard/network-summary`

### Changed

**Dashboard - Récapitulatif Réseau**

- 🔧 Suppression du doublon "Gestionnaire d'IPs Réseau (UniFi)" dans le widget dashboard (les informations DHCP UniFi sont maintenant uniquement dans la section dédiée)
- 🔧 Correction du doublon de plage IP dans le widget Freebox (affichage unique de la plage IP)

**UniFi - Interface Mobile**

- 🔧 Amélioration de l'intégration des badges de statut (Connexion, Site, Données) pour mobile
- 🔧 Header responsive : passage en colonne sur mobile (`flex-col sm:flex-row`)
- 🔧 Badges optimisés : texte réduit (`text-[10px] sm:text-xs`), icônes plus petites, espacements réduits
- 🔧 Séparateurs "•" masqués sur mobile pour plus de compacité
- 🔧 URL du controller tronquée (hostname uniquement sur mobile)
- 🔧 Titre et URL avec `truncate` pour éviter les débordements

**UniFi - InfoSystème**

- 🔧 Réorganisation en grille 2x2 colonnes pour meilleure utilisation de l'espace
- 🔧 Réduction des tailles de police pour les valeurs (`text-xs` pour certaines informations)
- 🔧 URL du controller avec hostname uniquement pour économiser l'espace

---

## [0.5.2] - 2026-02-03

### Added

**Scanner Réseau - Système de Blacklist IPs**

- ✅ Nouveau service `ipBlacklistService.ts` pour gérer la blacklist des IPs bannies
- ✅ Routes API blacklist : `GET /api/network-scan/blacklist`, `POST /api/network-scan/blacklist/add`, `DELETE /api/network-scan/blacklist/:ip`
- ✅ Bouton "Bannir" (icône orange ShieldX) dans la colonne Actions de la page Scanner pour bannir une IP
- ✅ Les IPs bannies sont exclues de tous les scans futurs et supprimées de la base de données
- ✅ Stockage de la blacklist dans `AppConfigRepository` avec la clé `network_scan_blacklist`

**Scanner Réseau - Rescan avec Ports**

- ✅ Nouvelle méthode `rescanSingleIpWithPorts()` pour rescanner une IP unique en mode complet
- ✅ Route API `POST /api/network-scan/:id/rescan` pour rescanner une IP avec scan de ports
- ✅ Bouton "Rescanner" (icône jaune RefreshCw) dans la colonne Actions de la page Scanner
- ✅ Bouton "Rescanner" dans la page de Recherche (résultats groupés et IP unique)
- ✅ Le rescan effectue : ping + détection MAC + hostname + vendor + scan de ports (nmap)

**Page de Recherche - Rescan IP**

- ✅ Bouton "Rescanner" dans la colonne Actions du tableau de résultats groupés
- ✅ Bouton "Rescanner" dans la section de détails d'une IP unique
- ✅ Rafraîchissement automatique des résultats après le rescan pour afficher les ports mis à jour

### Changed

**Scanner Réseau - Respect du Range Configuré**

- 🔧 Fonction `refreshExistingIps()` : filtrage par range configuré avant de scanner les IPs existantes
- 🔧 Fonction `parseIpRange()` : exclusion automatique des IPs Docker de la liste générée
- 🔧 Fonction `scanNetwork()` : exclusion des IPs Docker et bannies avant le scan
- 🔧 Fonction `scanSingleIp()` : vérification et exclusion des IPs Docker et bannies
- 🔧 Route `/api/network-scan/history` : filtrage automatique par range configuré, exclusion Docker et blacklist
- 🔧 Ajout de fonctions utilitaires : `isIpInRange()`, `isDockerIp()`, `getConfiguredRange()`

**Scanner Réseau - Exclusion des IPs Docker**

- 🔧 Détection automatique des IPs Docker : ranges 172.17.0.0/16 à 172.31.255.255 et 10.10.0.0/16
- 🔧 Exclusion des IPs Docker dans tous les scans (scan complet, refresh, scan unique)
- 🔧 Les IPs Docker n'apparaissent plus dans l'affichage des résultats

**Page de Recherche - Nettoyage URL**

- 🔧 Suppression automatique du paramètre `s` de l'URL lors de la navigation hors de la page de recherche
- 🔧 Nettoyage dans `App.tsx` via `useEffect` qui surveille les changements de page
- 🔧 Nettoyage dans `SearchPage.tsx` via wrapper `handleBack` et cleanup `useEffect`

### Fixed

**Scanner Réseau - Problèmes d'Affichage**

- 🐛 Correction : les IPs Docker (10.10.1.x, 172.17-31.x.x) n'apparaissent plus même si elles ne sont pas dans le range configuré
- 🐛 Correction : le refresh scannait toutes les IPs de la base sans respecter le range configuré (192.168.32.0/24)
- 🐛 Correction : les IPs hors du range configuré apparaissaient dans les résultats de recherche

---

## [0.5.1] - 2026-01-23

### Added

**Page de Recherche - Historique**

- ✅ Bouton "Effacer tout" dans le modal d'historique de recherche pour supprimer tout l'historique d'un clic
- ✅ Bouton visible uniquement si l'historique n'est pas vide

### Changed

**Page de Recherche - Interface**

- 🔧 Suppression du div vide au-dessus du champ de recherche
- 🔧 Ajustement de la hauteur des cadres recherche et filtres pour qu'ils soient égaux (utilisation de `items-stretch` et `flex-1`)
- 🔧 Amélioration du composant `Card` : le header n'est plus affiché si le titre est vide, éliminant l'espacement inutile

**Page Scanner - Colonne Status**

- 🔧 Renommage de la colonne "Ports" en "Status"
- 🔧 Affichage uniquement de l'icône (suppression du texte "Online"/"Offline")
- 🔧 Réduction de la largeur de la colonne (w-16, padding réduit)
- 🔧 Ajout de tooltips informatifs sur les icônes : "Online - Appareil en ligne", "Offline - Appareil hors ligne", "Unknown - Statut inconnu"
- 🔧 Centrage de l'icône dans la cellule

**Animations - Particle Waves**

- 🔧 Correction du bug des points fixes au centre de l'écran : les particules trop proches de la caméra sont maintenant filtrées au lieu d'être projetées au centre

---

## [0.5.0] - 2026-02-01

### Added

**Gestion des Thèmes - Sélection d'Animation Améliorée**

- ✅ Nouvelle section "Sélection de l'animation" avec grille multi-colonnes (2/3/4/5/6 colonnes selon la taille d'écran)
- ✅ Option "NON" en première position pour désactiver facilement les animations
- ✅ Affichage du nom de l'animation sélectionnée dans le preview de chaque thème
- ✅ Cartes cliquables pour chaque animation avec indicateur visuel de sélection (check jaune/rouge)
- ✅ Style distinct pour l'option "NON" (bordure rouge au lieu de jaune)

**Gestion des Thèmes - Réorganisation de l'Interface**

- ✅ Section "Opacité des blocs" déplacée en première position (avant la sélection des thèmes)
- ✅ Opacité fonctionne indépendamment de l'état de l'animation (même si animation désactivée)
- ✅ Réorganisation logique : Opacité → Thèmes → Animations → Paramètres

### Changed

**Gestion des Thèmes - Menu de Sélection d'Animation**

- 🔧 Menu de sélection d'animation modernisé : menu déroulant centré à l'écran au lieu d'un simple select
- 🔧 Menu scrollable avec toutes les animations visibles sans icônes (texte uniquement)
- 🔧 Largeur optimisée (500px) avec max-width responsive pour petits écrans
- 🔧 Boutons d'animation dans la grille : padding horizontal réduit (px-1.5) pour boutons plus compacts
- 🔧 Texte optimisé avec `leading-tight` pour meilleure utilisation de l'espace

**Gestion des Thèmes - Simplification de l'Interface**

- 🔧 Suppression de la section redondante "Arrière-plan animé" avec toggle d'activation
- 🔧 L'activation/désactivation se fait maintenant uniquement via l'option "NON" dans la grille d'animations
- 🔧 Section "Vitesse d'animation" et "Paramètres d'animation" affichées uniquement si animation activée

### Removed

**Thème Media Background**

- 🗑️ Suppression complète du thème "Media Background" (animation.99.media-background)
- 🗑️ Retrait du composant `MediaBackgroundCanvas` et de toutes ses dépendances
- 🗑️ Nettoyage des références dans `ThemeSection.tsx`, `AnimatedBackground.tsx`, `useBackgroundAnimation.ts` et `useAnimationParameters.ts`

---

## [0.4.8] - 2026-02-01

### Added

**Dashboard - Récapitulatif Réseau**

- ✅ Nouvelle route **GET /api/dashboard/network-summary** : agrégation Freebox (LAN, DMZ, DHCP, NAT) + UniFi (gateway, DHCP, clients)
- ✅ Affichage du widget Récapitulatif Réseau dès qu’un plugin Freebox **ou** UniFi est actif (au lieu de Freebox seul)
- ✅ Détection du rôle réseau : **Freebox**, **UniFi (Cloud Gateway)** ou **UniFi (via DMZ Freebox)** selon mode Freebox (bridge/routeur), DMZ et présence du gateway UniFi
- ✅ Passerelle et sous-réseau adaptés au setup (Freebox ou UniFi selon qui gère le réseau)
- ✅ Section **Freebox** : mode (Routeur/Bridge), IP, DMZ (actif/inactif + IP cible)
- ✅ Section **UniFi Gateway** : IP et nom du gateway (UGW, UDM, UCG)
- ✅ Liste **DHCP** : statut Actif/Inactif par source (Freebox, UniFi) avec détail (plage ou nombre de clients)
- ✅ **Règles NAT Freebox** : liste des redirections (commentaire, proto/port → IP:port), indicateur activé/désactivé
- ✅ **Gestionnaire d’IPs Réseau (Freebox)** : IPv4 libres/utilisées, utilisation % (quand le réseau est géré par Freebox et DHCP actif)
- ✅ **Gestionnaire d’IPs Réseau (UniFi)** : DHCP UniFi actif, nombre d’IP utilisées (clients) — affiché dès qu’un gateway UniFi + DHCP actif + comptage clients sont disponibles (indépendamment du rôle)

**UniFi - Vérification DHCP**

- ✅ **getNetworkConfig()** dans UniFiApiService : appel à `/api/s/<site>/rest/networkconf` pour lire `dhcpd_enabled` sur le réseau LAN
- ✅ Exposition de **dhcpEnabled** dans les stats système du plugin UniFi (dashboard et récap réseau)

### Changed

**Page Recherche - Colonne AP/Switch**

- 🔧 Backend (searchService) : déduction de **is_wireless** / **is_wired** à partir de `ap_name` ou `sw_name` lorsque les flags sont absents sur les clients UniFi
- 🔧 Frontend (SearchPage) : affichage du libellé AP/Switch dès qu’on a **ap_name** ou **sw_name**, même sans les flags is_wired/is_wireless (évite "--" pour les appareils comme Echo M5Stack)

**Dashboard**

- 🔧 Récapitulatif Réseau : source de données unique via `/api/dashboard/network-summary` (remplace les appels directs Freebox LAN/DHCP)

### Fixed

- 🐛 Colonne AP/Switch vide ("--") pour certains clients UniFi lorsque l’API ne renvoie pas is_wired/is_wireless : utilisation de ap_name/sw_name pour déduire le type et afficher WiFi/Filaire

---

## [0.4.7] - 2026-02-01

### Added

**Page Recherche**

- ✅ Bouton **Historique** dans la section Filtres : ouvre un modal avec l’historique des recherches (requête, options exact/étendu, case, actif)
- ✅ Persistance de l’historique dans localStorage (sans doublon), sélection d’une entrée pour relancer la recherche avec les mêmes options
- ✅ Badges **Recherches fréquentes** : affichage des 5 termes les plus recherchés sous la barre de recherche (cliquables)
- ✅ **Schéma de connexion UniFi** : bloc [ Appareil ] —trait—> [ Équipement | Port N ] avec trait WiFi (ondes) ou Filaire (câble + petits ovales), ports numérotés 1–8 avec le port connecté mis en avant
- ✅ Couleurs par catégorie de ports : **Système** (orange/ambre), **Docker** (indigo, ports 2375/2376), reste (cyan) — page Recherche et tooltip Scan Réseau
- ✅ Catégorie **Docker** dans les ports (préparation détection)

### Changed

**Page Recherche**

- 🔧 Suppression du bloc de sélection des plugins sous la barre de recherche (recherche sur tous les plugins actifs)
- 🔧 Infos mode strict/étendu déplacées dans le champ de recherche : placeholder et ligne d’aide sous l’input (« Strict : 1 IP ou 1 MAC uniquement — activer Étendu pour plus d’infos »)
- 🔧 **Badge Filaire** coloré (bleu) à la place du badge WiFi grisé quand pas de WiFi ou pas de RSSI valide ; plus de badge WiFi gris
- 🔧 Inversion des couleurs UniFi/Freebox en mode recherche étendue (tableau des résultats)
- 🔧 Panneau Ports ouverts en pleine largeur, boutons en flex-wrap pour ne plus être coupés
- 🔧 Ports détail : affichage plein page par catégorie (sans tooltip), comme le scan
- 🔧 Loupe de la barre de recherche recentrée (wrapper pour centrage par rapport au champ uniquement)

**Scripts version**

- 🔧 `bump-version.js` : mise à jour des plugins serveur (Freebox, UniFi, Scan Réseau) en plus de package.json, version.ts, main.tsx, README, Header
- 🔧 Plugins serveur synchronisés à la version 0.4.7

### Fixed

- 🐛 Badge connexion : si pas WiFi ou RSSI invalide, affichage « Filaire » (bleu) au lieu du badge WiFi grisé

---

## [0.4.6] - 2026-02-01

### Changed

- 🔧 Mise à jour de version (package.json, src/constants/version.ts, plugins serveur) pour invalidation du cache navigateur
- 🔧 Synchronisation des versions dans tous les fichiers du projet

---

## [0.4.5] - 2026-01-31

### Added

**Scan Réseau - Scan de ports (nmap)**

- ✅ Option "Scanner les ports ouverts après chaque scan complet" dans la config du scan auto (section Scan complet)
- ✅ Exécution en arrière-plan après chaque **Full scan** lorsque l’option est activée (Quick scan non concerné)
- ✅ Scan nmap (TCP, plage 1-10000) sur les IP **online** issues du scan, résultats stockés dans `additionalInfo` (openPorts, lastPortScan)
- ✅ Colonne **"Ports ouverts"** dans le tableau Scan Réseau : liste des ports (ex. 22, 80, 443), ou "En cours...", "En attente", "Aucun", "Non scanné"
- ✅ Indicateur dans l’en-tête de la colonne : icône de progression + compteur (current/total) quand le scan de ports est actif
- ✅ API **GET /api/network-scan/port-scan-progress** pour la progression du scan de ports
- ✅ Polling de la progression côté frontend (pendant et après le full scan) pour mettre à jour l’affichage en temps réel
- ✅ Dockerfile : ajout de **nmap** dans l’image runtime pour le scan de ports

**Page Search - Ports machine**

- ✅ Carte **"Ports ouverts (machine)"** dans la fiche détail d’une IP (recherche par IP exacte)
- ✅ Affichage de la liste des ports ouverts (scanner/nmap), date du dernier scan, ou "Aucun port ouvert" / "Non scanné"

### Changed

**Scan Réseau - Configuration unifiée**

- 🔧 Config unifiée étendue : `fullScan.portScanEnabled` (booléen) pour activer/désactiver le scan de ports après full scan
- 🔧 Route **POST /api/network-scan/unified-config** et **GET /api/network-scan/auto-status** : prise en charge de `portScanEnabled`

**Plugin UniFi - Priorité Controller / Site Manager**

- 🔧 Auto-détection : priorité au mode **Controller** si URL/username/password sont présents ; passage en Site Manager uniquement si URL unifi.ui.com + API key valide, ou si seule une API key est fournie
- 🔧 Nettoyage des paramètres de test (route test) : en mode controller, suppression de `apiKey` des settings de test pour éviter un basculement incorrect vers Site Manager (correction Docker vs npm dev)

### Fixed

**Plugin UniFi - Validation en Docker**

- 🐛 Correction du cas où le plugin fonctionnait en `npm run dev` mais échouait en Docker avec "Site Manager API error: 401" : la config Controller n’est plus écrasée par une API key résiduelle lors du test ou de l’initialisation

---

## [0.4.4] - 2026-01-25

### Added

**Plugin UniFi - Affichage du Type de Déploiement**

- ✅ Badge "Type:" affiché sur la carte du plugin UniFi quand connecté
- ✅ Détection et affichage automatique du type de déploiement :
  - "Site Manager (Cloud)" pour l'API cloud
  - "UniFiOS Gateway" pour les gateways UniFiOS (UDM Pro, UCG, etc.)
  - "Network Controller" pour les contrôleurs classiques
- ✅ Affichage conditionnel uniquement quand le plugin est connecté
- ✅ Couleurs distinctes selon le type de déploiement (indigo, purple, blue)

**Plugin UniFi - Détection Automatique Améliorée**

- ✅ Détection automatique UniFiOS vs Classic Controller lors du login
- ✅ Réutilisation intelligente du cookie de session pour éviter les doubles login
- ✅ Support complet de la documentation officielle UniFi API
- ✅ Détection automatique du mode Site Manager (cloud) si API key fournie

### Changed

**Plugin UniFi - Gestion des Erreurs Améliorée**

- 🔧 Messages d'erreur détaillés pour les erreurs réseau (ECONNREFUSED, timeout, SSL)
- 🔧 Détection spécifique de l'erreur 429 (Too Many Requests) avec indication de retry
- 🔧 Messages d'erreur en français avec suggestions contextuelles
- 🔧 Parsing amélioré des réponses d'erreur pour éviter "[object Object]"
- 🔧 Nettoyage automatique des messages d'erreur dupliqués
- 🔧 Messages d'erreur spécifiques selon le type de déploiement (UniFiOS vs Controller)

**Plugin UniFi - Modal de Configuration**

- 🔧 Désactivation du refresh automatique pendant l'édition du modal
- 🔧 Protection du formulaire contre la réinitialisation pendant l'édition
- 🔧 Boutons "Tester" et "Sauvegarder" mutuellement exclusifs (pas d'actions simultanées)
- 🔧 Tooltips explicatifs sur les boutons pour guider l'utilisateur
- 🔧 Le test ne fait plus de refresh qui casse le formulaire
- 🔧 La sauvegarde teste avec la configuration sauvegardée (pas le formulaire)

**Plugin UniFi - Protection Contre les Tests Inutiles**

- 🔧 Vérification du statut de connexion avant de faire un test
- 🔧 Si le plugin est déjà connecté avec les mêmes settings, retourne le statut sans test
- 🔧 Évite les tests qui déclenchent des erreurs 429 ou cassent la connexion active
- 🔧 Messages d'erreur clairs si le plugin est activé mais non connecté
- 🔧 Protection contre les tests répétés qui cassent le plugin

**WebSocket - Support des Accès Distants**

- 🔧 Détection automatique des accès via IP (pas localhost) en mode dev
- 🔧 Connexion directe au backend (port 3668) pour les accès distants, contournant le proxy Vite
- 🔧 Correction de l'erreur "Invalid frame header" pour les accès via IP
- 🔧 Gestion améliorée des erreurs WebSocket avec messages informatifs

### Fixed

**Plugin UniFi - Bouton de Test**

- 🐛 Correction du problème où le bouton "Tester" cassait le plugin après configuration
- 🐛 Le plugin ne se casse plus lors de tests répétés avec la même configuration
- 🐛 Le test ne réinitialise plus le formulaire pendant l'édition
- 🐛 Correction de l'affichage "[object Object]" dans les messages d'erreur
- 🐛 Correction des messages d'erreur dupliqués ("Verify URL... Verify URL...")

**Plugin UniFi - Détection de Déploiement**

- 🐛 Correction de la détection UniFiOS qui ne réutilisait pas le cookie de session
- 🐛 Amélioration de la gestion des erreurs réseau pendant la détection
- 🐛 Messages d'erreur plus clairs pour les problèmes de connexion (port 443 vs 8443)

**WebSocket - Connexions en Boucle**

- 🐛 Correction de l'erreur "Invalid frame header" qui causait des reconnexions infinies
- 🐛 Détection correcte du mode d'accès (Docker dev vs npm dev) pour le WebSocket

---

## [0.4.3] - 2026-01-24

### Added

**Plugin UniFi - Gestion des Erreurs 429 (Rate Limiting)**

- ✅ Système de retry avec backoff exponentiel pour les erreurs 429 (Too Many Requests)
- ✅ Respect automatique du header Retry-After si présent dans la réponse
- ✅ Délai d'attente progressif : 1 min, 2 min, 4 min (max 15 min)
- ✅ Maximum de 3 tentatives avec gestion intelligente des erreurs
- ✅ Réinitialisation automatique des compteurs après succès
- ✅ Évite les tentatives trop fréquentes qui déclenchent des erreurs 429

### Changed

**Plugin UniFi - Amélioration du Bouton "Tester"**

- 🔧 Restauration garantie de la configuration originale même en cas d'erreur
- 🔧 Évite la réinitialisation inutile si les paramètres de test sont identiques à la config actuelle
- 🔧 Vérification du statut réel de connexion avant de retourner un succès
- 🔧 Démarrage automatique du plugin après un test réussi si nécessaire
- 🔧 Le plugin ne peut plus être laissé dans un état cassé après un test
- 🔧 Logs de débogage pour tracer les problèmes de restauration

**Plugin UniFi - Cohérence du Statut de Connexion**

- 🔧 Le message "Test de connexion réussi" n'apparaît que si le plugin est réellement connecté
- 🔧 Le statut dans l'interface reflète toujours l'état réel de connexion
- 🔧 Le plugin ne passe plus en orange "Non connecté" après un test réussi
- 🔧 Vérification de la session active avant de relancer un login inutile

**WebSocket - Détection Automatique du Port Backend**

- 🔧 Détection automatique du port backend en mode dev (3668 pour Docker, 3003 pour npm)
- 🔧 Support de la variable d'environnement VITE_SERVER_PORT pour forcer le port
- 🔧 Messages d'erreur améliorés pour aider au débogage
- 🔧 Évite les erreurs de connexion WebSocket en mode développement

### Fixed

**Plugin UniFi - Test de Connexion**

- 🐛 Correction du problème où le test cassait le plugin après une configuration réussie
- 🐛 Correction de l'incohérence entre le message de succès et le statut réel du plugin
- 🐛 Le plugin reste fonctionnel même après plusieurs tests consécutifs

---

## [0.4.2] - 2026-01-23

### Added

**Freebox - Backup Complet de Configuration**

- ✅ Nouvelle section "Backup complet Freebox" dans l'onglet Backup
- ✅ Export complet de toutes les configurations Freebox dans un seul fichier JSON
- ✅ Import de backup avec restauration automatique des configurations
- ✅ Interface avec boutons Export/Import côte à côte
- ✅ Contenu du backup inclut :
  - Redirections de port WAN (Pare-feu)
  - Baux DHCP statiques
  - Configuration WiFi complète (full, config, BSS)
  - Configuration LAN (mode réseau, IP, hostnames)
  - Configuration de connexion (ping, WOL, adblock, accès distant)
  - Configurations DynDNS (OVH, DynDNS, No-IP)
- ✅ Avertissement de sécurité avant import
- ✅ Validation du format de backup avant import
- ✅ Affichage des résultats d'import (succès/erreurs)

**Dashboard - Widget Récapitulatif Réseau**

- ✅ Ajout du widget "Récapitulatif Réseau" sur le dashboard principal
- ✅ Placement au-dessus du widget "Système Serveur" dans la colonne de gauche
- ✅ Affichage conditionnel uniquement si le plugin Freebox est actif et connecté
- ✅ Affichage des informations réseau essentielles directement sur le dashboard

### Changed

**Freebox - Réorganisation de l'Onglet Réseau**

- 🔧 Séparation des catégories en blocs distincts pour une meilleure organisation
- 🔧 Section "Options réseau" simplifiée avec uniquement :
  - Réponse au ping
  - Wake on LAN
  - Blocage de publicités
- 🔧 Nouvelles sections séparées créées :
  - **Mode réseau** : Choix du mode (Server/Bridge) et adresse IP du Freebox Server
  - **Nom d'hôte** : Nom du Freebox Server, Nom DNS, Nom mDNS, Nom NetBIOS
  - **Nom de domaine** : Affichage du domaine personnalisé et certificat TLS
  - **DNS Dynamique** : Configuration complète DynDNS avec fournisseurs multiples
- 🔧 Section "Accès distant" déplacée en dernière position dans l'onglet Réseau
- 🔧 Amélioration de la lisibilité et de la navigation dans les paramètres réseau

**Freebox - Informations Freebox**

- 🔧 Section "Informations Freebox" (token) déplacée de l'onglet Réseau vers l'onglet Sécurité
- 🔧 Placement en première position dans l'onglet Sécurité pour un accès rapide

---

## [0.4.1] - 2026-01-21

### Added

**Freebox - Filtrage MAC WiFi**

- ✅ Ajout de la section "Filtrage MAC" dans les paramètres WiFi Freebox
- ✅ Toggle pour activer/désactiver le filtrage MAC
- ✅ Sélecteur de mode : Liste blanche ou Liste noire
- ✅ Gestion de la liste des adresses MAC avec ajout/suppression
- ✅ Validation du format MAC (XX:XX:XX:XX:XX:XX ou XX-XX-XX-XX-XX-XX)
- ✅ Ajout d'adresses MAC via champ de saisie (bouton ou touche Enter)
- ✅ Route API PUT `/api/wifi/mac-filter` pour sauvegarder la configuration
- ✅ Méthode `setWifiMacFilter()` dans le service Freebox API

**Scripts - Mise à jour de Version Automatique**

- ✅ Affichage de la version actuelle par défaut si aucun argument n'est fourni
- ✅ Détection automatique des modifications Git avec `git status`
- ✅ Préparation automatique du commit si des modifications sont détectées
- ✅ Ajout automatique des fichiers au staging area pour le commit
- ✅ Gestion des versions invalides (comme `--help`) avec valeur par défaut

### Changed

**Scripts - update-version.sh**

- 🔧 Amélioration de la logique de détection des modifications Git
- 🔧 Préparation automatique du commit avec confirmation utilisateur
- 🔧 Affichage amélioré du statut Git et des fichiers modifiés
- 🔧 Gestion des cas où la version existe déjà dans le CHANGELOG

---

## [0.4.0] - 2026-01-13

### Added

**Recherche - Support des Paramètres d'URL**

- ✅ Support du paramètre `?s=IP` dans l'URL pour rechercher directement une IP
- ✅ Navigation automatique vers la page de recherche si le paramètre `s` est présent dans l'URL
- ✅ Synchronisation bidirectionnelle entre l'URL et l'état de recherche
- ✅ Support des boutons précédent/suivant du navigateur pour la navigation
- ✅ Liens depuis la page scan réseau vers la recherche avec paramètre d'URL (au lieu de sessionStorage)
- ✅ URLs partageables : `192.168.1.150:5173/?s=192.168.1.41` ouvre directement la recherche

**Ping - Mode Strict et Étendu**

- ✅ Mode strict par défaut : ping d'une seule IP exacte quand le ping est activé
- ✅ Mode étendu activable : permet de pinger des ranges d'IP (CIDR et plages)
- ✅ Support des formats de range : `192.168.1.0/24`, `192.168.1.1-254`, `192.168.1.1-192.168.1.254`
- ✅ Parsing côté client des ranges IP avec génération automatique de la liste d'IPs
- ✅ Fast ping : utilisation de `count=1` pour vérification rapide UP/DOWN

**Ping - Interface Utilisateur**

- ✅ Affichage "UP" ou "DOWN" en gros sous la barre de recherche
- ✅ Affichage de la latence en millisecondes dans le tableau de résultats
- ✅ Affichage de la latence dans la carte Latence des détails IP
- ✅ Détails d'erreur affichés uniquement si le ping est DOWN
- ✅ Masquage automatique du bouton "Rechercher" quand le ping est activé
- ✅ Touche Enter pour valider les pings au lieu de lancer une recherche quand ping est actif
- ✅ Aide contextuelle affichée quand le ping est activé avec explications du mode strict/étendu

**Ping - Documentation**

- ✅ Mise à jour du modal d'aide avec explications du mode strict et étendu
- ✅ Exemples de formats de ranges supportés dans la documentation
- ✅ Explications des différences entre mode strict (1 IP) et mode étendu (ranges)

### Changed

**Recherche - Navigation**

- 🔧 Lecture du paramètre `s` depuis l'URL en priorité (au lieu de sessionStorage)
- 🔧 Mise à jour automatique de l'URL lors des recherches
- 🔧 NetworkScanPage utilise maintenant l'URL au lieu de sessionStorage pour les liens vers la recherche

**Ping - Comportement**

- 🔧 Activation du ping force le mode strict (exactMatch=true) par défaut
- 🔧 Le mode étendu peut être activé même quand le ping est actif (pour permettre les ranges)
- 🔧 Ping rapide avec count=1 mais latence toujours stockée pour affichage dans les résultats

**Interface Utilisateur - Ping**

- 🔧 Bouton "Rechercher" masqué conditionnellement quand pingEnabled est true
- 🔧 Comportement de la touche Enter adaptatif selon l'état du ping
- 🔧 Affichage contextuel de l'aide ping avec informations selon le mode actif

### Fixed

**Ping - Affichage**

- ✅ Correction de l'affichage "undefinedms" : vérification de l'existence de la latence avant affichage
- ✅ Affichage de "UP" si la latence n'est pas disponible mais que le ping est réussi
- ✅ Gestion correcte des cas où time est undefined dans les résultats de ping

---

## [0.3.9] - 2025-01-02

### Fixed

**Erreur de Compilation - Double Déclaration de Variable**

- 🐛 Correction de la double déclaration de `routePath` dans `getHostMachineIP()` (ligne 405)
- 🐛 Déplacement de la déclaration de `routePath` au niveau de la fonction pour éviter les conflits de scope
- 🐛 Résolution de l'erreur `The symbol "routePath" has already been declared` au démarrage Docker

**Affichage du Port dans les Logs Docker**

- 🐛 Ajout de la variable d'environnement `DASHBOARD_PORT` dans les fichiers docker-compose
- 🐛 Le port affiché dans les logs correspond maintenant au port défini dans docker-compose
- 🐛 Synchronisation du port par défaut entre `docker-compose.yml` et `server/index.ts` (7555)
- 🐛 Correction de l'affichage du port dans les logs au démarrage

**Fichiers Modifiés**

- `server/index.ts` : Correction de la double déclaration et synchronisation du port par défaut
- `docker-compose.yml` : Ajout de `DASHBOARD_PORT` dans la section `environment`
- `docker-compose.local.yml` : Ajout de `DASHBOARD_PORT` dans la section `environment`
- `docker-compose.dev.yml` : Variable déjà présente, vérification effectuée

### Changed

**Noms de Conteneurs Docker**

- 🔧 Ajout de `container_name` explicite dans tous les fichiers docker-compose
- 🔧 Conteneurs nommés de manière fixe et prévisible :
  - `mynetwork` pour la production (`docker-compose.yml`)
  - `mynetwork-local` pour le build local (`docker-compose.local.yml`)
  - `mynetwork-dev` pour le mode développement (`docker-compose.dev.yml`)
- 🔧 Plus de noms générés automatiquement comme `mynetwork-mynetwork-1`

**Configuration Docker**

- 🔧 Ajout de la variable `HOST_IP` optionnelle dans `docker-compose.yml` pour forcer l'IP de la machine hôte
- 🔧 Documentation améliorée pour la configuration du port et de l'IP dans les logs

**Fichiers Modifiés**

- `docker-compose.yml` : Ajout de `container_name` et `HOST_IP` optionnel
- `docker-compose.local.yml` : Ajout de `container_name`
- `docker-compose.dev.yml` : Ajout de `container_name`

---

## [0.3.8] - 2025-01-02

### Security

**Agents HTTPS Personnalisés pour Freebox et UniFi**

- 🔒 Remplacement de `NODE_TLS_REJECT_UNAUTHORIZED = '0'` global par des agents HTTPS sélectifs
- 🔒 Utilisation d'agents `undici` personnalisés avec `rejectUnauthorized: false` uniquement pour Freebox/UniFi
- 🔒 Plus de désactivation globale de la vérification TLS - sécurité améliorée
- 🔒 Fallback automatique vers variable d'environnement si `undici` n'est pas disponible
- 🔒 Suppression de l'avertissement TLS au démarrage Docker

**Fichiers Modifiés**

- `server/services/freeboxApi.ts` : Agent HTTPS personnalisé pour toutes les requêtes Freebox
- `server/plugins/freebox/FreeboxApiService.ts` : Agent HTTPS personnalisé pour le plugin Freebox
- `server/plugins/unifi/UniFiApiService.ts` : Agent HTTPS personnalisé pour le plugin UniFi

### Changed

**Détection IP Machine Hôte dans Docker**

- 🔧 Amélioration de `getHostMachineIP()` dans `server/index.ts` pour lire l'IP réelle depuis `/host/proc/net/route`
- 🔧 Parsing du fichier de routage pour trouver l'interface par défaut et son gateway
- 🔧 Conversion du gateway Docker (hex) en adresse IP lisible
- 🔧 Fallback vers gateway Docker si l'IP réelle n'est pas trouvée
- 🔧 Priorité donnée à la variable d'environnement `HOST_IP` (la plus fiable)
- 🔧 Affichage de l'IP de la machine hôte au lieu de l'IP Docker interne (172.18.0.2) dans les logs

**Nettoyage du Code**

- 🔧 Suppression du code de suppression d'avertissement TLS dans `server/index.ts` (lignes 1-35)
- 🔧 Code plus propre et maintenable sans interception d'avertissements

### Fixed

**Avertissement StorageType.persistent en Production**

- 🐛 Suppression de l'avertissement déprécié `StorageType.persistent is deprecated` en production Docker
- 🐛 Interception de `console.warn` pour filtrer uniquement cet avertissement spécifique
- 🐛 Conservation de tous les autres avertissements pour le debugging
- 🐛 Console du navigateur plus propre en production

**Fichiers Modifiés**

- `src/main.tsx` : Ajout de la suppression conditionnelle de l'avertissement StorageType.persistent

### Documentation

**Amélioration de la Documentation**

- 📝 Commentaires détaillés expliquant l'utilisation des agents HTTPS personnalisés
- 📝 Explication de la logique de fallback pour la compatibilité
- 📝 Documentation de la détection IP hôte dans Docker

---

## [0.3.7] - 2025-01-02

### Changed

**Affichage des Disques dans SystemServerWidget**

- 🔧 Remplacement des chemins système (`/etc/resolv.conf`, `/etc/hostname`, `/etc/hosts`) par des noms génériques ("Disque 1", "Disque 2", etc.)
- 🔧 Affichage du nom réel du disque si disponible (ex: `/dev/sda1` → `sda1`)
- 🔧 Amélioration de la lisibilité et de la cohérence de l'affichage

**Nettoyage des Logs Console**

- 🔧 Suppression des logs de debug dans `LatencyMonitoringModal` (Raw measurements, Total measurements, Sample latencies, Chart data)
- 🔧 Suppression des logs de debug dans `NetworkScanPage` (onDataChanged, Local state cleared, All data refreshed)
- 🔧 Conservation uniquement des logs d'erreur essentiels pour le debugging

### Accessibility

**Amélioration de l'Accessibilité des Formulaires**

- ♿ Ajout d'attributs `id` et `name` à tous les champs de formulaire manquants
- ♿ Association correcte des labels avec `htmlFor` pour tous les champs
- ♿ Corrections dans les modaux suivants :
  - `UserLoginModal` : champs username et password
  - `PluginConfigModal` : tous les champs de configuration (api-mode, api-key, unifi-url, unifi-username, unifi-password, unifi-site)
  - `NetworkScanConfigModal` : checkboxes et selects (auto-scan-enabled, full-scan-enabled, refresh-enabled, intervals, default-range)
  - `LoginModal` : champs local-ip et freebox-url
  - `CreateVmModal` : champ vm-name
  - `NetworkScanPage` : champ de recherche, select de filtre, select résultats par page, champs d'édition hostname, modal d'ajout IP

**Conformité aux Standards Web**

- ♿ Tous les champs de formulaire ont maintenant des attributs `id` et `name`
- ♿ Tous les labels sont correctement associés avec `htmlFor`
- ♿ Amélioration de l'autocomplétion du navigateur
- ♿ Meilleure compatibilité avec les lecteurs d'écran

## [0.3.6] - 2025-01-01

### Optimized

**Chargement de l'Onglet Plugins**

- ✅ Temps de chargement réduit de plusieurs secondes à < 500ms
- ✅ Retrait des tests de connexion systématiques au chargement de `/api/plugins`
- ✅ Retrait des appels `getPluginStats()` pour firmware/version au chargement
- ✅ Vérification légère du statut de connexion sans appels API lourds
- ✅ Cache intelligent de 30 secondes pour éviter les rechargements inutiles
- ✅ Réduction drastique des appels API : 1 appel au lieu de 6+ appels au chargement

**Route `/api/plugins`**

- ✅ Retour uniquement des informations de base depuis la DB et le plugin
- ✅ Vérification légère du statut de connexion :
  - Freebox : utilise `freeboxApi.isLoggedIn()` (vérification synchrone, pas d'appel API)
  - UniFi : utilise `unifiPlugin.apiService.isLoggedIn()` (vérification d'état interne)
  - Scanner réseau : toujours connecté si activé (pas de connexion externe)
- ✅ Validation simple de la structure des données retournées
- ✅ Filtrage automatique des plugins invalides avec warnings

**Composant PluginsManagementSection**

- ✅ Chargement unique au montage (pas de rechargements multiples)
- ✅ Retrait des `fetchPlugins()` redondants après chaque action mineure
- ✅ Optimisation des `useEffect` pour éviter les appels multiples
- ✅ Vérification conditionnelle de l'authentification Freebox (uniquement si nécessaire)

**Store pluginStore**

- ✅ Cache avec timestamp (`lastFetchTime`)
- ✅ Durée du cache : 30 secondes
- ✅ Paramètre `force` pour forcer le refresh si nécessaire
- ✅ Validation des données reçues (structure, types)
- ✅ Filtrage des plugins invalides avec warnings console

### Changed

**Route `/api/plugins` - Architecture**

- 🔧 Retrait des appels `testPluginConnection()` systématiques pour chaque plugin activé
- 🔧 Retrait des appels `getPluginStats()` pour récupérer firmware/version
- 🔧 Utilisation de méthodes légères pour vérifier le statut de connexion
- 🔧 Validation de la structure des données avant retour

**PluginsManagementSection - Gestion des Appels**

- 🔧 `useEffect` avec dépendances vides pour charger une seule fois au montage
- 🔧 Retrait de `fetchPlugins()` après `handleToggle` (déjà géré par `updatePluginConfig`)
- 🔧 Retrait de `fetchPlugins()` après login Freebox (non nécessaire)
- 🔧 Conservation uniquement après test de connexion et fermeture de config (avec `force: true`)
- 🔧 Optimisation du `useEffect` pour Freebox auth (vérification conditionnelle)

**pluginStore - Cache et Validation**

- 🔧 Ajout du paramètre `force?: boolean` à `fetchPlugins()`
- 🔧 Vérification du cache avant chaque appel API
- 🔧 Validation de la structure de réponse (vérification que c'est un tableau)
- 🔧 Validation de chaque plugin (id, name, enabled, version)
- 🔧 Filtrage automatique des plugins invalides

### Documentation

**Optimisation Performance**

- 📝 Commentaires détaillés expliquant les optimisations dans le code
- 📝 Explication de la logique de cache et de validation

---

## [0.3.6] - 2025-12-30

### Fixed

**Route `/api/network-scan/database-size-estimate` - Erreur 404**

- ✅ Correction de l'erreur 404 sur l'endpoint `/api/network-scan/database-size-estimate`
- ✅ Déplacement de la route avant la route dynamique `/:id` pour éviter les conflits de routage
- ✅ La route est maintenant correctement accessible depuis l'interface de maintenance

**Affichage "Rapide" au lieu de "Full" pour Full Scan**

- ✅ Correction de l'affichage du type de scan dans le widget dashboard et la page scan
- ✅ Le badge "Full Scan" affiche maintenant toujours "Complet" au lieu de "Rapide"
- ✅ Le dernier scan de type "full" affiche correctement "Complet" dans les deux interfaces

### Added

**Badge Plage IP**

- ✅ Ajout d'un badge cyan affichant la plage IP scannée dans le widget dashboard
- ✅ Ajout d'un badge cyan affichant la plage IP scannée dans la page scan réseau
- ✅ Badge affiché dans le dernier scan et dans les prochains scans automatiques
- ✅ Badge également disponible dans la colonne de gauche sous "Base vendors" avec label "Réseau:"

**Tri pour Toutes les Colonnes**

- ✅ Ajout du tri pour les colonnes "Avg1h", "Max" et "Monitoring" dans le tableau des scans
- ✅ Tri côté client utilisant les données déjà chargées (`latencyStats` et `monitoringStatus`)
- ✅ Indicateurs visuels (flèches) pour toutes les colonnes triables
- ✅ La colonne "Actions" reste non triable comme prévu

### Changed

**Réorganisation Section "Info Scans"**

- 🔧 Le "Dernier Scan" est maintenant affiché sous les sections "Full Scan" et "Refresh"
- 🔧 Ajout d'une bordure supérieure pour séparer visuellement le dernier scan
- 🔧 Meilleure organisation visuelle de l'information

**Positionnement Badge Réseau**

- 🔧 Badge réseau retiré de la ligne "Full Scan" dans la colonne de gauche
- 🔧 Badge réseau déplacé dans la colonne de gauche sous "Base vendors" avec label "Réseau:"
- 🔧 Badge réseau déplacé après le temps dans le widget dashboard (après "Dans Xh (HH:MM)")
- 🔧 Alignement des labels "Base vendors:", "Réseau:" et "Scan auto:" avec largeur fixe pour cohérence visuelle

**Widget Dashboard - Scan Réseau**

- 🔧 Réorganisation de l'ordre des éléments dans la ligne "Full Scan"
- 🔧 Ordre final : "Full Scan" → "Complet" → "Dans Xh (HH:MM)" → Badge réseau

---

## [0.3.4] - 2025-12-30

### Fixed

**Freebox Revolution - Appels Simultanés Multiples**

- ✅ Implémentation d'un système de verrous par endpoint pour éviter les appels simultanés multiples au même endpoint
- ✅ Si un appel à `/lan/browser/pub/` est déjà en cours, les autres appels réutilisent la même promesse au lieu d'en créer une nouvelle
- ✅ Élimination des appels parallèles multiples depuis `/api/lan`, `/api/wifi`, et `FreeboxPlugin.getStats()`
- ✅ Réduction drastique des erreurs `AbortError` causées par la surcharge de la Freebox Revolution

**Freebox Revolution - Timeouts Insuffisants**

- ✅ Augmentation des timeouts pour Revolution : 45s pour endpoints lents (au lieu de 30s)
- ✅ Augmentation des timeouts pour Revolution : 25s pour autres endpoints (au lieu de 20s)
- ✅ Endpoints lents identifiés : `/lan/browser/pub/`, `/dhcp/dynamic_lease/`, `/dhcp/static_lease/`, `/fw/redir/`
- ✅ Les autres modèles Freebox conservent leurs timeouts par défaut (10s)

**Freebox Revolution - Pas de Retry sur Timeouts**

- ✅ Implémentation d'un système de retry automatique avec backoff exponentiel pour Revolution
- ✅ Retry uniquement sur erreurs `AbortError` (timeout) et uniquement pour Revolution sur endpoints lents
- ✅ Maximum 2 tentatives supplémentaires (3 appels au total) pour éviter de surcharger la Freebox
- ✅ Délais de retry : 1s puis 2s (backoff exponentiel)

### Added

**Système de Verrous par Endpoint**

- ✅ Nouvelle méthode `requestWithLock()` pour éviter les appels simultanés multiples au même endpoint
- ✅ Map `endpointLocks` pour stocker les promesses en cours par endpoint
- ✅ Réutilisation automatique de la promesse existante si un appel est déjà en cours
- ✅ Libération automatique du verrou après résolution de la promesse (succès ou échec)

**Détection des Endpoints Lents**

- ✅ Nouvelle méthode `isSlowEndpoint()` pour identifier les endpoints problématiques sur Revolution
- ✅ Liste des endpoints lents : `/dhcp/dynamic_lease/`, `/dhcp/static_lease/`, `/fw/redir/`, `/lan/browser/pub/`
- ✅ Utilisée pour appliquer des timeouts et retries spécifiques uniquement où nécessaire

**Retry avec Backoff Exponentiel**

- ✅ Nouvelle méthode `requestWithRetry()` pour retenter automatiquement les timeouts sur Revolution
- ✅ Backoff exponentiel : délais de 1s puis 2s entre les tentatives
- ✅ Activation uniquement pour Revolution et uniquement sur endpoints lents
- ✅ Détection automatique des erreurs `AbortError` pour déclencher le retry

### Changed

**FreeboxApiService - Architecture des Requêtes**

- 🔧 Toutes les méthodes publiques utilisent maintenant `requestWithLock()` au lieu de `request()` directement
- 🔧 `requestWithLock()` appelle `requestWithRetry()` en interne pour gérer les retries
- 🔧 `requestWithRetry()` appelle `request()` en interne avec gestion des retries
- 🔧 Les méthodes d'authentification (`register`, `login`, `logout`, `getChallenge`) continuent d'utiliser `request()` directement (pas de lock nécessaire)

**FreeboxApiService - Timeouts Adaptatifs**

- 🔧 `getTimeoutForEndpoint()` retourne maintenant 45s pour endpoints lents sur Revolution (au lieu de 30s)
- 🔧 `getTimeoutForEndpoint()` retourne maintenant 25s pour autres endpoints sur Revolution (au lieu de 20s)
- 🔧 Utilisation de `isSlowEndpoint()` pour identifier les endpoints nécessitant des timeouts plus longs
- 🔧 Les autres modèles Freebox conservent le timeout par défaut (10s)

**Gestion des Erreurs AbortError**

- 🔧 `request()` ajoute maintenant un flag `_isAbortError` dans la réponse d'erreur pour faciliter la détection
- 🔧 `requestWithRetry()` détecte les `AbortError` via le flag ou via le message d'erreur
- 🔧 Retry automatique uniquement si conditions remplies (Revolution + endpoint lent + AbortError)

---

## [0.3.3] - 2025-12-30

### Fixed

**Freebox Plugin - WebSocket au Démarrage**

- ✅ Le WebSocket Freebox ne démarre plus si le plugin est désactivé
- ✅ Vérification de l'état du plugin avant chaque tentative de connexion WebSocket
- ✅ Arrêt automatique des tentatives de reconnexion si le plugin est désactivé
- ✅ Réduction des logs Freebox inutiles quand le plugin est désactivé

**UniFi Plugin - Appels API Inutiles**

- ✅ Le plugin UniFi ne fait plus d'appels API si désactivé
- ✅ Vérification de `isEnabled()` dans `stop()` avant d'appeler `logout()`
- ✅ Protection contre les appels API inutiles même lors de la réinitialisation du plugin

**Freebox Revolution - Appels Simultanés**

- ✅ Protection renforcée contre les appels simultanés multiples aux mêmes endpoints
- ✅ Réduction des erreurs `AbortError` grâce à une meilleure gestion des requêtes parallèles

### Added

**Optimisation Détection MAC - Cache des Stats Plugins**

- ✅ Cache des stats Freebox/UniFi pendant le scan pour éviter les appels répétés à `getStats()`
- ✅ Un seul appel à `getStats()` par plugin au début du scan au lieu d'un par IP
- ✅ Amélioration significative des performances de scan avec Freebox/UniFi activés
- ✅ Cache automatiquement invalidé à la fin du scan pour libérer la mémoire

**Documentation Options "Écraser"**

- ✅ Descriptions détaillées des options "Écraser les hostnames existants" et "Écraser les vendors existants"
- ✅ Recommandations d'utilisation ajoutées pour guider les utilisateurs
- ✅ Notes explicatives sur le comportement avec les vendors vides/invalides

### Changed

**NetworkScanService - Cache des Stats Plugins**

- 🔧 Ajout de `cachedFreeboxStats`, `cachedUniFiStats`, `cacheTimestamp` pour le cache
- 🔧 Méthode `initializePluginStatsCache()` pour charger les stats une seule fois au début du scan
- 🔧 Méthode `invalidatePluginStatsCache()` pour nettoyer le cache après le scan
- 🔧 `getMacFromFreebox()` et `getMacFromUniFi()` utilisent maintenant le cache au lieu d'appeler `getStats()` à chaque fois
- 🔧 Fallback automatique vers `getStats()` si le cache expire ou n'est pas disponible

**FreeboxNativeWebSocket - Vérification Plugin**

- 🔧 Vérification de l'état du plugin Freebox avant de démarrer le WebSocket
- 🔧 Vérification dans `start()`, `connect()`, `scheduleReconnect()`, et `onLogin()`
- 🔧 Arrêt automatique si le plugin est désactivé pendant une reconnexion

**Routes Auth - WebSocket Conditionnel**

- 🔧 `freeboxNativeWebSocket.onLogin()` appelé uniquement si le plugin Freebox est activé
- 🔧 Évite les tentatives de connexion WebSocket inutiles

**SettingsPage - Documentation Améliorée**

- 🔧 Descriptions plus détaillées des options "Écraser" avec explications claires
- 🔧 Recommandations et notes importantes ajoutées pour chaque option

---

## [0.3.2] - 2025-12-30

### Fixed

**Détection MAC dans Docker - Plugins en Priorité**

- ✅ Réorganisation de `getMacAddress()` pour utiliser les plugins activés (Freebox, UniFi) EN PREMIER selon la configuration de priorité
- ✅ Les méthodes système (ARP, `/proc/net/arp`) sont utilisées comme fallback si les plugins échouent ou ne sont pas activés
- ✅ Garantie que les méthodes système fonctionnent correctement même sans plugins (essentiel pour Docker)
- ✅ Correction du problème où certaines IPs n'avaient aucune MAC détectée dans Docker avec seulement le plugin Scanner activé

**Détection MAC - Support UniFi**

- ✅ Ajout de la méthode `getMacFromUniFi()` pour récupérer les MAC depuis le plugin UniFi
- ✅ Recherche dans `unifiPlugin.getStats().devices` par IP
- ✅ Validation du format MAC avant retour

**Détection Vendor - Fonctionnement sans Plugins**

- ✅ Garantie que la détection de vendor fonctionne même sans plugins grâce à la base de données Wireshark/OUI
- ✅ `getVendorWithSource()` utilise déjà le plugin "scanner" dans la priorité par défaut
- ✅ La plupart des MAC adresses peuvent être identifiées via la base de données OUI même sans plugins

### Added

**Détection MAC - Méthode UniFi**

- ✅ Nouvelle méthode `getMacFromUniFi(ip: string)` dans `NetworkScanService`
- ✅ Support complet de la détection MAC depuis UniFi Controller
- ✅ Recherche par IP dans les devices UniFi (access points, switches, clients)

### Changed

**NetworkScanService - Ordre de Détection MAC**

- 🔧 `getMacAddress()` réorganisé pour essayer les plugins activés en premier selon la priorité configurée
- 🔧 Si aucun plugin n'est activé OU si tous les plugins échouent, utilisation des méthodes système
- 🔧 Les méthodes système restent disponibles et fonctionnent même sans plugins
- 🔧 Logs améliorés pour indiquer quelle méthode (plugin ou système) a réussi/échoué

**NetworkScanService - Logs de Diagnostic**

- 🔧 Logs détaillés ajoutés pour chaque tentative de détection MAC
- 🔧 Logs indiquant les raisons d'échec (plugin non activé, pas de données, erreur réseau, etc.)
- 🔧 Logs du résultat final (MAC trouvée ou non, source utilisée)
- 🔧 Logs de débogage dans `getMacFromFreebox()` et `getMacFromUniFi()`

**Refresh Existing IPs**

- 🔧 `refreshExistingIps()` utilise automatiquement la nouvelle logique de détection MAC via `getMacAddress()`
- 🔧 Les MAC détectées lors du refresh sont correctement sauvegardées

---

## [0.3.1] - 2025-12-30

### Fixed

**Freebox Revolution - Appels Répétés et Timeouts**

- ✅ Protection contre les appels simultanés multiples à `getStats()` pour éviter la surcharge
- ✅ Si un appel est déjà en cours, réutilisation de la même promesse au lieu de créer un nouveau
- ✅ Réduction des erreurs `AbortError` grâce aux timeouts adaptatifs par endpoint
- ✅ Endpoints lents (`/dhcp/dynamic_lease/`, `/dhcp/static_lease/`, `/fw/redir/`, `/lan/browser/pub/`) : timeout de 30s sur Revolution
- ✅ Autres endpoints : timeout de 20s sur Revolution (au lieu de 10s pour les autres modèles)

**Freebox Revolution - Détection WiFi BSS**

- ✅ Amélioration de la détection SSID avec vérification de plus de champs (`ssid`, `name`, `config.ssid`, `id`, `bssid`)
- ✅ Logs de débogage ajoutés pour diagnostiquer les problèmes de détection WiFi
- ✅ Log du contenu complet du premier BSS si aucun réseau n'est trouvé
- ✅ Meilleure gestion des cas où le SSID est dans un champ non standard

### Changed

**FreeboxPlugin - Protection Concurrente**

- 🔧 Ajout de `isGettingStats` et `statsPromise` pour protéger contre les appels simultanés
- 🔧 Refactorisation de `getStats()` avec méthode interne `_getStatsInternal()`
- 🔧 Réutilisation de la promesse en cours si un appel est déjà actif

**FreeboxApiService - Timeouts Adaptatifs**

- 🔧 Nouvelle fonction `getTimeoutForEndpoint()` pour timeouts adaptatifs par endpoint
- 🔧 Timeout de 30s pour endpoints lents sur Revolution uniquement
- 🔧 Timeout de 20s pour autres endpoints sur Revolution
- 🔧 Timeout de 10s inchangé pour tous les autres modèles (Pop, Ultra, Delta)

**FreeboxPlugin - Requêtes Parallèles Groupées**

- 🔧 Requêtes organisées en 3 groupes séquentiels au lieu de toutes en parallèle
- 🔧 Groupe 1 : endpoints rapides (connection, system)
- 🔧 Groupe 2 : endpoints DHCP (config, leases)
- 🔧 Groupe 3 : endpoints réseau (LAN browser, port forwarding, WiFi)
- 🔧 Évite de surcharger la Freebox Revolution avec trop de requêtes simultanées

---

## [0.3.0] - 2025-12-29

### Fixed

**Scan Réseau - Erreur 504 Gateway Timeout**

- ✅ Correction de l'erreur 504 lors des scans longs (>60 secondes)
- ✅ Le scan est maintenant asynchrone : démarre immédiatement sans bloquer la requête HTTP
- ✅ Plus de timeout HTTP : le scan continue en arrière-plan pendant que le frontend suit le progrès
- ✅ Résolution définitive du problème de timeout sur les scans de grandes plages réseau

### Added

**Scan Réseau - Architecture Asynchrone**

- ✅ Route POST `/api/network-scan/scan` retourne immédiatement avec statut "scan démarré"
- ✅ Stockage des résultats finaux dans `NetworkScanService` pour récupération via polling
- ✅ Endpoint GET `/api/network-scan/progress` retourne les résultats finaux une fois le scan terminé
- ✅ Frontend adapté pour gérer la réponse asynchrone et récupérer automatiquement les résultats
- ✅ Meilleure expérience utilisateur : suivi du progrès en temps réel sans erreurs

**Service Scan Réseau - Gestion des Résultats**

- ✅ Ajout du champ `lastScanResult` pour stocker les résultats finaux du scan
- ✅ Méthode `getLastScanResult()` pour récupérer les résultats après completion
- ✅ Nettoyage automatique des résultats lors du démarrage d'un nouveau scan

### Changed

**Route POST `/api/network-scan/scan`**

- 🔧 Scan démarré en arrière-plan avec `Promise.then()` au lieu d'attendre la completion
- 🔧 Gestion des erreurs asynchrones avec logs détaillés
- 🔧 Retour immédiat avec statut "started" pour éviter les timeouts HTTP

**Route GET `/api/network-scan/progress`**

- 🔧 Retourne le progrès si scan en cours (`status: 'in_progress'`)
- 🔧 Retourne les résultats finaux si scan terminé (`status: 'completed'`)
- 🔧 Format unifié pour le progrès et les résultats finaux

**Frontend - NetworkScanPage.tsx**

- 🔧 `handleScan()` adapté pour gérer la réponse "scan démarré"
- 🔧 Polling mis à jour pour détecter automatiquement la completion et récupérer les résultats
- 🔧 Gestion du format legacy (sans champ `status`) pour compatibilité

---

## [0.2.8] - 2025-12-29

### Fixed

**Scan Réseau - Gestion des IPs Offline**

- ✅ Correction : Les nouvelles IPs offline jamais découvertes ne sont plus créées dans la base de données
- ✅ Seules les IPs qui répondent au ping sont ajoutées (comportement original restauré)
- ✅ Les IPs existantes qui passent de "online" à "offline" sont correctement mises à jour
- ✅ Évite la pollution de la base de données et les scans trop lents

**Scan Réseau - Erreur Compilation**

- ✅ Correction de l'erreur : `The symbol "isFirstAttempt" has already been declared`
- ✅ Suppression de la déclaration dupliquée dans la détection MAC

**Freebox Revolution - Timeouts API**

- ✅ Correction spécifique pour Freebox Revolution uniquement (ne touche pas Pop, Ultra, Delta)
- ✅ Timeout API augmenté à 20 secondes pour Revolution (au lieu de 10s)
- ✅ Les autres modèles Freebox conservent le timeout de 10 secondes (inchangé)
- ✅ Méthode `isRevolutionModel()` pour détecter uniquement la Revolution
- ✅ Résout les erreurs `AbortError` sur les endpoints `/dhcp/dynamic_lease/`, `/fw/redir/`, `/lan/browser/pub/`

**Timeout HTTP Serveur**

- ✅ Timeout HTTP augmenté de 2 minutes à 5 minutes pour les scans réseau longs
- ✅ Évite les erreurs 504 Gateway Timeout sur les scans de grandes plages

**Docker Local - Configuration**

- ✅ Correction du port par défaut affiché pour docker-compose.local.yml (3000 au lieu de 3666)
- ✅ Ajout du support de la variable d'environnement `HOST_IP` pour spécifier l'IP de l'hôte
- ✅ Documentation ajoutée dans docker-compose.local.yml pour configurer HOST_IP

### Added

**Interface Utilisateur - Affichage Durée Scan**

- ✅ Affichage de la durée du scan dans les résultats avec format lisible
- ✅ Format adaptatif : "1m 23s" si >= 60s, "45.2s" sinon
- ✅ Durée affichée pour les scans "full" et "refresh"
- ✅ Fonction utilitaire `formatDuration()` pour formatage cohérent

**Documentation - Troubleshooting Réseau Lent**

- ✅ Guide complet dans `Doc_Dev/TROUBLESHOOTING_MAC_DETECTION.md`
- ✅ Documentation des 7 problèmes potentiels sur réseau lent
- ✅ Valeurs actuelles documentées pour tous les timeouts et paramètres
- ✅ Recommandations de configuration pour réseau très lent (>50ms latence)
- ✅ Commandes de diagnostic pour identifier les problèmes

### Changed

**Service Scan Réseau - Traitement des Résultats**

- 🔧 Amélioration du traitement des promesses rejetées avec gestion explicite
- 🔧 Séparation claire entre IPs online, offline et erreurs
- 🔧 Logs améliorés pour le débogage des problèmes de scan
- 🔧 Meilleure robustesse face aux erreurs réseau

**Service Freebox API - Timeout Adaptatif**

- 🔧 Détection automatique du modèle Freebox (Revolution vs autres)
- 🔧 Timeout adaptatif selon le modèle détecté
- 🔧 Logs de débogage pour identifier le modèle et le timeout utilisé

**Configuration Docker Local**

- 🔧 Support de la variable d'environnement `HOST_IP` pour spécifier l'IP de l'hôte
- 🔧 Port par défaut corrigé pour correspondre au mapping docker-compose.local.yml

---

## [0.2.7] - 2025-12-29

### Fixed

**Scan Réseau - IPs Manquantes (Bug Critique)**

- ✅ Correction du problème "une IP sur deux" manquante lors des scans
- ✅ Gestion correcte des promesses rejetées (timeouts/erreurs de ping)
- ✅ Les promesses rejetées sont maintenant capturées et traitées comme IPs offline
- ✅ Sauvegarde des nouvelles IPs offline dans la base de données
- ✅ Toutes les IPs scannées apparaissent maintenant dans les résultats, même si elles sont offline
- ✅ Logs de débogage ajoutés pour identifier les problèmes de ping

**Scan Réseau - Déclaration Variable Dupliquée**

- ✅ Correction de l'erreur de compilation : `The symbol "isFirstAttempt" has already been declared`
- ✅ Suppression de la déclaration dupliquée dans la détection MAC

### Added

**Interface Utilisateur - Affichage Durée Scan**

- ✅ Affichage de la durée du scan dans les résultats (format lisible : "1m 23s" ou "45.2s")
- ✅ Fonction utilitaire `formatDuration()` pour formater la durée de manière cohérente
- ✅ Durée affichée pour les scans "full" et "refresh"
- ✅ Format adaptatif : minutes + secondes si >= 60s, secondes avec décimales sinon

**Documentation - Troubleshooting Réseau Lent**

- ✅ Guide complet dans `Doc_Dev/TROUBLESHOOTING_MAC_DETECTION.md`
- ✅ Documentation des 7 problèmes potentiels sur réseau lent
- ✅ Valeurs actuelles documentées pour tous les timeouts et paramètres de concurrence
- ✅ Recommandations de configuration pour réseau très lent (>50ms latence)
- ✅ Commandes de diagnostic pour identifier les problèmes de latence
- ✅ Instructions pour ajuster les timeouts et la concurrence selon le réseau

### Changed

**Service Scan Réseau - Traitement des Résultats**

- 🔧 Amélioration du traitement des résultats de ping avec gestion explicite des promesses rejetées
- 🔧 Séparation claire entre IPs online, offline et erreurs
- 🔧 Meilleure gestion des nouvelles IPs offline pour qu'elles apparaissent dans les résultats
- 🔧 Logs améliorés pour le débogage des problèmes de scan

---

## [0.2.6] - 2025-12-28

### Fixed

**Exports Prometheus - Parsing des Valeurs**

- ✅ Correction de l'erreur `strconv.ParseFloat: parsing "[object": invalid syntax`
- ✅ Fonction utilitaire `toPrometheusNumber()` pour convertir toutes les valeurs en nombres valides
- ✅ Gestion correcte des objets (extraction automatique de `usage`, `percentage`, `value`)
- ✅ Protection contre les valeurs null, undefined, NaN et Infinity
- ✅ Conversion des booléens en 0 ou 1 pour Prometheus

**Exports Prometheus - Métriques Système**

- ✅ Correction de `mynetwork_cpu_usage` : utilisation de `sys.cpu.usage` au lieu de l'objet `sys.cpu`
- ✅ Correction de `mynetwork_memory_*` : utilisation de `toPrometheusNumber()` pour toutes les valeurs
- ✅ Gestion du `percentage` depuis l'API ou calcul automatique si manquant
- ✅ Correction de `mynetwork_disk_*` : gestion correcte de `mountpoint` vs `mount` et conversion des valeurs

**Exports Prometheus - Métriques Réseau et Plugins**

- ✅ Correction de toutes les métriques réseau (download/upload) avec conversion sécurisée
- ✅ Correction des métriques plugins (uptime, temperature, memory, cpu) avec extraction correcte des valeurs
- ✅ Correction des métriques scan réseau (totalIps, onlineIps, offlineIps, unknownIps)
- ✅ Protection de toutes les valeurs contre les objets non convertis

**Exports InfluxDB - Cohérence**

- ✅ Application des mêmes corrections pour les exports InfluxDB
- ✅ Conversion sécurisée de toutes les valeurs avec `toPrometheusNumber()`
- ✅ Arrondi correct des valeurs entières pour InfluxDB (`i` suffix)

### Changed

**Service Métriques - Architecture**

- 🔧 Ajout de la fonction utilitaire `toPrometheusNumber()` pour centraliser la conversion des valeurs
- 🔧 Extraction automatique des propriétés numériques depuis les objets (`usage`, `percentage`, `value`)
- 🔧 Gestion unifiée des cas limites (null, undefined, objets, NaN, Infinity)
- 🔧 Application cohérente de la conversion sur toutes les métriques (Prometheus et InfluxDB)

---

## [0.2.5] - 2025-12-28

### Added

**Monitoring de Latence - Système Complet**

- 📊 Nouveau système de monitoring de latence inspiré de Lagident
- 🎯 Activation/désactivation du monitoring par IP depuis la page scanner
- 📈 Graphique scatter chart avec affichage des mesures de latence sur plusieurs jours
- 🎨 Graphique identique à Lagident : axes Latency/Loss/Temps, couleurs vert/orange/rouge selon la latence
- 📊 Statistiques affichées : Avg1h, Min, Max, Packet Loss %
- 🔄 Mesures automatiques toutes les 15 secondes pour les IPs surveillées
- 💾 Stockage des mesures avec valeurs décimales précises (REAL au lieu de INTEGER)

**Page Scanner - Colonnes Statistiques Latence**

- 📊 Nouvelles colonnes "Avg1h" et "Max" dans le tableau scanner
- 🎨 Couleurs dynamiques selon la valeur de latence (vert < 50ms, jaune/orange 50-150ms, rouge > 150ms)
- 📈 Affichage des valeurs avec 3 décimales pour précision maximale
- 🔘 Colonne "Monitoring" avec toggle pour activer/désactiver le suivi
- 📊 Icône graphique cliquable pour voir le graphique de latence si monitoring activé

**Graphique de Latence - Modal Complet**

- 🖼️ Modal plein écran avec graphique scatter chart haute qualité
- 📅 Affichage des données sur 90 jours avec tous les points
- 🎯 Format adaptatif de l'axe X selon la période (DD/MM HH:MM pour < 2 jours, DD/MM HHh pour 2-7 jours, DD/MM pour > 7 jours)
- 📊 Échelle Y dynamique avec algorithme identique à Lagident (démarre à 0, padding adaptatif)
- 🎨 Couleurs des points selon la latence (vert < 50ms, jaune 50-100ms, orange 100-150ms, rouge > 150ms)
- 📉 Représentation des pertes de paquets par lignes rouges sur l'axe Loss
- 🎨 Barre de légende avec dégradé de couleurs (LOW/HIGH)
- 📊 Fond sombre pour meilleur contraste visuel

**Page Scanner - Améliorations Visuelles**

- 🎨 Couleurs alternées pour les lignes paires/impaires du tableau (meilleure différenciation)
- 🖱️ Effet hover amélioré sur les lignes avec ombre et transition fluide
- 🎨 Colonne IP avec couleur personnalisée rgb(152, 181, 238) pour meilleure visibilité
- 📊 Barres statistiques agrandies (64px au lieu de 48px) pour remplir le cadre
- 🎨 Dégradé de transparence depuis le bas pour adoucir la couleur claire du haut (Total IPs)
- 📅 Période des barres augmentée à 48 heures (au lieu de 24h)
- 🔧 Tooltips repositionnés pour éviter qu'ils soient coupés sur les bords

**Dashboard - Widget Scanner**

- 🎨 Style unifié avec la page scanner pour "Dernier Scan" et "Prochains scans"
- 🏷️ Badges "Complet" et "Rapide" alignés avec la page principale
- 📐 Format identique pour cohérence visuelle

### Changed

**Scanner Réseau - Parsing de Latence**

- 🔧 Parsing amélioré pour conserver les valeurs décimales (parseFloat au lieu de parseInt)
- 📊 Support des valeurs décimales pour Windows et Linux
- 💾 Stockage des latences avec décimales dans la base de données (REAL au lieu de INTEGER)
- ✅ Correction pour inclure les latences de 0ms comme valeurs valides

**Base de Données - Schéma Latence**

- 💾 Table `latency_measurements` avec colonne `latency` en REAL pour valeurs décimales
- 💾 Table `network_scans` avec colonne `ping_latency` en REAL pour valeurs décimales
- 🔄 Migration automatique lors de la création des tables

**Graphique de Latence - Affichage**

- 📊 Affichage des valeurs avec 3 décimales dans le tooltip et les statistiques
- 🎯 Échelle Y adaptative avec algorithme identique à Lagident
- 📅 Format de l'axe X adaptatif selon la période affichée
- 🎨 Fond du graphique plus sombre (#0f0f0f) pour meilleur contraste

**Page Scanner - Tableau**

- 🎨 Couleurs de fond alternées : lignes paires (#111111), lignes impaires (#0e1013a3)
- 🖱️ Hover uniforme sur toutes les lignes avec fond #1d1d1d
- 📐 Transitions fluides pour tous les effets visuels

### Fixed

**Monitoring de Latence - Valeurs à 0ms**

- ✅ Correction du problème où toutes les valeurs affichaient 0ms dans le graphique
- ✅ Conversion explicite des valeurs avec Number() lors de l'enregistrement
- ✅ Affichage correct des valeurs décimales dans le graphique

**Scanner Réseau - Latence Manquante**

- ✅ Correction pour que les équipements Linux et Windows aient leur latence enregistrée
- ✅ Condition modifiée pour inclure les latences de 0ms (latency >= 0 au lieu de latency > 0)
- ✅ Parsing amélioré pour détecter les latences très faibles (< 1ms)

**TypeScript - Typage**

- ✅ Ajout de l'interface NetworkScanResponse pour typer la réponse API
- ✅ Correction de l'erreur "Property 'hostname' does not exist on type 'unknown'"

**Tooltips - Positionnement**

- ✅ Repositionnement des tooltips pour éviter qu'ils soient coupés sur les bords
- ✅ Alignement à gauche pour la première barre, à droite pour la dernière

---

## [0.2.4] - 2025-12-27

### Added

**Page Scan Réseau - Protection contre les Scans Multiples**

- 🛡️ Protection côté serveur pour empêcher les scans simultanés
- 🔒 Vérification de l'état du scan avant d'en démarrer un nouveau
- ⚠️ Messages d'erreur clairs si un scan est déjà en cours
- 🚫 Protection côté client contre les clics multiples rapides

**Page Scan Réseau - Ajout Manuel d'IP**

- ➕ Nouvelle fonctionnalité pour ajouter manuellement une IP à scanner
- 📝 Modal avec champs IP, MAC (optionnel) et hostname (optionnel)
- 🔍 Scan immédiat de l'IP ajoutée avec détection MAC et hostname
- 💾 Enregistrement dans la base de données avec source "manual"

### Changed

**Page Scan Réseau - Affichage des Prochains Scans**

- 🎨 Amélioration du visuel des prochains scans (Full Scan et Refresh)
- 🏷️ Badges colorés pour distinguer "Complet" (violet) et "Rapide" (bleu)
- 📐 Alignement parfait des badges entre les différents types de scans
- 📝 Texte simplifié : "Full Scan" et "Refresh" au lieu de "Prochain Full Scan"
- 🎯 Badges positionnés avant le temps pour meilleure lisibilité

**Scanner Réseau - Détection Windows Améliorée**

- 🪟 Amélioration de la détection des machines Windows
- ⚡ Correction du parsing des latences très faibles (< 1ms)
- 🔍 Détection améliorée des pings Linux vers Windows avec indicateurs `icmp_seq=`
- ✅ Acceptation des latences de 0ms comme pings réussis

### Fixed

**Scanner Réseau - Détection Windows**

- ✅ Correction du problème où les PC Windows n'étaient pas détectés lors des scans
- ✅ Correction du parsing de latence qui excluait les valeurs de 0ms
- ✅ Amélioration de la détection des pings réussis même avec latence très faible

**Scanner Réseau - Scans Multiples**

- ✅ Correction du problème où plusieurs scans pouvaient se lancer simultanément
- ✅ Protection contre les scans multiples côté serveur et client
- ✅ Prévention des conflits entre scans manuels et automatiques

---

## [0.2.3] - 2025-12-24

### Added

**Recherche Exacte IP - Agrégation Complète des Données**

- 🔍 Nouvelle route API `/api/search/ip-details/:ip` pour agrégation des données depuis Scanner, Freebox et UniFi
- 📊 Affichage unifié de toutes les informations IP dans une seule carte sans redondance
- 🎯 Détection automatique des recherches IP exactes avec affichage dédié

**Recherche Exacte IP - Informations UniFi Détaillées**

- 📡 Affichage complet des informations client UniFi (AP connecté, switch, ports)
- 📶 Affichage de la bande passante (upload/download) avec formatage automatique
- 📻 Affichage du SSID avec badge stylé et icône WiFi
- 📊 Affichage de la force du signal (RSSI) avec barre de progression colorée et qualité
- 🔌 Affichage des ports switch pour les clients filaires avec badge stylé
- 🎯 Détection automatique WiFi/Filaire basée sur les champs disponibles (SSID, AP, ports)

**Recherche Exacte IP - Informations Freebox**

- 🏠 Affichage des informations DHCP (réservation statique ou DHCP dynamique)
- 🔀 Affichage des règles de port forwarding avec détails complets (ports WAN/LAN, protocole, statut)
- 📋 Badge visuel pour distinguer les réservations DHCP des allocations dynamiques

**Recherche Exacte IP - Design Moderne**

- 🎨 Affichage en grille responsive multi-colonnes (1/2/3 colonnes selon la taille d'écran)
- 💳 Cartes individuelles pour chaque type d'information avec effets hover
- 🎯 Design moderne avec bordures, ombres et transitions fluides
- 📱 Responsive design optimisé pour mobile, tablette et desktop

**Affichage de la Latence - Couleurs Dynamiques**

- 🎨 Couleurs automatiques selon la valeur de latence (< 10ms: vert, 10-30ms: vert clair, 30-50ms: jaune, 50-100ms: orange, > 100ms: rouge)
- 📊 Application des couleurs dans tous les affichages de latence (ping, scanner, résultats)
- 🎯 Fonctions utilitaires `getLatencyColor()` et `getLatencyBgColor()` pour cohérence visuelle

### Changed

**Recherche Exacte IP - Normalisation des Données**

- 🔄 Normalisation du SSID depuis plusieurs champs possibles (`ssid`, `essid`, `wifi_ssid`, `wlan_ssid`)
- 📶 Normalisation du signal RSSI depuis plusieurs sources (`rssi`, `signal`, `signal_strength`, `noise`)
- 🔀 Conversion automatique des valeurs positives en RSSI négatif si nécessaire
- 🎯 Détection améliorée du type de connexion (WiFi/Filaire) avec priorité au SSID

**API Recherche - Récupération UniFi**

- 🔍 Recherche améliorée des clients UniFi dans plusieurs sources (devices array, clients array, plugin stats)
- 📊 Logs de debug détaillés pour diagnostiquer les problèmes de récupération
- 🔄 Tentative de récupération depuis le plugin directement si non trouvé dans les stats

**Interface Utilisateur - Badges**

- 🏷️ Badge SSID avec icône WiFi et largeur adaptée au texte (`w-fit`)
- 🔌 Badge Port avec icône Cable pour les connexions filaires
- 🎨 Badges colorés pour le type de connexion (WiFi orange, Filaire gris)

### Fixed

**Recherche Exacte IP - Données UniFi**

- ✅ Correction de la récupération des clients UniFi depuis `devices` array avec `type === 'client'`
- ✅ Correction de l'affichage du SSID et du signal qui étaient vides
- ✅ Amélioration de la détection automatique WiFi/Filaire pour les clients avec `is_wired: false` et `is_wireless: false`
- ✅ Normalisation correcte du RSSI depuis le champ `signal` si `rssi` est positif ou manquant

**Syntaxe & Erreurs**

- ✅ Correction de l'erreur de syntaxe dans `server/routes/search.ts` (accolades manquantes)
- ✅ Correction de l'erreur JSX dans `SearchPage.tsx` (balises div non fermées)
- ✅ Correction de l'erreur `getLatencyColor is not defined` (fonctions utilitaires correctement définies)

**API Routes - Structure**

- ✅ Correction de la structure des blocs try-catch dans la route `/api/search/ip-details/:ip`
- ✅ Fermeture correcte de tous les blocs conditionnels et boucles

---

## [0.2.2] - 2025-12-23

### Added

**Plugin Scan Réseau - Sauvegarde des Préférences Utilisateur**

- 💾 Sauvegarde automatique des préférences dans localStorage (filtre de statut, tri, nombre de résultats)
- 🔄 Restauration automatique des préférences au chargement de la page
- 📊 Persistance des choix utilisateur entre les sessions (filtre, colonne de tri, ordre de tri, pagination)

**Gestion du Thème - Chargement Automatique**

- 🎨 Chargement automatique des couleurs personnalisées après authentification utilisateur
- ⚡ Application immédiate des couleurs sans rechargement de page
- 🔄 Import dynamique de `initTheme()` pour éviter les dépendances circulaires

**Plugin Scan Réseau - Améliorations UI**

- 🔍 Barre de recherche agrandie et stylée avec placeholder descriptif
- 📏 Largeur minimale de 300px et maximale de 500px pour la barre de recherche
- 🎨 Effets visuels améliorés : bordure épaisse, coins arrondis, icône agrandie, bouton d'effacement
- 📍 Barre de recherche déplacée sur la même ligne que les menus de filtre
- 🎯 Alignement parfait de la hauteur avec les autres éléments de contrôle

### Changed

**Plugin Scan Réseau - Comportement par Défaut**

- 🔄 Filtre de statut par défaut changé de `'all'` à `'online'` pour afficher uniquement les IPs en ligne
- 📊 Tableau affiche par défaut uniquement les équipements actifs au démarrage

**Plugin Scan Réseau - Gestion de la Progression**

- 🔄 Réinitialisation correcte de `currentScanProgress` à `null` après la fin des scans
- 📊 Progression également initialisée et mise à jour pour les opérations de refresh
- ✅ Correction du problème de progression bloquée à 100% après la fin d'un scan

**Plugin Scan Réseau - Scheduler**

- 🚫 Désactivation du scan automatique au démarrage Docker pour éviter les scans inattendus
- 🔄 Statut `running` du scheduler basé sur la progression réelle du scan (via `getScanProgress()`)
- ✅ Correction de l'affichage de l'icône "Auto Full Scan" qui restait affichée après redémarrage

**Gestion du Thème - Optimisation**

- 🔒 Vérification de l'authentification avant l'appel API pour éviter les erreurs 401 au démarrage
- 🎨 Chargement conditionnel des couleurs personnalisées uniquement si un token JWT est présent
- ⚡ Amélioration des performances en évitant les appels API inutiles

**API Routes - Typage TypeScript**

- 📝 Ajout d'interfaces TypeScript pour les réponses API de purge (`PurgeResponse`, `PurgeAllResponse`, `ClearAllResponse`)
- 📝 Ajout d'interface `DatabaseStatsResponse` pour les statistiques de base de données
- ✅ Correction des erreurs TypeScript `Property 'deleted' does not exist on type 'unknown'`

**Interface Utilisateur - Tableau**

- 🔧 Correction de l'erreur d'hydratation React pour `<colgroup>` (suppression des espaces blancs)
- 📐 Toutes les balises `<col>` sur une seule ligne pour respecter les règles HTML strictes de React

### Fixed

**Authentification & Thème**

- ✅ Correction de l'erreur 401 au démarrage lors du chargement du thème (`/api/settings/theme`)
- ✅ Vérification du token JWT avant l'appel API pour éviter les erreurs non authentifiées
- ✅ Chargement automatique des couleurs personnalisées après connexion utilisateur

**Plugin Scan Réseau - Progression**

- ✅ Correction de la progression bloquée à 100% après la fin d'un scan
- ✅ Réinitialisation correcte de `currentScanProgress` après `scanNetwork()` et `refreshExistingIps()`
- ✅ Mise à jour de la progression pendant les opérations de refresh

**Plugin Scan Réseau - Scheduler**

- ✅ Correction du statut `running` qui restait `true` même après la fin d'un scan
- ✅ Vérification de la progression réelle via `networkScanService.getScanProgress()` au lieu du statut du cron
- ✅ Correction de l'icône "Auto Full Scan" qui s'affichait incorrectement après redémarrage Docker

**TypeScript - Typage API**

- ✅ Correction de `Property 'deleted' does not exist on type 'unknown'` dans `SettingsPage.tsx`
- ✅ Ajout de typage explicite pour toutes les réponses API de purge et statistiques
- ✅ Interfaces TypeScript complètes pour `PurgeResponse`, `PurgeAllResponse`, `DatabaseStatsResponse`, `ClearAllResponse`

**React - Hydratation**

- ✅ Correction de l'erreur d'hydratation pour `<colgroup>` : suppression des nœuds texte (espaces)
- ✅ Toutes les balises `<col>` sur une seule ligne sans espaces pour respecter les règles HTML strictes

---

## [0.2.1] - 2025-12-23

### Added

- Full migration to the official IEEE OUI database (standards-oui.ieee.org)
- Dedicated parser for the multi-line IEEE OUI format
- Improved MAC/Vendor detection from Freebox (`getMacFromFreebox`, `getVendorFromFreeboxByIp`)
- "Delete all network scans" button in the configuration modal
- 3D effect on mini bar charts with accented black base
- "Scan Info" refactor: buttons side-by-side, stats below
- Row pulse animation during automatic scans

### Changed

- Vendor database: migrated from Wireshark GitHub/GitLab to the official IEEE OUI source
- Results table: adaptive columns (`table-auto`), 2-line text, MAC/Hostname order swapped
- Mini bar charts: softer colors, black base >50% height, full 3D effect

---