22 Commits

Author SHA1 Message Date
Vibe Coder
26fdcdc124 Match LibreNMS port graphs: totals, max, aggregate 95th, compare, utilisation
Some checks failed
ci / lint (push) Successful in 1m25s
ci / openapi (push) Successful in 29s
ci / postgres (push) Successful in 3m32s
ci / test (push) Failing after 17m53s
Operators coming from LibreNMS expect the same figures under a port
graph. Add them:

- Totals: bytes transferred in and out over the selected range
  (GraphResponse.totals), integrated from the average rate across the
  stitched raw and hourly tiers without double counting; partial
  coverage is flagged.
- Summaries read Now / Avg / Max / 95th / Total per direction, plus an
  aggregate (in+out) 95th (p95.aggregate_bps, hot store only).
- Utilisation as a percentage of port speed, and a "% of speed" axis.
- "Compare previous period" overlay on the graph view and expanded row.
- Errors and discards graphs (GET /api/v1/graph/counters), hot-store
  range only since they have no rollups. Packet and broadcast/multicast
  graphs stay disabled: those counters are not polled (DEVIATIONS 56).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017TuKgyKve2XLFtrrXWMi2w
2026-10-25 21:02:32 +00:00
Vibe Coder
f31fb3712a Add settings forms, first-install wizard, webhook presets, help and poll tools
Some checks failed
ci / lint (push) Successful in 4m19s
ci / openapi (push) Successful in 29s
ci / postgres (push) Successful in 4m55s
ci / test (push) Failing after 19m48s
Make nmon easier to install and run without editing YAML by hand:

- Settings: point-and-click forms over config.yaml (GET/PUT
  /api/v1/settings/form); the YAML editor moves to /settings/yaml.
- First install: with no sign-in configured nmon starts in install
  mode, serves only the setup wizard (sign-in, basics, devices,
  alerting) and closes it for good once sign-in is saved. A
  self-signed certificate is generated when no TLS source is given.
  poller.interval becomes a config setting.
- Reverse proxies: opt-in --http-listen plain-HTTP listener beside
  HTTPS; sign-in stays required.
- Webhooks: Generic, Teams Adaptive Card, Slack and Discord presets,
  template preview and test send.
- Help: the operations, coverage and profile docs served at /help.
- Polling: per-device "Poll now", a dry-run test poll returning an
  SNMP trace, and time-limited in-memory SNMP debug recording, in the
  UI and as `nmon device poll`.
- Ansible: deploys into install mode when no sign-in is set, and no
  longer overwrites the config.yaml the wizard wrote.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017TuKgyKve2XLFtrrXWMi2w
2026-10-25 06:31:12 +00:00
Vibe Coder
881f0ae3b1 Poll every 300 s by default, with a per-device poll interval
The owner wants LibreNMS-like 5-minute polling by default and the option
to poll a given device faster or slower. Devices gain poll_interval_s
(new migration; 0 means the default, otherwise 30 to 3600 s), settable in
the device edit dialog and through PUT /api/v1/devices/{slug}; the
poller picks it up on the next inventory refresh.

Everything that assumed one-minute samples now follows the device's
interval: graph and p95 rate windows are at least two polls, the max
series uses irate, the up-series lookback, the no_data warning, the
"unheard of for three polls" fallback and the rollup's thin-window check.
interface_errors widens to 10 m. profiletest keeps 60 s because the
expect budgets are keyed by the 1m group. Recorded as DEVIATIONS 49.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017TuKgyKve2XLFtrrXWMi2w
2026-10-24 18:55:43 +00:00
Vibe Coder
55265698e9 Add range, p95 and a summary line to the port graph grid and expanded rows
The Ports grid only drew a fixed 24 h graph per port, and an expanded row
was fixed at 24 h too, so there was no way to look at 1 h, 7 d or 30 d
the way LibreNMS allows, and the grid never showed p95. The grid now has
one range picker (1 h to 1 y) and a p95 toggle shared by every card, and
each card shows current, average and p95 per direction under the graph.
An expanded row gets its own range picker and p95 toggle, and "Open graph
view" keeps the chosen range.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017TuKgyKve2XLFtrrXWMi2w
2026-10-24 18:55:43 +00:00
Vibe Coder
674816c98c Merge v1.3: BGP peers, MAC/ARP/FDB lookup and OUI vendors
Some checks failed
ci / lint (push) Has been cancelled
ci / test (ubuntu-24.04-arm) (push) Has been cancelled
ci / test (ubuntu-latest) (push) Has been cancelled
ci / postgres (push) Has been cancelled
ci / openapi (push) Has been cancelled
Brings in the L2 table job, Find host, the L2 and Routing tabs, the BGP page,
bgp_peer_down and the optional IEEE vendor registry on top of v1.2 custom
alert rules. Conflicts were the shared backup table list, DEVIATIONS rows
(v1.3's 50-55 renumbered to 43-48) and the generated openapi.json and web
schema, which were regenerated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T4PdJUPrakM8fHvN8jYv2x
2026-10-24 01:44:15 +00:00
Vibe Coder
61b5341774 Add Find host, the L2 tab, the BGP page and the Routing tab
Some checks failed
ci / lint (push) Has been cancelled
ci / test (ubuntu-24.04-arm) (push) Has been cancelled
ci / test (ubuntu-latest) (push) Has been cancelled
ci / postgres (push) Has been cancelled
ci / openapi (push) Has been cancelled
The UI for the MAC/IP lookup and BGP monitoring.

Find host (/find, and the header search when the text is a MAC, IP, CIDR or
vendor: query; a customer name or circuit id still goes to the port search)
answers "device X port Y, VLAN Z, last seen 3 min ago" with the uplinks and
history collapsed underneath, IPv6 addresses first. A vendor column and a
"private" badge for locally administered addresses appear wherever a MAC does.
The device page gets an L2 tab only when the job holds rows (MAC table with a
VLAN filter and history toggle, ARP/ND, VLAN list), and the Ports tab shows
each port's MAC count linking to that port's MACs.

The BGP page lists every session with the down ones first and state, AS,
family and text filters; a device's Routing tab shows its sessions with
click-to-graph on accepted prefixes and update rates. The Poller page shows the
L2 job status and non-alert notes such as oui_unavailable. The policy template
"Routers: BGP peer down" binds the new rule. The mock API and a new Playwright
spec cover all of it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T4PdJUPrakM8fHvN8jYv2x
2026-10-24 01:30:32 +00:00
Vibe Coder
7173360346 Add the MAC/ARP/FDB table job, the lookup API, BGP peer collection and bgp_peer_down
Owner request: finding where a host is plugged in from a MAC or IP is a must,
and BGP peer monitoring is wanted.

L2 job (internal/l2): a job of its own, off the 60 s poll path (its sessions
are path "l2"), walks BRIDGE-MIB, Q-BRIDGE-MIB, CISCO-VTP-MIB (a per-VLAN
context walk, enabled by the cisco-ios profile flag l2.per_vlan_context, not by
detection) and the IP-MIB neighbour tables (IPv4 ARP and IPv6 ND, IPv4-only
fallback) every l2.interval, with its own concurrency cap and a per-device
deadline. It yields to the poller like the rollup job, and a device that times
out is skipped for the cycle, counted and named on /poller/health. Migration 14
adds fdb_entries, arp_entries, vlans and l2_walks with first/last-seen history,
batched upserts, daily pruning by last_seen (l2.keep) and backup coverage.

API (additive): /l2/search ranks edge ports first (a port is an uplink above
l2.edge_max_macs MACs, with an LLDP neighbour, or as a LAG), plus the device
fdb/arp/l2/l2-ports tables and /interfaces/{id}/macs; vendor and mac_kind are
computed at query time from the optional registry.

BGP: profiles label peer rows by decoding the table index (index_label.layout),
so each peer, IPv4 or IPv6, is one component whatever MIB feeds it; AS, local
AS, router id and description are component attributes, never labels.
CISCO-BGP4 (cisco-ios, cisco-nxos), Juniper BGP4-V2 and Arista BGP4V2 replace
the optional generic BGP4-MIB metrics, which edgeos, fortigate, pfsense and vyos
switch on. /bgp/peers lists sessions down first. The built-in rule
bgp_peer_down (critical, not established for 2 minutes) fires only where a
policy binds it; its notification names the peer's AS and description.

Fixtures are generated (Q-BRIDGE with IPv4/IPv6 neighbours, BRIDGE-MIB only,
Cisco per-VLAN contexts via a new simagent contexts feature) and checked
against real LibreNMS recordings. DEVIATIONS 50-55 record the departures.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T4PdJUPrakM8fHvN8jYv2x
2026-10-24 01:30:32 +00:00
Vibe Coder
b529423324 Add custom alert rules built from metrics and inventory fields
The predefined rules are too constraining: an ISP cannot say "CPU over
80% on servers for 15 minutes" or "uplink without a description", and
every new need meant editing config.yaml. Admins now build their own
rules; the shipped ones stay as read-only built-in macros with their
names unchanged, so v1.1 policies and alert history keep working.

Custom rules live in a new alert_rules table (migration 13) and are
builder-only. A metric rule names a catalogue metric (every pollable
profile metric plus push-ingested names) with a transform (value, rate,
percent_of), window, function, aggregate, comparison and threshold; the
pure internal/alertrule package compiles it to MetricsQL from fixed
templates, so no user text reaches a query. An inventory rule ANDs one to
five conditions over whitelisted device or port fields and is evaluated
in Go over store rows. The evaluator takes its rule set from a function
each cycle, so edits apply live, and both kinds use the existing state
machine, policies, suppression and notifications. Comparisons now cover
> >= < <= == != everywhere. utilisation is not offered: port speed is not
a stored series. config.yaml rules still work but are deprecated.

API (additive): rule list gains id/kind/editable/spec/expr, plus get,
create, update, delete, catalogue, test (viewer, read-only) and copy of
a built-in (cpu_high, ups_on_battery, printer_supply_empty) to a custom
rule. Deleting or renaming a rule that a policy names is a 409. The UI
gets an Alert rules tab with a builder dialog, a test button and the
generated query, and the policy dialog lists custom rules.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T4PdJUPrakM8fHvN8jYv2x
2026-10-24 00:17:48 +00:00
Vibe Coder
06a3c85c69 e2e: point device-page specs at the Overview tab
992f09b removed DeviceHeader and OverviewSummary, but six mock specs and
the live gate screenshots still waited for their test ids (device-header,
summary-tile, generic-hint, device-warnings), so they failed and stopped
gate-m4 at its UI step. Assert on what the page shows now: the System
panel (role, profile, status), the Processors/Memory/Storage panels, and
the per-port unreliable flag on the Ports tab. The device-level warning
badge and the generic-profile hint no longer exist, so their assertions
are dropped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T4PdJUPrakM8fHvN8jYv2x
2026-10-23 23:29:41 +00:00
Vibe Coder
ddc8863a67 Add alert policies: alerts fire only where a policy says so
Preprod fires everything: every default rule applied to every device and
port, so a WiFi port that always sees errors was judged like a fiber
uplink where one error matters. WiFi errors are acceptable, fiber errors
are not, and one global threshold cannot say both.

An alert policy (new alert_policies table, migration 12) binds one rule
to a scope: groups plus device and port attribute matchers (ifType,
name/alias regexes, speed, site, role, profile, kind, the alert_down
bell). The highest priority decides, ignore beats alert on a tie, and a
policy's severity/threshold/for override the rule's (its threshold also
beats group overrides). With no policy nothing alerts: not metric rules,
not device_down/device_warning, not system alarms. Nothing is seeded, so
on upgrade open alerts without a binding close silently as retired.

The pure internal/alertpolicy package makes the decision; alert, reach
and the system alarms take a gate (Options.Policy, Options.Wants, a
system policy) that the app backs with a cache refreshed every 30 s and
invalidated on every API write, so policy edits apply live. New API:
/alert-policies CRUD, a viewer-level preview of what a scope covers, and
/alert-rules. The UI gets an Alert policies tab with templates and a
live preview; the port bell now says it only marks the port. Gate
scripts create the policies they need. DEVIATIONS row 41 and the
operations guide describe the new default.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T4PdJUPrakM8fHvN8jYv2x
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 18:41:25 +00:00
992f09b97a Overview tab: use generated overview types, drop dead DeviceHeader/OverviewSummary
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lx9FNLG2CqeBbVGGd9bFsP
2026-10-23 14:38:16 +00:00
d92c6e1e08 Device overview endpoint and aggregate metric graphs
GET /api/v1/devices/{slug}/overview (traffic, ports, availability, rtt, events);
metrics/graph gains aggregate=avg|sum|max when component is omitted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lx9FNLG2CqeBbVGGd9bFsP
2026-10-23 14:34:37 +00:00
9b4ab7ccbb Device Overview tab: LibreNMS-style panel dashboard (system, ports, availability, ping, processors, memory, storage, supplies, sensors, events)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lx9FNLG2CqeBbVGGd9bFsP
2026-10-23 14:17:49 +00:00
fe53998aa6 Device page tabs (Overview, Ports, per-category metrics, Printer and Wireless panels) and Printers fleet page
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lx9FNLG2CqeBbVGGd9bFsP
2026-10-23 05:44:54 +00:00
e25143881c Device metrics API, printer fleet, profile presentation schema (v1.0.1 backend)
Profiles gain category/label/of/display/scale_by/sentinels/enum_state and component_attrs;
every pollable non-interface metric is annotated and an orphan test enforces it. New routes
GET /devices/{slug}/metrics, /metrics/graph and /printers; printer supply type/colour read at
discovery into device_components.attrs; printer_supply_empty alert; supply_replaced audit event;
docs/coverage.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lx9FNLG2CqeBbVGGd9bFsP
2026-10-23 05:33:07 +00:00
ad220eae58 Interface-down alerts are opt-in: per port (bell toggle, bulk) and via groups
interfaces.alert_down / groups.alert_down (migration 9); interface_down rule OptIn; non-opted ports' open alerts close silently as retired.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lx9FNLG2CqeBbVGGd9bFsP
2026-10-23 05:04:41 +00:00
345daaa773 Profiles for EdgeSwitch, UniFi switch/AP and VyOS; profile pinning and re-match; whole-row navigation
Discovery re-matches unpinned devices every run and at startup; an admin can pin a
profile (PUT /devices/{slug} profile, GET /profiles, DeviceView profile_pinned and
profile_match). Rows on the device list, interface search and poller health navigate on
click and Enter.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lx9FNLG2CqeBbVGGd9bFsP
2026-10-23 05:03:17 +00:00
bf6dbd4923 Add device management UI: add, edit, rediscover, retire
The UI had no way to add a device. Adds an admin-only Add device dialog
(IPv6/IPv4 addresses, SNMP v1/v2c/v3, synchronous discovery, reattach 409
flow) and a device-page Actions menu (Edit with parents and SNMP
replacement, Rediscover now, Retire). Purge now requires typing the name.

API: POST /api/v1/devices/{slug}/retire (admin, audited, 409 if already
retired). OpenAPI change is additive.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lx9FNLG2CqeBbVGGd9bFsP
2026-10-23 03:36:10 +00:00
a768a15fb6 M6: web UI (user menu, role-aware controls, settings, token kinds, CSV import, purge, descriptions, graph and device-down fixes)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lx9FNLG2CqeBbVGGd9bFsP
2026-10-23 02:15:49 +00:00
b3aae800bf M4 integration (alerting wired, alerts API/UI, maintenance, dependency hold, redundant-topology gate) + M5 (rollup, ingest, Proxmox, checks, p95, groups, longq, gate) + M6 library packages (auth, backup, diskguard, importcsv, keyrotate)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lx9FNLG2CqeBbVGGd9bFsP
2026-10-23 00:10:25 +00:00
f85c3ef781 M3: Go API per UI contract, embedded UI, openapi compat check, nmon-devtools, live gate + screenshots; M4 WP1 reachability and dependency suppression
Some checks failed
ci / lint (push) Has been cancelled
ci / test (ubuntu-24.04-arm) (push) Has been cancelled
ci / test (ubuntu-latest) (push) Has been cancelled
ci / postgres (push) Has been cancelled
ci / openapi (push) Has been cancelled
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lx9FNLG2CqeBbVGGd9bFsP
2026-10-22 20:34:14 +00:00
e7c70ce466 M3 WP2: web UI (React 19, TanStack, uPlot TrafficGraph), contract-generated client, mock e2e + screenshots
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lx9FNLG2CqeBbVGGd9bFsP
2026-10-22 19:13:27 +00:00