// @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 (
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 && ( )}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.