1
0
Fork 0
mirror of https://github.com/portainer/portainer.git synced 2025-08-04 21:35:23 +02:00

refactor(ui/datatables): migrate views to use datatable component [EE-4064] (#7609)

This commit is contained in:
Chaim Lev-Ari 2022-11-22 14:16:34 +02:00 committed by GitHub
parent 0f0513c684
commit fe8e834dbf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
90 changed files with 1714 additions and 2717 deletions

View file

@ -1,4 +1,6 @@
import _ from 'lodash';
import { useStore } from 'zustand';
import { Box } from 'react-feather';
import { Environment } from '@/react/portainer/environments/types';
import type { DockerContainer } from '@/react/docker/containers/types';
@ -10,6 +12,8 @@ import {
QuickActionsSettings,
} from '@@/datatables/QuickActionsSettings';
import { ColumnVisibilityMenu } from '@@/datatables/ColumnVisibilityMenu';
import { useSearchBarState } from '@@/datatables/SearchBar';
import { TableSettingsProvider } from '@@/datatables/useTableSettings';
import { useContainers } from '../../queries/containers';
@ -20,7 +24,7 @@ import { ContainersDatatableActions } from './ContainersDatatableActions';
import { RowProvider } from './RowContext';
const storageKey = 'containers';
const useStore = createStore(storageKey);
const settingsStore = createStore(storageKey);
const actions = [
buildAction('logs', 'Logs'),
@ -39,13 +43,15 @@ export function ContainersDatatable({
isHostColumnVisible,
environment,
}: Props) {
const settings = useStore();
const settings = useStore(settingsStore);
const isGPUsColumnVisible = useShowGPUsColumn(environment.Id);
const columns = useColumns(isHostColumnVisible, isGPUsColumnVisible);
const hidableColumns = _.compact(
columns.filter((col) => col.canHide).map((col) => col.id)
);
const [search, setSearch] = useSearchBarState(storageKey);
const containersQuery = useContainers(
environment.Id,
true,
@ -55,53 +61,57 @@ export function ContainersDatatable({
return (
<RowProvider context={{ environment }}>
<Datatable
titleOptions={{
icon: 'svg-cubes',
title: 'Containers',
}}
settingsStore={settings}
columns={columns}
renderTableActions={(selectedRows) => (
<ContainersDatatableActions
selectedItems={selectedRows}
isAddActionVisible
endpointId={environment.Id}
/>
)}
isLoading={containersQuery.isLoading}
isRowSelectable={(row) => !row.original.IsPortainer}
initialTableState={{ hiddenColumns: settings.hiddenColumns }}
renderTableSettings={(tableInstance) => {
const columnsToHide = tableInstance.allColumns.filter((colInstance) =>
hidableColumns?.includes(colInstance.id)
);
<TableSettingsProvider settings={settingsStore}>
<Datatable
titleIcon={Box}
title="Containers"
initialPageSize={settings.pageSize}
onPageSizeChange={settings.setPageSize}
initialSortBy={settings.sortBy}
onSortByChange={settings.setSortBy}
searchValue={search}
onSearchChange={setSearch}
columns={columns}
renderTableActions={(selectedRows) => (
<ContainersDatatableActions
selectedItems={selectedRows}
isAddActionVisible
endpointId={environment.Id}
/>
)}
isLoading={containersQuery.isLoading}
isRowSelectable={(row) => !row.original.IsPortainer}
initialTableState={{ hiddenColumns: settings.hiddenColumns }}
renderTableSettings={(tableInstance) => {
const columnsToHide = tableInstance.allColumns.filter(
(colInstance) => hidableColumns?.includes(colInstance.id)
);
return (
<>
<ColumnVisibilityMenu<DockerContainer>
columns={columnsToHide}
onChange={(hiddenColumns) => {
settings.setHiddenColumns(hiddenColumns);
tableInstance.setHiddenColumns(hiddenColumns);
}}
value={settings.hiddenColumns}
/>
<TableSettingsMenu
quickActions={<QuickActionsSettings actions={actions} />}
>
<ContainersDatatableSettings
isRefreshVisible
settings={settings}
return (
<>
<ColumnVisibilityMenu<DockerContainer>
columns={columnsToHide}
onChange={(hiddenColumns) => {
settings.setHiddenColumns(hiddenColumns);
tableInstance.setHiddenColumns(hiddenColumns);
}}
value={settings.hiddenColumns}
/>
</TableSettingsMenu>
</>
);
}}
storageKey={storageKey}
dataset={containersQuery.data || []}
emptyContentLabel="No containers found"
/>
<TableSettingsMenu
quickActions={<QuickActionsSettings actions={actions} />}
>
<ContainersDatatableSettings
isRefreshVisible
settings={settings}
/>
</TableSettingsMenu>
</>
);
}}
dataset={containersQuery.data || []}
emptyContentLabel="No containers found"
/>
</TableSettingsProvider>
</RowProvider>
);
}

View file

@ -4,7 +4,7 @@ import { useSref } from '@uirouter/react';
import type { DockerContainer } from '@/react/docker/containers/types';
import { useTableSettings } from '@@/datatables/useZustandTableSettings';
import { useTableSettings } from '@@/datatables/useTableSettings';
import { TableSettings } from '../types';
@ -31,7 +31,7 @@ export function NameCell({
nodeName: container.NodeName,
});
const { settings } = useTableSettings<TableSettings>();
const settings = useTableSettings<TableSettings>();
const truncate = settings.truncateContainerName;
let shortName = name;

View file

@ -4,7 +4,7 @@ import { useAuthorizations } from '@/react/hooks/useUser';
import { ContainerQuickActions } from '@/react/docker/containers/components/ContainerQuickActions';
import { DockerContainer } from '@/react/docker/containers/types';
import { useTableSettings } from '@@/datatables/useZustandTableSettings';
import { useTableSettings } from '@@/datatables/useTableSettings';
import { TableSettings } from '../types';
@ -22,7 +22,7 @@ export const quickActions: Column<DockerContainer> = {
function QuickActionsCell({
row: { original: container },
}: CellProps<DockerContainer>) {
const { settings } = useTableSettings<TableSettings>();
const settings = useTableSettings<TableSettings>();
const { hiddenQuickActions = [] } = settings;

View file

@ -1,40 +1,26 @@
import create from 'zustand';
import { persist } from 'zustand/middleware';
import { keyBuilder } from '@/react/hooks/useLocalStorage';
import {
paginationSettings,
sortableSettings,
refreshableSettings,
hiddenColumnsSettings,
} from '@/react/components/datatables/types';
createPersistedStore,
} from '@@/datatables/types';
import { QuickAction, TableSettings } from './types';
export const TRUNCATE_LENGTH = 32;
export function createStore(storageKey: string) {
return create<TableSettings>()(
persist(
(set) => ({
...sortableSettings(set),
...paginationSettings(set),
...hiddenColumnsSettings(set),
...refreshableSettings(set),
truncateContainerName: TRUNCATE_LENGTH,
setTruncateContainerName(truncateContainerName: number) {
set({
truncateContainerName,
});
},
return createPersistedStore<TableSettings>(storageKey, 'Name', (set) => ({
...hiddenColumnsSettings(set),
...refreshableSettings(set),
truncateContainerName: TRUNCATE_LENGTH,
setTruncateContainerName(truncateContainerName: number) {
set({
truncateContainerName,
});
},
hiddenQuickActions: [] as QuickAction[],
setHiddenQuickActions: (hiddenQuickActions: QuickAction[]) =>
set({ hiddenQuickActions }),
}),
{
name: keyBuilder(storageKey),
}
)
);
hiddenQuickActions: [] as QuickAction[],
setHiddenQuickActions: (hiddenQuickActions: QuickAction[]) =>
set({ hiddenQuickActions }),
}));
}

View file

@ -1,9 +1,8 @@
import {
PaginationTableSettings,
BasicTableSettings,
RefreshableTableSettings,
SettableColumnsTableSettings,
SortableTableSettings,
} from '@/react/components/datatables/types';
} from '@@/datatables/types';
export type QuickAction = 'attach' | 'exec' | 'inspect' | 'logs' | 'stats';
@ -13,8 +12,7 @@ export interface SettableQuickActionsTableSettings<TAction> {
}
export interface TableSettings
extends SortableTableSettings,
PaginationTableSettings,
extends BasicTableSettings,
SettableColumnsTableSettings,
SettableQuickActionsTableSettings<QuickAction>,
RefreshableTableSettings {

View file

@ -38,63 +38,59 @@ export function NetworkContainersTable({
}
return (
<div className="row">
<div className="col-lg-12 col-md-12 col-xs-12">
<TableContainer>
<TableTitle label="Containers in network" icon="server" featherIcon />
<Table className="nopadding">
<DetailsTable
headers={tableHeaders}
dataCy="networkDetails-networkContainers"
>
{networkContainers.map((container) => (
<tr key={container.Id}>
<td>
<Link
to="docker.containers.container"
params={{
id: container.Id,
nodeName,
}}
title={container.Name}
>
{container.Name}
</Link>
</td>
<td>{container.IPv4Address || '-'}</td>
<td>{container.IPv6Address || '-'}</td>
<td>{container.MacAddress || '-'}</td>
<td>
<Authorized authorizations="DockerNetworkDisconnect">
<Button
data-cy={`networkDetails-disconnect${container.Name}`}
size="xsmall"
color="dangerlight"
onClick={() => {
if (container.Id) {
disconnectContainer.mutate({
containerId: container.Id,
environmentId,
networkId,
});
}
}}
>
<Icon
icon="trash-2"
feather
class-name="icon-secondary icon-md"
/>
Leave Network
</Button>
</Authorized>
</td>
</tr>
))}
</DetailsTable>
</Table>
</TableContainer>
</div>
</div>
<TableContainer>
<TableTitle label="Containers in network" icon="server" featherIcon />
<Table className="nopadding">
<DetailsTable
headers={tableHeaders}
dataCy="networkDetails-networkContainers"
>
{networkContainers.map((container) => (
<tr key={container.Id}>
<td>
<Link
to="docker.containers.container"
params={{
id: container.Id,
nodeName,
}}
title={container.Name}
>
{container.Name}
</Link>
</td>
<td>{container.IPv4Address || '-'}</td>
<td>{container.IPv6Address || '-'}</td>
<td>{container.MacAddress || '-'}</td>
<td>
<Authorized authorizations="DockerNetworkDisconnect">
<Button
data-cy={`networkDetails-disconnect${container.Name}`}
size="xsmall"
color="dangerlight"
onClick={() => {
if (container.Id) {
disconnectContainer.mutate({
containerId: container.Id,
environmentId,
networkId,
});
}
}}
>
<Icon
icon="trash-2"
feather
class-name="icon-secondary icon-md"
/>
Leave Network
</Button>
</Authorized>
</td>
</tr>
))}
</DetailsTable>
</Table>
</TableContainer>
);
}

View file

@ -29,86 +29,80 @@ export function NetworkDetailsTable({
);
return (
<div className="row">
<div className="col-lg-12 col-md-12 col-xs-12">
<TableContainer>
<TableTitle label="Network details" icon="share-2" featherIcon />
<Table className="nopadding">
<DetailsTable dataCy="networkDetails-detailsTable">
{/* networkRowContent */}
<DetailsTable.Row label="Name">{network.Name}</DetailsTable.Row>
<DetailsTable.Row label="Id">
{network.Id}
{allowRemoveNetwork && (
<Authorized authorizations="DockerNetworkDelete">
<Button
data-cy="networkDetails-deleteNetwork"
size="xsmall"
color="danger"
onClick={() => onRemoveNetworkClicked()}
>
<Icon
icon="trash-2"
feather
className="space-right"
aria-hidden="true"
/>
Delete this network
</Button>
</Authorized>
)}
</DetailsTable.Row>
<DetailsTable.Row label="Driver">
{network.Driver}
</DetailsTable.Row>
<DetailsTable.Row label="Scope">{network.Scope}</DetailsTable.Row>
<DetailsTable.Row label="Attachable">
{String(network.Attachable)}
</DetailsTable.Row>
<DetailsTable.Row label="Internal">
{String(network.Internal)}
</DetailsTable.Row>
<TableContainer>
<TableTitle label="Network details" icon="share-2" featherIcon />
<Table className="nopadding">
<DetailsTable dataCy="networkDetails-detailsTable">
{/* networkRowContent */}
<DetailsTable.Row label="Name">{network.Name}</DetailsTable.Row>
<DetailsTable.Row label="Id">
{network.Id}
{allowRemoveNetwork && (
<Authorized authorizations="DockerNetworkDelete">
<Button
data-cy="networkDetails-deleteNetwork"
size="xsmall"
color="danger"
onClick={() => onRemoveNetworkClicked()}
>
<Icon
icon="trash-2"
feather
className="space-right"
aria-hidden="true"
/>
Delete this network
</Button>
</Authorized>
)}
</DetailsTable.Row>
<DetailsTable.Row label="Driver">{network.Driver}</DetailsTable.Row>
<DetailsTable.Row label="Scope">{network.Scope}</DetailsTable.Row>
<DetailsTable.Row label="Attachable">
{String(network.Attachable)}
</DetailsTable.Row>
<DetailsTable.Row label="Internal">
{String(network.Internal)}
</DetailsTable.Row>
{/* IPV4 ConfigRowContent */}
{ipv4Configs.map((config) => (
<Fragment key={config.Subnet}>
<DetailsTable.Row
label={`IPV4 Subnet${getConfigDetails(config.Subnet)}`}
>
{`IPV4 Gateway${getConfigDetails(config.Gateway)}`}
</DetailsTable.Row>
<DetailsTable.Row
label={`IPV4 IP Range${getConfigDetails(config.IPRange)}`}
>
{`IPV4 Excluded IPs${getAuxiliaryAddresses(
config.AuxiliaryAddresses
)}`}
</DetailsTable.Row>
</Fragment>
))}
{/* IPV4 ConfigRowContent */}
{ipv4Configs.map((config) => (
<Fragment key={config.Subnet}>
<DetailsTable.Row
label={`IPV4 Subnet${getConfigDetails(config.Subnet)}`}
>
{`IPV4 Gateway${getConfigDetails(config.Gateway)}`}
</DetailsTable.Row>
<DetailsTable.Row
label={`IPV4 IP Range${getConfigDetails(config.IPRange)}`}
>
{`IPV4 Excluded IPs${getAuxiliaryAddresses(
config.AuxiliaryAddresses
)}`}
</DetailsTable.Row>
</Fragment>
))}
{/* IPV6 ConfigRowContent */}
{ipv6Configs.map((config) => (
<Fragment key={config.Subnet}>
<DetailsTable.Row
label={`IPV6 Subnet${getConfigDetails(config.Subnet)}`}
>
{`IPV6 Gateway${getConfigDetails(config.Gateway)}`}
</DetailsTable.Row>
<DetailsTable.Row
label={`IPV6 IP Range${getConfigDetails(config.IPRange)}`}
>
{`IPV6 Excluded IPs${getAuxiliaryAddresses(
config.AuxiliaryAddresses
)}`}
</DetailsTable.Row>
</Fragment>
))}
</DetailsTable>
</Table>
</TableContainer>
</div>
</div>
{/* IPV6 ConfigRowContent */}
{ipv6Configs.map((config) => (
<Fragment key={config.Subnet}>
<DetailsTable.Row
label={`IPV6 Subnet${getConfigDetails(config.Subnet)}`}
>
{`IPV6 Gateway${getConfigDetails(config.Gateway)}`}
</DetailsTable.Row>
<DetailsTable.Row
label={`IPV6 IP Range${getConfigDetails(config.IPRange)}`}
>
{`IPV6 Excluded IPs${getAuxiliaryAddresses(
config.AuxiliaryAddresses
)}`}
</DetailsTable.Row>
</Fragment>
))}
</DetailsTable>
</Table>
</TableContainer>
);
function getConfigDetails(configValue?: string) {

View file

@ -15,21 +15,17 @@ export function NetworkOptionsTable({ options }: Props) {
}
return (
<div className="row">
<div className="col-lg-12 col-md-12 col-xs-12">
<TableContainer>
<TableTitle label="Network options" icon="share-2" featherIcon />
<Table className="nopadding">
<DetailsTable dataCy="networkDetails-networkOptionsTable">
{networkEntries.map(([key, value]) => (
<DetailsTable.Row key={key} label={key}>
{value}
</DetailsTable.Row>
))}
</DetailsTable>
</Table>
</TableContainer>
</div>
</div>
<TableContainer>
<TableTitle label="Network options" icon="share-2" featherIcon />
<Table className="nopadding">
<DetailsTable dataCy="networkDetails-networkOptionsTable">
{networkEntries.map(([key, value]) => (
<DetailsTable.Row key={key} label={key}>
{value}
</DetailsTable.Row>
))}
</DetailsTable>
</Table>
</TableContainer>
);
}

View file

@ -1,4 +1,6 @@
import _ from 'lodash';
import { useStore } from 'zustand';
import { Box } from 'react-feather';
import { DockerContainer } from '@/react/docker/containers/types';
import { Environment } from '@/react/portainer/environments/types';
@ -14,12 +16,14 @@ import {
QuickActionsSettings,
} from '@@/datatables/QuickActionsSettings';
import { ColumnVisibilityMenu } from '@@/datatables/ColumnVisibilityMenu';
import { useSearchBarState } from '@@/datatables/SearchBar';
import { TableSettingsProvider } from '@@/datatables/useTableSettings';
import { useContainers } from '../../containers/queries/containers';
import { RowProvider } from '../../containers/ListView/ContainersDatatable/RowContext';
const storageKey = 'stack-containers';
const useStore = createStore(storageKey);
const settingsStore = createStore(storageKey);
const actions = [
buildAction('logs', 'Logs'),
@ -35,9 +39,12 @@ export interface Props {
}
export function StackContainersDatatable({ environment, stackName }: Props) {
const settings = useStore();
const settings = useStore(settingsStore);
const [search, setSearch] = useSearchBarState(storageKey);
const isGPUsColumnVisible = useShowGPUsColumn(environment.Id);
const columns = useColumns(false, isGPUsColumnVisible);
const hidableColumns = _.compact(
columns.filter((col) => col.canHide).map((col) => col.id)
);
@ -53,49 +60,53 @@ export function StackContainersDatatable({ environment, stackName }: Props) {
return (
<RowProvider context={{ environment }}>
<Datatable
titleOptions={{
icon: 'fa-cubes',
title: 'Containers',
}}
settingsStore={settings}
columns={columns}
renderTableActions={(selectedRows) => (
<ContainersDatatableActions
selectedItems={selectedRows}
isAddActionVisible={false}
endpointId={environment.Id}
/>
)}
initialTableState={{ hiddenColumns: settings.hiddenColumns }}
renderTableSettings={(tableInstance) => {
const columnsToHide = tableInstance.allColumns.filter((colInstance) =>
hidableColumns?.includes(colInstance.id)
);
<TableSettingsProvider settings={settingsStore}>
<Datatable
title="Containers"
titleIcon={Box}
initialPageSize={settings.pageSize}
onPageSizeChange={settings.setPageSize}
initialSortBy={settings.sortBy}
onSortByChange={settings.setSortBy}
searchValue={search}
onSearchChange={setSearch}
columns={columns}
renderTableActions={(selectedRows) => (
<ContainersDatatableActions
selectedItems={selectedRows}
isAddActionVisible={false}
endpointId={environment.Id}
/>
)}
initialTableState={{ hiddenColumns: settings.hiddenColumns }}
renderTableSettings={(tableInstance) => {
const columnsToHide = tableInstance.allColumns.filter(
(colInstance) => hidableColumns?.includes(colInstance.id)
);
return (
<>
<ColumnVisibilityMenu<DockerContainer>
columns={columnsToHide}
onChange={(hiddenColumns) => {
settings.setHiddenColumns(hiddenColumns);
tableInstance.setHiddenColumns(hiddenColumns);
}}
value={settings.hiddenColumns}
/>
<TableSettingsMenu
quickActions={<QuickActionsSettings actions={actions} />}
>
<ContainersDatatableSettings settings={settings} />
</TableSettingsMenu>
</>
);
}}
storageKey={storageKey}
dataset={containersQuery.data || []}
isLoading={containersQuery.isLoading}
emptyContentLabel="No containers found"
/>
return (
<>
<ColumnVisibilityMenu<DockerContainer>
columns={columnsToHide}
onChange={(hiddenColumns) => {
settings.setHiddenColumns(hiddenColumns);
tableInstance.setHiddenColumns(hiddenColumns);
}}
value={settings.hiddenColumns}
/>
<TableSettingsMenu
quickActions={<QuickActionsSettings actions={actions} />}
>
<ContainersDatatableSettings settings={settings} />
</TableSettingsMenu>
</>
);
}}
dataset={containersQuery.data || []}
isLoading={containersQuery.isLoading}
emptyContentLabel="No containers found"
/>
</TableSettingsProvider>
</RowProvider>
);
}