From 6be07609717b0b656d0fac2471f68ea6fdc44267 Mon Sep 17 00:00:00 2001 From: farodin91 Date: Mon, 1 Sep 2025 22:27:08 +0200 Subject: [PATCH] frontend: add endpointslice support * add support for resource map * add support for global search * add support for details and list * link in services Signed-off-by: farodin91 --- ...r.InClusterSidebarClosed.stories.storyshot | 23 + ...bar.InClusterSidebarOpen.stories.storyshot | 23 + ...edItemWithSidebarOmitted.stories.storyshot | 23 + .../src/components/Sidebar/useSidebarItems.ts | 4 + .../src/components/endpointSlices/Details.tsx | 141 ++++ .../EndpointSliceDetails.stories.tsx | 121 +++ .../EndpointSliceList.stories.tsx | 88 ++ .../src/components/endpointSlices/List.tsx | 84 ++ ...ointSliceDetails.Default.stories.storyshot | 363 ++++++++ ...dpointSliceDetails.Error.stories.storyshot | 103 +++ .../EndpointSliceList.Items.stories.storyshot | 798 ++++++++++++++++++ .../globalSearch/GlobalSearchContent.tsx | 2 + .../src/components/project/projectUtils.ts | 9 + .../resourceMap/details/KubeNodeDetails.tsx | 2 + .../resourceMap/graph/graphModel.tsx | 1 + .../resourceMap/kubeIcon/KubeIcon.tsx | 2 + .../sources/definitions/relations.tsx | 8 + .../sources/definitions/sources.tsx | 2 + frontend/src/components/service/Details.tsx | 48 ++ .../service/ServiceDetails.stories.tsx | 68 +- .../ServiceDetails.Default.stories.storyshot | 120 +++ ...tails.ErrorWithEndpoints.stories.storyshot | 45 + frontend/src/i18n/locales/de/glossary.json | 8 +- frontend/src/i18n/locales/de/translation.json | 8 +- frontend/src/i18n/locales/en/glossary.json | 8 +- frontend/src/i18n/locales/en/translation.json | 8 +- frontend/src/i18n/locales/es/glossary.json | 8 +- frontend/src/i18n/locales/es/translation.json | 8 +- frontend/src/i18n/locales/fr/glossary.json | 8 +- frontend/src/i18n/locales/fr/translation.json | 8 +- frontend/src/i18n/locales/hi/glossary.json | 8 +- frontend/src/i18n/locales/hi/translation.json | 8 +- frontend/src/i18n/locales/it/glossary.json | 8 +- frontend/src/i18n/locales/it/translation.json | 8 +- frontend/src/i18n/locales/ja/glossary.json | 8 +- frontend/src/i18n/locales/ja/translation.json | 8 +- frontend/src/i18n/locales/ko/glossary.json | 8 +- frontend/src/i18n/locales/ko/translation.json | 8 +- frontend/src/i18n/locales/pt/glossary.json | 8 +- frontend/src/i18n/locales/pt/translation.json | 8 +- frontend/src/i18n/locales/ta/glossary.json | 8 +- frontend/src/i18n/locales/ta/translation.json | 8 +- frontend/src/i18n/locales/zh-tw/glossary.json | 8 +- .../src/i18n/locales/zh-tw/translation.json | 8 +- frontend/src/i18n/locales/zh/glossary.json | 8 +- frontend/src/i18n/locales/zh/translation.json | 8 +- frontend/src/lib/k8s/ResourceCategory.tsx | 2 +- frontend/src/lib/k8s/endpointSlices.ts | 74 ++ frontend/src/lib/k8s/index.test.ts | 1 + frontend/src/lib/k8s/index.ts | 2 + frontend/src/lib/router/index.tsx | 15 + .../plugin/__snapshots__/pluginLib.snapshot | 1 + 52 files changed, 2314 insertions(+), 51 deletions(-) create mode 100644 frontend/src/components/endpointSlices/Details.tsx create mode 100644 frontend/src/components/endpointSlices/EndpointSliceDetails.stories.tsx create mode 100644 frontend/src/components/endpointSlices/EndpointSliceList.stories.tsx create mode 100644 frontend/src/components/endpointSlices/List.tsx create mode 100644 frontend/src/components/endpointSlices/__snapshots__/EndpointSliceDetails.Default.stories.storyshot create mode 100644 frontend/src/components/endpointSlices/__snapshots__/EndpointSliceDetails.Error.stories.storyshot create mode 100644 frontend/src/components/endpointSlices/__snapshots__/EndpointSliceList.Items.stories.storyshot create mode 100644 frontend/src/lib/k8s/endpointSlices.ts diff --git a/frontend/src/components/Sidebar/__snapshots__/Sidebar.InClusterSidebarClosed.stories.storyshot b/frontend/src/components/Sidebar/__snapshots__/Sidebar.InClusterSidebarClosed.stories.storyshot index fe367ce1f0a..87bd16f4322 100644 --- a/frontend/src/components/Sidebar/__snapshots__/Sidebar.InClusterSidebarClosed.stories.storyshot +++ b/frontend/src/components/Sidebar/__snapshots__/Sidebar.InClusterSidebarClosed.stories.storyshot @@ -558,6 +558,29 @@ /> +
  • + +
    + + Endpoint Slices + +
    + +
    +
  • diff --git a/frontend/src/components/Sidebar/__snapshots__/Sidebar.InClusterSidebarOpen.stories.storyshot b/frontend/src/components/Sidebar/__snapshots__/Sidebar.InClusterSidebarOpen.stories.storyshot index fe97d54e8da..da632c76ee6 100644 --- a/frontend/src/components/Sidebar/__snapshots__/Sidebar.InClusterSidebarOpen.stories.storyshot +++ b/frontend/src/components/Sidebar/__snapshots__/Sidebar.InClusterSidebarOpen.stories.storyshot @@ -600,6 +600,29 @@ />
  • +
  • + +
    + + Endpoint Slices + +
    + +
    +
  • diff --git a/frontend/src/components/Sidebar/__snapshots__/Sidebar.SelectedItemWithSidebarOmitted.stories.storyshot b/frontend/src/components/Sidebar/__snapshots__/Sidebar.SelectedItemWithSidebarOmitted.stories.storyshot index 4f3f83f23ed..00f749a42e5 100644 --- a/frontend/src/components/Sidebar/__snapshots__/Sidebar.SelectedItemWithSidebarOmitted.stories.storyshot +++ b/frontend/src/components/Sidebar/__snapshots__/Sidebar.SelectedItemWithSidebarOmitted.stories.storyshot @@ -600,6 +600,29 @@ />
  • +
  • + +
    + + Endpoint Slices + +
    + +
    +
  • diff --git a/frontend/src/components/Sidebar/useSidebarItems.ts b/frontend/src/components/Sidebar/useSidebarItems.ts index bc3c9b10be4..0bdf8291eea 100644 --- a/frontend/src/components/Sidebar/useSidebarItems.ts +++ b/frontend/src/components/Sidebar/useSidebarItems.ts @@ -247,6 +247,10 @@ export const useSidebarItems = (sidebarName: string = DefaultSidebars.IN_CLUSTER name: 'endpoints', label: t('glossary|Endpoints'), }, + { + name: 'endpointslices', + label: t('glossary|Endpoint Slices'), + }, { name: 'ingresses', label: t('glossary|Ingresses'), diff --git a/frontend/src/components/endpointSlices/Details.tsx b/frontend/src/components/endpointSlices/Details.tsx new file mode 100644 index 00000000000..529490e8884 --- /dev/null +++ b/frontend/src/components/endpointSlices/Details.tsx @@ -0,0 +1,141 @@ +/* + * Copyright 2025 The Kubernetes Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import Box from '@mui/material/Box'; +import { useTranslation } from 'react-i18next'; +import { useParams } from 'react-router-dom'; +import EndpointSlice from '../../lib/k8s/endpointSlices'; +import { SectionBox, SimpleTable, StatusLabel } from '../common'; +import { DetailsGrid } from '../common/Resource'; + +export default function EndpointSliceDetails(props: { + name?: string; + namespace?: string; + cluster?: string; +}) { + const params = useParams<{ namespace: string; name: string }>(); + const { name = params.name, namespace = params.namespace, cluster } = props; + const { t } = useTranslation(['glossary', 'translation']); + + return ( + + item && [ + { + name: t('translation|Address Type'), + value: item.spec.addressType, + }, + ] + } + extraSections={(item: EndpointSlice) => + item && [ + { + id: 'headlamp.endpoint-slice-endpoints', + section: ( + + endpoint.addresses?.join(','), + }, + { + label: t('Conditions'), + getter: endpoint => ( + <> + + + {t('Ready')} + + + + + {t('Serving')} + + + + + {t('Terminating')} + + + + ), + }, + ]} + defaultSortingColumn={1} + reflectInURL="endpoints" + /> + + ), + }, + { + id: 'headlamp.endpoint-slice-ports', + section: ( + + + + ), + }, + ] + } + /> + ); +} diff --git a/frontend/src/components/endpointSlices/EndpointSliceDetails.stories.tsx b/frontend/src/components/endpointSlices/EndpointSliceDetails.stories.tsx new file mode 100644 index 00000000000..fe41244cc4b --- /dev/null +++ b/frontend/src/components/endpointSlices/EndpointSliceDetails.stories.tsx @@ -0,0 +1,121 @@ +/* + * Copyright 2025 The Kubernetes Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Meta, StoryFn } from '@storybook/react'; +import { http, HttpResponse } from 'msw'; +import { TestContext } from '../../test'; +import EndpointSliceDetails from './Details'; + +export default { + title: 'endpointslice/EndpointSliceDetailsView', + component: EndpointSliceDetails, + argTypes: {}, + parameters: { + msw: { + handlers: { + storyBase: [ + http.get('http://localhost:4466/apis/discovery.k8s.io/v1/endpointslices', () => + HttpResponse.error() + ), + http.get('http://localhost:4466/api/v1/namespaces/my-namespace/events', () => + HttpResponse.error() + ), + ], + }, + }, + }, +} as Meta; + +const Template: StoryFn = () => { + return ( + + + + ); +}; + +export const Default = Template.bind({}); +Default.parameters = { + msw: { + handlers: { + story: [ + http.get( + 'http://localhost:4466/apis/discovery.k8s.io/v1/namespaces/my-namespace/endpointslices/my-endpoint', + () => + HttpResponse.json({ + kind: 'EndpointSlice', + apiVersion: 'discovery.k8s.io/v1', + metadata: { + name: 'my-endpoint', + namespace: 'my-namespace', + uid: 'phony', + creationTimestamp: new Date('2020-04-25').toISOString(), + resourceVersion: '1', + selfLink: '0', + }, + endpoints: [ + { + addresses: ['127.0.0.1'], + nodeName: 'mynode', + targetRef: { + kind: 'Pod', + namespace: 'MyNamespace', + name: 'mypod', + uid: 'phony-pod', + resourceVersion: '1', + apiVersion: 'v1', + }, + }, + { + addresses: ['127.0.0.2'], + nodeName: 'mynode', + targetRef: { + kind: 'Pod', + namespace: 'MyNamespace', + name: 'mypod-1', + uid: 'phony-pod-1', + resourceVersion: '1', + apiVersion: 'v1', + }, + }, + ], + ports: [ + { + name: 'myport', + port: 8080, + protocol: 'TCP', + }, + ], + }) + ), + ], + }, + }, +}; + +export const Error = Template.bind({}); +Error.parameters = { + msw: { + handlers: { + story: [ + http.get( + 'http://localhost:4466/apis/discovery.k8s.io/v1/namespaces/my-namespace/endpointslices/my-endpoint', + () => HttpResponse.error() + ), + ], + }, + }, +}; diff --git a/frontend/src/components/endpointSlices/EndpointSliceList.stories.tsx b/frontend/src/components/endpointSlices/EndpointSliceList.stories.tsx new file mode 100644 index 00000000000..22922243264 --- /dev/null +++ b/frontend/src/components/endpointSlices/EndpointSliceList.stories.tsx @@ -0,0 +1,88 @@ +/* + * Copyright 2025 The Kubernetes Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Meta, StoryFn } from '@storybook/react'; +import { http, HttpResponse } from 'msw'; +import { TestContext } from '../../test'; +import EndpointSliceList from './List'; + +const list = [ + { + kind: 'EndpointSlice', + apiVersion: 'discovery.k8s.io/v1', + metadata: { + namespace: '', + creationTimestamp: new Date('2022-01-01').toISOString(), + }, + endpoints: [ + { + addresses: ['127.0.0.1'], + nodeName: 'mynode', + targetRef: { + kind: 'Pod', + namespace: 'my-namespace', + name: 'mypod', + uid: 'phony-pod', + resourceVersion: '1', + apiVersion: 'v1', + }, + }, + ], + ports: [ + { + name: 'myport', + port: 8080, + protocol: 'TCP', + }, + ], + }, +]; + +export default { + title: 'endpointslice/EndpointSliceListView', + component: EndpointSliceList, + argTypes: {}, + decorators: [ + Story => { + return ( + + + + ); + }, + ], + parameters: { + msw: { + handlers: { + story: [ + http.get('http://localhost:4466/apis/discovery.k8s.io/v1/endpointslices', () => + HttpResponse.json({ + kind: 'EndpointSliceList', + items: list, + metadata: {}, + }) + ), + ], + }, + }, + }, +} as Meta; + +const Template: StoryFn = () => { + return ; +}; + +export const Items = Template.bind({}); diff --git a/frontend/src/components/endpointSlices/List.tsx b/frontend/src/components/endpointSlices/List.tsx new file mode 100644 index 00000000000..a7fc1780f3b --- /dev/null +++ b/frontend/src/components/endpointSlices/List.tsx @@ -0,0 +1,84 @@ +/* + * Copyright 2025 The Kubernetes Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import Box from '@mui/material/Box'; +import { useTranslation } from 'react-i18next'; +import EndpointSlice from '../../lib/k8s/endpointSlices'; +import { LabelListItem } from '../common'; +import { StatusLabel } from '../common/Label'; +import ResourceListView from '../common/Resource/ResourceListView'; + +function renderEndpoints(endpointSlice: EndpointSlice) { + const endpoints = endpointSlice.spec.endpoints; + if (!endpoints) { + return null; + } + + return endpoints.map((endpoint: any) => { + const { addresses, conditions } = endpoint; + return ( + + + {addresses.join(',')} + + + ); + }); +} + +export default function EndpointSliceList() { + const { t } = useTranslation(['glossary', 'translation']); + + return ( + + endpoint.spec?.endpoints?.map((c: any) => c.addresses?.join(','))?.join(','), + render: endpoint => renderEndpoints(endpoint), + gridTemplate: 'auto', + cellProps: { + sx: { + flexWrap: 'wrap', + gap: '4px', + }, + }, + }, + { + id: 'ports', + label: t('Ports'), + gridTemplate: 'auto', + getValue: endpoint => endpoint.ports?.join(', '), + render: endpoint => , + }, + { + id: 'addressType', + label: t('translation|Address Type'), + filterVariant: 'multi-select', + getValue: endpoint => endpoint?.spec?.addressType ?? '', + }, + 'age', + ]} + /> + ); +} diff --git a/frontend/src/components/endpointSlices/__snapshots__/EndpointSliceDetails.Default.stories.storyshot b/frontend/src/components/endpointSlices/__snapshots__/EndpointSliceDetails.Default.stories.storyshot new file mode 100644 index 00000000000..9ee674dc190 --- /dev/null +++ b/frontend/src/components/endpointSlices/__snapshots__/EndpointSliceDetails.Default.stories.storyshot @@ -0,0 +1,363 @@ + +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    +

    + Endpoint Slice +

    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + Name +
    +
    + + my-endpoint + +
    +
    + Namespace +
    +
    + + my-namespace + +
    +
    + Creation +
    +
    + + 2020-04-25T00:00:00.000Z + +
    +
    + Address Type +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +

    + Ports +

    +
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + +
    + Name + + + Port + + + Protocol + +
    + myport + + 8080 + + TCP +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +

    + Events +

    +
    +
    +
    +
    +
    +
    +
    +

    + No data to be shown. +

    +
    +
    +
    +
    +
    +
    +
    +
    + \ No newline at end of file diff --git a/frontend/src/components/endpointSlices/__snapshots__/EndpointSliceDetails.Error.stories.storyshot b/frontend/src/components/endpointSlices/__snapshots__/EndpointSliceDetails.Error.stories.storyshot new file mode 100644 index 00000000000..8054040dc2f --- /dev/null +++ b/frontend/src/components/endpointSlices/__snapshots__/EndpointSliceDetails.Error.stories.storyshot @@ -0,0 +1,103 @@ + +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    +

    + Endpoint Slice +

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

    + TypeError: Failed to fetch +

    +
    +
    +
    +
    +
    +
    + \ No newline at end of file diff --git a/frontend/src/components/endpointSlices/__snapshots__/EndpointSliceList.Items.stories.storyshot b/frontend/src/components/endpointSlices/__snapshots__/EndpointSliceList.Items.stories.storyshot new file mode 100644 index 00000000000..be42385b2f1 --- /dev/null +++ b/frontend/src/components/endpointSlices/__snapshots__/EndpointSliceList.Items.stories.storyshot @@ -0,0 +1,798 @@ + +
    +
    +
    +
    +
    +

    + Endpoint Slices +

    +
    + +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    + +
    + +
    + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    + +
    + +
    +
    + + + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    + + + + + + + + + +
    + + 127.0.0.1 + +
    +
    + + 8080 + + + +

    + 3mo +

    +
    + +
    +
    +
    + +
    +
    +
    +
    +
    +
    + \ No newline at end of file diff --git a/frontend/src/components/globalSearch/GlobalSearchContent.tsx b/frontend/src/components/globalSearch/GlobalSearchContent.tsx index e75573ed1f6..51ebe63f99b 100644 --- a/frontend/src/components/globalSearch/GlobalSearchContent.tsx +++ b/frontend/src/components/globalSearch/GlobalSearchContent.tsx @@ -35,6 +35,7 @@ import ConfigMap from '../../lib/k8s/configMap'; import CronJob from '../../lib/k8s/cronJob'; import Deployment from '../../lib/k8s/deployment'; import Endpoints from '../../lib/k8s/endpoints'; +import EndpointSlice from '../../lib/k8s/endpointSlices'; import Ingress from '../../lib/k8s/ingress'; import Job from '../../lib/k8s/job'; import { KubeObject, KubeObjectClass } from '../../lib/k8s/KubeObject'; @@ -92,6 +93,7 @@ const classes: KubeObjectClass[] = [ ReplicaSet, PersistentVolumeClaim, Endpoints, + EndpointSlice, Ingress, ServiceAccount, Node, diff --git a/frontend/src/components/project/projectUtils.ts b/frontend/src/components/project/projectUtils.ts index 2ae671a632c..badd0d9b9d9 100644 --- a/frontend/src/components/project/projectUtils.ts +++ b/frontend/src/components/project/projectUtils.ts @@ -55,6 +55,15 @@ export const defaultApiResources = (() => { kind: 'Endpoints', isNamespaced: true, }, + { + apiVersion: 'discovery.k8s.io/v1', + version: 'v1', + groupName: 'discovery.k8s.io', + pluralName: 'endpointslices', + singularName: 'endpointSlice', + kind: 'EndpointSlice', + isNamespaced: true, + }, { apiVersion: 'v1', version: 'v1', diff --git a/frontend/src/components/resourceMap/details/KubeNodeDetails.tsx b/frontend/src/components/resourceMap/details/KubeNodeDetails.tsx index fac82fadf22..c3bca9812f2 100644 --- a/frontend/src/components/resourceMap/details/KubeNodeDetails.tsx +++ b/frontend/src/components/resourceMap/details/KubeNodeDetails.tsx @@ -25,6 +25,7 @@ import CustomResourceDefinitionDetails from '../../crd/Details'; import CronJobDetails from '../../cronjob/Details'; import DaemonSetDetails from '../../daemonset/Details'; import EndpointDetails from '../../endpoints/Details'; +import EndpointSliceDetails from '../../endpointSlices/Details'; import BackendTLSPolicyDetails from '../../gateway/BackendTLSPolicyDetails'; import GatewayClassDetails from '../../gateway/ClassDetails'; import GatewayDetails from '../../gateway/GatewayDetails'; @@ -71,6 +72,7 @@ const kindComponentMap: Record< DaemonSet: DaemonSetDetails, ConfigMap: ConfigDetails, Endpoints: EndpointDetails, + EndpointSlice: EndpointSliceDetails, HorizontalPodAutoscaler: HpaDetails, Ingress: IngressDetails, Lease: LeaseDetails, diff --git a/frontend/src/components/resourceMap/graph/graphModel.tsx b/frontend/src/components/resourceMap/graph/graphModel.tsx index 09dece0e59a..e84cc6a1adc 100644 --- a/frontend/src/components/resourceMap/graph/graphModel.tsx +++ b/frontend/src/components/resourceMap/graph/graphModel.tsx @@ -160,6 +160,7 @@ const DEFAULT_NODE_WEIGHTS = { // Tier 6: Supporting Resources // Network supporting resources (cascading from Service/NetworkPolicy) Endpoints: 780, + EndpointSlice: 780, MutatingWebhookConfiguration: 780, ValidatingWebhookConfiguration: 780, IngressClass: 780, diff --git a/frontend/src/components/resourceMap/kubeIcon/KubeIcon.tsx b/frontend/src/components/resourceMap/kubeIcon/KubeIcon.tsx index c062c5bcaba..1d7734421ba 100644 --- a/frontend/src/components/resourceMap/kubeIcon/KubeIcon.tsx +++ b/frontend/src/components/resourceMap/kubeIcon/KubeIcon.tsx @@ -69,6 +69,7 @@ const kindToIcon = { Deployment: DeployIcon, Endpoint: EpIcon, Endpoints: EpIcon, + EndpointSlice: EpIcon, HorizontalPodAutoscaler: HpaIcon, Job: JobIcon, NetworkPolicy: NetpolIcon, @@ -98,6 +99,7 @@ const kindGroups = { 'Service', 'Endpoints', 'Endpoint', + 'EndpointSlice', 'Ingress', 'IngressClass', 'NetworkPolicy', diff --git a/frontend/src/components/resourceMap/sources/definitions/relations.tsx b/frontend/src/components/resourceMap/sources/definitions/relations.tsx index 4e21efddac8..158a7e0eac3 100644 --- a/frontend/src/components/resourceMap/sources/definitions/relations.tsx +++ b/frontend/src/components/resourceMap/sources/definitions/relations.tsx @@ -23,6 +23,7 @@ import CronJob from '../../../../lib/k8s/cronJob'; import DaemonSet from '../../../../lib/k8s/daemonSet'; import Deployment from '../../../../lib/k8s/deployment'; import Endpoints from '../../../../lib/k8s/endpoints'; +import EndpointSlice from '../../../../lib/k8s/endpointSlices'; import Gateway from '../../../../lib/k8s/gateway'; import GatewayClass from '../../../../lib/k8s/gatewayClass'; import HPA from '../../../../lib/k8s/hpa'; @@ -159,6 +160,12 @@ const endpointsToServices = makeRelation( (endpoint, service) => endpoint.getName() === service.getName() ); +const endpointSlicesToServices = makeRelation( + EndpointSlice, + Service, + (endpoint, service) => endpoint.getOwnerServiceName() === service.getName() +); + const ingressToService = makeRelation(Ingress, Service, (ingress, service) => ingress.spec.rules?.find((rule: any) => rule.http?.paths?.find((path: any) => service.metadata.name === path?.backend?.service?.name) @@ -270,6 +277,7 @@ export function useGetAllRelations(): Relation[] { mwcToService, serviceToPods, endpointsToServices, + endpointSlicesToServices, ingressToService, ingressToSecret, networkPolicyToPod, diff --git a/frontend/src/components/resourceMap/sources/definitions/sources.tsx b/frontend/src/components/resourceMap/sources/definitions/sources.tsx index 1156119ba5a..333d091857a 100644 --- a/frontend/src/components/resourceMap/sources/definitions/sources.tsx +++ b/frontend/src/components/resourceMap/sources/definitions/sources.tsx @@ -24,6 +24,7 @@ import CronJob from '../../../../lib/k8s/cronJob'; import DaemonSet from '../../../../lib/k8s/daemonSet'; import Deployment from '../../../../lib/k8s/deployment'; import Endpoints from '../../../../lib/k8s/endpoints'; +import EndpointSlice from '../../../../lib/k8s/endpointSlices'; import Gateway from '../../../../lib/k8s/gateway'; import GatewayClass from '../../../../lib/k8s/gatewayClass'; import GRPCRoute from '../../../../lib/k8s/grpcRoute'; @@ -139,6 +140,7 @@ export function useGetAllSources(): GraphSource[] { sources: [ makeKubeSource(Service), makeKubeSource(Endpoints), + makeKubeSource(EndpointSlice), makeKubeSource(Ingress), makeKubeSource(IngressClass), makeKubeSource(NetworkPolicy), diff --git a/frontend/src/components/service/Details.tsx b/frontend/src/components/service/Details.tsx index cf1bef1ce7c..c592851c8f9 100644 --- a/frontend/src/components/service/Details.tsx +++ b/frontend/src/components/service/Details.tsx @@ -21,6 +21,7 @@ import React from 'react'; import { useTranslation } from 'react-i18next'; import { useParams } from 'react-router-dom'; import Endpoint from '../../lib/k8s/endpoints'; +import EndpointSlice from '../../lib/k8s/endpointSlices'; import Service from '../../lib/k8s/service'; import Empty from '../common/EmptyContent'; import { ValueLabel } from '../common/Label'; @@ -40,10 +41,18 @@ export default function ServiceDetails(props: { const { t } = useTranslation(['glossary', 'translation']); const [endpoints, endpointsError] = Endpoint.useList({ namespace, cluster }); + const [endpointSlices, endpointSlicesError] = EndpointSlice.useList({ namespace, cluster }); function getOwnedEndpoints(item: Service) { return item ? endpoints?.filter(endpoint => endpoint.getName() === item.getName()) : null; } + function getOwnedEndpointSlices(item: Service) { + return item + ? endpointSlices?.filter( + endpointSlice => endpointSlice.getOwnerServiceName() === item.getName() + ) + : null; + } return ( ), }, + { + id: 'headlamp.service-endpointslices', + section: ( + + {endpointSlicesError ? ( + {endpointSlicesError.toString()} + ) : ( + , + }, + { + label: t('translation|Addresses'), + getter: endpointSlice => ( + + {endpointSlice.spec.endpoints.map((endpoint: any) => ( + {endpoint.addresses.join(',')} + ))} + + ), + }, + { + label: t('Ports'), + getter: endpoint => endpoint.ports?.join(', '), + }, + { + label: t('translation|Address Type'), + getter: endpoint => endpoint?.spec?.addressType ?? '', + }, + ]} + reflectInURL="endpoints" + /> + )} + + ), + }, ] } /> diff --git a/frontend/src/components/service/ServiceDetails.stories.tsx b/frontend/src/components/service/ServiceDetails.stories.tsx index 2351122643e..a031a7e9d69 100644 --- a/frontend/src/components/service/ServiceDetails.stories.tsx +++ b/frontend/src/components/service/ServiceDetails.stories.tsx @@ -57,7 +57,7 @@ const serviceMock = { }, }; -const list = [ +const endpoints = [ { apiVersion: 'v1', kind: 'Endpoints', @@ -75,6 +75,57 @@ const list = [ }, ]; +const endpointslices = [ + { + apiVersion: 'v1', + kind: 'EndpointSlice', + metadata: { + name: 'example-service', + namespace: 'default', + resourceVersion: '78910', + ownerReferences: [ + { + kind: 'Service', + name: 'example-service', + }, + ], + }, + endpoints: [ + { + addresses: ['127.0.0.1'], + nodeName: 'mynode', + targetRef: { + kind: 'Pod', + namespace: 'MyNamespace', + name: 'mypod', + uid: 'phony-pod', + resourceVersion: '1', + apiVersion: 'v1', + }, + }, + { + addresses: ['127.0.0.2'], + nodeName: 'mynode', + targetRef: { + kind: 'Pod', + namespace: 'MyNamespace', + name: 'mypod-1', + uid: 'phony-pod-1', + resourceVersion: '1', + apiVersion: 'v1', + }, + }, + ], + ports: [ + { + name: 'myport', + port: 8080, + protocol: 'TCP', + }, + ], + }, +]; + export default { title: 'Service/Details', component: Details, @@ -105,10 +156,19 @@ Default.parameters = { http.get('http://localhost:4466/api/v1/namespaces/default/endpoints', () => HttpResponse.json({ kind: 'List', - items: list, + items: endpoints, metadata: {}, }) ), + http.get( + 'http://localhost:4466/apis/discovery.k8s.io/v1/namespaces/default/endpointslices', + () => + HttpResponse.json({ + kind: 'List', + items: endpointslices, + metadata: {}, + }) + ), ], }, }, @@ -128,6 +188,10 @@ ErrorWithEndpoints.parameters = { http.get('http://localhost:4466/api/v1/namespaces/default/endpoints', () => HttpResponse.error() ), + http.get( + 'http://localhost:4466/apis/discovery.k8s.io/v1/namespaces/default/endpointslices', + () => HttpResponse.error() + ), ], }, }, diff --git a/frontend/src/components/service/__snapshots__/ServiceDetails.Default.stories.storyshot b/frontend/src/components/service/__snapshots__/ServiceDetails.Default.stories.storyshot index 1d6160c3824..c93c8d11893 100644 --- a/frontend/src/components/service/__snapshots__/ServiceDetails.Default.stories.storyshot +++ b/frontend/src/components/service/__snapshots__/ServiceDetails.Default.stories.storyshot @@ -430,6 +430,126 @@
    +
    +
    +
    +
    +
    +
    +

    + Endpoint Slices +

    +
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + +
    + Name + + Addresses + + Ports + + Address Type +
    + + example-service + + +
    + + 127.0.0.1 + + + 127.0.0.2 + +
    +
    + 8080 + +
    +
    +
    +
    +
    +
    diff --git a/frontend/src/components/service/__snapshots__/ServiceDetails.ErrorWithEndpoints.stories.storyshot b/frontend/src/components/service/__snapshots__/ServiceDetails.ErrorWithEndpoints.stories.storyshot index a950892392c..571ed86fc5c 100644 --- a/frontend/src/components/service/__snapshots__/ServiceDetails.ErrorWithEndpoints.stories.storyshot +++ b/frontend/src/components/service/__snapshots__/ServiceDetails.ErrorWithEndpoints.stories.storyshot @@ -380,6 +380,51 @@
    +
    +
    +
    +
    +
    +
    +

    + Endpoint Slices +

    +
    +
    +
    +
    +
    +
    +

    + TypeError: Failed to fetch +

    +
    +
    +
    +
    +
    diff --git a/frontend/src/i18n/locales/de/glossary.json b/frontend/src/i18n/locales/de/glossary.json index 8543c3e85ad..c42ca20b29c 100644 --- a/frontend/src/i18n/locales/de/glossary.json +++ b/frontend/src/i18n/locales/de/glossary.json @@ -62,6 +62,13 @@ "Port": "Port", "Protocol": "Protokoll", "Endpoints": "Endpunkte", + "Endpoint Slice": "", + "Addresses": "", + "Conditions": "", + "Ready": "", + "Serving": "", + "Terminating": "", + "Endpoint Slices": "", "Targets": "", "No targets defined": "", "TLS Validation": "", @@ -76,7 +83,6 @@ "Gateway Classes": "", "Controller": "Controller", "Class Name": "Name der Klasse", - "Addresses": "", "Listeners": "", "No data": "", "Gateways": "", diff --git a/frontend/src/i18n/locales/de/translation.json b/frontend/src/i18n/locales/de/translation.json index 1002758f3d5..5ff1ad5eed7 100644 --- a/frontend/src/i18n/locales/de/translation.json +++ b/frontend/src/i18n/locales/de/translation.json @@ -415,10 +415,14 @@ "Current": "Aktuell", "Desired//context:pods": "Gewünscht", "Addresses": "Adressen", + "Address Type": "", + "Hostname": "", + "Node Name": "", + "Zone": "", + "Endpoints": "", "Retry Budget": "", "Not specified": "", "Min Retry Rate": "", - "Hostname": "", "Port": "", "Protocol": "", "No addresses data to be shown.": "", @@ -521,14 +525,12 @@ "Load resources": "", "Upload files or load from URL": "", "Upload Files": "", - "Load from URL": "", "Drag & drop YAML files here or click to choose files": "", "Choose Files": "", "Supports .yaml and .yml files": "", "YAML URL": "", "Enter URL to YAML file": "", "Loading...": "", - "Load": "", "Load YAML resources from a remote URL": "", "Loaded Resources ({{count}})_one": "", "Loaded Resources ({{count}})_other": "", diff --git a/frontend/src/i18n/locales/en/glossary.json b/frontend/src/i18n/locales/en/glossary.json index da2e10c6d3b..ce9f247db33 100644 --- a/frontend/src/i18n/locales/en/glossary.json +++ b/frontend/src/i18n/locales/en/glossary.json @@ -62,6 +62,13 @@ "Port": "Port", "Protocol": "Protocol", "Endpoints": "Endpoints", + "Endpoint Slice": "Endpoint Slice", + "Addresses": "Addresses", + "Conditions": "Conditions", + "Ready": "Ready", + "Serving": "Serving", + "Terminating": "Terminating", + "Endpoint Slices": "Endpoint Slices", "Targets": "Targets", "No targets defined": "No targets defined", "TLS Validation": "TLS Validation", @@ -76,7 +83,6 @@ "Gateway Classes": "Gateway Classes", "Controller": "Controller", "Class Name": "Class Name", - "Addresses": "Addresses", "Listeners": "Listeners", "No data": "No data", "Gateways": "Gateways", diff --git a/frontend/src/i18n/locales/en/translation.json b/frontend/src/i18n/locales/en/translation.json index 800525b2fd3..ceaf6f1c863 100644 --- a/frontend/src/i18n/locales/en/translation.json +++ b/frontend/src/i18n/locales/en/translation.json @@ -415,10 +415,14 @@ "Current": "Current", "Desired//context:pods": "Desired", "Addresses": "Addresses", + "Address Type": "Address Type", + "Hostname": "Hostname", + "Node Name": "Node Name", + "Zone": "Zone", + "Endpoints": "Endpoints", "Retry Budget": "Retry Budget", "Not specified": "Not specified", "Min Retry Rate": "Min Retry Rate", - "Hostname": "Hostname", "Port": "Port", "Protocol": "Protocol", "No addresses data to be shown.": "No addresses data to be shown.", @@ -521,14 +525,12 @@ "Load resources": "Load resources", "Upload files or load from URL": "Upload files or load from URL", "Upload Files": "Upload Files", - "Load from URL": "Load from URL", "Drag & drop YAML files here or click to choose files": "Drag & drop YAML files here or click to choose files", "Choose Files": "Choose Files", "Supports .yaml and .yml files": "Supports .yaml and .yml files", "YAML URL": "YAML URL", "Enter URL to YAML file": "Enter URL to YAML file", "Loading...": "Loading...", - "Load": "Load", "Load YAML resources from a remote URL": "Load YAML resources from a remote URL", "Loaded Resources ({{count}})_one": "Loaded Resources ({{count}})", "Loaded Resources ({{count}})_other": "Loaded Resources ({{count}})", diff --git a/frontend/src/i18n/locales/es/glossary.json b/frontend/src/i18n/locales/es/glossary.json index 077dc051cc8..99105925b65 100644 --- a/frontend/src/i18n/locales/es/glossary.json +++ b/frontend/src/i18n/locales/es/glossary.json @@ -62,6 +62,13 @@ "Port": "Puerto", "Protocol": "Protocolo", "Endpoints": "Endpoints", + "Endpoint Slice": "", + "Addresses": "Direcciones", + "Conditions": "", + "Ready": "", + "Serving": "", + "Terminating": "", + "Endpoint Slices": "", "Targets": "", "No targets defined": "", "TLS Validation": "", @@ -76,7 +83,6 @@ "Gateway Classes": "Clases de Gateway", "Controller": "Controller", "Class Name": "Class Name", - "Addresses": "Direcciones", "Listeners": "Listeners", "No data": "Sin datos", "Gateways": "Gateways", diff --git a/frontend/src/i18n/locales/es/translation.json b/frontend/src/i18n/locales/es/translation.json index 3edc73fc588..f6b7ec48cc7 100644 --- a/frontend/src/i18n/locales/es/translation.json +++ b/frontend/src/i18n/locales/es/translation.json @@ -418,10 +418,14 @@ "Current": "Actual", "Desired//context:pods": "Deseados", "Addresses": "Direcciones", + "Address Type": "", + "Hostname": "Nombre de host", + "Node Name": "", + "Zone": "", + "Endpoints": "", "Retry Budget": "", "Not specified": "", "Min Retry Rate": "", - "Hostname": "Nombre de host", "Port": "Puerto", "Protocol": "Protocolo", "No addresses data to be shown.": "Sin datos de direcciones", @@ -524,14 +528,12 @@ "Load resources": "", "Upload files or load from URL": "", "Upload Files": "", - "Load from URL": "", "Drag & drop YAML files here or click to choose files": "", "Choose Files": "", "Supports .yaml and .yml files": "", "YAML URL": "", "Enter URL to YAML file": "", "Loading...": "", - "Load": "", "Load YAML resources from a remote URL": "", "Loaded Resources ({{count}})_one": "", "Loaded Resources ({{count}})_many": "", diff --git a/frontend/src/i18n/locales/fr/glossary.json b/frontend/src/i18n/locales/fr/glossary.json index 9f4fbabe4ef..619043b9e01 100644 --- a/frontend/src/i18n/locales/fr/glossary.json +++ b/frontend/src/i18n/locales/fr/glossary.json @@ -62,6 +62,13 @@ "Port": "Port", "Protocol": "Protocole", "Endpoints": "Endpoints", + "Endpoint Slice": "", + "Addresses": "", + "Conditions": "", + "Ready": "", + "Serving": "", + "Terminating": "", + "Endpoint Slices": "", "Targets": "", "No targets defined": "", "TLS Validation": "", @@ -76,7 +83,6 @@ "Gateway Classes": "", "Controller": "Controller", "Class Name": "Nom de la classe", - "Addresses": "", "Listeners": "", "No data": "", "Gateways": "", diff --git a/frontend/src/i18n/locales/fr/translation.json b/frontend/src/i18n/locales/fr/translation.json index df708dab35f..d3379d786c2 100644 --- a/frontend/src/i18n/locales/fr/translation.json +++ b/frontend/src/i18n/locales/fr/translation.json @@ -418,10 +418,14 @@ "Current": "Actuel", "Desired//context:pods": "Actuels", "Addresses": "Adresses", + "Address Type": "", + "Hostname": "", + "Node Name": "", + "Zone": "", + "Endpoints": "", "Retry Budget": "", "Not specified": "", "Min Retry Rate": "", - "Hostname": "", "Port": "", "Protocol": "", "No addresses data to be shown.": "", @@ -524,14 +528,12 @@ "Load resources": "", "Upload files or load from URL": "", "Upload Files": "", - "Load from URL": "", "Drag & drop YAML files here or click to choose files": "", "Choose Files": "", "Supports .yaml and .yml files": "", "YAML URL": "", "Enter URL to YAML file": "", "Loading...": "", - "Load": "", "Load YAML resources from a remote URL": "", "Loaded Resources ({{count}})_one": "", "Loaded Resources ({{count}})_many": "", diff --git a/frontend/src/i18n/locales/hi/glossary.json b/frontend/src/i18n/locales/hi/glossary.json index 72fd655ee6a..f1f9431cb18 100644 --- a/frontend/src/i18n/locales/hi/glossary.json +++ b/frontend/src/i18n/locales/hi/glossary.json @@ -62,6 +62,13 @@ "Port": "पोर्ट", "Protocol": "प्रोटोकॉल", "Endpoints": "एंडपॉइंट्स", + "Endpoint Slice": "", + "Addresses": "एड्रेसेज", + "Conditions": "", + "Ready": "", + "Serving": "", + "Terminating": "", + "Endpoint Slices": "", "Targets": "", "No targets defined": "", "TLS Validation": "", @@ -76,7 +83,6 @@ "Gateway Classes": "गेटवे क्लासेज", "Controller": "कंट्रोलर", "Class Name": "क्लास नाम", - "Addresses": "एड्रेसेज", "Listeners": "लिसनर्स", "No data": "कोई डेटा नहीं", "Gateways": "गेटवेज", diff --git a/frontend/src/i18n/locales/hi/translation.json b/frontend/src/i18n/locales/hi/translation.json index c7d73ced2d2..c89a267711d 100644 --- a/frontend/src/i18n/locales/hi/translation.json +++ b/frontend/src/i18n/locales/hi/translation.json @@ -415,10 +415,14 @@ "Current": "वर्तमान", "Desired//context:pods": "वांछित", "Addresses": "", + "Address Type": "", + "Hostname": "", + "Node Name": "", + "Zone": "", + "Endpoints": "", "Retry Budget": "", "Not specified": "", "Min Retry Rate": "", - "Hostname": "", "Port": "", "Protocol": "", "No addresses data to be shown.": "", @@ -521,14 +525,12 @@ "Load resources": "", "Upload files or load from URL": "", "Upload Files": "", - "Load from URL": "", "Drag & drop YAML files here or click to choose files": "", "Choose Files": "", "Supports .yaml and .yml files": "", "YAML URL": "", "Enter URL to YAML file": "", "Loading...": "", - "Load": "", "Load YAML resources from a remote URL": "", "Loaded Resources ({{count}})_one": "", "Loaded Resources ({{count}})_other": "", diff --git a/frontend/src/i18n/locales/it/glossary.json b/frontend/src/i18n/locales/it/glossary.json index d3561afb39e..839ae066f8f 100644 --- a/frontend/src/i18n/locales/it/glossary.json +++ b/frontend/src/i18n/locales/it/glossary.json @@ -62,6 +62,13 @@ "Port": "Porta", "Protocol": "Protocollo", "Endpoints": "Endpoint", + "Endpoint Slice": "", + "Addresses": "Indirizzi", + "Conditions": "", + "Ready": "", + "Serving": "", + "Terminating": "", + "Endpoint Slices": "", "Targets": "", "No targets defined": "", "TLS Validation": "", @@ -76,7 +83,6 @@ "Gateway Classes": "Classi Gateway", "Controller": "Controller", "Class Name": "Nome Classe", - "Addresses": "Indirizzi", "Listeners": "Listener", "No data": "Nessun dato", "Gateways": "Gateway", diff --git a/frontend/src/i18n/locales/it/translation.json b/frontend/src/i18n/locales/it/translation.json index cd0564e1ba8..889be581a6c 100644 --- a/frontend/src/i18n/locales/it/translation.json +++ b/frontend/src/i18n/locales/it/translation.json @@ -418,10 +418,14 @@ "Current": "Attuale", "Desired//context:pods": "Desiderato//context:pods", "Addresses": "Indirizzi", + "Address Type": "", + "Hostname": "Nome host", + "Node Name": "", + "Zone": "", + "Endpoints": "", "Retry Budget": "", "Not specified": "", "Min Retry Rate": "", - "Hostname": "Nome host", "Port": "Porta", "Protocol": "Protocollo", "No addresses data to be shown.": "Nessun dato sugli indirizzi da mostrare.", @@ -524,14 +528,12 @@ "Load resources": "", "Upload files or load from URL": "", "Upload Files": "", - "Load from URL": "", "Drag & drop YAML files here or click to choose files": "", "Choose Files": "", "Supports .yaml and .yml files": "", "YAML URL": "", "Enter URL to YAML file": "", "Loading...": "", - "Load": "", "Load YAML resources from a remote URL": "", "Loaded Resources ({{count}})_one": "", "Loaded Resources ({{count}})_many": "", diff --git a/frontend/src/i18n/locales/ja/glossary.json b/frontend/src/i18n/locales/ja/glossary.json index f4a1f988b73..e3e26ef271a 100644 --- a/frontend/src/i18n/locales/ja/glossary.json +++ b/frontend/src/i18n/locales/ja/glossary.json @@ -62,6 +62,13 @@ "Port": "ポート", "Protocol": "プロトコル", "Endpoints": "エンドポイント", + "Endpoint Slice": "", + "Addresses": "アドレス", + "Conditions": "", + "Ready": "", + "Serving": "", + "Terminating": "", + "Endpoint Slices": "", "Targets": "", "No targets defined": "", "TLS Validation": "", @@ -76,7 +83,6 @@ "Gateway Classes": "ゲートウェイクラス", "Controller": "コントローラー", "Class Name": "クラス名", - "Addresses": "アドレス", "Listeners": "リスナー", "No data": "データなし", "Gateways": "ゲートウェイ", diff --git a/frontend/src/i18n/locales/ja/translation.json b/frontend/src/i18n/locales/ja/translation.json index 02f9877d1eb..37048fdd2aa 100644 --- a/frontend/src/i18n/locales/ja/translation.json +++ b/frontend/src/i18n/locales/ja/translation.json @@ -412,10 +412,14 @@ "Current": "現在", "Desired//context:pods": "希望", "Addresses": "アドレス", + "Address Type": "", + "Hostname": "ホスト名", + "Node Name": "", + "Zone": "", + "Endpoints": "", "Retry Budget": "", "Not specified": "", "Min Retry Rate": "", - "Hostname": "ホスト名", "Port": "ポート", "Protocol": "プロトコル", "No addresses data to be shown.": "表示するアドレスデータがありません。", @@ -518,14 +522,12 @@ "Load resources": "", "Upload files or load from URL": "", "Upload Files": "", - "Load from URL": "", "Drag & drop YAML files here or click to choose files": "", "Choose Files": "", "Supports .yaml and .yml files": "", "YAML URL": "", "Enter URL to YAML file": "", "Loading...": "", - "Load": "", "Load YAML resources from a remote URL": "", "Loaded Resources ({{count}})_other": "", "Clear All": "", diff --git a/frontend/src/i18n/locales/ko/glossary.json b/frontend/src/i18n/locales/ko/glossary.json index 70452c7c7be..20f6003bf47 100644 --- a/frontend/src/i18n/locales/ko/glossary.json +++ b/frontend/src/i18n/locales/ko/glossary.json @@ -62,6 +62,13 @@ "Port": "포트", "Protocol": "프로토콜", "Endpoints": "엔드포인트", + "Endpoint Slice": "", + "Addresses": "주소", + "Conditions": "", + "Ready": "", + "Serving": "", + "Terminating": "", + "Endpoint Slices": "", "Targets": "", "No targets defined": "", "TLS Validation": "", @@ -76,7 +83,6 @@ "Gateway Classes": "게이트웨이 클래스", "Controller": "컨트롤러", "Class Name": "클래스 이름", - "Addresses": "주소", "Listeners": "리스너", "No data": "데이터 없음", "Gateways": "게이트웨이", diff --git a/frontend/src/i18n/locales/ko/translation.json b/frontend/src/i18n/locales/ko/translation.json index 3a03e087b55..f088f7e90aa 100644 --- a/frontend/src/i18n/locales/ko/translation.json +++ b/frontend/src/i18n/locales/ko/translation.json @@ -412,10 +412,14 @@ "Current": "Current", "Desired//context:pods": "Desired", "Addresses": "주소", + "Address Type": "", + "Hostname": "호스트 이름", + "Node Name": "", + "Zone": "", + "Endpoints": "", "Retry Budget": "", "Not specified": "", "Min Retry Rate": "", - "Hostname": "호스트 이름", "Port": "포트", "Protocol": "프로토콜", "No addresses data to be shown.": "표시할 주소 데이터가 없습니다.", @@ -518,14 +522,12 @@ "Load resources": "", "Upload files or load from URL": "", "Upload Files": "", - "Load from URL": "", "Drag & drop YAML files here or click to choose files": "", "Choose Files": "", "Supports .yaml and .yml files": "", "YAML URL": "", "Enter URL to YAML file": "", "Loading...": "", - "Load": "", "Load YAML resources from a remote URL": "", "Loaded Resources ({{count}})_other": "", "Clear All": "", diff --git a/frontend/src/i18n/locales/pt/glossary.json b/frontend/src/i18n/locales/pt/glossary.json index e84842c2ba6..bbccba21ab0 100644 --- a/frontend/src/i18n/locales/pt/glossary.json +++ b/frontend/src/i18n/locales/pt/glossary.json @@ -62,6 +62,13 @@ "Port": "Porta", "Protocol": "Protocolo", "Endpoints": "Endpoints", + "Endpoint Slice": "", + "Addresses": "Endereços", + "Conditions": "", + "Ready": "", + "Serving": "", + "Terminating": "", + "Endpoint Slices": "", "Targets": "", "No targets defined": "", "TLS Validation": "", @@ -76,7 +83,6 @@ "Gateway Classes": "Classes de Gateway", "Controller": "Controller", "Class Name": "Class Name", - "Addresses": "Endereços", "Listeners": "Listeners", "No data": "Sem dados", "Gateways": "Gateways", diff --git a/frontend/src/i18n/locales/pt/translation.json b/frontend/src/i18n/locales/pt/translation.json index 317f99e3632..159e97425f7 100644 --- a/frontend/src/i18n/locales/pt/translation.json +++ b/frontend/src/i18n/locales/pt/translation.json @@ -418,10 +418,14 @@ "Current": "Actual", "Desired//context:pods": "Desejados", "Addresses": "Endereços", + "Address Type": "", + "Hostname": "Nome do host", + "Node Name": "", + "Zone": "", + "Endpoints": "", "Retry Budget": "", "Not specified": "", "Min Retry Rate": "", - "Hostname": "Nome do host", "Port": "Porta", "Protocol": "Protocolo", "No addresses data to be shown.": "Sem dados de endereços a mostrar.", @@ -524,14 +528,12 @@ "Load resources": "", "Upload files or load from URL": "", "Upload Files": "", - "Load from URL": "", "Drag & drop YAML files here or click to choose files": "", "Choose Files": "", "Supports .yaml and .yml files": "", "YAML URL": "", "Enter URL to YAML file": "", "Loading...": "", - "Load": "", "Load YAML resources from a remote URL": "", "Loaded Resources ({{count}})_one": "", "Loaded Resources ({{count}})_many": "", diff --git a/frontend/src/i18n/locales/ta/glossary.json b/frontend/src/i18n/locales/ta/glossary.json index c41002e4256..e5372c9d6c8 100644 --- a/frontend/src/i18n/locales/ta/glossary.json +++ b/frontend/src/i18n/locales/ta/glossary.json @@ -62,6 +62,13 @@ "Port": "போர்ட்", "Protocol": "ப்ரோட்டோகால்", "Endpoints": "எண்ட்பாயிண்ட்ஸ்", + "Endpoint Slice": "", + "Addresses": "முகவரிகள்", + "Conditions": "", + "Ready": "", + "Serving": "", + "Terminating": "", + "Endpoint Slices": "", "Targets": "", "No targets defined": "", "TLS Validation": "", @@ -76,7 +83,6 @@ "Gateway Classes": "கேட்வே கிளாச்கள்", "Controller": "கன்ட்ரோலர்", "Class Name": "கிளாஸ் பெயர்", - "Addresses": "முகவரிகள்", "Listeners": "லிஸ்னர்கள்", "No data": "தரவு இல்லை", "Gateways": "கேட்வேக்கள்", diff --git a/frontend/src/i18n/locales/ta/translation.json b/frontend/src/i18n/locales/ta/translation.json index faf0c5939aa..7748198a079 100644 --- a/frontend/src/i18n/locales/ta/translation.json +++ b/frontend/src/i18n/locales/ta/translation.json @@ -415,10 +415,14 @@ "Current": "தற்போதைய", "Desired//context:pods": "விரும்பியவை", "Addresses": "முகவரிகள்", + "Address Type": "", + "Hostname": "ஹோஸ்ட்நேம்", + "Node Name": "", + "Zone": "", + "Endpoints": "", "Retry Budget": "", "Not specified": "", "Min Retry Rate": "", - "Hostname": "ஹோஸ்ட்நேம்", "Port": "போர்ட்", "Protocol": "ப்ரோட்டோகால்", "No addresses data to be shown.": "காட்ட முகவரி டேட்டா எதுவும் இல்லை.", @@ -521,14 +525,12 @@ "Load resources": "", "Upload files or load from URL": "", "Upload Files": "", - "Load from URL": "", "Drag & drop YAML files here or click to choose files": "", "Choose Files": "", "Supports .yaml and .yml files": "", "YAML URL": "", "Enter URL to YAML file": "", "Loading...": "", - "Load": "", "Load YAML resources from a remote URL": "", "Loaded Resources ({{count}})_one": "", "Loaded Resources ({{count}})_other": "", diff --git a/frontend/src/i18n/locales/zh-tw/glossary.json b/frontend/src/i18n/locales/zh-tw/glossary.json index b1806867a37..ce8ec71cea7 100644 --- a/frontend/src/i18n/locales/zh-tw/glossary.json +++ b/frontend/src/i18n/locales/zh-tw/glossary.json @@ -62,6 +62,13 @@ "Port": "埠", "Protocol": "協定", "Endpoints": "端點", + "Endpoint Slice": "", + "Addresses": "地址", + "Conditions": "", + "Ready": "", + "Serving": "", + "Terminating": "", + "Endpoint Slices": "", "Targets": "", "No targets defined": "", "TLS Validation": "", @@ -76,7 +83,6 @@ "Gateway Classes": "閘道類別", "Controller": "控制器", "Class Name": "類別名稱", - "Addresses": "地址", "Listeners": "監聽器", "No data": "無資料", "Gateways": "閘道", diff --git a/frontend/src/i18n/locales/zh-tw/translation.json b/frontend/src/i18n/locales/zh-tw/translation.json index 468f527c910..97c035d1b63 100644 --- a/frontend/src/i18n/locales/zh-tw/translation.json +++ b/frontend/src/i18n/locales/zh-tw/translation.json @@ -412,10 +412,14 @@ "Current": "當前", "Desired//context:pods": "期望", "Addresses": "地址", + "Address Type": "", + "Hostname": "主機名稱", + "Node Name": "", + "Zone": "", + "Endpoints": "", "Retry Budget": "", "Not specified": "", "Min Retry Rate": "", - "Hostname": "主機名稱", "Port": "連線埠", "Protocol": "協定", "No addresses data to be shown.": "無地址資料顯示。", @@ -518,14 +522,12 @@ "Load resources": "", "Upload files or load from URL": "", "Upload Files": "", - "Load from URL": "", "Drag & drop YAML files here or click to choose files": "", "Choose Files": "", "Supports .yaml and .yml files": "", "YAML URL": "", "Enter URL to YAML file": "", "Loading...": "", - "Load": "", "Load YAML resources from a remote URL": "", "Loaded Resources ({{count}})_other": "", "Clear All": "", diff --git a/frontend/src/i18n/locales/zh/glossary.json b/frontend/src/i18n/locales/zh/glossary.json index f9cc06749d8..4f344773153 100644 --- a/frontend/src/i18n/locales/zh/glossary.json +++ b/frontend/src/i18n/locales/zh/glossary.json @@ -62,6 +62,13 @@ "Port": "端口", "Protocol": "协议", "Endpoints": "端点", + "Endpoint Slice": "", + "Addresses": "地址", + "Conditions": "", + "Ready": "", + "Serving": "", + "Terminating": "", + "Endpoint Slices": "", "Targets": "", "No targets defined": "", "TLS Validation": "", @@ -76,7 +83,6 @@ "Gateway Classes": "网关类别", "Controller": "控制器", "Class Name": "类别名称", - "Addresses": "地址", "Listeners": "监听器", "No data": "暂无数据", "Gateways": "网关", diff --git a/frontend/src/i18n/locales/zh/translation.json b/frontend/src/i18n/locales/zh/translation.json index 39e4f905dd5..c717abe7f18 100644 --- a/frontend/src/i18n/locales/zh/translation.json +++ b/frontend/src/i18n/locales/zh/translation.json @@ -412,10 +412,14 @@ "Current": "当前", "Desired//context:pods": "期望", "Addresses": "地址", + "Address Type": "", + "Hostname": "主机名称", + "Node Name": "", + "Zone": "", + "Endpoints": "", "Retry Budget": "", "Not specified": "", "Min Retry Rate": "", - "Hostname": "主机名称", "Port": "端口", "Protocol": "协议", "No addresses data to be shown.": "无地址信息显示。", @@ -518,14 +522,12 @@ "Load resources": "", "Upload files or load from URL": "", "Upload Files": "", - "Load from URL": "", "Drag & drop YAML files here or click to choose files": "", "Choose Files": "", "Supports .yaml and .yml files": "", "YAML URL": "", "Enter URL to YAML file": "", "Loading...": "", - "Load": "", "Load YAML resources from a remote URL": "", "Loaded Resources ({{count}})_other": "", "Clear All": "", diff --git a/frontend/src/lib/k8s/ResourceCategory.tsx b/frontend/src/lib/k8s/ResourceCategory.tsx index 4bb05e360b9..c1de007250d 100644 --- a/frontend/src/lib/k8s/ResourceCategory.tsx +++ b/frontend/src/lib/k8s/ResourceCategory.tsx @@ -59,7 +59,7 @@ export const categoriesConfig: ResourceCategory[] = [ icon: 'mdi:folder-network-outline', description: 'Network connectivity and exposure', apiGroups: ['networking.k8s.io'], - coreKinds: ['Service', 'Endpoints'], + coreKinds: ['Service', 'Endpoints', 'EndpointSlice'], }, { label: 'Security', diff --git a/frontend/src/lib/k8s/endpointSlices.ts b/frontend/src/lib/k8s/endpointSlices.ts new file mode 100644 index 00000000000..dfc68cac4e8 --- /dev/null +++ b/frontend/src/lib/k8s/endpointSlices.ts @@ -0,0 +1,74 @@ +/* + * Copyright 2025 The Kubernetes Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { KubeMetadata } from './KubeMetadata'; +import { KubeObject, KubeObjectInterface } from './KubeObject'; + +export interface KubeEndpointSliceEndpointConditions { + ready: boolean; + serving: boolean; + terminating: boolean; +} + +export interface KubeEndpointSliceEndpoint { + addresses: string[]; + hostname: string; + nodeName?: string; + conditions?: KubeEndpointSliceEndpointConditions; + zone?: string; + targetRef?: Pick & + Pick & { + fieldPath: string; + }; +} + +export interface KubeEndpointSlicePort { + name?: string; + port: number; + protocol: string; + appProtocol?: string; +} + +export interface KubeEndpointSlice extends KubeObjectInterface { + addressType?: string; + ports?: KubeEndpointSlicePort[]; + endpoints?: KubeEndpointSliceEndpoint[]; +} + +class EndpointSlice extends KubeObject { + static kind = 'EndpointSlice'; + static apiName = 'endpointslices'; + static apiVersion = 'discovery.k8s.io/v1'; + static isNamespaced = true; + + static getBaseObject(): KubeEndpointSlice { + const baseObject = super.getBaseObject() as KubeEndpointSlice; + return baseObject; + } + get spec() { + return this.jsonData; + } + + get ports() { + return this.jsonData?.ports?.map(port => port.port) ?? []; + } + + getOwnerServiceName() { + return this.jsonData.metadata.ownerReferences?.find(t => t.kind === 'Service')?.name; + } +} + +export default EndpointSlice; diff --git a/frontend/src/lib/k8s/index.test.ts b/frontend/src/lib/k8s/index.test.ts index 108f87ce2f7..386fbee2cf6 100644 --- a/frontend/src/lib/k8s/index.test.ts +++ b/frontend/src/lib/k8s/index.test.ts @@ -243,6 +243,7 @@ const namespacedClasses = [ 'Deployment', 'Endpoint', 'Endpoints', + 'EndpointSlice', 'GRPCRoute', 'Gateway', 'HorizontalPodAutoscaler', diff --git a/frontend/src/lib/k8s/index.ts b/frontend/src/lib/k8s/index.ts index 99516de8bd2..d93d611c0ac 100644 --- a/frontend/src/lib/k8s/index.ts +++ b/frontend/src/lib/k8s/index.ts @@ -31,6 +31,7 @@ import CronJob from './cronJob'; import DaemonSet from './daemonSet'; import Deployment from './deployment'; import Endpoints from './endpoints'; +import EndpointSlice from './endpointSlices'; import Gateway from './gateway'; import GatewayClass from './gatewayClass'; import GRPCRoute from './grpcRoute'; @@ -71,6 +72,7 @@ export const ResourceClasses = { Deployment, Endpoint: Endpoints, Endpoints, + EndpointSlice, LimitRange, Lease, ResourceQuota, diff --git a/frontend/src/lib/router/index.tsx b/frontend/src/lib/router/index.tsx index 3c53d266462..14b57875d12 100644 --- a/frontend/src/lib/router/index.tsx +++ b/frontend/src/lib/router/index.tsx @@ -43,6 +43,8 @@ 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 EndpointSliceDetails from '../../components/endpointSlices/Details'; +import EndpointSliceList from '../../components/endpointSlices/List'; import BackendTLSPolicyDetails from '../../components/gateway/BackendTLSPolicyDetails'; import BackendTLSPolicyList from '../../components/gateway/BackendTLSPolicyList'; import BackendTrafficPolicyDetails from '../../components/gateway/BackendTrafficPolicyDetails'; @@ -337,6 +339,19 @@ const defaultRoutes: { [routeName: string]: Route } = { sidebar: 'endpoints', component: () => , }, + endpointslices: { + path: '/endpointslices', + exact: true, + name: 'EndpointSlices', + sidebar: 'endpointslices', + component: () => , + }, + endpointslice: { + path: '/endpointslices/:namespace/:name', + exact: true, + sidebar: 'endpointslices', + component: () => , + }, ingresses: { path: '/ingresses', exact: true, diff --git a/frontend/src/plugin/__snapshots__/pluginLib.snapshot b/frontend/src/plugin/__snapshots__/pluginLib.snapshot index 6509fac4b62..70bd9a90313 100644 --- a/frontend/src/plugin/__snapshots__/pluginLib.snapshot +++ b/frontend/src/plugin/__snapshots__/pluginLib.snapshot @@ -264,6 +264,7 @@ "DaemonSet": [Function], "Deployment": [Function], "Endpoint": [Function], + "EndpointSlice": [Function], "Endpoints": [Function], "GRPCRoute": [Function], "Gateway": [Function],