From ba49488b944e6339fe260d8bab4318836fd2077d Mon Sep 17 00:00:00 2001 From: Oleksandr Dubenko Date: Thu, 9 May 2024 16:54:05 +0200 Subject: [PATCH] frontend: Use new Table component in the ResourceTable Replaces SimpleTable with the new Table component. Out of the box ResourceTable will enable sorting and filtering on all columns. To provide that functionality 'getter' in the column definition has to provide a plaintext value (but will keep working if it returns jsx for compatibility reasons). e2e and snapshot tests were also updated Signed-off-by: Oleksandr Dubenko --- .../api/modules/plugin_registry.md | 2 +- e2e-tests/tests/headlampPage.ts | 8 +- .../__snapshots__/index.stories.storyshot | 933 ++++- frontend/src/components/App/Home/index.tsx | 26 +- frontend/src/components/cluster/Overview.tsx | 42 +- .../__snapshots__/Overview.stories.storyshot | 1215 ++++-- .../Resource/ResourceListView.stories.tsx | 2 +- .../common/Resource/ResourceListView.tsx | 46 +- .../common/Resource/ResourceTable.stories.tsx | 13 +- .../common/Resource/ResourceTable.tsx | 384 +- .../Resource/ResourceTableColumnChooser.tsx | 8 +- .../ResourceListView.stories.storyshot | 893 ++-- .../ResourceTable.stories.storyshot | 2077 ++++++++-- .../common/Resource/resourceTableSlice.ts | 6 +- .../common/Tooltip/TooltipLight.tsx | 14 +- frontend/src/components/configmap/List.tsx | 4 +- .../__snapshots__/List.stories.storyshot | 800 +++- .../src/components/crd/CustomResourceList.tsx | 16 +- frontend/src/components/crd/List.tsx | 20 +- ...CustomResourceDefinition.stories.storyshot | 1609 ++++++-- .../CustomResourceList.stories.storyshot | 793 +++- frontend/src/components/cronjob/List.tsx | 33 +- .../CronJobDetails.stories.storyshot | 96 +- frontend/src/components/daemonset/List.tsx | 33 +- .../__snapshots__/List.stories.storyshot | 1341 ++++-- frontend/src/components/deployments/List.tsx | 28 +- frontend/src/components/endpoints/List.tsx | 6 +- .../EndpointList.stories.storyshot | 1079 +++-- .../horizontalPodAutoscaler/List.tsx | 25 +- .../__snapshots__/HPAList.stories.storyshot | 1834 +++++--- frontend/src/components/ingress/ClassList.tsx | 8 +- frontend/src/components/ingress/List.tsx | 14 +- .../__snapshots__/ClassList.stories.storyshot | 757 +++- .../__snapshots__/List.stories.storyshot | 1040 ++++- frontend/src/components/job/List.tsx | 25 +- .../__snapshots__/JobList.stories.storyshot | 1499 +++++-- frontend/src/components/lease/List.tsx | 2 +- .../__snapshots__/List.stories.storyshot | 715 +++- .../__snapshots__/List.stories.storyshot | 621 ++- frontend/src/components/namespace/List.tsx | 80 +- .../NamespaceDetails.stories.storyshot | 3677 ++++++++++++----- .../NamespaceList.stories.storyshot | 889 ++-- .../src/components/networkpolicy/List.tsx | 12 +- frontend/src/components/node/List.tsx | 40 +- .../node/__snapshots__/List.stories.storyshot | 1456 +++++-- frontend/src/components/node/utils.tsx | 2 +- frontend/src/components/pod/List.tsx | 45 +- .../__snapshots__/PodList.stories.storyshot | 2263 ++++++---- .../components/podDisruptionBudget/List.tsx | 7 +- .../__snapshots__/pdbList.stories.storyshot | 1334 ++++-- .../src/components/priorityClass/List.tsx | 6 +- .../priorityClassList.stories.storyshot | 998 +++-- frontend/src/components/replicaset/List.tsx | 55 +- .../__snapshots__/List.stories.storyshot | 1444 +++++-- .../src/components/resourceQuota/List.tsx | 6 +- .../resourceQuotaList.stories.storyshot | 1339 ++++-- frontend/src/components/role/BindingList.tsx | 73 +- frontend/src/components/role/List.tsx | 13 +- frontend/src/components/runtimeClass/List.tsx | 2 +- .../__snapshots__/List.stories.storyshot | 618 ++- frontend/src/components/secret/List.tsx | 8 +- .../__snapshots__/List.stories.storyshot | 916 +++- frontend/src/components/service/List.tsx | 17 +- .../src/components/serviceaccount/List.tsx | 4 +- frontend/src/components/statefulset/List.tsx | 18 +- frontend/src/components/storage/ClaimList.tsx | 24 +- frontend/src/components/storage/ClassList.tsx | 12 +- .../src/components/storage/VolumeList.tsx | 27 +- .../__snapshots__/ClaimList.stories.storyshot | 1588 +++++-- .../__snapshots__/ClassList.stories.storyshot | 918 +++- .../VolumeList.stories.storyshot | 918 +++- .../components/verticalPodAutoscaler/List.tsx | 7 +- .../__snapshots__/VPAList.stories.storyshot | 1334 ++++-- .../MutatingWebhookConfigList.tsx | 2 +- .../ValidatingWebhookConfigList.tsx | 2 +- ...utatingWebhookConfigList.stories.storyshot | 919 ++-- ...idatingWebhookConfigList.stories.storyshot | 919 ++-- frontend/src/components/workload/Overview.tsx | 17 +- frontend/src/i18n/locales/de/glossary.json | 2 +- frontend/src/i18n/locales/de/translation.json | 1 - frontend/src/i18n/locales/en/glossary.json | 2 +- frontend/src/i18n/locales/en/translation.json | 1 - frontend/src/i18n/locales/es/glossary.json | 2 +- frontend/src/i18n/locales/es/translation.json | 1 - frontend/src/i18n/locales/fr/glossary.json | 2 +- frontend/src/i18n/locales/fr/translation.json | 1 - frontend/src/i18n/locales/pt/glossary.json | 2 +- frontend/src/i18n/locales/pt/translation.json | 1 - frontend/src/plugin/registry.tsx | 7 +- plugins/examples/tables/src/index.tsx | 3 +- 90 files changed, 30209 insertions(+), 9892 deletions(-) diff --git a/docs/development/api/modules/plugin_registry.md b/docs/development/api/modules/plugin_registry.md index 904151614df..a988a235523 100644 --- a/docs/development/api/modules/plugin_registry.md +++ b/docs/development/api/modules/plugin_registry.md @@ -639,7 +639,7 @@ registerResourceTableColumnsProcessor(function ageRemover({ id, columns }) { if (id === 'headlamp-pods') { columns.push({ label: 'Init Containers', - getter: (pod: Pod) => { + getValue: (pod: Pod) => { return pod.spec.initContainers.length; }, }); diff --git a/e2e-tests/tests/headlampPage.ts b/e2e-tests/tests/headlampPage.ts index 76e729e7857..4477d1657d3 100644 --- a/e2e-tests/tests/headlampPage.ts +++ b/e2e-tests/tests/headlampPage.ts @@ -1,3 +1,4 @@ +/// import { expect, Page } from '@playwright/test'; export class HeadlampPage { @@ -5,7 +6,6 @@ export class HeadlampPage { async authenticate() { await this.page.goto('/'); - await this.page.waitForSelector('h1:has-text("Authentication")'); // Expects the URL to contain c/main/token @@ -104,7 +104,9 @@ export class HeadlampPage { const rowsDisplayed1 = await this.getRowsDisplayed(); // Click on the next page button - const nextPageButton = this.page.getByTitle('Next page'); + const nextPageButton = this.page.getByRole('button', { + name: 'Go to next page', + }); await nextPageButton.click(); // Get value of rows per page after clicking next page button @@ -115,7 +117,7 @@ export class HeadlampPage { } async getRowsDisplayed() { - const paginationCaption = this.page.locator("p:has-text(' of ')"); + const paginationCaption = this.page.locator("span:has-text(' of ')"); const captionText = await paginationCaption.textContent(); return captionText; } diff --git a/frontend/src/components/App/Home/__snapshots__/index.stories.storyshot b/frontend/src/components/App/Home/__snapshots__/index.stories.storyshot index 07cc307af57..1b6d1f6dc9f 100644 --- a/frontend/src/components/App/Home/__snapshots__/index.stories.storyshot +++ b/frontend/src/components/App/Home/__snapshots__/index.stories.storyshot @@ -186,132 +186,584 @@ exports[`Storyshots Home/Home Base 1`] = ` class="MuiBox-root css-1txv3mw" >
- - +
+
+ +
+
+ + - - - - - - - - + + + +
+
+ +
+ +
+
- Name -
- Status - - Warnings - - Kubernetes Version - - -
- - - - - + + + + + + - + + + - - - + - - cluster1 - - - + - - - + + - - - + - - cluster2 - - - + - - - + + + + + +
- - cluster0 - - -
+ +
+ +
+
+ +
- -

+

+ Status +
+ + + + + + 0 + + +
+
- ⋯ -

+ +
- - -
- ⋯ - - ⋯ - + +
+ +
+ +
+
+
+
+ +
+ +
+
+
+
+ +
+ +
+
+
+
+
+ +

+ ⋯ +

+
+
+
+ ⋯ + + ⋯ +
-
-
+
-
+ cluster1 + +
- -

- ⋯ -

+ +

+ ⋯ +

+
- -
- ⋯ - - ⋯ - -
+
+ ⋯ + + ⋯ +
-
-
+
-
+ cluster2 + +
- -

- ⋯ -

+ +

+ ⋯ +

+
- -
- ⋯ - - ⋯ - -
+
+ ⋯ + + ⋯ +
+
+
+
+
+ +
+
+
+ +
+ + + +
- - - - + + 1-3 of 3 + +
+ + + + + + +
+
+
+
+
diff --git a/frontend/src/components/App/Home/index.tsx b/frontend/src/components/App/Home/index.tsx index 0419d53e9bd..4b4d5928dc7 100644 --- a/frontend/src/components/App/Home/index.tsx +++ b/frontend/src/components/App/Home/index.tsx @@ -212,38 +212,38 @@ function HomeComponent(props: HomeComponentProps) { > ( + getValue: cluster => cluster.name, + render: ({ name }) => ( {name} ), - sort: (c1: Cluster, c2: Cluster) => c1.name.localeCompare(c2.name), }, { label: t('Status'), - getter: ({ name }: Cluster) => , + getValue: cluster => cluster.name, + render: ({ name }) => , }, { label: t('Warnings'), - getter: ({ name }: Cluster) => renderWarningsText(name), - sort: true, + getValue: ({ name }) => renderWarningsText(name), }, { label: t('glossary|Kubernetes Version'), - getter: ({ name }: Cluster) => versions[name]?.gitVersion || '⋯', - sort: true, + getValue: ({ name }) => versions[name]?.gitVersion || '⋯', }, { label: '', - getter: (cluster: Cluster) => ( - - - - ), + getValue: () => '', + cellProps: { + align: 'right', + }, + render: cluster => , }, ]} data={Object.values(clusters)} diff --git a/frontend/src/components/cluster/Overview.tsx b/frontend/src/components/cluster/Overview.tsx index b3ede30f547..8bfdf2a6d4b 100644 --- a/frontend/src/components/cluster/Overview.tsx +++ b/frontend/src/components/cluster/Overview.tsx @@ -4,7 +4,7 @@ import React from 'react'; import { useTranslation } from 'react-i18next'; import { useDispatch } from 'react-redux'; import { useLocation } from 'react-router'; -import Event, { KubeEvent } from '../../lib/k8s/event'; +import Event from '../../lib/k8s/event'; import Node from '../../lib/k8s/node'; import Pod from '../../lib/k8s/pod'; import { useFilterFunc } from '../../lib/util'; @@ -68,7 +68,7 @@ function EventsSection() { const queryParams = new URLSearchParams(location.search); const eventsFilter = queryParams.get('eventsFilter'); const dispatch = useDispatch(); - const filterFunc = useFilterFunc(['.jsonData.involvedObject.kind']); + const filterFunc = useFilterFunc(['.jsonData.involvedObject.kind']); const [isWarningEventSwitchChecked, setIsWarningEventSwitchChecked] = React.useState( Boolean( JSON.parse( @@ -79,7 +79,7 @@ function EventsSection() { ); const [events, eventsError] = Event.useList({ limit: Event.maxLimit }); - const warningActionFilterFunc = (event: KubeEvent) => { + const warningActionFilterFunc = (event: Event) => { if (!filterFunc(event)) { return false; } @@ -112,7 +112,6 @@ function EventsSection() { status={event.type === 'Normal' ? '' : 'warning'} sx={theme => ({ [theme.breakpoints.up('md')]: { - minWidth: '180px', display: 'unset', }, })} @@ -152,51 +151,40 @@ function EventsSection() { columns={[ { label: t('Type'), - getter: event => event.involvedObject.kind, - sort: true, + getValue: event => event.involvedObject.kind, }, { label: t('Name'), - getter: event => makeObjectLink(event), - cellProps: { - scope: 'row', - component: 'th', - }, - gridTemplate: 1.5, - sort: true, + getValue: event => event.involvedObjectInstance?.getName() ?? event.involvedObject.name, + render: event => makeObjectLink(event), }, 'namespace', { label: t('Reason'), - getter: event => ( + getValue: event => event.reason, + render: event => ( {makeStatusLabel(event)} ), - sort: (e1: Event, e2: Event) => e1.reason.localeCompare(e2.reason), }, { label: t('Message'), - getter: event => ( + getValue: event => event.message ?? '', + render: event => ( {event.message || ''} ), - sort: true, - gridTemplate: 1.5, }, { + id: 'last-seen', label: t('Last Seen'), - getter: event => , - cellProps: { style: { textAlign: 'right' } }, - gridTemplate: 'minmax(150px, 0.5fr)', - sort: (e1: Event, e2: Event) => { - const date1 = e1.lastTimestamp || e1.metadata.creationTimestamp; - const date2 = e2.lastTimestamp || e2.metadata.creationTimestamp; - return new Date(date2).getTime() - new Date(date1).getTime(); - }, + getValue: event => -new Date(event.lastOccurrence).getTime(), + render: event => , + cellProps: { align: 'right' }, }, ]} filterFunction={warningActionFilterFunc} - defaultSortingColumn={6} + defaultSortingColumn={{ id: 'last-seen', desc: false }} id="headlamp-cluster.overview.events" /> ); diff --git a/frontend/src/components/cluster/__snapshots__/Overview.stories.storyshot b/frontend/src/components/cluster/__snapshots__/Overview.stories.storyshot index 494c8d98bbd..f1da93a53a0 100644 --- a/frontend/src/components/cluster/__snapshots__/Overview.stories.storyshot +++ b/frontend/src/components/cluster/__snapshots__/Overview.stories.storyshot @@ -521,45 +521,17 @@ exports[`Storyshots cluster/Overview Events 1`] = `
-
-
-
- -
-
- -
-
-
+ + +
@@ -568,334 +540,959 @@ exports[`Storyshots cluster/Overview Events 1`] = ` class="MuiBox-root css-1txv3mw" >
- - - - - - - - - - - - + + + +
+
- Type - - - Name - - - Namespace - - +
+
+ +
+
+ + + + + + +
+ +
+
- Reason -
- Message - - Last Seen -
- - - - - - - + +
+ +
+ + + + + + + + - - - + + + + + + + - - default - - - + + + + + + + - - FailedGetResourceMetric - - - + + + + + + + +
- Pod - - - nginx-deployment-1234567890-abcde - - - - default - - - - FailedGetResourceMetric - - -
+ +
+ +
+
+ +
- - - - -

+

+ Name +
+ + + + + + 0 + + + +
+ +
+ + +
- 3mo - -

- -
+
+ +
+ +
+
+
+
+ +
+ +
+
+
+
+ +
+ +
+
+
- Pod - - - nginx-deployment-abcd-1234567890 - - + Pod + + + nginx-deployment-abcd-1234567890 + + + + default + + + + FailedGetResourceMetric + + +
+ +
+
+

+ 3mo + +

+
+ Pod + + + nginx-deployment-1234567890-abcde + + + + default + + + + FailedGetResourceMetric + + +
+ +
+
+

+ 3mo + +

+
+ + + nginx-deployment + + + + default + + + + FailedGetResourceMetric + + +
+ +
+
+

+ 3mo + +

+
+
+
+
+ +
+
+
+ + + +
- - -

- 3mo - -

- - - - - HorizontalPodAutoscaler - - - - nginx-deployment - - - - - default - - - - FailedGetResourceMetric + 1-3 of 3 - -
- + + + + +
- - -

- 3mo - -

- - - - +
+
+
+
diff --git a/frontend/src/components/common/Resource/ResourceListView.stories.tsx b/frontend/src/components/common/Resource/ResourceListView.stories.tsx index 7d0e36fd184..2cf893e5f7f 100644 --- a/frontend/src/components/common/Resource/ResourceListView.stories.tsx +++ b/frontend/src/components/common/Resource/ResourceListView.stories.tsx @@ -32,7 +32,7 @@ const Template: Story = () => { 'namespace', { label: 'Num Containers', - getter: item => item?.spec.containers.length, + getValue: item => item?.spec.containers.length, show: false, }, 'age', diff --git a/frontend/src/components/common/Resource/ResourceListView.tsx b/frontend/src/components/common/Resource/ResourceListView.tsx index f80470f5445..08cc7f5c2e3 100644 --- a/frontend/src/components/common/Resource/ResourceListView.tsx +++ b/frontend/src/components/common/Resource/ResourceListView.tsx @@ -1,33 +1,28 @@ import React, { PropsWithChildren } from 'react'; -import { useTranslation } from 'react-i18next'; -import { ActionButton } from '..'; +import { KubeObject } from '../../../lib/k8s/cluster'; import SectionBox from '../SectionBox'; import SectionFilterHeader, { SectionFilterHeaderProps } from '../SectionFilterHeader'; -import ResourceTable, { - ResourceTableFromResourceClassProps, - ResourceTableProps, -} from './ResourceTable'; +import ResourceTable, { ResourceTableProps } from './ResourceTable'; -export interface ResourceListViewProps extends PropsWithChildren { +export interface ResourceListViewProps + extends PropsWithChildren> { title: string | JSX.Element; headerProps?: Omit; } -export interface ResourceListViewWithResourceClassProps - extends Omit { - resourceClass: ResourceTableFromResourceClassProps['resourceClass']; +type Class = new (...args: any[]) => T; + +export interface ResourceListViewWithResourceClassProps + extends Omit, 'data'> { + resourceClass: Class; } -export default function ResourceListView( - props: ResourceListViewProps | ResourceListViewWithResourceClassProps +export default function ResourceListView( + props: ResourceListViewProps | ResourceListViewWithResourceClassProps ) { const { title, children, headerProps, ...tableProps } = props; - const { t } = useTranslation(); - const [columnChooserAnchorEl, setColumnChooserAnchorEl] = React.useState( - null - ); - const withNamespaceFilter = (props as ResourceListViewWithResourceClassProps).resourceClass - ?.isNamespaced; + const withNamespaceFilter = + 'resourceClass' in props && (props.resourceClass as KubeObject)?.isNamespaced; return ( { - setColumnChooserAnchorEl(() => event.currentTarget); - }} - />, - ]} noNamespaceFilter={!withNamespaceFilter} {...headerProps} /> @@ -52,11 +38,7 @@ export default function ResourceListView( ) } > - setColumnChooserAnchorEl(null)} - {...tableProps} - /> + {children} ); diff --git a/frontend/src/components/common/Resource/ResourceTable.stories.tsx b/frontend/src/components/common/Resource/ResourceTable.stories.tsx index 219a571c8b3..763aca82d73 100644 --- a/frontend/src/components/common/Resource/ResourceTable.stories.tsx +++ b/frontend/src/components/common/Resource/ResourceTable.stories.tsx @@ -1,6 +1,5 @@ import { configureStore } from '@reduxjs/toolkit'; import { Meta, Story } from '@storybook/react'; -import { KubeObject } from '../../../lib/k8s/cluster'; import Pod, { KubePod } from '../../../lib/k8s/pod'; import { INITIAL_STATE as UI_INITIAL_STATE } from '../../../redux/reducers/ui'; import { TestContext } from '../../../test'; @@ -13,7 +12,7 @@ export default { } as Meta; const TemplateWithFilter: Story<{ - resourceTableArgs: ResourceTableFromResourceClassProps; + resourceTableArgs: ResourceTableFromResourceClassProps; namespaces: string[]; search: string; }> = args => { @@ -119,23 +118,23 @@ class MyPod extends Pod { ] as any; } -const podData: ResourceTableFromResourceClassProps = { +const podData: ResourceTableFromResourceClassProps = { columns: ['name', 'namespace', 'age'], - resourceClass: MyPod as KubeObject, + resourceClass: MyPod, }; -const withHiddenCols: ResourceTableFromResourceClassProps = { +const withHiddenCols: ResourceTableFromResourceClassProps = { columns: [ 'name', 'namespace', { label: 'UID', - getter: (pod: Pod) => pod.metadata.uid, + getValue: pod => pod.metadata.uid, show: false, }, 'age', ], - resourceClass: MyPod as KubeObject, + resourceClass: MyPod, hideColumns: ['namespace'], }; diff --git a/frontend/src/components/common/Resource/ResourceTable.tsx b/frontend/src/components/common/Resource/ResourceTable.tsx index 8b5f143a6b5..080d1ca9dc4 100644 --- a/frontend/src/components/common/Resource/ResourceTable.tsx +++ b/frontend/src/components/common/Resource/ResourceTable.tsx @@ -1,5 +1,7 @@ +import { TableCellProps } from '@mui/material'; import { useTheme } from '@mui/material/styles'; -import { useEffect, useMemo, useRef, useState } from 'react'; +import { MRT_Row, MRT_SortingFn } from 'material-react-table'; +import { ComponentProps, ReactNode, useEffect, useMemo, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import helpers from '../../../helpers'; import { KubeObject } from '../../../lib/k8s/cluster'; @@ -9,28 +11,56 @@ import { useTypedSelector } from '../../../redux/reducers/reducers'; import { useSettings } from '../../App/Settings/hook'; import { DateLabel } from '../Label'; import Link from '../Link'; -import SimpleTable, { - SimpleTableDatumColumn, - SimpleTableGetterColumn, - SimpleTableProps, -} from '../SimpleTable'; -import TableColumnChooserPopup from './ResourceTableColumnChooser'; - -interface ToggableColumn { +import Table, { TableColumn } from '../Table'; + +export type ResourceTableColumn = { + /** Unique id for the column, not required but recommended */ id?: string; + /** Show or hide the column @default true */ show?: boolean; -} - -export type ResourceTableColumn = - | (ToggableColumn & SimpleTableDatumColumn) - | (ToggableColumn & SimpleTableGetterColumn); + /** Label of the column that will be shown in the header */ + label: string; + cellProps?: TableCellProps & { + [propName: string]: any; + }; + /** + * By default the column will be sorted by the value provided in the getter + * but you can provide your own sorting function here + * + * @default true + **/ + sort?: boolean | ((a: RowItem, b: RowItem) => number); + /** Change how filtering behaves, by default it will just search the text value */ + filterVariant?: TableColumn['filterVariant']; + disableFiltering?: boolean; +} & ( + | { + /** To render a simple value provide property name of the item */ + datum: keyof RowItem; + render?: never; + getValue?: never; + } + | { + datum?: never; + /** How to render a cell */ + render?: (item: RowItem) => ReactNode; + /** Plain value for filtering and sorting. This is going to be displayed unless render property is defined */ + getValue: (item: RowItem) => string | number | null | undefined; + } + | { + datum?: never; + render?: never; + /** @deprecated please use getValue and render (optional, if you need custom renderer) */ + getter: (item: RowItem) => any; + } +); type ColumnType = 'age' | 'name' | 'namespace' | 'type' | 'kind'; -export interface ResourceTableProps extends Omit { +export interface ResourceTableProps { /** The columns to be rendered, like used in SimpleTable, or by name. */ - columns: (ResourceTableColumn | ColumnType)[]; - /** Allow to prefilter columns */ + columns: (ResourceTableColumn | ColumnType)[]; + /** Provide a list of columns that won't be shown and cannot be turned on */ hideColumns?: string[] | null; /** ID for the table. Will be used by plugins to identify this table. * Official tables in Headlamp will have the 'headlamp-' prefix for their IDs which is followed by the resource's plural name or the section in Headlamp the table is in. @@ -39,25 +69,88 @@ export interface ResourceTableProps extends Omit { id?: string; /** Deny plugins to process this table's columns. */ noProcessing?: boolean; - /** The anchor element for the column chooser popup. */ - columnChooserAnchor?: HTMLElement | null; /** The callback for when the columns chooser is closed. */ onColumnChooserClose?: () => void; + /** Column to be sorted */ + defaultSortingColumn?: { id: string; desc: boolean }; + data: Array | null; + filterFunction?: (item: RowItem) => boolean; + errorMessage?: string | null; + reflectInURL?: string | boolean; } -export interface ResourceTableFromResourceClassProps extends Omit { +export interface ResourceTableFromResourceClassProps + extends Omit, 'data'> { resourceClass: KubeObject; } -export default function ResourceTable( - props: ResourceTableFromResourceClassProps | ResourceTableProps +export default function ResourceTable( + props: ResourceTableFromResourceClassProps | ResourceTableProps ) { - if (!!(props as ResourceTableFromResourceClassProps).resourceClass) { - const { resourceClass, ...otherProps } = props as ResourceTableFromResourceClassProps; + if (!!(props as ResourceTableFromResourceClassProps).resourceClass) { + const { resourceClass, ...otherProps } = props as ResourceTableFromResourceClassProps; return ; } - return ; + return )} />; +} + +function TableFromResourceClass(props: ResourceTableFromResourceClassProps) { + const { resourceClass, id, ...otherProps } = props; + const [items, error] = resourceClass.useList(); + // throttle the update of the table to once per second + const throttledItems = useThrottle(items, 1000); + const dispatchHeadlampEvent = useEventCallback(HeadlampEventType.LIST_VIEW); + + useEffect(() => { + dispatchHeadlampEvent({ + resources: items, + resourceKind: resourceClass.className, + error: error || undefined, + }); + }, [items, error]); + + return ( + + ); +} + +/** + * Here we figure out which columns are visible and not visible + * We can control it using show property in the columns prop {@link ResourceTableColumn} + * And when user manually changes visibility it is saved to localStorage + */ +function initColumnVisibilityState(columns: ResourceTableProps['columns'], tableId?: string) { + const visibility: Record = {}; + + // Apply default visibility we got from the props + columns.forEach((col, index) => { + if (typeof col === 'string') return; + + if ('show' in col) { + visibility[col.id ?? String(index)] = col.show ?? true; + } + }); + + // Load and apply persisted settings from local storage + if (tableId) { + const localTableSettins = helpers.loadTableSettings(tableId); + localTableSettins.forEach(({ id, show }) => (visibility[id] = show)); + } + + return visibility; +} + +// By default MRT passes row object to the sorting function but we only need the original item +function sortingFn(sortFn?: (a: any, b: any) => number): MRT_SortingFn | undefined { + if (!sortFn) return undefined; + + return (a: any, b: any) => sortFn(a.original, b.original); } /** @@ -99,55 +192,33 @@ export function useThrottle(value: any, interval = 1000): any { return throttledValue; } -function TableFromResourceClass(props: ResourceTableFromResourceClassProps) { - const { resourceClass, id, ...otherProps } = props; - const [items, error] = resourceClass.useList(); - // throttle the update of the table to once per second - const throttledItems = useThrottle(items, 1000); - const dispatchHeadlampEvent = useEventCallback(HeadlampEventType.LIST_VIEW); - - useEffect(() => { - dispatchHeadlampEvent({ - resources: items, - resourceKind: resourceClass.className, - error: error || undefined, - }); - }, [items, error]); - - return ( -
- ); -} - -type ResourceTableColumnWithDefaultShow = ResourceTableColumn & { defaultShow?: boolean }; - -function Table(props: ResourceTableProps) { +function ResourceTableContent(props: ResourceTableProps) { const { columns, defaultSortingColumn, id, noProcessing = false, - columnChooserAnchor = null, hideColumns = [], - onColumnChooserClose, - ...otherProps + filterFunction, + errorMessage, + reflectInURL, + data, } = props; const { t } = useTranslation(['glossary', 'translation']); const theme = useTheme(); const storeRowsPerPageOptions = useSettings('tableRowsPerPageOptions'); const tableProcessors = useTypedSelector(state => state.resourceTable.tableColumnsProcessors); - const [tableSettings, setTableSettings] = useState<{ id: string; show: boolean }[]>( - !!id ? helpers.loadTableSettings(id) : [] + const filter = useTypedSelector(state => state.filter); + const defaultFilterFunc = useFilterFunc(); + const [columnVisibility, setColumnVisibility] = useState(() => + initColumnVisibilityState(columns, id) ); - const [resourceCols, cols, sortingColumn] = useMemo(() => { - let sortingColumn = defaultSortingColumn; + const [tableSettings] = useState<{ id: string; show: boolean }[]>( + !!id ? helpers.loadTableSettings(id) : [] + ); + const [allColumns, sort] = useMemo(() => { let processedColumns = columns; if (!noProcessing) { @@ -157,106 +228,112 @@ function Table(props: ResourceTableProps) { processorInfo.processor({ id: id || '', columns: processedColumns }) || []; }); } + const allColumns = processedColumns + .map((col, index): TableColumn => { + const indexId = String(index); - let shouldSortOnAge = false; - - const resourceCols: ResourceTableColumnWithDefaultShow[] = processedColumns - .map(col => { if (typeof col !== 'string') { - return col; + const column = col as ResourceTableColumn; + + const sort = column.sort ?? true; + + const mrtColumn: TableColumn = { + id: column.id ?? indexId, + header: column.label, + filterVariant: column.filterVariant, + enableMultiSort: !!sort, + enableSorting: !!sort, + enableColumnFilter: !column.disableFiltering, + muiTableHeadCellProps: column.cellProps, + muiTableBodyCellProps: column.cellProps, + grow: false, + }; + + if ('getValue' in column) { + mrtColumn.accessorFn = column.getValue; + } else if ('getter' in column) { + mrtColumn.accessorFn = column.getter; + } else { + mrtColumn.accessorFn = (item: KubeObject) => item[column.datum]; + } + if ('render' in column) { + mrtColumn.Cell = ({ row }: { row: MRT_Row }) => + column.render?.(row.original) ?? null; + } + if (sort && typeof sort === 'function') { + mrtColumn.sortingFn = sortingFn(sort); + } + + return mrtColumn; } switch (col) { case 'name': return { id: 'name', - label: t('translation|Name'), - gridTemplate: 1.5, - getter: (resource: KubeObject) => resource && , - sort: (n1: KubeObject, n2: KubeObject) => { - if (n1.metadata.name < n2.metadata.name) { - return -1; - } else if (n1.metadata.name > n2.metadata.name) { - return 1; - } - return 0; - }, + header: t('translation|Name'), + accessorFn: (item: KubeObject) => item.metadata.name, + Cell: ({ row }: { row: MRT_Row }) => + row.original && , }; case 'age': - shouldSortOnAge = defaultSortingColumn === undefined; return { id: 'age', - label: t('translation|Age'), - cellProps: { style: { textAlign: 'right' } }, - gridTemplate: 0.5, - getter: (resource: KubeObject) => - resource && ( + header: t('translation|Age'), + accessorFn: (item: KubeObject) => + -new Date(item.metadata.creationTimestamp).getTime(), + muiTableHeadCellProps: { align: 'right' }, + muiTableBodyCellProps: { align: 'right' }, + enableColumnFilter: false, + Cell: ({ row }: { row: MRT_Row }) => + row.original && ( ), - sort: (n1: KubeObject, n2: KubeObject) => - new Date(n2.metadata.creationTimestamp).getTime() - - new Date(n1.metadata.creationTimestamp).getTime(), }; case 'namespace': return { id: 'namespace', - label: t('glossary|Namespace'), - getter: (resource: KubeObject) => - resource?.getNamespace() ? ( - - {resource.getNamespace()} + header: t('glossary|Namespace'), + accessorFn: (item: KubeObject) => item.getNamespace(), + filterVariant: 'multi-select', + Cell: ({ row }: { row: MRT_Row }) => + row.original?.getNamespace() ? ( + + {row.original.getNamespace()} ) : ( '' ), - sort: (n1: KubeObject, n2: KubeObject) => { - const ns1 = n1.getNamespace() || ''; - const ns2 = n2.getNamespace() || ''; - if (ns1 < ns2) { - return -1; - } else if (ns1 > ns2) { - return 1; - } - return 0; - }, }; case 'type': case 'kind': return { id: 'kind', - label: t('translation|Type'), - getter: (resource: KubeObject) => resource?.kind, - sort: true, + header: t('translation|Type'), + Cell: ({ row }: { row: MRT_Row }) => row.original.kind, + accessorFn: (resource: KubeObject) => resource?.kind, + filterVariant: 'multi-select', }; default: throw new Error(`Unknown column: ${col}`); } }) - .filter(col => !hideColumns?.includes(col.id ?? '')) - .map((col, idx) => { - const newCol: ResourceTableColumnWithDefaultShow = { id: col.id || idx.toString(), ...col }; - // Assign the default show value so we can remember it later. - newCol.defaultShow = newCol.show ?? true; - - // Assign the actual show value from whatever is stored in the table settings. - const colSettings = tableSettings.find(col => col.id === newCol.id); - newCol.show = colSettings?.show ?? newCol.defaultShow; - return newCol; - }); + .filter(col => !hideColumns?.includes(col.id ?? '')) as TableColumn[]; - // Filter out columns that have show set to false. - const cols = resourceCols.filter(col => col.show !== false); - - if (shouldSortOnAge) { - // Sorting column on SimpleTable starts at 1. - sortingColumn = cols.findIndex(col => col.id === 'age') + 1; + let sort = undefined; + const sortingColumn = defaultSortingColumn ?? allColumns.find(it => it.id === 'age'); + if (sortingColumn) { + sort = { + id: sortingColumn.id!, + desc: false, + }; } - return [resourceCols, cols, sortingColumn]; + return [allColumns, sort]; }, [ columns, hideColumns, @@ -267,36 +344,51 @@ function Table(props: ResourceTableProps) { tableSettings, ]); - function onColumnsVisibilityToggled(cols: ResourceTableColumn[]) { - if (!!id) { - const colsToStore = cols.filter( - c => resourceCols.find(rc => rc.id === c.id)?.defaultShow !== c.show - ); - helpers.storeTableSettings( - id || '', - colsToStore.map(c => ({ id: c.id || '', show: c.show ?? true })) - ); - } - setTableSettings(cols.map(c => ({ id: c.id || '', show: c.show || c.show === undefined }))); + function onColumnsVisibilityChange(updater: any): void { + setColumnVisibility(oldCols => { + const newCols = updater(oldCols); + + if (!!id) { + const colsToStore = Object.entries(newCols).map(([id, show]) => ({ + id, + show: (show ?? true) as boolean, + })); + helpers.storeTableSettings(id, colsToStore); + } + + return newCols; + }); + } + + const initialState: ComponentProps['initialState'] = { + sorting: sort ? [sort] : undefined, + }; + + if (filter.search) { + initialState.globalFilter = filter.search; + initialState.showGlobalFilter = true; } return ( <> - >[]} + data={(data ?? []) as Array>} + loading={data === null} + initialState={initialState} rowsPerPage={storeRowsPerPageOptions} - defaultSortingColumn={sortingColumn} - filterFunction={useFilterFunc()} - reflectInURL - {...otherProps} - /> - { - onColumnChooserClose && onColumnChooserClose(); + state={{ + columnVisibility, }} - onToggleColumn={onColumnsVisibilityToggled} + reflectInURL={reflectInURL} + onColumnVisibilityChange={onColumnsVisibilityChange as any} + filterFunction={ + (filterFunction ?? defaultFilterFunc) as (item: Record) => boolean + } /> ); diff --git a/frontend/src/components/common/Resource/ResourceTableColumnChooser.tsx b/frontend/src/components/common/Resource/ResourceTableColumnChooser.tsx index f8057d8733e..ba7b264766a 100644 --- a/frontend/src/components/common/Resource/ResourceTableColumnChooser.tsx +++ b/frontend/src/components/common/Resource/ResourceTableColumnChooser.tsx @@ -7,14 +7,14 @@ import Popover from '@mui/material/Popover'; import React from 'react'; import { ResourceTableColumn } from './ResourceTable'; -interface ColumnsPopupProps { - columns: ResourceTableColumn[]; - onToggleColumn: (cols: ResourceTableColumn[]) => void; +interface ColumnsPopupProps { + columns: ResourceTableColumn[]; + onToggleColumn: (cols: ResourceTableColumn[]) => void; onClose: () => void; anchorEl: HTMLElement | null; } -export default function ColumnsPopup(props: ColumnsPopupProps) { +export default function ColumnsPopup(props: ColumnsPopupProps) { const { columns, onToggleColumn, onClose, anchorEl } = props; const [currentColumns, setColumnsChanged] = React.useState(columns); diff --git a/frontend/src/components/common/Resource/__snapshots__/ResourceListView.stories.storyshot b/frontend/src/components/common/Resource/__snapshots__/ResourceListView.stories.storyshot index 24487d07bbf..b8d761af125 100644 --- a/frontend/src/components/common/Resource/__snapshots__/ResourceListView.stories.storyshot +++ b/frontend/src/components/common/Resource/__snapshots__/ResourceListView.stories.storyshot @@ -33,45 +33,17 @@ exports[`Storyshots Resource/ListView One Hidden Column 1`] = `
-
-
-
- -
-
- -
-
-
+ + +
@@ -80,261 +52,706 @@ exports[`Storyshots Resource/ListView One Hidden Column 1`] = ` class="MuiBox-root css-1txv3mw" >
-
- - - - - - - - + + + +
+
+ + + + + +
+ +
+
- Name -
- Namespace - - Age -
- - - - + +
+ +
+ + + + + - - + - + - - - + + - - imagepullbackoff - - - + - + - - - + + - + + imagepullbackoff + + + + - + + - + + imagepullbackoff + + + - + + + -

+ + imagepullbackoff + + +

- - + default + + + + + + +
- - imagepullbackoff - - - + +
+ +
+ + +
- default - - - -

+

+
+ +
+ + +
- 3mo - -

- -
- - imagepullbackoff - - - + imagepullbackoff + + - default - - -

+ default + +

- 3mo - -

-
+ 3mo + +

+
- + imagepullbackoff + + - default - - -

+ default + +

- 3mo - -

-
+ 3mo + +

+
+ + default + + - imagepullbackoff - - + 3mo + +

+
- default - - + default + + +

+ 3mo + +

+
- 3mo - -

-
+

+ 3mo + +

+
+ +
+
+ + + - default - - - -

+

- 3mo - -

- - - - + + + + + + +
+
+
+ + diff --git a/frontend/src/components/common/Resource/__snapshots__/ResourceTable.stories.storyshot b/frontend/src/components/common/Resource/__snapshots__/ResourceTable.stories.storyshot index bcab273a0fd..8f54bdfe3b2 100644 --- a/frontend/src/components/common/Resource/__snapshots__/ResourceTable.stories.storyshot +++ b/frontend/src/components/common/Resource/__snapshots__/ResourceTable.stories.storyshot @@ -3,113 +3,612 @@ exports[`Storyshots ResourceTable Name Search 1`] = `
- - - - - - - - - + + + +
+
- Name - - +
+
+ +
+
+ + + + + + +
+ +
+
+
+
+
+
+ + +
+ + + +
+ +
+
+
+
+
+
- Namespace -
- Age -
- - - + + + + + - - MyNamespace3 - - - + + + + + +
- - mypod3 - - +
+ +
+ +
+
+ +
+
+ +
+ +
+
+
+
+ +
+ +
+
+
+ + mypod3 + + + + MyNamespace3 + + +

+ 3mo + +

+
+
+
+
+ +
+
-

- 3mo - -

- - - - + +
+ + + +
+
+ + 1-1 of 1 + +
+ + + + + + +
+
+
+
+
`; @@ -117,224 +616,666 @@ exports[`Storyshots ResourceTable Name Search 1`] = ` exports[`Storyshots ResourceTable No Filter 1`] = `
- - +
+
+ +
+
+ + +
-
+
-
- - - - - + + + +
+
- Name - - Namespace - - Age -
- - - - + - + +
+ +
+ + + + + - - - - - - + + + + - - mypod2 - - - + + + + - - MyNamespace2 - - - + + + + -

+ + mypod3 + + +

- - + MyNamespace3 + + + + + + +
- - mypod0 - - - - MyNamespace0 - - -

+

+ +
+ +
+
+ +
+
+ +
+ +
+
+
- 3mo - -

- -
- - mypod1 - - - - MyNamespace1 - - -

- 3mo - -

-
+ mypod0 + + + + MyNamespace0 + + +

+ 3mo + +

+
+ + mypod1 + + + + MyNamespace1 + + +

+ 3mo + +

+
+ + mypod2 + + + + MyNamespace2 + + +

+ 3mo + +

+
- 3mo - -

-
+

+ 3mo + +

+
+
+
+
+ +
- - - mypod3 - - - - - MyNamespace3 - - - -

+ +

+ + + +
+
+ - 3mo - -

- - - - + 1-4 of 4 +
+
+ + + + + + +
+
+
+
+ `; @@ -342,167 +1283,545 @@ exports[`Storyshots ResourceTable No Filter 1`] = ` exports[`Storyshots ResourceTable With Hidden Cols 1`] = `
- - +
+
+ +
+
+ + - - - - - + + + +
+
+ +
+ +
+
- Name + -
- Age -
- - - - + +
+ +
+ + + + + - - + + + -

- 3mo - -

- - - - + + + - - mypod2 - - - + + + -

+ + mypod3 + + +

- - + 3mo + +

+ +
+ + +
- - mypod0 - - -

+

+ +
+ +
+
+ +
- 3mo - -

- -
- - mypod1 - - + + mypod0 + + +

+ 3mo + +

+
+ mypod1 + + +

+ 3mo + +

+
+ + mypod2 + + +

+ 3mo + +

+
- 3mo - -

-
+
+
+
+ +
- - - mypod3 - - - -

+ +

+ + + +
+
+ - 3mo - -

- - - - + 1-4 of 4 +
+
+ + + + + + +
+
+
+
+ `; diff --git a/frontend/src/components/common/Resource/resourceTableSlice.ts b/frontend/src/components/common/Resource/resourceTableSlice.ts index defa01c446c..c584ff2a127 100644 --- a/frontend/src/components/common/Resource/resourceTableSlice.ts +++ b/frontend/src/components/common/Resource/resourceTableSlice.ts @@ -17,10 +17,10 @@ export type TableColumnsProcessor = { * * @returns The new table columns. */ - processor: (args: { + processor: (args: { id: string; - columns: ResourceTableProps['columns']; - }) => ResourceTableProps['columns']; + columns: ResourceTableProps['columns']; + }) => ResourceTableProps['columns']; }; const initialState: ResourceTableState = { diff --git a/frontend/src/components/common/Tooltip/TooltipLight.tsx b/frontend/src/components/common/Tooltip/TooltipLight.tsx index 4e83ca451c5..86bd7277335 100644 --- a/frontend/src/components/common/Tooltip/TooltipLight.tsx +++ b/frontend/src/components/common/Tooltip/TooltipLight.tsx @@ -1,13 +1,15 @@ import Tooltip, { TooltipProps } from '@mui/material/Tooltip'; import withStyles from '@mui/styles/withStyles'; +import { ReactElement, ReactNode } from 'react'; -export interface TooltipLightProps extends TooltipProps { +export interface TooltipLightProps extends Omit { /** * If true, the tooltip will be interactive. Defaults to true. * * If a tooltip is interactive, it will close when the user hovers over the tooltip before the leaveDelay is expired. */ interactive?: boolean; + children: ReactNode; } const StyledTooltip = withStyles(theme => ({ @@ -32,5 +34,13 @@ export default function TooltipLight(props: TooltipLightProps) { ); } - return ; + return ( + + ); } diff --git a/frontend/src/components/configmap/List.tsx b/frontend/src/components/configmap/List.tsx index 270e43a95f8..e72e341caf9 100644 --- a/frontend/src/components/configmap/List.tsx +++ b/frontend/src/components/configmap/List.tsx @@ -15,9 +15,7 @@ export default function ConfigMapList() { { id: 'data', label: t('translation|Data'), - getter: (configmap: ConfigMap) => Object.keys(configmap.data || {}).length || 0, - sort: true, - gridTemplate: 0.5, + getValue: (configmap: ConfigMap) => Object.keys(configmap.data || {}).length || 0, }, 'age', ]} diff --git a/frontend/src/components/configmap/__snapshots__/List.stories.storyshot b/frontend/src/components/configmap/__snapshots__/List.stories.storyshot index 6edc3a04097..3f810f6ea49 100644 --- a/frontend/src/components/configmap/__snapshots__/List.stories.storyshot +++ b/frontend/src/components/configmap/__snapshots__/List.stories.storyshot @@ -33,45 +33,17 @@ exports[`Storyshots ConfigMap/ListView Items 1`] = `
-
-
-
- -
-
- -
-
-
+ + +
@@ -80,177 +52,675 @@ exports[`Storyshots ConfigMap/ListView Items 1`] = ` class="MuiBox-root css-1txv3mw" >
- - - - - - - - - - + + + +
+
- Name - - +
+
+ +
+
+ + + + + + +
+ +
+
- Namespace -
- Data - - Age -
- - - - + - + +
+ +
+ + + + + - - + - + + + + - 0 - - + + + + + + +
- - my-pvc - - - + +
+ +
+ + +
- default - - - - 3 - -

+

+
+ +
+ + +
+
+ +
+ +
+
+
- 3mo - -

- -
- - my-pvc - - - + my-pvc + + - default - - + default + + + 3 + +

+ 3mo + +

+
+ + my-pvc + + + + default + + + 0 + +

+ 3mo + +

+
+
+
+
+ +
+
-

+ +

+ + + +
+
+ + 1-2 of 2 + +
- 3mo - -

- - - - + + + + + + +
+
+
+
+ diff --git a/frontend/src/components/crd/CustomResourceList.tsx b/frontend/src/components/crd/CustomResourceList.tsx index 310009fb37e..0db82cd48f4 100644 --- a/frontend/src/components/crd/CustomResourceList.tsx +++ b/frontend/src/components/crd/CustomResourceList.tsx @@ -6,11 +6,11 @@ import { useParams } from 'react-router-dom'; import { KubeObject } from '../../lib/k8s/cluster'; import CRD, { KubeCRD } from '../../lib/k8s/crd'; import { localeDate } from '../../lib/util'; -import { Link, Loader, PageGrid, SectionHeader, SimpleTableGetterColumn } from '../common'; +import { Link, Loader, PageGrid, SectionHeader } from '../common'; import BackLink from '../common/BackLink'; import Empty from '../common/EmptyContent'; import ResourceListView from '../common/Resource/ResourceListView'; -import { ResourceTableProps } from '../common/Resource/ResourceTable'; +import { ResourceTableColumn, ResourceTableProps } from '../common/Resource/ResourceTable'; export default function CustomResourceList() { const { t } = useTranslation(['glossary', 'translation']); @@ -124,7 +124,7 @@ export function CustomResourceListTable(props: CustomResourceTableProps) { crd.jsonData.spec.versions.find( (version: KubeCRD['spec']['versions'][number]) => version.name === currentVersion )?.additionalPrinterColumns || []; - const cols: SimpleTableGetterColumn[] = []; + const cols: ResourceTableColumn[] = []; for (let i = 0; i < colsFromSpec.length; i++) { const idx = i; const colSpec = colsFromSpec[idx]; @@ -135,7 +135,7 @@ export function CustomResourceListTable(props: CustomResourceTableProps) { cols.push({ label: colSpec.name, - getter: resource => { + getValue: resource => { let value = getValueWithJSONPath(resource, colSpec.jsonPath); if (colSpec.type === 'date') { value = localeDate(new Date(value)); @@ -150,13 +150,11 @@ export function CustomResourceListTable(props: CustomResourceTableProps) { }, [crd, apiGroup]); const cols = React.useMemo(() => { - const colsToDisplay: ResourceTableProps['columns'] = [ + const colsToDisplay: ResourceTableProps['columns'] = [ { label: t('translation|Name'), - getter: (resource: KubeObject) => , - sort: (c1: KubeObject, c2: KubeObject) => { - return c1.metadata.name.localeCompare(c2.metadata.name); - }, + getValue: resource => resource.metadata.name, + render: (resource: KubeObject) => , }, ...additionalPrinterCols, 'age', diff --git a/frontend/src/components/crd/List.tsx b/frontend/src/components/crd/List.tsx index 4e49763d43a..f7c183f3f64 100644 --- a/frontend/src/components/crd/List.tsx +++ b/frontend/src/components/crd/List.tsx @@ -16,7 +16,8 @@ export default function CustomResourceDefinitionList() { columns={[ { label: t('glossary|Resource'), - getter: crd => ( + getValue: crd => crd.spec.names.kind, + render: crd => ( ( + getValue: crd => crd.metadata.name, + render: crd => ( ), - sort: (c1: CRD, c2: CRD) => { - if (c1.metadata.name < c2.metadata.name) { - return -1; - } else if (c1.metadata.name > c2.metadata.name) { - return 1; - } - return 0; - }, }, { label: t('translation|Group'), - getter: crd => crd.spec.group, - sort: true, + getValue: crd => crd.spec.group, }, { label: t('Scope'), - getter: crd => crd.spec.scope, - sort: true, + getValue: crd => crd.spec.scope, }, 'age', ]} diff --git a/frontend/src/components/crd/__snapshots__/CustomResourceDefinition.stories.storyshot b/frontend/src/components/crd/__snapshots__/CustomResourceDefinition.stories.storyshot index 483fae36f17..d265b294a32 100644 --- a/frontend/src/components/crd/__snapshots__/CustomResourceDefinition.stories.storyshot +++ b/frontend/src/components/crd/__snapshots__/CustomResourceDefinition.stories.storyshot @@ -459,45 +459,17 @@ exports[`Storyshots crd/CustomResourceDefinition Details 1`] = `
-
-
-
- -
-
- -
-
-
+ + +
@@ -506,166 +478,675 @@ exports[`Storyshots crd/CustomResourceDefinition Details 1`] = ` class="MuiBox-root css-1txv3mw" >
- - - - - - - - - - + + + +
+
+ + + + + +
+ +
+
- Name -
- Namespace - - Test Col - - Age -
- - - - - + +
+ +
+ + + + + + - - + - + + + + - myotherresource - - + + + + + + +
- - mycustomresource - - - + +
+ +
+ + +
- mynamespace - - - - mycustomresource - -

+

+
+ +
+ + +
- 3mo - -

- -
+
+ +
+ +
+
+
- - myotherresource - - - + mycustomresource + + - mynamespace - - + mynamespace + + + mycustomresource + +

+ 3mo + +

+
+ + myotherresource + + + + mynamespace + + + myotherresource + +

+ 3mo + +

+
+
+
+
+ +
+
-

- 3mo - -

- - - - + +
+ + + +
+
+ + 1-2 of 2 + +
+ + + + + + +
+
+
+
+ @@ -857,45 +1338,17 @@ exports[`Storyshots crd/CustomResourceDefinition List 1`] = `
-
-
-
- -
-
- -
-
-
+ + +
@@ -904,146 +1357,712 @@ exports[`Storyshots crd/CustomResourceDefinition List 1`] = ` class="MuiBox-root css-1txv3mw" >
- - - - - - - - - - - + + + +
+
- Resource - - Definition - - +
+
+ +
+
+ + + + + + +
+ +
+
- Group -
- Scope - - Age -
- - - - + + + + + + - Namespaced - - + + + + + + + +
- - MyCustomResource - - - + +
+ +
+ + +
- mydefinition.phonyresources.io - - - - my.phonyresources.io - + +
+ +
+ + +
+
+ +
+ +
+
+
+
+ +
+ +
+
+
+
+ +
+ +
+
+
+ + MyCustomResource + + + + mydefinition.phonyresources.io + + + my.phonyresources.io + + Namespaced + +

+ 3mo + +

+
+
+
+
+ +
+
-

- 3mo - -

- - - - + +
+ + + +
+
+ + 1-1 of 1 + +
+ + + + + + +
+
+
+
+ diff --git a/frontend/src/components/crd/__snapshots__/CustomResourceList.stories.storyshot b/frontend/src/components/crd/__snapshots__/CustomResourceList.stories.storyshot index 67b3b730ba3..008f73c0e1f 100644 --- a/frontend/src/components/crd/__snapshots__/CustomResourceList.stories.storyshot +++ b/frontend/src/components/crd/__snapshots__/CustomResourceList.stories.storyshot @@ -107,45 +107,17 @@ exports[`Storyshots crd/CustomResourceList List 1`] = `
-
-
-
- -
-
- -
-
-
+ + +
@@ -154,166 +126,675 @@ exports[`Storyshots crd/CustomResourceList List 1`] = ` class="MuiBox-root css-1txv3mw" >
- - +
+
+ +
+
+ + - - - - - - - + + + +
+
+ +
+ +
+
- Name -
- Namespace - - Test Col - - Age -
- - - - + - + +
+ +
+ + + + + - - + - + + + + - myotherresource - - + + + + + + +
- - mycustomresource - - - + +
+ +
+ + +
- mynamespace - - - - mycustomresource - -

+

+
+ +
+ + +
+
+ +
+ +
+
+
- 3mo - -

- -
- - myotherresource - - - + mycustomresource + + - mynamespace - - + mynamespace + + + mycustomresource + +

+ 3mo + +

+
+ + myotherresource + + + + mynamespace + + + myotherresource + +

+ 3mo + +

+
+
+
+
+ +
+
-

+ +

+ + + +
+
+ + 1-2 of 2 + +
- 3mo - -

- - - - + + + + + + +
+
+
+
+ diff --git a/frontend/src/components/cronjob/List.tsx b/frontend/src/components/cronjob/List.tsx index ca967bdb82c..84c5eaa1334 100644 --- a/frontend/src/components/cronjob/List.tsx +++ b/frontend/src/components/cronjob/List.tsx @@ -1,6 +1,5 @@ import cronstrue from 'cronstrue/i18n'; import { useTranslation } from 'react-i18next'; -import { KubeContainer } from '../../lib/k8s/cluster'; import CronJob from '../../lib/k8s/cronJob'; import { DateLabel, HoverInfoLabel, LightTooltip } from '../common'; import ResourceListView from '../common/Resource/ResourceListView'; @@ -42,32 +41,35 @@ export default function CronJobList() { { id: 'schedule', label: t('Schedule'), - getter: cronJob => getSchedule(cronJob, i18n.language), + getValue: cronJob => cronJob.spec.schedule, + render: cronJob => getSchedule(cronJob, i18n.language), }, { id: 'suspend', label: t('translation|Suspend'), - getter: cronJob => cronJob.spec.suspend.toString(), - sort: true, - gridTemplate: 0.6, + getValue: cronJob => cronJob.spec.suspend.toString(), }, { id: 'active', label: t('translation|Active'), - getter: cronJob => cronJob.status?.active?.length || 0, - sort: true, - gridTemplate: 0.6, + getValue: cronJob => cronJob.status?.active?.length || 0, }, { id: 'lastScheduleTime', label: t('Last Schedule'), - getter: cronJob => getLastScheduleTime(cronJob), + getValue: cronJob => cronJob.status.lastScheduletime ?? '', + render: cronJob => getLastScheduleTime(cronJob), }, { id: 'containers', label: t('Containers'), - getter: deployment => { - const containers = deployment.getContainers().map((c: KubeContainer) => c.name); + getValue: deployment => + deployment + .getContainers() + .map(c => c.name) + .join(', '), + render: deployment => { + const containers = deployment.getContainers().map(c => c.name); const containerText = containers.join(', '); const containerTooltip = containers.join('\n'); return ( @@ -80,8 +82,13 @@ export default function CronJobList() { { id: 'images', label: t('Images'), - getter: deployment => { - const images = deployment.getContainers().map((c: KubeContainer) => c.image); + getValue: deployment => + deployment + .getContainers() + .map(c => c.image) + .join(', '), + render: deployment => { + const images = deployment.getContainers().map(c => c.image); const imageText = images.join(', '); const imageTooltip = images.join('\n'); return ( diff --git a/frontend/src/components/cronjob/__snapshots__/CronJobDetails.stories.storyshot b/frontend/src/components/cronjob/__snapshots__/CronJobDetails.stories.storyshot index 6b5059cc07a..5486a2dd0db 100644 --- a/frontend/src/components/cronjob/__snapshots__/CronJobDetails.stories.storyshot +++ b/frontend/src/components/cronjob/__snapshots__/CronJobDetails.stories.storyshot @@ -289,45 +289,17 @@ exports[`Storyshots CronJob/CronJobDetailsView Every Ast 1`] = `
-
-
-
- -
-
- -
-
-
+ + +
@@ -787,45 +759,17 @@ exports[`Storyshots CronJob/CronJobDetailsView Every Minute 1`] = `
-
-
-
- -
-
- -
-
-
+ + +
diff --git a/frontend/src/components/daemonset/List.tsx b/frontend/src/components/daemonset/List.tsx index 6397e51436b..901d2378c79 100644 --- a/frontend/src/components/daemonset/List.tsx +++ b/frontend/src/components/daemonset/List.tsx @@ -17,35 +17,28 @@ export default function DaemonSetList() { { id: 'pods', label: t('Pods'), - getter: daemonSet => daemonSet.status?.currentNumberScheduled || 0, - gridTemplate: 0.6, - sort: true, + getValue: daemonSet => daemonSet.status?.currentNumberScheduled || 0, }, { id: 'currentPods', label: t('translation|Current'), - getter: daemonSet => daemonSet.status?.currentNumberScheduled || 0, - gridTemplate: 0.6, - sort: true, + getValue: daemonSet => daemonSet.status?.currentNumberScheduled || 0, }, { id: 'desiredPods', label: t('translation|Desired', { context: 'pods' }), - getter: daemonSet => daemonSet.status?.desiredNumberScheduled || 0, - gridTemplate: 0.6, - sort: true, + getValue: daemonSet => daemonSet.status?.desiredNumberScheduled || 0, }, { id: 'readyPods', label: t('translation|Ready'), - getter: daemonSet => daemonSet.status?.numberReady || 0, - gridTemplate: 0.6, - sort: true, + getValue: daemonSet => daemonSet.status?.numberReady || 0, }, { id: 'nodeSelector', label: t('Node Selector'), - getter: daemonSet => { + getValue: daemonSet => daemonSet.getNodeSelectors().join(', '), + render: daemonSet => { const selectors = daemonSet.getNodeSelectors(); const nodeSelectorTooltip = selectors.join('\n'); const nodeSelectorText = selectors.join(', '); @@ -59,7 +52,12 @@ export default function DaemonSetList() { { id: 'containers', label: t('Containers'), - getter: daemonSet => { + getValue: daemonSet => + daemonSet + .getContainers() + .map((c: KubeContainer) => c.name) + .join(', '), + render: daemonSet => { const containerNames = daemonSet.getContainers().map((c: KubeContainer) => c.name); const containerText = containerNames.join(', '); const containerTooltip = containerNames.join('\n'); @@ -73,7 +71,12 @@ export default function DaemonSetList() { { id: 'images', label: t('Images'), - getter: daemonSet => { + getValue: daemonSet => + daemonSet + .getContainers() + .map((c: KubeContainer) => c.image) + .join(', '), + render: daemonSet => { const images = daemonSet.getContainers().map((c: KubeContainer) => c.image); const imageTooltip = images.join('\n'); const imageText = images.join(', '); diff --git a/frontend/src/components/daemonset/__snapshots__/List.stories.storyshot b/frontend/src/components/daemonset/__snapshots__/List.stories.storyshot index a290072c158..7b51e64171c 100644 --- a/frontend/src/components/daemonset/__snapshots__/List.stories.storyshot +++ b/frontend/src/components/daemonset/__snapshots__/List.stories.storyshot @@ -36,45 +36,17 @@ exports[`Storyshots DaemonSet/List Daemon Sets 1`] = `
-
-
-
- -
-
- -
-
-
+ + +
@@ -83,252 +55,1145 @@ exports[`Storyshots DaemonSet/List Daemon Sets 1`] = ` class="MuiBox-root css-1txv3mw" >
- - - - - - - - - - -
- Name - - +
+
+ +
+
+ + + + + + +
+ +
+
- Namespace -
- Pods - - Current - + + + +
+ + + - Desired - - - + - - - - + - - - - + +
+ +
+ + + + + + + + + + + + - - + - - - - - + - - + + + + + + + + +
- Ready - + + + - - - - - Node Selector - - Containers - - Images - - Age - + + + - - - -
+
+ +
+ +
+
+
+
+ +
+ +
+
+
+
+ +
+ +
+
+
+
+ +
+ +
+
+
+
+ +
+ +
+
+
+
+ +
+ +
+
+
+
+ +
+ +
+
+
- - gadget - - - + gadget + + - gadget - - - 2 - - 2 - - 2 - - 2 - - + gadget + + - kubernetes.io/os=linux - - - + - gadget - - + + 2 + + 2 + + + kubernetes.io/os=linux + + + + gadget + + + + ghcr.io/inspektor-gadget/inspektor-gadget:v0.15.0 + + +

+ 3mo + +

+
+
+
+
+ +
+
+
+ +
+ + + +
+
- ghcr.io/inspektor-gadget/inspektor-gadget:v0.15.0 + 1-1 of 1 - -
-

- 3mo - -

-
+ + + + + + +
+ + + + diff --git a/frontend/src/components/deployments/List.tsx b/frontend/src/components/deployments/List.tsx index 45bcf6c3874..9fd50312ae0 100644 --- a/frontend/src/components/deployments/List.tsx +++ b/frontend/src/components/deployments/List.tsx @@ -66,26 +66,30 @@ export default function DeploymentsList() { { id: 'pods', label: t('Pods'), - getter: deployment => renderPods(deployment), + getValue: deployment => deployment.status.availableReplicas, + render: deployment => renderPods(deployment), sort: sortByPods, - gridTemplate: 0.5, }, { id: 'replicas', label: t('Replicas'), - getter: deployment => deployment.spec.replicas || 0, - sort: true, - gridTemplate: 0.6, + getValue: deployment => deployment.spec.replicas || 0, }, { id: 'conditions', label: t('translation|Conditions'), - getter: deployment => renderConditions(deployment), + getValue: deployment => deployment.status?.conditions?.map((c: any) => c.type)?.join(''), + render: deployment => renderConditions(deployment), }, { id: 'containers', label: t('Containers'), - getter: deployment => { + getValue: deployment => + deployment + .getContainers() + .map(c => c.name) + .join(', '), + render: deployment => { const containers = deployment.getContainers().map((c: KubeContainer) => c.name); const containerText = containers.join(', '); const containerTooltip = containers.join('\n'); @@ -99,7 +103,12 @@ export default function DeploymentsList() { { id: 'images', label: t('Images'), - getter: deployment => { + getValue: deployment => + deployment + .getContainers() + .map(c => c.image) + .join(', '), + render: deployment => { const images = deployment.getContainers().map((c: KubeContainer) => c.image); const imageText = images.join(', '); const imageTooltip = images.join('\n'); @@ -113,7 +122,8 @@ export default function DeploymentsList() { { id: 'selector', label: t('Selector'), - getter: deployment => { + getValue: deployment => deployment.getMatchLabelsList().join(', '), + render: deployment => { const matchLabels = deployment.getMatchLabelsList(); const text = matchLabels.join(', '); const tooltip = matchLabels.join('\n'); diff --git a/frontend/src/components/endpoints/List.tsx b/frontend/src/components/endpoints/List.tsx index d4281bbd561..65ee574ed0f 100644 --- a/frontend/src/components/endpoints/List.tsx +++ b/frontend/src/components/endpoints/List.tsx @@ -6,7 +6,7 @@ import ResourceListView from '../common/Resource/ResourceListView'; export default function EndpointList() { const { t } = useTranslation(['glossary', 'translation']); - const filterFunc = useFilterFunc([ + const filterFunc = useFilterFunc([ '.jsonData.subsets[*].addresses[*].ip', '.jsonData.subsets[*].ports[*].port', '.jsonData.subsets[*].ports[*].name', @@ -23,8 +23,8 @@ export default function EndpointList() { { id: 'addresses', label: t('translation|Addresses'), - getter: endpoint => , - gridTemplate: 1.5, + getValue: endpoint => endpoint.getAddresses().join(', '), + render: endpoint => , }, 'age', ]} diff --git a/frontend/src/components/endpoints/__snapshots__/EndpointList.stories.storyshot b/frontend/src/components/endpoints/__snapshots__/EndpointList.stories.storyshot index 3b032992688..2ba122fa426 100644 --- a/frontend/src/components/endpoints/__snapshots__/EndpointList.stories.storyshot +++ b/frontend/src/components/endpoints/__snapshots__/EndpointList.stories.storyshot @@ -33,45 +33,17 @@ exports[`Storyshots endpoints/EndpointsListView Items 1`] = `
-
-
-
- -
-
- -
-
-
+ + +
@@ -80,322 +52,843 @@ exports[`Storyshots endpoints/EndpointsListView Items 1`] = ` class="MuiBox-root css-1txv3mw" >
- - +
+
+ +
+
+ + +
-
+
-
- - - - - - + + + +
+
- Name - - Namespace - - Addresses - - Age -
- - - - + +
+ +
+ + + + + - - + - + - + - - - + + - - endpoints-2 - - - + - + - + - - - + + - - endpoints-3 - - - + - + - + - - - + + - - endpoints-4 - - - + + + + + - + + endpoints-4 + + + - + + + + + +
- - endpoints-0 - - - + +
+ +
+ + +
- my-namespace - - - - + +
+ +
+ + +
- 127.0.01:8080 - - - -

+

+
+ +
+ + +
- 3mo - -

- -
- - endpoints-1 - - - + endpoints-0 + + - my-namespace - - - + my-namespace + + - 127.0.01:8080 - - -

+ 127.0.01:8080 + +

- 3mo - -

-
+ 3mo + +

+
- + endpoints-1 + + - my-namespace - - - + my-namespace + + - 127.0.01:8080 - - -

+ 127.0.01:8080 + +

- 3mo - -

-
+ 3mo + +

+
- + endpoints-2 + + - my-namespace - - - + my-namespace + + - 127.0.01:8080 - - -

+ 127.0.01:8080 + +

- 3mo - -

-
+ 3mo + +

+
+ endpoints-3 + + + + my-namespace + + + + 127.0.01:8080 + + +

+ 3mo + +

+
- my-namespace - - + my-namespace + + + + 127.0.01:8080 + + +

+ 3mo + +

+
+
+
+
+ +
+
+
+ +
+ + + +
+
- 127.0.01:8080 + 1-5 of 5 - - -

- 3mo - -

- - - - + + + + + + +
+
+
+
+ diff --git a/frontend/src/components/horizontalPodAutoscaler/List.tsx b/frontend/src/components/horizontalPodAutoscaler/List.tsx index 0c3532b7663..5394da603b7 100644 --- a/frontend/src/components/horizontalPodAutoscaler/List.tsx +++ b/frontend/src/components/horizontalPodAutoscaler/List.tsx @@ -32,7 +32,8 @@ export default function HpaList() { { id: 'reference', label: t('translation|Reference'), - getter: item => ( + getValue: item => item.referenceObject?.metadata.name, + render: item => ( {item.referenceObject?.kind}/{item.referenceObject?.metadata.name} @@ -41,7 +42,12 @@ export default function HpaList() { { id: 'targets', label: t('translation|Targets'), - getter: (hpa: HPA) => { + getValue: item => + item + .metrics(t) + .map(it => it.shortValue) + .join(', '), + render: (hpa: HPA) => { const value: JSX.Element[] = []; const metrics = hpa.metrics(t); if (metrics.length) { @@ -61,30 +67,21 @@ export default function HpaList() { } return {value}; }, - cellProps: { - style: { - width: 'fit-content', - minWidth: '100%', - }, - }, }, { id: 'minReplicas', label: t('translation|MinReplicas'), - getter: item => item.spec.minReplicas, - sort: true, + getValue: item => item.spec.minReplicas, }, { id: 'maxReplicas', label: t('translation|MaxReplicas'), - getter: item => item.spec.maxReplicas, - sort: true, + getValue: item => item.spec.maxReplicas, }, { id: 'currentReplicas', label: t('glossary|Replicas'), - getter: item => item.status.currentReplicas, - sort: true, + getValue: item => item.status.currentReplicas, }, 'age', ]} diff --git a/frontend/src/components/horizontalPodAutoscaler/__snapshots__/HPAList.stories.storyshot b/frontend/src/components/horizontalPodAutoscaler/__snapshots__/HPAList.stories.storyshot index 86e92fd7f83..437bf836e61 100644 --- a/frontend/src/components/horizontalPodAutoscaler/__snapshots__/HPAList.stories.storyshot +++ b/frontend/src/components/horizontalPodAutoscaler/__snapshots__/HPAList.stories.storyshot @@ -33,45 +33,17 @@ exports[`Storyshots hpa/HpaListView Items 1`] = `
-
-
-
- -
-
- -
-
-
+ + +
@@ -80,595 +52,1381 @@ exports[`Storyshots hpa/HpaListView Items 1`] = ` class="MuiBox-root css-1txv3mw" >
- - - - - - - - - - - - - - + + + +
+
- Name - - - Namespace - - - Reference - - Targets - - MinReplicas - - +
+
+ +
+
+ + + + + + +
+ +
+
- MaxReplicas -
- Replicas - - Age -
- - - - - - - - - - - - - - + - - - - - - - - + - - - - + +
+ +
+ + + + + - - - - + + + + + + + + + + + + + - - - - - - - - + + + + - - default - - - + + + + + + + + + - - Deployment - / - php-apache - - - + + + + + + + + + - + + + + + + + + + +
- - php-apache - - - - default - - - - Deployment - / - php-apache - - -
- - 14360576/100Mi - +
+ Name +
+ + + + + + 0 + + +
+
+ +
+ +
- - 1more… - +
+ Namespace +
+ + + + + + 0 + + +
+
+ +
- - -
- 1 - - 10 - - 1 - -

- 3mo - -

-
- - php-apache - - - - default - - - - Deployment - / - php-apache - - -
+
- +
+ Reference +
+ + + + + + 0 + + +
+
- 14360576/100Mi - + +
+
- +
+ Targets +
+ + + + + + 0 + + +
+
- 1more… - + +
- - -
- 1 - - 10 - - 1 - -

+

- 3mo - -

- -
- - php-apache - - - - default - - - - Deployment - / - php-apache - - -
+ +
+ +
+
+ +
- +
+ MaxReplicas +
+ + + + + + 0 + + +
+
- 14360576/100Mi - + +
+
- +
+ Replicas +
+ + + + + + 0 + + +
+
- 1more… - + +
- - -
- 1 - - 10 - - 1 - -

+

- 3mo - -

- -
- - php-apache - - - - default - - - - Deployment - / - php-apache - - - + + default + + + + Deployment + / + php-apache + +
- - 14360576/100Mi - + + 14360576/100Mi + +
+
+ + 1more… + +
+
+ 1 + + 10 + + 1 + +

+ 3mo + +

+
+ + php-apache + + + + default + + + + Deployment + / + php-apache + +
- + + 14360576/100Mi + +
+
- 1more… - + + 1more… + +
- -
- 1 - - 10 - - 1 - -

+

- 3mo - -

-
- - php-apache - - + + 10 + + 1 + +

+ 3mo + +

+
+ + php-apache + + + + default + + + + Deployment + / + php-apache + + +
+
+ + 14360576/100Mi + +
+
+ + 1more… + +
+
+
+ 1 + + 10 + + 1 + +

+ 3mo + +

+
+ + php-apache + + + + default + + + + Deployment + / + php-apache + + +
+
+ + 14360576/100Mi + +
+
+ + 1more… + +
+
+
+ 1 + + 10 + + 1 + +

+ 3mo + +

+
+ + default + + + + Deployment + / + php-apache + +
- + + 14360576/100Mi + +
+
- 14360576/100Mi - + + 1more… + +
+
+ 1 + + 10 + + 1 + +

+ 3mo + +

+
+
+
+
+ +
+
+
+
-
+ +
- - - 1 - - - 10 - - - 1 - - -

+ 1-5 of 5 + +

- 3mo - -

- - - - + + + + + + +
+
+
+
+ diff --git a/frontend/src/components/ingress/ClassList.tsx b/frontend/src/components/ingress/ClassList.tsx index 4a7a628286f..2ebec0389a5 100644 --- a/frontend/src/components/ingress/ClassList.tsx +++ b/frontend/src/components/ingress/ClassList.tsx @@ -17,17 +17,17 @@ export default function IngressClassList() { { id: 'default', label: '', - gridTemplate: 0.1, - getter: (resource: IngressClass) => + getValue: resource => (resource?.isDefault ? t('Default Ingress Class') : null), + render: (resource: IngressClass) => resource && resource.isDefault ? : null, sort: false, + disableFiltering: true, }, 'name', { id: 'controller', label: t('Controller'), - getter: (ingressClass: IngressClass) => ingressClass.spec?.controller, - sort: true, + getValue: (ingressClass: IngressClass) => ingressClass.spec?.controller, }, 'age', ]} diff --git a/frontend/src/components/ingress/List.tsx b/frontend/src/components/ingress/List.tsx index 6a8315397a3..46adebb3325 100644 --- a/frontend/src/components/ingress/List.tsx +++ b/frontend/src/components/ingress/List.tsx @@ -46,25 +46,31 @@ export default function IngressList() { { id: 'class', label: t('Class Name'), - getter: (ingress: Ingress) => + getValue: ingress => ingress.spec?.ingressClassName, + render: ingress => ingress.spec?.ingressClassName ? ( {ingress.spec?.ingressClassName} ) : null, - sort: true, }, { id: 'hosts', label: t('Hosts'), - getter: (ingress: Ingress) => ( + getValue: ingress => + ingress + .getRules() + .map(r => r.host ?? '*') + .join(''), + render: ingress => ( host || '*')} /> ), }, { id: 'ports', label: t('translation|Path'), - getter: (ingress: Ingress) => , + getValue: () => '', + render: (ingress: Ingress) => , }, 'age', ]} diff --git a/frontend/src/components/ingress/__snapshots__/ClassList.stories.storyshot b/frontend/src/components/ingress/__snapshots__/ClassList.stories.storyshot index 1c16093ef10..7c8c15053f0 100644 --- a/frontend/src/components/ingress/__snapshots__/ClassList.stories.storyshot +++ b/frontend/src/components/ingress/__snapshots__/ClassList.stories.storyshot @@ -33,45 +33,17 @@ exports[`Storyshots IngressClass/ListView Items 1`] = `
-
-
-
- -
-
- -
-
-
+ + +
@@ -80,161 +52,640 @@ exports[`Storyshots IngressClass/ListView Items 1`] = ` class="MuiBox-root css-1txv3mw" >
- - - - - - - - - - + + + +
+
- - +
+
+
+ +
+
+
+ + + + + +
+ +
+
- Name -
- Controller - - Age -
- - - - + - + +
+ +
+ + + + + + - - - + + + + - test - - + + + + + + +
- - - resource-example-ingress - - - test - -

+

+ +
+ +
+
+ +
+
+ +
+ +
+
+
- 3mo - -

- -
+
+ +
+ +
+
+
-

+

- - -

-
- - resource-example-ingress - - + resource-example-ingress + + + test + +

+ 3mo + +

+
+

+ + +

+
+ + resource-example-ingress + + + test + +

+ 3mo + +

+
+
+
+
+ +
+
-

+ +

+ + + +
+
+ + 1-2 of 2 + +
- 3mo - -

- - - - + + + + + + +
+
+
+
+ diff --git a/frontend/src/components/ingress/__snapshots__/List.stories.storyshot b/frontend/src/components/ingress/__snapshots__/List.stories.storyshot index 61011d21916..f10848be934 100644 --- a/frontend/src/components/ingress/__snapshots__/List.stories.storyshot +++ b/frontend/src/components/ingress/__snapshots__/List.stories.storyshot @@ -33,45 +33,17 @@ exports[`Storyshots Ingress/ListView Items 1`] = `
-
-
-
- -
-
- -
-
-
+ + +
@@ -80,230 +52,874 @@ exports[`Storyshots Ingress/ListView Items 1`] = ` class="MuiBox-root css-1txv3mw" >
- - - - - - - - - - - - + + + +
+
- Name - - +
+
+ +
+
+ + + + + + +
+ +
+
- Namespace -
- Class Name - - Hosts - - Path - - Age -
- - - - + +
+ +
+ + + + + + - - + - + + + + + - - https-example.foo.com - - - + + + + + + + +
- - tls-example-ingress - - - + +
+ +
+ + +
- default - - - - - + +
+ +
+ + +
- https-example.foo.com - - - - + +
+ +
+ + +
- / 🞂 service1:80, /second 🞂 service2:someport - - - -

+

+
+ +
+ + +
- 3mo - -

- -
+
+ +
+ +
+
+
- - resource-example-ingress - - - + tls-example-ingress + + - default - - - + default + + + + + https-example.foo.com + + + + / 🞂 service1:80, /second 🞂 service2:someport + + +

+ 3mo + +

+
+ resource-example-ingress + + + + default + + + + + https-example.foo.com + + + + / 🞂 Service:service1 + + +

+ 3mo + +

+
+
+
+
+ +
+
+
+ +
+ + + +
+
- / 🞂 Service:service1 + 1-2 of 2 - - -

- 3mo - -

- - - - + + + + + + +
+
+
+
+ diff --git a/frontend/src/components/job/List.tsx b/frontend/src/components/job/List.tsx index 4dc78038022..b5037e46e4e 100644 --- a/frontend/src/components/job/List.tsx +++ b/frontend/src/components/job/List.tsx @@ -98,18 +98,21 @@ export function JobsListRenderer(props: JobsListRendererProps) { { id: 'completions', label: t('Completions'), - getter: job => getCompletions(job), + getValue: job => getCompletions(job), sort: sortByCompletions, }, { id: 'conditions', label: t('translation|Conditions'), - getter: job => makePodStatusLabel(job), + getValue: job => + job.status?.conditions?.find(({ status }: { status: string }) => status === 'True') ?? + null, + render: job => makePodStatusLabel(job), }, { id: 'duration', label: t('translation|Duration'), - getter: job => { + getValue: job => { const startTime = job.status?.startTime; const completionTime = job.status?.completionTime; if (!!startTime && !!completionTime) { @@ -118,13 +121,16 @@ export function JobsListRenderer(props: JobsListRendererProps) { } return '-'; }, - gridTemplate: 0.6, - sort: true, }, { id: 'containers', label: t('Containers'), - getter: job => { + getValue: job => + job + .getContainers() + .map(c => c.name) + .join(''), + render: job => { const containerNames = job.getContainers().map((c: KubeContainer) => c.name); const containerTooltip = containerNames.join('\n'); const containerText = containerNames.join(', '); @@ -138,7 +144,12 @@ export function JobsListRenderer(props: JobsListRendererProps) { { id: 'images', label: t('Images'), - getter: job => { + getValue: job => + job + .getContainers() + .map(c => c.image) + .join(''), + render: job => { const containerImages = job.getContainers().map((c: KubeContainer) => c.image); const containerTooltip = containerImages.join('\n'); const containerText = containerImages.join(', '); diff --git a/frontend/src/components/job/__snapshots__/JobList.stories.storyshot b/frontend/src/components/job/__snapshots__/JobList.stories.storyshot index 7718ff50431..773b5ab1109 100644 --- a/frontend/src/components/job/__snapshots__/JobList.stories.storyshot +++ b/frontend/src/components/job/__snapshots__/JobList.stories.storyshot @@ -33,45 +33,17 @@ exports[`Storyshots Job/List Items 1`] = `
-
-
-
- -
-
- -
-
-
+ + +
@@ -80,404 +52,1191 @@ exports[`Storyshots Job/List Items 1`] = ` class="MuiBox-root css-1txv3mw" >
- - - - - - - - - - - - - - + + + +
+
- Name - - - Namespace - - +
+
+ +
+
+ + + + + + +
+ +
+
- Completions -
- Conditions - - Duration - - Containers - - Images - - Age -
- - - - - - - - - - - - - - + +
+ +
+ + + + + - - + + + + + + + + + - - default - - - + + + + + + + + + - 1/1 - - + + + + + + + + + + +
- - job-0 - - - - default - - - 1/1 - -
- - Complete
- +
+ Name +
+ + + + + + 0 + +
-
-
-
- 8h - - + + + + + - hello - - - - + +
+ +
+ + +
- busybox:1.28 - - - -

+

+
+ +
+ + +
- 3mo - -

- -
- - job-1 - - - - default - - - 1/1 - -
+ +
+ +
+
+ +
- - Failed
- +
+ Duration +
+ + + + + + 0 + +
-
- - -
- 8h - - + + + + + - hello - - - - + +
+ +
+ + +
- busybox:1.28 - - - -

+

+
+ +
+ + +
- 3mo - -

- -
- - job-2 - - + + job-0 + + + + default + + + 1/1 + +
+ + Complete +
+ +
+
+
+
+ 8h + + + hello + + + + busybox:1.28 + + +

+ 3mo + +

+
+ + job-1 + + + + default + + + 1/1 + +
+ + Failed +
+ +
+
+
+
+ 8h + + + hello + + + + busybox:1.28 + + +

+ 3mo + +

+
+ + job-2 + + + + default + + + 1/1 + +
+ + Suspended +
+ +
+
+
+
+ 8h + + + hello + + + + busybox:1.28 + + +

+ 3mo + +

+
+
+
+
+ +
+
- + Rows per page + +
- Suspended - + + +
- - - 8h - - - hello + 1-3 of 3 - - - - busybox:1.28 - - - -

- 3mo - -

- - - - + + + + + + +
+
+
+
+ diff --git a/frontend/src/components/lease/List.tsx b/frontend/src/components/lease/List.tsx index ce549eb52f3..6a791e78e18 100644 --- a/frontend/src/components/lease/List.tsx +++ b/frontend/src/components/lease/List.tsx @@ -14,7 +14,7 @@ export function LeaseList() { { id: 'holder', label: t('translation|Holder'), - getter: item => item?.spec.holderIdentity, + getValue: item => item?.spec.holderIdentity, }, 'age', ]} diff --git a/frontend/src/components/lease/__snapshots__/List.stories.storyshot b/frontend/src/components/lease/__snapshots__/List.stories.storyshot index aabd3620062..b12bfa2be2b 100644 --- a/frontend/src/components/lease/__snapshots__/List.stories.storyshot +++ b/frontend/src/components/lease/__snapshots__/List.stories.storyshot @@ -33,45 +33,17 @@ exports[`Storyshots Lease/ListView Items 1`] = `
-
-
-
- -
-
- -
-
-
+ + +
@@ -80,124 +52,629 @@ exports[`Storyshots Lease/ListView Items 1`] = ` class="MuiBox-root css-1txv3mw" >
- - - - - - - - - - + + + +
+
+ + + + + +
+ +
+
- Name -
- Namespace - - Holder - - Age -
- - - + + + + + - holder - - + + + + + + +
- - lease - - - + +
+ +
+ + +
+
+ +
+ +
+
+
- default - - - + +
+ +
+ + +
+
+ +
+ +
+
+
+ + lease + + + + default + + + holder + +

+ 3mo + +

+
+
+
+
+ +
+
-

- 3mo - -

- - - - + +
+ + + +
+
+ + 1-1 of 1 + +
+ + + + + + +
+
+
+
+ diff --git a/frontend/src/components/limitRange/__snapshots__/List.stories.storyshot b/frontend/src/components/limitRange/__snapshots__/List.stories.storyshot index 8ef98f67960..88b82c0351f 100644 --- a/frontend/src/components/limitRange/__snapshots__/List.stories.storyshot +++ b/frontend/src/components/limitRange/__snapshots__/List.stories.storyshot @@ -33,45 +33,17 @@ exports[`Storyshots LimitRange/ListView Items 1`] = `
-
-
-
- -
-
- -
-
-
+ + +
@@ -80,113 +52,546 @@ exports[`Storyshots LimitRange/ListView Items 1`] = ` class="MuiBox-root css-1txv3mw" >
- - - - - - - - - + + + +
+
+ + + + + +
+ +
+
- Name -
- Namespace - - Age -
- - + + + + - + + limit-range + + + - + + + + +
- +
+ +
+ +
+
+ +
- limit-range - - - + +
+ +
+ + +
+
+ +
+ +
+
+
- default - - + default + + +

+ 3mo + +

+
+
+
+
+ +
+
-

+ +

+ + + +
+
+ + 1-1 of 1 + +
- 3mo - -

- - - - + + + + + + +
+
+
+
+ diff --git a/frontend/src/components/namespace/List.tsx b/frontend/src/components/namespace/List.tsx index a6a25b8a31f..bdb29c2ff7f 100644 --- a/frontend/src/components/namespace/List.tsx +++ b/frontend/src/components/namespace/List.tsx @@ -37,53 +37,55 @@ export default function NamespacesList() { return {status}; } - const resourceTableProps: ResourceTableFromResourceClassProps | ResourceTableProps = - React.useMemo(() => { - if (allowedNamespaces.length > 0) { - return { - columns: [ - { - id: 'name', - label: t('translation|Name'), - getter: ({ metadata }) => ( - - {metadata.name} - - ), - }, - { - id: 'status', - label: t('translation|Status'), - getter: () => 'Unknown', - }, - { - id: 'age', - label: t('translation|Age'), - getter: () => 'Unknown', - }, - ], - data: allowedNamespaces, - }; - } + const resourceTableProps: + | ResourceTableFromResourceClassProps + | ResourceTableProps = React.useMemo(() => { + if (allowedNamespaces.length > 0) { return { - resourceClass: Namespace, columns: [ - 'name', + { + id: 'name', + label: t('translation|Name'), + getValue: ns => ns.metadata.name, + render: ({ metadata }) => ( + + {metadata.name} + + ), + }, { id: 'status', label: t('translation|Status'), - getter: makeStatusLabel, - sort: (namespace: Namespace) => namespace.status.phase, + getValue: () => 'Unknown', + }, + { + id: 'age', + label: t('translation|Age'), + getValue: () => 'Unknown', }, - 'age', ], + data: allowedNamespaces as unknown as Namespace[], }; - }, [allowedNamespaces]); + } + return { + resourceClass: Namespace, + columns: [ + 'name', + { + id: 'status', + label: t('translation|Status'), + getValue: ns => ns.status.phase, + render: makeStatusLabel, + }, + 'age', + ], + }; + }, [allowedNamespaces]); return ( + +
- +
+ +
+
-
-
- -
-
- - - - - - - - - - + + + + + + + +
+
- - - - - - - - - - + + + - - + - - - - - + + + - + - - + + + - - - + - + - + + + + + + + + + + + + + + + +
- - test-cpu-quota - - -
- - requests.cpu: 0.5 cores/0.2 cores - +
+ Name +
+ + + + + + 0 + + +
+
+ +
- -
-
+
- - limits.cpu: 0 cores/0.3 cores - +
+ Request +
+ + + + + + 0 + + +
+
+ +
- - -
-

- 3mo - -

-
- - test-cpu-quota - - -
+
- - requests.cpu: 0.5 cores/0.2 cores - +
+ Limit +
+ + + + + + 0 + + +
+
+ +
- - -
-
+
- - limits.cpu: 0 cores/0.3 cores - +
+ Age +
+ + + + + + 0 + + +
+
+ +
- - -
-

- 3mo - -

-
- - test-cpu-quota - - -
+ test-cpu-quota + +
- - requests.cpu: 0.5 cores/0.2 cores - + + requests.cpu: 0.5 cores/0.2 cores + +
- -
-
+
- - limits.cpu: 0 cores/0.3 cores - + + limits.cpu: 0 cores/0.3 cores + +
- -
-

- 3mo - -

-
- + +

+ 3mo + +

+
- test-cpu-quota - - -
+ test-cpu-quota + +
- - requests.cpu: 0.5 cores/0.2 cores - + + requests.cpu: 0.5 cores/0.2 cores + +
- -
-
+
- - limits.cpu: 0 cores/0.3 cores - + + limits.cpu: 0 cores/0.3 cores + +
- -
-

+

+

+ 3mo + +

+
- 3mo - -

-
- + test-cpu-quota + + - test-cpu-quota - - -
+
+ + requests.cpu: 0.5 cores/0.2 cores + +
+
+
- - requests.cpu: 0.5 cores/0.2 cores - + + limits.cpu: 0 cores/0.3 cores + +
- -
+ +

+ 3mo + +

+
+ + test-cpu-quota + + +
+
+ + requests.cpu: 0.5 cores/0.2 cores + +
+
+
+
+
+ + limits.cpu: 0 cores/0.3 cores + +
+
+
+

+ 3mo + +

+
+ + test-cpu-quota + + +
+
+ + requests.cpu: 0.5 cores/0.2 cores + +
+
+
+
+
+ + limits.cpu: 0 cores/0.3 cores + +
+
+
+

+ 3mo + +

+
+
+
+
+ +
+
+
- + 15 +
+ +
- - -

- 3mo - -

- - - - + 1-5 of 5 + +
+ + + + + + +
+
+
+
+
@@ -616,910 +1124,1971 @@ exports[`Storyshots Namespace/DetailsView Active 1`] = ` /> + +
+
+
+ +
+
+
+ -
-
-
-
-
- - - +
-
- - - - - - - - - -
- Name - - Age -
- - limit-range - - -

- 3mo - -

-
-
-
- - - -
-
-
-
-
-
-

- Pods -

-
+ + + +
+
+
-
-
-
- -
-
-
-
-
-
- + + + + + + + + + + + +
+
+ +
+ +
+
+
+
+ +
+ +
+
+
+ + limit-range + + +

+ 3mo + +

+
+
+
- - +
- - Name - - - - Restarts -
+ + 1-1 of 1 + +
- - - - - Ready - - + + + + + +
+
+
+
+ + + + + + +
+
+
+
+
+
+

+ Pods +

+
+
+
+
+
+
+
+
+
+
- Status - - - +
+
+ +
+
+
+
+
+
+
+ +
+ +
+
- IP - - - Node - - - Age - - - - +
+
+
+
+ - - - - - - + + + + + + + + - - my-node + imagepullbackoff - - - + + - - - + - - - - - + + + - + + - - - + - - - - - + + + - + + - - - + - - - - - + + + - - - - + + + - - - + + - - + + + - + + - - - + - - - - - + + + - + + - - - + - - - - - + + + - + + - - - + - - - - - + + + - + + + + + + + + +
- - imagepullbackoff - - - 0 - - 0/1 - -
+ +
+ +
+
+ +
- - ImagePullBackOff - - - - - 0.0.0.2 - +
+ Restarts +
+ + + + + + 0 + + + +
+ +
+ + +
+
+ +
+ +
+
+
+
+ +
+ +
+
+
+
+ +
+ +
+
+
+
+ +
+ +
+
+
+
+ +
+ +
+
+
-

+

+ 0 + + 0/1 + - 3mo - -

-
- + + ImagePullBackOff + + + - successful - - - 0 - - 0/1 - -
+
- Completed + + my-node + - - - 0.0.0.2 - - + +

+ 3mo + +

+
- my-node + successful - - -

+

+ 0 + + 0/1 + - 3mo - -

-
- + + Completed + + + - initializing - - - 0 - - 0/1 - -
+
- Init:0/2 + + my-node + - - - 0.0.0.2 - - + +

+ 3mo + +

+
- my-node + initializing - - -

+

+ 0 + + 0/1 + - 3mo - -

-
- + + Init:0/2 + + + - liveness-http - - - 337 (3mo ago) - - 0/1 - -
+
- CrashLoopBackOff -
- -
+ my-node +
- -
- 0.0.0.2 - - + +

+ 3mo + +

+
- my-node - - - -

- 3mo - -

-
- + liveness-http + + + 337 (3mo ago) + + 0/1 + - terminated - - - 0 - - 0/1 - -
+ + CrashLoopBackOff +
+ +
+
+
+
+ 0.0.0.2 + - Error -
- -
+ my-node +
- -
- 0.0.0.2 - - + +

+ 3mo + +

+
- my-node + terminated - - -

+

+ 0 + + 0/1 + - 3mo - -

-
- + + Error +
+ +
+
+ +
- running - - - 0 - - 1/1 - -
+
- Running + + my-node + - - - 0.0.0.2 - - + +

+ 3mo + +

+
- my-node + running - - -

+

+ 0 + + 1/1 + - 3mo - -

-
- + + Running + + + - nominated-node - - - 0 - - 1/1 - -
+
- Running + + my-node + - - - 0.0.0.2 - - + +

+ 3mo + +

+
- my-node + nominated-node - - -

+

+ 0 + + 1/1 + - 3mo - -

-
- + + Running + + + - readiness-gate - - - 0 - - 1/1 - -
+
- Running + + my-node + - - - 0.0.0.2 - - + +

+ 3mo + +

+
- my-node + readiness-gate - - + + 0 + + 1/1 + +
+ + Running + +
+
+ 0.0.0.2 + + + + my-node + + + +

+ 3mo + +

+
+
+
+
+ +
+
-

- 3mo - -

- - - - + +
+ + + +
+
+ + 1-8 of 8 + +
+ + + + + + +
+
+
+
+
diff --git a/frontend/src/components/namespace/__snapshots__/NamespaceList.stories.storyshot b/frontend/src/components/namespace/__snapshots__/NamespaceList.stories.storyshot index ccd549b4887..55f87c918cf 100644 --- a/frontend/src/components/namespace/__snapshots__/NamespaceList.stories.storyshot +++ b/frontend/src/components/namespace/__snapshots__/NamespaceList.stories.storyshot @@ -33,45 +33,17 @@ exports[`Storyshots Namespace/ListView Regular 1`] = `
-
-
-
- -
-
- -
-
-
+ + +
@@ -80,261 +52,706 @@ exports[`Storyshots Namespace/ListView Regular 1`] = ` class="MuiBox-root css-1txv3mw" >
- - - - - - - - - + + + +
+
+ + + + + +
+ +
+
- Name -
- Status - - Age -
- - - - + +
+ +
+ + + + + - - + - + - - - + + - - namespace-2 - - - + - + - - - + + - + + namespace-2 + + + - + + + - - Active - - - + + + + -

- 3mo - -

- - - + namespace-4 + + + + + + + +
- - namespace-0 - - - + +
+ +
+ + +
- Active - - - -

+

+
+ +
+ + +
- 3mo - -

- -
- - namespace-1 - - - + namespace-0 + + - Active - - -

+ Active + +

- 3mo - -

-
+ 3mo + +

+
- + namespace-1 + + - Active - - -

+ Active + +

- 3mo - -

-
+ 3mo + +

+
- namespace-3 - - + Active + + +

+ 3mo + +

+
+ namespace-3 + + + + Active + + +

+ 3mo + +

+
+ + Active + + +

+ 3mo + +

+
+
+
+
+ +
- - - namespace-4 - - - + +
+ + + +
+
- Active + 1-5 of 5 - - -

- 3mo - -

- - - - + + + + + + +
+
+ + + diff --git a/frontend/src/components/networkpolicy/List.tsx b/frontend/src/components/networkpolicy/List.tsx index f023c7b7764..8cd2e8a6be4 100644 --- a/frontend/src/components/networkpolicy/List.tsx +++ b/frontend/src/components/networkpolicy/List.tsx @@ -15,7 +15,7 @@ export function NetworkPolicyList() { { id: 'type', label: t('translation|Type'), - getter: networkpolicy => { + getValue: networkpolicy => { console.log(networkpolicy); const isIngressAvailable = networkpolicy.jsonData.spec.ingress && networkpolicy.jsonData.spec.ingress.length > 0; @@ -33,7 +33,15 @@ export function NetworkPolicyList() { { id: 'podSelector', label: t('Pod Selector'), - getter: networkpolicy => { + getValue: networkpolicy => { + const podSelector = networkpolicy.jsonData.spec.podSelector; + return podSelector.matchLabels + ? Object.keys(podSelector.matchLabels) + .map(key => `${key}=${podSelector.matchLabels[key]}`) + .join(', ') + : null; + }, + render: networkpolicy => { const podSelector = networkpolicy.jsonData.spec.podSelector; return podSelector.matchLabels ? ( ( + getValue: node => { + const [used] = getResourceMetrics(node, nodeMetrics || [], 'cpu'); + return used; + }, + render: node => ( ( + getValue: node => { + const [used] = getResourceMetrics(node, nodeMetrics || [], 'memory'); + return used; + }, + render: node => ( , + getValue: node => { + const isReady = !!node.status.conditions.find( + condition => condition.type === 'Ready' && condition.status === 'True' + ); + return isReady ? t('translation|Yes') : t('translation|No'); + }, + render: node => , }, { id: 'taints', label: t('translation|Taints'), - getter: (item: Node) => , + getValue: node => + node.spec?.taints?.map(taint => `${taint.key}:${taint.effect}`)?.join(', '), + render: (item: Node) => , }, { id: 'roles', label: t('Roles'), - gridTemplate: 'minmax(100px, .5fr)', - getter: node => { + getValue: node => { return Object.keys(node.metadata.labels) .filter((t: String) => t.startsWith('node-role.kubernetes.io/')) .map(t => t.replace('node-role.kubernetes.io/', '')) @@ -76,24 +91,23 @@ export default function NodeList() { { id: 'internalIP', label: t('translation|Internal IP'), - getter: node => node.getInternalIP(), + getValue: node => node.getInternalIP(), }, { id: 'externalIP', label: t('External IP'), - getter: node => node.getExternalIP(), + getValue: node => node.getExternalIP(), }, { id: 'version', label: t('translation|Version'), - gridTemplate: 'minmax(100px, .5fr)', - getter: node => node.status.nodeInfo.kubeletVersion, + getValue: node => node.status.nodeInfo.kubeletVersion, }, { id: 'software', label: t('translation|Software'), - gridTemplate: 'minmax(200px, 1.5fr)', - getter: node => { + getValue: node => node.status.nodeInfo.operatingSystem, + render: node => { let osIcon = 'mdi:desktop-classic'; if (node.status.nodeInfo.operatingSystem === 'linux') { osIcon = 'mdi:linux'; diff --git a/frontend/src/components/node/__snapshots__/List.stories.storyshot b/frontend/src/components/node/__snapshots__/List.stories.storyshot index 2712df3ba86..df36dbb598d 100644 --- a/frontend/src/components/node/__snapshots__/List.stories.storyshot +++ b/frontend/src/components/node/__snapshots__/List.stories.storyshot @@ -36,45 +36,17 @@ exports[`Storyshots node/List Nodes 1`] = `
-
-
-
- -
-
- -
-
-
+ + +
@@ -83,267 +55,1225 @@ exports[`Storyshots node/List Nodes 1`] = ` class="MuiBox-root css-1txv3mw" >
- - +
+
+ +
+
+ + - - - - - - - - - - - - - + + + +
+
+ +
+ +
+
- Name + -
- CPU - - Memory - - Ready - - Taints - - Roles - - Internal IP - - External IP - - Version - - Age -
- - - - - - - - - - - - + +
+ +
+ + + + + + + + + + + + + - - + + + + + + + + + + + -
- -
+ + + + + + + + + + + + +
- - my-windows-node - - -
-
-
-
- - Yes - - -
-
- agent - - 1.2.3.4 - - - - v1.26.3 - -

+

+
+ +
+ + +
- 3mo - -

- -
+
+ +
+ +
+
+
+
+ +
+ +
+
+
+
+ +
+ +
+
+
+
+ +
+ +
+
+
+
+ +
+ +
+
+
+
+ +
+ +
+
+
+
+ +
+ +
+
+
+
+ +
+ +
+
+
- - my-linux-node - - + + my-windows-node + + +
+
+
+
+ + Yes + + +
+
+ agent + + 1.2.3.4 + + + + v1.26.3 + +

+ 3mo + +

+
+ + my-linux-node + + +
+
+
+
+ + Yes + + +
+
+ + + 10.2.3.4 + + + + v1.23.12 + +

+ 3mo + +

+
+
+
+
+ +
+
- - + class="MuiBox-root css-exd1zr" + > + +
+ + + +
+
- Yes + 1-2 of 2 - -
- - - - - - 10.2.3.4 - - - - - - v1.23.12 - - -

- 3mo - -

- - - - + + + + + + +
+
+
+
+
diff --git a/frontend/src/components/node/utils.tsx b/frontend/src/components/node/utils.tsx index 32dfb96317f..618fef9833c 100644 --- a/frontend/src/components/node/utils.tsx +++ b/frontend/src/components/node/utils.tsx @@ -24,7 +24,7 @@ export function NodeTaintsLabel(props: { node: Node }) { const limits: JSX.Element[] = []; node.spec.taints.forEach(taint => { limits.push( - + ); diff --git a/frontend/src/components/pod/List.tsx b/frontend/src/components/pod/List.tsx index 5612e300cf5..1b31a5a9b08 100644 --- a/frontend/src/components/pod/List.tsx +++ b/frontend/src/components/pod/List.tsx @@ -99,7 +99,7 @@ export function PodListRenderer(props: PodListProps) { 'namespace', { label: t('Restarts'), - getter: (pod: Pod) => { + getValue: (pod: Pod) => { const { restarts, lastRestartDate } = pod.getDetailedStatus(); return lastRestartDate.getTime() !== 0 ? t('{{ restarts }} ({{ abbrevTime }} ago)', { @@ -108,12 +108,11 @@ export function PodListRenderer(props: PodListProps) { }) : restarts; }, - sort: true, }, { id: 'ready', label: t('translation|Ready'), - getter: (pod: Pod) => { + getValue: (pod: Pod) => { const podRow = pod.getDetailedStatus(); return `${podRow.readyContainers}/${podRow.totalContainers}`; }, @@ -121,51 +120,55 @@ export function PodListRenderer(props: PodListProps) { { id: 'status', label: t('translation|Status'), - getter: makePodStatusLabel, - sort: (pod: Pod) => { - const podRow = pod.getDetailedStatus(); - return podRow.reason; - }, + getValue: pod => pod.getDetailedStatus().reason, + render: makePodStatusLabel, }, { id: 'ip', label: t('glossary|IP'), - getter: (pod: Pod) => pod.status.podIP, - sort: true, + getValue: (pod: Pod) => pod.status.podIP, }, { id: 'node', label: t('glossary|Node'), - getter: (pod: Pod) => + getValue: pod => pod?.spec?.nodeName, + render: pod => pod?.spec?.nodeName && ( {pod.spec.nodeName} ), - sort: (p1: Pod, p2: Pod) => { - return p1?.spec?.nodeName?.localeCompare(p2?.spec?.nodeName || '') || 0; - }, }, { id: 'nominatedNode', label: t('glossary|Nominated Node'), - getter: (pod: Pod) => + getValue: pod => pod?.status?.nominatedNodeName, + render: pod => !!pod?.status?.nominatedNodeName && ( {pod?.status?.nominatedNodeName} ), - sort: (p1: Pod, p2: Pod) => { - return ( - p1?.status?.nominatedNodeName?.localeCompare(p2?.status?.nominatedNodeName || '') || 0 - ); - }, show: false, }, { id: 'readinessGates', label: t('glossary|Readiness Gates'), - getter: (pod: Pod) => { + getValue: pod => { + const readinessGatesStatus = getReadinessGatesStatus(pod); + const total = Object.keys(readinessGatesStatus).length; + + if (total === 0) { + return ''; + } + + const statusTrueCount = Object.values(readinessGatesStatus).filter( + status => status === 'True' + ).length; + + return statusTrueCount; + }, + render: (pod: Pod) => { const readinessGatesStatus = getReadinessGatesStatus(pod); const total = Object.keys(readinessGatesStatus).length; diff --git a/frontend/src/components/pod/__snapshots__/PodList.stories.storyshot b/frontend/src/components/pod/__snapshots__/PodList.stories.storyshot index c5c48b8e973..f5100f7ffea 100644 --- a/frontend/src/components/pod/__snapshots__/PodList.stories.storyshot +++ b/frontend/src/components/pod/__snapshots__/PodList.stories.storyshot @@ -33,45 +33,17 @@ exports[`Storyshots Pod/PodListView Items 1`] = `
-
-
-
- -
-
- -
-
-
+ + +
@@ -80,836 +52,1641 @@ exports[`Storyshots Pod/PodListView Items 1`] = ` class="MuiBox-root css-1txv3mw" >
- - - - - - - - - - -
- Name - - +
+
+ +
+
+ + + + + + +
+ +
+
- Namespace -
- Restarts - - Ready - - Status - - IP -
+ +
+ + + +
+
+ + 1-8 of 8 + +
+ + + + + + +
+ + + + diff --git a/frontend/src/components/podDisruptionBudget/List.tsx b/frontend/src/components/podDisruptionBudget/List.tsx index 4a82375f34c..366778e8f6f 100644 --- a/frontend/src/components/podDisruptionBudget/List.tsx +++ b/frontend/src/components/podDisruptionBudget/List.tsx @@ -15,18 +15,17 @@ export default function PDBList() { { id: 'minAvailable', label: t('translation|Min Available'), - getter: (item: PDB) => item.spec.minAvailable || t('translation|N/A'), - sort: true, + getValue: (item: PDB) => item.spec.minAvailable || t('translation|N/A'), }, { id: 'maxUnavailable', label: t('translation|Max Unavailable'), - getter: (item: PDB) => item.spec.maxUnavailable || t('translation|N/A'), + getValue: (item: PDB) => item.spec.maxUnavailable || t('translation|N/A'), }, { id: 'allowedDisruptions', label: t('translation|Allowed Disruptions'), - getter: (item: PDB) => item.status.disruptionsAllowed || t('translation|N/A'), + getValue: (item: PDB) => item.status.disruptionsAllowed || t('translation|N/A'), }, 'age', ]} diff --git a/frontend/src/components/podDisruptionBudget/__snapshots__/pdbList.stories.storyshot b/frontend/src/components/podDisruptionBudget/__snapshots__/pdbList.stories.storyshot index bdd1a1046da..9d4a6f4c3ca 100644 --- a/frontend/src/components/podDisruptionBudget/__snapshots__/pdbList.stories.storyshot +++ b/frontend/src/components/podDisruptionBudget/__snapshots__/pdbList.stories.storyshot @@ -33,45 +33,17 @@ exports[`Storyshots PDB/PDBListView Items 1`] = `
-
-
-
- -
-
- -
-
-
+ + +
@@ -80,365 +52,1027 @@ exports[`Storyshots PDB/PDBListView Items 1`] = ` class="MuiBox-root css-1txv3mw" >
- - - - - - - - - - - - + + + +
+
- Name - - +
+
+ +
+
+ + + + + + +
+ +
+
- Namespace -
- Min Available - - Max Unavailable - - Allowed Disruptions - - Age -
- - - - - - - - - - - - - - - - - - - - - - + + - + +
+ +
+ + + + + + - - - + + + + + + + - 1 - - + + + + + + + - N/A - - + + + + + + + - 1 - - + + + + + + + -

+ + coredns-pdb + + +

+ + + - - + + + + +
- - coredns-pdb - - - - kube-system - - - 1 - - N/A - - 1 - -

- 3mo - -

-
- - coredns-pdb - - - - kube-system - - - 1 - - N/A - - 1 - -

+

+
+ +
+ + +
- 3mo - -

- -
- - coredns-pdb - - - - kube-system - - - 1 - - N/A - - 1 - -

+

+
+ +
+ + +
+
+ +
+ +
+
+
+
+ +
+ +
+
+
- 3mo - -

- -
+
+ +
+ +
+
+
- - coredns-pdb - - - - kube-system - - + + coredns-pdb + + + + kube-system + + + 1 + + N/A + + 1 + +

+ 3mo + +

+
+ + coredns-pdb + + + + kube-system + + + 1 + + N/A + + 1 + +

+ 3mo + +

+
+ + coredns-pdb + + + + kube-system + + + 1 + + N/A + + 1 + +

+ 3mo + +

+
+ + coredns-pdb + + + + kube-system + + + 1 + + N/A + + 1 + +

+ 3mo + +

+
+ + kube-system + + + 1 + + N/A + - 3mo - -

-
+

+ 3mo + +

+
+
+
+
+ +
- - - coredns-pdb - - - - - kube-system - - - - 1 - - - N/A - - - 1 - - -

+ +

+ + + +
+
+ + 1-5 of 5 + +
- 3mo - -

- - - - + + + + + + +
+
+
+ + diff --git a/frontend/src/components/priorityClass/List.tsx b/frontend/src/components/priorityClass/List.tsx index 3481579a2c3..baf7518135f 100644 --- a/frontend/src/components/priorityClass/List.tsx +++ b/frontend/src/components/priorityClass/List.tsx @@ -14,14 +14,12 @@ export default function PriorityClassList() { { id: 'value', label: t('translation|Value'), - getter: item => item.value, - sort: true, + getValue: item => item.value, }, { id: 'globalDefault', label: t('translation|Global Default'), - getter: item => item.globalDefault || 'False', - sort: true, + getValue: item => String(item.globalDefault || 'False'), }, 'age', ]} diff --git a/frontend/src/components/priorityClass/__snapshots__/priorityClassList.stories.storyshot b/frontend/src/components/priorityClass/__snapshots__/priorityClassList.stories.storyshot index ee9ed57b060..b49e71b2426 100644 --- a/frontend/src/components/priorityClass/__snapshots__/priorityClassList.stories.storyshot +++ b/frontend/src/components/priorityClass/__snapshots__/priorityClassList.stories.storyshot @@ -33,45 +33,17 @@ exports[`Storyshots PriorityClass/PriorityClassListView Items 1`] = `
-
-
-
- -
-
- -
-
-
+ + +
@@ -80,278 +52,788 @@ exports[`Storyshots PriorityClass/PriorityClassListView Items 1`] = ` class="MuiBox-root css-1txv3mw" >
- - - - - - - - - - + + + +
+
- Name - - +
+
+ +
+
+ + + + + + +
+ +
+
- Value -
- Global Default - - Age -
- - - - - - - - - - + +
+ +
+ + + + + - - - - + - - - + + + - + + high-priority-apps + + + - + + + - 1000000 - - + + + + + - False - - + + + + + -

- 3mo - -

- - - + high-priority-apps + + + + + + + + +
- - high-priority-apps - - - 1000000 - - False - -

+

+
+ +
+ + +
- 3mo - -

- -
- + +
+ +
+ + +
- high-priority-apps - - - - 1000000 - - False - -

+

+
+ +
+ + +
- 3mo - -

- -
- - high-priority-apps - - - 1000000 - - False - -

+ high-priority-apps + +

- 3mo - -

-
+ + False + +

+ 3mo + +

+
- high-priority-apps - - + + False + +

+ 3mo + +

+
+ + high-priority-apps + + + 1000000 + + False + +

+ 3mo + +

+
+ + high-priority-apps + + + 1000000 + + False + +

+ 3mo + +

+
+ 1000000 + + False + +

+ 3mo + +

+
+
+
+
+ +
- - - high-priority-apps - - - - 1000000 - - - False - - -

+ Rows per page + +

+ + + +
+
+ - 3mo - -

- - - - + 1-5 of 5 +
+
+ + + + + + +
+
+
+ + diff --git a/frontend/src/components/replicaset/List.tsx b/frontend/src/components/replicaset/List.tsx index fa72b9973db..040d6e48507 100644 --- a/frontend/src/components/replicaset/List.tsx +++ b/frontend/src/components/replicaset/List.tsx @@ -17,35 +17,33 @@ export default function ReplicaSetList() { { id: 'generation', label: t('Generation'), - getter: replicaSet => replicaSet?.status?.observedGeneration, - sort: true, + getValue: replicaSet => replicaSet?.status?.observedGeneration, show: false, }, { id: 'currentReplicas', label: t('translation|Current', { context: 'replicas' }), - getter: replicaSet => replicaSet?.status?.replicas || 0, - gridTemplate: 0.6, - sort: true, + getValue: replicaSet => replicaSet?.status?.replicas || 0, }, { id: 'desiredReplicas', label: t('translation|Desired', { context: 'replicas' }), - getter: replicaSet => replicaSet?.spec?.replicas || 0, - gridTemplate: 0.6, - sort: true, + getValue: replicaSet => replicaSet?.spec?.replicas || 0, }, { id: 'readyReplicas', label: t('translation|Ready'), - getter: replicaSet => replicaSet?.status?.readyReplicas || 0, - gridTemplate: 0.6, - sort: true, + getValue: replicaSet => replicaSet?.status?.readyReplicas || 0, }, { id: 'containers', label: t('Containers'), - getter: replicaSet => { + getValue: replicaSet => + replicaSet + .getContainers() + .map(c => c.name) + .join(''), + render: replicaSet => { const containerText = replicaSet .getContainers() .map((c: KubeContainer) => c.name) @@ -56,22 +54,16 @@ export default function ReplicaSetList() { ); }, - sort: (rs1, rs2) => { - const containers1 = rs1 - .getContainers() - .map((c: KubeContainer) => c.name) - .join(''); - const containers2 = rs2 - .getContainers() - .map((c: KubeContainer) => c.name) - .join(''); - return containers1.localeCompare(containers2); - }, }, { id: 'images', label: t('Images'), - getter: replicaSet => { + getValue: replicaSet => + replicaSet + .getContainers() + .map((c: KubeContainer) => c.image) + .join(''), + render: replicaSet => { const imageText = replicaSet .getContainers() .map((c: KubeContainer) => c.image) @@ -82,22 +74,12 @@ export default function ReplicaSetList() { ); }, - sort: (rs1, rs2) => { - const images1 = rs1 - .getContainers() - .map((c: KubeContainer) => c.image) - .join(''); - const images2 = rs2 - .getContainers() - .map((c: KubeContainer) => c.image) - .join(''); - return images1.localeCompare(images2); - }, }, { id: 'selector', label: t('Selector'), - getter: replicaSet => { + getValue: replicaSet => replicaSet.getMatchLabelsList().join(''), + render: replicaSet => { const selectorText = replicaSet.getMatchLabelsList().join('\n'); return ( selectorText && ( @@ -107,7 +89,6 @@ export default function ReplicaSetList() { ) ); }, - sort: true, }, 'age', ]} diff --git a/frontend/src/components/replicaset/__snapshots__/List.stories.storyshot b/frontend/src/components/replicaset/__snapshots__/List.stories.storyshot index ba650fc9ddc..f9e4d2565fb 100644 --- a/frontend/src/components/replicaset/__snapshots__/List.stories.storyshot +++ b/frontend/src/components/replicaset/__snapshots__/List.stories.storyshot @@ -36,45 +36,17 @@ exports[`Storyshots ReplicaSet/List Replica Sets 1`] = `
-
-
-
- -
-
- -
-
-
+ + +
@@ -83,354 +55,1162 @@ exports[`Storyshots ReplicaSet/List Replica Sets 1`] = ` class="MuiBox-root css-1txv3mw" >
- - - - - - - - - - -
- Name - - +
+
+ +
+
+ + + + + + +
+ +
+
- Namespace -
- Current - - Desired - + + + +
+ + + - Ready - - - + - + - + - + - - - - + +
+ +
+ + + + + + + + + - - - - - - - + - + + + + + + - + - - - - - - - - + + - - headlamp - - - + - + + + + + + + + + + +
- Containers - + + + - - - - - Images - + + + - - - - - Selector - + + + - - - - - Age - + + + - - - -
+
+ +
+ +
+
+
+
+ +
+ +
+
+
+
+ +
+ +
+
+
+
+ +
+ +
+
+
- - headlamp-release-b123456 - - - - default - - - 0 - - 1 - - 0 - - - headlamp - - - + headlamp-release-b123456 + + - ghcr.io/headlamp-k8s/headlamp:v0.18.0 - - - + default + + + 0 + + 1 + + 0 + + + headlamp + + + + ghcr.io/headlamp-k8s/headlamp:v0.18.0 + + + - app.kubernetes.io/instance=headlamp-release + class="" + data-mui-internal-clone-element="true" + > + app.kubernetes.io/instance=headlamp-release app.kubernetes.io/name=headlamp pod-template-hash=b123456 - - -

+

- 3mo - -

-
- - headlamp-a123456 - - - - default - - - 0 - - 2 - - 0 - + 3mo + +

+
- + headlamp-a123456 + + - ghcr.io/headlamp-k8s/headlamp:latest - - + default + + + 0 + + 2 + + 0 + + + headlamp + + + + ghcr.io/headlamp-k8s/headlamp:latest + + + + k8s-app=headlamp +pod-template-hash=a123456 + + +

+ 3mo + +

+
+
+
+
+ +
+
+
+ +
+ + + +
+
- k8s-app=headlamp -pod-template-hash=a123456 + 1-2 of 2 - -
-

- 3mo - -

-
+ + + + + + +
+ + + + diff --git a/frontend/src/components/resourceQuota/List.tsx b/frontend/src/components/resourceQuota/List.tsx index c882b39eac1..1e9a1e378c6 100644 --- a/frontend/src/components/resourceQuota/List.tsx +++ b/frontend/src/components/resourceQuota/List.tsx @@ -48,7 +48,8 @@ export function ResourceQuotaRenderer(props: ResourceQuotaProps) { { id: 'requests', label: t('translation|Request'), - getter: (item: ResourceQuota) => { + getValue: item => item.requests.join(', '), + render: item => { const requests: JSX.Element[] = []; item.requests.forEach((request: string) => { requests.push(); @@ -65,7 +66,8 @@ export function ResourceQuotaRenderer(props: ResourceQuotaProps) { { id: 'limits', label: t('translation|Limit'), - getter: (item: ResourceQuota) => { + getValue: item => item.limits.join(', '), + render: item => { const limits: JSX.Element[] = []; item.limits.forEach((limit: string) => { limits.push(); diff --git a/frontend/src/components/resourceQuota/__snapshots__/resourceQuotaList.stories.storyshot b/frontend/src/components/resourceQuota/__snapshots__/resourceQuotaList.stories.storyshot index 41bf28ecc71..c907f3b9a57 100644 --- a/frontend/src/components/resourceQuota/__snapshots__/resourceQuotaList.stories.storyshot +++ b/frontend/src/components/resourceQuota/__snapshots__/resourceQuotaList.stories.storyshot @@ -33,45 +33,17 @@ exports[`Storyshots ResourceQuota/ResourceQuotaListView Items 1`] = `
-
-
-
- -
-
- -
-
-
+ + +
@@ -80,449 +52,1046 @@ exports[`Storyshots ResourceQuota/ResourceQuotaListView Items 1`] = ` class="MuiBox-root css-1txv3mw" >
- - - - - - - - - - - + + + +
+
+ + + + + +
+ +
+
- Name -
- Namespace - - Request - - Limit - - Age -
- - - - - + - + +
+ +
+ + + + + + - - - + + - - - - - - - + + - + - - - - - - - + + - + - - - - - - + + - - test - - - + + + + + + - + - + + + + +
- - test-cpu-quota - - - - test - - -
- - requests.cpu: 0.5 cores/0.2 cores - +
+ Name +
+ + + + + + 0 + + +
+
+ +
- -
-
+
- +
+ Namespace +
+ + + + + + 0 + + +
+
- limits.cpu: 0 cores/0.3 cores - + +
- - -
-

+

+
+ +
+ +
+
+
- 3mo - -

- -
+
+ +
+ +
+
+
- - test-cpu-quota - - - - test - - - + + test + +
- - requests.cpu: 0.5 cores/0.2 cores - + + requests.cpu: 0.5 cores/0.2 cores + +
- -
-
+
- - limits.cpu: 0 cores/0.3 cores - + + limits.cpu: 0 cores/0.3 cores + +
- -
-

+

- 3mo - -

-
- - test-cpu-quota - - - - test - - + 3mo + +

+
+ + test + +
- - requests.cpu: 0.5 cores/0.2 cores - + + requests.cpu: 0.5 cores/0.2 cores + +
- -
-
+
- - limits.cpu: 0 cores/0.3 cores - + + limits.cpu: 0 cores/0.3 cores + +
- -
-

+

- 3mo - -

-
- - test-cpu-quota - - - - test - - + 3mo + +

+
+ + test + +
- - requests.cpu: 0.5 cores/0.2 cores - + + requests.cpu: 0.5 cores/0.2 cores + +
- -
-
+
- - limits.cpu: 0 cores/0.3 cores - + + limits.cpu: 0 cores/0.3 cores + +
- -
-

+

- 3mo - -

-
- - test-cpu-quota - - + 3mo + +

+
+ + test-cpu-quota + + + + test + + +
+
+ + requests.cpu: 0.5 cores/0.2 cores + +
+
+
+
+
+ + limits.cpu: 0 cores/0.3 cores + +
+
+
+

+ 3mo + +

+
+ + test + +
- - requests.cpu: 0.5 cores/0.2 cores - + + requests.cpu: 0.5 cores/0.2 cores + +
- -
+ +
+
+ + limits.cpu: 0 cores/0.3 cores + +
+
+
+

+ 3mo + +

+
+
+
+
+ +
+
+
- + 15 +
+ +
- - -

- 3mo - -

- - - - + 1-5 of 5 + +
+ + + + + + +
+
+
+
+ diff --git a/frontend/src/components/role/BindingList.tsx b/frontend/src/components/role/BindingList.tsx index 0002cd50a10..1a6aedb5adf 100644 --- a/frontend/src/components/role/BindingList.tsx +++ b/frontend/src/components/role/BindingList.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { useTranslation } from 'react-i18next'; import ClusterRoleBinding from '../../lib/k8s/clusterRoleBinding'; -import RoleBinding, { KubeRoleBinding } from '../../lib/k8s/roleBinding'; +import RoleBinding from '../../lib/k8s/roleBinding'; import { useErrorState, useFilterFunc } from '../../lib/util'; import { Link } from '../common'; import LabelListItem from '../common/LabelListItem'; @@ -35,7 +35,7 @@ export default function RoleBindingList() { const [clusterRoleBindingError, onClusterRoleBindingError] = useErrorState(setupClusterRoleBindings); const { t } = useTranslation(['glossary', 'translation']); - const filterFunc = useFilterFunc(['.jsonData.kind']); + const filterFunc = useFilterFunc(['.jsonData.kind']); function setRoleBindings(newBindings: RoleBinding[] | null, kind: string) { setBindings(currentBindings => ({ ...currentBindings, [kind]: newBindings })); @@ -74,22 +74,24 @@ export default function RoleBindingList() { return null; } - function sortBindings(kind: string, r1: RoleBinding, r2: RoleBinding) { - const groups1 = r1?.subjects - ?.filter(subject => subject.kind === kind) - .map(subject => subject.name); - const groups2 = r2?.subjects - ?.filter(subject => subject.kind === kind) - .map(subject => subject.name); - if (groups1 && groups2) { - return groups1.join('').localeCompare(groups2.join('')); - } else if (groups1) { - return 1; - } else if (groups2) { - return -1; - } else { - return 0; - } + function sortBindings(kind: string) { + return function (r1: RoleBinding, r2: RoleBinding) { + const groups1 = r1?.subjects + ?.filter(subject => subject.kind === kind) + .map(subject => subject.name); + const groups2 = r2?.subjects + ?.filter(subject => subject.kind === kind) + .map(subject => subject.name); + if (groups1 && groups2) { + return groups1.join('').localeCompare(groups2.join('')); + } else if (groups1) { + return 1; + } else if (groups2) { + return -1; + } else { + return 0; + } + }; } RoleBinding.useApiList(setupRoleBindings, onRoleBindingError); @@ -106,7 +108,8 @@ export default function RoleBindingList() { { id: 'namespace', label: t('glossary|Namespace'), - getter: item => + getValue: item => item.getNamespace() ?? t('translation|All namespaces'), + render: item => item.getNamespace() ? ( {item.getNamespace()} @@ -114,18 +117,22 @@ export default function RoleBindingList() { ) : ( t('translation|All namespaces') ), - sort: true, }, { id: 'role', label: t('glossary|Role'), - getter: item => , - sort: true, + getValue: item => item.roleRef.name, + render: item => , }, { id: 'users', label: t('translation|Users'), - getter: (item: KubeRoleBinding) => ( + getValue: item => + item?.subjects + ?.filter(s => s.kind === 'User') + ?.map(s => s.name) + ?.join(' '), + render: item => ( ), - sort: (r1, r2) => sortBindings('User', r1, r2), + sort: sortBindings('User'), }, { id: 'groups', label: t('glossary|Groups'), - getter: (item: KubeRoleBinding) => ( + getValue: item => + item?.subjects + ?.filter(subject => subject.kind === 'Group') + ?.map(subject => subject.name) + ?.join(' '), + render: item => ( ), - sort: (r1, r2) => sortBindings('Group', r1, r2), + sort: sortBindings('Group'), }, { id: 'serviceaccounts', label: t('glossary|Service Accounts'), - getter: (item: KubeRoleBinding) => ( + getValue: item => + item?.subjects + ?.filter(subject => subject.kind === 'ServiceAccount') + ?.map(subject => subject.name) + ?.join(' '), + render: item => ( ), - sort: (r1, r2) => sortBindings('ServiceAccount', r1, r2), + sort: sortBindings('Service Accounts'), }, 'age', ]} diff --git a/frontend/src/components/role/List.tsx b/frontend/src/components/role/List.tsx index b5c801c8bd7..8463ead2ea9 100644 --- a/frontend/src/components/role/List.tsx +++ b/frontend/src/components/role/List.tsx @@ -15,7 +15,7 @@ export default function RoleList() { const [roleError, setRolesError] = useErrorState(setupRoles); const [clusterRoleError, setClusterRolesError] = useErrorState(setupClusterRoles); const { t } = useTranslation('glossary'); - const filterFunc = useFilterFunc(['.jsonData.kind']); + const filterFunc = useFilterFunc(['.jsonData.kind']); function setupRolesWithKind(newRoles: Role[] | null, kind: string) { setRoles(oldRoles => ({ ...(oldRoles || {}), [kind]: newRoles })); @@ -67,7 +67,8 @@ export default function RoleList() { 'type', { label: t('translation|Name'), - getter: item => ( + getValue: item => item.metadata.namespace, + render: item => ( ), - sort: (r1: Role, r2: Role) => { - if (r1.metadata.name < r2.metadata.name) { - return -1; - } else if (r1.metadata.name > r2.metadata.name) { - return 1; - } - return 0; - }, }, 'namespace', 'age', diff --git a/frontend/src/components/runtimeClass/List.tsx b/frontend/src/components/runtimeClass/List.tsx index 2faf53ce098..2f596d21e4c 100644 --- a/frontend/src/components/runtimeClass/List.tsx +++ b/frontend/src/components/runtimeClass/List.tsx @@ -14,7 +14,7 @@ export function RuntimeClassList() { { id: 'handler', label: t('translation|Handler'), - getter: item => item?.jsonData?.handler, + getValue: item => item?.jsonData?.handler, }, 'age', ]} diff --git a/frontend/src/components/runtimeClass/__snapshots__/List.stories.storyshot b/frontend/src/components/runtimeClass/__snapshots__/List.stories.storyshot index adbb41fc02c..f428c02c60e 100644 --- a/frontend/src/components/runtimeClass/__snapshots__/List.stories.storyshot +++ b/frontend/src/components/runtimeClass/__snapshots__/List.stories.storyshot @@ -33,45 +33,17 @@ exports[`Storyshots RuntimeClass/ListView Items 1`] = `
-
-
-
- -
-
- -
-
-
+ + +
@@ -80,97 +52,541 @@ exports[`Storyshots RuntimeClass/ListView Items 1`] = ` class="MuiBox-root css-1txv3mw" >
- - - - - - - - - + + + + + + + +
+
+ + + + + +
+ +
+
- Name -
- Handler - - Age -
- - + + + + - handler - - + + + + + +
- +
+ +
+ +
+
+ +
- runtime-class - - - + +
+ +
+ + +
+
+ +
+ +
+
+
+ + runtime-class + + + handler + +

+ 3mo + +

+
+
+
+
+ +
+
-

+ +

+ + + +
+
+ - 3mo - -

- - - - + 1-1 of 1 +
+
+ + + + + + +
+
+
+
+ diff --git a/frontend/src/components/secret/List.tsx b/frontend/src/components/secret/List.tsx index 9b601a52733..c0206a9f25f 100644 --- a/frontend/src/components/secret/List.tsx +++ b/frontend/src/components/secret/List.tsx @@ -4,7 +4,7 @@ import { useFilterFunc } from '../../lib/util'; import ResourceListView from '../common/Resource/ResourceListView'; export default function SecretList() { - const filterFunc = useFilterFunc(['.jsonData.type']); + const filterFunc = useFilterFunc(['.jsonData.type']); const { t } = useTranslation(['glossary', 'translation']); return ( @@ -18,14 +18,12 @@ export default function SecretList() { { id: 'type', label: t('translation|Type'), - getter: secret => secret.type, - sort: true, + getValue: secret => secret.type, }, { id: 'data', label: t('translation|Data'), - getter: (secret: Secret) => Object.keys(secret.data || {}).length || 0, - sort: true, + getValue: (secret: Secret) => Object.keys(secret.data || {}).length || 0, }, 'age', ]} diff --git a/frontend/src/components/secret/__snapshots__/List.stories.storyshot b/frontend/src/components/secret/__snapshots__/List.stories.storyshot index 7e8ef8cbaec..0337b3ad330 100644 --- a/frontend/src/components/secret/__snapshots__/List.stories.storyshot +++ b/frontend/src/components/secret/__snapshots__/List.stories.storyshot @@ -33,45 +33,17 @@ exports[`Storyshots Secret/ListView Items 1`] = `
-
-
-
- -
-
- -
-
-
+ + +
@@ -80,204 +52,764 @@ exports[`Storyshots Secret/ListView Items 1`] = ` class="MuiBox-root css-1txv3mw" >
- - - - - - - - - - - + + + +
+
- Name - - - Namespace - - +
+
+ +
+
+ + + + + + +
+ +
+
- Type -
- Data - - Age -
- - - - - + + - + +
+ +
+ + + + + - - + - - + + + + + - 3 - - + + + + + + + +
- - my-pvc - - - + +
+ +
+ + +
- default - - - - bla - - 0 - -

+

+
+ +
+ + +
+
+ +
+ +
+
+
+
+ +
+ +
+
+
- 3mo - -

- -
- - my-pvc - - - + my-pvc + + - default - - - test - + default + + + bla + + 0 + +

+ 3mo + +

+
+ + my-pvc + + + + default + + + test + + 3 + +

+ 3mo + +

+
+
+
+
+ +
+
-

+ +

+ + + +
+
+ + 1-2 of 2 + +
- 3mo - -

- - - - + + + + + + +
+
+
+
+ diff --git a/frontend/src/components/service/List.tsx b/frontend/src/components/service/List.tsx index 2edb4df507f..ca63ebf413e 100644 --- a/frontend/src/components/service/List.tsx +++ b/frontend/src/components/service/List.tsx @@ -16,32 +16,29 @@ export default function ServiceList() { { id: 'type', label: t('translation|Type'), - getter: service => service.spec.type, - sort: true, + getValue: service => service.spec.type, }, { id: 'clusterIP', label: t('Cluster IP'), - getter: service => service.spec.clusterIP, - sort: true, + getValue: service => service.spec.clusterIP, }, { id: 'externalIP', label: t('External IP'), - getter: service => service.getExternalAddresses(), - sort: true, + getValue: service => service.getExternalAddresses(), }, { id: 'ports', label: t('Ports'), - getter: service => , - sort: true, + getValue: service => service.getPorts().join(', '), + render: service => , }, { id: 'selector', label: t('Selector'), - getter: service => , - sort: true, + getValue: service => service.getSelector().join(', '), + render: service => , }, 'age', ]} diff --git a/frontend/src/components/serviceaccount/List.tsx b/frontend/src/components/serviceaccount/List.tsx index bdf9302a577..471eefe6827 100644 --- a/frontend/src/components/serviceaccount/List.tsx +++ b/frontend/src/components/serviceaccount/List.tsx @@ -15,9 +15,7 @@ export default function ServiceAccountList() { { id: 'secrets', label: t('Secrets'), - getter: (serviceaccount: ServiceAccount) => serviceaccount?.secrets?.length || 0, - sort: true, - gridTemplate: 0.5, + getValue: (serviceaccount: ServiceAccount) => serviceaccount?.secrets?.length || 0, }, 'age', ]} diff --git a/frontend/src/components/statefulset/List.tsx b/frontend/src/components/statefulset/List.tsx index 58a6c084afd..8eb6b424026 100644 --- a/frontend/src/components/statefulset/List.tsx +++ b/frontend/src/components/statefulset/List.tsx @@ -23,21 +23,18 @@ export default function StatefulSetList() { { id: 'pods', label: t('Pods'), - getter: statefulSet => renderPods(statefulSet), - gridTemplate: 0.6, - sort: true, + getValue: statefulSet => renderPods(statefulSet), }, { id: 'replicas', label: t('Replicas'), - getter: statefulSet => statefulSet.spec.replicas, - gridTemplate: 0.6, - sort: true, + getValue: statefulSet => statefulSet.spec.replicas, }, { id: 'containers', label: t('Containers'), - getter: statefulSet => { + getValue: statefulSet => statefulSet.containerNames.join(', '), + render: statefulSet => { const containerNames = statefulSet.getContainers().map((c: KubeContainer) => c.name); const containerTooltip = containerNames.join('\n'); const containerText = containerNames.join(', '); @@ -52,7 +49,12 @@ export default function StatefulSetList() { { id: 'images', label: t('Images'), - getter: statefulSet => { + getValue: statefulSet => + statefulSet + .getContainers() + .map(it => it.image) + .join(', '), + render: statefulSet => { const containerImages = statefulSet.getContainers().map((c: KubeContainer) => c.image); const containerTooltip = containerImages.join('\n'); const containerText = containerImages.join(', '); diff --git a/frontend/src/components/storage/ClaimList.tsx b/frontend/src/components/storage/ClaimList.tsx index bfea60cdcc6..8d609a8bf2c 100644 --- a/frontend/src/components/storage/ClaimList.tsx +++ b/frontend/src/components/storage/ClaimList.tsx @@ -18,7 +18,8 @@ export default function VolumeClaimList() { { id: 'className', label: t('Class Name'), - getter: volumeClaim => { + getValue: volumeClaim => volumeClaim.spec.storageClassName, + render: volumeClaim => { const name = volumeClaim.spec.storageClassName; if (!name) { return ''; @@ -29,32 +30,28 @@ export default function VolumeClaimList() { ); }, - sort: (v1, v2) => v1.spec.storageClassName.localeCompare(v2.spec.storageClassName), }, { id: 'capacity', label: t('Capacity'), - getter: volumeClaim => volumeClaim.status.capacity?.storage, - sort: true, - gridTemplate: 0.8, + getValue: volumeClaim => volumeClaim.status.capacity?.storage, }, { id: 'accessModes', label: t('Access Modes'), - getter: volumeClaim => , - sort: (v1, v2) => - v1.spec.accessModes.join('').localeCompare(v2.spec.accessModes.join('')), + getValue: volumeClaim => volumeClaim.spec.accessModes.join(', '), + render: volumeClaim => , }, { id: 'volumeMode', label: t('Volume Mode'), - getter: volumeClaim => volumeClaim.spec.volumeMode, - sort: true, + getValue: volumeClaim => volumeClaim.spec.volumeMode, }, { id: 'volume', label: t('Volume'), - getter: volumeClaim => { + getValue: volumeClaim => volumeClaim.spec.volumeName, + render: volumeClaim => { const name = volumeClaim.spec.volumeName; if (!name) { return ''; @@ -69,9 +66,8 @@ export default function VolumeClaimList() { { id: 'status', label: t('translation|Status'), - getter: volume => makePVCStatusLabel(volume), - gridTemplate: 0.3, - sort: true, + getValue: volume => volume.status.phase, + render: volume => makePVCStatusLabel(volume), }, 'age', ]} diff --git a/frontend/src/components/storage/ClassList.tsx b/frontend/src/components/storage/ClassList.tsx index 07a475be832..d9c07df2ce2 100644 --- a/frontend/src/components/storage/ClassList.tsx +++ b/frontend/src/components/storage/ClassList.tsx @@ -17,26 +17,22 @@ export default function ClassList() { { id: 'provisioner', label: t('Provisioner'), - getter: storageClass => storageClass.provisioner, - sort: true, + getValue: storageClass => storageClass.provisioner, }, { id: 'reclaimPolicy', label: t('Reclaim Policy'), - getter: storageClass => storageClass.reclaimPolicy, - sort: true, + getValue: storageClass => storageClass.reclaimPolicy, }, { id: 'volumeBindingMode', label: t('Volume Binding Mode'), - getter: storageClass => storageClass.volumeBindingMode, - sort: true, + getValue: storageClass => storageClass.volumeBindingMode, }, { id: 'allowVolumeExpansion', label: t('Allow Volume Expansion'), - getter: storageClass => storageClass.allowVolumeExpansion, - sort: true, + getValue: storageClass => storageClass.allowVolumeExpansion, }, 'age', ]} diff --git a/frontend/src/components/storage/VolumeList.tsx b/frontend/src/components/storage/VolumeList.tsx index d326568164d..b56e116e3c7 100644 --- a/frontend/src/components/storage/VolumeList.tsx +++ b/frontend/src/components/storage/VolumeList.tsx @@ -20,7 +20,8 @@ export default function VolumeList() { { id: 'className', label: t('Class Name'), - getter: volume => { + getValue: volume => volume.spec.storageClassName ?? '', + render: volume => { const name = volume.spec.storageClassName; if (!name) { return ''; @@ -31,40 +32,38 @@ export default function VolumeList() { ); }, - sort: (v1, v2) => v1.spec.storageClassName.localeCompare(v2.spec.storageClassName), }, { id: 'capacity', label: t('Capacity'), - getter: volume => volume.spec.capacity.storage, - sort: true, + getValue: volume => volume.spec.capacity.storage, }, { id: 'accessModes', label: t('Access Modes'), - getter: volume => , - sort: true, + getValue: volume => volume?.spec?.accesModes?.join(', '), + render: volume => , }, { id: 'reclaimPolicy', label: t('Reclaim Policy'), - getter: volume => volume.spec.persistentVolumeReclaimPolicy, - sort: true, + getValue: volume => volume.spec.persistentVolumeReclaimPolicy, }, { id: 'reason', label: t('translation|Reason'), - getter: volume => { + getValue: volume => volume.status.reason, + render: volume => { const reason = volume.status.reason; return {reason}; }, show: false, - sort: true, }, { id: 'claim', label: t('Claim'), - getter: volume => { + getValue: volume => volume.spec?.claimRef?.name ?? '', + render: volume => { const claim = volume.spec.claimRef?.name; if (!claim) { return null; @@ -81,14 +80,12 @@ export default function VolumeList() { ); }, - sort: true, }, { id: 'status', label: t('translation|Status'), - getter: volume => makePVStatusLabel(volume), - gridTemplate: 0.3, - sort: true, + getValue: volume => volume.status?.phase, + render: volume => makePVStatusLabel(volume), }, 'age', ]} diff --git a/frontend/src/components/storage/__snapshots__/ClaimList.stories.storyshot b/frontend/src/components/storage/__snapshots__/ClaimList.stories.storyshot index ddb5707d69a..a7b3a2f0789 100644 --- a/frontend/src/components/storage/__snapshots__/ClaimList.stories.storyshot +++ b/frontend/src/components/storage/__snapshots__/ClaimList.stories.storyshot @@ -33,45 +33,17 @@ exports[`Storyshots PersistentVolumeClaim/ListView Items 1`] = `
-
-
-
- -
-
- -
-
-
+ + +
@@ -80,195 +52,860 @@ exports[`Storyshots PersistentVolumeClaim/ListView Items 1`] = ` class="MuiBox-root css-1txv3mw" >
- - - - - - - - - - - - - - - - - - -
- Name - - +
+
+ +
+
+ + + + + + +
+ +
+
- Namespace -
- Class Name - - Capacity - + + + +
+ + + - Access Modes - - - + - - + - + - - - - + +
+ +
+ + + + + + + + + + - - - + - - - - + + + + + + + + - - pvc-abc-1234 + no-storage-class-name-pvc - - - - + - - - - - - - - - + + + + + + - - pvc-abc-1234 + no-volume-name-pvc - - - - + - - + + + + + + + + + +
- Volume Mode - + + + - - - - - Volume - - Status - + + + - - - - - Age - + + + - - - -
+
+ +
+ +
+
+
+
+ +
+ +
+
+
+
+ +
+ +
+
+
+
+ +
+ +
+
+
+
+ +
+ +
+
+
- - my-pvc - - - - default - - - + + my-pvc + + default - - - 8Gi - - + - ReadWriteOnce - - - Filesystem - + + default + + + + 8Gi + + + ReadWriteOnce + + + Filesystem + + + + pvc-abc-1234 + + + + + Bound + + +

+ 3mo + +

+
- + - Bound - - -

+ default + +

- 3mo - -

-
- - no-storage-class-name-pvc - - - - default - - - - - 8Gi - - + - ReadWriteOnce - - - Filesystem - + + + ReadWriteOnce + + + Filesystem + + + + pvc-abc-1234 + + + + + Bound + + +

+ 3mo + +

+
- + - Bound - - -

+ default + +

- 3mo - -

-
+ 8Gi + + + ReadWriteOnce + + + Block + + + + + Bound + + +

+ 3mo + +

+
+
+
+
+ +
-
- - no-volume-name-pvc - - - - default - - - - - 8Gi - - - ReadWriteOnce - - - Block - - - + +
+ + + +
+ - Bound + 1-3 of 3 -
-

- 3mo - -

-
+ + + + + + +
+ + + + diff --git a/frontend/src/components/storage/__snapshots__/ClassList.stories.storyshot b/frontend/src/components/storage/__snapshots__/ClassList.stories.storyshot index 069cf77130c..7e856e41b2d 100644 --- a/frontend/src/components/storage/__snapshots__/ClassList.stories.storyshot +++ b/frontend/src/components/storage/__snapshots__/ClassList.stories.storyshot @@ -33,45 +33,17 @@ exports[`Storyshots StorageClass/ListView Items 1`] = `
-
-
-
- -
-
- -
-
-
+ + +
@@ -80,172 +52,788 @@ exports[`Storyshots StorageClass/ListView Items 1`] = ` class="MuiBox-root css-1txv3mw" >
- - - - - - - - +
-
- - - - - - -
- Name - - +
+
+ +
+
+ + + + + + +
+ +
+
- Provisioner -
- Reclaim Policy - - Volume Binding Mode - + + + +
+ + + - Allow Volume Expansion - - - + + + + + + + + + - Age - - - - - + my-pvc + + + + + + + + + +
+ +
+ +
+ +
+
+ +
+ +
+
+
+
+ +
+ +
+
+
+
+ +
+ +
+
+
+
+ +
+ +
+
+
+
+ +
+ +
+
+
+ csi.test + + Delete + + WaitForFirstConsumer + + +

+ 3mo + +

+
+
+
+
-
- - my-pvc - - - csi.test - - Delete - - WaitForFirstConsumer - - -

+ +

+ + + +
+ + + 1-1 of 1 + +
- 3mo - -

-
+ + + + + + +
+ + + + diff --git a/frontend/src/components/storage/__snapshots__/VolumeList.stories.storyshot b/frontend/src/components/storage/__snapshots__/VolumeList.stories.storyshot index aecf5fbb451..c1f6fa6b2ef 100644 --- a/frontend/src/components/storage/__snapshots__/VolumeList.stories.storyshot +++ b/frontend/src/components/storage/__snapshots__/VolumeList.stories.storyshot @@ -33,45 +33,17 @@ exports[`Storyshots PersistentVolume/ListView Items 1`] = `
-
-
-
- -
-
- -
-
-
+ + +
@@ -80,172 +52,788 @@ exports[`Storyshots PersistentVolume/ListView Items 1`] = ` class="MuiBox-root css-1txv3mw" >
- - - - - - - - +
-
- - - - - - -
- Name - - +
+
+ +
+
+ + + + + + +
+ +
+
- Provisioner -
- Reclaim Policy - - Volume Binding Mode - + + + +
+ + + - Allow Volume Expansion - - - + + + + + + + + + - Age - - - - - + my-pvc + + + + + + + + + +
+ +
+ +
+ +
+
+ +
+ +
+
+
+
+ +
+ +
+
+
+
+ +
+ +
+
+
+
+ +
+ +
+
+
+
+ +
+ +
+
+
+ csi.test + + Delete + + WaitForFirstConsumer + + +

+ 3mo + +

+
+
+
+
-
- - my-pvc - - - csi.test - - Delete - - WaitForFirstConsumer - - -

+ +

+ + + +
+ + + 1-1 of 1 + +
- 3mo - -

-
+ + + + + + +
+ + + + diff --git a/frontend/src/components/verticalPodAutoscaler/List.tsx b/frontend/src/components/verticalPodAutoscaler/List.tsx index d6ded38f3ec..49e1a91dc12 100644 --- a/frontend/src/components/verticalPodAutoscaler/List.tsx +++ b/frontend/src/components/verticalPodAutoscaler/List.tsx @@ -40,18 +40,17 @@ export default function VpaList() { { id: 'cpu', label: t('glossary|CPU'), - getter: item => item?.targetRecommendations?.cpu, + getValue: item => item?.targetRecommendations?.cpu ?? null, }, { id: 'memory', label: t('glossary|Memory'), - getter: item => item?.targetRecommendations?.memory, + getValue: item => item?.targetRecommendations?.memory ?? null, }, { id: 'provided', label: t('translation|Provided'), - getter: item => item?.status?.conditions?.[0]?.status, - sort: true, + getValue: item => item?.status?.conditions?.[0]?.status ?? null, }, 'age', ]} diff --git a/frontend/src/components/verticalPodAutoscaler/__snapshots__/VPAList.stories.storyshot b/frontend/src/components/verticalPodAutoscaler/__snapshots__/VPAList.stories.storyshot index 755eb1d1484..b2bcdcf5539 100644 --- a/frontend/src/components/verticalPodAutoscaler/__snapshots__/VPAList.stories.storyshot +++ b/frontend/src/components/verticalPodAutoscaler/__snapshots__/VPAList.stories.storyshot @@ -33,45 +33,17 @@ exports[`Storyshots VPA/VPAListView List 1`] = `
-
-
-
- -
-
- -
-
-
+ + +
@@ -80,365 +52,1027 @@ exports[`Storyshots VPA/VPAListView List 1`] = ` class="MuiBox-root css-1txv3mw" >
- - - - - - - - - - - - + + + +
+
- Name - - +
+
+ +
+
+ + + + + + +
+ +
+
- Namespace -
- CPU - - Memory - - Provided - - Age -
- - - - - - - - - - - - - - - - - - - - - - + + - + +
+ +
+ + + + + + - - - + + + + + + + - 1 - - + + + + + + + - 2Gi - - + + + + + + + - True - - + + + + + + + -

+ + multi-container-vpa + + +

+ + + - - + + + + +
- - multi-container-vpa - - - - default - - - 1 - - 2Gi - - True - -

- 3mo - -

-
- - multi-container-vpa - - - - default - - - 1 - - 2Gi - - True - -

+

+
+ +
+ + +
- 3mo - -

- -
- - multi-container-vpa - - - - default - - - 1 - - 2Gi - - True - -

+

+
+ +
+ + +
+
+ +
+ +
+
+
+
+ +
+ +
+
+
- 3mo - -

- -
+
+ +
+ +
+
+
- - multi-container-vpa - - - - default - - + + multi-container-vpa + + + + default + + + 1 + + 2Gi + + True + +

+ 3mo + +

+
+ + multi-container-vpa + + + + default + + + 1 + + 2Gi + + True + +

+ 3mo + +

+
+ + multi-container-vpa + + + + default + + + 1 + + 2Gi + + True + +

+ 3mo + +

+
+ + multi-container-vpa + + + + default + + + 1 + + 2Gi + + True + +

+ 3mo + +

+
+ + default + + + 1 + + 2Gi + - 3mo - -

-
+

+ 3mo + +

+
+
+
+
+ +
- - - multi-container-vpa - - - - - default - - - - 1 - - - 2Gi - - - True - - -

+ +

+ + + +
+
+ + 1-5 of 5 + +
- 3mo - -

- - - - + + + + + + +
+
+
+ + diff --git a/frontend/src/components/webhookconfiguration/MutatingWebhookConfigList.tsx b/frontend/src/components/webhookconfiguration/MutatingWebhookConfigList.tsx index f1563285f5f..7ca856f734c 100644 --- a/frontend/src/components/webhookconfiguration/MutatingWebhookConfigList.tsx +++ b/frontend/src/components/webhookconfiguration/MutatingWebhookConfigList.tsx @@ -14,7 +14,7 @@ export default function MutatingWebhookConfigurationList() { { id: 'webhooks', label: t('Webhooks'), - getter: mutatingWebhookConfig => mutatingWebhookConfig.webhooks?.length || 0, + getValue: mutatingWebhookConfig => mutatingWebhookConfig.webhooks?.length || 0, }, 'age', ]} diff --git a/frontend/src/components/webhookconfiguration/ValidatingWebhookConfigList.tsx b/frontend/src/components/webhookconfiguration/ValidatingWebhookConfigList.tsx index 75d85c54053..6123dde71b6 100644 --- a/frontend/src/components/webhookconfiguration/ValidatingWebhookConfigList.tsx +++ b/frontend/src/components/webhookconfiguration/ValidatingWebhookConfigList.tsx @@ -14,7 +14,7 @@ export default function ValidatingWebhookConfigurationList() { { id: 'webhooks', label: t('Webhooks'), - getter: mutatingWebhookConfig => mutatingWebhookConfig.webhooks?.length || 0, + getValue: mutatingWebhookConfig => mutatingWebhookConfig.webhooks?.length || 0, }, 'age', ]} diff --git a/frontend/src/components/webhookconfiguration/__snapshots__/MutatingWebhookConfigList.stories.storyshot b/frontend/src/components/webhookconfiguration/__snapshots__/MutatingWebhookConfigList.stories.storyshot index fbf7dd7268c..73a52546a63 100644 --- a/frontend/src/components/webhookconfiguration/__snapshots__/MutatingWebhookConfigList.stories.storyshot +++ b/frontend/src/components/webhookconfiguration/__snapshots__/MutatingWebhookConfigList.stories.storyshot @@ -36,45 +36,17 @@ exports[`Storyshots WebhookConfiguration/MutatingWebhookConfig/List Items 1`] =
-
-
-
- -
-
- -
-
-
+ + +
@@ -83,257 +55,716 @@ exports[`Storyshots WebhookConfiguration/MutatingWebhookConfig/List Items 1`] = class="MuiBox-root css-1txv3mw" >
- - - - - - - - - + + + +
+
+ + + + + +
+ +
+
- Name + -
- Webhooks - - Age -
- - - - - + +
+ +
+ + + + + + - - - + - - - - - + + -

- 3mo - -

- - - - + - - + + -

- 3mo - -

- - - - + + + + - + + mwc-test-with-service-0 + + + + - + + - 1 - - + + + + -

+ + mwc-test-with-service-2 + + +

+ - - + 3mo + +

+ +
+ + +
- - mwc-test-0 - - - 1 - -

+

+
+ +
+ + +
- 3mo - -

- -
+
+ +
+ +
+
+
- - mwc-test-1 - - - 1 - -

+ mwc-test-0 + +

- 3mo - -

-
- + - mwc-test-2 - - - 1 - + 3mo + +

+
- + mwc-test-1 + + - mwc-test-with-service-0 - - - 1 - + +

+ 3mo + +

+
+ mwc-test-2 + + + 1 + +

+ 3mo + +

+
+ 1 + - mwc-test-with-service-1 - - + 3mo + +

+
+ + mwc-test-with-service-1 + + + 1 + +

+ 3mo + +

+
+ 1 + - 3mo - -

-
+
+
+
+ +
- - - mwc-test-with-service-2 - - - - 1 - - -

+ Rows per page + +

+ + + +
+
+ - 3mo - -

- - - - + 1-6 of 6 +
+
+ + + + + + +
+
+
+ + diff --git a/frontend/src/components/webhookconfiguration/__snapshots__/ValidatingWebhookConfigList.stories.storyshot b/frontend/src/components/webhookconfiguration/__snapshots__/ValidatingWebhookConfigList.stories.storyshot index b8a0a0588bd..45698fdd24c 100644 --- a/frontend/src/components/webhookconfiguration/__snapshots__/ValidatingWebhookConfigList.stories.storyshot +++ b/frontend/src/components/webhookconfiguration/__snapshots__/ValidatingWebhookConfigList.stories.storyshot @@ -36,45 +36,17 @@ exports[`Storyshots WebhookConfiguration/ValidatingWebhookConfig/List Items 1`]
-
-
-
- -
-
- -
-
-
+ + +
@@ -83,257 +55,716 @@ exports[`Storyshots WebhookConfiguration/ValidatingWebhookConfig/List Items 1`] class="MuiBox-root css-1txv3mw" >
- - - - - - - - - + + + +
+
+ + + + + +
+ +
+
- Name + -
- Webhooks - - Age -
- - - - - + +
+ +
+ + + + + + - - - + - - - - - + + -

- 3mo - -

- - - - + - - + + -

- 3mo - -

- - - - + + + + - + + vwc-test-with-service-0 + + + + - + + - 1 - - + + + + -

+ + vwc-test-with-service-2 + + +

+ - - + 3mo + +

+ +
+ + +
- - vwc-test-0 - - - 1 - -

+

+
+ +
+ + +
- 3mo - -

- -
+
+ +
+ +
+
+
- - vwc-test-1 - - - 1 - -

+ vwc-test-0 + +

- 3mo - -

-
- + - vwc-test-2 - - - 1 - + 3mo + +

+
- + vwc-test-1 + + - vwc-test-with-service-0 - - - 1 - + +

+ 3mo + +

+
+ vwc-test-2 + + + 1 + +

+ 3mo + +

+
+ 1 + - vwc-test-with-service-1 - - + 3mo + +

+
+ + vwc-test-with-service-1 + + + 1 + +

+ 3mo + +

+
+ 1 + - 3mo - -

-
+
+
+
+ +
- - - vwc-test-with-service-2 - - - - 1 - - -

+ Rows per page + +

+ + + +
+
+ - 3mo - -

- - - - + 1-6 of 6 +
+
+ + + + + + +
+
+
+ + diff --git a/frontend/src/components/workload/Overview.tsx b/frontend/src/components/workload/Overview.tsx index de49cec30b3..7b569657d0a 100644 --- a/frontend/src/components/workload/Overview.tsx +++ b/frontend/src/components/workload/Overview.tsx @@ -25,7 +25,7 @@ interface WorkloadDict { export default function Overview() { const [workloadsData, setWorkloadsData] = React.useState({}); const location = useLocation(); - const filterFunc = useFilterFunc(['.jsonData.kind']); + const filterFunc = useFilterFunc(['.jsonData.kind']); const { t } = useTranslation('glossary'); const cluster = useCluster(); @@ -119,21 +119,16 @@ export default function Overview() { { id: 'name', label: t('translation|Name'), - getter: item => , - sort: (w1: Workload, w2: Workload) => { - if (w1.metadata.name < w2.metadata.name) { - return -1; - } else if (w1.metadata.name > w2.metadata.name) { - return 1; - } - return 0; - }, + getValue: item => item.metadata.name, + render: item => ( + + ), }, 'namespace', { id: 'pods', label: t('Pods'), - getter: item => item && getPods(item), + getValue: item => item && getPods(item), sort: sortByReplicas, }, 'age', diff --git a/frontend/src/i18n/locales/de/glossary.json b/frontend/src/i18n/locales/de/glossary.json index 29dd77b265c..e63233ba42c 100644 --- a/frontend/src/i18n/locales/de/glossary.json +++ b/frontend/src/i18n/locales/de/glossary.json @@ -62,8 +62,8 @@ "Endpoints": "Endpunkte", "Horizontal Pod Autoscalers": "Horizontaler Pod-Autoskalierer", "Ingress Classes": "Ingress Classes", - "Controller": "Controller", "Default Ingress Class": "Standard Ingress Class", + "Controller": "Controller", "Default Backend": "Standard-Backend", "Class Name": "Name der Klasse", "Rules": "Regeln", diff --git a/frontend/src/i18n/locales/de/translation.json b/frontend/src/i18n/locales/de/translation.json index 55f7e4aa292..68b1f250ed2 100644 --- a/frontend/src/i18n/locales/de/translation.json +++ b/frontend/src/i18n/locales/de/translation.json @@ -215,7 +215,6 @@ "Init Containers": "Init-Container", "Conditions": "Bedingungen", "Volumes": "Speicher-Volumes", - "Change columns displayed": "Angezeigte Spalten ändern", "Restarting {{ itemName }}…": "Starte {{ itemName }} neu…", "Cancelled restarting {{ itemName }}.": "Der Neustart von {{ itemName }} wurde abgebrochen.", "Restarted {{ itemName }}.": "{{ itemName }} neu gestartet.", diff --git a/frontend/src/i18n/locales/en/glossary.json b/frontend/src/i18n/locales/en/glossary.json index fef4b151c50..565f0c3f101 100644 --- a/frontend/src/i18n/locales/en/glossary.json +++ b/frontend/src/i18n/locales/en/glossary.json @@ -62,8 +62,8 @@ "Endpoints": "Endpoints", "Horizontal Pod Autoscalers": "Horizontal Pod Autoscalers", "Ingress Classes": "Ingress Classes", - "Controller": "Controller", "Default Ingress Class": "Default Ingress Class", + "Controller": "Controller", "Default Backend": "Default Backend", "Class Name": "Class Name", "Rules": "Rules", diff --git a/frontend/src/i18n/locales/en/translation.json b/frontend/src/i18n/locales/en/translation.json index 93cbc80f624..f820e967192 100644 --- a/frontend/src/i18n/locales/en/translation.json +++ b/frontend/src/i18n/locales/en/translation.json @@ -215,7 +215,6 @@ "Init Containers": "Init Containers", "Conditions": "Conditions", "Volumes": "Volumes", - "Change columns displayed": "Change columns displayed", "Restarting {{ itemName }}…": "Restarting {{ itemName }}…", "Cancelled restarting {{ itemName }}.": "Cancelled restarting {{ itemName }}.", "Restarted {{ itemName }}.": "Restarted {{ itemName }}.", diff --git a/frontend/src/i18n/locales/es/glossary.json b/frontend/src/i18n/locales/es/glossary.json index 79c2b0a2c32..9856b221811 100644 --- a/frontend/src/i18n/locales/es/glossary.json +++ b/frontend/src/i18n/locales/es/glossary.json @@ -62,8 +62,8 @@ "Endpoints": "Endpoints", "Horizontal Pod Autoscalers": "Horizontal Pod Autoscalers", "Ingress Classes": "Ingress Classes", - "Controller": "Controller", "Default Ingress Class": "Default Ingress Class", + "Controller": "Controller", "Default Backend": "Backend por defecto", "Class Name": "Class Name", "Rules": "Reglas", diff --git a/frontend/src/i18n/locales/es/translation.json b/frontend/src/i18n/locales/es/translation.json index 2150d464c9c..03970791fbd 100644 --- a/frontend/src/i18n/locales/es/translation.json +++ b/frontend/src/i18n/locales/es/translation.json @@ -215,7 +215,6 @@ "Init Containers": "Contenedores de Inicialización", "Conditions": "Condiciones", "Volumes": "Volumenes", - "Change columns displayed": "Cambiar columnas mostradas", "Restarting {{ itemName }}…": "Reiniciando {{ itemName }}…", "Cancelled restarting {{ itemName }}.": "Se ha cancelado el reinicio de {{ itemName }}.", "Restarted {{ itemName }}.": "Se ha reiniciado {{ itemName }}.", diff --git a/frontend/src/i18n/locales/fr/glossary.json b/frontend/src/i18n/locales/fr/glossary.json index e3bbccaed33..79557830a63 100644 --- a/frontend/src/i18n/locales/fr/glossary.json +++ b/frontend/src/i18n/locales/fr/glossary.json @@ -62,8 +62,8 @@ "Endpoints": "Endpoints", "Horizontal Pod Autoscalers": "Horizontal Pod Autoscalers", "Ingress Classes": "Ingress Classes", - "Controller": "Controller", "Default Ingress Class": "Default Ingress Class", + "Controller": "Controller", "Default Backend": "Backend par défaut", "Class Name": "Nom de la classe", "Rules": "Règles", diff --git a/frontend/src/i18n/locales/fr/translation.json b/frontend/src/i18n/locales/fr/translation.json index 55fba5bf7f2..4fc00184664 100644 --- a/frontend/src/i18n/locales/fr/translation.json +++ b/frontend/src/i18n/locales/fr/translation.json @@ -215,7 +215,6 @@ "Init Containers": "Conteneurs d'initialisation", "Conditions": "Conditions", "Volumes": "Volumes", - "Change columns displayed": "Modifier les colonnes affichées", "Restarting {{ itemName }}…": "Redémarrage de {{ itemName }}…", "Cancelled restarting {{ itemName }}.": "Redémarrage de {{ itemName }} annulé.", "Restarted {{ itemName }}.": "{{ itemName }} redémarré.", diff --git a/frontend/src/i18n/locales/pt/glossary.json b/frontend/src/i18n/locales/pt/glossary.json index 097a74bab15..f6fa730de3c 100644 --- a/frontend/src/i18n/locales/pt/glossary.json +++ b/frontend/src/i18n/locales/pt/glossary.json @@ -62,8 +62,8 @@ "Endpoints": "Endpoints", "Horizontal Pod Autoscalers": "Horizontal Pod Autoscalers", "Ingress Classes": "Ingress Classes", - "Controller": "Controller", "Default Ingress Class": "Default Ingress Class", + "Controller": "Controller", "Default Backend": "Backend Padrão", "Class Name": "Class Name", "Rules": "Regras", diff --git a/frontend/src/i18n/locales/pt/translation.json b/frontend/src/i18n/locales/pt/translation.json index 5549c6e2ef1..d5afaae8b6b 100644 --- a/frontend/src/i18n/locales/pt/translation.json +++ b/frontend/src/i18n/locales/pt/translation.json @@ -215,7 +215,6 @@ "Init Containers": "Containers de Inicialização", "Conditions": "Condições", "Volumes": "Volumes", - "Change columns displayed": "Alterar colunas mostradas", "Restarting {{ itemName }}…": "A reiniciar {{ itemName }}…", "Cancelled restarting {{ itemName }}.": "Cancelado o reinício de {{ itemName }}.", "Restarted {{ itemName }}.": "{{ itemName }} foi reiniciado.", diff --git a/frontend/src/plugin/registry.tsx b/frontend/src/plugin/registry.tsx index bf8fd37a018..36dcf3c9fa2 100644 --- a/frontend/src/plugin/registry.tsx +++ b/frontend/src/plugin/registry.tsx @@ -391,9 +391,12 @@ export function registerDetailsViewHeaderActionsProcessor( * if (id === 'headlamp-pods') { * columns.push({ * label: 'Init Containers', - * getter: (pod: Pod) => { + * // return plain value to allow filtering and sorting + * getValue: (pod: Pod) => { * return pod.spec.initContainers.length; - * }, + * } + * // (optional) customise how the cell value is rendered + * render: (pod: Pod) =>
{pod.spec.initContainers.length}
* }); * } * diff --git a/plugins/examples/tables/src/index.tsx b/plugins/examples/tables/src/index.tsx index d1e8b2219cb..ade07cea45a 100644 --- a/plugins/examples/tables/src/index.tsx +++ b/plugins/examples/tables/src/index.tsx @@ -71,7 +71,8 @@ registerResourceTableColumnsProcessor(function setupContextMenuForPodsList({ id, if (id === 'headlamp-pods') { columns.push({ label: '', - getter: (pod: Pod) => { + getValue: (pod: Pod) => pod.getDetailsLink(), + render: (pod: Pod) => { return ; }, });