import { CellContext, createColumnHelper } from '@tanstack/react-table'; import { Layers } from 'lucide-react'; import { useMemo, useState } from 'react'; import { humanize } from '@/portainer/filters/filters'; import { pluralize } from '@/portainer/helpers/strings'; import { Button } from '@@/buttons'; import { Datatable } from '@@/datatables'; import { useTableStateWithoutStorage } from '@@/datatables/useTableState'; import { Modal } from '@@/modals'; import { ModalBody } from '@@/modals/Modal/ModalBody'; import { NodeRowData } from '../types'; import { columnHelper } from './helper'; interface CachedImageRow { id: string; image: string; aliases: string; aliasesCount: number; sizeBytes: number; } type NodeImage = NonNullable< NonNullable['images'] >[number]; const imageColumnHelper = createColumnHelper(); const columns = [ imageColumnHelper.accessor('image', { id: 'image', header: 'Image', meta: { width: '70%', }, cell: ({ getValue }) => { const imageName = getValue(); return ( {imageName} ); }, }), imageColumnHelper.accessor('aliases', { id: 'aliases', header: 'Aliases', sortingFn: (left, right) => left.original.aliasesCount - right.original.aliasesCount, meta: { width: '15%', className: 'whitespace-nowrap', }, cell: ({ row }) => { const { aliasesCount } = row.original; return ( {aliasesCount} alias{aliasesCount !== 1 ? 'es' : ''} ); }, }), imageColumnHelper.accessor('sizeBytes', { id: 'sizeBytes', header: 'Size', sortingFn: 'alphanumeric', meta: { width: '15%', className: 'whitespace-nowrap', }, cell: ({ getValue }) => ( {humanize(getValue() ?? 0)} ), }), ]; export const cachedImages = columnHelper.accessor( (node) => node.status?.images?.length ?? 0, { id: 'cachedImages', header: 'Cached Images', cell: (props) => ( ), } ); function CachedImagesCell({ row: { original: node }, }: CellContext) { const [isModalOpen, setIsModalOpen] = useState(false); const nodeName = node.metadata?.name ?? 'node'; const images: NodeImage[] = node.status?.images ?? []; return ( <> {isModalOpen && ( setIsModalOpen(false)} /> )} ); } function CachedImagesModal({ nodeName, images, onDismiss, }: { nodeName: string; images: NodeImage[]; onDismiss: () => void; }) { const tableState = useTableStateWithoutStorage('sizeBytes', true); const rows = useMemo( () => [...images] .sort((left, right) => (right.sizeBytes ?? 0) - (left.sizeBytes ?? 0)) .map((image, index) => { const names = image.names ?? []; const primaryName = names[0] ?? ''; const aliases = names.slice(1).join(' '); const sizeBytes = image.sizeBytes ?? 0; return { id: `${primaryName}-${sizeBytes}-${index}`, image: primaryName, aliases, aliasesCount: Math.max(names.length - 1, 0), sizeBytes, }; }), [images] ); const totalSizeBytes = useMemo( () => rows.reduce((current, image) => current + image.sizeBytes, 0), [rows] ); return ( {images.length === 0 && (
No cached images reported on this node.
)} {images.length > 0 && ( disableSelect dataset={rows} columns={columns} settingsManager={tableState} title={`Cached Images on ${nodeName}`} titleIcon={Layers} description={`${pluralize(rows.length, 'image')}, ${humanize( totalSizeBytes )} total`} getRowId={(row) => row.id} data-cy="cached-images-datatable" /> )}
); }