mirror of
https://github.com/traefik/traefik.git
synced 2026-02-03 20:39:51 -05:00
Merge 0560896db0 into 29d1c751c1
This commit is contained in:
commit
84f07f4cd5
66 changed files with 614 additions and 653 deletions
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Button } from '@traefiklabs/faency'
|
||||
import { Button } from '@traefik-labs/faency'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
|
||||
export const ScrollTopButton = () => {
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Flex } from '@traefiklabs/faency'
|
||||
import { Flex } from '@traefik-labs/faency'
|
||||
import { useContext } from 'react'
|
||||
|
||||
import { Toast } from './Toast'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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> & {
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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> & {
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Text } from '@traefiklabs/faency'
|
||||
import { Text } from '@traefik-labs/faency'
|
||||
import { ReactNode } from 'react'
|
||||
|
||||
import { BooleanState, ItemBlock } from './DetailSections'
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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?: {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Flex, styled } from '@traefiklabs/faency'
|
||||
import { Flex, styled } from '@traefik-labs/faency'
|
||||
|
||||
import breakpoints from 'utils/breakpoints'
|
||||
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Flex } from '@traefiklabs/faency'
|
||||
import { Flex } from '@traefik-labs/faency'
|
||||
import { useId } from 'react'
|
||||
|
||||
import { CustomIconProps } from 'components/icons'
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Flex } from '@traefiklabs/faency'
|
||||
import { Flex } from '@traefik-labs/faency'
|
||||
|
||||
import { CustomIconProps } from 'components/icons'
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Flex } from '@traefiklabs/faency'
|
||||
import { Flex } from '@traefik-labs/faency'
|
||||
import { useId } from 'react'
|
||||
|
||||
import { CustomIconProps } from 'components/icons'
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Flex } from '@traefiklabs/faency'
|
||||
import { Flex } from '@traefik-labs/faency'
|
||||
import { useId } from 'react'
|
||||
|
||||
import { CustomIconProps } from 'components/icons'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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" />
|
||||
</>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
938
webui/yarn.lock
938
webui/yarn.lock
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue