import { PermissionAction } from '@supabase/shared-types/out/constants' import dayjs from 'dayjs' import { sortBy } from 'lodash' import { Fragment, useEffect, useState } from 'react' import { toast } from 'sonner' import { useParams } from 'common' import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext' import { ButtonTooltip } from 'components/ui/ButtonTooltip' import { DocsButton } from 'components/ui/DocsButton' import { usePgSodiumKeyCreateMutation } from 'data/pg-sodium-keys/pg-sodium-key-create-mutation' import { usePgSodiumKeyDeleteMutation } from 'data/pg-sodium-keys/pg-sodium-key-delete-mutation' import { usePgSodiumKeysQuery } from 'data/pg-sodium-keys/pg-sodium-keys-query' import { useCheckPermissions } from 'hooks/misc/useCheckPermissions' import { Key, Loader, Search, Trash, X } from 'lucide-react' import { Button, Form, Input, Listbox, Modal, Separator } from 'ui' import { Admonition } from 'ui-patterns' const DEFAULT_KEY_NAME = 'No description provided' export const EncryptionKeysManagement = () => { const { search } = useParams() const { project } = useProjectContext() const [searchValue, setSearchValue] = useState('') const [selectedSort, setSelectedSort] = useState<'name' | 'created'>('created') const [showAddKeyModal, setShowAddKeyModal] = useState(false) const [selectedKeyToRemove, setSelectedKeyToRemove] = useState() const canManageKeys = useCheckPermissions(PermissionAction.TENANT_SQL_ADMIN_WRITE, 'tables') useEffect(() => { if (search !== undefined) setSearchValue(search) }, [search]) const { data, isLoading } = usePgSodiumKeysQuery({ projectRef: project?.ref, connectionString: project?.connectionString, }) const { mutate: addKeyMutation, isLoading: isCreating } = usePgSodiumKeyCreateMutation({ onSuccess: () => { toast.success('Successfully added new key') setShowAddKeyModal(false) }, }) const { mutate: deleteKeyMutation, isLoading: isDeleting } = usePgSodiumKeyDeleteMutation({ onSuccess: () => { toast.success(`Successfully deleted encryption key`) setSelectedKeyToRemove(undefined) }, }) const allKeys = data || [] const keys = sortBy( searchValue ? allKeys.filter( (key) => (key?.name ?? '').toLowerCase().includes(searchValue.toLowerCase()) || key.id.toLowerCase().includes(searchValue.toLowerCase()) ) : allKeys, (k) => { if (selectedSort === 'created') { return Number(new Date(k.created)) } else { return k[selectedSort] } } ) const addKey = async (values: any, { setSubmitting }: any) => { if (!project) return console.error('Project is required') addKeyMutation({ projectRef: project.ref, connectionString: project.connectionString, name: values.name, }) } const confirmDeleteKey = async () => { if (!selectedKeyToRemove) return if (!project) return console.error('Project is required') deleteKeyMutation({ projectRef: project.ref, connectionString: project.connectionString, id: selectedKeyToRemove.id, }) } return ( <>
setSearchValue(event.target.value)} icon={} actions={ searchValue.length > 0 ? [
setShowAddKeyModal(true)} tooltip={{ content: { side: 'bottom', text: !canManageKeys ? 'You need additional permissions to add keys' : undefined, }, }} > Add new key
{/* Table of keys */}
{isLoading ? (

Loading keys from the Vault

) : ( <> {keys.map((key, idx) => { return (

{key.name || DEFAULT_KEY_NAME}

ID: {key.id}

Added on {dayjs(key.created).format('MMM D, YYYY')}

} disabled={!canManageKeys} onClick={() => setSelectedKeyToRemove(key)} tooltip={{ content: { side: 'bottom', text: !canManageKeys ? 'You need additional permissions to delete keys' : undefined, }, }} />
{idx !== keys.length - 1 && }
) })} {keys.length === 0 && ( <> {searchValue.length === 0 ? (

No encryption keys added yet

Encryption keys are created by the pgsodium extension and can be used to encrypt your columns and secrets

) : (

No results found

Your search for "{searchValue}" did not return any results

)} )} )}
setSelectedKeyToRemove(undefined)} onConfirm={confirmDeleteKey} loading={isDeleting} header="Confirm to delete key" >

The following key will be permanently removed and cannot be recovered.

{selectedKeyToRemove?.name ?? DEFAULT_KEY_NAME}

ID: {selectedKeyToRemove?.id}

setShowAddKeyModal(false)} header="Add a new key" >
{ const errors: any = {} if (values.name.length === 0) errors.name = 'Please provide a name for your key' return errors }} onSubmit={addKey} > {() => { return ( <>

Provide a name for your key for easier identification.

) }}
) }