From 453f0d2025235fd37f345881ef4be23a0009bc46 Mon Sep 17 00:00:00 2001 From: Chris Hoage Date: Sat, 16 Nov 2024 10:56:21 -0800 Subject: [PATCH 1/2] feat: Support configurable base path for server --- docs/source/config.rst | 2 ++ internal/config/config.go | 3 +++ internal/frontend/frontend.go | 1 + internal/frontend/middleware/global.go | 15 ++++++++++++--- internal/frontend/server/server.go | 7 +++++-- internal/frontend/server/templates.go | 7 ++++++- internal/frontend/templates/base.gohtml | 3 ++- ui/src/App.tsx | 2 +- ui/src/contexts/ConfigContext.tsx | 1 + ui/webpack.prod.js | 2 +- 10 files changed, 34 insertions(+), 9 deletions(-) diff --git a/docs/source/config.rst b/docs/source/config.rst index 1675f1132..fc83ef137 100644 --- a/docs/source/config.rst +++ b/docs/source/config.rst @@ -25,6 +25,7 @@ Server Configuration ~~~~~~~~~~~~~~~~~~ - ``DAGU_HOST`` (``127.0.0.1``): Server binding host - ``DAGU_PORT`` (``8080``): Server binding port +- ``DAGU_BASE_PATH`` (``""``): Base path to serve the application - ``DAGU_TZ`` (``""``): Server timezone (default: system timezone) - ``DAGU_CERT_FILE``: SSL certificate file path - ``DAGU_KEY_FILE``: SSL key file path @@ -59,6 +60,7 @@ Create ``admin.yaml`` in ``$HOME/.config/dagu/`` to override default settings. B # Server Configuration host: "127.0.0.1" # Web UI hostname port: 8080 # Web UI port + basePath: "" # Base path to serve the application tz: "Asia/Tokyo" # Timezone (e.g., "America/New_York") # Directory Configuration diff --git a/internal/config/config.go b/internal/config/config.go index 8e9fbe11c..6f13fd9cd 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -52,6 +52,7 @@ type Config struct { IsAuthToken bool // Enable auth token for API AuthToken string // Auth token for API LatestStatusToday bool // Show latest status today or the latest status + BasePath string // Base path for the server APIBaseURL string // Base URL for API Debug bool // Enable debug mode (verbose logging) LogFormat string // Log format @@ -168,6 +169,7 @@ func setupViper() error { viper.SetDefault("host", "127.0.0.1") viper.SetDefault("port", "8080") viper.SetDefault("navbarTitle", "Dagu") + viper.SetDefault("basePath", "") viper.SetDefault("apiBaseURL", "/api/v1") // Set executable path @@ -198,6 +200,7 @@ func bindEnvs() { _ = viper.BindEnv("logEncodingCharset", "DAGU_LOG_ENCODING_CHARSET") _ = viper.BindEnv("navbarColor", "DAGU_NAVBAR_COLOR") _ = viper.BindEnv("navbarTitle", "DAGU_NAVBAR_TITLE") + _ = viper.BindEnv("basePath", "DAGU_BASE_PATH") _ = viper.BindEnv("apiBaseURL", "DAGU_API_BASE_URL") _ = viper.BindEnv("tz", "DAGU_TZ") diff --git a/internal/frontend/frontend.go b/internal/frontend/frontend.go index 0e78ea06a..4b078eff8 100644 --- a/internal/frontend/frontend.go +++ b/internal/frontend/frontend.go @@ -42,6 +42,7 @@ func New(cfg *config.Config, lg logger.Logger, cli client.Client) *server.Server AssetsFS: assetsFS, NavbarColor: cfg.NavbarColor, NavbarTitle: cfg.NavbarTitle, + BasePath: cfg.BasePath, APIBaseURL: cfg.APIBaseURL, TimeZone: cfg.TZ, } diff --git a/internal/frontend/middleware/global.go b/internal/frontend/middleware/global.go index ef74d5ebd..218f619c5 100644 --- a/internal/frontend/middleware/global.go +++ b/internal/frontend/middleware/global.go @@ -18,6 +18,7 @@ package middleware import ( "context" "net/http" + "path" "strings" "github.com/dagu-org/dagu/internal/logger" @@ -72,6 +73,7 @@ var ( authBasic *AuthBasic authToken *AuthToken appLogger logger.Logger + basePath string ) type Options struct { @@ -79,6 +81,7 @@ type Options struct { AuthBasic *AuthBasic AuthToken *AuthToken Logger logger.Logger + BasePath string } type AuthBasic struct { @@ -95,15 +98,21 @@ func Setup(opts *Options) { authBasic = opts.AuthBasic authToken = opts.AuthToken appLogger = opts.Logger + basePath = opts.BasePath } func prefixChecker(next http.Handler) http.Handler { return http.HandlerFunc( func(w http.ResponseWriter, r *http.Request) { - if strings.HasPrefix(r.URL.Path, "/api") { - next.ServeHTTP(w, r) + if basePath != "" && r.URL.Path == "/" { + http.Redirect(w, r, basePath, http.StatusSeeOther) + return + } + + if strings.HasPrefix(r.URL.Path, path.Join(basePath, "/api")) { + http.StripPrefix(basePath, next).ServeHTTP(w, r) } else { - defaultHandler.ServeHTTP(w, r) + http.StripPrefix(basePath, defaultHandler).ServeHTTP(w, r) } }) } diff --git a/internal/frontend/server/server.go b/internal/frontend/server/server.go index b8a8203c0..fb5e17683 100644 --- a/internal/frontend/server/server.go +++ b/internal/frontend/server/server.go @@ -62,6 +62,7 @@ type NewServerArgs struct { // Configuration for the frontend NavbarColor string NavbarTitle string + BasePath string APIBaseURL string TimeZone string } @@ -92,6 +93,7 @@ func New(params NewServerArgs) *Server { funcsConfig: funcsConfig{ NavbarColor: params.NavbarColor, NavbarTitle: params.NavbarTitle, + BasePath: params.BasePath, APIBaseURL: params.APIBaseURL, TZ: params.TimeZone, }, @@ -110,8 +112,9 @@ func (svr *Server) Shutdown() { func (svr *Server) Serve(ctx context.Context) (err error) { middlewareOptions := &pkgmiddleware.Options{ - Handler: svr.defaultRoutes(chi.NewRouter()), - Logger: svr.logger, + Handler: svr.defaultRoutes(chi.NewRouter()), + Logger: svr.logger, + BasePath: svr.funcsConfig.BasePath, } if svr.authToken != nil { middlewareOptions.AuthToken = &pkgmiddleware.AuthToken{ diff --git a/internal/frontend/server/templates.go b/internal/frontend/server/templates.go index 29fd429db..cf00d1ce2 100644 --- a/internal/frontend/server/templates.go +++ b/internal/frontend/server/templates.go @@ -19,6 +19,7 @@ import ( "bytes" "io" "net/http" + "path" "path/filepath" "text/template" @@ -56,6 +57,7 @@ func (srv *Server) useTemplate( type funcsConfig struct { NavbarColor string NavbarTitle string + BasePath string APIBaseURL string TZ string } @@ -78,8 +80,11 @@ func defaultFunctions(cfg funcsConfig) template.FuncMap { "navbarTitle": func() string { return cfg.NavbarTitle }, + "basePath": func() string { + return cfg.BasePath + }, "apiURL": func() string { - return cfg.APIBaseURL + return path.Join(cfg.BasePath, cfg.APIBaseURL) }, "tz": func() string { return cfg.TZ diff --git a/internal/frontend/templates/base.gohtml b/internal/frontend/templates/base.gohtml index e309cf2fe..c8eb9ab2d 100644 --- a/internal/frontend/templates/base.gohtml +++ b/internal/frontend/templates/base.gohtml @@ -10,6 +10,7 @@ function getConfig() { return { apiURL: "{{ apiURL }}", + basePath: "{{ basePath }}", title: "{{ navbarTitle }}", navbarColor: "{{ navbarColor }}", version: "{{ version }}", @@ -17,7 +18,7 @@ }; } - + {{template "content" .}} diff --git a/ui/src/App.tsx b/ui/src/App.tsx index e70afb450..afe94ce4f 100644 --- a/ui/src/App.tsx +++ b/ui/src/App.tsx @@ -37,7 +37,7 @@ function App({ config }: Props) { > - + } /> diff --git a/ui/src/contexts/ConfigContext.tsx b/ui/src/contexts/ConfigContext.tsx index e1b360efb..c59c8c68f 100644 --- a/ui/src/contexts/ConfigContext.tsx +++ b/ui/src/contexts/ConfigContext.tsx @@ -2,6 +2,7 @@ import { createContext, useContext } from 'react'; export type Config = { apiURL: string; + basePath: string; title: string; navbarColor: string; tz: string; diff --git a/ui/webpack.prod.js b/ui/webpack.prod.js index 37329c574..a4a381c9f 100644 --- a/ui/webpack.prod.js +++ b/ui/webpack.prod.js @@ -21,7 +21,7 @@ module.exports = merge(common, { output: { filename: 'bundle.js', path: path.resolve(__dirname, 'dist'), - publicPath: '/assets/', + publicPath: 'auto', clean: true, }, }); From cd80c4351546445b7029dcc40ad0ff5767680ee4 Mon Sep 17 00:00:00 2001 From: Chris Hoage Date: Sun, 17 Nov 2024 10:21:41 -0800 Subject: [PATCH 2/2] fixup! feat: Support configurable base path for server --- internal/config/config.go | 13 +++++++++++++ internal/frontend/middleware/global.go | 13 +++++++------ 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/internal/config/config.go b/internal/config/config.go index 6f13fd9cd..398963691 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -19,6 +19,7 @@ import ( "fmt" "log" "os" + "path" "path/filepath" "strconv" "strings" @@ -119,6 +120,18 @@ func Load() (*Config, error) { cfg.Location = time.Local } + if cfg.BasePath != "" { + cfg.BasePath = path.Clean(cfg.BasePath) + + if !path.IsAbs(cfg.BasePath) { + cfg.BasePath = path.Join("/", cfg.BasePath) + } + + if cfg.BasePath == "/" { + cfg.BasePath = "" + } + } + return &cfg, nil } diff --git a/internal/frontend/middleware/global.go b/internal/frontend/middleware/global.go index 218f619c5..dc4df0fb8 100644 --- a/internal/frontend/middleware/global.go +++ b/internal/frontend/middleware/global.go @@ -18,7 +18,6 @@ package middleware import ( "context" "net/http" - "path" "strings" "github.com/dagu-org/dagu/internal/logger" @@ -109,11 +108,13 @@ func prefixChecker(next http.Handler) http.Handler { return } - if strings.HasPrefix(r.URL.Path, path.Join(basePath, "/api")) { - http.StripPrefix(basePath, next).ServeHTTP(w, r) - } else { - http.StripPrefix(basePath, defaultHandler).ServeHTTP(w, r) - } + http.StripPrefix(basePath, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if strings.HasPrefix(r.URL.Path, "/api") { + next.ServeHTTP(w, r) + } else { + defaultHandler.ServeHTTP(w, r) + } + })).ServeHTTP(w, r) }) }