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
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
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
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
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
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
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>
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
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
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
- docs/MAINTAINABILITY.md: limits and architecture rules
- .golangci.yml (pinned v2.14.0) + file-size check; make lint runs first in every gate
- web/go.mod stops the Go tool from walking web/node_modules
- layering: Target -> internal/snmptarget, TableKey -> profile.Metric.TableKey; poller no longer imports discovery
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lx9FNLG2CqeBbVGGd9bFsP