diff --git a/src/webui/internal/auth/login.go b/src/webui/internal/auth/login.go index 7257bedb..8274814a 100644 --- a/src/webui/internal/auth/login.go +++ b/src/webui/internal/auth/login.go @@ -74,9 +74,12 @@ func (h *LoginHandler) DoLogin(w http.ResponseWriter, r *http.Request) { // Probe optional features once at login and bake into the session. caps := handlers.DetectCapabilities(ctx, h.RC) - // The web console (ttyd) is config-gated; fold it into the same feature - // map so templates gate on .Capabilities.Has "console". - caps.Features()["console"] = handlers.DetectConsole(ctx, h.RC) + // The external web-app shortcuts (console/ttyd, netbrowse) are + // config-gated; fold them into the same feature map so templates gate + // on .Capabilities.Has "console" / "netbrowse". + console, netbrowse := handlers.DetectWebShortcuts(ctx, h.RC) + caps.Features()["console"] = console + caps.Features()["netbrowse"] = netbrowse // Trigger any post-login hooks (e.g. schema sync) with full credentials. if h.OnLogin != nil { diff --git a/src/webui/internal/handlers/capabilities.go b/src/webui/internal/handlers/capabilities.go index 87485aac..21cd497c 100644 --- a/src/webui/internal/handlers/capabilities.go +++ b/src/webui/internal/handlers/capabilities.go @@ -70,26 +70,31 @@ type yangLibrary struct { } `json:"ietf-yang-library:yang-library"` } -// webConsoleConfig mirrors the slice of infix-services we need for the -// console gate. -type webConsoleConfig struct { +// webConfig mirrors the slice of infix-services we need to gate the +// external web-app shortcuts (console, netbrowse). +type webConfig struct { Web struct { Console struct { Enabled bool `json:"enabled"` } `json:"console"` + Netbrowse struct { + Enabled bool `json:"enabled"` + } `json:"netbrowse"` } `json:"infix-services:web"` } -// DetectConsole reports whether the web console (ttyd on :7681) is enabled -// in config, so the UI can show or hide the terminal entry. Fails closed: -// a read error hides the entry rather than offering a dead link. -func DetectConsole(ctx context.Context, rc restconf.Fetcher) bool { - var cfg webConsoleConfig +// DetectWebShortcuts reports whether the web console (ttyd on :7681) and +// the mDNS network browser (netbrowse at network.local) are enabled in +// config, so the UI can show or hide their shortcuts. One read covers +// both. Fails closed: a read error hides both rather than offering a +// dead link. +func DetectWebShortcuts(ctx context.Context, rc restconf.Fetcher) (console, netbrowse bool) { + var cfg webConfig if err := rc.Get(ctx, "/data/infix-services:web", &cfg); err != nil { - log.Printf("web/console config: %v (console entry hidden)", err) - return false + log.Printf("web config: %v (console/netbrowse shortcuts hidden)", err) + return false, false } - return cfg.Web.Console.Enabled + return cfg.Web.Console.Enabled, cfg.Web.Netbrowse.Enabled } func DetectCapabilities(ctx context.Context, rc restconf.Fetcher) *Capabilities { diff --git a/src/webui/static/css/style.css b/src/webui/static/css/style.css index 42a63109..d8cec957 100644 --- a/src/webui/static/css/style.css +++ b/src/webui/static/css/style.css @@ -2133,8 +2133,8 @@ details[open] > .cfg-multi-summary { border-color: var(--fg-muted); } -/* Web console (terminal) shortcut in the topbar */ -.topbar-console { +/* External web-app shortcuts (console, network browser) in the topbar */ +.topbar-link { display: flex; align-items: center; justify-content: center; @@ -2144,7 +2144,7 @@ details[open] > .cfg-multi-summary { text-decoration: none; transition: color 0.15s, background 0.15s; } -.topbar-console:hover { +.topbar-link:hover { color: var(--fg); background: var(--border); } diff --git a/src/webui/static/img/icons/README.md b/src/webui/static/img/icons/README.md index 7f137fd7..299e7d30 100644 --- a/src/webui/static/img/icons/README.md +++ b/src/webui/static/img/icons/README.md @@ -29,6 +29,7 @@ untouched. | logs.svg | scroll-text | | mdns.svg | radio | | nacm.svg | users | +| netbrowse.svg | radar | | ntp.svg | clock | | routes.svg | git-compare-arrows | | routing.svg | git-compare-arrows | diff --git a/src/webui/static/img/icons/netbrowse.svg b/src/webui/static/img/icons/netbrowse.svg new file mode 100644 index 00000000..b06537e3 --- /dev/null +++ b/src/webui/static/img/icons/netbrowse.svg @@ -0,0 +1 @@ + diff --git a/src/webui/templates/layouts/base.html b/src/webui/templates/layouts/base.html index 8e53cb38..a3bfa4f4 100644 --- a/src/webui/templates/layouts/base.html +++ b/src/webui/templates/layouts/base.html @@ -24,10 +24,18 @@
+ {{if .Capabilities.Has "netbrowse"}} + {{/* mDNS network browser — fixed network.local vhost, so a static + href (no JS, no CSP concern). */}} + + {{template "icon-radar"}} + + {{end}} {{if .Capabilities.Has "console"}} {{/* Web console (ttyd) on :7681. href is set by JS from the current hostname since the port differs and CSP forbids inline script. */}} - {{template "icon-terminal"}} diff --git a/src/webui/templates/layouts/icons.html b/src/webui/templates/layouts/icons.html index 9c5d236d..95d472f8 100644 --- a/src/webui/templates/layouts/icons.html +++ b/src/webui/templates/layouts/icons.html @@ -11,6 +11,7 @@ {{define "icon-reset"}}{{end}} {{define "icon-menu"}}{{end}} {{define "icon-terminal"}}{{end}} +{{define "icon-radar"}}{{end}} {{define "icon-user"}}{{end}} {{define "icon-user-lg"}}{{end}} {{define "icon-chevron-down"}}{{end}} diff --git a/src/webui/templates/layouts/sidebar.html b/src/webui/templates/layouts/sidebar.html index f6de73cf..eb3a812c 100644 --- a/src/webui/templates/layouts/sidebar.html +++ b/src/webui/templates/layouts/sidebar.html @@ -103,6 +103,16 @@ mDNS + {{if .Capabilities.Has "netbrowse"}} + {{/* External link to the mDNS network browser at network.local + (new tab); not an htmx nav. */}} +
  • + + + Network Browser + +
  • + {{end}}