Files

72 lines
2.0 KiB
Go
Raw Permalink Normal View History

2026-08-23 20:39:22 +03:00
// Package dashboard implements the admin dashboard's HTTP surface: a
// server-rendered (html/template + htmx + Alpine.js) web UI giving full
// coverage of control-api's /api/v1/admin/* API. It never talks to
// internal/db directly and has no state of its own — every page and
// fragment is computed fresh, on each request, from control-api's API via
// the client in client.go.
package dashboard
import (
"html/template"
"log/slog"
"net/http"
"time"
)
type Config struct {
ControlAPIBaseURL string
ControlAPITimeout time.Duration
LastCompletedCount int
OverviewPollIntervalS int
// ControlAPIToken is the admin bearer token sent to control-api. Empty =
// no Authorization header.
ControlAPIToken string
// Username/Password are the single dashboard administrator's login. If
// either is empty, login is DISABLED (every page is open).
Username string
Password string
// SessionSecret is the HMAC key for session cookies; empty = random per
// process start (sessions are lost on restart).
SessionSecret string
// SessionTTL is the session lifetime (default 8h).
SessionTTL time.Duration
2026-08-23 20:39:22 +03:00
}
type Server struct {
CA *client
Cfg Config
tmpl *template.Template
Log *slog.Logger
auth *authState
2026-08-23 20:39:22 +03:00
}
func New(cfg Config, log *slog.Logger) (*Server, error) {
tmpl, err := parseTemplates()
if err != nil {
return nil, err
}
ca := newClient(cfg.ControlAPIBaseURL, cfg.ControlAPITimeout)
ca.token = cfg.ControlAPIToken
2026-08-23 20:39:22 +03:00
return &Server{
CA: ca,
2026-08-23 20:39:22 +03:00
Cfg: cfg,
tmpl: tmpl,
Log: log,
auth: newAuthState(cfg, log),
2026-08-23 20:39:22 +03:00
}, nil
}
func (s *Server) Handler() http.Handler {
mux := http.NewServeMux()
s.routes(mux)
// Never log cookies, Authorization or form bodies here.
return loggingMiddleware(s.Log, s.authMiddleware(mux))
2026-08-23 20:39:22 +03:00
}
func loggingMiddleware(log *slog.Logger, next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
next.ServeHTTP(w, r)
log.Debug("request", "method", r.Method, "path", r.URL.Path)
})
}