fix(stacks): store filter state [EE-5159] (#11637)

pull/11874/head
Chaim Lev-Ari 2024-05-28 08:14:12 +03:00 committed by GitHub
parent 84fe3cf2a2
commit 1261887c9e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 252 additions and 57 deletions

View File

@ -1,10 +1,10 @@
import { TableOptions } from '@tanstack/react-table'; import { TableOptions } from '@tanstack/react-table';
type OptionExtender<T> = (options: TableOptions<T>) => TableOptions<T>; import { OptionsExtension } from './types';
export function mergeOptions<T>( export function mergeOptions<D>(
...extenders: Array<OptionExtender<T>> ...extenders: Array<OptionsExtension<D>>
): OptionExtender<T> { ): OptionsExtension<D> {
return (options: TableOptions<T>) => return (options: TableOptions<D>) =>
extenders.reduce((acc, option) => option(acc), options); extenders.reduce((acc, option) => option(acc), options);
} }

View File

@ -0,0 +1,3 @@
import { TableOptions } from '@tanstack/react-table';
export type OptionsExtension<D> = (options: TableOptions<D>) => TableOptions<D>;

View File

@ -0,0 +1,29 @@
import { ColumnFiltersState, TableOptions } from '@tanstack/react-table';
import { applySetStateAction } from '@/react-tools/apply-set-state-action';
import { DefaultType } from '../types';
import { OptionsExtension } from './types';
export function withColumnFilters<D extends DefaultType>(
filters: ColumnFiltersState,
onChange: (filters: ColumnFiltersState) => void
): OptionsExtension<D> {
return function extendOptions(options: TableOptions<D>) {
return {
...options,
state: {
...options.state,
columnFilters: filters,
},
onColumnFiltersChange: (updater) => {
onChange(applySetStateAction(updater, filters));
},
initialState: {
...options.initialState,
columnFilters: filters,
},
};
};
}

View File

@ -6,10 +6,12 @@ import {
import { DefaultType } from '../types'; import { DefaultType } from '../types';
import { OptionsExtension } from './types';
export function withControlledSelected<D extends DefaultType>( export function withControlledSelected<D extends DefaultType>(
onChange?: (value: string[]) => void, onChange?: (value: string[]) => void,
value?: string[] value?: string[]
) { ): OptionsExtension<D> {
return function extendTableOptions(options: TableOptions<D>) { return function extendTableOptions(options: TableOptions<D>) {
if (!onChange || !value) { if (!onChange || !value) {
return options; return options;

View File

@ -3,12 +3,14 @@ import { TableOptions } from '@tanstack/react-table';
import { defaultGlobalFilterFn } from '../Datatable'; import { defaultGlobalFilterFn } from '../Datatable';
import { DefaultType } from '../types'; import { DefaultType } from '../types';
import { OptionsExtension } from './types';
export function withGlobalFilter< export function withGlobalFilter<
D extends DefaultType, D extends DefaultType,
TFilter extends { TFilter extends {
search: string; search: string;
}, },
>(filterFn: typeof defaultGlobalFilterFn<D, TFilter>) { >(filterFn: typeof defaultGlobalFilterFn<D, TFilter>): OptionsExtension<D> {
return function extendOptions(options: TableOptions<D>) { return function extendOptions(options: TableOptions<D>) {
return { return {
...options, ...options,

View File

@ -2,7 +2,11 @@ import { TableOptions } from '@tanstack/react-table';
import { DefaultType } from '../types'; import { DefaultType } from '../types';
export function withMeta<D extends DefaultType>(meta: Record<string, unknown>) { import { OptionsExtension } from './types';
export function withMeta<D extends DefaultType>(
meta: Record<string, unknown>
): OptionsExtension<D> {
return function extendOptions(options: TableOptions<D>) { return function extendOptions(options: TableOptions<D>) {
return { return {
...options, ...options,

View File

@ -1,5 +1,6 @@
import { createStore } from 'zustand'; import { createStore } from 'zustand';
import { persist } from 'zustand/middleware'; import { persist } from 'zustand/middleware';
import { ColumnFiltersState } from '@tanstack/react-table';
import { keyBuilder } from '@/react/hooks/useLocalStorage'; import { keyBuilder } from '@/react/hooks/useLocalStorage';
@ -76,6 +77,22 @@ export function refreshableSettings<T extends RefreshableTableSettings>(
}; };
} }
export interface FilteredColumnsTableSettings {
columnFilters: ColumnFiltersState;
setColumnFilters(columns: ColumnFiltersState): void;
}
export function filteredColumnsSettings<T extends FilteredColumnsTableSettings>(
set: ZustandSetFunc<T>
): FilteredColumnsTableSettings {
return {
columnFilters: [],
setColumnFilters(columns) {
set((s) => ({ ...s, columnFilters: columns }));
},
};
}
export interface BasicTableSettings export interface BasicTableSettings
extends SortableTableSettings, extends SortableTableSettings,
PaginationTableSettings {} PaginationTableSettings {}

View File

@ -15,6 +15,8 @@ import {
} from '@@/datatables/ColumnVisibilityMenu'; } from '@@/datatables/ColumnVisibilityMenu';
import { TableSettingsProvider } from '@@/datatables/useTableSettings'; import { TableSettingsProvider } from '@@/datatables/useTableSettings';
import { useTableState } from '@@/datatables/useTableState'; import { useTableState } from '@@/datatables/useTableState';
import { mergeOptions } from '@@/datatables/extend-options/mergeOptions';
import { withColumnFilters } from '@@/datatables/extend-options/withColumnFilters';
import { useContainers } from '../../queries/containers'; import { useContainers } from '../../queries/containers';
@ -92,6 +94,12 @@ export function ContainersDatatable({
)} )}
dataset={containersQuery.data || []} dataset={containersQuery.data || []}
emptyContentLabel="No containers found" emptyContentLabel="No containers found"
extendTableOptions={mergeOptions(
withColumnFilters(
tableState.columnFilters,
tableState.setColumnFilters
)
)}
/> />
</TableSettingsProvider> </TableSettingsProvider>
</RowProvider> </RowProvider>

View File

@ -2,6 +2,7 @@ import {
refreshableSettings, refreshableSettings,
hiddenColumnsSettings, hiddenColumnsSettings,
createPersistedStore, createPersistedStore,
filteredColumnsSettings,
} from '@@/datatables/types'; } from '@@/datatables/types';
import { QuickAction, TableSettings } from './types'; import { QuickAction, TableSettings } from './types';
@ -12,6 +13,7 @@ export function createStore(storageKey: string) {
return createPersistedStore<TableSettings>(storageKey, 'name', (set) => ({ return createPersistedStore<TableSettings>(storageKey, 'name', (set) => ({
...hiddenColumnsSettings(set), ...hiddenColumnsSettings(set),
...refreshableSettings(set), ...refreshableSettings(set),
...filteredColumnsSettings(set),
truncateContainerName: TRUNCATE_LENGTH, truncateContainerName: TRUNCATE_LENGTH,
setTruncateContainerName(truncateContainerName: number) { setTruncateContainerName(truncateContainerName: number) {
set({ set({

View File

@ -1,5 +1,6 @@
import { import {
BasicTableSettings, BasicTableSettings,
FilteredColumnsTableSettings,
RefreshableTableSettings, RefreshableTableSettings,
SettableColumnsTableSettings, SettableColumnsTableSettings,
} from '@@/datatables/types'; } from '@@/datatables/types';
@ -15,7 +16,8 @@ export interface TableSettings
extends BasicTableSettings, extends BasicTableSettings,
SettableColumnsTableSettings, SettableColumnsTableSettings,
SettableQuickActionsTableSettings<QuickAction>, SettableQuickActionsTableSettings<QuickAction>,
RefreshableTableSettings { RefreshableTableSettings,
FilteredColumnsTableSettings {
truncateContainerName: number; truncateContainerName: number;
setTruncateContainerName: (value: number) => void; setTruncateContainerName: (value: number) => void;
} }

View File

@ -10,6 +10,8 @@ import { Datatable, TableSettingsMenu } from '@@/datatables';
import { import {
BasicTableSettings, BasicTableSettings,
createPersistedStore, createPersistedStore,
FilteredColumnsTableSettings,
filteredColumnsSettings,
refreshableSettings, refreshableSettings,
RefreshableTableSettings, RefreshableTableSettings,
} from '@@/datatables/types'; } from '@@/datatables/types';
@ -18,6 +20,8 @@ import { AddButton, Button, ButtonGroup, LoadingButton } from '@@/buttons';
import { Link } from '@@/Link'; import { Link } from '@@/Link';
import { ButtonWithRef } from '@@/buttons/Button'; import { ButtonWithRef } from '@@/buttons/Button';
import { TableSettingsMenuAutoRefresh } from '@@/datatables/TableSettingsMenuAutoRefresh'; import { TableSettingsMenuAutoRefresh } from '@@/datatables/TableSettingsMenuAutoRefresh';
import { mergeOptions } from '@@/datatables/extend-options/mergeOptions';
import { withColumnFilters } from '@@/datatables/extend-options/withColumnFilters';
import { ImagesListResponse, useImages } from '../../queries/useImages'; import { ImagesListResponse, useImages } from '../../queries/useImages';
@ -28,13 +32,15 @@ const tableKey = 'images';
export interface TableSettings export interface TableSettings
extends BasicTableSettings, extends BasicTableSettings,
RefreshableTableSettings {} RefreshableTableSettings,
FilteredColumnsTableSettings {}
const settingsStore = createPersistedStore<TableSettings>( const settingsStore = createPersistedStore<TableSettings>(
tableKey, tableKey,
'tags', 'tags',
(set) => ({ (set) => ({
...refreshableSettings(set), ...refreshableSettings(set),
...filteredColumnsSettings(set),
}) })
); );
@ -65,6 +71,9 @@ export function ImagesDatatable({
title="Images" title="Images"
titleIcon={List} titleIcon={List}
data-cy="docker-images-datatable" data-cy="docker-images-datatable"
extendTableOptions={mergeOptions(
withColumnFilters(tableState.columnFilters, tableState.setColumnFilters)
)}
renderTableActions={(selectedItems) => ( renderTableActions={(selectedItems) => (
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<RemoveButtonMenu selectedItems={selectedItems} onRemove={onRemove} /> <RemoveButtonMenu selectedItems={selectedItems} onRemove={onRemove} />

View File

@ -1,15 +1,24 @@
import { List } from 'lucide-react'; import { List } from 'lucide-react';
import { Datatable } from '@@/datatables'; import { Datatable } from '@@/datatables';
import { createPersistedStore } from '@@/datatables/types'; import {
import { useTableState } from '@@/datatables/useTableState'; BasicTableSettings,
type FilteredColumnsTableSettings,
filteredColumnsSettings,
} from '@@/datatables/types';
import { useTableStateWithStorage } from '@@/datatables/useTableState';
import { withMeta } from '@@/datatables/extend-options/withMeta'; import { withMeta } from '@@/datatables/extend-options/withMeta';
import { mergeOptions } from '@@/datatables/extend-options/mergeOptions';
import { withColumnFilters } from '@@/datatables/extend-options/withColumnFilters';
import { useColumns } from './columns'; import { useColumns } from './columns';
import { DecoratedTask } from './types'; import { DecoratedTask } from './types';
const storageKey = 'docker-service-tasks'; const storageKey = 'docker-service-tasks';
const store = createPersistedStore(storageKey);
interface TableSettings
extends BasicTableSettings,
FilteredColumnsTableSettings {}
export function TasksDatatable({ export function TasksDatatable({
dataset, dataset,
@ -20,7 +29,13 @@ export function TasksDatatable({
isSlotColumnVisible: boolean; isSlotColumnVisible: boolean;
serviceName: string; serviceName: string;
}) { }) {
const tableState = useTableState(store, storageKey); const tableState = useTableStateWithStorage<TableSettings>(
storageKey,
undefined,
(set) => ({
...filteredColumnsSettings(set),
})
);
const columns = useColumns(isSlotColumnVisible); const columns = useColumns(isSlotColumnVisible);
return ( return (
@ -31,7 +46,11 @@ export function TasksDatatable({
columns={columns} columns={columns}
dataset={dataset} dataset={dataset}
emptyContentLabel="No task available." emptyContentLabel="No task available."
extendTableOptions={withMeta({ table: 'tasks', serviceName })} extendTableOptions={mergeOptions(
withMeta({ table: 'tasks', serviceName }),
withColumnFilters(tableState.columnFilters, tableState.setColumnFilters)
)}
disableSelect
data-cy="docker-service-tasks-datatable" data-cy="docker-service-tasks-datatable"
/> />
); );

View File

@ -5,22 +5,20 @@ import { useAuthorizations, useIsEdgeAdmin } from '@/react/hooks/useUser';
import { isBE } from '@/react/portainer/feature-flags/feature-flags.service'; import { isBE } from '@/react/portainer/feature-flags/feature-flags.service';
import { Datatable } from '@@/datatables'; import { Datatable } from '@@/datatables';
import { useTableState } from '@@/datatables/useTableState';
import { useRepeater } from '@@/datatables/useRepeater'; import { useRepeater } from '@@/datatables/useRepeater';
import { defaultGlobalFilterFn } from '@@/datatables/Datatable'; import { defaultGlobalFilterFn } from '@@/datatables/Datatable';
import { withGlobalFilter } from '@@/datatables/extend-options/withGlobalFilter'; import { withGlobalFilter } from '@@/datatables/extend-options/withGlobalFilter';
import { mergeOptions } from '@@/datatables/extend-options/mergeOptions';
import { withColumnFilters } from '@@/datatables/extend-options/withColumnFilters';
import { isExternalStack, isOrphanedStack } from '../../view-models/utils'; import { isExternalStack, isOrphanedStack } from '../../view-models/utils';
import { TableActions } from './TableActions'; import { TableActions } from './TableActions';
import { TableSettingsMenus } from './TableSettingsMenus'; import { TableSettingsMenus } from './TableSettingsMenus';
import { createStore } from './store'; import { useStore } from './store';
import { useColumns } from './columns'; import { useColumns } from './columns';
import { DecoratedStack } from './types'; import { DecoratedStack } from './types';
const tableKey = 'docker_stacks';
const settingsStore = createStore(tableKey);
export function StacksDatatable({ export function StacksDatatable({
onRemove, onRemove,
onReload, onReload,
@ -32,7 +30,7 @@ export function StacksDatatable({
isImageNotificationEnabled: boolean; isImageNotificationEnabled: boolean;
dataset: Array<DecoratedStack>; dataset: Array<DecoratedStack>;
}) { }) {
const tableState = useTableState(settingsStore, tableKey); const tableState = useStore();
useRepeater(tableState.autoRefreshRate, onReload); useRepeater(tableState.autoRefreshRate, onReload);
const isAdminQuery = useIsEdgeAdmin(); const isAdminQuery = useIsEdgeAdmin();
const { authorized: canManageStacks } = useAuthorizations([ const { authorized: canManageStacks } = useAuthorizations([
@ -69,7 +67,10 @@ export function StacksDatatable({
tableState.hiddenColumns.map((col) => [col, false]) tableState.hiddenColumns.map((col) => [col, false])
), ),
}} }}
extendTableOptions={withGlobalFilter(globalFilterFn)} extendTableOptions={mergeOptions(
withGlobalFilter(globalFilterFn),
withColumnFilters(tableState.columnFilters, tableState.setColumnFilters)
)}
data-cy="docker-stacks-datatable" data-cy="docker-stacks-datatable"
/> />
); );

View File

@ -1,24 +1,30 @@
import { import {
BasicTableSettings, type BasicTableSettings,
RefreshableTableSettings, type FilteredColumnsTableSettings,
SettableColumnsTableSettings, type RefreshableTableSettings,
createPersistedStore, type SettableColumnsTableSettings,
hiddenColumnsSettings, hiddenColumnsSettings,
refreshableSettings, refreshableSettings,
filteredColumnsSettings,
} from '@@/datatables/types'; } from '@@/datatables/types';
import { useTableStateWithStorage } from '@@/datatables/useTableState';
export interface TableSettings export interface TableSettings
extends BasicTableSettings, extends BasicTableSettings,
SettableColumnsTableSettings, SettableColumnsTableSettings,
RefreshableTableSettings { RefreshableTableSettings,
FilteredColumnsTableSettings {
showOrphanedStacks: boolean; showOrphanedStacks: boolean;
setShowOrphanedStacks(value: boolean): void; setShowOrphanedStacks(value: boolean): void;
} }
export function createStore(storageKey: string) { const tableKey = 'docker_stacks';
return createPersistedStore<TableSettings>(storageKey, 'name', (set) => ({
export function useStore() {
return useTableStateWithStorage<TableSettings>(tableKey, 'name', (set) => ({
...hiddenColumnsSettings(set), ...hiddenColumnsSettings(set),
...refreshableSettings(set), ...refreshableSettings(set),
...filteredColumnsSettings(set),
showOrphanedStacks: false, showOrphanedStacks: false,
setShowOrphanedStacks(showOrphanedStacks) { setShowOrphanedStacks(showOrphanedStacks) {
set((s) => ({ ...s, showOrphanedStacks })); set((s) => ({ ...s, showOrphanedStacks }));

View File

@ -4,6 +4,8 @@ import { Datatable, TableSettingsMenu } from '@@/datatables';
import { TableSettingsMenuAutoRefresh } from '@@/datatables/TableSettingsMenuAutoRefresh'; import { TableSettingsMenuAutoRefresh } from '@@/datatables/TableSettingsMenuAutoRefresh';
import { import {
BasicTableSettings, BasicTableSettings,
FilteredColumnsTableSettings,
filteredColumnsSettings,
RefreshableTableSettings, RefreshableTableSettings,
createPersistedStore, createPersistedStore,
refreshableSettings, refreshableSettings,
@ -11,13 +13,17 @@ import {
import { useRepeater } from '@@/datatables/useRepeater'; import { useRepeater } from '@@/datatables/useRepeater';
import { useTableState } from '@@/datatables/useTableState'; import { useTableState } from '@@/datatables/useTableState';
import { withMeta } from '@@/datatables/extend-options/withMeta'; import { withMeta } from '@@/datatables/extend-options/withMeta';
import { withColumnFilters } from '@@/datatables/extend-options/withColumnFilters';
import { DecoratedVolume } from '../types'; import { DecoratedVolume } from '../types';
import { TableActions } from './TableActions'; import { TableActions } from './TableActions';
import { useColumns } from './columns'; import { useColumns } from './columns';
interface TableSettings extends BasicTableSettings, RefreshableTableSettings {} interface TableSettings
extends BasicTableSettings,
RefreshableTableSettings,
FilteredColumnsTableSettings {}
const storageKey = 'docker-volumes'; const storageKey = 'docker-volumes';
const store = createPersistedStore<TableSettings>( const store = createPersistedStore<TableSettings>(
@ -25,6 +31,7 @@ const store = createPersistedStore<TableSettings>(
undefined, undefined,
(set) => ({ (set) => ({
...refreshableSettings(set), ...refreshableSettings(set),
...filteredColumnsSettings(set),
}) })
); );
@ -63,10 +70,16 @@ export function VolumesDatatable({
/> />
</TableSettingsMenu> </TableSettingsMenu>
)} )}
extendTableOptions={withMeta({ extendTableOptions={
table: 'volumes', (withMeta({
isBrowseVisible, table: 'volumes',
})} isBrowseVisible,
}),
withColumnFilters(
tableState.columnFilters,
tableState.setColumnFilters
))
}
data-cy="docker-volumes-datatable" data-cy="docker-volumes-datatable"
/> />
); );

View File

@ -10,7 +10,7 @@ type AuthorizationOptions = {
type RedirectOptions = { type RedirectOptions = {
to: string; to: string;
params: Record<string, unknown>; params?: Record<string, unknown>;
}; };
/** /**

View File

@ -5,8 +5,11 @@ import { CronJob, Job } from 'kubernetes-types/batch/v1';
import { useEnvironmentId } from '@/react/hooks/useEnvironmentId'; import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
import { Authorized, useAuthorizations } from '@/react/hooks/useUser'; import { Authorized, useAuthorizations } from '@/react/hooks/useUser';
import { DefaultDatatableSettings } from '@/react/kubernetes/datatables/DefaultDatatableSettings'; import {
import { createStore } from '@/react/kubernetes/datatables/default-kube-datatable-store'; DefaultDatatableSettings,
TableSettings as KubeTableSettings,
} from '@/react/kubernetes/datatables/DefaultDatatableSettings';
import { useKubeStore } from '@/react/kubernetes/datatables/default-kube-datatable-store';
import { SystemResourceDescription } from '@/react/kubernetes/datatables/SystemResourceDescription'; import { SystemResourceDescription } from '@/react/kubernetes/datatables/SystemResourceDescription';
import { pluralize } from '@/portainer/helpers/strings'; import { pluralize } from '@/portainer/helpers/strings';
import { useNamespacesQuery } from '@/react/kubernetes/namespaces/queries/useNamespacesQuery'; import { useNamespacesQuery } from '@/react/kubernetes/namespaces/queries/useNamespacesQuery';
@ -17,9 +20,14 @@ import { useJobs } from '@/react/kubernetes/applications/useJobs';
import { useCronJobs } from '@/react/kubernetes/applications/useCronJobs'; import { useCronJobs } from '@/react/kubernetes/applications/useCronJobs';
import { Datatable, TableSettingsMenu } from '@@/datatables'; import { Datatable, TableSettingsMenu } from '@@/datatables';
import { useTableState } from '@@/datatables/useTableState'; import { AddButton } from '@@/buttons';
import { DeleteButton } from '@@/buttons/DeleteButton'; import { DeleteButton } from '@@/buttons/DeleteButton';
import { AddButton } from '@@/buttons/AddButton'; import {
type FilteredColumnsTableSettings,
filteredColumnsSettings,
} from '@@/datatables/types';
import { mergeOptions } from '@@/datatables/extend-options/mergeOptions';
import { withColumnFilters } from '@@/datatables/extend-options/withColumnFilters';
import { import {
useConfigMapsForCluster, useConfigMapsForCluster,
@ -31,11 +39,20 @@ import { getIsConfigMapInUse } from './utils';
import { ConfigMapRowData } from './types'; import { ConfigMapRowData } from './types';
import { columns } from './columns'; import { columns } from './columns';
interface TableSettings
extends KubeTableSettings,
FilteredColumnsTableSettings {}
const storageKey = 'k8sConfigMapsDatatable'; const storageKey = 'k8sConfigMapsDatatable';
const settingsStore = createStore(storageKey);
export function ConfigMapsDatatable() { export function ConfigMapsDatatable() {
const tableState = useTableState(settingsStore, storageKey); const tableState = useKubeStore<TableSettings>(
storageKey,
undefined,
(set) => ({
...filteredColumnsSettings(set),
})
);
const { authorized: canWrite } = useAuthorizations(['K8sConfigMapsW']); const { authorized: canWrite } = useAuthorizations(['K8sConfigMapsW']);
const readOnly = !canWrite; const readOnly = !canWrite;
const { authorized: canAccessSystemResources } = useAuthorizations( const { authorized: canAccessSystemResources } = useAuthorizations(
@ -109,6 +126,9 @@ export function ConfigMapsDatatable() {
/> />
} }
data-cy="k8s-configmaps-datatable" data-cy="k8s-configmaps-datatable"
extendTableOptions={mergeOptions(
withColumnFilters(tableState.columnFilters, tableState.setColumnFilters)
)}
/> />
); );
} }

View File

@ -5,8 +5,11 @@ import { CronJob, Job } from 'kubernetes-types/batch/v1';
import { useEnvironmentId } from '@/react/hooks/useEnvironmentId'; import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
import { Authorized, useAuthorizations } from '@/react/hooks/useUser'; import { Authorized, useAuthorizations } from '@/react/hooks/useUser';
import { DefaultDatatableSettings } from '@/react/kubernetes/datatables/DefaultDatatableSettings'; import {
import { createStore } from '@/react/kubernetes/datatables/default-kube-datatable-store'; DefaultDatatableSettings,
TableSettings as KubeTableSettings,
} from '@/react/kubernetes/datatables/DefaultDatatableSettings';
import { useKubeStore } from '@/react/kubernetes/datatables/default-kube-datatable-store';
import { SystemResourceDescription } from '@/react/kubernetes/datatables/SystemResourceDescription'; import { SystemResourceDescription } from '@/react/kubernetes/datatables/SystemResourceDescription';
import { pluralize } from '@/portainer/helpers/strings'; import { pluralize } from '@/portainer/helpers/strings';
import { useNamespacesQuery } from '@/react/kubernetes/namespaces/queries/useNamespacesQuery'; import { useNamespacesQuery } from '@/react/kubernetes/namespaces/queries/useNamespacesQuery';
@ -18,8 +21,13 @@ import { useCronJobs } from '@/react/kubernetes/applications/useCronJobs';
import { Datatable, TableSettingsMenu } from '@@/datatables'; import { Datatable, TableSettingsMenu } from '@@/datatables';
import { AddButton } from '@@/buttons'; import { AddButton } from '@@/buttons';
import { useTableState } from '@@/datatables/useTableState';
import { DeleteButton } from '@@/buttons/DeleteButton'; import { DeleteButton } from '@@/buttons/DeleteButton';
import {
type FilteredColumnsTableSettings,
filteredColumnsSettings,
} from '@@/datatables/types';
import { mergeOptions } from '@@/datatables/extend-options/mergeOptions';
import { withColumnFilters } from '@@/datatables/extend-options/withColumnFilters';
import { import {
useSecretsForCluster, useSecretsForCluster,
@ -32,17 +40,26 @@ import { SecretRowData } from './types';
import { columns } from './columns'; import { columns } from './columns';
const storageKey = 'k8sSecretsDatatable'; const storageKey = 'k8sSecretsDatatable';
const settingsStore = createStore(storageKey);
interface TableSettings
extends KubeTableSettings,
FilteredColumnsTableSettings {}
export function SecretsDatatable() { export function SecretsDatatable() {
const tableState = useTableState(settingsStore, storageKey); const tableState = useKubeStore<TableSettings>(
storageKey,
undefined,
(set) => ({
...filteredColumnsSettings(set),
})
);
const environmentId = useEnvironmentId();
const { authorized: canWrite } = useAuthorizations(['K8sSecretsW']); const { authorized: canWrite } = useAuthorizations(['K8sSecretsW']);
const readOnly = !canWrite; const readOnly = !canWrite;
const { authorized: canAccessSystemResources } = useAuthorizations( const { authorized: canAccessSystemResources } = useAuthorizations(
'K8sAccessSystemNamespaces' 'K8sAccessSystemNamespaces'
); );
const environmentId = useEnvironmentId();
const { data: namespaces, ...namespacesQuery } = useNamespacesQuery( const { data: namespaces, ...namespacesQuery } = useNamespacesQuery(
environmentId, environmentId,
{ {
@ -109,6 +126,9 @@ export function SecretsDatatable() {
/> />
} }
data-cy="k8s-secrets-datatable" data-cy="k8s-secrets-datatable"
extendTableOptions={mergeOptions(
withColumnFilters(tableState.columnFilters, tableState.setColumnFilters)
)}
/> />
); );
} }

View File

@ -4,14 +4,22 @@ import { useMemo } from 'react';
import { useEnvironmentId } from '@/react/hooks/useEnvironmentId'; import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
import { useAuthorizations, Authorized } from '@/react/hooks/useUser'; import { useAuthorizations, Authorized } from '@/react/hooks/useUser';
import Route from '@/assets/ico/route.svg?c'; import Route from '@/assets/ico/route.svg?c';
import { DefaultDatatableSettings } from '@/react/kubernetes/datatables/DefaultDatatableSettings'; import {
import { createStore } from '@/react/kubernetes/datatables/default-kube-datatable-store'; DefaultDatatableSettings,
TableSettings as KubeTableSettings,
} from '@/react/kubernetes/datatables/DefaultDatatableSettings';
import { useKubeStore } from '@/react/kubernetes/datatables/default-kube-datatable-store';
import { SystemResourceDescription } from '@/react/kubernetes/datatables/SystemResourceDescription'; import { SystemResourceDescription } from '@/react/kubernetes/datatables/SystemResourceDescription';
import { Datatable, TableSettingsMenu } from '@@/datatables'; import { Datatable, TableSettingsMenu } from '@@/datatables';
import { AddButton } from '@@/buttons'; import { AddButton } from '@@/buttons';
import { useTableState } from '@@/datatables/useTableState';
import { DeleteButton } from '@@/buttons/DeleteButton'; import { DeleteButton } from '@@/buttons/DeleteButton';
import {
type FilteredColumnsTableSettings,
filteredColumnsSettings,
} from '@@/datatables/types';
import { mergeOptions } from '@@/datatables/extend-options/mergeOptions';
import { withColumnFilters } from '@@/datatables/extend-options/withColumnFilters';
import { DeleteIngressesRequest, Ingress } from '../types'; import { DeleteIngressesRequest, Ingress } from '../types';
import { useDeleteIngresses, useIngresses } from '../queries'; import { useDeleteIngresses, useIngresses } from '../queries';
@ -29,10 +37,18 @@ interface SelectedIngress {
} }
const storageKey = 'ingressClassesNameSpace'; const storageKey = 'ingressClassesNameSpace';
const settingsStore = createStore(storageKey); interface TableSettings
extends KubeTableSettings,
FilteredColumnsTableSettings {}
export function IngressDatatable() { export function IngressDatatable() {
const tableState = useTableState(settingsStore, storageKey); const tableState = useKubeStore<TableSettings>(
storageKey,
undefined,
(set) => ({
...filteredColumnsSettings(set),
})
);
const environmentId = useEnvironmentId(); const environmentId = useEnvironmentId();
const { authorized: canAccessSystemResources } = useAuthorizations( const { authorized: canAccessSystemResources } = useAuthorizations(
@ -91,6 +107,9 @@ export function IngressDatatable() {
} }
disableSelect={useCheckboxes()} disableSelect={useCheckboxes()}
data-cy="k8s-ingresses-datatable" data-cy="k8s-ingresses-datatable"
extendTableOptions={mergeOptions(
withColumnFilters(tableState.columnFilters, tableState.setColumnFilters)
)}
/> />
); );

View File

@ -9,14 +9,23 @@ import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
import { Authorized, useAuthorizations } from '@/react/hooks/useUser'; import { Authorized, useAuthorizations } from '@/react/hooks/useUser';
import { notifyError, notifySuccess } from '@/portainer/services/notifications'; import { notifyError, notifySuccess } from '@/portainer/services/notifications';
import { pluralize } from '@/portainer/helpers/strings'; import { pluralize } from '@/portainer/helpers/strings';
import { DefaultDatatableSettings } from '@/react/kubernetes/datatables/DefaultDatatableSettings'; import {
DefaultDatatableSettings,
TableSettings as KubeTableSettings,
} from '@/react/kubernetes/datatables/DefaultDatatableSettings';
import { useKubeStore } from '@/react/kubernetes/datatables/default-kube-datatable-store';
import { SystemResourceDescription } from '@/react/kubernetes/datatables/SystemResourceDescription'; import { SystemResourceDescription } from '@/react/kubernetes/datatables/SystemResourceDescription';
import { useNamespacesQuery } from '@/react/kubernetes/namespaces/queries/useNamespacesQuery'; import { useNamespacesQuery } from '@/react/kubernetes/namespaces/queries/useNamespacesQuery';
import { CreateFromManifestButton } from '@/react/kubernetes/components/CreateFromManifestButton'; import { CreateFromManifestButton } from '@/react/kubernetes/components/CreateFromManifestButton';
import { Datatable, Table, TableSettingsMenu } from '@@/datatables'; import { Datatable, Table, TableSettingsMenu } from '@@/datatables';
import { useTableState } from '@@/datatables/useTableState';
import { DeleteButton } from '@@/buttons/DeleteButton'; import { DeleteButton } from '@@/buttons/DeleteButton';
import {
type FilteredColumnsTableSettings,
filteredColumnsSettings,
} from '@@/datatables/types';
import { mergeOptions } from '@@/datatables/extend-options/mergeOptions';
import { withColumnFilters } from '@@/datatables/extend-options/withColumnFilters';
import { import {
useMutationDeleteServices, useMutationDeleteServices,
@ -25,13 +34,20 @@ import {
import { Service } from '../../types'; import { Service } from '../../types';
import { columns } from './columns'; import { columns } from './columns';
import { createStore } from './datatable-store';
const storageKey = 'k8sServicesDatatable'; const storageKey = 'k8sServicesDatatable';
const settingsStore = createStore(storageKey); interface TableSettings
extends KubeTableSettings,
FilteredColumnsTableSettings {}
export function ServicesDatatable() { export function ServicesDatatable() {
const tableState = useTableState(settingsStore, storageKey); const tableState = useKubeStore<TableSettings>(
storageKey,
undefined,
(set) => ({
...filteredColumnsSettings(set),
})
);
const environmentId = useEnvironmentId(); const environmentId = useEnvironmentId();
const { data: namespaces, ...namespacesQuery } = const { data: namespaces, ...namespacesQuery } =
useNamespacesQuery(environmentId); useNamespacesQuery(environmentId);
@ -91,6 +107,9 @@ export function ServicesDatatable() {
} }
renderRow={servicesRenderRow} renderRow={servicesRenderRow}
data-cy="k8s-services-datatable" data-cy="k8s-services-datatable"
extendTableOptions={mergeOptions(
withColumnFilters(tableState.columnFilters, tableState.setColumnFilters)
)}
/> />
); );
} }