Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
b0d18f3
statd: add contact + location to ietf-system:system operational
troglobit Mar 29, 2026
40c9791
webui: initial import
troglobit Mar 28, 2026
1347138
webui: accordion sidebar and general-purpose status badges
troglobit Mar 28, 2026
7b85133
webui: anonymous login, user menu, theme toggle on login page
troglobit Mar 28, 2026
8af25e5
webui: sidebar fixes, content sectioning, and theme fixes
troglobit Mar 28, 2026
62ad2af
webui: slightly more professional look to login page
troglobit Mar 28, 2026
3a6b9bb
webui: redesign firmware page, use slot bootnames
troglobit Mar 28, 2026
fdcba0e
webui: overhaul dashboard cards
troglobit Mar 28, 2026
1abb896
webui: keystore — expandable row detail view for key data
troglobit Mar 28, 2026
db2b142
webui: fix formatting in dhcp and lldp pages
troglobit Mar 28, 2026
de0b8d8
webui: interfaces — tree view, collapsible bridges, forwarding flag
troglobit Mar 29, 2026
ff4bbab
webui: firewall — zone matrix, drill-down, conditional detection
troglobit Mar 29, 2026
b91aa16
webui: new page, mdns neighbor table
troglobit Mar 29, 2026
3f674cd
webui: new page, services table
troglobit Mar 29, 2026
acdac96
webui: reorganise sidebar and rename Dashboard to Overview
troglobit Mar 29, 2026
a3766a8
webui: add Configure mode with System, Users, and NACM pages
troglobit Mar 29, 2026
d01f6c3
webui: advanced configure using a yang tree
troglobit Apr 21, 2026
8c3b928
webui: relocate keystore status -> configure
troglobit Apr 23, 2026
01662df
webui: add activity log support to curated pages
troglobit Apr 23, 2026
c6d635e
webui: refactor maintenance section
troglobit Apr 24, 2026
8377a17
webui: drop wifi status, add 'configure ->' shortcuts
troglobit Apr 27, 2026
19dea23
webui: configure routes — static route management page
troglobit Apr 27, 2026
7fd3297
webui: configure firewall — zone and policy management
troglobit Apr 27, 2026
6b541e3
webui: configure interfaces, initial wizard support
troglobit Apr 27, 2026
40f9919
webui: auto-logout with configurable inactivity timeout
troglobit Apr 28, 2026
96ffcd1
webui: dedupe restconf 404 checks via restconf.IsNotFound
troglobit May 24, 2026
5af9bc2
webui: render NTP empty state gracefully when not configured
troglobit May 24, 2026
d313adc
webui: add Status > Hardware page
troglobit May 24, 2026
91f4e58
webui: sidebar, don't auto-navigate when expanding a section
troglobit May 31, 2026
a8d80a2
webui: configure, unify delete affordance on the bin icon
troglobit May 31, 2026
04127d8
webui: clear stale cfg-unsaved banner across device reboots
troglobit Jun 1, 2026
6657d96
webui: configure, restore HX-Trigger on save error
troglobit Jun 2, 2026
606a476
doc: update ChangeLog
troglobit Jun 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions board/common/rootfs/etc/default/webui
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
RESTCONF_URL=https://127.0.0.1/restconf
INSECURE_TLS=1
# Spool firmware uploads (and any other temp files) to the eMMC-backed /var/tmp
# instead of the RAM-backed /tmp — a 160 MB .pkg upload otherwise OOM-kills us.
TMPDIR=/var/tmp
1 change: 1 addition & 0 deletions board/common/rootfs/etc/nginx/app/restconf.conf
3 changes: 3 additions & 0 deletions board/common/rootfs/etc/nginx/restconf-access.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
allow 127.0.0.1;
allow ::1;
deny all;
1 change: 1 addition & 0 deletions board/common/rootfs/etc/nginx/restconf.app
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# /telemetry/optics is for streaming (not used atm)
location ~ ^/(restconf|yang|.well-known)/ {
include /etc/nginx/restconf-access.conf;
grpc_pass grpc://[::1]:10080;
grpc_set_header Host $host;
grpc_set_header X-Real-IP $remote_addr;
Expand Down
3 changes: 2 additions & 1 deletion configs/aarch64_defconfig
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,6 @@ BR2_PACKAGE_FIREWALL=y
BR2_PACKAGE_IITO=y
BR2_PACKAGE_KEYACK=y
BR2_PACKAGE_KLISH_PLUGIN_INFIX=y
BR2_PACKAGE_LANDING=y
BR2_PACKAGE_LOWDOWN=y
BR2_PACKAGE_MCD=y
BR2_PACKAGE_MDNS_ALIAS=y
Expand All @@ -187,6 +186,8 @@ BR2_PACKAGE_PODMAN_DRIVER_DEVICEMAPPER=y
BR2_PACKAGE_PODMAN_DRIVER_VFS=y
BR2_PACKAGE_TETRIS=y
BR2_PACKAGE_ROUSETTE=y
# BR2_PACKAGE_LANDING is not set
BR2_PACKAGE_WEBUI=y
BR2_PACKAGE_RAUC_INSTALLATION_STATUS=y
BR2_PACKAGE_HOST_PYTHON_YANGDOC=y
BR2_PACKAGE_PCIUTILS=y
Expand Down
1 change: 1 addition & 0 deletions configs/aarch64_minimal_defconfig
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ BR2_PACKAGE_FIREWALL=y
BR2_PACKAGE_IITO=y
BR2_PACKAGE_KEYACK=y
BR2_PACKAGE_KLISH_PLUGIN_INFIX=y
BR2_PACKAGE_LANDING=y
BR2_PACKAGE_LOWDOWN=y
BR2_PACKAGE_MCD=y
BR2_PACKAGE_MDNS_ALIAS=y
Expand Down
3 changes: 2 additions & 1 deletion configs/arm_defconfig
Original file line number Diff line number Diff line change
Expand Up @@ -162,14 +162,15 @@ BR2_PACKAGE_FIREWALL=y
BR2_PACKAGE_IITO=y
BR2_PACKAGE_KEYACK=y
BR2_PACKAGE_KLISH_PLUGIN_INFIX=y
BR2_PACKAGE_LANDING=y
BR2_PACKAGE_LOWDOWN=y
BR2_PACKAGE_MCD=y
BR2_PACKAGE_MDNS_ALIAS=y
BR2_PACKAGE_NETBROWSE=y
BR2_PACKAGE_ONIEPROM=y
BR2_PACKAGE_TETRIS=y
BR2_PACKAGE_ROUSETTE=y
# BR2_PACKAGE_LANDING is not set
BR2_PACKAGE_WEBUI=y
BR2_PACKAGE_RAUC_INSTALLATION_STATUS=y
BR2_PACKAGE_HOST_PYTHON_YANGDOC=y
IMAGE_ITB_AUX=y
Expand Down
1 change: 1 addition & 0 deletions configs/arm_minimal_defconfig
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ BR2_PACKAGE_FIREWALL=y
BR2_PACKAGE_IITO=y
BR2_PACKAGE_KEYACK=y
BR2_PACKAGE_KLISH_PLUGIN_INFIX=y
BR2_PACKAGE_LANDING=y
BR2_PACKAGE_LOWDOWN=y
BR2_PACKAGE_MCD=y
BR2_PACKAGE_MDNS_ALIAS=y
Expand Down
3 changes: 2 additions & 1 deletion configs/riscv64_defconfig
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,6 @@ BR2_PACKAGE_FIREWALL=y
BR2_PACKAGE_IITO=y
BR2_PACKAGE_KEYACK=y
BR2_PACKAGE_KLISH_PLUGIN_INFIX=y
BR2_PACKAGE_LANDING=y
BR2_PACKAGE_LOWDOWN=y
BR2_PACKAGE_MCD=y
BR2_PACKAGE_MDNS_ALIAS=y
Expand All @@ -206,6 +205,8 @@ BR2_PACKAGE_PODMAN_DRIVER_DEVICEMAPPER=y
BR2_PACKAGE_PODMAN_DRIVER_VFS=y
BR2_PACKAGE_TETRIS=y
BR2_PACKAGE_ROUSETTE=y
# BR2_PACKAGE_LANDING is not set
BR2_PACKAGE_WEBUI=y
BR2_PACKAGE_RAUC_INSTALLATION_STATUS=y
BR2_PACKAGE_HOST_PYTHON_YANGDOC=y
BR2_PACKAGE_PCIUTILS=y
Expand Down
3 changes: 2 additions & 1 deletion configs/x86_64_defconfig
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,6 @@ BR2_PACKAGE_FIREWALL=y
BR2_PACKAGE_IITO=y
BR2_PACKAGE_KEYACK=y
BR2_PACKAGE_KLISH_PLUGIN_INFIX=y
BR2_PACKAGE_LANDING=y
BR2_PACKAGE_LOWDOWN=y
BR2_PACKAGE_MCD=y
BR2_PACKAGE_MDNS_ALIAS=y
Expand All @@ -181,6 +180,8 @@ BR2_PACKAGE_PODMAN_DRIVER_DEVICEMAPPER=y
BR2_PACKAGE_PODMAN_DRIVER_VFS=y
BR2_PACKAGE_TETRIS=y
BR2_PACKAGE_ROUSETTE=y
# BR2_PACKAGE_LANDING is not set
BR2_PACKAGE_WEBUI=y
BR2_PACKAGE_RAUC_INSTALLATION_STATUS=y
BR2_PACKAGE_HOST_PYTHON_YANGDOC=y
BR2_PACKAGE_PCIUTILS=y
Expand Down
1 change: 1 addition & 0 deletions configs/x86_64_minimal_defconfig
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ BR2_PACKAGE_FIREWALL=y
BR2_PACKAGE_IITO=y
BR2_PACKAGE_KEYACK=y
BR2_PACKAGE_KLISH_PLUGIN_INFIX=y
BR2_PACKAGE_LANDING=y
BR2_PACKAGE_LOWDOWN=y
BR2_PACKAGE_MCD=y
BR2_PACKAGE_MDNS_ALIAS=y
Expand Down
14 changes: 14 additions & 0 deletions doc/ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,20 @@ Change Log

All notable changes to the project are documented in this file.


[v26.06.0][UNRELEASED]
-------------------------

### Changes

- Initial WebUI: static status pages and a tree view of operational status.
Curated configuration pages for a some tasks and a tree view for the rest.
Also includes a maintenance section for firmware upgrade and more

### Fixes

N/A

[v26.05.0][] - 2026-05-29
-------------------------

Expand Down
1 change: 1 addition & 0 deletions package/Config.in
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ source "$BR2_EXTERNAL_INFIX_PATH/package/tetris/Config.in"
source "$BR2_EXTERNAL_INFIX_PATH/package/libyang-cpp/Config.in"
source "$BR2_EXTERNAL_INFIX_PATH/package/sysrepo-cpp/Config.in"
source "$BR2_EXTERNAL_INFIX_PATH/package/rousette/Config.in"
source "$BR2_EXTERNAL_INFIX_PATH/package/webui/Config.in"
source "$BR2_EXTERNAL_INFIX_PATH/package/nghttp2-asio/Config.in"
source "$BR2_EXTERNAL_INFIX_PATH/package/date-cpp/Config.in"
source "$BR2_EXTERNAL_INFIX_PATH/package/rauc-installation-status/Config.in"
Expand Down
2 changes: 2 additions & 0 deletions package/landing/landing.mk
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ define LANDING_INSTALL_TARGET_CMDS
mkdir -p $(TARGET_DIR)/usr/html/
cp $(@D)/*.html $(TARGET_DIR)/usr/html/
cp $(@D)/*.png $(TARGET_DIR)/usr/html/
$(INSTALL) -D -m 0644 $(LANDING_PKGDIR)/default.conf \
$(TARGET_DIR)/etc/nginx/available/default.conf
endef

$(eval $(generic-package))
30 changes: 30 additions & 0 deletions package/webui/50x.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<!DOCTYPE html>
<html>
<head>
<title>Loading…</title>
<meta charset="utf-8">
<meta http-equiv="refresh" content="3">
<style>
html { color-scheme: light dark; font-family: Tahoma, Verdana, Arial, sans-serif; }
body { max-width: 32em; margin: 4em auto; text-align: center; }
h1 { font-weight: 500; margin-bottom: 0.5em; }
p { color: #888; margin: 0.5em 0; }
.spinner {
display: inline-block;
width: 1.5em;
height: 1.5em;
margin-top: 1em;
border: 3px solid currentColor;
border-top-color: transparent;
border-radius: 50%;
animation: rot 1s linear infinite;
}
@keyframes rot { to { transform: rotate(360deg); } }
</style>
</head>
<body>
<h1>Loading…</h1>
<p>The device is finishing its startup. This page refreshes automatically.</p>
<p><span class="spinner" aria-hidden="true"></span></p>
</body>
</html>
9 changes: 9 additions & 0 deletions package/webui/Config.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
config BR2_PACKAGE_WEBUI
bool "webui"
depends on BR2_PACKAGE_HOST_GO_TARGET_ARCH_SUPPORTS
depends on BR2_PACKAGE_ROUSETTE
depends on !BR2_PACKAGE_LANDING
help
Web management interface for Infix, a Go+HTMX application
that provides browser-based configuration and monitoring
via RESTCONF.
24 changes: 24 additions & 0 deletions package/webui/default.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
server {
listen 80;
listen [::]:80;
server_name _;
return 301 https://$host$request_uri;
}

server {
listen 443 ssl;
listen [::]:443 ssl;
server_name _;
include ssl.conf;

# 404 also points at /50x.html: the page is a "Loading…" screen
# with a meta-refresh, so the early-boot window where the Go
# backend isn't up yet (and any other transient 404 / 5xx) self-
# recovers as soon as upstream comes back.
error_page 404 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}

include /etc/nginx/app/*.conf;
}
12 changes: 12 additions & 0 deletions package/webui/webui-proxy.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Shared proxy-pass shape for the webui upstream. Included from every
# location in webui.conf that forwards to the Go app so we don't have to
# restate the header block when nested locations declare their own
# proxy_* directives (which suppresses inheritance from the outer block).

proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_redirect off;
39 changes: 39 additions & 0 deletions package/webui/webui.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# 256 MiB covers current and near-future firmware images (the aarch64
# .pkg is ~160 MiB today) without setting an alarming body-size cap on
# devices that may only have 512 MiB of RAM. nginx is the single source
# of truth for the upload ceiling.
#
# Set at server scope (top of file) rather than once on an outer
# `location /` with a nested inner location: nginx inheritance of
# client_max_body_size into a nested location that declares its own
# `proxy_pass` block has bitten us before, silently falling back to
# the http-level default of 1m and rejecting 160 MiB firmware uploads
# with 413.
client_max_body_size 256m;

location / {
include /etc/nginx/webui-proxy.conf;
}

location = /firmware/upload {
# Body is buffered by nginx (to /var/cache/nginx/client-body, ext4
# on eMMC, with 16 KB RAM cap before spill) before forwarding to
# Go. The extra disk pass on a 160 MiB upload is ~30s on eMMC; in
# exchange Go gets a complete, well-formed request with a known
# Content-Length. The previous setup used `proxy_request_buffering
# off` to stream the body straight through and avoid the double
# write, but that raced the response write with the body forward:
# net/http EOFs the body reader at Content-Length, then closes the
# socket, but with streaming the multipart trailer can still be in
# the kernel receive buffer, so close() sends RST instead of FIN
# and nginx serves /50x.html to the client.
proxy_read_timeout 600s;
include /etc/nginx/webui-proxy.conf;
}

# Liveness probe served by nginx itself — no upstream call, no log line.
# Used by the watchdog div in base.html and the reboot-overlay poller.
location = /device-status {
access_log off;
return 204;
}
29 changes: 29 additions & 0 deletions package/webui/webui.mk
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
################################################################################
#
# webui
#
################################################################################

WEBUI_VERSION = 1.0
WEBUI_SITE_METHOD = local
WEBUI_SITE = $(BR2_EXTERNAL_INFIX_PATH)/src/webui
WEBUI_GOMOD = github.com/kernelkit/webui
WEBUI_LICENSE = MIT
WEBUI_LICENSE_FILES = LICENSE
WEBUI_REDISTRIBUTE = NO

define WEBUI_INSTALL_EXTRA
$(INSTALL) -D -m 0644 $(WEBUI_PKGDIR)/webui.svc \
$(FINIT_D)/available/webui.conf
$(INSTALL) -D -m 0644 $(WEBUI_PKGDIR)/webui.conf \
$(TARGET_DIR)/etc/nginx/app/webui.conf
$(INSTALL) -D -m 0644 $(WEBUI_PKGDIR)/webui-proxy.conf \
$(TARGET_DIR)/etc/nginx/webui-proxy.conf
$(INSTALL) -D -m 0644 $(WEBUI_PKGDIR)/default.conf \
$(TARGET_DIR)/etc/nginx/available/default.conf
$(INSTALL) -D -m 0644 $(WEBUI_PKGDIR)/50x.html \
$(TARGET_DIR)/usr/html/50x.html
endef
WEBUI_POST_INSTALL_TARGET_HOOKS += WEBUI_INSTALL_EXTRA

$(eval $(golang-package))
3 changes: 3 additions & 0 deletions package/webui/webui.svc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
service <!> name:webui log:prio:daemon.info,tag:webui \
[2345] env:-/etc/default/webui webui -listen 127.0.0.1:8080 \
-- Web management interface
22 changes: 19 additions & 3 deletions src/confd/src/services.c
Original file line number Diff line number Diff line change
Expand Up @@ -557,7 +557,20 @@ static int restconf_change(sr_session_ctx_t *session, struct lyd_node *config, s

ena = lydx_is_enabled(srv, "enabled") &&
lydx_is_enabled(lydx_get_xpathf(config, WEB_XPATH), "enabled");
svc_enable(ena, restconf, "restconf");

/*
* restconf.app is permanently installed in nginx/app/ so rousette is
* always reachable from loopback (required by the WebUI). When external
* RESTCONF access is disabled we tighten the location to loopback-only
* by writing the appropriate allow/deny rules into the include file.
*/
FILE *fp = fopen("/etc/nginx/restconf-access.conf", "w");
if (fp) {
if (!ena)
fputs("allow 127.0.0.1;\nallow ::1;\ndeny all;\n", fp);
fclose(fp);
}
mdns_records(ena ? MDNS_ADD : MDNS_DELETE, restconf);
finit_reload("nginx");

return put(cfg);
Expand Down Expand Up @@ -703,13 +716,16 @@ static int web_change(sr_session_ctx_t *session, struct lyd_node *config, struct

/* Web master on/off: propagate to nginx and all sub-services */
if (lydx_get_xpathf(diff, WEB_XPATH "/enabled")) {
int rc_ena = ena && lydx_is_enabled(lydx_get_xpathf(config, WEB_RESTCONF_XPATH), "enabled");
int nb_ena = ena && lydx_is_enabled(lydx_get_xpathf(config, WEB_NETBROWSE_XPATH), "enabled");

svc_enable(ena && lydx_is_enabled(lydx_get_xpathf(config, WEB_CONSOLE_XPATH), "enabled"),
ttyd, "ttyd");
svc_enable(nb_ena, netbrowse, "netbrowse");
svc_enable(ena && lydx_is_enabled(lydx_get_xpathf(config, WEB_RESTCONF_XPATH), "enabled"),
restconf, "restconf");
/* Rousette follows web/enabled; external access is gated separately via restconf/enabled */
ena ? finit_enable("restconf") : finit_disable("restconf");
ena ? finit_enable("webui") : finit_disable("webui");
mdns_records(rc_ena ? MDNS_ADD : MDNS_DELETE, restconf);
svc_enable(ena, web, "nginx");
mdns_alias_conf(nb_ena);
finit_reload("mdns-alias");
Expand Down
6 changes: 3 additions & 3 deletions src/rauc-installation-status/rauc-installation-status.c
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,10 @@ int main(int argc, char **argv)
json_object_set_new(json, "last-error", json_string(strval));
props = rauc_installer_get_progress(rauc);
if(props) {
GVariant *val;
gint32 pct;
progress = json_object();
g_variant_get(props, "(@isi)", &val, &strval, NULL);
json_object_set_new(progress, "percentage", json_string(g_variant_print(val, FALSE)));
g_variant_get(props, "(isi)", &pct, &strval, NULL);
json_object_set_new(progress, "percentage", json_integer(pct));
json_object_set_new(progress, "message", json_string(strval));
json_object_set_new(json, "progress", progress);
}
Expand Down
10 changes: 9 additions & 1 deletion src/statd/python/yanger/ietf_system.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,9 +257,16 @@ def add_software(out):
insert(out, "infix-system:software", software)

def add_hostname(out):
hostname = HOST.run(tuple(["hostname"]))
hostname = HOST.run(tuple(["hostname"]))
out["hostname"] = hostname.strip()

def add_contact_location(out):
for name in ("contact", "location"):
data = HOST.run_json(("copy", "running", "-x", f"/system/{name}"), {})
val = data.get("ietf-system:system", {}).get(name)
if val:
out[name] = val

def add_timezone(out):
path = HOST.run(tuple("realpath /etc/localtime".split()), "")
timezone = None
Expand Down Expand Up @@ -448,6 +455,7 @@ def operational():
out_state = out["ietf-system:system-state"]
out_system = out["ietf-system:system"]
add_hostname(out_system)
add_contact_location(out_system)
add_users(out_system)
add_timezone(out_system)
add_software(out_state)
Expand Down
1 change: 1 addition & 0 deletions src/webui/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
webui
Loading
Loading