Infix defines its own Kconfig options with unprefixed names (IMAGE_*,
QEMU_*, TRUSTED_KEYS*) and an INFIX_ prefix. Unprefixed names risk
clashing with Buildroot and with other br2-externals/spins that source
our tree.
Rename all 86 options to a common IX_ prefix, collapsing the existing
INFIX_ ones, e.g. INFIX_IMAGE_ID becomes IX_IMAGE_ID. The os-release
INFIX_DESC field is a runtime interface, not a Kconfig option, and is
kept; the qemu Config.in generator's @ARCH@ symbol is updated to match.
Closes#1305
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
Releases exposed the RAUC upgrade bundle only inside the per-arch
tarball, and shipped no ready-to-flash Raspberry Pi image; users had
to unpack the tarball or assemble an image from a separate bootloader.
Upload output/images/*.pkg as its own artifact, and build an rpi64 SD
card image in the aarch64 release leg before rootfs.squashfs is
stripped from the tarball, combining it with the rpi64 bootloader from
the latest-boot release. Publish both as release assets; the tarball
still carries the .pkg too.
Closes#1084
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
Services could not bind to addresses not yet present on an interface,
e.g. floating VIPs managed by a routing daemon, since nonlocal bind
was left at the kernel default of disabled.
Enable net.ipv4.ip_nonlocal_bind and net.ipv6.ip_nonlocal_bind among
the router defaults in /etc/sysctl.d.
Closes#1022
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
Developer builds report the bare upstream U-Boot version, e.g.
2025.01, and are easily mistaken for release builds. Nothing stamps
a local version on non-release builds to tell them apart.
Write a .scmversion of -DEVEL from a pre-build hook when INFIX_RELEASE
is unset, so developer builds report 2025.01-DEVEL. Release builds
are left untouched.
Closes#919
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
Three more RESTCONF wrappers assume the response to a keyed GET is the
bare node, when the server nests it under the full parent path: the
container resource-usage stats always read zero, the WireGuard listen
port never populated, and resetting advertised link modes silently
re-enabled auto-negotiation on links where it was forced off.
Same class of bug as the firewall zone reset fix: the decode matches
nothing and the zero value is used as if valid. Model the full nesting
in all three wrappers, reusing the existing container list wrapper.
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
Resetting a zone's services (or interfaces) wiped the entire zone:
action, interfaces, address-sets, everything but the name.
The keyed zone GET decodes into a wrapper expecting the node bare, but
the server nests it under the full parent path:
{"infix-firewall:firewall": {"zone": [ ... ]}}
The decode silently matched nothing, so the reset handler rebuilt the
zone from an empty struct and PUT only the zone name. The same
mismatch also broke network preservation when saving a zone.
Clear leaf-lists by deleting each instance instead of rebuilding the
zone with PUT, so a reset cannot touch anything else. Fix the wrapper
shape, document the nesting convention on the RESTCONF client Get, and
log a warning when a non-empty response decodes to a zero-valued
wrapper. When saving zones, omit empty leaf-lists and a defaulted
action. Refresh the firewall page after resets and update the handler
tests to mock the real response shape.
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
Add firewall address-sets: named sets of IP addresses and networks,
usable as zone sources for per-IP access control, issue #1189. Static
entries are part of the configuration; dynamic entries are managed at
runtime with new add/remove/flush actions, from the CLI or over
NETCONF/RESTCONF. Dynamic entries survive firewall configuration
changes but are not saved to the configuration. Sets with a timeout
expire their entries automatically.
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
gen-hostname baked the last three MAC octets straight into the generated
hostname, so the actual MAC ended up stored in the configuration. Copy
that config to another device and its hostname no longer matched the
hardware.
Emit the %h and %m format specifiers and let confd expand them when the
config is applied, so the MAC is resolved per device and never written
to a saved config. This also drops the /run/system.json lookup.
Closes#1554
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
On weekly builds INFIX_RELEASE is "latest", and it fed straight into
INFIX_VERSION, so the keyword showed up as VERSION, VERSION_ID and
PRETTY_NAME in /etc/os-release, in /etc/version (show version), in the
ietf-system os-version operational leaf, and in the mDNS advertisement.
None of those pinned down the source revision; only BUILD_ID did.
Tie INFIX_VERSION to INFIX_BUILD_ID (git describe). INFIX_RELEASE now
only labels the release channel (IMAGE_VERSION) and names the published
artifacts.
Also exclude the moving 'latest*' tags from git describe, so the build
id can never resolve to "latest" should that tag land on, or nearest to,
the built commit.
Fixes#1524
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
In netdag_gen_ethtool_autoneg() the unmapped and "exactly one entry"
checks already guarantee a forced PMD link mode is selected, but
Coverity cannot see the correlation and flags the later deref. Fold
!forced into the entry-count guard to make the invariant explicit.
Fixes Coverity CID 561388.
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
lydx_get_cattr() yields NULL for an absent leaf or a failed radio
component lookup; that NULL was passed straight to strcmp() on several
WiFi paths (wifi_chan_to_freq, the wifi_gen_mesh width branches,
wifi_find_higher_band_twin, dep_radio_components), risking a crash.
Guard each value before use.
Fixes Coverity CID 561387, 561389, 561390, 561391, 561392.
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
When a veth pair has one end in a container and the other in the host
namespace, removing it failed: the container end's teardown deleted the
whole pair, so the host end's own delete then failed ("Cannot find
device") and aborted the teardown, leaving the interface behind.
The host-namespace delete added for the both-ends-in-container case was
emitted for every container veth end. Restrict it to the primary end,
which for a host/container pair is the host end that already deletes the
pair itself.
Extend the container veth tests to remove the pair and verify it is gone,
covering both the single-end (regression) and both-ends teardown paths.
Fixes: #1546
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
Previously at least one end of a veth pair had to remain in the host
namespace, because that end created and destroyed the pair. Assigning
both ends to containers left no one to create it.
Select a deterministic primary end so exactly one side creates the pair.
When the primary is itself a container interface, create the pair in the
host namespace before the container starts; CNI host-device then moves
each end into its container. Teardown is deferred to the container
removal script so the pair does not linger and block re-creation.
Drop the now-obsolete limitation notes from the documentation and YANG,
and add a regression test connecting two containers over a veth pair.
Fixes: #941
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
The per-port PHY temperature sensors on Marvell DSA switches show up in
'show hardware' with the name the kernel derives from the full device
tree path, e.g. cp0busbusf2000000mdio12a200switch2mdio01, which honestly
is completely unreadable and also overruns the value column. Name each
sensor after the front-panel port it serves (e1, e2, ...) by matching
the PHY's device-tree phandle against each interface's phy-handle.
Also:
- show system: report a representative SoC temperature on CN913x by
matching the ap-* and cp<N>-* thermal zones, hottest wins.
- cli-pretty: truncate over-long sensor names so they can never spill
into the value column again.
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
Reorder the README so newcomers see the product before the philosophy:
CLI demo and web UI up top, a "Try it in 5 minutes" section linking the
GNS3 and build-from-source guides, then hardware and the rest. Tone
down the remaining marketing prose in the hardware and use-case lists.
Rename CODE-OF-CONDUCT.md to CODE_OF_CONDUCT.md so GitHub recognises it
as the community code of conduct, and link the .github/ health files
from the README.
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
GitHub now publishes a sha256 checksum for each release asset, so the
separately generated .sha256 files are redundant.
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
Refactor to use a dummy interface instead of wasting a dut. This not
only simplifies the test but also increases test coverage on test rigs
with a single DUT.
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
Add govulncheck CI and Dependabot for the two Go modules, src/webui
and src/netbrowse, so vendored dependencies don't quietly accumulate
CVEs between manual updates.
The workflow reports every finding in the run summary but only fails
on vulnerabilities our code actually calls in a dependency. Called
stdlib vulnerabilities are surfaced too, but they're fixed by bumping
the Buildroot host Go rather than a module's go.mod, so they don't
gate the build.
Dependabot ignores openconfig/goyang: it's pinned to our kernelkit
fork via a replace directive and stepped by hand.
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
The three YANG 1.1 fixes lived as a frozen, one-shot copy of goyang
v1.6.3 under internal/goyang (via a local `replace`) — invisible to
dependency/CVE tracking and impossible to update. Move them to the
maintained kernelkit/goyang fork (v1.6.3-kkit branch) and depend on it
through go.mod, pinned by commit hash; drop the in-tree copy. getopt
falls out of the dependency set since it was only used by goyang's CLI,
not pkg/yang.
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
The "unsaved changes" banner was set unconditionally at every site that
touches config, so it could appear when running-config actually equals
startup — contradicting the "Show diff" modal, which correctly showed
nothing. A shared updateCfgUnsaved helper now compares running against
startup (the same datastore pair the diff uses) and sets or clears the
banner cookie to match:
- Apply and restore-to-running clear it when an Apply/restore reverts
an out-of-band (e.g. CLI) change back to match startup, instead of
always showing it.
- The advanced-tree presence toggle writes only the candidate
datastore, so it no longer sets the running-vs-startup banner at all
(matching the other candidate-only tree handlers).
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
These two shortcuts are toggleable in the configuration but were only
detected once at login and baked into the session, so disabling them
didn't drop the topbar icon / user-menu entry until re-login.
Refresh just those two from running-config on full page loads (htmx
swaps and pollers, which never re-draw them, are skipped), so toggling
takes effect on the next reload without logging out.
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
Add a Hash dropdown to the add-user and change-password forms — yescrypt
(default), SHA-512, SHA-256, MD5 — passed through to mkpasswd. These are
exactly the four hashes the infix-system:crypt-hash YANG type accepts;
unknown values fall back to the default. Password inputs switch to the
CSS-masked type=text used by keystore secrets, which keeps the browser
password manager from offering to save device credentials. Each add-user
field gets a YANG description as a hover tooltip.
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
Adds a button next to "Save to startup" that opens a modal showing a
unified diff between startup-config and running-config, so the pending
changes can be reviewed before persisting them. The handler fetches
both datastores over RESTCONF (same serializer → low-noise diff),
writes them to temp files, and runs busybox `diff -u`.
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
Move the per-user shell editor out of the always-visible row into the
expanded detail; the summary now reports the shell as plain text. This
also fixes the editor showing "bash" for non-bash users: the dropdown
matched the module-prefixed option value against the bare stored shell,
so it never matched and fell back to the first option.
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
Add conditional YANG feature 'web-ui' to infix-services.yang, set when
the webui is built. This feature is what the test now looks for to
determine if it should be skipped or not.
Also, clean up remnant of old test, which was entirely replaced by
Mattias' login/csrf test.
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
Verify:
- Unauthenticated GET / -> 303 to /login
- Bad-password rejection -> no session is created
- Session cookie is flagged HttpOnly
- Authenticated GET /interfaces -> 200
- with htmx-config meta tag check
- State-changing POST without CSRF token -> 403
- POST with X-CSRF-Token header -> succeeds
- After logout the session no longer grants access
The cookie-preserving WebUI helper class also folds in the IPv6
link-local zone-id workaround (re-encode %25 back to %) that
infamy.restconf already uses.
Signed-off-by: Mattias Walström <lazzer@gmail.com>
Surface network diagnostics the Overview lacked: default gateway(s) with
a live reachability dot, an Internet reachability check, the effective DNS
resolvers (origin static vs DHCP, plus the interface), search domains, NTP
sync, and the IP addresses of every L3 interface.
All from the operational datastore. Reachability is an async per-address
ping (pulsing green dot on reply, red cross on none).
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
Auto-logout was client-side only while the server kept a fixed 1h idle
timeout, so "Off" still logged you out. Sessions now carry a per-session
timeout the client syncs from the menu (0 = never), capped at 24h.
Theme: the login toggle icon was never initialised on load (stuck on the
'auto' glyph); add color-scheme so native controls follow the OS on
'auto', and re-apply on OS theme change.
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
- Add missing Configure -> shortcuts
- For shortcuts into YANG tree, expand and mark the tree node
- Revamp the /mdns status page to mimic /ntp => more vertical space
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
Surface the configured hostname right of the logo, pinned to the
sidebar's right edge so it reads as the start of the content area. When
location and/or contact are set they appear in a hover/focus popover, so
the bar stays uncluttered; the name links to Configure > System.
Loaded asynchronously (hx-trigger="load") to keep it out of every page's
data path — the topbar persists across htmx swaps, so it fetches once per
full page load and renders nothing on error or when no hostname is set.
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
External services like the mdns-browser and the web console belong in
the User Menu next to the bundled user guide. They all open another
browser tab and provide another service.
Place common tasks close to each other and high up: Interface overview,
routing table, and firewall. Feels slightly better imo.
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
The collapsed Interfaces list left DHCP/SLAAC/zeroconf invisible until a
row was unfolded, so L3 interfaces showed only empty cells. Replace the
Addresses column with a Config column whose pills are chosen by role,
mirroring the Status > Interfaces data column but slanted to configured
intent: vid for VLANs, mode for WiFi, 802.1Q for filtering bridges, and
DHCPv4/DHCPv6/SLAAC/Zeroconf for IP interfaces, alongside any static
addresses.
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
Replace the single bulk-PUT form + client-side row cloning (which broke
when these pages were split out of System settings) with the
server-driven list pattern used by Keystore/Users: each server is a row
keyed by name that expands to edit, with per-row delete and a dedicated
add form. DNS search domains become add/delete rows. Server creates
its container on first use, and field-info ⓘ help is sourced from the
YANG descriptions.
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
Surface confd-inferred values (DHCP option lists, etc.) on the curated
Interfaces page: SaveIPv4/IPv6Settings re-render just the interface's IP
block from the fresh candidate, swapped in place via outerHTML, so the
row stays expanded. The DHCP foldout auto-expands after a save to reveal
the inferred options.
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
confd infers Infix-suggested values (DHCP option lists, related leaves,
normalised values) into the candidate when a change is written, but the
tree editor's write handlers only wrote — SaveLeaf echoed the typed value
back, SaveGroup returned 204 — so the inferred values never appeared
until a manual page reload.
After a save (cfgSaved) re-fetch the current tree page from the now
up-to-date candidate and re-render it, so inference surfaces wherever it
landed on the page. SaveLeaf now emits cfgSaved + 204 like SaveGroup
(its form uses hx-swap="none") instead of echoing the raw input.
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
SaveIPv4Settings/SaveIPv6Settings PATCHed the ietf-ip:ipv4 container to
set forwarding, but a plain PATCH 400s ("Target resource does not exist")
when the interface has no L3 config yet — exactly the first-time
DHCP-enable case. PATCH the interface instead, with the family container
nested in a single-element list entry (rousette rejects the bare-object
form with LY_EVALID once an augmented container is nested), so the
container is created on first use.
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
When we reconnect to a device we should reload the page instead of
silently resume. Because the device may have been rebooted from the
CLI or experienced power loss. Either way, our session in RAM only
is likely gone, and if so we'll be redirected to /login again.
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
Build the mkdocs User's Guide into WebUI images and surface it in the UI.
post-build.sh runs `mkdocs build` from the top-level mkdocs.yml into
/var/www/guide when the webui package is selected and mkdocs is on the
build host; nginx serves it read-only at /guide/. The build is
best-effort — a missing mkdocs warns and ships without the guide rather
than failing — and minimal images (no webui) skip it.
The WebUI gates a book icon in the topbar and a User Guide item in the
user menu on the docs being present on disk (os.Stat, fail-closed), both
opening /guide/ in a new tab.
CI: a local setup-mkdocs composite action installs mkdocs and the
plugins from mkdocs.yml; the build and release workflows run it before
the build so images produced in CI include the guide. developers-guide
documents the new build dependency and restores the missing
mkdocs-glightbox plugin.
Fixes#633
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
Surface the mDNS network browser (netbrowse at network.local) from the
WebUI: a radar icon in the topbar and a Network Browser entry under
Status > Network, both opening it in a new tab.
Like the console, this is a config-gated capability; fold the console and
netbrowse enable flags into a single infix-services web read at login
(DetectWebShortcuts) and render each entry only when enabled, fail-closed
on a read error. The link is a static https://network.local/ since
netbrowse is a fixed name-based vhost.
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
Surface the ttyd web console (port 7681) from the WebUI: a terminal icon
in the topbar and a Console entry under Maintenance, both opening the
console in a new tab. The link target is built client-side from the
current hostname since the console runs on its own HTTPS port.
The console is a config-gated capability (infix-services web/console
enabled), so both entries are detected at login and rendered only when
it is enabled — a disabled console shows no entry rather than a dead
link. Detection fails closed: a config read error hides the entry.
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
Rename the Maintenance > Backup & Restore page to Backup & Support and
add a Support Bundle card. Download support runs the on-device
`support collect` tool, which gathers configuration, logs, routing,
interfaces, hardware inventory, and container and service status into a
single archive; the WebUI runs as root so the collection is complete.
The handler buffers the archive and only commits response headers once
collection succeeds, so a mid-collection failure is a clean error rather
than a truncated download. An optional password encrypts the bundle via
the tool's GPG support, fed on stdin so it never reaches the process
list. Collection blocks for up to a minute with no output, so the write
deadline is extended past the server's 15s WriteTimeout and nginx gets a
matching read timeout; the browser shows a "collecting" state and saves
the blob client-side using the server's filename.
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
New Diagnostics page under Maintenance runs ping, traceroute, mtr and
nmap with live output, plus a one-shot DNS lookup. A tab per tool
shares a target field, source-interface and address-family selectors,
and per-tool options; the field is focused on load and Enter runs the
active tool from any field.
Ping and traceroute stream their output line by line over SSE. mtr
runs --raw and its records are translated into a live hop table —
Loss%, Snt, Last, Avg, Best, Worst updating in place — running
continuously until Stop, or for a set number of cycles. nmap offers a
fixed set of scan profiles (quick, standard, service/version, host
discovery); a CIDR target sweeps a subnet.
Address family is forced with an explicit -4/-6 flag on the base
binary, so a dual-stack hostname no longer follows the system's
IPv6-first resolver preference on hosts that only have IPv4 routes.
The streamer hands the child a real OS pipe and reaps it on exit, so a
client disconnect (Stop closing the EventSource) kills the tool without
leaking processes. User input is guarded: targets reject a leading
dash and whitespace, the source interface is allow-listed against
configured interfaces, and arguments are always passed as a slice. The
target field keeps a history in localStorage.
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
New Logs page under Maintenance presents the on-disk syslog files
(messages, syslog, kern, auth, routing, firewall, upgrade, debug,
container, mail) with a tab per source and an (empty) chip for files
with no content.
The buffer loads the last 250 lines and pins to the bottom on open and
on tab change, with keyboard focus on the pane so PageUp/PageDown work
immediately. Load earlier pages back 250 lines at a time, preserving
the reader's scroll position as older lines fill in above; once the
current file is exhausted the button re-labels to "Load previous from
<rotation>" and walks back through rotated and gzipped archives.
Live tail streams new entries over SSE. The server runs tail -F -n 0
and debounces output; the client follows the bottom unless the reader
has scrolled up. A status pill shows connecting/live/disconnected.
nginx gets a no-buffering location for the tail stream, mirroring the
software progress endpoint.
Download returns the full archive — current file plus every rotated and
gzipped predecessor — concatenated chronologically.
Severity is coloured by a keyword heuristic since sysklogd writes no
<PRI> to disk; BSD-format lines are shown unconditionally. Filter is
client-side substring or /regex/, focusable with Ctrl/Cmd-F.
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
GET /keystore was just an http.Redirect to /configure/keystore — no
actual status view ever existed; the link landed users in Configure
and folded the Status group on the way, which was confusing.
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
Vendor a Lucide icon set under static/img/icons/ and partial:ize the
inline SVGs across base, login, firmware, sidebar, and yang-tree.
Reorganize the sidebar with mirrored Status/Configure section labels.
Rename NACM to Users & Groups and Firmware to Software; swap Routes and
Routing between Status and Configure.
Split Configure > System into General, NTP Client, and DNS Client pages,
which opens up for relocating the Set Time RPC from the Maintenance
section to a new Date & Time card in the System General config page.
Update browser tab title on htmx navigation via an HX-Trigger
setPageTitle event; encode the payload as 7-bit ASCII so the middle
dot in "Page · Context" survives header transit.
Add missing hover-help ⓘ icons to bridge port configuration, and adjust
their alignment slightly. While in interface configuration:
- fix the annoying Save => collapse so one can edit multiple
interface sections without having to re-expand the iface
- add missing dhcpv4 settings
- add missing custom mac (use advanced view for other settings)
- clean up Save button mess (consolidate)
Rename Firmware -> Software everywhere in the Maintenance section and
fix "Install progress stuck at 10%" issue.
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
Status overview gains a Speed column reading ietf-interfaces:speed +
ethernet/duplex. Interface detail page surfaces PHY type, PMD type,
supported and advertised PMDs.
Configure > Interfaces gains a per-row Ethernet panel: auto-negotiation
toggle, advertised PMDs picker, duplex tri-state, mdi-x tri-state. Each
has a reset-to-YANG-default button. Backend writes leaf-by-leaf via
PATCH+DELETE since a single PUT silently drops the duplex leaf.
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
Adds an nginx-side rate limit on POST /login so a brute-force or DoS
attempt can't pin the box on the bcrypt-shaped credential check inside
rousette/PAM. Configuration:
- Keyed on $binary_remote_addr (per source IP), 5 requests/minute
sustained with a burst of 3 nodelay, a real user fumbling their
password three times in a row sails through; the 4th attempt in
the same window returns 429.
- GET /login is unmetered (the keyed map yields the empty string,
which limit_req treats as "no key, no limit"), so a 401-driven
HX-Redirect bouncing the user back to the login page doesn't
eat into the budget.
- 32k zone (~500 IPs, the minimum nginx accepts). At cache
saturation an attacker rotating addresses still tops out around
~2500 attempts/min, all serialised through bcrypt one at a time.
- 429 instead of the default 503 keeps the response out of the
/50x.html error_page rewrite (which auto-refreshes and would
loop a throttled client straight back to /login).
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
The webui Go code now lives in the infix source tree rather than being
imported as an external module. Switch the module path to match that
reality so the imports read coherently with the rest of the codebase.
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
renderSaveError lost the HX-Trigger header during the Configure-mode
fold, leaving forms with hx-swap="none" silent on validation errors.
Re-emit `{"cfgError":...}` so the toast layer still gets the event.
DeleteKey's response goes back to a bare 200 to match the other
configure_users.go endpoints; the activity-log redirect form belongs
on the Add/Edit flows.
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
The "Running config has unsaved changes" banner kept showing after a
power cycle even though the device reloaded running from startup at
boot. The cookie was just "1" with a 24-hour MaxAge, so the browser
held on to it and the banner stayed up — but the state it referred to
was long gone.
Carry a unix timestamp in the cookie value (when set) and compare
against a webuiStartTime captured at process start. Cookies whose
timestamp predates this run are from a previous boot or webui restart;
running was reloaded from startup since then, so they no longer
describe reality and the banner must stay hidden.
- Apply / Restore-to-running: cookie = now → banner shown.
- Power cycle, page load: webuiStartTime > cookie ts → banner hidden.
- Save / Apply-and-Save: cookie cleared (unchanged).
- Legacy "1" / unparseable values: treated as stale.
Also centre the "Device unreachable" connectivity banner past the
sidebar on desktop — left:0 leaves its text drifting behind the
sidebar's z-index. On ≤1024px the sidebar slides off-screen so the
banner reclaims full width.
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
Five pages drew their per-row delete control as a bespoke <svg> "X"
glyph instead of the icon-trash partial used by Hardware, Routes,
Firewall, Interfaces and the yang tree fragments.
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
Clicking a section header (Status / Configure / Maintenance) used to
fold the previously-open section AND fire a click on the first page
link inside the just-opened section. That meant going from
"Status > Overview" to "Configure > Hardware" was three clicks worth
of page loads: Configure header expands and lands you on Configure >
Interfaces, then you click Hardware and wait again.
Toggle now just expands or collapses its own section. Sibling
sections stay open until a real page-link click, at which point
updateActiveNav() already closes the others to leave only the
section containing the active page open.
Configure's enter-POST (running → candidate snapshot) still fires
the first time the Configure accordion opens per page lifecycle, so
the candidate datastore is ready by the time the user clicks a
Configure page.
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
Mirrors the CLI's "show hardware" command — a single page that summarises
everything ietf-hardware exposes about the device:
- Board (chassis info: model, manufacturer, serial, base MAC)
- USB Ports (state + description)
- WiFi Radios (bands, standards, max-AP)
- GPS Receivers
- Sensors (grouped by parent component, all status values)
- Other Components (fallback so the page is exhaustive)
This is Phase 1 of webui-roadmap-v2. Phase 2 will trim the dashboard's
Sensors card to a few key vitals with an "All sensors →" link here,
fixing the existing height-clipping regression in the process.
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
The NTP page showed a red "Failed to fetch NTP data" banner whenever
/data/ietf-ntp:ntp returned 404 — which is the normal RESTCONF response
when NTP isn't configured at all. Treat 404 as the not-configured case
and render the empty-state card with a Configure → link, mirroring the
DHCP status page.
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
The errors.As(err, &rcErr) + StatusCode == http.StatusNotFound boilerplate
was repeated across six handlers. Replace with the IsNotFound helper
introduced in restconf/errors.go, dropping the now-unused errors import
from each affected file along the way.
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
Adds a per-user inactivity countdown that POSTs /logout after a
configurable timeout (default 15 min, persisted in localStorage)
and navigates to /login. Background pollers (watchdog, *counters
refresh) are excluded from the activity reset so they don't keep
the session alive indefinitely. htmx:responseError on 401 short-
circuits straight to /login so a server-side expiration doesn't
leave the UI failing silently.
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
Idea of wizard is to simplify:
- Re-adding Ethernet interfaces you've accidentally removed
- Setting up WiFi access point/station, incl. radio & PSK
- VLAN filtering bridges
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
Adds Configure > Routes page for viewing, adding, editing, and deleting
IPv4 and IPv6 static routes. Each route row expands in-place to an edit
form; YANG field descriptions are shown as ⓘ tooltips.
Also fixes a goyang bug where inline augments inside a uses block were
never applied to the expanded tree, making it so next-hop-address and
outgoing-interface were missing from the schema — and therefore had no
tooltip text.
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
Navigating interfaces now work, and the tree follows the accordion style
like the rest of the UI.
Also, some updates to the curated pages: online help, lists of leafrefs
instead of hard-coded list options.
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
Implements the configure accordion section with a candidate datastore
workflow: opening Configure copies running → candidate (Enter), each
card saves directly to candidate, Apply copies candidate → running
atomically, and Abort discards by resetting candidate from running.
- RESTCONF client: add Put, Patch, Delete, GetDatastore, PutDatastore,
CopyDatastore, and a shared doRequest/writeJSON helper
- Handlers: ConfigureHandler (enter/apply/abort), ConfigureSystemHandler
(hostname, clock, NTP, DNS, motd, editor), ConfigureUsersHandler
(add/delete user, shell, password, SSH keys, NACM group membership)
- Status > NACM page: read-only group permission matrix derived from
ietf-netconf-acm groups + rule-list mappings
- Sticky Apply/Abort toolbar with custom confirm dialog; server returns
HX-Redirect:/ so HTMX does a full-page reload, keeping sidebar in sync
- Accordion persistence fixed: Configure excluded from localStorage so it
never auto-reopens after Abort/Apply; restore no longer closes an
accordion that contains the active page
- Password hashing by shelling out to mkpasswd(1)
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
Group nav items by purpose:
- Security: Firewall, WireGuard, NACM
- Services: Services, DHCP, NTP, LLDP, mDNS (moved from Network)
- Network trimmed to: Interfaces, Routing, WiFi
Rename Dashboard → Overview for clearer Status > System > Overview
navigation hierarchy.
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
Add a zone-to-zone traffic matrix matching the CLI's traffic_flow logic:
- HOST row/column always present; zone→HOST uses zone action + services
(not policies), producing allow/conditional/deny verdicts correctly
- Clicking a matrix cell shows a detail panel with the policy or zone
reason for the verdict
- Conditional (⚠) detection for zones with services or port-forwarding
Enrich the policy and zone tables:
- Immutable (built-in) policies flagged with ⚷ in column header and row
- Policy rows show description as tooltip; sorted by priority
- Zones table renamed "Host Services" column; Policies drops Priority column
- Wide tables (Zones, Policies) span two grid columns like Dashboard WiFi
Fix the info-grid layout: #content lacked width:100% so margin:0 auto
was overriding flex stretch, collapsing the grid to a single column on
both the firewall and dashboard pages.
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
Display interfaces in a hierarchical tree: bridge and LAG parent rows
show a ▼ toggle in the tree column that collapses/expands their member
ports. The vertical connector line disappears when collapsed. Member
ports within each group are sorted alphabetically; loopback interfaces
always appear first, the rest alphabetically.
A ⚑ forwarding-flag column indicates which interfaces have IP
forwarding enabled, fetched concurrently from ietf-routing:routing.
The "Address" and "Detail" columns are replaced by a single "Data"
column matching the CLI layout: IP addresses, VLAN id, WiFi SSID/mode,
WireGuard peer count, etc. Table rows are top-aligned so interfaces
with multiple addresses don't cause odd vertical centering on other
cells; the two narrow icon columns (⚑ and tree) stay middle-aligned.
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>