mirror of
https://github.com/containous/traefik.git
synced 2025-01-03 01:17:53 +03:00
Configurable API & Dashboard base path
Co-authored-by: Kevin Pollet <pollet.kevin@gmail.com>
This commit is contained in:
parent
090db6d4b0
commit
0ec12c7aa7
@ -87,8 +87,44 @@ rule = "Host(`traefik.example.com`) && (PathPrefix(`/api`) || PathPrefix(`/dashb
|
|||||||
??? example "Dashboard Dynamic Configuration Examples"
|
??? example "Dashboard Dynamic Configuration Examples"
|
||||||
--8<-- "content/operations/include-dashboard-examples.md"
|
--8<-- "content/operations/include-dashboard-examples.md"
|
||||||
|
|
||||||
|
### Custom API Base Path
|
||||||
|
|
||||||
|
As shown above, by default Traefik exposes its API and Dashboard under the `/` base path,
|
||||||
|
which means that respectively the API is served under the `/api` path,
|
||||||
|
and the dashboard under the `/dashboard` path.
|
||||||
|
|
||||||
|
However, it is possible to configure this base path:
|
||||||
|
|
||||||
|
```yaml tab="File (YAML)"
|
||||||
|
api:
|
||||||
|
# Customizes the base path:
|
||||||
|
# - Serving API under `/traefik/api`
|
||||||
|
# - Serving Dashboard under `/traefik/dashboard`
|
||||||
|
basePath: /traefik
|
||||||
|
```
|
||||||
|
|
||||||
|
```toml tab="File (TOML)"
|
||||||
|
[api]
|
||||||
|
# Customizes the base path:
|
||||||
|
# - Serving API under `/traefik/api`
|
||||||
|
# - Serving Dashboard under `/traefik/dashboard`
|
||||||
|
basePath = "/traefik"
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash tab="CLI"
|
||||||
|
# Customizes the base path:
|
||||||
|
# - Serving API under `/traefik/api`
|
||||||
|
# - Serving Dashboard under `/traefik/dashboard`
|
||||||
|
--api.basePath=/traefik
|
||||||
|
```
|
||||||
|
|
||||||
|
??? example "Dashboard Under Custom Path Dynamic Configuration Examples"
|
||||||
|
--8<-- "content/operations/include-dashboard-custom-path-examples.md"
|
||||||
|
|
||||||
## Insecure Mode
|
## Insecure Mode
|
||||||
|
|
||||||
|
!!! warning "Please note that this mode is incompatible with the [custom API base path option](#custom-api-base-path)."
|
||||||
|
|
||||||
When _insecure_ mode is enabled, one can access the dashboard on the `traefik` port (default: `8080`) of the Traefik instance,
|
When _insecure_ mode is enabled, one can access the dashboard on the `traefik` port (default: `8080`) of the Traefik instance,
|
||||||
at the following URL: `http://<Traefik IP>:8080/dashboard/` (trailing slash is mandatory).
|
at the following URL: `http://<Traefik IP>:8080/dashboard/` (trailing slash is mandatory).
|
||||||
|
|
||||||
|
@ -0,0 +1,83 @@
|
|||||||
|
```yaml tab="Docker & Swarm"
|
||||||
|
# Dynamic Configuration
|
||||||
|
labels:
|
||||||
|
- "traefik.http.routers.dashboard.rule=Host(`traefik.example.com`) && PathPrefix(`/traefik`)"
|
||||||
|
- "traefik.http.routers.dashboard.service=api@internal"
|
||||||
|
- "traefik.http.routers.dashboard.middlewares=auth"
|
||||||
|
- "traefik.http.middlewares.auth.basicauth.users=test:$$apr1$$H6uskkkW$$IgXLP6ewTrSuBkTrqE8wj/,test2:$$apr1$$d9hr9HBB$$4HxwgUir3HP4EsggP/QNo0"
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml tab="Docker (Swarm)"
|
||||||
|
# Dynamic Configuration
|
||||||
|
deploy:
|
||||||
|
labels:
|
||||||
|
- "traefik.http.routers.dashboard.rule=Host(`traefik.example.com`) && PathPrefix(`/traefik`)"
|
||||||
|
- "traefik.http.routers.dashboard.service=api@internal"
|
||||||
|
- "traefik.http.routers.dashboard.middlewares=auth"
|
||||||
|
- "traefik.http.middlewares.auth.basicauth.users=test:$$apr1$$H6uskkkW$$IgXLP6ewTrSuBkTrqE8wj/,test2:$$apr1$$d9hr9HBB$$4HxwgUir3HP4EsggP/QNo0"
|
||||||
|
# Dummy service for Swarm port detection. The port can be any valid integer value.
|
||||||
|
- "traefik.http.services.dummy-svc.loadbalancer.server.port=9999"
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml tab="Kubernetes CRD"
|
||||||
|
apiVersion: traefik.io/v1alpha1
|
||||||
|
kind: IngressRoute
|
||||||
|
metadata:
|
||||||
|
name: traefik-dashboard
|
||||||
|
spec:
|
||||||
|
routes:
|
||||||
|
- match: Host(`traefik.example.com`) && PathPrefix(`/traefik`)
|
||||||
|
kind: Rule
|
||||||
|
services:
|
||||||
|
- name: api@internal
|
||||||
|
kind: TraefikService
|
||||||
|
middlewares:
|
||||||
|
- name: auth
|
||||||
|
---
|
||||||
|
apiVersion: traefik.io/v1alpha1
|
||||||
|
kind: Middleware
|
||||||
|
metadata:
|
||||||
|
name: auth
|
||||||
|
spec:
|
||||||
|
basicAuth:
|
||||||
|
secret: secretName # Kubernetes secret named "secretName"
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml tab="Consul Catalog"
|
||||||
|
# Dynamic Configuration
|
||||||
|
- "traefik.http.routers.dashboard.rule=Host(`traefik.example.com`) && PathPrefix(`/traefik`)"
|
||||||
|
- "traefik.http.routers.dashboard.service=api@internal"
|
||||||
|
- "traefik.http.routers.dashboard.middlewares=auth"
|
||||||
|
- "traefik.http.middlewares.auth.basicauth.users=test:$$apr1$$H6uskkkW$$IgXLP6ewTrSuBkTrqE8wj/,test2:$$apr1$$d9hr9HBB$$4HxwgUir3HP4EsggP/QNo0"
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml tab="File (YAML)"
|
||||||
|
# Dynamic Configuration
|
||||||
|
http:
|
||||||
|
routers:
|
||||||
|
dashboard:
|
||||||
|
rule: Host(`traefik.example.com`) && PathPrefix(`/traefik`)
|
||||||
|
service: api@internal
|
||||||
|
middlewares:
|
||||||
|
- auth
|
||||||
|
middlewares:
|
||||||
|
auth:
|
||||||
|
basicAuth:
|
||||||
|
users:
|
||||||
|
- "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/"
|
||||||
|
- "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"
|
||||||
|
```
|
||||||
|
|
||||||
|
```toml tab="File (TOML)"
|
||||||
|
# Dynamic Configuration
|
||||||
|
[http.routers.my-api]
|
||||||
|
rule = "Host(`traefik.example.com`) && PathPrefix(`/traefik`)"
|
||||||
|
service = "api@internal"
|
||||||
|
middlewares = ["auth"]
|
||||||
|
|
||||||
|
[http.middlewares.auth.basicAuth]
|
||||||
|
users = [
|
||||||
|
"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/",
|
||||||
|
"test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
|
||||||
|
]
|
||||||
|
```
|
@ -42,6 +42,9 @@ Access log format: json | common (Default: ```common```)
|
|||||||
`--api`:
|
`--api`:
|
||||||
Enable api/dashboard. (Default: ```false```)
|
Enable api/dashboard. (Default: ```false```)
|
||||||
|
|
||||||
|
`--api.basepath`:
|
||||||
|
Defines the base path where the API and Dashboard will be exposed. (Default: ```/```)
|
||||||
|
|
||||||
`--api.dashboard`:
|
`--api.dashboard`:
|
||||||
Activate dashboard. (Default: ```true```)
|
Activate dashboard. (Default: ```true```)
|
||||||
|
|
||||||
|
@ -42,6 +42,9 @@ Access log format: json | common (Default: ```common```)
|
|||||||
`TRAEFIK_API`:
|
`TRAEFIK_API`:
|
||||||
Enable api/dashboard. (Default: ```false```)
|
Enable api/dashboard. (Default: ```false```)
|
||||||
|
|
||||||
|
`TRAEFIK_API_BASEPATH`:
|
||||||
|
Defines the base path where the API and Dashboard will be exposed. (Default: ```/```)
|
||||||
|
|
||||||
`TRAEFIK_API_DASHBOARD`:
|
`TRAEFIK_API_DASHBOARD`:
|
||||||
Activate dashboard. (Default: ```true```)
|
Activate dashboard. (Default: ```true```)
|
||||||
|
|
||||||
|
@ -294,6 +294,7 @@
|
|||||||
name1 = "foobar"
|
name1 = "foobar"
|
||||||
|
|
||||||
[api]
|
[api]
|
||||||
|
basePath = "foobar"
|
||||||
insecure = true
|
insecure = true
|
||||||
dashboard = true
|
dashboard = true
|
||||||
debug = true
|
debug = true
|
||||||
|
@ -330,6 +330,7 @@ providers:
|
|||||||
name0: foobar
|
name0: foobar
|
||||||
name1: foobar
|
name1: foobar
|
||||||
api:
|
api:
|
||||||
|
basePath: foobar
|
||||||
insecure: true
|
insecure: true
|
||||||
dashboard: true
|
dashboard: true
|
||||||
debug: true
|
debug: true
|
||||||
|
@ -1,36 +1,88 @@
|
|||||||
package dashboard
|
package dashboard
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
"github.com/traefik/traefik/v3/webui"
|
"github.com/traefik/traefik/v3/webui"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type indexTemplateData struct {
|
||||||
|
APIUrl string
|
||||||
|
}
|
||||||
|
|
||||||
// Handler expose dashboard routes.
|
// Handler expose dashboard routes.
|
||||||
type Handler struct {
|
type Handler struct {
|
||||||
|
BasePath string
|
||||||
|
|
||||||
assets fs.FS // optional assets, to override the webui.FS default
|
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
|
// Append adds dashboard routes on the given router, optionally using the given
|
||||||
// assets (or webui.FS otherwise).
|
// 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
|
assets := customAssets
|
||||||
if assets == nil {
|
if assets == nil {
|
||||||
assets = webui.FS
|
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
|
// Expose dashboard
|
||||||
router.Methods(http.MethodGet).
|
router.Methods(http.MethodGet).
|
||||||
Path("/").
|
Path(basePath).
|
||||||
HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
|
HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
|
||||||
prefix := strings.TrimSuffix(req.Header.Get("X-Forwarded-Prefix"), "/")
|
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).
|
router.Methods(http.MethodGet).
|
||||||
PathPrefix("/dashboard/").
|
Path(dashboardPath).
|
||||||
HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
// allow iframes from our domains only
|
// allow iframes from our domains only
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/frame-src
|
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/frame-src
|
||||||
@ -40,16 +92,18 @@ func Append(router *mux.Router, customAssets fs.FS) {
|
|||||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options
|
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options
|
||||||
w.Header().Del("Content-Type")
|
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) {
|
router.Methods(http.MethodGet).
|
||||||
assets := g.assets
|
PathPrefix(dashboardPath).
|
||||||
if assets == nil {
|
HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
assets = webui.FS
|
// allow iframes from traefik domains only
|
||||||
}
|
|
||||||
// allow iframes from our domains only
|
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/frame-src
|
// 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;")
|
w.Header().Set("Content-Security-Policy", "frame-src 'self' https://traefik.io https://*.traefik.io;")
|
||||||
|
|
||||||
@ -57,5 +111,7 @@ func (g Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options
|
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options
|
||||||
w.Header().Del("Content-Type")
|
w.Header().Del("Content-Type")
|
||||||
|
|
||||||
http.FileServerFS(assets).ServeHTTP(w, r)
|
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 {
|
func (h Handler) createRouter() *mux.Router {
|
||||||
router := mux.NewRouter().UseEncodedPath()
|
router := mux.NewRouter().UseEncodedPath()
|
||||||
|
|
||||||
|
apiRouter := router.PathPrefix(h.staticConfig.API.BasePath).Subrouter().UseEncodedPath()
|
||||||
|
|
||||||
if h.staticConfig.API.Debug {
|
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
|
// 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)
|
apiRouter.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/{entryPointID}").HandlerFunc(h.getEntryPoint)
|
||||||
|
|
||||||
router.Methods(http.MethodGet).Path("/api/http/routers").HandlerFunc(h.getRouters)
|
apiRouter.Methods(http.MethodGet).Path("/api/http/routers").HandlerFunc(h.getRouters)
|
||||||
router.Methods(http.MethodGet).Path("/api/http/routers/{routerID}").HandlerFunc(h.getRouter)
|
apiRouter.Methods(http.MethodGet).Path("/api/http/routers/{routerID}").HandlerFunc(h.getRouter)
|
||||||
router.Methods(http.MethodGet).Path("/api/http/services").HandlerFunc(h.getServices)
|
apiRouter.Methods(http.MethodGet).Path("/api/http/services").HandlerFunc(h.getServices)
|
||||||
router.Methods(http.MethodGet).Path("/api/http/services/{serviceID}").HandlerFunc(h.getService)
|
apiRouter.Methods(http.MethodGet).Path("/api/http/services/{serviceID}").HandlerFunc(h.getService)
|
||||||
router.Methods(http.MethodGet).Path("/api/http/middlewares").HandlerFunc(h.getMiddlewares)
|
apiRouter.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/middlewares/{middlewareID}").HandlerFunc(h.getMiddleware)
|
||||||
|
|
||||||
router.Methods(http.MethodGet).Path("/api/tcp/routers").HandlerFunc(h.getTCPRouters)
|
apiRouter.Methods(http.MethodGet).Path("/api/tcp/routers").HandlerFunc(h.getTCPRouters)
|
||||||
router.Methods(http.MethodGet).Path("/api/tcp/routers/{routerID}").HandlerFunc(h.getTCPRouter)
|
apiRouter.Methods(http.MethodGet).Path("/api/tcp/routers/{routerID}").HandlerFunc(h.getTCPRouter)
|
||||||
router.Methods(http.MethodGet).Path("/api/tcp/services").HandlerFunc(h.getTCPServices)
|
apiRouter.Methods(http.MethodGet).Path("/api/tcp/services").HandlerFunc(h.getTCPServices)
|
||||||
router.Methods(http.MethodGet).Path("/api/tcp/services/{serviceID}").HandlerFunc(h.getTCPService)
|
apiRouter.Methods(http.MethodGet).Path("/api/tcp/services/{serviceID}").HandlerFunc(h.getTCPService)
|
||||||
router.Methods(http.MethodGet).Path("/api/tcp/middlewares").HandlerFunc(h.getTCPMiddlewares)
|
apiRouter.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/middlewares/{middlewareID}").HandlerFunc(h.getTCPMiddleware)
|
||||||
|
|
||||||
router.Methods(http.MethodGet).Path("/api/udp/routers").HandlerFunc(h.getUDPRouters)
|
apiRouter.Methods(http.MethodGet).Path("/api/udp/routers").HandlerFunc(h.getUDPRouters)
|
||||||
router.Methods(http.MethodGet).Path("/api/udp/routers/{routerID}").HandlerFunc(h.getUDPRouter)
|
apiRouter.Methods(http.MethodGet).Path("/api/udp/routers/{routerID}").HandlerFunc(h.getUDPRouter)
|
||||||
router.Methods(http.MethodGet).Path("/api/udp/services").HandlerFunc(h.getUDPServices)
|
apiRouter.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/services/{serviceID}").HandlerFunc(h.getUDPService)
|
||||||
|
|
||||||
version.Handler{}.Append(router)
|
version.Handler{}.Append(apiRouter)
|
||||||
|
|
||||||
return router
|
return router
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ package static
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -145,6 +146,7 @@ type TLSClientConfig struct {
|
|||||||
|
|
||||||
// API holds the API configuration.
|
// API holds the API configuration.
|
||||||
type API struct {
|
type API struct {
|
||||||
|
BasePath string `description:"Defines the base path where the API and Dashboard will be exposed." json:"basePath,omitempty" toml:"basePath,omitempty" yaml:"basePath,omitempty" export:"true"`
|
||||||
Insecure bool `description:"Activate API directly on the entryPoint named traefik." json:"insecure,omitempty" toml:"insecure,omitempty" yaml:"insecure,omitempty" export:"true"`
|
Insecure bool `description:"Activate API directly on the entryPoint named traefik." json:"insecure,omitempty" toml:"insecure,omitempty" yaml:"insecure,omitempty" export:"true"`
|
||||||
Dashboard bool `description:"Activate dashboard." json:"dashboard,omitempty" toml:"dashboard,omitempty" yaml:"dashboard,omitempty" export:"true"`
|
Dashboard bool `description:"Activate dashboard." json:"dashboard,omitempty" toml:"dashboard,omitempty" yaml:"dashboard,omitempty" export:"true"`
|
||||||
Debug bool `description:"Enable additional endpoints for debugging and profiling." json:"debug,omitempty" toml:"debug,omitempty" yaml:"debug,omitempty" export:"true"`
|
Debug bool `description:"Enable additional endpoints for debugging and profiling." json:"debug,omitempty" toml:"debug,omitempty" yaml:"debug,omitempty" export:"true"`
|
||||||
@ -155,6 +157,7 @@ type API struct {
|
|||||||
|
|
||||||
// SetDefaults sets the default values.
|
// SetDefaults sets the default values.
|
||||||
func (a *API) SetDefaults() {
|
func (a *API) SetDefaults() {
|
||||||
|
a.BasePath = "/"
|
||||||
a.Dashboard = true
|
a.Dashboard = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -360,6 +363,10 @@ func (c *Configuration) ValidateConfiguration() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if c.API != nil && !path.IsAbs(c.API.BasePath) {
|
||||||
|
return errors.New("API basePath must be a valid absolute path")
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
"github.com/traefik/traefik/v3/pkg/api"
|
"github.com/traefik/traefik/v3/pkg/api"
|
||||||
"github.com/traefik/traefik/v3/pkg/api/dashboard"
|
"github.com/traefik/traefik/v3/pkg/api/dashboard"
|
||||||
"github.com/traefik/traefik/v3/pkg/config/runtime"
|
"github.com/traefik/traefik/v3/pkg/config/runtime"
|
||||||
@ -44,10 +45,13 @@ func NewManagerFactory(staticConfiguration static.Configuration, routinesPool *s
|
|||||||
apiRouterBuilder := api.NewBuilder(staticConfiguration)
|
apiRouterBuilder := api.NewBuilder(staticConfiguration)
|
||||||
|
|
||||||
if staticConfiguration.API.Dashboard {
|
if staticConfiguration.API.Dashboard {
|
||||||
factory.dashboardHandler = dashboard.Handler{}
|
factory.dashboardHandler = dashboard.Handler{BasePath: staticConfiguration.API.BasePath}
|
||||||
factory.api = func(configuration *runtime.Configuration) http.Handler {
|
factory.api = func(configuration *runtime.Configuration) http.Handler {
|
||||||
router := apiRouterBuilder(configuration).(*mux.Router)
|
router := apiRouterBuilder(configuration).(*mux.Router)
|
||||||
dashboard.Append(router, nil)
|
if err := dashboard.Append(router, staticConfiguration.API.BasePath, nil); err != nil {
|
||||||
|
log.Error().Err(err).Msg("Error appending dashboard to API router")
|
||||||
|
}
|
||||||
|
|
||||||
return router
|
return router
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,6 +1,13 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
|
|
||||||
|
{{if .APIUrl}}
|
||||||
|
<script>
|
||||||
|
window.APIURL = "{{.APIUrl}}"
|
||||||
|
</script>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
<title><%= productName %></title>
|
<title><%= productName %></title>
|
||||||
|
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
|
@ -4,7 +4,7 @@ import { APP } from '../_helpers/APP'
|
|||||||
|
|
||||||
// Set config defaults when creating the instance
|
// Set config defaults when creating the instance
|
||||||
const api = axios.create({
|
const api = axios.create({
|
||||||
baseURL: APP.config.apiUrl
|
baseURL: window.APIURL || APP.config.apiUrl
|
||||||
})
|
})
|
||||||
|
|
||||||
export default boot(({ app }) => {
|
export default boot(({ app }) => {
|
||||||
|
Loading…
Reference in New Issue
Block a user