import { PermissionAction } from '@supabase/shared-types/out/constants' import { useParams } from 'common' import { isNull, partition } from 'lodash' import { AlertCircle, Search } from 'lucide-react' import { useEffect, useRef, useState } from 'react' import { Card, InputGroup, InputGroupAddon, InputGroupInput, ShadowScrollArea, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from 'ui' import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader' import { ExtensionRow } from './ExtensionRow' import { HIDDEN_EXTENSIONS, SEARCH_TERMS } from './Extensions.constants' import InformationBox from '@/components/ui/InformationBox' import { NoSearchResults } from '@/components/ui/NoSearchResults' import { useDatabaseExtensionsQuery } from '@/data/database-extensions/database-extensions-query' import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject' import { onSearchInputEscape } from '@/lib/keyboard' import { SHORTCUT_IDS } from '@/state/shortcuts/registry' import { useShortcut } from '@/state/shortcuts/useShortcut' export const Extensions = () => { const { filter } = useParams() const { data: project } = useSelectedProjectQuery() const [filterString, setFilterString] = useState('') const searchInputRef = useRef(null) useShortcut( SHORTCUT_IDS.LIST_PAGE_FOCUS_SEARCH, () => { searchInputRef.current?.focus() searchInputRef.current?.select() }, { label: 'Search extensions' } ) useShortcut(SHORTCUT_IDS.LIST_PAGE_RESET_FILTERS, () => { setFilterString('') }) const { data = [], isPending: isLoading } = useDatabaseExtensionsQuery({ projectRef: project?.ref, connectionString: project?.connectionString, }) const visibleExtensions = data.filter((ext) => !HIDDEN_EXTENSIONS.includes(ext.name)) const extensions = filterString.length === 0 ? visibleExtensions : visibleExtensions.filter((ext) => { const nameMatchesSearch = ext.name.toLowerCase().includes(filterString.toLowerCase()) const searchTermsMatchesSearch = (SEARCH_TERMS[ext.name] || []).some((x) => x.includes(filterString.toLowerCase()) ) return nameMatchesSearch || searchTermsMatchesSearch }) const [enabledExtensions, disabledExtensions] = partition( extensions, (ext) => !isNull(ext.installed_version) ) const { can: canUpdateExtensions, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissions( PermissionAction.TENANT_SQL_ADMIN_WRITE, 'extensions' ) useEffect(() => { if (filter !== undefined) setFilterString(filter as string) }, [filter]) return ( <>
setFilterString(e.target.value)} onKeyDown={onSearchInputEscape(filterString, setFilterString)} />
{isPermissionsLoaded && !canUpdateExtensions && ( } title="You need additional permissions to update database extensions" /> )} {isLoading ? ( ) : ( Name Version Schema Description Used by Links {/* [Joshen] All these classes are just to make the last column sticky I reckon we can pull these out into the Table component where we can declare sticky columns via props, but we can do that if we start to have more tables in the dashboard with sticky columns */}
Enabled
{[...enabledExtensions, ...disabledExtensions].map((extension) => ( ))} {extensions.length === 0 && ( setFilterString('')} /> )}
)} ) }