This commit is contained in:
Murat Aslan 2026-02-02 17:40:15 +01:00 committed by GitHub
commit 84f07f4cd5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
66 changed files with 614 additions and 653 deletions

View file

@ -49,7 +49,7 @@
"@testing-library/jest-dom": "^6.4.2",
"@testing-library/react": "^14.2.1",
"@testing-library/user-event": "^14.5.2",
"@traefiklabs/faency": "12.0.4",
"@traefik-labs/faency": "12.0.7",
"@types/lodash": "^4.17.16",
"@types/node": "^22.15.18",
"@types/react": "^18.2.0",

View file

@ -1,4 +1,4 @@
import { globalCss, Box, darkTheme, FaencyProvider, lightTheme } from '@traefiklabs/faency'
import { globalCss, Box, darkTheme, FaencyProvider, lightTheme } from '@traefik-labs/faency'
import { Suspense, useContext, useEffect } from 'react'
import { HelmetProvider } from 'react-helmet-async'
import { HashRouter, Navigate, Route, Routes as RouterRoutes, useLocation } from 'react-router-dom'

View file

@ -1,4 +1,4 @@
import { AriaTr, VariantProps, styled } from '@traefiklabs/faency'
import { AriaTr, VariantProps, styled } from '@traefik-labs/faency'
import { ComponentProps, forwardRef, ReactNode } from 'react'
import { useHref } from 'react-router-dom'

View file

@ -1,4 +1,4 @@
import { Button } from '@traefiklabs/faency'
import { Button } from '@traefik-labs/faency'
import { useCallback, useEffect, useState } from 'react'
export const ScrollTopButton = () => {

View file

@ -1,4 +1,4 @@
import { Flex } from '@traefiklabs/faency'
import { Flex } from '@traefik-labs/faency'
import { motion } from 'framer-motion'
import { FiLoader } from 'react-icons/fi'

View file

@ -1,4 +1,4 @@
import { Box, Button, Flex, TextField, InputHandle } from '@traefiklabs/faency'
import { Box, Button, Flex, TextField, InputHandle } from '@traefik-labs/faency'
import { isUndefined, omitBy } from 'lodash'
import { useCallback, useRef, useState } from 'react'
import { FiSearch, FiXCircle } from 'react-icons/fi'

View file

@ -1,4 +1,4 @@
import { AccessibleIcon, Button } from '@traefiklabs/faency'
import { AccessibleIcon, Button } from '@traefik-labs/faency'
import { FiMoon, FiSun } from 'react-icons/fi'
import { AutoThemeIcon } from 'components/icons/AutoThemeIcon'

View file

@ -1,4 +1,4 @@
import { Box, Button, Flex, styled, Text } from '@traefiklabs/faency'
import { Box, Button, Flex, styled, Text } from '@traefik-labs/faency'
import { AnimatePresence, motion } from 'framer-motion'
import { ReactNode, useEffect } from 'react'
import { FiX } from 'react-icons/fi'

View file

@ -1,4 +1,4 @@
import { Flex } from '@traefiklabs/faency'
import { Flex } from '@traefik-labs/faency'
import { useContext } from 'react'
import { Toast } from './Toast'

View file

@ -1,4 +1,4 @@
import { Button, Flex, Text, Tooltip as FaencyTooltip } from '@traefiklabs/faency'
import { Button, Flex, Text, Tooltip as FaencyTooltip } from '@traefik-labs/faency'
import { MouseEvent, ReactNode, useMemo, useState } from 'react'
import { FiCheck, FiCopy } from 'react-icons/fi'

View file

@ -1,4 +1,4 @@
import { CSS, Text } from '@traefiklabs/faency'
import { CSS, Text } from '@traefik-labs/faency'
import { useMemo } from 'react'
import Tooltip from 'components/Tooltip'

View file

@ -12,7 +12,7 @@ You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Button, Flex, Text } from '@traefiklabs/faency'
import { Button, Flex, Text } from '@traefik-labs/faency'
import { ComponentProps, ReactNode } from 'react'
type IconButtonProps = ComponentProps<typeof Button> & {

View file

@ -1,4 +1,4 @@
import { styled, Flex, Label } from '@traefiklabs/faency'
import { styled, Flex, Label } from '@traefik-labs/faency'
import { ComponentProps } from 'react'
import SortIcon from 'components/icons/SortIcon'

View file

@ -1,4 +1,4 @@
import { config, Flex } from '@traefiklabs/faency'
import { config, Flex } from '@traefik-labs/faency'
import { useEffect, useState } from 'react'
import { CustomIconProps } from 'components/icons'

View file

@ -1,4 +1,4 @@
import { CSS, Flex, VariantProps } from '@traefiklabs/faency'
import { CSS, Flex, VariantProps } from '@traefik-labs/faency'
import { HTMLAttributes } from 'react'
export type CustomIconProps = HTMLAttributes<SVGElement> & {

View file

@ -1,4 +1,4 @@
import { Badge, Box, Text } from '@traefiklabs/faency'
import { Badge, Box, Text } from '@traefik-labs/faency'
import Tooltip from 'components/Tooltip'
import { MiddlewareProps, ValuesMapType } from 'hooks/use-resource-detail'

View file

@ -1,4 +1,4 @@
import { Badge, Box, Card, Flex, H2, styled, Text } from '@traefiklabs/faency'
import { Badge, Box, Card, Flex, H2, styled, Text } from '@traefik-labs/faency'
import { ReactNode } from 'react'
import { FiArrowRight, FiToggleLeft, FiToggleRight } from 'react-icons/fi'
import { useNavigate } from 'react-router-dom'

View file

@ -1,4 +1,4 @@
import { Box, Card, Flex, Grid, Skeleton as FaencySkeleton, Text } from '@traefiklabs/faency'
import { Box, Card, Flex, Grid, Skeleton as FaencySkeleton, Text } from '@traefik-labs/faency'
import ResourceCard from 'components/resources/ResourceCard'

View file

@ -1,4 +1,4 @@
import { AriaTable, AriaTbody, AriaTd, AriaTr, Flex, Text } from '@traefiklabs/faency'
import { AriaTable, AriaTbody, AriaTd, AriaTr, Flex, Text } from '@traefik-labs/faency'
import { useMemo } from 'react'
import Status, { StatusType } from './Status'

View file

@ -1,4 +1,4 @@
import { AriaTable, AriaTbody, AriaTd, AriaTr, Badge, Flex, Text } from '@traefiklabs/faency'
import { AriaTable, AriaTbody, AriaTd, AriaTr, Badge, Flex, Text } from '@traefik-labs/faency'
import Tooltip from 'components/Tooltip'

View file

@ -1,4 +1,4 @@
import { Box, Flex, H3, styled, Text } from '@traefiklabs/faency'
import { Box, Flex, H3, styled, Text } from '@traefik-labs/faency'
import { FiLayers } from 'react-icons/fi'
import { DetailSection, EmptyPlaceholder, ItemBlock, LayoutTwoCols, ProviderName } from './DetailSections'

View file

@ -1,4 +1,4 @@
import { Text } from '@traefiklabs/faency'
import { Text } from '@traefik-labs/faency'
import { ReactNode } from 'react'
import { BooleanState, ItemBlock } from './DetailSections'

View file

@ -1,4 +1,4 @@
import { Card, CSS, Flex, Text } from '@traefiklabs/faency'
import { Card, CSS, Flex, Text } from '@traefik-labs/faency'
import { ReactNode } from 'react'
type ResourceCardProps = {

View file

@ -1,4 +1,4 @@
import { Flex, styled, Text } from '@traefiklabs/faency'
import { Flex, styled, Text } from '@traefik-labs/faency'
import { ReactNode } from 'react'
import { colorByStatus, iconByStatus, StatusType } from 'components/resources/Status'

View file

@ -1,4 +1,4 @@
import { Badge, Text } from '@traefiklabs/faency'
import { Badge, Text } from '@traefik-labs/faency'
import { FiInfo } from 'react-icons/fi'
import { DetailSection, ItemBlock, LayoutTwoCols, ProviderName } from './DetailSections'

View file

@ -1,4 +1,4 @@
import { Box, CSS } from '@traefiklabs/faency'
import { Box, CSS } from '@traefik-labs/faency'
import { ReactNode } from 'react'
import { FiAlertCircle, FiAlertTriangle, FiCheckCircle } from 'react-icons/fi'

View file

@ -1,4 +1,4 @@
import { Badge, Box, Flex, Text } from '@traefiklabs/faency'
import { Badge, Box, Flex, Text } from '@traefik-labs/faency'
import { FiShield } from 'react-icons/fi'
import { BooleanState, DetailSection, EmptyPlaceholder, ItemBlock } from './DetailSections'

View file

@ -1,4 +1,4 @@
import { Box, Card, Flex, H3, Skeleton, styled, Text } from '@traefiklabs/faency'
import { Box, Card, Flex, H3, Skeleton, styled, Text } from '@traefik-labs/faency'
import { Chart as ChartJs, ArcElement, Tooltip } from 'chart.js'
import { ReactNode, useEffect, useMemo, useState } from 'react'
import { Doughnut } from 'react-chartjs-2'

View file

@ -1,4 +1,4 @@
import { AriaTable, AriaTbody, AriaTd, AriaTh, AriaThead, AriaTr, Box, Flex, styled } from '@traefiklabs/faency'
import { AriaTable, AriaTbody, AriaTd, AriaTh, AriaThead, AriaTr, Box, Flex, styled } from '@traefik-labs/faency'
import { orderBy } from 'lodash'
import { useContext, useEffect, useMemo } from 'react'
import { useSearchParams } from 'react-router-dom'

View file

@ -1,4 +1,4 @@
import { AriaTh, CSS, Flex, Label } from '@traefiklabs/faency'
import { AriaTh, CSS, Flex, Label } from '@traefik-labs/faency'
import { useCallback, useMemo } from 'react'
import { useSearchParams } from 'react-router-dom'

View file

@ -1,4 +1,4 @@
import { AriaTd, AriaTr } from '@traefiklabs/faency'
import { AriaTd, AriaTr } from '@traefik-labs/faency'
import { stringify } from 'query-string'
import { ReactNode } from 'react'
import useSWRInfinite, { SWRInfiniteConfiguration } from 'swr/infinite'

View file

@ -80,7 +80,7 @@ export type ServiceDetailType = {
mirrors?: Mirror[]
}
loadBalancer?: {
servers?: { url: string }[]
servers?: { url: string; address?: string; weight?: number }[]
passHostHeader?: boolean
terminationDelay?: number
healthCheck?: {

View file

@ -1,4 +1,4 @@
import { Flex, styled } from '@traefiklabs/faency'
import { Flex, styled } from '@traefik-labs/faency'
import breakpoints from 'utils/breakpoints'

View file

@ -1,4 +1,4 @@
import { AriaTd, Flex, Text } from '@traefiklabs/faency'
import { AriaTd, Flex, Text } from '@traefik-labs/faency'
import { FiAlertTriangle } from 'react-icons/fi'
type EmptyPlaceholderProps = {

View file

@ -1,4 +1,4 @@
import { Box, Button, Text } from '@traefiklabs/faency'
import { Box, Button, Text } from '@traefik-labs/faency'
import { FallbackProps } from 'react-error-boundary'
const ErrorFallback = ({ error, resetErrorBoundary }: FallbackProps) => {

View file

@ -19,7 +19,7 @@ import {
Text,
Tooltip,
VisuallyHidden,
} from '@traefiklabs/faency'
} from '@traefik-labs/faency'
import { useContext, useEffect, useMemo, useState } from 'react'
import { Helmet } from 'react-helmet-async'
import { BsChevronDoubleRight, BsChevronDoubleLeft } from 'react-icons/bs'

View file

@ -1,4 +1,4 @@
import { Flex, globalCss, styled } from '@traefiklabs/faency'
import { Flex, globalCss, styled } from '@traefik-labs/faency'
import { ReactNode, useMemo, useState } from 'react'
import { Helmet } from 'react-helmet-async'
import { useLocation } from 'react-router-dom'

View file

@ -1,4 +1,4 @@
import { Box, Button, Flex, H1, Text } from '@traefiklabs/faency'
import { Box, Button, Flex, H1, Text } from '@traefik-labs/faency'
import { Helmet } from 'react-helmet-async'
import { useNavigate } from 'react-router-dom'

View file

@ -1,4 +1,4 @@
import { Card, CSS, Flex, Grid, H2, Text } from '@traefiklabs/faency'
import { Card, CSS, Flex, Grid, H2, Text } from '@traefik-labs/faency'
import { ReactNode, useMemo } from 'react'
import { Helmet } from 'react-helmet-async'
import useSWR from 'swr'

View file

@ -1,4 +1,4 @@
import { Box, Card, H1, Skeleton, styled, Text } from '@traefiklabs/faency'
import { Box, Card, H1, Skeleton, styled, Text } from '@traefik-labs/faency'
import { Helmet } from 'react-helmet-async'
import { useParams } from 'react-router-dom'

View file

@ -1,4 +1,4 @@
import { AriaTable, AriaTbody, AriaTd, AriaTfoot, AriaThead, AriaTr, Box, Flex } from '@traefiklabs/faency'
import { AriaTable, AriaTbody, AriaTd, AriaTfoot, AriaThead, AriaTr, Box, Flex } from '@traefik-labs/faency'
import { useMemo } from 'react'
import { Helmet } from 'react-helmet-async'
import useInfiniteScroll from 'react-infinite-scroll-hook'

View file

@ -1,4 +1,4 @@
import { Flex, styled, Text } from '@traefiklabs/faency'
import { Flex, styled, Text } from '@traefik-labs/faency'
import { useContext, useEffect, useMemo } from 'react'
import { Helmet } from 'react-helmet-async'
import { FiGlobe, FiLayers, FiLogIn, FiZap } from 'react-icons/fi'

View file

@ -1,4 +1,4 @@
import { AriaTable, AriaTbody, AriaTd, AriaTfoot, AriaThead, AriaTr, Box, Flex } from '@traefiklabs/faency'
import { AriaTable, AriaTbody, AriaTd, AriaTfoot, AriaThead, AriaTr, Box, Flex } from '@traefik-labs/faency'
import { useMemo } from 'react'
import { Helmet } from 'react-helmet-async'
import { FiShield } from 'react-icons/fi'

View file

@ -94,6 +94,7 @@ describe('<HttpServicePage />', () => {
const serversList = getByTestId('servers-list')
expect(serversList.childNodes.length).toBe(1)
expect(serversList.innerHTML).toContain('http://10.0.1.12:80')
expect(serversList.innerHTML).toContain('1')
const routersTable = getByTestId('routers-table')
const tableBody = routersTable.querySelectorAll('div[role="rowgroup"]')[1]
@ -110,6 +111,47 @@ describe('<HttpServicePage />', () => {
}).toThrow('Unable to find an element by: [data-testid="mirror-services"]')
})
it('should render a service with server weights', async () => {
const mockData = {
loadBalancer: {
servers: [
{
url: 'http://10.0.1.12:80',
weight: 3,
},
{
url: 'http://10.0.1.13:80',
weight: 5,
},
],
passHostHeader: true,
},
status: 'enabled',
usedBy: [],
serverStatus: {
'http://10.0.1.12:80': 'UP',
'http://10.0.1.13:80': 'UP',
},
name: 'service-weighted',
provider: 'docker',
type: 'loadbalancer',
routers: [],
}
const { getByTestId } = renderWithProviders(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
<HttpServiceRender name="mock-service" data={mockData as any} error={undefined} />,
{ route: '/http/services/mock-service', withPage: true },
)
const serversList = getByTestId('servers-list')
expect(serversList.childNodes.length).toBe(2)
expect(serversList.innerHTML).toContain('http://10.0.1.12:80')
expect(serversList.innerHTML).toContain('http://10.0.1.13:80')
expect(serversList.innerHTML).toContain('3')
expect(serversList.innerHTML).toContain('5')
})
it('should render a service with health check', async () => {
const mockData = {
loadBalancer: {

View file

@ -1,4 +1,4 @@
import { Badge, Box, Flex, H1, Skeleton, styled, Text } from '@traefiklabs/faency'
import { Badge, Box, Flex, H1, Skeleton, styled, Text } from '@traefik-labs/faency'
import { useMemo } from 'react'
import { Helmet } from 'react-helmet-async'
import { FiGlobe, FiInfo, FiShield } from 'react-icons/fi'
@ -68,30 +68,25 @@ const GridTitle = styled(Text, {
type Server = {
url: string
address?: string
weight?: number
}
type ServerStatus = {
[server: string]: string
}
function getServerStatusList(data: ServiceDetailType): ServerStatus {
const serversList: ServerStatus = {}
data.loadBalancer?.servers?.forEach((server: Server) => {
serversList[server.address || server.url] = 'DOWN'
})
if (data.serverStatus) {
Object.entries(data.serverStatus).forEach(([server, status]) => {
serversList[server] = status
})
}
return serversList
type ServerInfo = {
url: string
status: string
weight?: number
}
export const ServicePanels = ({ data, protocol = '' }: DetailProps) => {
const serversList = getServerStatusList(data)
const serversList = useMemo<ServerInfo[]>(
() =>
data.loadBalancer?.servers?.map((server: Server) => ({
url: server.address || server.url,
status: data.serverStatus?.[server.address || server.url] || 'DOWN',
weight: server.weight,
})) || [],
[data.loadBalancer?.servers, data.serverStatus],
)
const getProviderFromName = (serviceName: string): string => {
const [, provider] = serviceName.split('@')
return provider || data.provider
@ -217,22 +212,27 @@ export const ServicePanels = ({ data, protocol = '' }: DetailProps) => {
</>
</DetailSection>
)}
{Object.keys(serversList).length > 0 && (
{serversList.length > 0 && (
<DetailSection narrow icon={<FiGlobe size={20} />} title="Servers" noPadding>
<>
<ServersGrid css={{ gridTemplateColumns: protocol === 'http' ? '25% auto' : 'inherit', mt: '$2' }}>
<ServersGrid css={{ gridTemplateColumns: protocol === 'http' ? '20% 60% 20%' : '80% 20%', mt: '$2' }}>
{protocol === 'http' && <ItemTitle css={{ mb: 0 }}>Status</ItemTitle>}
<ItemTitle css={{ mb: 0 }}>URL</ItemTitle>
<ItemTitle css={{ mb: 0, textAlign: 'center' }}>Weight</ItemTitle>
</ServersGrid>
<Box data-testid="servers-list">
{Object.entries(serversList).map(([server, status]) => (
<ServersGrid key={server} css={{ gridTemplateColumns: protocol === 'http' ? '25% auto' : 'inherit' }}>
{protocol === 'http' && <ResourceStatus status={status === 'UP' ? 'enabled' : 'disabled'} />}
{serversList.map((server) => (
<ServersGrid
key={server.url}
css={{ gridTemplateColumns: protocol === 'http' ? '20% 60% 20%' : '80% 20%' }}
>
{protocol === 'http' && <ResourceStatus status={server.status === 'UP' ? 'enabled' : 'disabled'} />}
<Box>
<Tooltip label={server} action="copy">
<Text>{server}</Text>
<Tooltip label={server.url} action="copy">
<Text>{server.url}</Text>
</Tooltip>
</Box>
<Text css={{ textAlign: 'center' }}>{server.weight ?? 1}</Text>
</ServersGrid>
))}
</Box>

View file

@ -1,4 +1,4 @@
import { AriaTable, AriaTbody, AriaTd, AriaTfoot, AriaThead, AriaTr, Box, Flex, Text } from '@traefiklabs/faency'
import { AriaTable, AriaTbody, AriaTd, AriaTfoot, AriaThead, AriaTr, Box, Flex, Text } from '@traefik-labs/faency'
import { useMemo } from 'react'
import { Helmet } from 'react-helmet-async'
import useInfiniteScroll from 'react-infinite-scroll-hook'

View file

@ -1,4 +1,4 @@
import { Box, Flex, Image, Link, Text } from '@traefiklabs/faency'
import { Box, Flex, Image, Link, Text } from '@traefik-labs/faency'
import { useMemo, useEffect, useState } from 'react'
import { Helmet } from 'react-helmet-async'
import { useParams } from 'react-router-dom'

View file

@ -1,4 +1,4 @@
import { Badge, Box, Flex, Text } from '@traefiklabs/faency'
import { Badge, Box, Flex, Text } from '@traefik-labs/faency'
import { useContext, useState } from 'react'
import { BsChevronRight } from 'react-icons/bs'

View file

@ -1,4 +1,4 @@
import { Flex } from '@traefiklabs/faency'
import { Flex } from '@traefik-labs/faency'
import { useId } from 'react'
import { CustomIconProps } from 'components/icons'

View file

@ -1,4 +1,4 @@
import { Flex } from '@traefiklabs/faency'
import { Flex } from '@traefik-labs/faency'
import { CustomIconProps } from 'components/icons'

View file

@ -1,4 +1,4 @@
import { Flex } from '@traefiklabs/faency'
import { Flex } from '@traefik-labs/faency'
import { useId } from 'react'
import { CustomIconProps } from 'components/icons'

View file

@ -1,4 +1,4 @@
import { Flex } from '@traefiklabs/faency'
import { Flex } from '@traefik-labs/faency'
import { useId } from 'react'
import { CustomIconProps } from 'components/icons'

View file

@ -1,4 +1,4 @@
import { Card, Box, H1, Skeleton, styled, Text } from '@traefiklabs/faency'
import { Card, Box, H1, Skeleton, styled, Text } from '@traefik-labs/faency'
import { Helmet } from 'react-helmet-async'
import { useParams } from 'react-router-dom'

View file

@ -1,4 +1,4 @@
import { AriaTable, AriaTbody, AriaTd, AriaTfoot, AriaThead, AriaTr, Box, Flex } from '@traefiklabs/faency'
import { AriaTable, AriaTbody, AriaTd, AriaTfoot, AriaThead, AriaTr, Box, Flex } from '@traefik-labs/faency'
import { useMemo } from 'react'
import { Helmet } from 'react-helmet-async'
import useInfiniteScroll from 'react-infinite-scroll-hook'

View file

@ -1,4 +1,4 @@
import { Flex, styled, Text } from '@traefiklabs/faency'
import { Flex, styled, Text } from '@traefik-labs/faency'
import { Helmet } from 'react-helmet-async'
import { useParams } from 'react-router-dom'

View file

@ -1,4 +1,4 @@
import { AriaTable, AriaTbody, AriaTd, AriaTfoot, AriaThead, AriaTr, Box, Flex } from '@traefiklabs/faency'
import { AriaTable, AriaTbody, AriaTd, AriaTfoot, AriaThead, AriaTr, Box, Flex } from '@traefik-labs/faency'
import { useMemo } from 'react'
import { Helmet } from 'react-helmet-async'
import { FiShield } from 'react-icons/fi'

View file

@ -106,6 +106,7 @@ describe('<TcpServicePage />', () => {
const serversList = getByTestId('tcp-servers-list')
expect(serversList.childNodes.length).toBe(1)
expect(serversList.innerHTML).toContain('http://10.0.1.12:80')
expect(serversList.innerHTML).toContain('1')
const routersTable = getByTestId('routers-table')
const tableBody = routersTable.querySelectorAll('div[role="rowgroup"]')[1]
@ -116,6 +117,11 @@ describe('<TcpServicePage />', () => {
it('should render the service servers from the serverStatus property', async () => {
const mockData = {
loadBalancer: {
servers: [
{
address: 'http://10.0.1.12:81',
},
],
terminationDelay: 10,
},
status: 'enabled',
@ -189,6 +195,47 @@ describe('<TcpServicePage />', () => {
}).toThrow('Unable to find an element by: [data-testid="routers-table"]')
})
it('should render the service with server weights', async () => {
const mockData = {
loadBalancer: {
servers: [
{
address: '10.0.1.12:80',
weight: 3,
},
{
address: '10.0.1.13:80',
weight: 7,
},
],
terminationDelay: 10,
},
serverStatus: {
'10.0.1.12:80': 'UP',
'10.0.1.13:80': 'UP',
},
status: 'enabled',
usedBy: [],
name: 'service-weighted-servers',
provider: 'docker',
type: 'loadbalancer',
routers: [],
}
const { getByTestId } = renderWithProviders(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
<TcpServiceRender name="mock-service" data={mockData as any} error={undefined} />,
{ route: '/tcp/services/mock-service', withPage: true },
)
const serversList = getByTestId('tcp-servers-list')
expect(serversList.childNodes.length).toBe(2)
expect(serversList.innerHTML).toContain('10.0.1.12:80')
expect(serversList.innerHTML).toContain('10.0.1.13:80')
expect(serversList.innerHTML).toContain('3')
expect(serversList.innerHTML).toContain('7')
})
it('should render weighted services', async () => {
const mockData = {
weighted: {

View file

@ -1,4 +1,4 @@
import { Box, Flex, H1, Skeleton, styled, Text } from '@traefiklabs/faency'
import { Box, Flex, H1, Skeleton, styled, Text } from '@traefik-labs/faency'
import { useMemo } from 'react'
import { Helmet } from 'react-helmet-async'
import { FiGlobe, FiInfo, FiShield } from 'react-icons/fi'
@ -52,10 +52,14 @@ const GridTitle = styled(Text, {
type TcpServer = {
address: string
url?: string
weight?: number
}
type ServerStatus = {
[server: string]: string
type ServerInfo = {
address: string
status: string
weight?: number
}
type TcpHealthCheck = {
@ -67,28 +71,19 @@ type TcpHealthCheck = {
timeout?: string
}
function getTcpServerStatusList(data: ServiceDetailType): ServerStatus {
const serversList: ServerStatus = {}
data.loadBalancer?.servers?.forEach((server: any) => {
// TCP servers should have address, but handle both url and address for compatibility
const serverKey = (server as TcpServer).address || (server as any).url
if (serverKey) {
serversList[serverKey] = 'DOWN'
}
})
if (data.serverStatus) {
Object.entries(data.serverStatus).forEach(([server, status]) => {
serversList[server] = status
})
}
return serversList
}
export const TcpServicePanels = ({ data }: TcpDetailProps) => {
const serversList = getTcpServerStatusList(data)
const serversList = useMemo<ServerInfo[]>(
() =>
data.loadBalancer?.servers?.map((server: TcpServer) => {
const address = server.address || server.url
return {
address: address!,
status: (address && data.serverStatus?.[address]) || 'DOWN',
weight: server.weight,
}
}) || [],
[data.loadBalancer?.servers, data.serverStatus],
)
const getProviderFromName = (serviceName: string): string => {
const [, provider] = serviceName.split('@')
return provider || data.provider
@ -203,22 +198,24 @@ export const TcpServicePanels = ({ data }: TcpDetailProps) => {
</>
</DetailSection>
)}
{Object.keys(serversList).length > 0 && (
{serversList.length > 0 && (
<DetailSection narrow icon={<FiGlobe size={20} />} title="Servers" noPadding>
<>
<ServersGrid css={{ gridTemplateColumns: '25% auto', mt: '$2' }}>
<ServersGrid css={{ gridTemplateColumns: '20% 60% 20%', mt: '$2' }}>
<ItemTitle css={{ mb: 0 }}>Status</ItemTitle>
<ItemTitle css={{ mb: 0 }}>Address</ItemTitle>
<ItemTitle css={{ mb: 0, textAlign: 'center' }}>Weight</ItemTitle>
</ServersGrid>
<Box data-testid="tcp-servers-list">
{Object.entries(serversList).map(([server, status]) => (
<ServersGrid key={server} css={{ gridTemplateColumns: '25% auto' }}>
<ResourceStatus status={status === 'UP' ? 'enabled' : 'disabled'} />
{serversList.map((server) => (
<ServersGrid key={server.address} css={{ gridTemplateColumns: '20% 60% 20%' }}>
<ResourceStatus status={server.status === 'UP' ? 'enabled' : 'disabled'} />
<Box>
<Tooltip label={server} action="copy">
<Text>{server}</Text>
<Tooltip label={server.address} action="copy">
<Text>{server.address}</Text>
</Tooltip>
</Box>
<Text css={{ textAlign: 'center' }}>{server.weight ?? 1}</Text>
</ServersGrid>
))}
</Box>

View file

@ -1,4 +1,4 @@
import { AriaTable, AriaTbody, AriaTd, AriaTfoot, AriaThead, AriaTr, Box, Flex, Text } from '@traefiklabs/faency'
import { AriaTable, AriaTbody, AriaTd, AriaTfoot, AriaThead, AriaTr, Box, Flex, Text } from '@traefik-labs/faency'
import { useMemo } from 'react'
import { Helmet } from 'react-helmet-async'
import useInfiniteScroll from 'react-infinite-scroll-hook'

View file

@ -1,4 +1,4 @@
import { Flex, styled, Text } from '@traefiklabs/faency'
import { Flex, styled, Text } from '@traefik-labs/faency'
import { Helmet } from 'react-helmet-async'
import { useParams } from 'react-router-dom'

View file

@ -1,4 +1,4 @@
import { AriaTable, AriaTbody, AriaTd, AriaTfoot, AriaThead, AriaTr, Box, Flex } from '@traefiklabs/faency'
import { AriaTable, AriaTbody, AriaTd, AriaTfoot, AriaThead, AriaTr, Box, Flex } from '@traefik-labs/faency'
import { useMemo } from 'react'
import { Helmet } from 'react-helmet-async'
import useInfiniteScroll from 'react-infinite-scroll-hook'

View file

@ -94,6 +94,11 @@ describe('<UdpServicePage />', () => {
it('should render the service servers from the serverStatus property', async () => {
const mockData = {
loadBalancer: {
servers: [
{
address: 'http://10.0.1.12:81',
},
],
terminationDelay: 10,
},
status: 'enabled',

View file

@ -1,4 +1,4 @@
import { Flex, H1, Skeleton, styled, Text } from '@traefiklabs/faency'
import { Flex, H1, Skeleton, styled, Text } from '@traefik-labs/faency'
import { Helmet } from 'react-helmet-async'
import { useParams } from 'react-router-dom'
@ -60,7 +60,7 @@ export const UdpServiceRender = ({ data, error, name }: UdpServiceRenderProps) =
<title>{data.name} - Traefik Proxy</title>
</Helmet>
<H1 css={{ mb: '$7' }}>{data.name}</H1>
<ServicePanels data={data} />
<ServicePanels data={data} protocol="udp" />
<UsedByRoutersSection data={data} protocol="udp" />
</>
)

View file

@ -1,4 +1,4 @@
import { AriaTable, AriaTbody, AriaTd, AriaTfoot, AriaThead, AriaTr, Box, Flex, Text } from '@traefiklabs/faency'
import { AriaTable, AriaTbody, AriaTd, AriaTfoot, AriaThead, AriaTr, Box, Flex, Text } from '@traefik-labs/faency'
import { useMemo } from 'react'
import { Helmet } from 'react-helmet-async'
import useInfiniteScroll from 'react-infinite-scroll-hook'

View file

@ -1,5 +1,5 @@
import { cleanup, render } from '@testing-library/react'
import { FaencyProvider } from '@traefiklabs/faency'
import { FaencyProvider } from '@traefik-labs/faency'
import { HelmetProvider } from 'react-helmet-async'
import { MemoryRouter } from 'react-router-dom'
import { SWRConfig } from 'swr'

File diff suppressed because it is too large Load diff