175 lines
No EOL
5.7 KiB
Go
175 lines
No EOL
5.7 KiB
Go
package api
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/rs/zerolog/log"
|
|
|
|
"github.com/traefik/traefik-gui/backend/internal/api/handlers"
|
|
"github.com/traefik/traefik-gui/backend/internal/api/middleware"
|
|
"github.com/traefik/traefik-gui/backend/internal/config"
|
|
"github.com/traefik/traefik-gui/backend/internal/config/file"
|
|
"github.com/traefik/traefik-gui/backend/internal/database"
|
|
"github.com/traefik/traefik-gui/backend/internal/database/repositories"
|
|
"github.com/traefik/traefik-gui/backend/internal/traefik"
|
|
)
|
|
|
|
type Server struct {
|
|
httpServer *http.Server
|
|
engine *gin.Engine
|
|
config *config.Config
|
|
db *database.DB
|
|
traefik traefik.TraefikAPI
|
|
}
|
|
|
|
func NewServer(cfg *config.Config, db *database.DB, traefikAPI traefik.TraefikAPI) *Server {
|
|
if !cfg.DevMode {
|
|
gin.SetMode(gin.ReleaseMode)
|
|
}
|
|
|
|
engine := gin.New()
|
|
if len(cfg.TrustedProxies) > 0 {
|
|
engine.SetTrustedProxies(cfg.TrustedProxies)
|
|
}
|
|
|
|
userRepo := repositories.NewUserRepository(db.DB)
|
|
sessionRepo := repositories.NewSessionRepository(db.DB)
|
|
|
|
authMiddleware := middleware.NewAuthMiddleware(sessionRepo, userRepo)
|
|
|
|
healthHandler := handlers.NewHealthHandler()
|
|
authHandler := handlers.NewAuthHandler(
|
|
userRepo,
|
|
sessionRepo,
|
|
cfg.SessionSecret,
|
|
"", // cookie domain
|
|
!cfg.DevMode, // cookie secure - true in prod
|
|
)
|
|
// Legacy adapter for old ConfigHandler (which expects TraefikClient returning models)
|
|
legacyTraefik := traefik.NewLegacyAdapter(traefikAPI)
|
|
configHandler := handlers.NewConfigHandler(legacyTraefik)
|
|
traefikHandler := handlers.NewTraefikAPIHandler(traefikAPI)
|
|
|
|
// File-provider service (Phase 2)
|
|
auditRepo := repositories.NewAuditRepository(db.DB)
|
|
fileSvc, err := file.NewService(cfg.ConfigDir, db.DB, auditRepo)
|
|
if err != nil {
|
|
log.Fatal().Err(err).Str("configDir", cfg.ConfigDir).Msg("Failed to init file service")
|
|
}
|
|
fileHandler := handlers.NewFileConfigHandler(fileSvc, auditRepo, userRepo)
|
|
// Enhance health ready to check config dir writable
|
|
healthHandler.SetConfigDir(cfg.ConfigDir)
|
|
|
|
// Middleware
|
|
engine.Use(middleware.LoggingMiddleware())
|
|
engine.Use(middleware.SecurityHeadersMiddleware())
|
|
engine.Use(middleware.CORSMiddleware(cfg.CORSOrigin))
|
|
engine.Use(gin.Recovery())
|
|
|
|
// Health endpoints (no auth)
|
|
engine.GET("/api/health", healthHandler.Health)
|
|
engine.GET("/api/ready", healthHandler.Ready)
|
|
|
|
// Auth endpoints
|
|
authGroup := engine.Group("/api/auth")
|
|
{
|
|
authGroup.POST("/login", authHandler.Login)
|
|
authGroup.POST("/logout", authMiddleware.RequireAuth(), authMiddleware.RequireCSRF(), authHandler.Logout)
|
|
authGroup.GET("/me", authMiddleware.RequireAuth(), authHandler.Me)
|
|
authGroup.GET("/csrf", authMiddleware.RequireAuth(), authHandler.GetCSRF)
|
|
}
|
|
|
|
// Protected API endpoints
|
|
apiGroup := engine.Group("/api")
|
|
apiGroup.Use(authMiddleware.RequireAuth())
|
|
apiGroup.Use(authMiddleware.RequireCSRF())
|
|
{
|
|
// Config endpoints (file-provider, Phase 2)
|
|
configGroup := apiGroup.Group("/config")
|
|
{
|
|
// File-provider management (secure, atomic, validated)
|
|
configGroup.GET("/files", fileHandler.ListFiles)
|
|
configGroup.GET("/files/:name", fileHandler.GetFile)
|
|
configGroup.GET("/history", fileHandler.History)
|
|
configGroup.POST("/preview", fileHandler.Preview)
|
|
configGroup.POST("/validate", fileHandler.Validate)
|
|
configGroup.POST("/apply", fileHandler.Apply)
|
|
configGroup.POST("/rollback", fileHandler.Rollback)
|
|
|
|
// Legacy mock endpoints (read-only dashboard, kept for compatibility)
|
|
configGroup.GET("/routers", configHandler.ListRouters)
|
|
configGroup.GET("/routers/:id", configHandler.GetRouter)
|
|
configGroup.POST("/routers", configHandler.CreateRouter)
|
|
configGroup.PUT("/routers/:id", configHandler.UpdateRouter)
|
|
configGroup.DELETE("/routers/:id", configHandler.DeleteRouter)
|
|
|
|
configGroup.GET("/services", configHandler.ListServices)
|
|
configGroup.GET("/middlewares", configHandler.ListMiddlewares)
|
|
configGroup.GET("/certificates", configHandler.ListCertificates)
|
|
configGroup.GET("/entrypoints", configHandler.ListEntryPoints)
|
|
}
|
|
|
|
// Traefik status endpoints (read-only, new typed client with cache, viewer+)
|
|
traefikGroup := engine.Group("/api/traefik")
|
|
traefikGroup.Use(authMiddleware.RequireAuth())
|
|
{
|
|
traefikGroup.GET("/health", traefikHandler.Health)
|
|
traefikGroup.GET("/overview", traefikHandler.Overview)
|
|
traefikGroup.GET("/entrypoints", traefikHandler.Entrypoints)
|
|
traefikGroup.GET("/routers", traefikHandler.Routers)
|
|
traefikGroup.GET("/services", traefikHandler.Services)
|
|
traefikGroup.GET("/middlewares", traefikHandler.Middlewares)
|
|
traefikGroup.GET("/providers", traefikHandler.Providers)
|
|
traefikGroup.GET("/certificates", traefikHandler.Certificates)
|
|
}
|
|
}
|
|
|
|
// Serve embedded frontend in production
|
|
if !cfg.DevMode {
|
|
// TODO: Embed frontend assets
|
|
engine.NoRoute(func(c *gin.Context) {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
|
|
})
|
|
}
|
|
|
|
srv := &Server{
|
|
engine: engine,
|
|
config: cfg,
|
|
db: db,
|
|
traefik: traefikAPI,
|
|
httpServer: &http.Server{
|
|
Addr: cfg.Addr,
|
|
Handler: engine,
|
|
ReadTimeout: 15 * time.Second,
|
|
WriteTimeout: 15 * time.Second,
|
|
IdleTimeout: 60 * time.Second,
|
|
},
|
|
}
|
|
|
|
return srv
|
|
}
|
|
|
|
func (s *Server) Start(ctx context.Context) error {
|
|
log.Info().Str("addr", s.config.Addr).Msg("Starting HTTP server")
|
|
|
|
go func() {
|
|
<-ctx.Done()
|
|
log.Info().Msg("Shutting down HTTP server")
|
|
shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
defer cancel()
|
|
s.httpServer.Shutdown(shutdownCtx)
|
|
}()
|
|
|
|
if err := s.httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *Server) Shutdown(ctx context.Context) error {
|
|
return s.httpServer.Shutdown(ctx)
|
|
} |