import * as Tooltip from '@radix-ui/react-tooltip' import type { PostgresTable } from '@supabase/postgres-meta' import { PermissionAction } from '@supabase/shared-types/out/constants' import { useParams } from 'common' import { Lock, MousePointer2, PlusCircle, Unlock } from 'lucide-react' import Link from 'next/link' import { useState } from 'react' import toast from 'react-hot-toast' import { getEntityLintDetails } from 'components/interfaces/TableGridEditor/TableEntity.utils' import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext' import APIDocsButton from 'components/ui/APIDocsButton' 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 { ENTITY_TYPE } from 'data/entity-types/entity-type-constants' import { useProjectLintsQuery } from 'data/lint/lint-query' import { useTableUpdateMutation } from 'data/tables/table-update-mutation' import { useCheckPermissions } from 'hooks/misc/useCheckPermissions' import useEntityType from 'hooks/misc/useEntityType' import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled' import { TableLike } from 'hooks/misc/useTable' import { EXCLUDED_SCHEMAS } from 'lib/constants/schemas' import { Button, PopoverContent_Shadcn_, PopoverTrigger_Shadcn_, Popover_Shadcn_ } from 'ui' import ConfirmModal from 'ui-patterns/Dialogs/ConfirmDialog' import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal' import { RoleImpersonationPopover } from '../RoleImpersonationSelector' export interface GridHeaderActionsProps { table: TableLike canEditViaTableEditor: boolean } const GridHeaderActions = ({ table }: GridHeaderActionsProps) => { const entityType = useEntityType(table?.id) const { ref } = useParams() const { project } = useProjectContext() // need project lints to get security status for views const { data: lints = [] } = useProjectLintsQuery({ projectRef: project?.ref, }) const isTable = entityType?.type === ENTITY_TYPE.TABLE const isMaterializedView = entityType?.type === ENTITY_TYPE.MATERIALIZED_VIEW const isView = entityType?.type === ENTITY_TYPE.VIEW // check if current entity is a view and has an associated security definer lint const isForeignTable = entityType?.type === ENTITY_TYPE.FOREIGN_TABLE const realtimeEnabled = useIsFeatureEnabled('realtime:all') const isLocked = EXCLUDED_SCHEMAS.includes(table.schema) const { mutate: updateTable } = useTableUpdateMutation({ onError: (error) => { toast.error(`Failed to toggle RLS: ${error.message}`) }, onSettled: () => { closeConfirmModal() }, }) const [showEnableRealtime, setShowEnableRealtime] = useState(false) const [open, setOpen] = useState(false) const [rlsConfirmModalOpen, setRlsConfirmModalOpen] = useState(false) 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: any) => t.id === table?.id) const { mutate: updatePublications, isLoading: isTogglingRealtime } = useDatabasePublicationUpdateMutation({ onSuccess: () => { setShowEnableRealtime(false) }, onError: (error) => { toast.error(`Failed to toggle realtime for ${table.name}: ${error.message}`) }, }) const canSqlWriteTables = useCheckPermissions(PermissionAction.TENANT_SQL_ADMIN_WRITE, 'tables') const canSqlWriteColumns = useCheckPermissions(PermissionAction.TENANT_SQL_ADMIN_WRITE, 'columns') const isReadOnly = !canSqlWriteTables && !canSqlWriteColumns // This will change when we allow autogenerated API docs for schemas other than `public` const doesHaveAutoGeneratedAPIDocs = table.schema === 'public' 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 toggleRealtime = async () => { if (!project) return console.error('Project is required') if (!realtimePublication) return console.error('Unable to find realtime publication') const exists = realtimeEnabledTables.some((x: any) => x.id == table.id) const tables = !exists ? [`${table.schema}.${table.name}`].concat( realtimeEnabledTables.map((t: any) => `${t.schema}.${t.name}`) ) : realtimeEnabledTables .filter((x: any) => x.id != table.id) .map((x: any) => `${x.schema}.${x.name}`) 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: !(table as PostgresTable).rls_enabled, } updateTable({ projectRef: project?.ref!, connectionString: project?.connectionString, id: payload.id, schema: table.schema, payload: payload, }) } return ( <>
{isReadOnly && (
Viewing as read-only
You need additional permissions to manage your project's data
)} {isTable ? ( (table as PostgresTable).rls_enabled ? ( <> {policies.length < 1 && !isLocked ? (

RLS is enabled for this table, but no policies are set.

Select queries may return 0 results.

) : ( )} ) : ( setOpen(!open)} modal={false}>

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.

{!isLocked && (
)}
) ) : null} {isView && viewHasLints && ( setOpen(!open)} modal={false}>

Secure your View

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.

)} {isMaterializedView && materializedViewHasLints && ( setOpen(!open)} modal={false}>

Secure your View

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.

)} {isForeignTable && entityType.schema === 'public' && ( setOpen(!open)} modal={false}>

Secure Foreign table

Foreign tables do not enforce RLS. Move them to a private schema not exposed to Postgrest or disable Postgrest.

)} {isTable && realtimeEnabled && ( )} {doesHaveAutoGeneratedAPIDocs && }
setShowEnableRealtime(false)} onConfirm={() => toggleRealtime()} >

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.

)}
{entityType?.type === ENTITY_TYPE.TABLE && ( )} ) } export default GridHeaderActions