From d09a4e8a9594b9de639d78e0fb061ce1c4948eb3 Mon Sep 17 00:00:00 2001 From: farodin91 Date: Wed, 30 Oct 2024 22:23:58 +0100 Subject: [PATCH] frontend: add support for gateway-api --- ...r.InClusterSidebarClosed.stories.storyshot | 86 +++++++++++++++++ ...bar.InClusterSidebarOpen.stories.storyshot | 93 +++++++++++++++++++ ...edItemWithSidebarOmitted.stories.storyshot | 93 +++++++++++++++++++ .../src/components/Sidebar/prepareRoutes.ts | 23 +++++ .../src/components/gateway/ClassDetails.tsx | 38 ++++++++ frontend/src/components/gateway/ClassList.tsx | 26 ++++++ .../components/gateway/GRPCRouteDetails.tsx | 67 +++++++++++++ .../src/components/gateway/GRPCRouteList.tsx | 15 +++ .../src/components/gateway/GatewayDetails.tsx | 72 ++++++++++++++ .../src/components/gateway/GatewayList.tsx | 32 +++++++ .../components/gateway/HTTPRouteDetails.tsx | 74 +++++++++++++++ .../src/components/gateway/HTTPRouteList.tsx | 29 ++++++ frontend/src/lib/k8s/gateway.ts | 43 +++++++++ frontend/src/lib/k8s/gatewayClass.ts | 33 +++++++ frontend/src/lib/k8s/grpcRoute.ts | 29 ++++++ frontend/src/lib/k8s/httpRoute.ts | 41 ++++++++ frontend/src/lib/router.tsx | 65 +++++++++++++ 17 files changed, 859 insertions(+) create mode 100644 frontend/src/components/gateway/ClassDetails.tsx create mode 100644 frontend/src/components/gateway/ClassList.tsx create mode 100644 frontend/src/components/gateway/GRPCRouteDetails.tsx create mode 100644 frontend/src/components/gateway/GRPCRouteList.tsx create mode 100644 frontend/src/components/gateway/GatewayDetails.tsx create mode 100644 frontend/src/components/gateway/GatewayList.tsx create mode 100644 frontend/src/components/gateway/HTTPRouteDetails.tsx create mode 100644 frontend/src/components/gateway/HTTPRouteList.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 76b2e2a398b..3b8df3832ed 100644 --- a/frontend/src/components/Sidebar/__snapshots__/Sidebar.InClusterSidebarClosed.stories.storyshot +++ b/frontend/src/components/Sidebar/__snapshots__/Sidebar.InClusterSidebarClosed.stories.storyshot @@ -592,6 +592,92 @@ +
  • + +
  • +
  • + +
  • diff --git a/frontend/src/components/Sidebar/__snapshots__/Sidebar.InClusterSidebarOpen.stories.storyshot b/frontend/src/components/Sidebar/__snapshots__/Sidebar.InClusterSidebarOpen.stories.storyshot index cbdc28331e8..951bdaa60d9 100644 --- a/frontend/src/components/Sidebar/__snapshots__/Sidebar.InClusterSidebarOpen.stories.storyshot +++ b/frontend/src/components/Sidebar/__snapshots__/Sidebar.InClusterSidebarOpen.stories.storyshot @@ -627,6 +627,99 @@
  • +
  • + +
  • +
  • + +
  • diff --git a/frontend/src/components/Sidebar/__snapshots__/Sidebar.SelectedItemWithSidebarOmitted.stories.storyshot b/frontend/src/components/Sidebar/__snapshots__/Sidebar.SelectedItemWithSidebarOmitted.stories.storyshot index 3a175f63564..1ad72b19a17 100644 --- a/frontend/src/components/Sidebar/__snapshots__/Sidebar.SelectedItemWithSidebarOmitted.stories.storyshot +++ b/frontend/src/components/Sidebar/__snapshots__/Sidebar.SelectedItemWithSidebarOmitted.stories.storyshot @@ -627,6 +627,99 @@
  • +
  • + +
  • +
  • + +
  • diff --git a/frontend/src/components/Sidebar/prepareRoutes.ts b/frontend/src/components/Sidebar/prepareRoutes.ts index f0bf009dff9..e25de4286bd 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:folder-network-outline', + 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.tsx b/frontend/src/components/gateway/ClassDetails.tsx new file mode 100644 index 00000000000..a35e1e88035 --- /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.tsx b/frontend/src/components/gateway/ClassList.tsx new file mode 100644 index 00000000000..1d6ca831a36 --- /dev/null +++ b/frontend/src/components/gateway/ClassList.tsx @@ -0,0 +1,26 @@ +import { useTranslation } from 'react-i18next'; +import GatewayClass from '../../lib/k8s/gatewayClass'; +import ResourceListView from '../common/Resource/ResourceListView'; + +export default function GatewayClassList() { + const { t } = useTranslation('glossary'); + + return ( + GatewayClass.spec?.controllerName, + }, + 'age', + ]} + /> + ); +} diff --git a/frontend/src/components/gateway/GRPCRouteDetails.tsx b/frontend/src/components/gateway/GRPCRouteDetails.tsx new file mode 100644 index 00000000000..0cd521c3ec9 --- /dev/null +++ b/frontend/src/components/gateway/GRPCRouteDetails.tsx @@ -0,0 +1,67 @@ +import { useTranslation } from 'react-i18next'; +import { useParams } from 'react-router-dom'; +import GRPCRoute from '../../lib/k8s/grpcRoute'; +import { GatewayParentReference } from '../../lib/k8s/httpRoute'; +import { Link, SimpleTable } from '../common'; +import { DetailsGrid } from '../common/Resource'; +import SectionBox from '../common/SectionBox'; + +export default function GRPCRouteDetails(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 ( + + item && [ + { + id: 'headlamp.grpcroute-parentrefs', + section: ( + + ( + + {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, + }, + ]} + data={item?.parentRefs || []} + reflectInURL="listeners" + /> + + ), + }, + ] + } + /> + ); +} diff --git a/frontend/src/components/gateway/GRPCRouteList.tsx b/frontend/src/components/gateway/GRPCRouteList.tsx new file mode 100644 index 00000000000..45ebd7a11a0 --- /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.tsx b/frontend/src/components/gateway/GatewayDetails.tsx new file mode 100644 index 00000000000..025e1e56e93 --- /dev/null +++ b/frontend/src/components/gateway/GatewayDetails.tsx @@ -0,0 +1,72 @@ +import { useTranslation } from 'react-i18next'; +import { useParams } from 'react-router-dom'; +import Gateway, { GatewayListener } from '../../lib/k8s/gateway'; +import Link from '../common/Link'; +import { ConditionsTable, DetailsGrid } from '../common/Resource'; +import SectionBox from '../common/SectionBox'; +import SimpleTable from '../common/SimpleTable'; + +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-listeners', + section: item && ( + + data.hostname, + }, + { + label: t('translation|Name'), + getter: (data: GatewayListener) => data.name, + }, + { + label: t('translation|Protocol'), + getter: (data: GatewayListener) => data.protocol, + }, + ]} + data={item?.getListeners() || []} + reflectInURL="listeners" + /> + + ), + }, + { + id: 'headlamp.gateway-conditions', + section: ( + + + + ), + }, + ] + } + /> + ); +} diff --git a/frontend/src/components/gateway/GatewayList.tsx b/frontend/src/components/gateway/GatewayList.tsx new file mode 100644 index 00000000000..159d204c872 --- /dev/null +++ b/frontend/src/components/gateway/GatewayList.tsx @@ -0,0 +1,32 @@ +import { useTranslation } from 'react-i18next'; +import Gateway from '../../lib/k8s/gateway'; +import Link from '../common/Link'; +import ResourceListView from '../common/Resource/ResourceListView'; + +export default function GatewayList() { + const { t } = useTranslation(['glossary', 'translation']); + + return ( + gateway.spec?.gatewayClassName, + render: gateway => + gateway.spec?.gatewayClassName ? ( + + {gateway.spec?.gatewayClassName} + + ) : null, + }, + 'age', + ]} + /> + ); +} diff --git a/frontend/src/components/gateway/HTTPRouteDetails.tsx b/frontend/src/components/gateway/HTTPRouteDetails.tsx new file mode 100644 index 00000000000..4fb89819ee8 --- /dev/null +++ b/frontend/src/components/gateway/HTTPRouteDetails.tsx @@ -0,0 +1,74 @@ +import { useTranslation } from 'react-i18next'; +import { useParams } from 'react-router-dom'; +import HTTPRoute, { GatewayParentReference } from '../../lib/k8s/httpRoute'; +import { LabelListItem, Link, SimpleTable } from '../common'; +import { DetailsGrid } from '../common/Resource'; +import SectionBox from '../common/SectionBox'; + +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-parentrefs', + section: ( + + ( + + {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, + }, + ]} + data={item?.parentRefs || []} + reflectInURL="listeners" + /> + + ), + }, + ] + } + /> + ); +} diff --git a/frontend/src/components/gateway/HTTPRouteList.tsx b/frontend/src/components/gateway/HTTPRouteList.tsx new file mode 100644 index 00000000000..7051e58cdd5 --- /dev/null +++ b/frontend/src/components/gateway/HTTPRouteList.tsx @@ -0,0 +1,29 @@ +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 || '*')} /> + ), + }, + 'age', + ]} + /> + ); +} diff --git a/frontend/src/lib/k8s/gateway.ts b/frontend/src/lib/k8s/gateway.ts new file mode 100644 index 00000000000..86a8529ed7c --- /dev/null +++ b/frontend/src/lib/k8s/gateway.ts @@ -0,0 +1,43 @@ +import { KubeObject, KubeObjectInterface } from './KubeObject'; + +export interface GatewayListener { + hostname: string; + name: string; + protocol: string; + [key: string]: any; +} + +export interface KubeGateway extends KubeObjectInterface { + spec: { + gatewayClassName?: string; + listeners: GatewayListener[]; + [key: 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; + } + + getListeners(): GatewayListener[] { + return this.jsonData.spec.listeners; + } + + 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 00000000000..27c98803143 --- /dev/null +++ b/frontend/src/lib/k8s/gatewayClass.ts @@ -0,0 +1,33 @@ +import { KubeObject, KubeObjectInterface } from './KubeObject'; + +export interface KubeGatewayClass extends KubeObjectInterface { + spec: { + controllerName: string; + [key: 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 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 00000000000..72d4fcd2bd4 --- /dev/null +++ b/frontend/src/lib/k8s/grpcRoute.ts @@ -0,0 +1,29 @@ +import { GatewayParentReference } from './httpRoute'; +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 00000000000..a09784a3b3c --- /dev/null +++ b/frontend/src/lib/k8s/httpRoute.ts @@ -0,0 +1,41 @@ +import { KubeObject, KubeObjectInterface } from './KubeObject'; + +export interface GatewayParentReference { + group: string; + kind: string; + namespace: string; + name: string; + [key: string]: any; +} + +export interface KubeHTTPRoute extends KubeObjectInterface { + spec: { + hostnames: string[]; + parentRefs: GatewayParentReference[]; + [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 parentRefs(): GatewayParentReference[] { + return this.jsonData.spec.parentRefs; + } + + static get pluralName() { + return 'httproutes'; + } +} + +export default HTTPRoute; diff --git a/frontend/src/lib/router.tsx b/frontend/src/lib/router.tsx index bc2b98ba3d6..0837ffdc323 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'; @@ -321,6 +329,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,