Add a localhost-only web console so VM and image management no longer depends on the CLI for every inspection and lifecycle action. Wire bangerd up to a configurable web listener, expose dashboard and async image-build state through the daemon, and serve CSRF-protected HTML pages with host-path picking, VM/image detail views, logs, ports, and progress polling for long-running operations. Keep the browser path aligned with the existing sudo and host-owned artifact model: surface sudo readiness, print the web URL in daemon status, and document the new workflow. Polish the UI with resource usage cards, clearer clickable affordances, cancel paths, confirmation prompts, image-name links, and HTTP port links. Validation: GOCACHE=/tmp/banger-gocache go test ./...
65 lines
1.4 KiB
Go
65 lines
1.4 KiB
Go
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)
|
|
}
|