From 6af404b9da0b6d933286fc2036dcdac3959003b8 Mon Sep 17 00:00:00 2001
From: "Gina A." <70909035+gndz07@users.noreply.github.com>
Date: Tue, 23 Dec 2025 15:58:04 +0100
Subject: [PATCH] Add dashboard name configuration
---
cmd/traefik/traefik.go | 1 +
.../configuration-options.md | 1 +
pkg/config/static/static_config.go | 2 +
pkg/version/version.go | 4 ++
.../middlewares/MiddlewareDetail.tsx | 14 ++----
webui/src/components/routers/RouterDetail.tsx | 14 ++----
.../src/components/services/ServiceDetail.tsx | 14 ++----
webui/src/contexts/version.tsx | 6 ++-
.../src/hooks/use-hub-upgrade-button.spec.tsx | 4 +-
webui/src/layout/Page.tsx | 6 +--
webui/src/layout/PageTitle.spec.tsx | 48 +++++++++++++++++++
webui/src/layout/PageTitle.tsx | 21 ++++++++
webui/src/mocks/data/api-version.json | 5 +-
webui/src/pages/NotFound.tsx | 7 ++-
webui/src/pages/dashboard/Dashboard.tsx | 6 +--
webui/src/pages/http/HttpMiddlewares.tsx | 6 +--
webui/src/pages/http/HttpRouters.tsx | 6 +--
webui/src/pages/http/HttpServices.tsx | 6 +--
webui/src/pages/tcp/TcpMiddlewares.tsx | 6 +--
webui/src/pages/tcp/TcpRouters.tsx | 6 +--
webui/src/pages/tcp/TcpServices.tsx | 6 +--
webui/src/pages/udp/UdpRouters.tsx | 6 +--
webui/src/pages/udp/UdpServices.tsx | 6 +--
webui/src/types/API.d.ts | 1 +
24 files changed, 124 insertions(+), 78 deletions(-)
create mode 100644 webui/src/layout/PageTitle.spec.tsx
create mode 100644 webui/src/layout/PageTitle.tsx
diff --git a/cmd/traefik/traefik.go b/cmd/traefik/traefik.go
index d49ee4651..1d89309de 100644
--- a/cmd/traefik/traefik.go
+++ b/cmd/traefik/traefik.go
@@ -231,6 +231,7 @@ func setupServer(staticConfiguration *static.Configuration) (*server.Server, err
if staticConfiguration.API != nil {
version.DisableDashboardAd = staticConfiguration.API.DisableDashboardAd
+ version.DashboardName = staticConfiguration.API.DashboardName
}
// Plugins
diff --git a/docs/content/reference/install-configuration/configuration-options.md b/docs/content/reference/install-configuration/configuration-options.md
index 14284b855..0854ae617 100644
--- a/docs/content/reference/install-configuration/configuration-options.md
+++ b/docs/content/reference/install-configuration/configuration-options.md
@@ -40,6 +40,7 @@ THIS FILE MUST NOT BE EDITED BY HAND
| api | Enable api/dashboard. | false |
| api.basepath | Defines the base path where the API and Dashboard will be exposed. | / |
| api.dashboard | Activate dashboard. | true |
+| api.dashboardname | Custom name for the dashboard. | |
| api.debug | Enable additional endpoints for debugging and profiling. | false |
| api.disabledashboardad | Disable ad in the dashboard. | false |
| api.insecure | Activate API directly on the entryPoint named traefik. | false |
diff --git a/pkg/config/static/static_config.go b/pkg/config/static/static_config.go
index 6491c354a..59799c4e2 100644
--- a/pkg/config/static/static_config.go
+++ b/pkg/config/static/static_config.go
@@ -159,6 +159,7 @@ type API struct {
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"`
DisableDashboardAd bool `description:"Disable ad in the dashboard." json:"disableDashboardAd,omitempty" toml:"disableDashboardAd,omitempty" yaml:"disableDashboardAd,omitempty" export:"true"`
+ DashboardName string `description:"Custom name for the dashboard." json:"dashboardName,omitempty" toml:"dashboardName,omitempty" yaml:"dashboardName,omitempty" export:"true"`
// TODO: Re-enable statistics
// Statistics *types.Statistics `description:"Enable more detailed statistics." json:"statistics,omitempty" toml:"statistics,omitempty" yaml:"statistics,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
}
@@ -167,6 +168,7 @@ type API struct {
func (a *API) SetDefaults() {
a.BasePath = "/"
a.Dashboard = true
+ a.DashboardName = ""
}
// RespondingTimeouts contains timeout configurations for incoming requests to the Traefik instance.
diff --git a/pkg/version/version.go b/pkg/version/version.go
index da2f45bc5..f0c5b05d4 100644
--- a/pkg/version/version.go
+++ b/pkg/version/version.go
@@ -24,6 +24,8 @@ var (
StartDate = time.Now()
// DisableDashboardAd disables ad in the dashboard.
DisableDashboardAd = false
+ // DashboardName holds the custom name for the dashboard.
+ DashboardName = ""
)
// Handler expose version routes.
@@ -43,11 +45,13 @@ func (v Handler) Append(router *mux.Router) {
StartDate time.Time `json:"startDate"`
UUID string `json:"uuid,omitempty"`
DisableDashboardAd bool `json:"disableDashboardAd,omitempty"`
+ DashboardName string `json:"dashboardName,omitempty"`
}{
Version: Version,
Codename: Codename,
StartDate: StartDate,
DisableDashboardAd: DisableDashboardAd,
+ DashboardName: DashboardName,
}
if err := templatesRenderer.JSON(response, http.StatusOK, v); err != nil {
diff --git a/webui/src/components/middlewares/MiddlewareDetail.tsx b/webui/src/components/middlewares/MiddlewareDetail.tsx
index 14ca8f290..b93940cbc 100644
--- a/webui/src/components/middlewares/MiddlewareDetail.tsx
+++ b/webui/src/components/middlewares/MiddlewareDetail.tsx
@@ -1,6 +1,5 @@
import { Card, Flex, H1, Skeleton, Text } from '@traefiklabs/faency'
import { useMemo } from 'react'
-import { Helmet } from 'react-helmet-async'
import MiddlewareDefinition from './MiddlewareDefinition'
import { RenderUnknownProp } from './RenderUnknownProp'
@@ -8,6 +7,7 @@ import { RenderUnknownProp } from './RenderUnknownProp'
import { DetailsCardSkeleton } from 'components/resources/DetailsCard'
import ResourceErrors, { ResourceErrorsSkeleton } from 'components/resources/ResourceErrors'
import { UsedByRoutersSection, UsedByRoutersSkeleton } from 'components/resources/UsedByRoutersSection'
+import PageTitle from 'layout/PageTitle'
import { NotFound } from 'pages/NotFound'
type MiddlewareDetailProps = {
@@ -42,9 +42,7 @@ export const MiddlewareDetail = ({ data, error, name, protocol }: MiddlewareDeta
if (error) {
return (
<>
-
- {name} - Traefik Proxy
-
+
Sorry, we could not fetch detail information for this Middleware right now. Please, try again later.
@@ -55,9 +53,7 @@ export const MiddlewareDetail = ({ data, error, name, protocol }: MiddlewareDeta
if (!data) {
return (
<>
-
- {name} - Traefik Proxy
-
+
@@ -74,9 +70,7 @@ export const MiddlewareDetail = ({ data, error, name, protocol }: MiddlewareDeta
return (
<>
-
- {data.name} - Traefik Proxy
-
+
{data.name}
diff --git a/webui/src/components/routers/RouterDetail.tsx b/webui/src/components/routers/RouterDetail.tsx
index 5f13369ab..766acc3f9 100644
--- a/webui/src/components/routers/RouterDetail.tsx
+++ b/webui/src/components/routers/RouterDetail.tsx
@@ -1,11 +1,11 @@
import { Flex, H1, Skeleton, Text } from '@traefiklabs/faency'
import { useMemo } from 'react'
-import { Helmet } from 'react-helmet-async'
import { DetailsCardSkeleton } from 'components/resources/DetailsCard'
import ResourceErrors, { ResourceErrorsSkeleton } from 'components/resources/ResourceErrors'
import RouterFlowDiagram, { RouterFlowDiagramSkeleton } from 'components/routers/RouterFlowDiagram'
import TlsSection from 'components/routers/TlsSection'
+import PageTitle from 'layout/PageTitle'
import { NotFound } from 'pages/NotFound'
type RouterDetailProps = {
@@ -21,9 +21,7 @@ export const RouterDetail = ({ data, error, name, protocol }: RouterDetailProps)
if (error) {
return (
<>
-
- {name} - Traefik Proxy
-
+
Sorry, we could not fetch detail information for this Router right now. Please, try again later.
@@ -34,9 +32,7 @@ export const RouterDetail = ({ data, error, name, protocol }: RouterDetailProps)
if (!data) {
return (
<>
-
- {name} - Traefik Proxy
-
+
@@ -53,9 +49,7 @@ export const RouterDetail = ({ data, error, name, protocol }: RouterDetailProps)
return (
<>
-
- {data.name} - Traefik Proxy
-
+
{data.name}
diff --git a/webui/src/components/services/ServiceDetail.tsx b/webui/src/components/services/ServiceDetail.tsx
index 86adf8b26..66642bc7b 100644
--- a/webui/src/components/services/ServiceDetail.tsx
+++ b/webui/src/components/services/ServiceDetail.tsx
@@ -1,5 +1,4 @@
import { Box, Flex, H1, Skeleton, Text } from '@traefiklabs/faency'
-import { Helmet } from 'react-helmet-async'
import MirrorServices from './MirrorServices'
import Servers from './Servers'
@@ -10,6 +9,7 @@ import WeightedServices from './WeightedServices'
import { DetailsCardSkeleton } from 'components/resources/DetailsCard'
import { UsedByRoutersSection, UsedByRoutersSkeleton } from 'components/resources/UsedByRoutersSection'
import AriaTableSkeleton from 'components/tables/AriaTableSkeleton'
+import PageTitle from 'layout/PageTitle'
import { NotFound } from 'pages/NotFound'
type ServiceDetailProps = {
@@ -23,9 +23,7 @@ export const ServiceDetail = ({ data, error, name, protocol }: ServiceDetailProp
if (error) {
return (
<>
-
- {name} - Traefik Proxy
-
+
Sorry, we could not fetch detail information for this Service right now. Please, try again later.
@@ -36,9 +34,7 @@ export const ServiceDetail = ({ data, error, name, protocol }: ServiceDetailProp
if (!data) {
return (
<>
-
- {name} - Traefik Proxy
-
+
@@ -65,9 +61,7 @@ export const ServiceDetail = ({ data, error, name, protocol }: ServiceDetailProp
return (
<>
-
- {data.name} - Traefik Proxy
-
+
{data.name}
diff --git a/webui/src/contexts/version.tsx b/webui/src/contexts/version.tsx
index 742d38c28..068209c76 100644
--- a/webui/src/contexts/version.tsx
+++ b/webui/src/contexts/version.tsx
@@ -5,11 +5,13 @@ import { BASE_PATH } from 'libs/utils'
type VersionContextProps = {
showHubButton: boolean
version: string
+ dashboardName: string
}
export const VersionContext = createContext({
showHubButton: false,
version: '',
+ dashboardName: '',
})
type VersionProviderProps = {
@@ -19,6 +21,7 @@ type VersionProviderProps = {
export const VersionProvider = ({ children }: VersionProviderProps) => {
const [showHubButton, setShowHubButton] = useState(false)
const [version, setVersion] = useState('')
+ const [dashboardName, setDashboardName] = useState('')
useEffect(() => {
const fetchVersion = async () => {
@@ -30,6 +33,7 @@ export const VersionProvider = ({ children }: VersionProviderProps) => {
const data: API.Version = await response.json()
setShowHubButton(!data.disableDashboardAd)
setVersion(data.Version)
+ setDashboardName(data.dashboardName || '')
} catch (err) {
console.error(err)
}
@@ -38,5 +42,5 @@ export const VersionProvider = ({ children }: VersionProviderProps) => {
fetchVersion()
}, [])
- return {children}
+ return {children}
}
diff --git a/webui/src/hooks/use-hub-upgrade-button.spec.tsx b/webui/src/hooks/use-hub-upgrade-button.spec.tsx
index f0f914675..83cf8399d 100644
--- a/webui/src/hooks/use-hub-upgrade-button.spec.tsx
+++ b/webui/src/hooks/use-hub-upgrade-button.spec.tsx
@@ -12,7 +12,9 @@ const mockVerifySignature = vi.mocked(verifySignature)
const createWrapper = (showHubButton: boolean) => {
return ({ children }: { children: ReactNode }) => (
- {children}
+
+ {children}
+
)
}
diff --git a/webui/src/layout/Page.tsx b/webui/src/layout/Page.tsx
index d28f7f3bc..b9549ba5a 100644
--- a/webui/src/layout/Page.tsx
+++ b/webui/src/layout/Page.tsx
@@ -1,9 +1,9 @@
import { Flex, globalCss, styled } from '@traefiklabs/faency'
import { ReactNode, useMemo, useState } from 'react'
-import { Helmet } from 'react-helmet-async'
import { useLocation } from 'react-router-dom'
import Container from './Container'
+import PageTitle from './PageTitle'
import { ToastPool } from 'components/ToastPool'
import { ToastProvider } from 'contexts/toasts'
@@ -64,9 +64,7 @@ const Page = ({ children }: Props) => {
return (
{globalStyles()}
-
- Traefik Proxy
-
+
setIsSideBarPanelOpen(true)} isResponsive />
diff --git a/webui/src/layout/PageTitle.spec.tsx b/webui/src/layout/PageTitle.spec.tsx
new file mode 100644
index 000000000..6e2c179b4
--- /dev/null
+++ b/webui/src/layout/PageTitle.spec.tsx
@@ -0,0 +1,48 @@
+import { waitFor } from '@testing-library/react'
+
+import PageTitle from './PageTitle'
+
+import { VersionContext } from 'contexts/version'
+import { renderWithProviders } from 'utils/test'
+
+describe('', () => {
+ it('should render default title without page title or dashboard name', async () => {
+ renderWithProviders()
+
+ await waitFor(() => {
+ expect(document.title).toBe('Traefik Proxy')
+ })
+ })
+
+ it('should render with page title', async () => {
+ renderWithProviders()
+
+ await waitFor(() => {
+ expect(document.title).toBe('Dashboard - Traefik Proxy')
+ })
+ })
+
+ it('should render with dashboard name', async () => {
+ renderWithProviders(
+
+
+ ,
+ )
+
+ await waitFor(() => {
+ expect(document.title).toBe('Traefik Proxy [MyDashboard]')
+ })
+ })
+
+ it('should render with page title and dashboard name', async () => {
+ renderWithProviders(
+
+
+ ,
+ )
+
+ await waitFor(() => {
+ expect(document.title).toBe('Dashboard - Traefik Proxy [MyDashboard]')
+ })
+ })
+})
diff --git a/webui/src/layout/PageTitle.tsx b/webui/src/layout/PageTitle.tsx
new file mode 100644
index 000000000..aac492fb9
--- /dev/null
+++ b/webui/src/layout/PageTitle.tsx
@@ -0,0 +1,21 @@
+import { useContext, useMemo } from 'react'
+import { Helmet } from 'react-helmet-async'
+
+import { VersionContext } from 'contexts/version'
+
+const PageTitle = ({ title }: { title?: string }) => {
+ const { dashboardName } = useContext(VersionContext)
+
+ const pageTitle = useMemo(
+ () => `${title ? `${title} - ` : ''}Traefik Proxy${dashboardName ? ` [${dashboardName}]` : ''}`,
+ [dashboardName, title],
+ )
+
+ return (
+
+ {pageTitle}
+
+ )
+}
+
+export default PageTitle
diff --git a/webui/src/mocks/data/api-version.json b/webui/src/mocks/data/api-version.json
index 4923a8856..0aadb6bc1 100644
--- a/webui/src/mocks/data/api-version.json
+++ b/webui/src/mocks/data/api-version.json
@@ -2,5 +2,6 @@
"Version": "3.6.0",
"Codename": "ramequin",
"disableDashboardAd": false,
- "startDate": "2025-03-28T14:58:25.8937758+01:00"
-}
\ No newline at end of file
+ "startDate": "2025-03-28T14:58:25.8937758+01:00",
+ "dashboardName": "Pre-prod"
+}
diff --git a/webui/src/pages/NotFound.tsx b/webui/src/pages/NotFound.tsx
index 486b0b217..55ba8e127 100644
--- a/webui/src/pages/NotFound.tsx
+++ b/webui/src/pages/NotFound.tsx
@@ -1,15 +1,14 @@
import { Box, Button, Flex, H1, Text } from '@traefiklabs/faency'
-import { Helmet } from 'react-helmet-async'
import { useNavigate } from 'react-router-dom'
+import PageTitle from 'layout/PageTitle'
+
export const NotFound = () => {
const navigate = useNavigate()
return (
-
- Not found - Traefik Proxy
-
+
404
diff --git a/webui/src/pages/dashboard/Dashboard.tsx b/webui/src/pages/dashboard/Dashboard.tsx
index b26cf0a9c..06ac71de8 100644
--- a/webui/src/pages/dashboard/Dashboard.tsx
+++ b/webui/src/pages/dashboard/Dashboard.tsx
@@ -1,12 +1,12 @@
import { Card, CSS, Flex, Grid, H2, Text } from '@traefiklabs/faency'
import { ReactNode, useMemo } from 'react'
-import { Helmet } from 'react-helmet-async'
import useSWR from 'swr'
import ProviderIcon from 'components/icons/providers'
import FeatureCard, { FeatureCardSkeleton } from 'components/resources/FeatureCard'
import ResourceCard from 'components/resources/ResourceCard'
import TraefikResourceStatsCard, { StatsCardSkeleton } from 'components/resources/TraefikResourceStatsCard'
+import PageTitle from 'layout/PageTitle'
import { capitalizeFirstLetter } from 'utils/string'
const RESOURCES = ['routers', 'services', 'middlewares']
@@ -77,9 +77,7 @@ export const Dashboard = () => {
return (
-
- Dashboard - Traefik Proxy
-
+
{entrypoints?.map((i, idx) => (
{
@@ -99,9 +99,7 @@ export const HttpMiddlewares = () => {
return (
<>
-
- HTTP Middlewares - Traefik Proxy
-
+
{
const HttpRoutersRenderRow = (row) => (
@@ -132,9 +132,7 @@ export const HttpRouters = () => {
return (
<>
-
- HTTP Routers - Traefik Proxy
-
+
{
const HttpServicesRenderRow = (row) => (
@@ -98,9 +98,7 @@ export const HttpServices = () => {
return (
<>
-
- HTTP Services - Traefik Proxy
-
+
{
@@ -99,9 +99,7 @@ export const TcpMiddlewares = () => {
return (
<>
-
- TCP Middlewares - Traefik Proxy
-
+
{
const TcpRoutersRenderRow = (row) => (
@@ -117,9 +117,7 @@ export const TcpRouters = () => {
return (
<>
-
- TCP Routers - Traefik Proxy
-
+
{
const TcpServicesRenderRow = (row) => (
@@ -98,9 +98,7 @@ export const TcpServices = () => {
return (
<>
-
- TCP Services - Traefik Proxy
-
+
{
const UdpRoutersRenderRow = (row) => (
@@ -101,9 +101,7 @@ export const UdpRouters = () => {
return (
<>
-
- UDP Routers - Traefik Proxy
-
+
{
const UdpServicesRenderRow = (row) => (
@@ -98,9 +98,7 @@ export const UdpServices = () => {
return (
<>
-
- UDP Services - Traefik Proxy
-
+