remove experimental web UI
The web UI shipped as "experimental" and was never finished — no nav
off the dashboard, no live updates, no settled design, never a
supported surface. It was opt-in by default already; leaving the code
in the tree for v0.1.0 only invited "does this work?" questions and
kept HostSummary/BangerSummary/SudoStatus types on the public RPC
surface that nothing else uses.
Removed:
internal/webui/ (all Go + templates + assets)
internal/daemon/web.go (server start / Layout / Config / ListVMs / ListImages)
internal/daemon/dashboard.go (DashboardSummary aggregator)
Simplified:
internal/api/types.go drop WebURL on PingResult, drop
HostSummary / SudoStatus / BangerSummary /
DashboardSummary / DashboardSummaryResult
internal/model/types.go drop DaemonConfig.WebListenAddr
internal/config/config.go drop web_listen_addr from fileConfig + Load
internal/daemon/daemon.go drop webListener / webServer / webURL fields +
startWebServer() call + ping WebURL population
internal/cli/banger.go `daemon status` output no longer branches on web
internal/daemon/{doc.go,ARCHITECTURE.md} drop web UI sections
README.md drop web_listen_addr config bullet + security paragraph
Tests updated to reflect the new shape. Coverage 57.3 -> 58.9% (the
webui package was largely untested; its removal lifts the ratio
without moving the numerator). `banger daemon status` output and
--help are web-free. Lint + full suite green.
This commit is contained in:
parent
687fcf0b59
commit
d1b9a8c102
24 changed files with 9 additions and 2752 deletions
|
|
@ -34,7 +34,7 @@ owning types:
|
|||
- `tapPool tapPool` — TAP interface pool; owns its own lock.
|
||||
- `sessions sessionRegistry` — active guest session controllers; owns
|
||||
its own lock.
|
||||
- `listener`, `webListener`, `webServer`, `webURL`, `vmDNS` — networking.
|
||||
- `listener`, `vmDNS` — networking.
|
||||
- `vmCaps` — registered VM capability hooks.
|
||||
- `pullAndFlatten`, `finalizePulledRootfs`, `bundleFetch`,
|
||||
`requestHandler`, `guestWaitForSSH`, `guestDial`,
|
||||
|
|
@ -98,9 +98,3 @@ Only `internal/cli` imports this package. The surface is:
|
|||
|
||||
All other `*Daemon` methods are reached only through the RPC `dispatch`
|
||||
switch in `daemon.go` and are free to move/rename during refactoring.
|
||||
|
||||
## Web UI
|
||||
|
||||
The optional web UI served at `web_listen_addr` is experimental. It is
|
||||
enabled by default for local observability but is not considered a stable
|
||||
or supported interface. Set `web_listen_addr = ""` in config to disable.
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import (
|
|||
"fmt"
|
||||
"log/slog"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
|
|
@ -58,9 +57,6 @@ type Daemon struct {
|
|||
once sync.Once
|
||||
pid int
|
||||
listener net.Listener
|
||||
webListener net.Listener
|
||||
webServer *http.Server
|
||||
webURL string
|
||||
vmDNS *vmdns.Server
|
||||
vmCaps []vmCapability
|
||||
pullAndFlatten func(ctx context.Context, ref, cacheDir, destDir string) (imagepull.Metadata, error)
|
||||
|
|
@ -138,12 +134,6 @@ func (d *Daemon) Close() error {
|
|||
if d.listener != nil {
|
||||
_ = d.listener.Close()
|
||||
}
|
||||
if d.webServer != nil {
|
||||
_ = d.webServer.Close()
|
||||
}
|
||||
if d.webListener != nil {
|
||||
_ = d.webListener.Close()
|
||||
}
|
||||
err = errors.Join(d.clearVMDNSResolverRouting(context.Background()), d.stopVMDNS(), d.closeGuestSessionControllers(), d.store.Close())
|
||||
})
|
||||
return err
|
||||
|
|
@ -167,10 +157,6 @@ func (d *Daemon) Serve(ctx context.Context) error {
|
|||
if d.logger != nil {
|
||||
d.logger.Info("daemon serving", "socket", d.layout.SocketPath, "pid", d.pid)
|
||||
}
|
||||
if err := d.startWebServer(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
go d.backgroundLoop()
|
||||
|
||||
for {
|
||||
|
|
@ -274,7 +260,6 @@ func (d *Daemon) dispatch(ctx context.Context, req rpc.Request) rpc.Response {
|
|||
result, _ := rpc.NewResult(api.PingResult{
|
||||
Status: "ok",
|
||||
PID: d.pid,
|
||||
WebURL: d.webURL,
|
||||
Version: info.Version,
|
||||
Commit: info.Commit,
|
||||
BuiltAt: info.BuiltAt,
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ func TestRegisterImageRequiresKernel(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestDispatchPingIncludesBuildInfo(t *testing.T) {
|
||||
d := &Daemon{pid: 42, webURL: "http://127.0.0.1:7777"}
|
||||
d := &Daemon{pid: 42}
|
||||
|
||||
resp := d.dispatch(context.Background(), rpc.Request{Version: rpc.Version, Method: "ping"})
|
||||
if !resp.OK {
|
||||
|
|
@ -46,8 +46,8 @@ func TestDispatchPingIncludesBuildInfo(t *testing.T) {
|
|||
}
|
||||
|
||||
info := buildinfo.Current()
|
||||
if got.Status != "ok" || got.PID != 42 || got.WebURL != "http://127.0.0.1:7777" {
|
||||
t.Fatalf("PingResult = %+v, want status/pid/weburl populated", got)
|
||||
if got.Status != "ok" || got.PID != 42 {
|
||||
t.Fatalf("PingResult = %+v, want status/pid populated", got)
|
||||
}
|
||||
if got.Version != info.Version || got.Commit != info.Commit || got.BuiltAt != info.BuiltAt {
|
||||
t.Fatalf("PingResult build info = %+v, want %+v", got, info)
|
||||
|
|
|
|||
|
|
@ -1,63 +0,0 @@
|
|||
package daemon
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"banger/internal/api"
|
||||
"banger/internal/model"
|
||||
"banger/internal/system"
|
||||
)
|
||||
|
||||
func (d *Daemon) DashboardSummary(ctx context.Context) (api.DashboardSummary, error) {
|
||||
summary := api.DashboardSummary{
|
||||
GeneratedAt: model.Now(),
|
||||
Sudo: api.SudoStatus{
|
||||
Command: "sudo -v",
|
||||
},
|
||||
}
|
||||
if err := system.CheckSudo(ctx); err != nil {
|
||||
summary.Sudo.Error = err.Error()
|
||||
} else {
|
||||
summary.Sudo.Available = true
|
||||
}
|
||||
|
||||
if host, err := system.ReadHostResources(); err == nil {
|
||||
summary.Host.CPUCount = host.CPUCount
|
||||
summary.Host.TotalMemoryBytes = host.TotalMemoryBytes
|
||||
}
|
||||
if usage, err := system.ReadFilesystemUsage(d.layout.StateDir); err == nil {
|
||||
summary.Host.StateFilesystemTotalBytes = usage.TotalBytes
|
||||
summary.Host.StateFilesystemFreeBytes = usage.FreeBytes
|
||||
}
|
||||
|
||||
images, err := d.store.ListImages(ctx)
|
||||
if err != nil {
|
||||
return api.DashboardSummary{}, err
|
||||
}
|
||||
for _, image := range images {
|
||||
summary.Banger.ImageCount++
|
||||
if image.Managed {
|
||||
summary.Banger.ManagedImageCount++
|
||||
}
|
||||
}
|
||||
|
||||
vms, err := d.store.ListVMs(ctx)
|
||||
if err != nil {
|
||||
return api.DashboardSummary{}, err
|
||||
}
|
||||
for _, vm := range vms {
|
||||
summary.Banger.VMCount++
|
||||
summary.Banger.ConfiguredVCPUCount += vm.Spec.VCPUCount
|
||||
summary.Banger.ConfiguredMemoryBytes += int64(vm.Spec.MemoryMiB) * 1024 * 1024
|
||||
summary.Banger.ConfiguredDiskBytes += vm.Spec.WorkDiskSizeBytes
|
||||
summary.Banger.UsedSystemOverlayBytes += vm.Stats.SystemOverlayBytes
|
||||
summary.Banger.UsedWorkDiskBytes += vm.Stats.WorkDiskBytes
|
||||
if d.vmAlive(vm) {
|
||||
summary.Banger.RunningVMCount++
|
||||
summary.Banger.RunningCPUPercent += vm.Stats.CPUPercent
|
||||
summary.Banger.RunningRSSBytes += vm.Stats.RSSBytes
|
||||
summary.Banger.RunningVSZBytes += vm.Stats.VSZBytes
|
||||
}
|
||||
}
|
||||
return summary, nil
|
||||
}
|
||||
|
|
@ -1,9 +1,8 @@
|
|||
// Package daemon hosts the Banger daemon process.
|
||||
//
|
||||
// The daemon exposes a JSON-RPC endpoint over a Unix socket and, optionally,
|
||||
// an experimental local web UI. It owns VM lifecycle, image management,
|
||||
// guest sessions, host networking bootstrap, and state persistence via
|
||||
// internal/store.
|
||||
// The daemon exposes a JSON-RPC endpoint over a Unix socket. It owns VM
|
||||
// lifecycle, image management, guest sessions, host networking bootstrap,
|
||||
// and state persistence via internal/store.
|
||||
//
|
||||
// The package is organised into cohesive groups. Pure stateless helpers for
|
||||
// each group have been lifted into subpackages; orchestrator methods
|
||||
|
|
@ -68,11 +67,9 @@
|
|||
// Core (in this package):
|
||||
//
|
||||
// daemon.go Daemon struct, Open/Close/Serve, dispatch
|
||||
// dashboard.go dashboard metrics aggregation
|
||||
// doctor.go host diagnostics
|
||||
// logger.go slog configuration
|
||||
// runtime_assets.go paths to bundled companion binaries
|
||||
// web.go experimental local web UI server
|
||||
//
|
||||
// Lock ordering:
|
||||
//
|
||||
|
|
|
|||
|
|
@ -1,65 +0,0 @@
|
|||
package daemon
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"banger/internal/model"
|
||||
"banger/internal/paths"
|
||||
"banger/internal/webui"
|
||||
)
|
||||
|
||||
func (d *Daemon) startWebServer() error {
|
||||
listenAddr := strings.TrimSpace(d.config.WebListenAddr)
|
||||
if listenAddr == "" {
|
||||
d.webURL = ""
|
||||
return nil
|
||||
}
|
||||
listener, err := net.Listen("tcp", listenAddr)
|
||||
if err != nil {
|
||||
if d.logger != nil {
|
||||
d.logger.Error("web ui listen failed", "addr", listenAddr, "error", err.Error())
|
||||
}
|
||||
return fmt.Errorf("web ui listen on %s: %w", listenAddr, err)
|
||||
}
|
||||
d.webListener = listener
|
||||
d.webURL = "http://" + listener.Addr().String()
|
||||
d.webServer = &http.Server{
|
||||
Handler: webui.NewHandler(d),
|
||||
ReadHeaderTimeout: 5 * time.Second,
|
||||
}
|
||||
if d.logger != nil {
|
||||
d.logger.Info("web ui serving", "addr", listener.Addr().String(), "url", d.webURL)
|
||||
}
|
||||
go func() {
|
||||
err := d.webServer.Serve(listener)
|
||||
if err == nil || errors.Is(err, http.ErrServerClosed) {
|
||||
return
|
||||
}
|
||||
if d.logger != nil {
|
||||
d.logger.Error("web ui serve failed", "addr", listener.Addr().String(), "error", err.Error())
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Daemon) Layout() paths.Layout {
|
||||
return d.layout
|
||||
}
|
||||
|
||||
func (d *Daemon) Config() model.DaemonConfig {
|
||||
return d.config
|
||||
}
|
||||
|
||||
func (d *Daemon) ListVMs(ctx context.Context) ([]model.VMRecord, error) {
|
||||
return d.store.ListVMs(ctx)
|
||||
}
|
||||
|
||||
func (d *Daemon) ListImages(ctx context.Context) ([]model.Image, error) {
|
||||
return d.store.ListImages(ctx)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue