Skip to content

v2.0.3 Release Changes (TBD)#988

Open
tvancott42 wants to merge 4 commits into
mainfrom
dev
Open

v2.0.3 Release Changes (TBD)#988
tvancott42 wants to merge 4 commits into
mainfrom
dev

Conversation

@tvancott42

@tvancott42 tvancott42 commented Jul 15, 2026

Copy link
Copy Markdown
Collaborator

Rolling collection of dev changes for the v2.0.3 release. This description tracks what's on dev as PRs land; release notes are drafted from it at release time.

So far: #987 (fixes #986, #982) and #990 (fixes #989), plus 502/503/504 handling and default-site reconnect follow-ups on dev

Settings - Connection

  • API key connections self-heal after a UniFi Network application restart or upgrade - While the Network application is restarting, upgrading, or wedged, its proxy answers with 401/403 even though the key is still valid. The client treated any auth failure in API key mode as a revoked key and permanently stopped calling the console - everything that reads the API (Wi-Fi Optimizer, Config Optimizer, Security Audit, Threat Intelligence, SNMP detection) went dark until the container was restarted or the connection re-saved. The client now re-validates the key, throttled to one probe per minute, and recovers on its own within a minute of the console coming back. Username/password (cookie) authentication is unaffected.
  • Reverse-proxied consoles recover from 502/503/504 too - A self-hosted UniFi OS Server or Network application behind a reverse proxy answers with 502/503/504 (Bad Gateway / Service Unavailable / Gateway Timeout), not 401/403, while its backend restarts, upgrades, or reprovisions. Those gateway errors used to just fail each console read and leave the connection stuck showing "connected" - no re-authentication, no alert, no recovery. They now self-heal on the same path as an expired session: the client re-authenticates, raises the UniFi Console connection alerts, and recovers when the backend returns. The connection screen shows "temporarily unavailable, retrying" instead of a raw "failed with status 502". Applies to both direct and agent-tunneled connections, API key and cookie auth.
  • The main site reconnects on its own after a transient console outage - The periodic reconnect only covered agent-tunneled secondary sites, so a directly-connected console (the default site, or any non-agent secondary site) that was down when the optimizer started - or that dropped and disposed the connection - stayed dark until a schedule ran or the app restarted. Any disconnected site that isn't waiting on an agent tunnel now retries its console every 30s, so it comes back within a minute of the console returning. Verified end to end: console down at cold start recovers automatically ~50s after it comes back, no restart needed.

Multi-Site

  • Disable / Remove Site - New third section in a site's Configuration panel (Settings - Multi-Site), alongside Agent and Settings. Disable stops the site's monitoring and console connection immediately and hides it from the site switcher and Sites views; all data is kept and the site can be re-enabled in place without confirmation. Remove is permanent, behind a double confirmation: it deletes the site, its database, and its agent enrollments, and frees the license seat. InfluxDB buckets are left in place (deleting them needs the admin token, which is never stored) and the confirmation says so. Both actions are blocked for the default site and for the site currently being viewed.
  • Create Site responsiveness - Two fixes for the accidental duplicate site from Delete a secondary site completely #982: the spinner/disabled state now actually renders during the multi-second provisioning (SQLite completes EF's async calls synchronously, so the render never flushed), and a re-entrancy guard swallows the queued double-click that created a "-2" copy.
  • Add Site jumps to the form - The + card on the Sites page lands on the add form scrolled into view with the name field focused.
  • Site switcher stays current - Rebuilds in every open tab when sites are created, renamed, enabled/disabled, or removed, instead of waiting for a page reload.
  • Site switcher keyboard support - Escape closes the dropdown, Up/Down arrows move through the sites, Enter or Space selects. Tab and Shift+Tab keep their native behavior.

Client Performance

  • Devices resolve on dual-stack hosts (Docker/Proxmox) - When the app accepts an IPv4 client on a dual-stack socket, the browser's address arrives as an IPv4-mapped IPv6 (::ffff:a.b.c.d), which never matched the UniFi Console's plain-IPv4 client list - so the page showed "Device Not Found" with a ::ffff:-prefixed address even though the device was right there. The address is normalized to plain IPv4 before lookup, on the default site and via the on-site agent whoami probe on managed sites (fixes Client Performance: device not found / Client Speed Test: showing LAN device as WAN (::ffff: IP prefix) #989). Genuine IPv6 clients are left untouched.

Client Speed Test

  • Real client IPs and correct LAN/WAN labels - Results from a dual-stack client stored the mapped ::ffff: address, so the device column showed the prefix and a local device was mislabeled WAN. New results store and display plain IPv4 with the right label; Client WAN Test and LAN Speed Test share the results table and benefit too. A one-time migration collapses existing ::ffff: addresses so past results also show real IPs, and normalizing before enrichment lets a result resolve its device MAC and correlate to that client's Client Performance history.

Alerts & Schedule - Rules

  • New UniFi Console event types - console.connection_failed (Warning) after two consecutive failed authentication attempts spanning at least the revalidation interval; console.connection_restored (Info) on recovery. Per-site, armed only after a successful connection, suppressed while a site's agent tunnel is coming up, 30-minute failure cooldown so a flapping console upgrade produces one alert.
  • New On-Site Agent event types - agent.offline (Warning) when an enrolled agent is continuously offline for 3 minutes; agent.reconnected (Info) when it returns. Judged by the same live definition the UI uses (open tunnel or fresh heartbeat) via a periodic sweep, so agent redeploys and brief tunnel bounces stay silent. Agents never connected, disabled, or on a disabled site don't alert.
  • Seeding - Default rules seed on startup and at site creation. On-Site Agent rules skip the main site unless the default site has its own agent (enrolling one seeds them then), so single-site installs don't see agent rules they can't use. All four types are in the event type patterns catalog and deliver through all existing Notification Channels.

Fixes

  • Form help text legibility - Bumped from 0.75rem to 0.8rem across settings forms.

tvancott42 and others added 4 commits July 14, 2026 19:15
…te Agent connection alerts; Multi-Site Disable / Remove Site (#982) (#987)

* Settings - Connection: API key auth recovers after the UniFi Network application restarts (#986)

A transient 401/403 from /proxy/network while the Network application is
upgrading, reprovisioning, or wedged permanently killed an API-key client:
EnsureAuthenticatedAsync treated any auth failure as a revoked key and
short-circuited every subsequent call without touching the network, until
the container was restarted or the connection re-saved. Re-validate the
key instead, throttled to one probe per minute so a genuinely revoked key
stays cheap.

* Alerts & Schedule - Rules: new UniFi Console event types alert when the console connection fails and recovers

Two consecutive failed auth probes spanning at least 50s publish
console.connection_failed (Warning); the next successful auth publishes
console.connection_restored (Info). Armed only after a successful
connection, suppressed while an agent tunnel is coming up, counters
reset on intentional teardown, and default rules seed with a 30-minute
failure cooldown so a flapping console upgrade produces one alert, not
a stream. Per-site via the connection service's site slug.

* Alerts & Schedule - Rules: On-Site Agent event types alert when a site agent goes offline and reconnects

A periodic sweep checks IsAgentLive (open tunnel or fresh heartbeat) for
every enrolled agent; 3 continuous minutes offline publishes agent.offline
(Warning) once per outage, and the next live sweep pairs it with
agent.reconnected (Info). Sweeping instead of hooking tunnel teardown
keeps agent redeploys and tunnel bounces silent. Never-seen and disabled
agents are excluded so in-progress setups don't alert.

* Alerts & Schedule - Rules: On-Site Agent rules only seed on the main site when the default site has an agent

Secondary sites keep them (agents are their normal connection mode, and
site provisioning seeds them at creation). Single-site installs no longer
get agent rules they can't use; enrolling a default-site agent seeds them
at that moment. Skipped rules also mean a user deletion sticks instead of
re-seeding every startup.

* Multi-Site: site switcher keyboard support - Esc closes, arrows navigate, Enter/Space select

Arrow keys move focus through the menu (Down from the trigger enters the
list), Enter activates natively, Space activates per the ARIA menu
pattern, and Escape closes the dropdown. Tab/Shift+Tab keep their native
behavior - only fully-handled keys are intercepted.

* Settings - Multi-Site: Disable / Remove Site (#982)

New third section in a site's Configuration panel alongside Agent and
Settings. Disable stops the site's monitoring collection and console
connection immediately and hides it from the site switcher, Sites page,
and overview card - data is kept and it can be re-enabled in place.
Remove is permanent behind a double confirmation: it deletes the site's
agents, registry row, and database directory (SQLite pools cleared
first so the files aren't locked), then frees the license seat. Both
are blocked for the default site and for the site currently being
viewed. Disabled sites' agents are excluded from the offline alert
sweep - taking a paused site's agent down is intentional.

* Multi-Site: double-click guard on Create Site, no confirm on re-enable, form-help legibility

Create Site already disabled while busy, but a fast double-click queues
both click events before the disabled state renders and the circuit
runs them back to back - the second created the duplicate name-2 site
from #982. A re-entrancy guard on the created site swallows it.
Re-enabling a disabled site no longer asks for confirmation (only
disable does). Form help text bumped 0.75rem -> 0.8rem.

* Multi-Site: site switcher rebuilds live when the site list changes

New SiteRegistryChangeNotifier singleton broadcasts create, rename,
enable/disable, remove, and the multi-site toggle from
SiteManagementService; every open circuit's switcher subscribes,
reloads its list on its own dispatcher, and closes the dropdown if it
was open. Previously the switcher only loaded at circuit start, so
registry changes needed a page reload to show up.

* Multi-Site: Create Site spinner actually renders; Remove Site confirm notes InfluxDB buckets stay

SQLite completes EF's async calls synchronously, so site provisioning
never yielded and the queued disabled+spinner render only flushed after
it finished - the button looked inert for the full few seconds. A
Task.Yield after flagging busy pushes the spinner to the browser first.
Bucket deletion needs the InfluxDB admin token, which is deliberately
never persisted, so Remove Site leaves the site's buckets; the
confirmation now says so (retention ages the data out regardless).

* Sites: Add Site card scrolls to the add form and focuses the name field

Rides the existing Settings anchor scroll-and-highlight: the card now
navigates to the multisite tab with the #site-agent-setup anchor, which
scrolls to the wizard and focuses its site name input.
…504, matching the 401/403 recovery

A self-hosted UniFi OS Server / Network application behind a reverse proxy
returns 502/503/504 while the backend restarts, upgrades, or reprovisions.
The client only reset auth state (and thus re-authenticated, fed the
connection alerting, and self-healed) on 401/403 - a gateway 5xx just failed
the call and left the client wedged "connected" while every console read
failed. Treat the gateway-unavailable family the same as a stale session via
a shared IsRecoverableAuthFailure check across every re-auth path, and surface
a "temporarily unavailable, retrying" message on connect instead of a raw
"failed with status 502" or a JSON parse error.
…fter a transient outage

The periodic device-fetch reconnect only covered agent-tunneled secondary
sites (!_isDefault && console-via-agent). The default, directly-connected
site relied solely on the one-shot startup connect, so a self-hosted console
returning 502/503 during a restart or upgrade - especially right when the
optimizer starts - failed that connect, disposed the client, and left the
main site dark until a schedule ran or the process restarted. Reconnect any
disconnected site that isn't waiting on an agent tunnel, still throttled to
one attempt per 30s, so the default site recovers on its own once the
console's backend is back.
…ped IPv6 (::ffff:) (#990)

* Client Performance / speed tests: normalize IPv4-mapped client IPs (::ffff:)

An IPv4 client accepted on Kestrel's dual-stack socket (the Docker/Proxmox
default) arrives as the IPv4-mapped IPv6 form ::ffff:a.b.c.d. That prefix was
carried through unmodified, so:

- Client Performance showed "Device Not Found" (the detected ::ffff: address
  never equals the console's plain-IPv4 BestIp), and
- Client / LAN / WAN Client Speed Test stored and displayed the mapped address
  and mislabeled LAN devices as WAN (the private-IP prefix check missed it),
  and the result never enriched a ClientMac, so it couldn't correlate to a
  client's Performance dashboard.

Normalize IPv4-mapped addresses to plain IPv4 at every point a client address
is captured - the client-dashboard HTTP capture and agent whoami probe, the
speed-test endpoints/recording, the iperf3 client relay and connection parse,
and the agent's result relay - via a shared NetworkUtilities.NormalizeToIPv4String
helper. Going forward DeviceHost stores plain IPv4, enrichment matches the
console list and sets ClientMac, and correlation works by MAC (the stable key).

Also harden the consumers as defense in depth (IdentifyClientAsync, the speed
test LAN/WAN badge, and IsPrivateIpAddress, which had a latent bug misreporting
a mapped RFC1918 address as public), and add a data migration that collapses
existing ::ffff: DeviceHost values so the three speed-test views show real IPs.

The normalization only unwraps ::ffff: mapped addresses (IsIPv4MappedToIPv6);
genuine IPv6 client identity - GUA, ULA, link-local - is never altered. The
migration deliberately does not re-derive ClientMac: correlating a historical
row by its point-in-time IP is unsound (DHCP/reservation churn), so old rows
are cleaned for display but stay uncorrelated rather than risk mis-attribution.

* Test: GetClientIp normalizes a mapped RemoteIpAddress and X-Forwarded-For

Exercises the actual client-IP capture entry point with a synthetic
DefaultHttpContext - the path an IPv4 client hits on Kestrel's dual-stack
socket - so the ::ffff: unwrap is verified without needing that environment.
Covers mapped connection address, mapped XFF (precedence), plain IPv4, a real
IPv6 client (left untouched), and the no-address "unknown" fallback.

* Test: use RFC 3849 documentation IPv6 range for the passthrough case
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant