// @ts-nocheck import { PermissionAction } from '@supabase/shared-types/out/constants' import { useParams } from 'common' import { Realtime } from 'icons' import { BookOpenText, Lightbulb, Lock, MoreVertical, PlusCircle, Unlock } from 'lucide-react' import Link from 'next/link' import { parseAsBoolean, useQueryState } from 'nuqs' import { useState } from 'react' import { toast } from 'sonner' import { Button, cn, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, Popover, PopoverContent, PopoverTrigger, Tooltip, TooltipContent, TooltipTrigger, } from 'ui' import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal' import { EnableIndexAdvisorDialog } from '../QueryPerformance/IndexAdvisor/EnableIndexAdvisorButton' import { RoleImpersonationPopover } from '../RoleImpersonationSelector/RoleImpersonationPopover' import { InsertButton } from './InsertButton' import { RealtimeToggleDialog } from './RealtimeToggleDialog' import { SecurityDefinerViewPopover } from './SecurityDefinerViewPopover' import { ViewEntityAutofixSecurityModal } from './ViewEntityAutofixSecurityModal' import { RefreshButton } from '@/components/grid/components/header/RefreshButton' import { useTableIndexAdvisor } from '@/components/grid/context/TableIndexAdvisorContext' import { getEntityLintDetails, getTablePoliciesUrl, } from '@/components/interfaces/TableGridEditor/TableEntity.utils' import { ButtonTooltip } from '@/components/ui/ButtonTooltip' import { useDatabasePoliciesQuery } from '@/data/database-policies/database-policies-query' import { useIsTableRealtimeEnabled } from '@/data/database-publications/database-publications-query' import { useProjectLintsQuery } from '@/data/lint/lint-query' import { Entity, isTableLike, isForeignTable as isTableLikeForeignTable, isMaterializedView as isTableLikeMaterializedView, isView as isTableLikeView, } from '@/data/table-editor/table-editor-types' import { useTableUpdateMutation } from '@/data/tables/table-update-mutation' import { useSendEventMutation } from '@/data/telemetry/send-event-mutation' import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' import { useIsFeatureEnabled } from '@/hooks/misc/useIsFeatureEnabled' import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization' import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject' import { useIsProtectedSchema } from '@/hooks/useProtectedSchemas' import { DOCS_URL } from '@/lib/constants' import { useTrack } from '@/lib/telemetry/track' import { useAppStateSnapshot } from '@/state/app-state' import { useTableEditorTableStateSnapshot } from '@/state/table-editor-table' export interface GridHeaderActionsProps { table: Entity isRefetching: boolean } export const GridHeaderActions = ({ table, isRefetching }: GridHeaderActionsProps) => { const track = useTrack() const { ref } = useParams() const appSnap = useAppStateSnapshot() const snap = useTableEditorTableStateSnapshot() const { data: project } = useSelectedProjectQuery() const { data: org } = useSelectedOrganizationQuery() const { mutate: sendEvent } = useSendEventMutation() const [rlsConfirmModalOpen, setRlsConfirmModalOpen] = useState(false) const [realtimeDialogOpen, setRealtimeDialogOpen] = useState(false) const [indexAdvisorDialogOpen, setIndexAdvisorDialogOpen] = useState(false) const [isAutofixViewSecurityModalOpen, setIsAutofixViewSecurityModalOpen] = useState(false) const [showWarning, setShowWarning] = useQueryState( 'showWarning', parseAsBoolean.withDefault(false) ) // need project lints to get security status for views const { data: lints = [] } = useProjectLintsQuery({ projectRef: project?.ref }) // Use table-specific index advisor context const { isAvailable: isIndexAdvisorAvailable, isEnabled: isIndexAdvisorEnabled } = useTableIndexAdvisor() const isTable = isTableLike(table) const isForeignTable = isTableLikeForeignTable(table) const isView = isTableLikeView(table) const isMaterializedView = isTableLikeMaterializedView(table) const { realtimeAll: realtimeEnabled } = useIsFeatureEnabled(['realtime:all']) const { isSchemaLocked } = useIsProtectedSchema({ schema: table.schema }) const isRealtimeEnabled = useIsTableRealtimeEnabled({ id: table.id }) const { mutate: updateTable, isPending: isUpdatingTable } = useTableUpdateMutation({ onError: (error) => { toast.error(`Failed to toggle RLS: ${error.message}`) }, onSettled: () => { closeConfirmModal() }, }) const showHeaderActions = snap.selectedRows.size === 0 const projectRef = project?.ref const { data } = useDatabasePoliciesQuery({ projectRef: project?.ref, connectionString: project?.connectionString, }) const policies = (data ?? []).filter( (policy) => policy.schema === table.schema && policy.table === table.name ) const { can: canSqlWriteTables, isLoading: isLoadingPermissions } = useAsyncCheckPermissions( PermissionAction.TENANT_SQL_ADMIN_WRITE, 'tables' ) const { can: canSqlWriteColumns } = useAsyncCheckPermissions( PermissionAction.TENANT_SQL_ADMIN_WRITE, 'columns' ) const isReadOnly = !isLoadingPermissions && !canSqlWriteTables && !canSqlWriteColumns // This will change when we allow autogenerated API docs for schemas other than `public` const doesHaveAutoGeneratedAPIDocs = table.schema === 'public' const { hasLint: tableHasLints } = getEntityLintDetails( table.name, 'rls_disabled_in_public', ['ERROR'], lints, table.schema ) const { hasLint: viewHasLints, matchingLint: matchingViewLint } = getEntityLintDetails( table.name, 'security_definer_view', ['ERROR', 'WARN'], lints, table.schema ) const { hasLint: materializedViewHasLints, matchingLint: matchingMaterializedViewLint } = getEntityLintDetails( table.name, 'materialized_view_in_api', ['ERROR', 'WARN'], lints, table.schema ) const closeConfirmModal = () => { setRlsConfirmModalOpen(false) } const onViewAPIDocs = () => { appSnap.setActiveDocsSection(['entities', table.name]) appSnap.setShowProjectApiDocs(true) sendEvent({ action: 'api_docs_opened', properties: { source: 'table_editor', }, groups: { project: ref ?? 'Unknown', organization: org?.slug ?? 'Unknown', }, }) } const onToggleRLS = async () => { const payload = { id: table.id, rls_enabled: !(isTable && table.rls_enabled), } updateTable({ projectRef: project?.ref!, connectionString: project?.connectionString, id: table.id, name: table.name, schema: table.schema, payload: payload, }) track('table_rls_enabled', { method: 'table_editor', schema_name: table.schema, table_name: table.name, }) } return (
{showHeaderActions && (
{isReadOnly && (
Viewing as read-only
You need additional permissions to manage your project's data
)} {isTable && !isSchemaLocked ? ( table.rls_enabled ? ( <> {policies.length < 1 && !isSchemaLocked ? ( } tooltip={{ content: { side: 'bottom', className: 'w-[280px]', text: 'RLS is enabled for this table, but no policies are set. Select queries may return 0 results.', }, }} > Add RLS policy ) : ( )} ) : tableHasLints ? (

Row Level Security (RLS)

You can restrict and control who can read, write and update data in this table using Row Level Security.

With RLS enabled, anonymous users will not be able to read/write data in the table.

{!isSchemaLocked && ( )}
) : null ) : null} {isView && viewHasLints && ( { setIsAutofixViewSecurityModalOpen(true) }} /> )} {isMaterializedView && materializedViewHasLints && ( )} {isForeignTable && table.schema === 'public' && (

Secure Foreign table

Foreign tables do not enforce RLS, which may allow unrestricted access. To secure them, either move foreign tables to a private schema not exposed by PostgREST, or disable PostgREST access entirely.

)}
)} {isTable && ( )}
) }