The rack front is drawn from the rack units on cutsheet items. Switches
with a faceplate use it, with live port state; patch panels get a generic
panel with port labels; everything else is a labelled plate. Each
documented port shows its cutsheet state, and hovering a port lights up
every port on the same cable run (through patch panels) and names the
cables. Clicking opens the switch port or the cable.
Overlapping items and items past the top of the rack are listed instead
of drawn over another; where two claim the same top unit, the first one
recorded wins. rack.units sets the height (default 42).
/api/rack builds on the cutsheet view; cutsheet.rack_layout and
cutsheet.port_marks hold the placement and per-port logic.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Device pages get an Inventory button: it opens the device's HomeBox record
once an admin pastes its URL (stored in the new device_links table; only
URLs on inventory.base_url are accepted), and searches the inventory for
the model until then. Tickets link the record too.
Faceplate ports with a documented cable get a corner dot: green matches,
red mismatch, grey documented but unconfirmed. The port's tooltip names
the cable, and a legend toggle (remembered per browser) hides them.
Page titles no longer repeat GANDALF ("Alert History - GANDALF - GANDALF").
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
/events lists every alert from the last 24 hours to 90 days, open or
cleared, with how long each lasted, failed checks and its ticket. Filters
(device, type, severity, state, range) live in the URL, so device pages
and tickets link straight to a device's history. A "Recurring" table
lists alerts that fired more than once on the same target. On phones the
rows become cards.
/api/events/history backs it (100 per page). The existing /api/events is
unchanged.
/health and the dashboard's monitor indicator now call the monitor
stalled after five missed polls (at least 10 minutes) instead of a fixed
20, and /health reports the monitor's age.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When an alert that opened a ticket clears, the monitor now comments on the
ticket (when it cleared, how long it lasted, failed checks, a link back).
It never closes or changes a ticket's status. Failed notes retry each
cycle for 24 hours; a 401/403 from Tinker stops them until restart.
Tickets are now Markdown with a "What to check" list and, where known:
the switch port the device plugs into (kept from the last time it was
seen, so a dead host still shows it), cutsheet cables and their path,
how often the alert fired in the last 7/30 days, other open alerts on the
device, and links into Gandalf (gandalf.public_url). Titles are unchanged
so Tinker's duplicate detection still works.
network_events gains recovery_notified, added at monitor startup; rows
that cleared before the upgrade count as already notified.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Trace a cord: pick a panel port, press Start, and unplug the cord at the
panel. The page polls a new admin-only endpoint that reads every switch
port's state from UniFi (cached 2 s), shows the one port that dropped
with a button to record the cable, and confirms when it's plugged back
in. Several ports dropping at once, or none within 3 minutes, is
explained rather than guessed at.
Mismatch alerts: the monitor runs the cutsheet check every poll. Each
mismatched installed run (wrong neighbor, no link, missing port, port
conflict) keeps one cable_mismatch event on its switch, resolved when
fixed, and opens a P4 ticket after cutsheet.mismatch_ticket_hours
(default 24, 0 never). Suppressing the switch or port stops the ticket.
Also fixes buttons with the hidden attribute still showing, which left a
Delete button on the new-cable form.
Closes#12
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A new Cutsheet page records where every cable is meant to go and compares
it with what Gandalf discovers.
- Inventory for what discovery can't see: patch panels, wall jacks,
shelves and rack positions. Panels and jacks pass through front to rear.
- Cables with two endpoints, ID, type, length, color, status (planned,
installed, dark, retired) and notes; the far end may be unterminated.
- Each cable's full run is traced through panels and jacks, and runs
ending on a switch port are checked against its discovered neighbor
(LLDP, client table, or a DAC serial seen at both ends): matches,
wrong neighbor, no link, missing port, port conflict, unverified, idle.
- Undocumented links: live switch ports no cable mentions, one click to
add with what Gandalf already knows. Port panels show the planned cable.
- Labels to make: cable IDs and panel-port labels not yet applied, with a
print view, for a handheld label maker.
- CSV export, and all-or-nothing import that upserts by cable ID.
- Cables, panels and jacks appear in the dashboard search.
Editing needs the admin group and a JSON request body. Every change is
written to cutsheet_history in the same transaction. Tables are created
on first use.
Refs #12
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Ports were keyed by their UniFi name, so when two ports shared a name the
later one replaced the earlier. The 24-port switch lost port 13 this way
(ports 13 and 14 are both "Office TV"). A repeated name is now kept as
"<name> (Port N)".
Traffic rates are now matched to the previous poll by port number rather
than name. Names are re-read from UniFi every poll, so a port renamed
there keeps its rates instead of losing them for a cycle.
Collect what UniFi reports about each SFP cage: whether a module is
present, its type (DAC or optic), part, serial, fault flags, and DOM
readings when the module has them. The faceplate draws empty cages,
optics, DACs and unidentified modules differently and outlines a faulted
module in red. The port panel gets an SFP Module section. Both ends of a
DAC report the same cable serial, so each end links to the other.
Closes#7Closes#8
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Remote-User/Name/Email/Groups were read from every request, so a LAN host
connecting to the container directly could claim any user and group. Add
auth.trusted_proxies and ignore those headers unless the request came from
one of them: the X-Real-IP the local nginx sets, or the peer address when
gunicorn is reached directly. With nothing configured, nobody is trusted.
Rejected attempts are logged, and /api/avatar now uses the same check.
The container's nginx site blanks the same headers for non-NPM sources
(pve-infra 4e3ab7c).
Closes#3
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A host's node_exporter stops as soon as a shutdown begins and a normal
reboot takes about 3-5 minutes, so the 2-check failure_threshold cut a
ticket for planned reboots. Add monitor.host_failure_threshold, default 5,
for hosts with no metrics and ping-only hosts. Interface link-down alerts
keep the shorter threshold, and the dashboard still shows the host as down
straight away.
Closes#14
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Replace the port blocks on device pages with SVG faceplates traced from
reference images: Dream Machine Pro, USW Pro 24 PoE, USW Aggregation,
USW Flex, USW Flex Mini, USP PDU Pro and UPS 2U Pro. Port LEDs use each
model's documented colors, and ports show a cable or module when up.
Remove SWITCH_LAYOUTS, which had wrong data: the aggregation switch was
numbered across the rows instead of odd over even, and the USW Flex had
an SFP cage it doesn't have. Models without a faceplate use plain blocks.
Show PDU and UPS outlets with on/off state and auto power-cycle, and let
the UPS page show its panel even though it reports no ports.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Replace the all-switches Inspector page with a per-device front panel,
port table and detail panel. /inspector now redirects to the dashboard,
and /device/<name>#port-<n> links directly to a port.
Restore neighbor names after UniFi's LLDP change: read local_port_idx
and resolve neighbors by MAC using Prometheus NIC data, UniFi devices
and the wired client table. This also brings back Run Diagnostics on
server ports, which depends on a named neighbor.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Modem and WAN:
- Poll the BGW320 status pages read-only for light levels, PON and WAN
state, with a WAN / Fiber panel, 7-day Rx trend, and alerts on WAN down,
optical limits, and Rx drift
- Read WAN outages and Smart Power restarts from the UniFi system log,
since outages can be over between 5 minute polls
Device pages:
- Add device search to the dashboard and a page per host, switch, router,
AP, PDU, or modem
- Move all Link Debug data into expandable rows there; /links redirects
- Collect UDM ports and UniFi device details (firmware, load, WAN, outlets)
- Reorder the dashboard
Monitoring fixes:
- Alert when a configured host stops reporting: its series disappear
rather than reporting down, so a dead host never raised an alert
- Alert on Prometheus outages instead of blanking the dashboard
- Treat a failed Pulse ping as unknown rather than host unreachable
- Stop new switch ports reporting lifetime bytes as one interval's rate
- Run database sessions in UTC: other server time zones shifted displayed
ages and timed suppression expiries
Also log every avatar 404 reason, add a favicon, and correct the WAN link
labels (1G on Port 9, not 10G SFP+).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
UniFi switches/APs take several minutes to rejoin after a firmware-update
reboot (observed AP taking 12 min on the scheduled 3am update), longer than
the 2-consecutive-check (~4 min) threshold shared with host link-down
detection. Give UniFi device offline detection its own, more forgiving
threshold so momentary maintenance reboots don't cut tickets.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UB2D82eYNzD1bnMHhFPjRr
- storage-01 now monitored via Prometheus node_exporter (10.10.10.11:9100),
removed from ping_hosts
- Updated data sources table (6 hosts via Prometheus, pbs only via ping)
- Added storage-01 to monitored hosts table
- Fixed Authelia reload command (restart, not reload)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Two-service architecture: Flask web app (gandalf.service) + background
polling daemon (gandalf-monitor.service)
- Monitor polls Prometheus node_network_up for physical NIC states on all
6 hypervisors (added storage-01 at 10.10.10.11:9100)
- UniFi API monitoring for switches, APs, and gateway device status
- Ping reachability for hosts without node_exporter (pbs only now)
- Smart baseline: interfaces first seen as down are never alerted on;
only UP→DOWN regressions trigger tickets
- Cluster-wide P1 ticket when 3+ hosts have genuine simultaneous
interface regressions (guards against false positives on startup)
- Tinker Tickets integration with 24-hour hash-based deduplication
- Alert suppression: manual toggle or timed windows (30m/1h/4h/8h)
- Authelia SSO via forward-auth headers, admin group required
- Network topology: Internet → UDM-Pro → Agg Switch (10G DAC) →
PoE Switch (10G DAC) → Hosts
- MariaDB schema, suppression management UI, host/interface cards
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>