import { PermissionAction } from '@supabase/shared-types/out/constants' import { Lightbulb, Lock, MousePointer2, PlusCircle, Unlock } from 'lucide-react' import Link from 'next/link' import { useState } from 'react' import { toast } from 'sonner' import { useParams } from 'common' import { RefreshButton } from 'components/grid/components/header/RefreshButton' import { useTableIndexAdvisor } from 'components/grid/context/TableIndexAdvisorContext' import { EnableIndexAdvisorButton } from 'components/interfaces/QueryPerformance/IndexAdvisor/EnableIndexAdvisorButton' import { getEntityLintDetails } from 'components/interfaces/TableGridEditor/TableEntity.utils' import { APIDocsButton } from 'components/ui/APIDocsButton' import { ButtonTooltip } from 'components/ui/ButtonTooltip' import { useDatabasePoliciesQuery } from 'data/database-policies/database-policies-query' import { useDatabasePublicationsQuery } from 'data/database-publications/database-publications-query' import { useDatabasePublicationUpdateMutation } from 'data/database-publications/database-publications-update-mutation' import { useDatabaseTriggersQuery } from 'data/database-triggers/database-triggers-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 { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled' import { RealtimeButtonVariant, useRealtimeExperiment } from 'hooks/misc/useRealtimeExperiment' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { useIsProtectedSchema } from 'hooks/useProtectedSchemas' import { DOCS_URL } from 'lib/constants' import { useTrack } from 'lib/telemetry/track' import { parseAsBoolean, useQueryState } from 'nuqs' import { useTableEditorTableStateSnapshot } from 'state/table-editor-table' import { Button, PopoverContent_Shadcn_, PopoverTrigger_Shadcn_, Popover_Shadcn_, Tooltip, TooltipContent, TooltipTrigger, cn, } from 'ui' import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal' import { RoleImpersonationPopover } from '../RoleImpersonationSelector/RoleImpersonationPopover' import ViewEntityAutofixSecurityModal from './ViewEntityAutofixSecurityModal' export interface GridHeaderActionsProps { table: Entity isRefetching: boolean } export const GridHeaderActions = ({ table, isRefetching }: GridHeaderActionsProps) => { const { ref } = useParams() const { data: project } = useSelectedProjectQuery() const track = useTrack() 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 { mutate: updateTable, isPending: isUpdatingTable } = useTableUpdateMutation({ onError: (error) => { toast.error(`Failed to toggle RLS: ${error.message}`) }, onSettled: () => { closeConfirmModal() }, }) const [showEnableRealtime, setShowEnableRealtime] = useState(false) const [rlsConfirmModalOpen, setRlsConfirmModalOpen] = useState(false) const [isAutofixViewSecurityModalOpen, setIsAutofixViewSecurityModalOpen] = useState(false) const snap = useTableEditorTableStateSnapshot() 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 { data: publications } = useDatabasePublicationsQuery({ projectRef: project?.ref, connectionString: project?.connectionString, }) const realtimePublication = (publications ?? []).find( (publication) => publication.name === 'supabase_realtime' ) const realtimeEnabledTables = realtimePublication?.tables ?? [] const isRealtimeEnabled = realtimeEnabledTables.some((t) => t.id === table?.id) const { activeVariant: activeRealtimeVariant } = useRealtimeExperiment({ isTable, isRealtimeEnabled, }) const { mutate: updatePublications, isPending: isTogglingRealtime } = useDatabasePublicationUpdateMutation({ onSuccess: () => { setShowEnableRealtime(false) track(isRealtimeEnabled ? 'table_realtime_disabled' : 'table_realtime_enabled', { method: 'ui', schema_name: table.schema, table_name: table.name, }) }, onError: (error) => { toast.error(`Failed to toggle realtime for ${table.name}: ${error.message}`) }, }) const { data: triggersData } = useDatabaseTriggersQuery( { projectRef: project?.ref, connectionString: project?.connectionString, }, { enabled: isTable, } ) const tableTriggers = (triggersData ?? []).filter( (trigger) => trigger.schema === table.schema && trigger.table === table.name ) const tableTriggersCount = tableTriggers.length 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 manageTriggersHref = `/project/${ref}/database/triggers?schema=${table.schema}` const toggleRealtime = async () => { if (!project || !realtimePublication) return const exists = realtimeEnabledTables.some((x) => x.id === table.id) const tables = !exists ? [`${table.schema}.${table.name}`].concat( realtimeEnabledTables.map((t) => `${t.schema}.${t.name}`) ) : realtimeEnabledTables.filter((x) => x.id !== table.id).map((x) => `${x.schema}.${x.name}`) track('realtime_toggle_table_clicked', { newState: exists ? 'disabled' : 'enabled', origin: 'tableGridHeader', }) updatePublications({ projectRef: project?.ref, connectionString: project?.connectionString, id: realtimePublication.id, tables, }) } const closeConfirmModal = () => { setRlsConfirmModalOpen(false) } 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 && ( )}Index Advisor recommends indexes to improve query performance on this table.
Enable Index Advisor to get recommendations based on your actual query patterns.
This view is defined with the Security Definer property, giving it permissions of the view's creator (Postgres), rather than the permissions of the querying user.
Since this view is in the public schema, it is accessible via your project's APIs.
This view is defined with the Security Definer property, giving it permissions of the view's creator (Postgres), rather than the permissions of the querying user.
Since this view is in the public schema, it is accessible via your project's APIs.
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.
Once realtime has been {isRealtimeEnabled ? 'disabled' : 'enabled'}, the table will{' '} {isRealtimeEnabled ? 'no longer ' : ''}broadcast any changes to authorized subscribers.
{!isRealtimeEnabled && (You may also select which events to broadcast to subscribers on the{' '} database publications {' '} settings.
)}