import { PermissionAction } from '@supabase/shared-types/out/constants' import { useParams } from 'common' import { sortBy } from 'lodash' import { RefreshCw, Search, X } from 'lucide-react' import { parseAsBoolean, useQueryState } from 'nuqs' import { useEffect, useMemo, useState } from 'react' import DataGrid, { Row } from 'react-data-grid' import { Button, cn, LoadingLine, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from 'ui' import { Input } from 'ui-patterns/DataInputs/Input' import { AddNewSecretModal } from './AddNewSecretModal' import { DeleteSecretModal } from './DeleteSecretModal' import { EditSecretModal } from './EditSecretModal' import { formatSecretColumns } from './Secrets.utils' import AlertError from '@/components/ui/AlertError' import { ButtonTooltip } from '@/components/ui/ButtonTooltip' import { DocsButton } from '@/components/ui/DocsButton' import { useVaultSecretsQuery } from '@/data/vault/vault-secrets-query' import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject' import { DOCS_URL } from '@/lib/constants' import type { VaultSecret } from '@/types' export const SecretsManagement = () => { const { search } = useParams() const { data: project } = useSelectedProjectQuery() const [searchValue, setSearchValue] = useState('') const [, setShowAddSecretModal] = useQueryState('new', parseAsBoolean.withDefault(false)) const [selectedSort, setSelectedSort] = useState<'updated_at' | 'name'>('updated_at') const { can: canManageSecrets } = useAsyncCheckPermissions( PermissionAction.TENANT_SQL_ADMIN_WRITE, 'tables' ) const { data, error, isError, isPending: isLoading, isRefetching, refetch, } = useVaultSecretsQuery({ projectRef: project?.ref, connectionString: project?.connectionString, }) const allSecrets = useMemo(() => data || [], [data]) const secrets = useMemo(() => { const filtered = searchValue.length > 0 ? allSecrets.filter( (secret) => (secret?.name ?? '').toLowerCase().includes(searchValue.trim().toLowerCase()) || (secret?.id ?? '').toLowerCase().includes(searchValue.trim().toLowerCase()) ) : allSecrets if (selectedSort === 'updated_at') { return sortBy(filtered, (s) => Number(new Date(s.updated_at))).reverse() } return sortBy(filtered, (s) => (s.name || '').toLowerCase()) }, [allSecrets, searchValue, selectedSort]) const columns = useMemo(() => formatSecretColumns(), []) useEffect(() => { if (search !== undefined) setSearchValue(search) }, [search]) return ( <>
} value={searchValue ?? ''} onChange={(e) => setSearchValue(e.target.value)} actions={[ searchValue && (
setShowAddSecretModal(true)} tooltip={{ content: { side: 'bottom', text: !canManageSecrets ? 'You need additional permissions to add secrets' : undefined, }, }} > Add new secret
{isError ? (
) : ( row.id} rowClass={() => { return cn( 'cursor-pointer', '[&>.rdg-cell]:border-box [&>.rdg-cell]:outline-hidden [&>.rdg-cell]:shadow-none', '[&>.rdg-cell:first-child>div]:pl-8' ) }} renderers={{ renderRow(_, props) { return }, }} /> )} {secrets.length === 0 && !isLoading && !isError ? (

{searchValue ? 'No secrets found' : 'No secrets added yet'}

{searchValue ? `There are currently no secrets based on the search "${searchValue}"` : 'The Vault allows you to store sensitive information like API keys'}

) : null}
) }