From 0dfb4bc22cf4eb7aa74fb891af1b27cba3b8d592 Mon Sep 17 00:00:00 2001 From: farodin91 Date: Thu, 7 Nov 2024 23:08:28 +0100 Subject: [PATCH] frontend: add support for gateway-api Signed-off-by: farodin91 --- ...r.InClusterSidebarClosed.stories.storyshot | 132 +++ ...bar.InClusterSidebarOpen.stories.storyshot | 139 +++ ...edItemWithSidebarOmitted.stories.storyshot | 139 +++ .../src/components/Sidebar/prepareRoutes.ts | 23 + .../gateway/ClassDetails.stories.tsx | 60 ++ .../src/components/gateway/ClassDetails.tsx | 38 + .../components/gateway/ClassList.stories.tsx | 43 + frontend/src/components/gateway/ClassList.tsx | 75 ++ .../gateway/GRPCRouteDetails.stories.tsx | 66 ++ .../components/gateway/GRPCRouteDetails.tsx | 26 + .../gateway/GRPCRouteList.stories.tsx | 45 + .../src/components/gateway/GRPCRouteList.tsx | 15 + .../gateway/GatewayDetails.stories.tsx | 66 ++ .../src/components/gateway/GatewayDetails.tsx | 138 +++ .../gateway/GatewayList.stories.tsx | 45 + .../src/components/gateway/GatewayList.tsx | 47 + .../gateway/HTTPRouteDetails.stories.tsx | 66 ++ .../components/gateway/HTTPRouteDetails.tsx | 71 ++ .../gateway/HTTPRouteList.stories.tsx | 45 + .../src/components/gateway/HTTPRouteList.tsx | 34 + .../ClassDetails.Basic.stories.storyshot | 288 ++++++ .../ClassList.Items.stories.storyshot | 707 +++++++++++++ .../GRPCRouteDetails.Basic.stories.storyshot | 343 ++++++ .../GRPCRouteList.Items.stories.storyshot | 718 +++++++++++++ .../GatewayDetails.Basic.stories.storyshot | 429 ++++++++ .../GatewayList.Items.stories.storyshot | 976 ++++++++++++++++++ .../HTTPRouteDetails.Basic.stories.storyshot | 335 ++++++ .../HTTPRouteList.Items.stories.storyshot | 894 ++++++++++++++++ .../src/components/gateway/storyHelper.ts | 104 ++ frontend/src/components/gateway/utils.tsx | 49 + frontend/src/i18n/locales/de/glossary.json | 18 +- frontend/src/i18n/locales/de/translation.json | 15 +- frontend/src/i18n/locales/en/glossary.json | 18 +- frontend/src/i18n/locales/en/translation.json | 15 +- frontend/src/i18n/locales/es/glossary.json | 18 +- frontend/src/i18n/locales/es/translation.json | 15 +- frontend/src/i18n/locales/fr/glossary.json | 18 +- frontend/src/i18n/locales/fr/translation.json | 15 +- frontend/src/i18n/locales/pt/glossary.json | 18 +- frontend/src/i18n/locales/pt/translation.json | 15 +- frontend/src/lib/k8s/gateway.ts | 80 ++ frontend/src/lib/k8s/gatewayClass.ts | 40 + frontend/src/lib/k8s/grpcRoute.ts | 29 + frontend/src/lib/k8s/httpRoute.ts | 46 + frontend/src/lib/k8s/index.test.ts | 4 + frontend/src/lib/k8s/index.ts | 8 + frontend/src/lib/router.tsx | 65 ++ .../plugin/__snapshots__/pluginLib.snapshot | 4 + 48 files changed, 6572 insertions(+), 25 deletions(-) create mode 100644 frontend/src/components/gateway/ClassDetails.stories.tsx create mode 100644 frontend/src/components/gateway/ClassDetails.tsx create mode 100644 frontend/src/components/gateway/ClassList.stories.tsx create mode 100644 frontend/src/components/gateway/ClassList.tsx create mode 100644 frontend/src/components/gateway/GRPCRouteDetails.stories.tsx create mode 100644 frontend/src/components/gateway/GRPCRouteDetails.tsx create mode 100644 frontend/src/components/gateway/GRPCRouteList.stories.tsx create mode 100644 frontend/src/components/gateway/GRPCRouteList.tsx create mode 100644 frontend/src/components/gateway/GatewayDetails.stories.tsx create mode 100644 frontend/src/components/gateway/GatewayDetails.tsx create mode 100644 frontend/src/components/gateway/GatewayList.stories.tsx create mode 100644 frontend/src/components/gateway/GatewayList.tsx create mode 100644 frontend/src/components/gateway/HTTPRouteDetails.stories.tsx create mode 100644 frontend/src/components/gateway/HTTPRouteDetails.tsx create mode 100644 frontend/src/components/gateway/HTTPRouteList.stories.tsx create mode 100644 frontend/src/components/gateway/HTTPRouteList.tsx create mode 100644 frontend/src/components/gateway/__snapshots__/ClassDetails.Basic.stories.storyshot create mode 100644 frontend/src/components/gateway/__snapshots__/ClassList.Items.stories.storyshot create mode 100644 frontend/src/components/gateway/__snapshots__/GRPCRouteDetails.Basic.stories.storyshot create mode 100644 frontend/src/components/gateway/__snapshots__/GRPCRouteList.Items.stories.storyshot create mode 100644 frontend/src/components/gateway/__snapshots__/GatewayDetails.Basic.stories.storyshot create mode 100644 frontend/src/components/gateway/__snapshots__/GatewayList.Items.stories.storyshot create mode 100644 frontend/src/components/gateway/__snapshots__/HTTPRouteDetails.Basic.stories.storyshot create mode 100644 frontend/src/components/gateway/__snapshots__/HTTPRouteList.Items.stories.storyshot create mode 100644 frontend/src/components/gateway/storyHelper.ts create mode 100644 frontend/src/components/gateway/utils.tsx create mode 100644 frontend/src/lib/k8s/gateway.ts create mode 100644 frontend/src/lib/k8s/gatewayClass.ts create mode 100644 frontend/src/lib/k8s/grpcRoute.ts create mode 100644 frontend/src/lib/k8s/httpRoute.ts diff --git a/frontend/src/components/Sidebar/__snapshots__/Sidebar.InClusterSidebarClosed.stories.storyshot b/frontend/src/components/Sidebar/__snapshots__/Sidebar.InClusterSidebarClosed.stories.storyshot index 4146ec27ef..b1c907f256 100644 --- a/frontend/src/components/Sidebar/__snapshots__/Sidebar.InClusterSidebarClosed.stories.storyshot +++ b/frontend/src/components/Sidebar/__snapshots__/Sidebar.InClusterSidebarClosed.stories.storyshot @@ -592,6 +592,138 @@ +
  • + +
  • +
  • + +
  • diff --git a/frontend/src/components/Sidebar/__snapshots__/Sidebar.InClusterSidebarOpen.stories.storyshot b/frontend/src/components/Sidebar/__snapshots__/Sidebar.InClusterSidebarOpen.stories.storyshot index 67544741f4..19e916b6c2 100644 --- a/frontend/src/components/Sidebar/__snapshots__/Sidebar.InClusterSidebarOpen.stories.storyshot +++ b/frontend/src/components/Sidebar/__snapshots__/Sidebar.InClusterSidebarOpen.stories.storyshot @@ -627,6 +627,145 @@
  • +
  • + +
  • +
  • + +
  • diff --git a/frontend/src/components/Sidebar/__snapshots__/Sidebar.SelectedItemWithSidebarOmitted.stories.storyshot b/frontend/src/components/Sidebar/__snapshots__/Sidebar.SelectedItemWithSidebarOmitted.stories.storyshot index 25cd091be7..dedab4e13b 100644 --- a/frontend/src/components/Sidebar/__snapshots__/Sidebar.SelectedItemWithSidebarOmitted.stories.storyshot +++ b/frontend/src/components/Sidebar/__snapshots__/Sidebar.SelectedItemWithSidebarOmitted.stories.storyshot @@ -627,6 +627,145 @@
  • +
  • + +
  • +
  • + +
  • diff --git a/frontend/src/components/Sidebar/prepareRoutes.ts b/frontend/src/components/Sidebar/prepareRoutes.ts index 9216abeee8..7b08045723 100644 --- a/frontend/src/components/Sidebar/prepareRoutes.ts +++ b/frontend/src/components/Sidebar/prepareRoutes.ts @@ -166,6 +166,29 @@ function prepareRoutes( }, ], }, + { + name: 'gateway', + label: t('glossary|Gateway'), + icon: 'mdi:lan-connect', + subList: [ + { + name: 'k8sgateways', + label: t('glossary|Gateways'), + }, + { + name: 'gatewayclasses', + label: t('glossary|Gateway Classes'), + }, + { + name: 'httproutes', + label: t('glossary|HTTP Routes'), + }, + { + name: 'grpcroutes', + label: t('glossary|GRPC Routes'), + }, + ], + }, { name: 'security', label: t('glossary|Security'), diff --git a/frontend/src/components/gateway/ClassDetails.stories.tsx b/frontend/src/components/gateway/ClassDetails.stories.tsx new file mode 100644 index 0000000000..090c96d24e --- /dev/null +++ b/frontend/src/components/gateway/ClassDetails.stories.tsx @@ -0,0 +1,60 @@ +import { Meta, StoryFn } from '@storybook/react'; +import { http, HttpResponse } from 'msw'; +import { TestContext } from '../../test'; +import Details from './ClassDetails'; +import { DEFAULT_GATEWAY_CLASS } from './storyHelper'; + +export default { + title: 'GatewayClass/DetailsView', + component: Details, + argTypes: {}, + decorators: [ + Story => { + return ( + + + + ); + }, + ], + parameters: { + msw: { + handlers: { + storyBase: [ + http.get( + 'http://localhost:4466/apis/gateway.networking.k8s.io/v1beta1/gatewayclasses', + () => HttpResponse.error() + ), + http.get('http://localhost:4466/api/v1/namespaces/default/events', () => + HttpResponse.json({ + kind: 'EventList', + items: [], + metadata: {}, + }) + ), + ], + }, + }, + }, +} as Meta; + +const Template: StoryFn = () => { + return
    ; +}; + +export const Basic = Template.bind({}); +Basic.args = { + gatewayJson: DEFAULT_GATEWAY_CLASS, +}; +Basic.parameters = { + msw: { + handlers: { + story: [ + http.get( + 'http://localhost:4466/apis/gateway.networking.k8s.io/v1beta1/gatewayclasses/default-gateway-class', + () => HttpResponse.json(DEFAULT_GATEWAY_CLASS) + ), + ], + }, + }, +}; diff --git a/frontend/src/components/gateway/ClassDetails.tsx b/frontend/src/components/gateway/ClassDetails.tsx new file mode 100644 index 0000000000..a35e1e8803 --- /dev/null +++ b/frontend/src/components/gateway/ClassDetails.tsx @@ -0,0 +1,38 @@ +import { useTranslation } from 'react-i18next'; +import { useParams } from 'react-router-dom'; +import GatewayClass, { KubeGatewayClass } from '../../lib/k8s/gatewayClass'; +import { ConditionsTable, DetailsGrid } from '../common/Resource'; +import SectionBox from '../common/SectionBox'; + +export default function GatewayClassDetails() { + const { name } = useParams<{ name: string }>(); + const { t } = useTranslation(['glossary', 'translation']); + + return ( + + gatewayClass && [ + { + name: t('Controller Name'), + value: gatewayClass.controllerName, + }, + ] + } + extraSections={(item: KubeGatewayClass) => + item && [ + { + id: 'headlamp.gatewayclass-conditions', + section: ( + + + + ), + }, + ] + } + /> + ); +} diff --git a/frontend/src/components/gateway/ClassList.stories.tsx b/frontend/src/components/gateway/ClassList.stories.tsx new file mode 100644 index 0000000000..4d8c186028 --- /dev/null +++ b/frontend/src/components/gateway/ClassList.stories.tsx @@ -0,0 +1,43 @@ +import { Meta, StoryFn } from '@storybook/react'; +import { http, HttpResponse } from 'msw'; +import { TestContext } from '../../test'; +import ListView from './ClassList'; +import { DEFAULT_GATEWAY_CLASS } from './storyHelper'; + +export default { + title: 'GatewayClass/ListView', + component: ListView, + argTypes: {}, + decorators: [ + Story => { + return ( + + + + ); + }, + ], + parameters: { + msw: { + handlers: { + story: [ + http.get( + 'http://localhost:4466/apis/gateway.networking.k8s.io/v1beta1/gatewayclasses', + () => + HttpResponse.json({ + kind: 'GatewayClassList', + metadata: {}, + items: [DEFAULT_GATEWAY_CLASS], + }) + ), + ], + }, + }, + }, +} as Meta; + +const Template: StoryFn = () => { + return ; +}; + +export const Items = Template.bind({}); diff --git a/frontend/src/components/gateway/ClassList.tsx b/frontend/src/components/gateway/ClassList.tsx new file mode 100644 index 0000000000..8d0696d157 --- /dev/null +++ b/frontend/src/components/gateway/ClassList.tsx @@ -0,0 +1,75 @@ +import { Box } from '@mui/material'; +import { useTranslation } from 'react-i18next'; +import GatewayClass from '../../lib/k8s/gatewayClass'; +import { LightTooltip, StatusLabel, StatusLabelProps } from '../common'; +import ResourceListView from '../common/Resource/ResourceListView'; + +export function makeGatewayStatusLabel(conditions: any[] | null) { + if (!conditions) { + return null; + } + + const conditionOptions = { + Accepted: { + status: 'success', + icon: 'mdi:check-bold', + }, + }; + + const condition = conditions.find( + ({ status, type }: { status: string; type: string }) => + type in conditionOptions && status === 'True' + ); + + if (!condition) { + return null; + } + + const tooltip = ''; + + const conditionInfo = conditionOptions[condition.type as 'Accepted']; + + return ( + + + + {condition.type} + + + + ); +} + +export default function GatewayClassList() { + const { t } = useTranslation('glossary'); + + return ( + gatewayClass.spec?.controllerName, + }, + { + id: 'conditions', + label: t('translation|Conditions'), + getValue: (gatewayClass: GatewayClass) => + gatewayClass.status?.conditions?.find( + ({ status }: { status: string }) => status === 'True' + ) ?? null, + render: (gatewayClass: GatewayClass) => + makeGatewayStatusLabel(gatewayClass.status?.conditions), + }, + 'age', + ]} + /> + ); +} diff --git a/frontend/src/components/gateway/GRPCRouteDetails.stories.tsx b/frontend/src/components/gateway/GRPCRouteDetails.stories.tsx new file mode 100644 index 0000000000..855bc3db5f --- /dev/null +++ b/frontend/src/components/gateway/GRPCRouteDetails.stories.tsx @@ -0,0 +1,66 @@ +import { Meta, StoryFn } from '@storybook/react'; +import { http, HttpResponse } from 'msw'; +import { TestContext } from '../../test'; +import GRPCRouteDetails from './GRPCRouteDetails'; +import { DEFAULT_GRPC_ROUTE } from './storyHelper'; + +export default { + title: 'GRPCRoute/DetailsView', + component: GRPCRouteDetails, + argTypes: {}, + decorators: [ + Story => { + return ( + + + + ); + }, + ], + parameters: { + msw: { + handlers: { + baseStory: [ + http.get('http://localhost:4466/apis/gateway.networking.k8s.io/v1beta1/grpcroutes', () => + HttpResponse.json({}) + ), + http.get('http://localhost:4466/apis/gateway.networking.k8s.io/v1beta1/grpcroutes', () => + HttpResponse.error() + ), + http.get('http://localhost:4466/api/v1/namespaces/default/events', () => + HttpResponse.json({ + kind: 'EventList', + items: [], + metadata: {}, + }) + ), + http.post( + 'http://localhost:4466/apis/authorization.k8s.io/v1/selfsubjectaccessreviews', + () => HttpResponse.json({ status: { allowed: true, reason: '', code: 200 } }) + ), + ], + }, + }, + }, +} as Meta; + +const Template: StoryFn = () => { + return ; +}; + +export const Basic = Template.bind({}); +Basic.args = { + grpcRouteJson: DEFAULT_GRPC_ROUTE, +}; +Basic.parameters = { + msw: { + handlers: { + story: [ + http.get( + 'http://localhost:4466/apis/gateway.networking.k8s.io/v1beta1/grpcroutes/default-grpcroute', + () => HttpResponse.json(DEFAULT_GRPC_ROUTE) + ), + ], + }, + }, +}; diff --git a/frontend/src/components/gateway/GRPCRouteDetails.tsx b/frontend/src/components/gateway/GRPCRouteDetails.tsx new file mode 100644 index 0000000000..d1dce3d079 --- /dev/null +++ b/frontend/src/components/gateway/GRPCRouteDetails.tsx @@ -0,0 +1,26 @@ +import { useParams } from 'react-router-dom'; +import GRPCRoute from '../../lib/k8s/grpcRoute'; +import { DetailsGrid } from '../common/Resource'; +import { GatewayParentRefSection } from './utils'; + +export default function GRPCRouteDetails(props: { name?: string; namespace?: string }) { + const params = useParams<{ namespace: string; name: string }>(); + const { name = params.name, namespace = params.namespace } = props; + + return ( + + item && [ + { + id: 'headlamp.httproute-parentrefs', + section: , + }, + ] + } + /> + ); +} diff --git a/frontend/src/components/gateway/GRPCRouteList.stories.tsx b/frontend/src/components/gateway/GRPCRouteList.stories.tsx new file mode 100644 index 0000000000..844e356e1d --- /dev/null +++ b/frontend/src/components/gateway/GRPCRouteList.stories.tsx @@ -0,0 +1,45 @@ +import { Meta, StoryFn } from '@storybook/react'; +import { http, HttpResponse } from 'msw'; +import { TestContext } from '../../test'; +import ListView from './GRPCRouteList'; +import { DEFAULT_GRPC_ROUTE } from './storyHelper'; + +export default { + title: 'GRPCRoute/ListView', + component: ListView, + argTypes: {}, + decorators: [ + Story => { + return ( + + + + ); + }, + ], + parameters: { + msw: { + handlers: { + storyBase: [], + story: [ + http.get('http://localhost:4466/apis/gateway.networking.k8s.io/v1beta1/grpcroutes', () => + HttpResponse.json({ + kind: 'GRPCRouteList', + metadata: {}, + items: [DEFAULT_GRPC_ROUTE], + }) + ), + http.get('http://localhost:4466/apis/gateway.networking.k8s.io/v1beta1/grpcroutes', () => + HttpResponse.error() + ), + ], + }, + }, + }, +} as Meta; + +const Template: StoryFn = () => { + return ; +}; + +export const Items = Template.bind({}); diff --git a/frontend/src/components/gateway/GRPCRouteList.tsx b/frontend/src/components/gateway/GRPCRouteList.tsx new file mode 100644 index 0000000000..45ebd7a11a --- /dev/null +++ b/frontend/src/components/gateway/GRPCRouteList.tsx @@ -0,0 +1,15 @@ +import { useTranslation } from 'react-i18next'; +import GRPCRoute from '../../lib/k8s/grpcRoute'; +import ResourceListView from '../common/Resource/ResourceListView'; + +export default function GRPCRouteList() { + const { t } = useTranslation(['glossary', 'translation']); + + return ( + + ); +} diff --git a/frontend/src/components/gateway/GatewayDetails.stories.tsx b/frontend/src/components/gateway/GatewayDetails.stories.tsx new file mode 100644 index 0000000000..7ab814e162 --- /dev/null +++ b/frontend/src/components/gateway/GatewayDetails.stories.tsx @@ -0,0 +1,66 @@ +import { Meta, StoryFn } from '@storybook/react'; +import { http, HttpResponse } from 'msw'; +import { TestContext } from '../../test'; +import GatewayDetails from './GatewayDetails'; +import { DEFAULT_GATEWAY } from './storyHelper'; + +export default { + title: 'Gateway/DetailsView', + component: GatewayDetails, + argTypes: {}, + decorators: [ + Story => { + return ( + + + + ); + }, + ], + parameters: { + msw: { + handlers: { + baseStory: [ + http.get('http://localhost:4466/apis/gateway.networking.k8s.io/v1beta1/gateways', () => + HttpResponse.json({}) + ), + http.get('http://localhost:4466/apis/gateway.networking.k8s.io/v1beta1/gateways', () => + HttpResponse.error() + ), + http.get('http://localhost:4466/api/v1/namespaces/default/events', () => + HttpResponse.json({ + kind: 'EventList', + items: [], + metadata: {}, + }) + ), + http.post( + 'http://localhost:4466/apis/authorization.k8s.io/v1/selfsubjectaccessreviews', + () => HttpResponse.json({ status: { allowed: true, reason: '', code: 200 } }) + ), + ], + }, + }, + }, +} as Meta; + +const Template: StoryFn = () => { + return ; +}; + +export const Basic = Template.bind({}); +Basic.args = { + gatewayJson: DEFAULT_GATEWAY, +}; +Basic.parameters = { + msw: { + handlers: { + story: [ + http.get( + 'http://localhost:4466/apis/gateway.networking.k8s.io/v1beta1/gateways/default-gateway', + () => HttpResponse.json(DEFAULT_GATEWAY) + ), + ], + }, + }, +}; diff --git a/frontend/src/components/gateway/GatewayDetails.tsx b/frontend/src/components/gateway/GatewayDetails.tsx new file mode 100644 index 0000000000..138be428f7 --- /dev/null +++ b/frontend/src/components/gateway/GatewayDetails.tsx @@ -0,0 +1,138 @@ +import Box from '@mui/system/Box'; +import { useTranslation } from 'react-i18next'; +import { useParams } from 'react-router-dom'; +import { KubeCondition } from '../../lib/k8s/cluster'; +import Gateway, { + GatewayAddress, + GatewayListener, + GatewayListenerStatus, +} from '../../lib/k8s/gateway'; +import { EmptyContent, StatusLabel, StatusLabelProps } from '../common'; +import Link from '../common/Link'; +import { ConditionsTable, DetailsGrid } from '../common/Resource'; +import SectionBox from '../common/SectionBox'; +import SimpleTable, { NameValueTable } from '../common/SimpleTable'; + +function GatewayListenerTable(props: { + listener: GatewayListener; + status: GatewayListenerStatus | null; +}) { + const { listener, status } = props; + const { t } = useTranslation(['glossary', 'translation']); + + function makeStatusLabel(condition: KubeCondition) { + let status: StatusLabelProps['status'] = ''; + if (condition.type === 'Available') { + status = condition.status === 'True' ? 'success' : 'error'; + } + + return ( + ({ paddingRight: theme.spacing(1) })}> + {condition.type} + + ); + } + const mainRows = [ + { + name: listener.name, + withHighlightStyle: true, + }, + { + name: t('translation|Hostname'), + value: listener.hostname, + }, + { + name: t('translation|Port'), + value: listener.port, + }, + { + name: t('translation|Protocol'), + value: listener.protocol, + }, + { + name: t('translation|Conditions'), + value: status?.conditions.map(c => makeStatusLabel(c)), + }, + ]; + return ; +} + +export default function GatewayDetails(props: { name?: string; namespace?: string }) { + const params = useParams<{ namespace: string; name: string }>(); + const { name = params.name, namespace = params.namespace } = props; + const { t } = useTranslation(['glossary', 'translation']); + + return ( + + gateway && [ + { + name: t('Class Name'), + value: gateway.spec?.gatewayClassName ? ( + + {gateway.spec?.gatewayClassName} + + ) : null, + }, + ] + } + extraSections={(item: Gateway) => + item && [ + { + id: 'headlamp.gateway-addresses', + section: item && ( + + data.type, + }, + { + label: t('translation|Value'), + getter: (data: GatewayAddress) => data.value, + }, + ]} + data={item?.getAddresses() || []} + reflectInURL="addresses" + /> + + ), + }, + { + id: 'headlamp.gateway-listeners', + section: item && ( + + {item.getListeners().length === 0 ? ( + {t('No data')} + ) : ( + item + .getListeners() + .map((listener: GatewayListener) => ( + + )) + )} + + ), + }, + { + id: 'headlamp.gateway-conditions', + section: ( + + + + ), + }, + ] + } + /> + ); +} diff --git a/frontend/src/components/gateway/GatewayList.stories.tsx b/frontend/src/components/gateway/GatewayList.stories.tsx new file mode 100644 index 0000000000..aeecc6862c --- /dev/null +++ b/frontend/src/components/gateway/GatewayList.stories.tsx @@ -0,0 +1,45 @@ +import { Meta, StoryFn } from '@storybook/react'; +import { http, HttpResponse } from 'msw'; +import { TestContext } from '../../test'; +import ListView from './GatewayList'; +import { DEFAULT_GATEWAY } from './storyHelper'; + +export default { + title: 'Gateway/ListView', + component: ListView, + argTypes: {}, + decorators: [ + Story => { + return ( + + + + ); + }, + ], + parameters: { + msw: { + handlers: { + storyBase: [], + story: [ + http.get('http://localhost:4466/apis/gateway.networking.k8s.io/v1beta1/gateways', () => + HttpResponse.json({ + kind: 'GatewayList', + metadata: {}, + items: [DEFAULT_GATEWAY], + }) + ), + http.get('http://localhost:4466/apis/gateway.networking.k8s.io/v1beta1/gateways', () => + HttpResponse.error() + ), + ], + }, + }, + }, +} as Meta; + +const Template: StoryFn = () => { + return ; +}; + +export const Items = Template.bind({}); diff --git a/frontend/src/components/gateway/GatewayList.tsx b/frontend/src/components/gateway/GatewayList.tsx new file mode 100644 index 0000000000..94fb4d1ee9 --- /dev/null +++ b/frontend/src/components/gateway/GatewayList.tsx @@ -0,0 +1,47 @@ +import { useTranslation } from 'react-i18next'; +import Gateway from '../../lib/k8s/gateway'; +import Link from '../common/Link'; +import ResourceListView from '../common/Resource/ResourceListView'; +import { makeGatewayStatusLabel } from './ClassList'; + +export default function GatewayList() { + const { t } = useTranslation(['glossary', 'translation']); + + return ( + gateway.spec?.gatewayClassName, + render: gateway => + gateway.spec?.gatewayClassName ? ( + + {gateway.spec?.gatewayClassName} + + ) : null, + }, + { + id: 'conditions', + label: t('translation|Conditions'), + getValue: (gateway: Gateway) => + gateway.status?.conditions?.find( + ({ status }: { status: string }) => status === 'True' + ) ?? null, + render: (gateway: Gateway) => makeGatewayStatusLabel(gateway.status?.conditions), + }, + { + id: 'listeners', + label: t('translation|Listeners'), + getValue: (gateway: Gateway) => gateway.spec.listeners.length, + }, + 'age', + ]} + /> + ); +} diff --git a/frontend/src/components/gateway/HTTPRouteDetails.stories.tsx b/frontend/src/components/gateway/HTTPRouteDetails.stories.tsx new file mode 100644 index 0000000000..8831e25fb1 --- /dev/null +++ b/frontend/src/components/gateway/HTTPRouteDetails.stories.tsx @@ -0,0 +1,66 @@ +import { Meta, StoryFn } from '@storybook/react'; +import { http, HttpResponse } from 'msw'; +import { TestContext } from '../../test'; +import HTTPRouteDetails from './HTTPRouteDetails'; +import { DEFAULT_HTTP_ROUTE } from './storyHelper'; + +export default { + title: 'HTTPRoute/DetailsView', + component: HTTPRouteDetails, + argTypes: {}, + decorators: [ + Story => { + return ( + + + + ); + }, + ], + parameters: { + msw: { + handlers: { + baseStory: [ + http.get('http://localhost:4466/apis/gateway.networking.k8s.io/v1beta1/httproutes', () => + HttpResponse.json({}) + ), + http.get('http://localhost:4466/apis/gateway.networking.k8s.io/v1beta1/httproutes', () => + HttpResponse.error() + ), + http.get('http://localhost:4466/api/v1/namespaces/default/events', () => + HttpResponse.json({ + kind: 'EventList', + items: [], + metadata: {}, + }) + ), + http.post( + 'http://localhost:4466/apis/authorization.k8s.io/v1/selfsubjectaccessreviews', + () => HttpResponse.json({ status: { allowed: true, reason: '', code: 200 } }) + ), + ], + }, + }, + }, +} as Meta; + +const Template: StoryFn = () => { + return ; +}; + +export const Basic = Template.bind({}); +Basic.args = { + httpRouteJson: DEFAULT_HTTP_ROUTE, +}; +Basic.parameters = { + msw: { + handlers: { + story: [ + http.get( + 'http://localhost:4466/apis/gateway.networking.k8s.io/v1beta1/httproutes/default-httproute', + () => HttpResponse.json(DEFAULT_HTTP_ROUTE) + ), + ], + }, + }, +}; diff --git a/frontend/src/components/gateway/HTTPRouteDetails.tsx b/frontend/src/components/gateway/HTTPRouteDetails.tsx new file mode 100644 index 0000000000..130fe83d4d --- /dev/null +++ b/frontend/src/components/gateway/HTTPRouteDetails.tsx @@ -0,0 +1,71 @@ +import { useTranslation } from 'react-i18next'; +import { useParams } from 'react-router-dom'; +import HTTPRoute, { HTTPRouteRule } from '../../lib/k8s/httpRoute'; +import { EmptyContent, LabelListItem, NameValueTable } from '../common'; +import { DetailsGrid } from '../common/Resource'; +import SectionBox from '../common/SectionBox'; +import { GatewayParentRefSection } from './utils'; + +function HTTPRouteRuleTable(props: { rule: HTTPRouteRule }) { + const { rule } = props; + const { t } = useTranslation(['glossary', 'translation']); + + const mainRows = [ + { + name: t('translation|BackendRefs'), + value: rule.backendRefs?.length, + hide: (rule.backendRefs?.length || 0) === 0, + }, + { + name: t('translation|Matches'), + value: rule.matches?.length, + hide: (rule.matches?.length || 0) === 0, + }, + ]; + return ; +} + +export default function HTTPRouteDetails(props: { name?: string; namespace?: string }) { + const params = useParams<{ namespace: string; name: string }>(); + const { name = params.name, namespace = params.namespace } = props; + const { t } = useTranslation(['glossary', 'translation']); + + return ( + + httpRoute && [ + { + name: 'Hostnames', + value: `${tls}`)} />, + }, + ] + } + withEvents + extraSections={(item: HTTPRoute) => + item && [ + { + id: 'headlamp.httproute-rules', + section: item && ( + + {item.rules.length === 0 ? ( + {t('No data')} + ) : ( + item.rules.map((rule: HTTPRouteRule, index: any) => ( + + )) + )} + + ), + }, + { + id: 'headlamp.httproute-parentrefs', + section: , + }, + ] + } + /> + ); +} diff --git a/frontend/src/components/gateway/HTTPRouteList.stories.tsx b/frontend/src/components/gateway/HTTPRouteList.stories.tsx new file mode 100644 index 0000000000..5c21b2b9b4 --- /dev/null +++ b/frontend/src/components/gateway/HTTPRouteList.stories.tsx @@ -0,0 +1,45 @@ +import { Meta, StoryFn } from '@storybook/react'; +import { http, HttpResponse } from 'msw'; +import { TestContext } from '../../test'; +import ListView from './HTTPRouteList'; +import { DEFAULT_HTTP_ROUTE } from './storyHelper'; + +export default { + title: 'HTTPRoute/ListView', + component: ListView, + argTypes: {}, + decorators: [ + Story => { + return ( + + + + ); + }, + ], + parameters: { + msw: { + handlers: { + storyBase: [], + story: [ + http.get('http://localhost:4466/apis/gateway.networking.k8s.io/v1beta1/httproutes', () => + HttpResponse.json({ + kind: 'HTTPRouteList', + metadata: {}, + items: [DEFAULT_HTTP_ROUTE], + }) + ), + http.get('http://localhost:4466/apis/gateway.networking.k8s.io/v1beta1/httproutes', () => + HttpResponse.error() + ), + ], + }, + }, + }, +} as Meta; + +const Template: StoryFn = () => { + return ; +}; + +export const Items = Template.bind({}); diff --git a/frontend/src/components/gateway/HTTPRouteList.tsx b/frontend/src/components/gateway/HTTPRouteList.tsx new file mode 100644 index 0000000000..654c702ded --- /dev/null +++ b/frontend/src/components/gateway/HTTPRouteList.tsx @@ -0,0 +1,34 @@ +import { useTranslation } from 'react-i18next'; +import HTTPRoute from '../../lib/k8s/httpRoute'; +import { LabelListItem } from '../common'; +import ResourceListView from '../common/Resource/ResourceListView'; + +export default function HTTPRouteList() { + const { t } = useTranslation(['glossary', 'translation']); + + return ( + httpRoute.hostnames.join(''), + render: httpRoute => ( + host || '*')} /> + ), + }, + { + id: 'rules', + label: t('translation|rules'), + getValue: (httpRoute: HTTPRoute) => httpRoute.spec.rules.length, + }, + 'age', + ]} + /> + ); +} diff --git a/frontend/src/components/gateway/__snapshots__/ClassDetails.Basic.stories.storyshot b/frontend/src/components/gateway/__snapshots__/ClassDetails.Basic.stories.storyshot new file mode 100644 index 0000000000..1a54089a8e --- /dev/null +++ b/frontend/src/components/gateway/__snapshots__/ClassDetails.Basic.stories.storyshot @@ -0,0 +1,288 @@ + +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    +

    + GatewayClass +

    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + Name +
    +
    + + default-gateway-class + +
    +
    + Namespace +
    +
    + + default + +
    +
    + Creation +
    +
    + + 2023-07-19T09:48:42.000Z + +
    +
    + Controller Name +
    +
    + + test + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +

    + Conditions +

    +
    +
    +
    +
    +
    +
    +
    +

    + No data to be shown. +

    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +

    + Events +

    +
    +
    +
    +
    +
    +
    +
    +

    + No data to be shown. +

    +
    +
    +
    +
    +
    +
    +
    +
    + \ No newline at end of file diff --git a/frontend/src/components/gateway/__snapshots__/ClassList.Items.stories.storyshot b/frontend/src/components/gateway/__snapshots__/ClassList.Items.stories.storyshot new file mode 100644 index 0000000000..c513429242 --- /dev/null +++ b/frontend/src/components/gateway/__snapshots__/ClassList.Items.stories.storyshot @@ -0,0 +1,707 @@ + +
    +
    +
    +
    +
    +

    + Gateway Classes +

    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    + +
    + +
    +
    + + + +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + +
    +
    + +
    + +
    +
    +
    +
    + +
    + +
    +
    +
    +
    + +
    + +
    +
    +
    +
    + +
    + +
    +
    +
    +
    + +
    +
    +
    + + default-gateway-class + + + test + + +

    + 3mo +

    +
    + +
    +
    +
    +
    + +
    +
    +
    + +
    + + + +
    +
    + + 1-1 of 1 + +
    + + + + + + +
    +
    +
    +
    +
    +
    +
    +
    +
    + \ No newline at end of file diff --git a/frontend/src/components/gateway/__snapshots__/GRPCRouteDetails.Basic.stories.storyshot b/frontend/src/components/gateway/__snapshots__/GRPCRouteDetails.Basic.stories.storyshot new file mode 100644 index 0000000000..705f457a8b --- /dev/null +++ b/frontend/src/components/gateway/__snapshots__/GRPCRouteDetails.Basic.stories.storyshot @@ -0,0 +1,343 @@ + +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    +

    + GRPCRoute +

    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + Name +
    +
    + + default-httproute + +
    +
    + Namespace +
    +
    + + default + +
    +
    + Creation +
    +
    + + 2023-07-19T09:48:42.000Z + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +

    + ParentRefs +

    +
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + +
    + Name + + Namespace + + Kind + + Group + + Section Name +
    + + envoy-gateway-system + + + shared-gateway + + Gateway + + gateway.networking.k8s.io + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +

    + Events +

    +
    +
    +
    +
    +
    +
    +
    +

    + No data to be shown. +

    +
    +
    +
    +
    +
    +
    +
    +
    + \ No newline at end of file diff --git a/frontend/src/components/gateway/__snapshots__/GRPCRouteList.Items.stories.storyshot b/frontend/src/components/gateway/__snapshots__/GRPCRouteList.Items.stories.storyshot new file mode 100644 index 0000000000..bc51dac68b --- /dev/null +++ b/frontend/src/components/gateway/__snapshots__/GRPCRouteList.Items.stories.storyshot @@ -0,0 +1,718 @@ + +
    +
    +
    +
    +
    +

    + GRPCRoutes +

    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    + +
    + +
    + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    + +
    + +
    +
    + + + +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + +
    +
    + +
    + +
    +
    +
    +
    + +
    + +
    +
    +
    +
    + +
    + +
    +
    +
    +
    + +
    +
    +
    + + default-httproute + + + + default + + +

    + 3mo +

    +
    + +
    +
    +
    +
    + +
    +
    +
    + +
    + + + +
    +
    + + 1-1 of 1 + +
    + + + + + + +
    +
    +
    +
    +
    +
    +
    +
    +
    + \ No newline at end of file diff --git a/frontend/src/components/gateway/__snapshots__/GatewayDetails.Basic.stories.storyshot b/frontend/src/components/gateway/__snapshots__/GatewayDetails.Basic.stories.storyshot new file mode 100644 index 0000000000..0a02a92637 --- /dev/null +++ b/frontend/src/components/gateway/__snapshots__/GatewayDetails.Basic.stories.storyshot @@ -0,0 +1,429 @@ + +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    +

    + Gateway +

    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + Name +
    +
    + + default-gateway + +
    +
    + Namespace +
    +
    + + default + +
    +
    + Creation +
    +
    + + 2023-07-19T09:48:42.000Z + +
    +
    + Class Name +
    +
    + + test + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +

    + Addresses +

    +
    +
    +
    +
    +
    +
    +
    +

    + No addresses data to be shown. +

    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +

    + Listeners +

    +
    +
    +
    +
    +
    +
    +
    + test +
    +
    + Hostname +
    +
    + + test + +
    +
    + Port +
    +
    + 80 +
    +
    + Protocol +
    +
    + + HTTP + +
    +
    + Conditions +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +

    + Conditions +

    +
    +
    +
    +
    +
    +
    +
    +

    + No data to be shown. +

    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +

    + Events +

    +
    +
    +
    +
    +
    +
    +
    +

    + No data to be shown. +

    +
    +
    +
    +
    +
    +
    +
    +
    + \ No newline at end of file diff --git a/frontend/src/components/gateway/__snapshots__/GatewayList.Items.stories.storyshot b/frontend/src/components/gateway/__snapshots__/GatewayList.Items.stories.storyshot new file mode 100644 index 0000000000..aed9a7b4c6 --- /dev/null +++ b/frontend/src/components/gateway/__snapshots__/GatewayList.Items.stories.storyshot @@ -0,0 +1,976 @@ + +
    +
    +
    +
    +
    +

    + Gateways +

    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    + +
    + +
    + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    + +
    + +
    +
    + + + +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + +
    + +
    +
    +
    +
    + +
    + +
    +
    +
    +
    + +
    + +
    +
    +
    +
    + +
    + +
    +
    +
    +
    + +
    + +
    +
    +
    +
    + +
    + +
    +
    +
    +
    + +
    +
    +
    + + default-gateway + + + + default + + + + test + + + + 1 + +

    + 3mo +

    +
    + +
    +
    +
    +
    + +
    +
    +
    + +
    + + + +
    +
    + + 1-1 of 1 + +
    + + + + + + +
    +
    +
    +
    +
    +
    +
    +
    +
    + \ No newline at end of file diff --git a/frontend/src/components/gateway/__snapshots__/HTTPRouteDetails.Basic.stories.storyshot b/frontend/src/components/gateway/__snapshots__/HTTPRouteDetails.Basic.stories.storyshot new file mode 100644 index 0000000000..47b23b56f9 --- /dev/null +++ b/frontend/src/components/gateway/__snapshots__/HTTPRouteDetails.Basic.stories.storyshot @@ -0,0 +1,335 @@ + +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    +

    + HTTPRoute +

    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + Name +
    +
    + + default-httproute + +
    +
    + Namespace +
    +
    + + default + +
    +
    + Creation +
    +
    + + 2023-07-19T09:48:42.000Z + +
    +
    + Hostnames +
    +
    + + test + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +

    + Rules +

    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +

    + ParentRefs +

    +
    +
    +
    +
    +
    +
    +
    +

    + No rules data to be shown. +

    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +

    + Events +

    +
    +
    +
    +
    +
    +
    +
    +

    + No data to be shown. +

    +
    +
    +
    +
    +
    +
    +
    +
    + \ No newline at end of file diff --git a/frontend/src/components/gateway/__snapshots__/HTTPRouteList.Items.stories.storyshot b/frontend/src/components/gateway/__snapshots__/HTTPRouteList.Items.stories.storyshot new file mode 100644 index 0000000000..248a2e9a28 --- /dev/null +++ b/frontend/src/components/gateway/__snapshots__/HTTPRouteList.Items.stories.storyshot @@ -0,0 +1,894 @@ + +
    +
    +
    +
    +
    +

    + HttpRoutes +

    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    + +
    + +
    + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    + +
    + +
    +
    + + + +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + +
    +
    + +
    + +
    +
    +
    +
    + +
    + +
    +
    +
    +
    + +
    + +
    +
    +
    +
    + +
    + +
    +
    +
    +
    + +
    + +
    +
    +
    +
    + +
    +
    +
    + + default-httproute + + + + default + + + + test + + + 3 + +

    + 3mo +

    +
    + +
    +
    +
    +
    + +
    +
    +
    + +
    + + + +
    +
    + + 1-1 of 1 + +
    + + + + + + +
    +
    +
    +
    +
    +
    +
    +
    +
    + \ No newline at end of file diff --git a/frontend/src/components/gateway/storyHelper.ts b/frontend/src/components/gateway/storyHelper.ts new file mode 100644 index 0000000000..7b67c025cf --- /dev/null +++ b/frontend/src/components/gateway/storyHelper.ts @@ -0,0 +1,104 @@ +import { KubeGateway } from '../../lib/k8s/gateway'; +import { KubeGatewayClass } from '../../lib/k8s/gatewayClass'; +import { KubeGRPCRoute } from '../../lib/k8s/grpcRoute'; +import { KubeHTTPRoute } from '../../lib/k8s/httpRoute'; + +export const DEFAULT_GATEWAY: KubeGateway = { + apiVersion: 'gateway.networking.k8s.io/v1beta1', + kind: 'Gateway', + metadata: { + creationTimestamp: '2023-07-19T09:48:42Z', + generation: 1, + name: 'default-gateway', + namespace: 'default', + resourceVersion: '12345', + uid: 'abc123', + }, + spec: { + gatewayClassName: 'test', + listeners: [ + { + hostname: 'test', + name: 'test', + protocol: 'HTTP', + port: 80, + }, + ], + }, + status: { + addresses: [], + listeners: [], + }, +}; + +export const DEFAULT_GATEWAY_CLASS: KubeGatewayClass = { + apiVersion: 'gateway.networking.k8s.io/v1beta1', + kind: 'GatewayClass', + metadata: { + creationTimestamp: '2023-07-19T09:48:42Z', + generation: 1, + name: 'default-gateway-class', + namespace: 'default', + resourceVersion: '1234', + uid: 'abc1234', + }, + spec: { + controllerName: 'test', + }, + status: {}, +}; + +export const DEFAULT_HTTP_ROUTE: KubeHTTPRoute = { + apiVersion: 'gateway.networking.k8s.io/v1beta1', + kind: 'HTTPRoute', + metadata: { + creationTimestamp: '2023-07-19T09:48:42Z', + generation: 1, + name: 'default-httproute', + namespace: 'default', + resourceVersion: '1234', + uid: 'abc1234', + }, + spec: { + hostnames: ['test'], + parentRefs: [], + rules: [ + { + backendRefs: [], + matches: [], + }, + { + backendRefs: null, + matches: [], + }, + { + backendRefs: [], + matches: null, + }, + ], + }, +}; + +export const DEFAULT_GRPC_ROUTE: KubeGRPCRoute = { + apiVersion: 'gateway.networking.k8s.io/v1beta1', + kind: 'GRPCRoute', + metadata: { + creationTimestamp: '2023-07-19T09:48:42Z', + generation: 1, + name: 'default-httproute', + namespace: 'default', + resourceVersion: '1234', + uid: 'abc1234', + }, + spec: { + parentRefs: [ + { + group: 'gateway.networking.k8s.io', + kind: 'Gateway', + namespace: 'shared-gateway', + sectionName: null, + name: 'envoy-gateway-system', + }, + ], + }, +}; diff --git a/frontend/src/components/gateway/utils.tsx b/frontend/src/components/gateway/utils.tsx new file mode 100644 index 0000000000..ae2f0338db --- /dev/null +++ b/frontend/src/components/gateway/utils.tsx @@ -0,0 +1,49 @@ +import { useTranslation } from 'react-i18next'; +import { GatewayParentReference } from '../../lib/k8s/gateway'; +import { Link, SectionBox, SimpleTable } from '../common'; + +export function GatewayParentRefSection(props: { parentRefs: GatewayParentReference[] }) { + const { parentRefs } = props; + const { t } = useTranslation(['glossary', 'translation']); + + return ( + + ( + + {data.name} + + ), + }, + { + label: t('translation|Namespace'), + getter: (data: GatewayParentReference) => data.namespace, + }, + { + label: t('translation|Kind'), + getter: (data: GatewayParentReference) => data.kind, + }, + { + label: t('translation|Group'), + getter: (data: GatewayParentReference) => data.group, + }, + { + label: t('translation|Section Name'), + getter: (data: GatewayParentReference) => data.sectionName, + }, + ]} + data={parentRefs || []} + reflectInURL="listeners" + /> + + ); +} diff --git a/frontend/src/i18n/locales/de/glossary.json b/frontend/src/i18n/locales/de/glossary.json index 867d56c829..4abd94346f 100644 --- a/frontend/src/i18n/locales/de/glossary.json +++ b/frontend/src/i18n/locales/de/glossary.json @@ -61,13 +61,22 @@ "Port": "Port", "Protocol": "Protokoll", "Endpoints": "Endpunkte", + "Controller Name": "", + "Gateway Classes": "", + "Controller": "Controller", + "Class Name": "Name der Klasse", + "Addresses": "", + "Listeners": "", + "No data": "", + "Gateways": "", + "GRPCRoutes": "", + "Rules": "Regeln", + "HttpRoutes": "", + "Hostnames": "", "Horizontal Pod Autoscalers": "Horizontaler Pod-Autoskalierer", "Ingress Classes": "Ingress Classes", "Default Ingress Class": "Standard Ingress Class", - "Controller": "Controller", "Default Backend": "Standard-Backend", - "Class Name": "Name der Klasse", - "Rules": "Regeln", "Backends": "Backends", "Ingresses": "Ingresses", "Hosts": "Hosts", @@ -165,6 +174,9 @@ "Persistent Volumes": "Persistent Volumes", "Storage Classes": "Speicher-Klassen", "Network": "Netzwerk", + "Gateway": "", + "HTTP Routes": "", + "GRPC Routes": "", "Security": "Sicherheit", "Configuration": "Konfiguration", "HPAs": "HPAs", diff --git a/frontend/src/i18n/locales/de/translation.json b/frontend/src/i18n/locales/de/translation.json index b47092ce46..2c12cb3875 100644 --- a/frontend/src/i18n/locales/de/translation.json +++ b/frontend/src/i18n/locales/de/translation.json @@ -323,6 +323,19 @@ "Current": "Aktuell", "Desired//context:pods": "Gewünscht", "Addresses": "Adressen", + "Hostname": "", + "Port": "", + "Protocol": "", + "No addresses data to be shown.": "", + "Listeners": "", + "BackendRefs": "", + "Matches": "", + "rules": "", + "ParentRefs": "", + "No rules data to be shown.": "Keine Regeldaten vorhanden.", + "Namespace": "", + "Kind": "", + "Section Name": "", "Search": "", "Press<1>/to search": "", "Search resources, pages, clusters by name": "", @@ -339,7 +352,6 @@ "Targets": "Ziele", "more…": "mehr…", "Default": "Standard", - "No rules data to be shown.": "Keine Regeldaten vorhanden.", "Host": "Host", "Path": "Pfad", "Duration": "Dauer", @@ -394,7 +406,6 @@ "Zoom out": "", "No data to be shown. Try to change filters or select a different namespace.": "", "Group By: {{ name }}": "", - "Namespace": "", "Instance": "", "Node": "", "Status: Error or Warning": "", diff --git a/frontend/src/i18n/locales/en/glossary.json b/frontend/src/i18n/locales/en/glossary.json index fe69299f90..f1e5c77cd4 100644 --- a/frontend/src/i18n/locales/en/glossary.json +++ b/frontend/src/i18n/locales/en/glossary.json @@ -61,13 +61,22 @@ "Port": "Port", "Protocol": "Protocol", "Endpoints": "Endpoints", + "Controller Name": "Controller Name", + "Gateway Classes": "Gateway Classes", + "Controller": "Controller", + "Class Name": "Class Name", + "Addresses": "Addresses", + "Listeners": "Listeners", + "No data": "No data", + "Gateways": "Gateways", + "GRPCRoutes": "GRPCRoutes", + "Rules": "Rules", + "HttpRoutes": "HttpRoutes", + "Hostnames": "Hostnames", "Horizontal Pod Autoscalers": "Horizontal Pod Autoscalers", "Ingress Classes": "Ingress Classes", "Default Ingress Class": "Default Ingress Class", - "Controller": "Controller", "Default Backend": "Default Backend", - "Class Name": "Class Name", - "Rules": "Rules", "Backends": "Backends", "Ingresses": "Ingresses", "Hosts": "Hosts", @@ -165,6 +174,9 @@ "Persistent Volumes": "Persistent Volumes", "Storage Classes": "Storage Classes", "Network": "Network", + "Gateway": "Gateway", + "HTTP Routes": "HTTP Routes", + "GRPC Routes": "GRPC Routes", "Security": "Security", "Configuration": "Configuration", "HPAs": "HPAs", diff --git a/frontend/src/i18n/locales/en/translation.json b/frontend/src/i18n/locales/en/translation.json index d8c8c190b1..cf3b0f0a42 100644 --- a/frontend/src/i18n/locales/en/translation.json +++ b/frontend/src/i18n/locales/en/translation.json @@ -323,6 +323,19 @@ "Current": "Current", "Desired//context:pods": "Desired", "Addresses": "Addresses", + "Hostname": "Hostname", + "Port": "Port", + "Protocol": "Protocol", + "No addresses data to be shown.": "No addresses data to be shown.", + "Listeners": "Listeners", + "BackendRefs": "BackendRefs", + "Matches": "Matches", + "rules": "rules", + "ParentRefs": "ParentRefs", + "No rules data to be shown.": "No rules data to be shown.", + "Namespace": "Namespace", + "Kind": "Kind", + "Section Name": "Section Name", "Search": "Search", "Press<1>/to search": "Press<1>/to search", "Search resources, pages, clusters by name": "Search resources, pages, clusters by name", @@ -339,7 +352,6 @@ "Targets": "Targets", "more…": "more…", "Default": "Default", - "No rules data to be shown.": "No rules data to be shown.", "Host": "Host", "Path": "Path", "Duration": "Duration", @@ -394,7 +406,6 @@ "Zoom out": "Zoom out", "No data to be shown. Try to change filters or select a different namespace.": "No data to be shown. Try to change filters or select a different namespace.", "Group By: {{ name }}": "Group By: {{ name }}", - "Namespace": "Namespace", "Instance": "Instance", "Node": "Node", "Status: Error or Warning": "Status: Error or Warning", diff --git a/frontend/src/i18n/locales/es/glossary.json b/frontend/src/i18n/locales/es/glossary.json index c577910684..a7eb4820c6 100644 --- a/frontend/src/i18n/locales/es/glossary.json +++ b/frontend/src/i18n/locales/es/glossary.json @@ -61,13 +61,22 @@ "Port": "Puerto", "Protocol": "Protocolo", "Endpoints": "Endpoints", + "Controller Name": "", + "Gateway Classes": "", + "Controller": "Controller", + "Class Name": "Class Name", + "Addresses": "", + "Listeners": "", + "No data": "", + "Gateways": "", + "GRPCRoutes": "", + "Rules": "Reglas", + "HttpRoutes": "", + "Hostnames": "", "Horizontal Pod Autoscalers": "Horizontal Pod Autoscalers", "Ingress Classes": "Ingress Classes", "Default Ingress Class": "Default Ingress Class", - "Controller": "Controller", "Default Backend": "Backend por defecto", - "Class Name": "Class Name", - "Rules": "Reglas", "Backends": "Backends", "Ingresses": "Ingresses", "Hosts": "Hosts", @@ -165,6 +174,9 @@ "Persistent Volumes": "Persistent Volumes", "Storage Classes": "Storage Classes", "Network": "Red", + "Gateway": "", + "HTTP Routes": "", + "GRPC Routes": "", "Security": "Seguridad", "Configuration": "Configuración", "HPAs": "HPAs", diff --git a/frontend/src/i18n/locales/es/translation.json b/frontend/src/i18n/locales/es/translation.json index b36fed1617..b990c7d8d0 100644 --- a/frontend/src/i18n/locales/es/translation.json +++ b/frontend/src/i18n/locales/es/translation.json @@ -324,6 +324,19 @@ "Current": "Actual", "Desired//context:pods": "Deseados", "Addresses": "Direcciones", + "Hostname": "", + "Port": "", + "Protocol": "", + "No addresses data to be shown.": "", + "Listeners": "", + "BackendRefs": "", + "Matches": "", + "rules": "", + "ParentRefs": "", + "No rules data to be shown.": "Sin datos de reglas", + "Namespace": "", + "Kind": "", + "Section Name": "", "Search": "", "Press<1>/to search": "", "Search resources, pages, clusters by name": "", @@ -340,7 +353,6 @@ "Targets": "Objetivos", "more…": "más…", "Default": "Por defecto", - "No rules data to be shown.": "Sin datos de reglas", "Host": "Host", "Path": "Ruta", "Duration": "Duración", @@ -395,7 +407,6 @@ "Zoom out": "", "No data to be shown. Try to change filters or select a different namespace.": "", "Group By: {{ name }}": "", - "Namespace": "", "Instance": "", "Node": "", "Status: Error or Warning": "", diff --git a/frontend/src/i18n/locales/fr/glossary.json b/frontend/src/i18n/locales/fr/glossary.json index 5016b4448a..903470f478 100644 --- a/frontend/src/i18n/locales/fr/glossary.json +++ b/frontend/src/i18n/locales/fr/glossary.json @@ -61,13 +61,22 @@ "Port": "Port", "Protocol": "Protocole", "Endpoints": "Endpoints", + "Controller Name": "", + "Gateway Classes": "", + "Controller": "Controller", + "Class Name": "Nom de la classe", + "Addresses": "", + "Listeners": "", + "No data": "", + "Gateways": "", + "GRPCRoutes": "", + "Rules": "Règles", + "HttpRoutes": "", + "Hostnames": "", "Horizontal Pod Autoscalers": "Horizontal Pod Autoscalers", "Ingress Classes": "Ingress Classes", "Default Ingress Class": "Default Ingress Class", - "Controller": "Controller", "Default Backend": "Backend par défaut", - "Class Name": "Nom de la classe", - "Rules": "Règles", "Backends": "Backends", "Ingresses": "Ingresses", "Hosts": "Hôtes", @@ -165,6 +174,9 @@ "Persistent Volumes": "Persistent Volumes", "Storage Classes": "Classes de stockage", "Network": "Réseau", + "Gateway": "", + "HTTP Routes": "", + "GRPC Routes": "", "Security": "Sécurité", "Configuration": "Configuration", "HPAs": "HPAs", diff --git a/frontend/src/i18n/locales/fr/translation.json b/frontend/src/i18n/locales/fr/translation.json index 1e9de5ed1d..f7b5a48257 100644 --- a/frontend/src/i18n/locales/fr/translation.json +++ b/frontend/src/i18n/locales/fr/translation.json @@ -324,6 +324,19 @@ "Current": "Actuel", "Desired//context:pods": "Actuels", "Addresses": "Adresses", + "Hostname": "", + "Port": "", + "Protocol": "", + "No addresses data to be shown.": "", + "Listeners": "", + "BackendRefs": "", + "Matches": "", + "rules": "", + "ParentRefs": "", + "No rules data to be shown.": "Aucune donnée sur les règles à afficher.", + "Namespace": "", + "Kind": "", + "Section Name": "", "Search": "", "Press<1>/to search": "", "Search resources, pages, clusters by name": "", @@ -340,7 +353,6 @@ "Targets": "Cibles", "more…": "plus…", "Default": "Par défaut", - "No rules data to be shown.": "Aucune donnée sur les règles à afficher.", "Host": "Host", "Path": "Path", "Duration": "Durée", @@ -395,7 +407,6 @@ "Zoom out": "", "No data to be shown. Try to change filters or select a different namespace.": "", "Group By: {{ name }}": "", - "Namespace": "", "Instance": "", "Node": "", "Status: Error or Warning": "", diff --git a/frontend/src/i18n/locales/pt/glossary.json b/frontend/src/i18n/locales/pt/glossary.json index fdf7e072ca..32aec71cda 100644 --- a/frontend/src/i18n/locales/pt/glossary.json +++ b/frontend/src/i18n/locales/pt/glossary.json @@ -61,13 +61,22 @@ "Port": "Porta", "Protocol": "Protocolo", "Endpoints": "Endpoints", + "Controller Name": "", + "Gateway Classes": "", + "Controller": "Controller", + "Class Name": "Class Name", + "Addresses": "", + "Listeners": "", + "No data": "", + "Gateways": "", + "GRPCRoutes": "", + "Rules": "Regras", + "HttpRoutes": "", + "Hostnames": "", "Horizontal Pod Autoscalers": "Horizontal Pod Autoscalers", "Ingress Classes": "Ingress Classes", "Default Ingress Class": "Default Ingress Class", - "Controller": "Controller", "Default Backend": "Backend Padrão", - "Class Name": "Class Name", - "Rules": "Regras", "Backends": "Backends", "Ingresses": "Ingresses", "Hosts": "Hosts", @@ -165,6 +174,9 @@ "Persistent Volumes": "Persistent Volumes", "Storage Classes": "Storage Classes", "Network": "Rede", + "Gateway": "", + "HTTP Routes": "", + "GRPC Routes": "", "Security": "Segurança", "Configuration": "Configuração", "HPAs": "HPAs", diff --git a/frontend/src/i18n/locales/pt/translation.json b/frontend/src/i18n/locales/pt/translation.json index bd7adecd1d..3d6b5de11b 100644 --- a/frontend/src/i18n/locales/pt/translation.json +++ b/frontend/src/i18n/locales/pt/translation.json @@ -324,6 +324,19 @@ "Current": "Actual", "Desired//context:pods": "Desejados", "Addresses": "Endereços", + "Hostname": "", + "Port": "", + "Protocol": "", + "No addresses data to be shown.": "", + "Listeners": "", + "BackendRefs": "", + "Matches": "", + "rules": "", + "ParentRefs": "", + "No rules data to be shown.": "Sem dados de regras a mostrar", + "Namespace": "", + "Kind": "", + "Section Name": "", "Search": "", "Press<1>/to search": "", "Search resources, pages, clusters by name": "", @@ -340,7 +353,6 @@ "Targets": "Objectivos", "more…": "mais…", "Default": "Por defeito", - "No rules data to be shown.": "Sem dados de regras a mostrar", "Host": "Host", "Path": "Caminho", "Duration": "Duração", @@ -395,7 +407,6 @@ "Zoom out": "", "No data to be shown. Try to change filters or select a different namespace.": "", "Group By: {{ name }}": "", - "Namespace": "", "Instance": "", "Node": "", "Status: Error or Warning": "", diff --git a/frontend/src/lib/k8s/gateway.ts b/frontend/src/lib/k8s/gateway.ts new file mode 100644 index 0000000000..e6af4c001f --- /dev/null +++ b/frontend/src/lib/k8s/gateway.ts @@ -0,0 +1,80 @@ +import { KubeCondition } from './cluster'; +import { KubeObject, KubeObjectInterface } from './KubeObject'; + +export interface GatewayParentReference { + group: string; + kind: string; + namespace: string; + sectionName: string | null; + name: string; + [key: string]: any; +} + +export interface GatewayListener { + hostname: string; + name: string; + protocol: string; + port: number; + [key: string]: any; +} +export interface GatewayListenerStatus { + name: string; + conditions: KubeCondition[]; + [key: string]: any; +} +export interface GatewayAddress { + type: string; + value: string; +} + +export interface KubeGateway extends KubeObjectInterface { + spec: { + gatewayClassName?: string; + listeners: GatewayListener[]; + [key: string]: any; + }; + status: { + addresses: GatewayAddress[]; + listeners: GatewayListenerStatus[]; + [otherProps: string]: any; + }; +} + +class Gateway extends KubeObject { + static kind = 'Gateway'; + static apiName = 'gateways'; + static apiVersion = 'gateway.networking.k8s.io/v1beta1'; + static isNamespaced = true; + + get spec(): KubeGateway['spec'] { + return this.jsonData.spec; + } + + get status() { + return this.jsonData.status; + } + + getListeners(): GatewayListener[] { + return this.jsonData.spec.listeners; + } + + getAddresses(): GatewayAddress[] { + return this.jsonData.status.addresses; + } + + getListernerStatusByName(name: string): GatewayListenerStatus | null { + return this.jsonData.status.listeners.find(t => t.name === name) || null; + } + + static get pluralName() { + return 'gateways'; + } + get listRoute(): string { + return 'k8sgateways'; // fix magic name gateway + } + get detailsRoute(): string { + return 'k8sgateway'; // fix magic name gateway + } +} + +export default Gateway; diff --git a/frontend/src/lib/k8s/gatewayClass.ts b/frontend/src/lib/k8s/gatewayClass.ts new file mode 100644 index 0000000000..658b97a06f --- /dev/null +++ b/frontend/src/lib/k8s/gatewayClass.ts @@ -0,0 +1,40 @@ +import { KubeObject, KubeObjectInterface } from './KubeObject'; + +export interface KubeGatewayClass extends KubeObjectInterface { + spec: { + controllerName: string; + [key: string]: any; + }; + status: { + [otherProps: string]: any; + }; +} + +class GatewayClass extends KubeObject { + static kind = 'GatewayClass'; + static apiName = 'gatewayclasses'; + static apiVersion = 'gateway.networking.k8s.io/v1beta1'; + static isNamespaced = false; + + get spec(): KubeGatewayClass['spec'] { + return this.jsonData.spec; + } + + get status() { + return this.jsonData.status; + } + + get controllerName() { + return this.spec!.controllerName; + } + + static get listRoute() { + return 'GatewayClasses'; + } + + static get pluralName() { + return 'gatewayclasses'; + } +} + +export default GatewayClass; diff --git a/frontend/src/lib/k8s/grpcRoute.ts b/frontend/src/lib/k8s/grpcRoute.ts new file mode 100644 index 0000000000..fd6d541380 --- /dev/null +++ b/frontend/src/lib/k8s/grpcRoute.ts @@ -0,0 +1,29 @@ +import { GatewayParentReference } from './gateway'; +import { KubeObject, KubeObjectInterface } from './KubeObject'; + +export interface KubeGRPCRoute extends KubeObjectInterface { + spec: { + parentRefs: GatewayParentReference[]; + [key: string]: any; + }; +} + +class GRPCRoute extends KubeObject { + static kind = 'GRPCRoute'; + static apiName = 'grpcroutes'; + static apiVersion = 'gateway.networking.k8s.io/v1beta1'; + static isNamespaced = true; + + get spec(): KubeGRPCRoute['spec'] { + return this.jsonData.spec; + } + get parentRefs(): GatewayParentReference[] { + return this.jsonData.spec.parentRefs; + } + + static get pluralName() { + return 'grpcroutes'; + } +} + +export default GRPCRoute; diff --git a/frontend/src/lib/k8s/httpRoute.ts b/frontend/src/lib/k8s/httpRoute.ts new file mode 100644 index 0000000000..ade384acb0 --- /dev/null +++ b/frontend/src/lib/k8s/httpRoute.ts @@ -0,0 +1,46 @@ +import { GatewayParentReference } from './gateway'; +import { KubeObject, KubeObjectInterface } from './KubeObject'; + +export interface HTTPRouteRule { + backendRefs: any[] | null; + matches: any[] | null; + [key: string]: any; +} + +export interface KubeHTTPRoute extends KubeObjectInterface { + spec: { + hostnames: string[]; + parentRefs: GatewayParentReference[]; + rules: HTTPRouteRule[]; + [key: string]: any; + }; +} + +class HTTPRoute extends KubeObject { + static kind = 'HTTPRoute'; + static apiName = 'httproutes'; + static apiVersion = 'gateway.networking.k8s.io/v1beta1'; + static isNamespaced = true; + + get spec(): KubeHTTPRoute['spec'] { + return this.jsonData.spec; + } + + get hostnames(): string[] { + return this.jsonData.spec.hostnames; + } + + get rules(): HTTPRouteRule[] { + return this.jsonData.spec.rules; + } + + get parentRefs(): GatewayParentReference[] { + return this.jsonData.spec.parentRefs; + } + + static get pluralName() { + return 'httproutes'; + } +} + +export default HTTPRoute; diff --git a/frontend/src/lib/k8s/index.test.ts b/frontend/src/lib/k8s/index.test.ts index a14c0adb4d..7cab819e14 100644 --- a/frontend/src/lib/k8s/index.test.ts +++ b/frontend/src/lib/k8s/index.test.ts @@ -210,6 +210,7 @@ const notNamespacedClasses = [ 'ClusterRole', 'ClusterRoleBinding', 'CustomResourceDefinition', + 'GatewayClass', 'IngressClass', 'Namespace', 'Node', @@ -226,7 +227,10 @@ const namespacedClasses = [ 'Deployment', 'Endpoint', 'Endpoints', + 'GRPCRoute', + 'Gateway', 'HorizontalPodAutoscaler', + 'HTTPRoute', 'Ingress', 'Job', 'Lease', diff --git a/frontend/src/lib/k8s/index.ts b/frontend/src/lib/k8s/index.ts index 5e1958e44b..f3e63a3adc 100644 --- a/frontend/src/lib/k8s/index.ts +++ b/frontend/src/lib/k8s/index.ts @@ -14,7 +14,11 @@ import CronJob from './cronJob'; import DaemonSet from './daemonSet'; import Deployment from './deployment'; import Endpoints from './endpoints'; +import Gateway from './gateway'; +import GatewayClass from './gatewayClass'; +import GRPCRoute from './grpcRoute'; import HPA from './hpa'; +import HTTPRoute from './httpRoute'; import Ingress from './ingress'; import IngressClass from './ingressClass'; import Job from './job'; @@ -73,6 +77,10 @@ export const ResourceClasses = { ServiceAccount, StatefulSet, StorageClass, + Gateway, + GatewayClass, + HTTPRoute, + GRPCRoute, }; /** Hook for getting or fetching the clusters configuration. diff --git a/frontend/src/lib/router.tsx b/frontend/src/lib/router.tsx index dd533784b2..47fcacdc19 100644 --- a/frontend/src/lib/router.tsx +++ b/frontend/src/lib/router.tsx @@ -27,6 +27,14 @@ import DaemonSetList from '../components/daemonset/List'; import DeploymentsList from '../components/deployments/List'; import EndpointDetails from '../components/endpoints/Details'; import EndpointList from '../components/endpoints/List'; +import GatewayClassDetails from '../components/gateway/ClassDetails'; +import GatewayClassList from '../components/gateway/ClassList'; +import GatewayDetails from '../components/gateway/GatewayDetails'; +import GatewayList from '../components/gateway/GatewayList'; +import GRPCRouteDetails from '../components/gateway/GRPCRouteDetails'; +import GRPCRouteList from '../components/gateway/GRPCRouteList'; +import HTTPRouteDetails from '../components/gateway/HTTPRouteDetails'; +import HTTPRouteList from '../components/gateway/HTTPRouteList'; import HpaDetails from '../components/horizontalPodAutoscaler/Details'; import HpaList from '../components/horizontalPodAutoscaler/List'; import IngressClassDetails from '../components/ingress/ClassDetails'; @@ -327,6 +335,63 @@ const defaultRoutes: { sidebar: 'NetworkPolicies', component: () => , }, + k8sgateways: { + // fix magic name gateway + path: '/k8sgateways', + exact: true, + name: 'Gateways', + sidebar: 'k8sgateways', + component: () => , + }, + k8sgateway: { + // fix magic name gateway + path: '/k8sgateways/:namespace/:name', + exact: true, + name: 'Gateways', + sidebar: 'k8sgateways', + component: () => , + }, + httproutes: { + path: '/httproutes', + exact: true, + name: 'HttpRoutes', + sidebar: 'httproutes', + component: () => , + }, + httproute: { + path: '/httproutes/:namespace/:name', + exact: true, + name: 'HttpRoutes', + sidebar: 'httproutes', + component: () => , + }, + grpcroutes: { + path: '/grpcroutes', + exact: true, + name: 'GRPCRoutes', + sidebar: 'grpcroutes', + component: () => , + }, + grpcroute: { + path: '/grpcroutes/:namespace/:name', + exact: true, + name: 'GRPCRoutes', + sidebar: 'grpcroutes', + component: () => , + }, + gatewayclasses: { + path: '/gatewayclasses', + exact: true, + name: 'GatewayClasses', + sidebar: 'gatewayclasses', + component: () => , + }, + gatewayclass: { + path: '/gatewayclasses/:name', + exact: true, + sidebar: 'gatewayclasses', + component: () => , + }, DaemonSets: { path: '/daemonsets', exact: true, diff --git a/frontend/src/plugin/__snapshots__/pluginLib.snapshot b/frontend/src/plugin/__snapshots__/pluginLib.snapshot index dafdc1d599..914ca92e23 100644 --- a/frontend/src/plugin/__snapshots__/pluginLib.snapshot +++ b/frontend/src/plugin/__snapshots__/pluginLib.snapshot @@ -257,6 +257,10 @@ "Deployment": [Function], "Endpoint": [Function], "Endpoints": [Function], + "GRPCRoute": [Function], + "Gateway": [Function], + "GatewayClass": [Function], + "HTTPRoute": [Function], "HorizontalPodAutoscaler": [Function], "Ingress": [Function], "IngressClass": [Function],