1
0
mirror of https://github.com/amir20/dozzle.git synced 2025-12-21 13:23:07 +01:00
Files
dozzle/internal/web/routes.go
2025-02-03 12:42:09 -08:00

154 lines
4.0 KiB
Go

package web
import (
"io/fs"
"time"
"net/http"
"strings"
"github.com/amir20/dozzle/internal/auth"
"github.com/amir20/dozzle/internal/container"
docker_support "github.com/amir20/dozzle/internal/support/docker"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
"github.com/rs/zerolog/log"
)
type AuthProvider string
const (
NONE AuthProvider = "none"
SIMPLE AuthProvider = "simple"
FORWARD_PROXY AuthProvider = "forward-proxy"
)
// Config is a struct for configuring the web service
type Config struct {
Base string
Addr string
Version string
Hostname string
NoAnalytics bool
Dev bool
Authorization Authorization
EnableActions bool
Filter container.ContainerFilter
}
type Authorization struct {
Provider AuthProvider
Authorizer Authorizer
TTL time.Duration
}
type Authorizer interface {
AuthMiddleware(http.Handler) http.Handler
CreateToken(string, string) (string, error)
}
type handler struct {
content fs.FS
config *Config
multiHostService *docker_support.MultiHostService
}
type MultiHostService = docker_support.MultiHostService
type ContainerFilter = docker_support.ContainerFilter
func CreateServer(multiHostService *MultiHostService, content fs.FS, config Config) *http.Server {
handler := &handler{
content: content,
config: &config,
multiHostService: multiHostService,
}
return &http.Server{Addr: config.Addr, Handler: createRouter(handler)}
}
var fileServer http.Handler
func createRouter(h *handler) *chi.Mux {
fileServer = http.FileServer(http.FS(h.content))
base := h.config.Base
r := chi.NewRouter()
if !h.config.Dev {
r.Use(cspHeaders)
}
if h.config.Authorization.Provider != NONE && h.config.Authorization.Authorizer == nil {
log.Fatal().Msg("Authorization provider is set but no authorizer is provided")
}
r.Route(base, func(r chi.Router) {
if h.config.Authorization.Provider != NONE {
r.Use(h.config.Authorization.Authorizer.AuthMiddleware)
}
r.Route("/api", func(r chi.Router) {
// Authenticated routes
r.Group(func(r chi.Router) {
if h.config.Authorization.Provider != NONE {
r.Use(auth.RequireAuthentication)
}
r.Get("/hosts/{host}/containers/{id}/logs/stream", h.streamContainerLogs)
r.Get("/hosts/{host}/logs/stream", h.streamHostLogs)
r.Get("/hosts/{host}/containers/{id}/logs", h.fetchLogsBetweenDates)
r.Get("/hosts/{host}/logs/mergedStream/{ids}", h.streamLogsMerged)
r.Get("/containers/{hostIds}/download", h.downloadLogs) // formatted as host:container,host:container
r.Get("/stacks/{stack}/logs/stream", h.streamStackLogs)
r.Get("/services/{service}/logs/stream", h.streamServiceLogs)
r.Get("/groups/{group}/logs/stream", h.streamGroupedLogs)
r.Get("/events/stream", h.streamEvents)
if h.config.EnableActions {
r.Post("/hosts/{host}/containers/{id}/actions/{action}", h.containerActions)
}
r.Get("/releases", h.releases)
r.Get("/profile/avatar", h.avatar)
r.Patch("/profile", h.updateProfile)
r.Get("/version", h.version)
if log.Debug().Enabled() {
r.Get("/debug/store", h.debugStore)
}
})
// Public API routes
if h.config.Authorization.Provider == SIMPLE {
r.Post("/token", h.createToken)
r.Delete("/token", h.deleteToken)
}
})
r.Get("/healthcheck", h.healthcheck)
defaultHandler := http.StripPrefix(strings.Replace(base+"/", "//", "/", 1), http.HandlerFunc(h.index))
r.Get("/*", func(w http.ResponseWriter, req *http.Request) {
defaultHandler.ServeHTTP(w, req)
})
})
if base != "/" {
r.Get(base, func(w http.ResponseWriter, req *http.Request) {
http.Redirect(w, req, base+"/", http.StatusMovedPermanently)
})
}
if log.Debug().Enabled() {
r.Mount("/debug", middleware.Profiler())
}
return r
}
func hostKey(r *http.Request) string {
host := chi.URLParam(r, "host")
if host == "" {
log.Fatal().Str("url", r.URL.String()).Msg("Host parameter not found in the URL path")
}
return host
}