1
0
Fork 0
mirror of https://github.com/portainer/portainer.git synced 2025-07-24 07:49:41 +02:00

refactor(kube/apps): migrate stacks table to react [EE-4661] (#10091)

This commit is contained in:
Chaim Lev-Ari 2023-09-20 09:04:26 +03:00 committed by GitHub
parent a5f60c64ef
commit 25d5e62f5c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
31 changed files with 516 additions and 565 deletions

View file

@ -0,0 +1,105 @@
import { List } from 'lucide-react';
import { useAuthorizations } from '@/react/hooks/useUser';
import { SystemResourceDescription } from '@/react/kubernetes/datatables/SystemResourceDescription';
import { systemResourcesSettings } from '@/react/kubernetes/datatables/SystemResourcesSettings';
import { ExpandableDatatable } from '@@/datatables/ExpandableDatatable';
import { createPersistedStore, refreshableSettings } from '@@/datatables/types';
import { useRepeater } from '@@/datatables/useRepeater';
import { useTableState } from '@@/datatables/useTableState';
import { InsightsBox } from '@@/InsightsBox';
import { KubernetesStack } from '../../types';
import { columns } from './columns';
import { SubRows } from './SubRows';
import { Namespace, TableSettings } from './types';
import { StacksSettingsMenu } from './StacksSettingsMenu';
import { NamespaceFilter } from './NamespaceFilter';
import { TableActions } from './TableActions';
const storageKey = 'kubernetes.applications.stacks';
const settingsStore = createPersistedStore<TableSettings>(
storageKey,
'name',
(set) => ({
...systemResourcesSettings(set),
...refreshableSettings(set),
})
);
interface Props {
dataset: Array<KubernetesStack>;
onRemove(selectedItems: Array<KubernetesStack>): void;
onRefresh(): Promise<void>;
namespace?: string;
namespaces: Array<Namespace>;
onNamespaceChange(namespace: string): void;
isLoading?: boolean;
}
export function ApplicationsStacksDatatable({
dataset,
onRemove,
onRefresh,
namespace = '',
namespaces,
onNamespaceChange,
isLoading,
}: Props) {
const tableState = useTableState(settingsStore, storageKey);
const authorized = useAuthorizations('K8sApplicationsW');
useRepeater(tableState.autoRefreshRate, onRefresh);
return (
<ExpandableDatatable
getRowCanExpand={(row) => row.original.Applications.length > 0}
title="Stacks"
titleIcon={List}
dataset={dataset}
isLoading={isLoading}
columns={columns}
settingsManager={tableState}
disableSelect={!authorized}
renderSubRow={(row) => (
<SubRows stack={row.original} span={row.getVisibleCells().length} />
)}
noWidget
emptyContentLabel="No stack available."
description={
<div className="w-full">
<div className="min-w-[140px] float-right">
<NamespaceFilter
namespaces={namespaces}
value={namespace}
onChange={onNamespaceChange}
showSystem={tableState.showSystemResources}
/>
</div>
<div className="space-y-2">
<SystemResourceDescription
showSystemResources={tableState.showSystemResources}
/>
<div className="w-fit">
<InsightsBox
type="slim"
header="From 2.18 on, you can filter this view by namespace."
insightCloseId="k8s-namespace-filtering"
/>
</div>
</div>
</div>
}
renderTableActions={(selectedItems) => (
<TableActions selectedItems={selectedItems} onRemove={onRemove} />
)}
renderTableSettings={() => <StacksSettingsMenu settings={tableState} />}
getRowId={(row) => `${row.Name}-${row.ResourcePool}`}
/>
);
}

View file

@ -0,0 +1,61 @@
import { Filter } from 'lucide-react';
import { useEffect } from 'react';
import { Icon } from '@@/Icon';
import { Select } from '@@/form-components/Input';
import { InputGroup } from '@@/form-components/InputGroup';
import { Namespace } from './types';
function transformNamespaces(namespaces: Namespace[], showSystem: boolean) {
return namespaces
.filter((ns) => showSystem || !ns.IsSystem)
.map(({ Name, IsSystem }) => ({
label: IsSystem ? `${Name} - system` : Name,
value: Name,
}));
}
export function NamespaceFilter({
namespaces,
value,
onChange,
showSystem,
}: {
namespaces: Namespace[];
value: string;
onChange: (value: string) => void;
showSystem: boolean;
}) {
const transformedNamespaces = transformNamespaces(namespaces, showSystem);
// sync value with displayed namespaces
useEffect(() => {
const names = transformedNamespaces.map((ns) => ns.value);
if (value && !names.find((ns) => ns === value)) {
onChange(
names.length > 0 ? names.find((ns) => ns === 'default') || names[0] : ''
);
}
}, [value, onChange, transformedNamespaces]);
return (
<InputGroup>
<InputGroup.Addon>
<div className="flex items-center gap-1">
<Icon icon={Filter} />
Namespace
</div>
</InputGroup.Addon>
<Select
className="!h-[30px] py-1"
value={value || ''}
onChange={(e) => onChange(e.target.value)}
options={[
{ label: 'All namespaces', value: '' },
...transformedNamespaces,
]}
/>
</InputGroup>
);
}

View file

@ -0,0 +1,22 @@
import { SystemResourcesSettings } from '@/react/kubernetes/datatables/SystemResourcesSettings';
import { TableSettingsMenu } from '@@/datatables';
import { TableSettingsMenuAutoRefresh } from '@@/datatables/TableSettingsMenuAutoRefresh';
import { type TableSettings } from './types';
export function StacksSettingsMenu({ settings }: { settings: TableSettings }) {
return (
<TableSettingsMenu>
<SystemResourcesSettings
value={settings.showSystemResources}
onChange={(value) => settings.setShowSystemResources(value)}
/>
<TableSettingsMenuAutoRefresh
onChange={settings.setAutoRefreshRate}
value={settings.autoRefreshRate}
/>
</TableSettingsMenu>
);
}

View file

@ -0,0 +1,46 @@
import clsx from 'clsx';
import KubernetesApplicationHelper from '@/kubernetes/helpers/application';
import KubernetesNamespaceHelper from '@/kubernetes/helpers/namespaceHelper';
import { Link } from '@@/Link';
import { KubernetesStack } from '../../types';
export function SubRows({
stack,
span,
}: {
stack: KubernetesStack;
span: number;
}) {
return (
<>
{stack.Applications.map((app) => (
<tr
className={clsx({
'datatable-highlighted': stack.Highlighted,
'datatable-unhighlighted': !stack.Highlighted,
})}
key={app.Name}
>
<td />
<td colSpan={span - 1}>
<Link
to="kubernetes.applications.application"
params={{ name: app.Name, namespace: app.ResourcePool }}
>
{app.Name}
</Link>
{KubernetesNamespaceHelper.isSystemNamespace(app.ResourcePool) &&
KubernetesApplicationHelper.isExternalApplication(app) && (
<span className="space-left label label-primary image-tag">
external
</span>
)}
</td>
</tr>
))}
</>
);
}

View file

@ -0,0 +1,29 @@
import { Trash2 } from 'lucide-react';
import { Authorized } from '@/react/hooks/useUser';
import { Button } from '@@/buttons';
import { KubernetesStack } from '../../types';
export function TableActions({
selectedItems,
onRemove,
}: {
selectedItems: Array<KubernetesStack>;
onRemove: (selectedItems: Array<KubernetesStack>) => void;
}) {
return (
<Authorized authorizations="K8sApplicationsW">
<Button
disabled={selectedItems.length === 0}
color="dangerlight"
onClick={() => onRemove(selectedItems)}
icon={Trash2}
data-cy="k8sApp-removeStackButton"
>
Remove
</Button>
</Authorized>
);
}

View file

@ -0,0 +1,62 @@
import { FileText } from 'lucide-react';
import { createColumnHelper } from '@tanstack/react-table';
import KubernetesNamespaceHelper from '@/kubernetes/helpers/namespaceHelper';
import { buildExpandColumn } from '@@/datatables/expand-column';
import { Link } from '@@/Link';
import { Icon } from '@@/Icon';
import { KubernetesStack } from '../../types';
export const columnHelper = createColumnHelper<KubernetesStack>();
export const columns = [
buildExpandColumn<KubernetesStack>(),
columnHelper.accessor('Name', {
id: 'name',
header: 'Stack',
}),
columnHelper.accessor('ResourcePool', {
id: 'namespace',
header: 'Namespace',
cell: ({ getValue }) => {
const value = getValue();
return (
<>
<Link
to="kubernetes.resourcePools.resourcePool"
params={{ id: value }}
>
{value}
</Link>
{KubernetesNamespaceHelper.isSystemNamespace(value) && (
<span className="label label-info image-tag label-margins">
system
</span>
)}
</>
);
},
}),
columnHelper.accessor((row) => row.Applications.length, {
id: 'applications',
header: 'Applications',
}),
columnHelper.display({
id: 'actions',
header: 'Actions',
cell: ({ row: { original: item } }) => (
<Link
to="kubernetes.stacks.stack.logs"
params={{ namespace: item.ResourcePool, name: item.Name }}
className="flex items-center gap-1"
>
<Icon icon={FileText} />
Logs
</Link>
),
}),
];

View file

@ -0,0 +1 @@
export { ApplicationsStacksDatatable } from './ApplicationsStacksDatatable';

View file

@ -0,0 +1,18 @@
import { SystemResourcesTableSettings } from '@/react/kubernetes/datatables/SystemResourcesSettings';
import {
BasicTableSettings,
RefreshableTableSettings,
} from '@@/datatables/types';
export interface TableSettings
extends BasicTableSettings,
RefreshableTableSettings,
SystemResourcesTableSettings {}
export interface Namespace {
Id: string;
Name: string;
Yaml: string;
IsSystem?: boolean;
}