- User-scoped API keys (Profile > API Keys) for 3rd-party REST API access
and MCP clients, each locked to one scope at creation, with expiry,
revocation, and last-used tracking.
- A hand-rolled MCP (Model Context Protocol) server exposing the fleet
(connections, nodes, guests, storage, pools, alerts, cluster status) as
read tools plus one admin-gated power-action tool, so Claude Code/Desktop
or any other MCP client can query and operate the fleet directly.
- Both the REST API and MCP are off by default and toggleable instance-wide
from Settings > API & MCP, enforced live on every request.
- Admin-managed AI providers (any OpenAI-chat-completions-compatible
endpoint) backing the AI Assistant's tool-calling loop, replacing the
single hardcoded provider.
- A built-in, zero-config, no-API-key local provider backed by Needle 2
(internal/needle) for fully offline tool-calling, wired in as a one-click
preset. Requires the operator to separately download the Needle 2 binary
and point FERRUM_NEEDLE_BIN at it -- Ferrum never fetches executable
content from the network itself; see README "Built-in LLM (Needle 2)".
- System settings (CORS allow-list, instance-wide toggles) moved to the
admin Settings UI; environment variables are now scoped to true
bootstrap-level config only (listen address, TLS, DB connection, secret,
optional Needle binary path).
- Fixed: node Journal tab 502'ing with "unexpected end of JSON input" on an
empty response, and separately with a decode error on PVE versions that
return a bare-string journal line instead of the documented {n,t} object.
- Fixed: bottom content padding disappearing on every page except the AI
Assistant (an unconditional h-full on the content wrapper let overflowing
content bleed through where the padding should render).
- Fixed: Profile page felt cramped despite a wide viewport (stray max-w-2xl
cap not present on the equivalent Settings page).
- Test coverage added for the previously-untested MCP package and the new
Needle adapter (20 new Go tests), plus a regression test for the journal
decode fix.
- Fleet Overview's Storage KPI and per-connection totals (internal/api/
overview.go) were summing shared storage once per node that mounts it,
since PVE's own Shared flag is unreliable for storage added per-node
without ticking it. New dedupeSharedStorage collapses those by an exact
(name, total, used) match within a connection; local-only plugin types
are never touched. Mirrored on the Storage page, which also now
disambiguates same-named local pools (e.g. every node's "local-lvm") by
node so a bar chart's category axis never gets duplicate labels.
- Sign-out: React Query keeps a query's last-successful data through a
failed refetch, so the cached signed-in user survived the post-logout
401 and the app never noticed. signOut() now pins ["auth","me"] to null
directly instead of invalidating-and-hoping a refetch lands in time.
- SMTP: net/smtp.SendMail opportunistically attempts STARTTLS whenever the
server advertises it regardless of the admin's "Use STARTTLS" toggle, so
an internal relay with a bad cert failed sends the admin explicitly asked
to be plaintext. A dedicated sendPlain path never attempts TLS.
- OIDC: added an "Auto-create new accounts" toggle — when off, a
first-time SSO login for an unrecognized identity is refused instead of
silently provisioning one. Also added RP-Initiated Logout: signing out of
an SSO session now also ends the session at the identity provider when it
advertises support for it (end_session_endpoint), via a stored id_token
on OIDC sessions (new sessions.oidc_id_token column).
- ResourceAreaChart tooltips now fall back to the same yTickFormatter given
to the axis when a series has no formatter of its own — fixes raw
unformatted numbers in Fleet Trend and every RRD chart built on it
(Node/Guest detail) that only ever set the axis formatter.
- Default all queries to a 20s poll + refetch-on-focus (main.tsx) instead of
a per-page opt-in, so every page/widget stays live without manual tuning.
- New internal/notify package: Gotify and SMTP (stdlib net/smtp, STARTTLS
and implicit-TLS-on-465) notifications, each independently optional. Fires
from the alert evaluator on new alert triggers; admin-configurable from
Settings with a send-test-notification action per channel.
- OIDC/SSO moved from config.yaml-only to a DB-backed, admin-editable
Settings card — swaps the live client with no restart. config.yaml is
used to seed the database once on first boot after upgrading.
- New Security settings: session TTL, login lockout policy, and a real
"require 2FA for admins" enforcement (requireTOTPEnrolled middleware)
that blocks non-enrolled admins from everything but /profile and logout.
- New org-wide default preferences (theme/accent/look/landing page) for
brand-new accounts, plus a personal landing-page picker and an
email-me-alerts opt-in on Profile.
- Storage page: separate Local vs Shared/External storage tables and
capacity donuts, fixing shared-storage totals that were being summed once
per node that mounts them (e.g. a 2TB NFS share on 4 nodes read as 8TB).
- RankedBarChart: stop the longest bar's value label wrapping onto two
lines (recharts auto-wraps LabelList when space is tight).