Configurable API & Dashboard base path
Co-authored-by: Kevin Pollet <pollet.kevin@gmail.com>
This commit is contained in:
parent
090db6d4b0
commit
0ec12c7aa7
12 changed files with 253 additions and 50 deletions
|
@ -1,36 +1,88 @@
|
|||
package dashboard
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/traefik/traefik/v3/webui"
|
||||
)
|
||||
|
||||
type indexTemplateData struct {
|
||||
APIUrl string
|
||||
}
|
||||
|
||||
// Handler expose dashboard routes.
|
||||
type Handler struct {
|
||||
BasePath string
|
||||
|
||||
assets fs.FS // optional assets, to override the webui.FS default
|
||||
}
|
||||
|
||||
func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
assets := h.assets
|
||||
if assets == nil {
|
||||
assets = webui.FS
|
||||
}
|
||||
|
||||
// allow iframes from traefik domains only
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/frame-src
|
||||
w.Header().Set("Content-Security-Policy", "frame-src 'self' https://traefik.io https://*.traefik.io;")
|
||||
|
||||
// The content type must be guessed by the file server.
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options
|
||||
w.Header().Del("Content-Type")
|
||||
|
||||
if r.RequestURI == "/" {
|
||||
indexTemplate, err := template.ParseFS(assets, "index.html")
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Unable to parse index template")
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
apiPath := strings.TrimSuffix(h.BasePath, "/") + "/api/"
|
||||
if err = indexTemplate.Execute(w, indexTemplateData{APIUrl: apiPath}); err != nil {
|
||||
log.Error().Err(err).Msg("Unable to render index template")
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
http.FileServerFS(assets).ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
// Append adds dashboard routes on the given router, optionally using the given
|
||||
// assets (or webui.FS otherwise).
|
||||
func Append(router *mux.Router, customAssets fs.FS) {
|
||||
func Append(router *mux.Router, basePath string, customAssets fs.FS) error {
|
||||
assets := customAssets
|
||||
if assets == nil {
|
||||
assets = webui.FS
|
||||
}
|
||||
|
||||
indexTemplate, err := template.ParseFS(assets, "index.html")
|
||||
if err != nil {
|
||||
return fmt.Errorf("parsing index template: %w", err)
|
||||
}
|
||||
|
||||
dashboardPath := strings.TrimSuffix(basePath, "/") + "/dashboard/"
|
||||
|
||||
// Expose dashboard
|
||||
router.Methods(http.MethodGet).
|
||||
Path("/").
|
||||
Path(basePath).
|
||||
HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
|
||||
prefix := strings.TrimSuffix(req.Header.Get("X-Forwarded-Prefix"), "/")
|
||||
http.Redirect(resp, req, prefix+"/dashboard/", http.StatusFound)
|
||||
http.Redirect(resp, req, prefix+dashboardPath, http.StatusFound)
|
||||
})
|
||||
|
||||
router.Methods(http.MethodGet).
|
||||
PathPrefix("/dashboard/").
|
||||
Path(dashboardPath).
|
||||
HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// allow iframes from our domains only
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/frame-src
|
||||
|
@ -40,22 +92,26 @@ func Append(router *mux.Router, customAssets fs.FS) {
|
|||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options
|
||||
w.Header().Del("Content-Type")
|
||||
|
||||
http.StripPrefix("/dashboard/", http.FileServerFS(assets)).ServeHTTP(w, r)
|
||||
apiPath := strings.TrimSuffix(basePath, "/") + "/api/"
|
||||
if err = indexTemplate.Execute(w, indexTemplateData{APIUrl: apiPath}); err != nil {
|
||||
log.Error().Err(err).Msg("Unable to render index template")
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (g Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
assets := g.assets
|
||||
if assets == nil {
|
||||
assets = webui.FS
|
||||
}
|
||||
// allow iframes from our domains only
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/frame-src
|
||||
w.Header().Set("Content-Security-Policy", "frame-src 'self' https://traefik.io https://*.traefik.io;")
|
||||
|
||||
// The content type must be guessed by the file server.
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options
|
||||
w.Header().Del("Content-Type")
|
||||
|
||||
http.FileServerFS(assets).ServeHTTP(w, r)
|
||||
|
||||
router.Methods(http.MethodGet).
|
||||
PathPrefix(dashboardPath).
|
||||
HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// allow iframes from traefik domains only
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/frame-src
|
||||
w.Header().Set("Content-Security-Policy", "frame-src 'self' https://traefik.io https://*.traefik.io;")
|
||||
|
||||
// The content type must be guessed by the file server.
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options
|
||||
w.Header().Del("Content-Type")
|
||||
|
||||
http.StripPrefix(dashboardPath, http.FileServerFS(assets)).ServeHTTP(w, r)
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -78,38 +78,40 @@ func New(staticConfig static.Configuration, runtimeConfig *runtime.Configuration
|
|||
func (h Handler) createRouter() *mux.Router {
|
||||
router := mux.NewRouter().UseEncodedPath()
|
||||
|
||||
apiRouter := router.PathPrefix(h.staticConfig.API.BasePath).Subrouter().UseEncodedPath()
|
||||
|
||||
if h.staticConfig.API.Debug {
|
||||
DebugHandler{}.Append(router)
|
||||
DebugHandler{}.Append(apiRouter)
|
||||
}
|
||||
|
||||
router.Methods(http.MethodGet).Path("/api/rawdata").HandlerFunc(h.getRuntimeConfiguration)
|
||||
apiRouter.Methods(http.MethodGet).Path("/api/rawdata").HandlerFunc(h.getRuntimeConfiguration)
|
||||
|
||||
// Experimental endpoint
|
||||
router.Methods(http.MethodGet).Path("/api/overview").HandlerFunc(h.getOverview)
|
||||
apiRouter.Methods(http.MethodGet).Path("/api/overview").HandlerFunc(h.getOverview)
|
||||
|
||||
router.Methods(http.MethodGet).Path("/api/entrypoints").HandlerFunc(h.getEntryPoints)
|
||||
router.Methods(http.MethodGet).Path("/api/entrypoints/{entryPointID}").HandlerFunc(h.getEntryPoint)
|
||||
apiRouter.Methods(http.MethodGet).Path("/api/entrypoints").HandlerFunc(h.getEntryPoints)
|
||||
apiRouter.Methods(http.MethodGet).Path("/api/entrypoints/{entryPointID}").HandlerFunc(h.getEntryPoint)
|
||||
|
||||
router.Methods(http.MethodGet).Path("/api/http/routers").HandlerFunc(h.getRouters)
|
||||
router.Methods(http.MethodGet).Path("/api/http/routers/{routerID}").HandlerFunc(h.getRouter)
|
||||
router.Methods(http.MethodGet).Path("/api/http/services").HandlerFunc(h.getServices)
|
||||
router.Methods(http.MethodGet).Path("/api/http/services/{serviceID}").HandlerFunc(h.getService)
|
||||
router.Methods(http.MethodGet).Path("/api/http/middlewares").HandlerFunc(h.getMiddlewares)
|
||||
router.Methods(http.MethodGet).Path("/api/http/middlewares/{middlewareID}").HandlerFunc(h.getMiddleware)
|
||||
apiRouter.Methods(http.MethodGet).Path("/api/http/routers").HandlerFunc(h.getRouters)
|
||||
apiRouter.Methods(http.MethodGet).Path("/api/http/routers/{routerID}").HandlerFunc(h.getRouter)
|
||||
apiRouter.Methods(http.MethodGet).Path("/api/http/services").HandlerFunc(h.getServices)
|
||||
apiRouter.Methods(http.MethodGet).Path("/api/http/services/{serviceID}").HandlerFunc(h.getService)
|
||||
apiRouter.Methods(http.MethodGet).Path("/api/http/middlewares").HandlerFunc(h.getMiddlewares)
|
||||
apiRouter.Methods(http.MethodGet).Path("/api/http/middlewares/{middlewareID}").HandlerFunc(h.getMiddleware)
|
||||
|
||||
router.Methods(http.MethodGet).Path("/api/tcp/routers").HandlerFunc(h.getTCPRouters)
|
||||
router.Methods(http.MethodGet).Path("/api/tcp/routers/{routerID}").HandlerFunc(h.getTCPRouter)
|
||||
router.Methods(http.MethodGet).Path("/api/tcp/services").HandlerFunc(h.getTCPServices)
|
||||
router.Methods(http.MethodGet).Path("/api/tcp/services/{serviceID}").HandlerFunc(h.getTCPService)
|
||||
router.Methods(http.MethodGet).Path("/api/tcp/middlewares").HandlerFunc(h.getTCPMiddlewares)
|
||||
router.Methods(http.MethodGet).Path("/api/tcp/middlewares/{middlewareID}").HandlerFunc(h.getTCPMiddleware)
|
||||
apiRouter.Methods(http.MethodGet).Path("/api/tcp/routers").HandlerFunc(h.getTCPRouters)
|
||||
apiRouter.Methods(http.MethodGet).Path("/api/tcp/routers/{routerID}").HandlerFunc(h.getTCPRouter)
|
||||
apiRouter.Methods(http.MethodGet).Path("/api/tcp/services").HandlerFunc(h.getTCPServices)
|
||||
apiRouter.Methods(http.MethodGet).Path("/api/tcp/services/{serviceID}").HandlerFunc(h.getTCPService)
|
||||
apiRouter.Methods(http.MethodGet).Path("/api/tcp/middlewares").HandlerFunc(h.getTCPMiddlewares)
|
||||
apiRouter.Methods(http.MethodGet).Path("/api/tcp/middlewares/{middlewareID}").HandlerFunc(h.getTCPMiddleware)
|
||||
|
||||
router.Methods(http.MethodGet).Path("/api/udp/routers").HandlerFunc(h.getUDPRouters)
|
||||
router.Methods(http.MethodGet).Path("/api/udp/routers/{routerID}").HandlerFunc(h.getUDPRouter)
|
||||
router.Methods(http.MethodGet).Path("/api/udp/services").HandlerFunc(h.getUDPServices)
|
||||
router.Methods(http.MethodGet).Path("/api/udp/services/{serviceID}").HandlerFunc(h.getUDPService)
|
||||
apiRouter.Methods(http.MethodGet).Path("/api/udp/routers").HandlerFunc(h.getUDPRouters)
|
||||
apiRouter.Methods(http.MethodGet).Path("/api/udp/routers/{routerID}").HandlerFunc(h.getUDPRouter)
|
||||
apiRouter.Methods(http.MethodGet).Path("/api/udp/services").HandlerFunc(h.getUDPServices)
|
||||
apiRouter.Methods(http.MethodGet).Path("/api/udp/services/{serviceID}").HandlerFunc(h.getUDPService)
|
||||
|
||||
version.Handler{}.Append(router)
|
||||
version.Handler{}.Append(apiRouter)
|
||||
|
||||
return router
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue