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 @@
  • +
  • + +
    +
    + + Gateway + +
    + +
    +
  • +
  • +
    + +
    +
  • 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 @@
  • +
  • + +
    +
    + + Gateway + +
    + +
    +
  • +
  • +
    + +
    +
  • diff --git a/frontend/src/components/Sidebar/prepareRoutes.ts b/frontend/src/components/Sidebar/prepareRoutes.ts index 9216abeee8..ec4b398a56 100644 --- a/frontend/src/components/Sidebar/prepareRoutes.ts +++ b/frontend/src/components/Sidebar/prepareRoutes.ts @@ -166,6 +166,29 @@ function prepareRoutes( }, ], }, + { + name: 'gatewayapi', + label: t('glossary|Gateway'), + icon: 'mdi:lan-connect', + subList: [ + { + name: 'gateways', + 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..1bb1d7aed7 --- /dev/null +++ b/frontend/src/components/gateway/ClassDetails.stories.tsx @@ -0,0 +1,59 @@ +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/v1/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/v1/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..9b3869f44a --- /dev/null +++ b/frontend/src/components/gateway/ClassList.stories.tsx @@ -0,0 +1,41 @@ +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/v1/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..d8f26317b9 --- /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' + )?.type || null, + render: (gatewayClass: GatewayClass) => + makeGatewayStatusLabel(gatewayClass.status?.conditions || null), + }, + '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..3e65f2e14a --- /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/v1/grpcroutes', () => + HttpResponse.json({}) + ), + http.get('http://localhost:4466/apis/gateway.networking.k8s.io/v1/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/v1/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..ad6d29f3bf --- /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/v1/grpcroutes', () => + HttpResponse.json({ + kind: 'GRPCRouteList', + metadata: {}, + items: [DEFAULT_GRPC_ROUTE], + }) + ), + http.get('http://localhost:4466/apis/gateway.networking.k8s.io/v1/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..46ad2d93ef --- /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/v1/gateways', () => + HttpResponse.json({}) + ), + http.get('http://localhost:4466/apis/gateway.networking.k8s.io/v1/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/v1/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..f57cd0540b --- /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, { + GatewayListener, + GatewayListenerStatus, + GatewayStatusAddress, +} 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: GatewayStatusAddress) => 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..85f2bf270c --- /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/v1/gateways', () => + HttpResponse.json({ + kind: 'GatewayList', + metadata: {}, + items: [DEFAULT_GATEWAY], + }) + ), + http.get('http://localhost:4466/apis/gateway.networking.k8s.io/v1/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..c361bac8d2 --- /dev/null +++ b/frontend/src/components/gateway/GatewayList.tsx @@ -0,0 +1,46 @@ +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') + ?.type || null, + render: (gateway: Gateway) => makeGatewayStatusLabel(gateway.status?.conditions || null), + }, + { + 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..231b1fa75b --- /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/v1/httproutes', () => + HttpResponse.json({}) + ), + http.get('http://localhost:4466/apis/gateway.networking.k8s.io/v1/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/v1/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..03fc741657 --- /dev/null +++ b/frontend/src/components/gateway/HTTPRouteDetails.tsx @@ -0,0 +1,76 @@ +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: rule.name, + withHighlightStyle: true, + hide: rule.name === undefined, + }, + { + 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..0bb3db9b1c --- /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/v1/httproutes', () => + HttpResponse.json({ + kind: 'HTTPRouteList', + metadata: {}, + items: [DEFAULT_HTTP_ROUTE], + }) + ), + http.get('http://localhost:4466/apis/gateway.networking.k8s.io/v1/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..bf1b5e3e11 --- /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..4c3448e772 --- /dev/null +++ b/frontend/src/components/gateway/__snapshots__/ClassDetails.Basic.stories.storyshot @@ -0,0 +1,101 @@ + +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + + + + +
    +
    +
    +
    +
    + \ 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..ca36082877 --- /dev/null +++ b/frontend/src/components/gateway/__snapshots__/ClassList.Items.stories.storyshot @@ -0,0 +1,579 @@ + +
    +
    +
    +
    +
    +

    + Gateway Classes +

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

    + 3mo +

    +
    + +
    +
    +
    + +
    +
    +
    +
    +
    +
    + \ 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..179ee930c1 --- /dev/null +++ b/frontend/src/components/gateway/__snapshots__/GRPCRouteDetails.Basic.stories.storyshot @@ -0,0 +1,389 @@ + +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    +

    + GRPCRoute: default-httproute +

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

    + ParentRefs +

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

    + 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..00f9a16d75 --- /dev/null +++ b/frontend/src/components/gateway/__snapshots__/GRPCRouteList.Items.stories.storyshot @@ -0,0 +1,615 @@ + +
    +
    +
    +
    +
    +

    + GRPCRoutes +

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

    + 3mo +

    +
    + +
    +
    +
    + +
    +
    +
    +
    +
    +
    + \ 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..635b52fe91 --- /dev/null +++ b/frontend/src/components/gateway/__snapshots__/GatewayDetails.Basic.stories.storyshot @@ -0,0 +1,429 @@ + +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    +

    + Gateway: default-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..67cf7bbffa --- /dev/null +++ b/frontend/src/components/gateway/__snapshots__/GatewayList.Items.stories.storyshot @@ -0,0 +1,798 @@ + +
    +
    +
    +
    +
    +

    + Gateways +

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

    + 3mo +

    +
    + +
    +
    +
    + +
    +
    +
    +
    +
    +
    + \ 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..d04105b967 --- /dev/null +++ b/frontend/src/components/gateway/__snapshots__/HTTPRouteDetails.Basic.stories.storyshot @@ -0,0 +1,341 @@ + +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    +

    + HTTPRoute: default-httproute +

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

    + Rules +

    +
    +
    +
    +
    +
    +
    +
    + test +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +

    + 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..d4a8ea946f --- /dev/null +++ b/frontend/src/components/gateway/__snapshots__/HTTPRouteList.Items.stories.storyshot @@ -0,0 +1,741 @@ + +
    +
    +
    +
    +
    +

    + HttpRoutes +

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

    + 3mo +

    +
    + +
    +
    +
    + +
    +
    +
    +
    +
    +
    + \ 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..954e558eb0 --- /dev/null +++ b/frontend/src/components/gateway/storyHelper.ts @@ -0,0 +1,109 @@ +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: [ + { + name: 'test', + backendRefs: [], + matches: [], + }, + { + matches: [], + }, + { + backendRefs: [], + }, + ], + }, +}; + +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', + name: 'envoy-gateway-system', + }, + { + group: 'gateway.networking.k8s.io', + kind: 'Gateway', + namespace: 'shared-gateway', + sectionName: 'test', + name: 'envoy-gateway-system-test', + }, + ], + }, +}; diff --git a/frontend/src/components/gateway/utils.tsx b/frontend/src/components/gateway/utils.tsx new file mode 100644 index 0000000000..de7c025838 --- /dev/null +++ b/frontend/src/components/gateway/utils.tsx @@ -0,0 +1,51 @@ +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, + }, + { + label: t('translation|Port'), + getter: (data: GatewayParentReference) => data.port, + }, + ]} + data={parentRefs || []} + reflectInURL="parentRefs" + /> + + ); +} 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/i18n/locales/zh-tw/glossary.json b/frontend/src/i18n/locales/zh-tw/glossary.json index cfd1452a7b..329f83fda3 100644 --- a/frontend/src/i18n/locales/zh-tw/glossary.json +++ b/frontend/src/i18n/locales/zh-tw/glossary.json @@ -61,13 +61,22 @@ "Port": "端口", "Protocol": "協定", "Endpoints": "端點", + "Controller Name": "", + "Gateway Classes": "", + "Controller": "控制器", + "Class Name": "類別名稱", + "Addresses": "", + "Listeners": "", + "No data": "", + "Gateways": "", + "GRPCRoutes": "", + "Rules": "規則", + "HttpRoutes": "", + "Hostnames": "", "Horizontal Pod Autoscalers": "水平 Pod 自動擴縮", "Ingress Classes": "Ingress 類別", "Default Ingress Class": "預設 Ingress 類別", - "Controller": "控制器", "Default Backend": "預設後端", - "Class Name": "類別名稱", - "Rules": "規則", "Backends": "後端", "Ingresses": "Ingresses", "Hosts": "主機", @@ -165,6 +174,9 @@ "Persistent Volumes": "持續性卷", "Storage Classes": "儲存類別", "Network": "網路", + "Gateway": "", + "HTTP Routes": "", + "GRPC Routes": "", "Security": "安全", "Configuration": "配置", "HPAs": "水平 Pod 自動擴縮", diff --git a/frontend/src/i18n/locales/zh-tw/translation.json b/frontend/src/i18n/locales/zh-tw/translation.json index dce645076a..81c93901ec 100644 --- a/frontend/src/i18n/locales/zh-tw/translation.json +++ b/frontend/src/i18n/locales/zh-tw/translation.json @@ -322,6 +322,19 @@ "Current": "當前", "Desired//context:pods": "期望", "Addresses": "地址", + "Hostname": "", + "Port": "", + "Protocol": "", + "No addresses data to be shown.": "", + "Listeners": "", + "BackendRefs": "", + "Matches": "", + "rules": "", + "ParentRefs": "", + "No rules data to be shown.": "無規則數據顯示。", + "Namespace": "命名空間", + "Kind": "", + "Section Name": "", "Search": "搜索", "Press<1>/to search": "按<1>/搜索", "Search resources, pages, clusters by name": "按名稱搜索資源、頁面、叢集", @@ -338,7 +351,6 @@ "Targets": "目標", "more…": "更多…", "Default": "預設", - "No rules data to be shown.": "無規則數據顯示。", "Host": "主機", "Path": "路徑", "Duration": "持續時間", @@ -393,7 +405,6 @@ "Zoom out": "縮小", "No data to be shown. Try to change filters or select a different namespace.": "無數據顯示。嘗試更改篩選器或選擇不同的命名空間。", "Group By: {{ name }}": "分組:{{ 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..eee86db9f0 --- /dev/null +++ b/frontend/src/lib/k8s/gateway.ts @@ -0,0 +1,105 @@ +import { KubeCondition } from './cluster'; +import { KubeObject, KubeObjectInterface } from './KubeObject'; + +/** + * ParentReference identifies an API object (usually a Gateway) that can be considered a parent of this resource (usually a route). + * + * @see {@link https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.ParentReference} Gateway API reference for ParentReference + */ +export interface GatewayParentReference { + group?: string; + kind?: string; + namespace?: string; + sectionName?: string; + name: string; + port?: number; +} + +/** + * Listener embodies the concept of a logical endpoint where a Gateway accepts network connections. + * + * @see {@link https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.Listener} Gateway API reference for Listener + */ +export interface GatewayListener { + hostname: string; + name: string; + protocol: string; + port: number; + [key: string]: any; +} + +/** + * ListenerStatus is the status associated with a Listener. + * + * @see {@link https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.ListenerStatus} Gateway API reference for ListenerStatus + */ +export interface GatewayListenerStatus { + name: string; + attachedRoutes: number; + supportedKinds: any[]; + conditions: KubeCondition[]; +} + +/** + * GatewayStatusAddress describes a network address that is bound to a Gateway. + * + * @see {@link https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.GatewayStatusAddress} Gateway API reference for GatewayStatusAddress + */ +export interface GatewayStatusAddress { + type?: string; + value: string; +} + +/** + * Gateway represents an instance of a service-traffic handling infrastructure by binding Listeners to a set of IP addresses. + * + * @see {@link https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.Gateway} Gateway API reference for Gateway + * + * @see {@link https://gateway-api.sigs.k8s.io/api-types/gateway/} Gateway API definition for Gateway + */ +export interface KubeGateway extends KubeObjectInterface { + spec: { + gatewayClassName?: string; + listeners: GatewayListener[]; + [key: string]: any; + }; + status: { + addresses?: GatewayStatusAddress[]; + listeners?: GatewayListenerStatus[]; + conditions?: KubeCondition[]; + [otherProps: string]: any; + }; +} + +class Gateway extends KubeObject { + static kind = 'Gateway'; + static apiName = 'gateways'; + static apiVersion = ['gateway.networking.k8s.io/v1', '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(): GatewayStatusAddress[] { + 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'; + } +} + +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..9f04338fac --- /dev/null +++ b/frontend/src/lib/k8s/gatewayClass.ts @@ -0,0 +1,49 @@ +import { KubeCondition } from './cluster'; +import { KubeObject, KubeObjectInterface } from './KubeObject'; + +/** + * GatewayClass is cluster-scoped resource defined by the infrastructure provider. This resource represents a class of Gateways that can be instantiated. + * + * @see {@link https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.GatewayClass} Gateway API reference for GatewayClass + * + * @see {@link https://gateway-api.sigs.k8s.io/api-types/gatewayclass/} Gateway API definition for GatewayClass + */ +export interface KubeGatewayClass extends KubeObjectInterface { + spec: { + controllerName: string; + [key: string]: any; + }; + status: { + conditions?: KubeCondition[]; + [otherProps: string]: any; + }; +} + +class GatewayClass extends KubeObject { + static kind = 'GatewayClass'; + static apiName = 'gatewayclasses'; + static apiVersion = ['gateway.networking.k8s.io/v1', '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..74b93b3e65 --- /dev/null +++ b/frontend/src/lib/k8s/grpcRoute.ts @@ -0,0 +1,36 @@ +import { GatewayParentReference } from './gateway'; +import { KubeObject, KubeObjectInterface } from './KubeObject'; + +/** + * GRPCRoute is a Gateway API type for specifying routing behavior of gRPC requests from a Gateway listener to an API object, i.e. Service. + * + * @see {@link https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.GRPCRoute} Gateway API reference for GRPCRoute + * + * @see {@link https://gateway-api.sigs.k8s.io/api-types/grpcroute/} Gateway API definition for GRPCRoute + */ +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/v1', '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..4dc140b102 --- /dev/null +++ b/frontend/src/lib/k8s/httpRoute.ts @@ -0,0 +1,61 @@ +import { GatewayParentReference } from './gateway'; +import { KubeObject, KubeObjectInterface } from './KubeObject'; + +/** + * HTTPRouteRule defines semantics for matching an HTTP request based on conditions (matches), processing it (filters), and forwarding the request to an API object (backendRefs). + * + * @see {@link https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.HTTPRouteRule} Gateway API reference for HTTPRouteRule + * + * @see {@link https://gateway-api.sigs.k8s.io/api-types/httproute/#rules} Gateway API definition for HTTPRouteRule + */ +export interface HTTPRouteRule { + name?: string; + backendRefs?: any[]; + matches?: any[]; + [key: string]: any; +} + +/** + * HTTPRoute is a Gateway API type for specifying routing behavior of HTTP requests from a Gateway listener to an API object, i.e. Service. + * + * @see {@link https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.HTTPRoute} Gateway API reference for HTTPRoute + * + * @see {@link https://gateway-api.sigs.k8s.io/api-types/httproute/} Gateway API definition for HTTPRoute + */ +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/v1', '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..f480a73bf5 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: () => , }, + gateways: { + // fix magic name gateway + path: '/gateways', + exact: true, + name: 'Gateways', + sidebar: 'gateways', + component: () => , + }, + gateway: { + // fix magic name gateway + path: '/gateways/:namespace/:name', + exact: true, + name: 'Gateways', + sidebar: 'gateways', + 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],