Skip to content

Commit

Permalink
support for headless mode as mentioned in #800 (#805)
Browse files Browse the repository at this point in the history
  • Loading branch information
kriyanshii authored Feb 1, 2025
1 parent e747cef commit 4766a18
Show file tree
Hide file tree
Showing 6 changed files with 42 additions and 20 deletions.
7 changes: 6 additions & 1 deletion internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ type Config struct {
// Deprecated: Use UI.NavbarTitle instead
NavbarTitle string `mapstructure:"navbarTitle"`
// Deprecated: Use UI.MaxDashboardPageLimit instead
MaxDashboardPageLimit int `mapstructure:"maxDashboardPageLimit"`
MaxDashboardPageLimit int `mapstructure:"maxDashboardPageLimit"`
Headless bool `mapstructure:"headless"`
// Legacy fields for backward compatibility - End

// Other settings
Expand Down Expand Up @@ -112,6 +113,7 @@ type UI struct {
NavbarColor string `mapstructure:"navbarColor"`
NavbarTitle string `mapstructure:"navbarTitle"`
MaxDashboardPageLimit int `mapstructure:"maxDashboardPageLimit"`
Headless bool `mapstructure:"headless"`
}

// RemoteNode represents a remote node configuration
Expand Down Expand Up @@ -206,6 +208,9 @@ func (c *Config) migrateUISettings() {
if c.MaxDashboardPageLimit > 0 {
c.UI.MaxDashboardPageLimit = c.MaxDashboardPageLimit
}
if c.Headless {
c.UI.Headless = c.Headless
}
}

func (c *Config) cleanBasePath() {
Expand Down
1 change: 1 addition & 0 deletions internal/config/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ func (l *ConfigLoader) bindEnvironmentVariables() {
l.bindEnv("ui.logEncodingCharset", "UI_LOG_ENCODING_CHARSET")
l.bindEnv("ui.navbarColor", "UI_NAVBAR_COLOR")
l.bindEnv("ui.navbarTitle", "UI_NAVBAR_TITLE")
l.bindEnv("ui.headless", "UI_HEADLESS")

// UI configurations (legacy)
l.bindEnv("ui.maxDashboardPageLimit", "MAX_DASHBOARD_PAGE_LIMIT")
Expand Down
1 change: 1 addition & 0 deletions internal/frontend/frontend.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ func New(cfg *config.Config, cli client.Client) *server.Server {
APIBaseURL: cfg.APIBaseURL,
TimeZone: cfg.TZ,
RemoteNodes: remoteNodes,
Headless: cfg.UI.Headless,
}

if cfg.Auth.Token.Enabled {
Expand Down
12 changes: 12 additions & 0 deletions internal/frontend/server/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,23 @@ import (
"context"
"net/http"

"github.com/dagu-org/dagu/internal/logger"
"github.com/go-chi/chi/v5"
)

func (svr *Server) defaultRoutes(ctx context.Context, r *chi.Mux) *chi.Mux {
// Always allow API routes to work
if svr.headless {
logger.Info(ctx, "Headless mode enabled: UI is disabled, but API remains active")

// Only register API routes, skip Web UI routes
return r
}

// Serve assets (optional, remove if not needed)
r.Get("/assets/*", svr.handleGetAssets())

// Serve UI pages (disable when headless)
r.Get("/*", svr.handleRequest(ctx))

return r
Expand Down
34 changes: 15 additions & 19 deletions internal/frontend/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (
"github.com/dagu-org/dagu/internal/frontend/gen/restapi"
"github.com/dagu-org/dagu/internal/logger"
"github.com/go-openapi/loads"
flags "github.com/jessevdk/go-flags"
"github.com/jessevdk/go-flags"

"github.com/dagu-org/dagu/internal/frontend/gen/restapi/operations"
pkgmiddleware "github.com/dagu-org/dagu/internal/frontend/middleware"
Expand All @@ -31,6 +31,7 @@ type Server struct {
server *restapi.Server
handlers []Handler
assets fs.FS
headless bool
}

type NewServerArgs struct {
Expand All @@ -42,7 +43,7 @@ type NewServerArgs struct {
Handlers []Handler
AssetsFS fs.FS

// Configuration for the frontend
Headless bool
NavbarColor string
NavbarTitle string
BasePath string
Expand Down Expand Up @@ -74,6 +75,7 @@ func New(params NewServerArgs) *Server {
tls: params.TLS,
handlers: params.Handlers,
assets: params.AssetsFS,
headless: params.Headless, // Assign headless mode flag
funcsConfig: funcsConfig{
NavbarColor: params.NavbarColor,
NavbarTitle: params.NavbarTitle,
Expand All @@ -98,11 +100,14 @@ func (svr *Server) Shutdown(ctx context.Context) {

func (svr *Server) Serve(ctx context.Context) (err error) {
loggerInstance := logger.FromContext(ctx)

// Setup middleware & routes
middlewareOptions := &pkgmiddleware.Options{
Handler: svr.defaultRoutes(ctx, chi.NewRouter()),
Handler: svr.defaultRoutes(ctx, chi.NewRouter()), // API remains active
BasePath: svr.funcsConfig.BasePath,
Logger: loggerInstance,
}

if svr.authToken != nil {
middlewareOptions.AuthToken = &pkgmiddleware.AuthToken{
Token: svr.authToken.Token,
Expand All @@ -116,6 +121,7 @@ func (svr *Server) Serve(ctx context.Context) (err error) {
}
pkgmiddleware.Setup(middlewareOptions)

// Load API spec (Always required)
swaggerSpec, err := loads.Analyzed(restapi.SwaggerJSON, "")
if err != nil {
logger.Error(ctx, "Failed to load API spec", "err", err)
Expand All @@ -124,35 +130,28 @@ func (svr *Server) Serve(ctx context.Context) (err error) {
api := operations.NewDaguAPI(swaggerSpec)
api.Logger = loggerInstance.Infof
for _, h := range svr.handlers {
h.Configure(api)
h.Configure(api) // Always configure API handlers
}

// Start API server
svr.server = restapi.NewServer(api)
defer svr.Shutdown(ctx)

svr.server.Host = svr.host
svr.server.Port = svr.port
svr.server.ConfigureAPI()

// Server run context
// Listen for system signals (CTRL+C, termination)
serverCtx, serverStopCtx := context.WithCancel(ctx)

// Listen for syscall signals for process to interrupt/quit
sig := make(chan os.Signal, 1)
signal.Notify(
sig, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT,
)
signal.Notify(sig, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
go func() {
<-sig

// Trigger graceful shutdown
err := svr.server.Shutdown()
if err != nil {
logger.Error(ctx, "Server shutdown", "err", err)
}
_ = svr.server.Shutdown()
serverStopCtx()
}()

// Run with or without TLS
if svr.tls != nil {
svr.server.TLSCertificate = flags.Filename(svr.tls.CertFile)
svr.server.TLSCertificateKey = flags.Filename(svr.tls.KeyFile)
Expand All @@ -167,10 +166,7 @@ func (svr *Server) Serve(ctx context.Context) (err error) {
logger.Error(ctx, "Server error", "err", err)
}

// Wait for server context to be stopped
<-serverCtx.Done()

logger.Info(ctx, "Server stopped")

return nil
}
7 changes: 7 additions & 0 deletions internal/frontend/server/templates.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@ var (
)

func (srv *Server) useTemplate(ctx context.Context, layout string, name string) func(http.ResponseWriter, any) {
// Skip template rendering if headless
if srv.headless {
return func(w http.ResponseWriter, _ any) {
http.Error(w, "Web UI is disabled in headless mode", http.StatusForbidden)
}
}

files := append(baseTemplates(), filepath.Join(templatePath, layout))
tmpl, err := template.New(name).Funcs(
defaultFunctions(srv.funcsConfig)).ParseFS(srv.assets, files...,
Expand Down

1 comment on commit 4766a18

@ghansham
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work..

Please sign in to comment.