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 - +