From 4bd1bb0ea6076766b4e71cdd06f86f77e53e2fb4 Mon Sep 17 00:00:00 2001 From: Alex Freska Date: Wed, 16 Oct 2024 10:04:06 -0400 Subject: [PATCH] feat(renterd): keys batch operations --- .../components/Keys/KeyContextMenu.tsx | 2 +- .../Keys/KeysBatchMenu/KeysBatchDelete.tsx | 79 +++++++++++++++++++ .../components/Keys/KeysBatchMenu/index.tsx | 21 +++++ apps/renterd/components/Keys/index.tsx | 4 + apps/renterd/contexts/keys/columns.tsx | 29 +++++-- apps/renterd/contexts/keys/index.tsx | 30 +++++++ apps/renterd/contexts/keys/types.ts | 11 ++- 7 files changed, 169 insertions(+), 7 deletions(-) create mode 100644 apps/renterd/components/Keys/KeysBatchMenu/KeysBatchDelete.tsx create mode 100644 apps/renterd/components/Keys/KeysBatchMenu/index.tsx diff --git a/apps/renterd/components/Keys/KeyContextMenu.tsx b/apps/renterd/components/Keys/KeyContextMenu.tsx index bfbf3afdd..aa68ed451 100644 --- a/apps/renterd/components/Keys/KeyContextMenu.tsx +++ b/apps/renterd/components/Keys/KeyContextMenu.tsx @@ -54,7 +54,7 @@ export function KeyContextMenu({ s3Key, contentProps, buttonProps }: Props) { return ( + } diff --git a/apps/renterd/components/Keys/KeysBatchMenu/KeysBatchDelete.tsx b/apps/renterd/components/Keys/KeysBatchMenu/KeysBatchDelete.tsx new file mode 100644 index 000000000..c27a19517 --- /dev/null +++ b/apps/renterd/components/Keys/KeysBatchMenu/KeysBatchDelete.tsx @@ -0,0 +1,79 @@ +import { + Button, + Paragraph, + triggerSuccessToast, + triggerErrorToast, +} from '@siafoundation/design-system' +import { Delete16 } from '@siafoundation/react-icons' +import { + useSettingsS3, + useSettingsS3Update, +} from '@siafoundation/renterd-react' +import { useCallback, useMemo } from 'react' +import { omit } from '@technically/lodash' +import { useDialog } from '../../../contexts/dialog' +import { useKeys } from '../../../contexts/keys' + +export function KeysBatchDelete() { + const { selectionMap, deselect } = useKeys() + + const ids = useMemo( + () => Object.entries(selectionMap).map(([_, item]) => item.id), + [selectionMap] + ) + const keys = useMemo( + () => Object.entries(selectionMap).map(([_, item]) => item.key), + [selectionMap] + ) + const { openConfirmDialog } = useDialog() + const settingsS3 = useSettingsS3() + const settingsS3Update = useSettingsS3Update() + const deleteKeys = useCallback(async () => { + if (!settingsS3.data) { + triggerErrorToast({ title: 'Error deleting key' }) + return + } + const newKeys = omit(settingsS3.data?.authentication.v4Keypairs, keys) + const response = await settingsS3Update.put({ + payload: { + ...settingsS3.data, + authentication: { + ...settingsS3.data.authentication, + v4Keypairs: newKeys, + }, + }, + }) + deselect(ids) + if (response.error) { + triggerErrorToast({ title: 'Error deleting keys', body: response.error }) + } else { + triggerSuccessToast({ title: `Keys deleted` }) + } + }, [settingsS3.data, settingsS3Update, deselect, keys, ids]) + + return ( + + ) +} diff --git a/apps/renterd/components/Keys/KeysBatchMenu/index.tsx b/apps/renterd/components/Keys/KeysBatchMenu/index.tsx new file mode 100644 index 000000000..973c4369e --- /dev/null +++ b/apps/renterd/components/Keys/KeysBatchMenu/index.tsx @@ -0,0 +1,21 @@ +import { MultiSelectionMenu } from '@siafoundation/design-system' +import { useKeys } from '../../../contexts/keys' +import { KeysBatchDelete } from './KeysBatchDelete' + +export function KeysBatchMenu() { + const { selectionCount, isPageAllSelected, pageCount, deselectAll } = + useKeys() + + return ( + 0} + selectionCount={selectionCount} + isPageAllSelected={isPageAllSelected} + deselectAll={deselectAll} + pageCount={pageCount} + entityWord="key" + > + + + ) +} diff --git a/apps/renterd/components/Keys/index.tsx b/apps/renterd/components/Keys/index.tsx index 392f077f7..c86673bdb 100644 --- a/apps/renterd/components/Keys/index.tsx +++ b/apps/renterd/components/Keys/index.tsx @@ -8,6 +8,7 @@ import { StateNoneYet } from './StateNoneYet' import { KeysActionsMenu } from './KeysActionsMenu' import { StateError } from './StateError' import { useKeys } from '../../contexts/keys' +import { KeysBatchMenu } from './KeysBatchMenu' export function Keys() { const { openDialog } = useDialog() @@ -20,6 +21,7 @@ export function Keys() { toggleSort, limit, dataState, + cellContext, } = useKeys() return ( @@ -31,6 +33,7 @@ export function Keys() { actions={} >
+ & { +type KeysTableColumn = TableColumn & { fixed?: boolean category?: string } @@ -12,8 +17,22 @@ export const columns: KeysTableColumn[] = [ id: 'actions', label: '', fixed: true, - cellClassName: 'w-[50px] !pl-2 !pr-4 [&+*]:!pl-0', - render: ({ data: { key } }) => , + contentClassName: '!pl-3 !pr-4', + cellClassName: 'w-[20px] !pl-0 !pr-0', + heading: ({ context: { onSelectPage, isPageAllSelected } }) => ( + + + + ), + render: ({ data: { id, key }, context: { selectionMap, onSelect } }) => ( + + onSelect(id, e)} + checked={!!selectionMap[id]} + /> + + + ), }, { id: 'key', diff --git a/apps/renterd/contexts/keys/index.tsx b/apps/renterd/contexts/keys/index.tsx index fc7339c36..4fa91c7a9 100644 --- a/apps/renterd/contexts/keys/index.tsx +++ b/apps/renterd/contexts/keys/index.tsx @@ -3,10 +3,12 @@ import { useDatasetEmptyState, useClientFilters, useClientFilteredDataset, + useMultiSelect, } from '@siafoundation/design-system' import { useRouter } from 'next/router' import { createContext, useContext, useMemo } from 'react' import { + CellContext, KeyData, columnsDefaultVisible, defaultSortField, @@ -99,6 +101,27 @@ function useKeysMain() { filters ) + const { + onSelect, + deselect, + deselectAll, + selectionMap, + selectionCount, + onSelectPage, + isPageAllSelected, + } = useMultiSelect(dataset) + + const cellContext = useMemo( + () => + ({ + selectionMap, + onSelect, + onSelectPage, + isPageAllSelected, + } as CellContext), + [selectionMap, onSelect, onSelectPage, isPageAllSelected] + ) + return { dataState, limit, @@ -109,6 +132,13 @@ function useKeysMain() { datasetCount: dataset?.length || 0, datasetFilteredCount: datasetFiltered?.length || 0, columns: filteredTableColumns, + selectionMap, + selectionCount, + onSelectPage, + isPageAllSelected, + deselect, + deselectAll, + cellContext, dataset, datasetPage, configurableColumns, diff --git a/apps/renterd/contexts/keys/types.ts b/apps/renterd/contexts/keys/types.ts index 6981dc5c1..b6e51c16a 100644 --- a/apps/renterd/contexts/keys/types.ts +++ b/apps/renterd/contexts/keys/types.ts @@ -1,10 +1,19 @@ +import { MouseEvent } from 'react' + export type KeyData = { id: string key: string secret: string } -export type TableColumnId = 'actions' | 'key' | 'secret' +export type CellContext = { + selectionMap: Record + onSelect: (id: string, e: MouseEvent) => void + onSelectPage: () => void + isPageAllSelected: boolean | 'indeterminate' +} + +export type TableColumnId = 'selection' | 'actions' | 'key' | 'secret' export const columnsDefaultVisible: TableColumnId[] = ['key', 'secret']