From a57e5f36fbfab339aa57509bb66f8e9719bcc357 Mon Sep 17 00:00:00 2001 From: Joaquim Rocha Date: Wed, 9 Nov 2022 11:38:09 +0000 Subject: [PATCH 1/7] frontend: Add node_modules to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index f2d137d7134..b5388e2c1eb 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ backend/tools app/electron/src/* docs/development/storybook/ .plugins +node_modules From 593aa3c8ef2d35397d4d7cb0de44b66a87106490 Mon Sep 17 00:00:00 2001 From: Joaquim Rocha Date: Wed, 9 Nov 2022 17:30:00 +0000 Subject: [PATCH 2/7] frontend: Add useURLState hook This hook works like the useState and reflects the state in the URL as well. The initial value of the state may be either the default value provided by the caller or the state in the URL. --- frontend/src/lib/util.ts | 115 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 114 insertions(+), 1 deletion(-) diff --git a/frontend/src/lib/util.ts b/frontend/src/lib/util.ts index 38acf9c9638..0fa8690ebea 100644 --- a/frontend/src/lib/util.ts +++ b/frontend/src/lib/util.ts @@ -1,7 +1,7 @@ import humanizeDuration from 'humanize-duration'; import { JSONPath } from 'jsonpath-plus'; import React from 'react'; -import { matchPath } from 'react-router'; +import { matchPath, useHistory } from 'react-router'; import helpers from '../helpers'; import { useTypedSelector } from '../redux/reducers/reducers'; import { ApiError } from './k8s/apiProxy'; @@ -234,6 +234,119 @@ export function useErrorState(dependentSetter?: (...args: any) => void) { return [error, setError as any]; } +type URLStateParams = { + /** The defaultValue for the URL state. */ + defaultValue: T; + /** Whether to hide the parameter when the value is the default one (true by default). */ + hideDefault?: boolean; + /** The prefix of the URL key to use for this state (a prefix 'my' with a key name 'key' will be used in the URL as 'my.key'). */ + prefix?: string; +}; +export function useURLState( + key: string, + defaultValue: number +): [number, React.Dispatch>]; +export function useURLState( + key: string, + valueOrParams: number | URLStateParams +): [number, React.Dispatch>]; +/** + * A hook to manage a state variable that is also stored in the URL. + * + * @param key The name of the key in the URL. If empty, then the hook behaves like useState. + * @param paramsOrDefault The default value of the state variable, or the params object. + * + */ +export function useURLState( + key: string, + paramsOrDefault: T | URLStateParams +): [T, React.Dispatch>] { + const params: URLStateParams = + typeof paramsOrDefault === 'object' ? paramsOrDefault : { defaultValue: paramsOrDefault }; + const { defaultValue, hideDefault = true, prefix = '' } = params; + const history = useHistory(); + // Don't even use the prefix if the key is empty + const fullKey = !key ? '' : !!prefix ? prefix + '.' + key : key; + + function getURLValue() { + // An empty key means that we don't want to use the state from the URL. + if (fullKey === '') { + return null; + } + + const urlParams = new URLSearchParams(history.location.search); + const urlValue = urlParams.get(fullKey); + if (urlValue === null) { + return null; + } + let newValue: string | number = urlValue; + if (typeof defaultValue === 'number') { + newValue = Number(urlValue); + if (newValue === NaN) { + return null; + } + } + + return newValue; + } + + const initialValue = React.useMemo(() => { + const newValue = getURLValue(); + if (newValue === null) { + return defaultValue; + } + return newValue; + }, []); + const [value, setValue] = React.useState(initialValue as T); + + React.useEffect( + () => { + const newValue = getURLValue(); + if (newValue === null) { + if (defaultValue !== undefined && defaultValue !== value) { + setValue(defaultValue); + } + } else if (newValue !== value) { + setValue(newValue as T); + } + }, + // eslint-disable-next-line + [history] + ); + + React.useEffect(() => { + // An empty key means that we don't want to use the state from the URL. + if (fullKey === '') { + return; + } + + const urlCurrentValue = getURLValue(); + + if (urlCurrentValue === value) { + return; + } + + const urlParams = new URLSearchParams(history.location.search); + let shouldUpdateURL = false; + + if ((value === null || value === defaultValue) && hideDefault) { + urlParams.delete(fullKey); + shouldUpdateURL = true; + } else if (value !== undefined) { + const urlValue = value as NonNullable; + + urlParams.set(fullKey, urlValue.toString()); + shouldUpdateURL = true; + } + + if (shouldUpdateURL) { + history.replace({ ...location, search: urlParams.toString() }); + } + }, [value]); + + return [value, setValue] as [T, React.Dispatch>]; +} + // Make units available from here export * as auth from './auth'; export * as units from './units'; From fd5f554bc5ecc8128bea58380fb1b571c8c34f4c Mon Sep 17 00:00:00 2001 From: Joaquim Rocha Date: Thu, 10 Nov 2022 11:41:44 +0000 Subject: [PATCH 3/7] frontend: Add reflectInURL property to the SimpleTable These changes allow the SimpleTable to reflect its page and rowsPerPage properties in the URL, so views can quickly set that up and make sure users can go back to previous views that reflect the table pages they were reading. --- .../src/components/common/SimpleTable.tsx | 63 +++++++++++++++++-- 1 file changed, 57 insertions(+), 6 deletions(-) diff --git a/frontend/src/components/common/SimpleTable.tsx b/frontend/src/components/common/SimpleTable.tsx index c7a4e77275f..3cf39997c36 100644 --- a/frontend/src/components/common/SimpleTable.tsx +++ b/frontend/src/components/common/SimpleTable.tsx @@ -11,6 +11,7 @@ import TableRow from '@material-ui/core/TableRow'; import React from 'react'; import { useTranslation } from 'react-i18next'; import helpers from '../../helpers'; +import { useURLState } from '../../lib/util'; import Empty from './EmptyContent'; import { ValueLabel } from './Label'; import Loader from './Loader'; @@ -76,6 +77,11 @@ export interface SimpleTableProps { errorMessage?: string | null; defaultSortingColumn?: number; noTableHeader?: boolean; + /** Whether to reflect the page/perPage properties in the URL. + * If assigned to a string, it will be the prefix for the page/perPage parameters. + * If true or '', it'll reflect the parameters without a prefix. + * By default, no parameters are reflected in the URL. */ + reflectInURL?: string | boolean; } interface ColumnSortButtonProps { @@ -106,6 +112,33 @@ function ColumnSortButtons(props: ColumnSortButtonProps) { ); } +// Use a zero-indexed "useURLState" hook, so pages are shown in the URL as 1-indexed +// but internally are 0-indexed. +function usePageURLState( + key: string, + prefix: string, + initialPage: number +): ReturnType { + const [page, setPage] = useURLState(key, { defaultValue: initialPage + 1, prefix }); + const [zeroIndexPage, setZeroIndexPage] = React.useState(initialPage); + + React.useEffect(() => { + setZeroIndexPage((zeroIndexPage: number) => { + if (page - 1 !== zeroIndexPage) { + return page - 1; + } + + return zeroIndexPage; + }); + }, [page]); + + React.useEffect(() => { + setPage(zeroIndexPage + 1); + }, [zeroIndexPage]); + + return [zeroIndexPage, setZeroIndexPage]; +} + export default function SimpleTable(props: SimpleTableProps) { const { columns, @@ -115,14 +148,22 @@ export default function SimpleTable(props: SimpleTableProps) { errorMessage = null, defaultSortingColumn, noTableHeader = false, + reflectInURL, } = props; - const [page, setPage] = React.useState(0); + const shouldReflectInURL = reflectInURL !== undefined && reflectInURL !== false; + const prefix = reflectInURL === true ? '' : reflectInURL || ''; + const [page, setPage] = usePageURLState(shouldReflectInURL ? 'p' : '', prefix, 0); const [currentData, setCurrentData] = React.useState(data); const [displayData, setDisplayData] = React.useState(data); const rowsPerPageOptions = props.rowsPerPage || [15, 25, 50]; - const [rowsPerPage, setRowsPerPage] = React.useState( - helpers.getTablesRowsPerPage(rowsPerPageOptions[0]) + const defaultRowsPerPage = React.useMemo( + () => helpers.getTablesRowsPerPage(rowsPerPageOptions[0]), + [] ); + const [rowsPerPage, setRowsPerPage] = useURLState(shouldReflectInURL ? 'perPage' : '', { + defaultValue: defaultRowsPerPage, + prefix, + }); const classes = useTableStyle(); const [isIncreasingOrder, setIsIncreasingOrder] = React.useState( !defaultSortingColumn || defaultSortingColumn > 0 @@ -137,6 +178,18 @@ export default function SimpleTable(props: SimpleTableProps) { setPage(newPage); } + // Protect against invalid page values + React.useEffect(() => { + if (page < 0) { + setPage(0); + return; + } + + if (displayData && page * rowsPerPage > displayData.length) { + setPage(Math.floor(displayData.length / rowsPerPage)); + } + }, [page, displayData, rowsPerPage]); + function handleChangeRowsPerPage( event: React.ChangeEvent | React.ChangeEvent ) { @@ -149,9 +202,6 @@ export default function SimpleTable(props: SimpleTableProps) { React.useEffect( () => { if (currentData === data) { - if (page !== 0) { - setPage(0); - } return; } @@ -270,6 +320,7 @@ export default function SimpleTable(props: SimpleTableProps) { startIcon={} onClick={() => { setCurrentData(data); + setPage(0); }} > {t('frequent|Refresh')} From b9c544edc6dc6e21a63ed70f1b620de251a2e514 Mon Sep 17 00:00:00 2001 From: Joaquim Rocha Date: Thu, 10 Nov 2022 17:02:15 +0000 Subject: [PATCH 4/7] frontend: Allow to use URL search params in the TestContext --- frontend/src/components/common/SimpleTable.tsx | 2 +- frontend/src/test/index.tsx | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/common/SimpleTable.tsx b/frontend/src/components/common/SimpleTable.tsx index 3cf39997c36..b0962cec82c 100644 --- a/frontend/src/components/common/SimpleTable.tsx +++ b/frontend/src/components/common/SimpleTable.tsx @@ -120,7 +120,7 @@ function usePageURLState( initialPage: number ): ReturnType { const [page, setPage] = useURLState(key, { defaultValue: initialPage + 1, prefix }); - const [zeroIndexPage, setZeroIndexPage] = React.useState(initialPage); + const [zeroIndexPage, setZeroIndexPage] = React.useState(page - 1); React.useEffect(() => { setZeroIndexPage((zeroIndexPage: number) => { diff --git a/frontend/src/test/index.tsx b/frontend/src/test/index.tsx index e70aa611be8..45871df9d65 100644 --- a/frontend/src/test/index.tsx +++ b/frontend/src/test/index.tsx @@ -7,10 +7,13 @@ import defaultStore from '../redux/stores/store'; export type TestContextProps = PropsWithChildren<{ store?: ReturnType; routerMap?: Record; + urlSearchParams?: { + [key: string]: string; + }; }>; export function TestContext(props: TestContextProps) { - const { store, routerMap, children } = props; + const { store, routerMap, urlSearchParams, children } = props; let url = ''; let routePath = ''; @@ -20,6 +23,10 @@ export function TestContext(props: TestContextProps) { url += '/' + value; } + if (!!urlSearchParams) { + url += '?' + new URLSearchParams(urlSearchParams).toString(); + } + return ( From 678196b8b0eaff086322f0fdb8d7f34b6b1ee3b1 Mon Sep 17 00:00:00 2001 From: Joaquim Rocha Date: Thu, 10 Nov 2022 14:52:07 +0000 Subject: [PATCH 5/7] frontend: Add page and showPagination props to Simpletable These will be useful for users and for testing. --- .../components/common/SimpleTable.stories.tsx | 100 +++++- .../src/components/common/SimpleTable.tsx | 10 +- .../SimpleTable.stories.storyshot | 294 ++++++++++++++++++ 3 files changed, 400 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/common/SimpleTable.stories.tsx b/frontend/src/components/common/SimpleTable.stories.tsx index 57a1ee03931..16d99b4a531 100644 --- a/frontend/src/components/common/SimpleTable.stories.tsx +++ b/frontend/src/components/common/SimpleTable.stories.tsx @@ -1,10 +1,12 @@ +import { Box, Typography } from '@material-ui/core'; import { Meta, Story } from '@storybook/react/types-6-0'; import { Provider } from 'react-redux'; -import { MemoryRouter } from 'react-router-dom'; +import { MemoryRouter, useLocation } from 'react-router-dom'; import { createStore } from 'redux'; import { KubeObjectInterface } from '../../lib/k8s/cluster'; import { useFilterFunc } from '../../lib/util'; import store from '../../redux/stores/store'; +import { TestContext, TestContextProps } from '../../test'; import SectionFilterHeader from './SectionFilterHeader'; import SimpleTable, { SimpleTableProps } from './SimpleTable'; @@ -14,10 +16,27 @@ export default { argTypes: {}, } as Meta; +function TestSimpleTable(props: SimpleTableProps) { + const location = useLocation(); + if (!!props.reflectInURL) { + return ( + + Test changing the page and rows per page. + + Current URL search: {`${location.search || ''}`} + + + + ); + } + + return ; +} + const Template: Story = args => ( - + ); @@ -131,6 +150,83 @@ Datum.args = { ], }; +const TemplateWithURLReflection: Story<{ + simpleTableProps: SimpleTableProps; + testContextProps: TestContextProps; +}> = args => { + const { testContextProps, simpleTableProps } = args; + return ( + + + + ); +}; + +export const ReflectInURL = TemplateWithURLReflection.bind({}); +const lotsOfData = (() => { + const data = []; + for (let i = 0; i < 50; i++) { + data.push({ + name: `Name ${i}`, + namespace: `Namespace ${i}`, + number: i, + }); + } + return data; +})(); +ReflectInURL.args = { + simpleTableProps: { + data: lotsOfData, + columns: [ + { + label: 'Name', + datum: 'name', + }, + { + label: 'Namespace', + datum: 'namespace', + }, + { + label: 'Number', + datum: 'number', + }, + ], + rowsPerPage: [5, 10, 15], + reflectInURL: true, + showPagination: !process.env.UNDER_TEST, // Disable for snapshots: The pagination uses useId so snapshots will fail. + }, + testContextProps: { + urlSearchParams: { p: '2' }, // 2nd page + }, +}; + +export const ReflectInURLWithPrefix = TemplateWithURLReflection.bind({}); +ReflectInURLWithPrefix.args = { + simpleTableProps: { + data: lotsOfData, + columns: [ + { + label: 'Name', + datum: 'name', + }, + { + label: 'Namespace', + datum: 'namespace', + }, + { + label: 'Number', + datum: 'creationDate', + }, + ], + rowsPerPage: [5, 10, 15], + reflectInURL: 'mySuperTable', + showPagination: !process.env.UNDER_TEST, // Disable for snapshots: The pagination uses useId so snapshots will fail. + }, + testContextProps: { + urlSearchParams: { p: '2' }, // 2nd page + }, +}; + // filter Function type SimpleTableWithFilterProps = SimpleTableProps & { matchCriteria?: string[] }; diff --git a/frontend/src/components/common/SimpleTable.tsx b/frontend/src/components/common/SimpleTable.tsx index b0962cec82c..1ba71c02c77 100644 --- a/frontend/src/components/common/SimpleTable.tsx +++ b/frontend/src/components/common/SimpleTable.tsx @@ -82,6 +82,10 @@ export interface SimpleTableProps { * If true or '', it'll reflect the parameters without a prefix. * By default, no parameters are reflected in the URL. */ reflectInURL?: string | boolean; + /** The page number to show by default (by default it's the first page). */ + page?: number; + /** Whether to show the pagination component */ + showPagination?: boolean; } interface ColumnSortButtonProps { @@ -145,6 +149,8 @@ export default function SimpleTable(props: SimpleTableProps) { data, filterFunction = null, emptyMessage = null, + page: initialPage = 0, + showPagination = true, errorMessage = null, defaultSortingColumn, noTableHeader = false, @@ -152,7 +158,7 @@ export default function SimpleTable(props: SimpleTableProps) { } = props; const shouldReflectInURL = reflectInURL !== undefined && reflectInURL !== false; const prefix = reflectInURL === true ? '' : reflectInURL || ''; - const [page, setPage] = usePageURLState(shouldReflectInURL ? 'p' : '', prefix, 0); + const [page, setPage] = usePageURLState(shouldReflectInURL ? 'p' : '', prefix, initialPage); const [currentData, setCurrentData] = React.useState(data); const [displayData, setDisplayData] = React.useState(data); const rowsPerPageOptions = props.rowsPerPage || [15, 25, 50]; @@ -385,7 +391,7 @@ export default function SimpleTable(props: SimpleTableProps) { )} - {filteredData.length > rowsPerPageOptions[0] && ( + {filteredData.length > rowsPerPageOptions[0] && showPagination && ( `; +exports[`Storyshots SimpleTable Reflect In URL 1`] = ` +
+
+

+ Test changing the page and rows per page. +

+

+ + Current URL search: + + + ?p=2 +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Name + + Namespace + + Number +
+ Name 5 + + Namespace 5 + + 5 +
+ Name 6 + + Namespace 6 + + 6 +
+ Name 7 + + Namespace 7 + + 7 +
+ Name 8 + + Namespace 8 + + 8 +
+ Name 9 + + Namespace 9 + + 9 +
+
+
+`; + +exports[`Storyshots SimpleTable Reflect In URL With Prefix 1`] = ` +
+
+

+ Test changing the page and rows per page. +

+

+ + Current URL search: + + + ?p=2 +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Name + + Namespace + + Number +
+ Name 0 + + Namespace 0 + +
+ Name 1 + + Namespace 1 + +
+ Name 2 + + Namespace 2 + +
+ Name 3 + + Namespace 3 + +
+ Name 4 + + Namespace 4 + +
+
+
+`; + exports[`Storyshots SimpleTable UID Search 1`] = `
Date: Thu, 10 Nov 2022 11:46:10 +0000 Subject: [PATCH 6/7] frontend: Make the ResourceTable reflect its page props in URL We want this behavior by default, but it's easier to override. --- frontend/src/components/common/Resource/ResourceTable.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/src/components/common/Resource/ResourceTable.tsx b/frontend/src/components/common/Resource/ResourceTable.tsx index 84cc6dd3b65..6f74b98075f 100644 --- a/frontend/src/components/common/Resource/ResourceTable.tsx +++ b/frontend/src/components/common/Resource/ResourceTable.tsx @@ -105,6 +105,7 @@ function Table(props: ResourceTableProps) { rowsPerPage={[15, 25, 50]} defaultSortingColumn={sortingColumn} filterFunction={useFilterFunc()} + reflectInURL {...otherProps} /> ); From a627757745c6fa549caa1ee4e589375009101240 Mon Sep 17 00:00:00 2001 From: Joaquim Rocha Date: Thu, 10 Nov 2022 11:48:07 +0000 Subject: [PATCH 7/7] frontend: Override reflectInURL in all views that need it These are views that have more than one table, and as such will need a prefix for their page data. --- docs/development/frontend.md | 9 ++++++ frontend/src/components/crd/Details.tsx | 3 ++ frontend/src/components/daemonset/Details.tsx | 1 + frontend/src/components/endpoints/Details.tsx | 2 ++ .../endpoints/EndpointDetails.stories.tsx | 6 +++- .../EndpointDetails.stories.storyshot | 28 +------------------ frontend/src/components/ingress/Details.tsx | 1 + frontend/src/components/pod/Details.tsx | 1 + frontend/src/components/pod/List.tsx | 8 ++++-- .../src/components/role/BindingDetails.tsx | 1 + frontend/src/components/role/Details.tsx | 1 + frontend/src/components/service/Details.tsx | 2 ++ 12 files changed, 32 insertions(+), 31 deletions(-) diff --git a/docs/development/frontend.md b/docs/development/frontend.md index dd84914a788..6902439b584 100644 --- a/docs/development/frontend.md +++ b/docs/development/frontend.md @@ -51,6 +51,15 @@ From within the [Headlamp](https://github.com/kinvolk/headlamp/) repo run: make storybook ``` +If you are adding new stories, please wrap your story components with the `TestContext` helper +component as it sets up the store, memory router, and other utilities that may be needed for +current or future stories: + +```jsx + + + +``` ## Accessibility (a11y) diff --git a/frontend/src/components/crd/Details.tsx b/frontend/src/components/crd/Details.tsx index 9dd0db54f7e..5bd0609838e 100644 --- a/frontend/src/components/crd/Details.tsx +++ b/frontend/src/components/crd/Details.tsx @@ -138,6 +138,7 @@ export default function CustomResourceDefinitionDetails() { datum: 'listKind', }, ]} + reflectInURL="acceptedNames" /> @@ -157,6 +158,7 @@ export default function CustomResourceDefinitionDetails() { getter: version => version.storage.toString(), }, ]} + reflectInURL="versions" /> @@ -177,6 +179,7 @@ export default function CustomResourceDefinitionDetails() { }, 'age', ]} + reflectInURL="objects" /> diff --git a/frontend/src/components/daemonset/Details.tsx b/frontend/src/components/daemonset/Details.tsx index 51bb362a612..e17b3407ce1 100644 --- a/frontend/src/components/daemonset/Details.tsx +++ b/frontend/src/components/daemonset/Details.tsx @@ -60,6 +60,7 @@ function TolerationsSection(props: TolerationsSection) { sort: true, }, ]} + reflectInURL="tolerations" /> ); diff --git a/frontend/src/components/endpoints/Details.tsx b/frontend/src/components/endpoints/Details.tsx index 12b2870a83a..3ec971683fc 100644 --- a/frontend/src/components/endpoints/Details.tsx +++ b/frontend/src/components/endpoints/Details.tsx @@ -70,6 +70,7 @@ export default function EndpointDetails() { }, }, ]} + reflectInURL="addresses" /> )) diff --git a/frontend/src/components/endpoints/EndpointDetails.stories.tsx b/frontend/src/components/endpoints/EndpointDetails.stories.tsx index c1d8638b258..d64da0f40f3 100644 --- a/frontend/src/components/endpoints/EndpointDetails.stories.tsx +++ b/frontend/src/components/endpoints/EndpointDetails.stories.tsx @@ -89,7 +89,11 @@ const Template: Story = (args: MockerStory) => { Endpoints.useList = args.useList; } - return ; + return ( + + + + ); }; export const Default = Template.bind({}); diff --git a/frontend/src/components/endpoints/__snapshots__/EndpointDetails.stories.storyshot b/frontend/src/components/endpoints/__snapshots__/EndpointDetails.stories.storyshot index 6264c46ffe8..9414ed54bf0 100644 --- a/frontend/src/components/endpoints/__snapshots__/EndpointDetails.stories.storyshot +++ b/frontend/src/components/endpoints/__snapshots__/EndpointDetails.stories.storyshot @@ -111,33 +111,7 @@ exports[`Storyshots endpoints/EndpointsDetailsView Default 1`] = ` - - my-endpoint - - - - - - Namespace - - - - my-namespace - - + /> )} diff --git a/frontend/src/components/pod/Details.tsx b/frontend/src/components/pod/Details.tsx index 9836613a5c7..5a9737ed4c7 100644 --- a/frontend/src/components/pod/Details.tsx +++ b/frontend/src/components/pod/Details.tsx @@ -271,6 +271,7 @@ export function VolumeDetails(props: VolumeDetailsProps) { }, ]} data={volumes} + reflectInURL="volumes" /> ); diff --git a/frontend/src/components/pod/List.tsx b/frontend/src/components/pod/List.tsx index 1c7134a0485..db186374716 100644 --- a/frontend/src/components/pod/List.tsx +++ b/frontend/src/components/pod/List.tsx @@ -4,7 +4,7 @@ import { useTranslation } from 'react-i18next'; import { ApiError } from '../../lib/k8s/apiProxy'; import Pod from '../../lib/k8s/pod'; import { timeAgo } from '../../lib/util'; -import { LightTooltip, SectionFilterHeader } from '../common'; +import { LightTooltip, SectionFilterHeader, SimpleTableProps } from '../common'; import { StatusLabel, StatusLabelProps } from '../common/Label'; import ResourceTable, { ResourceTableProps } from '../common/Resource/ResourceTable'; import { SectionBox } from '../common/SectionBox'; @@ -52,10 +52,11 @@ export interface PodListProps { pods: Pod[] | null; error: ApiError | null; hideColumns?: ('namespace' | 'restarts')[]; + reflectTableInURL?: SimpleTableProps['reflectInURL']; } export function PodListRenderer(props: PodListProps) { - const { pods, error, hideColumns = [] } = props; + const { pods, error, hideColumns = [], reflectTableInURL = 'pods' } = props; const { t } = useTranslation('glossary'); function getDataCols() { @@ -110,6 +111,7 @@ export function PodListRenderer(props: PodListProps) { errorMessage={Pod.getErrorMessage(error)} columns={getDataCols()} data={pods} + reflectInURL={reflectTableInURL} /> ); @@ -118,5 +120,5 @@ export function PodListRenderer(props: PodListProps) { export default function PodList() { const [pods, error] = Pod.useList(); - return ; + return ; } diff --git a/frontend/src/components/role/BindingDetails.tsx b/frontend/src/components/role/BindingDetails.tsx index 13f15f1bf89..48efa0284ce 100644 --- a/frontend/src/components/role/BindingDetails.tsx +++ b/frontend/src/components/role/BindingDetails.tsx @@ -67,6 +67,7 @@ export default function RoleBindingDetails() { getter: item => item.namespace, }, ]} + reflectInURL="bindingInfo" /> ) diff --git a/frontend/src/components/role/Details.tsx b/frontend/src/components/role/Details.tsx index c555adf9780..30d1f88e8c5 100644 --- a/frontend/src/components/role/Details.tsx +++ b/frontend/src/components/role/Details.tsx @@ -42,6 +42,7 @@ export default function RoleDetails() { }, ]} data={item.rules} + reflectInURL="rules" /> ) diff --git a/frontend/src/components/service/Details.tsx b/frontend/src/components/service/Details.tsx index 2c45fff0d4d..d25adcee560 100644 --- a/frontend/src/components/service/Details.tsx +++ b/frontend/src/components/service/Details.tsx @@ -74,6 +74,7 @@ export default function ServiceDetails() { ), }, ]} + reflectInURL="ports" /> @@ -93,6 +94,7 @@ export default function ServiceDetails() { cellProps: { style: { width: '40%', maxWidth: '40%' } }, }, ]} + reflectInURL="endpoints" /> )}