Files
Joshen LimandIvan Vasilov bb4eaef0df Shift old policies UI into storage since its only being used there (#47497)
## Context

This is one chonky boy of a PR, but it's just re-organizing files and
folders to clean things up

Storage Policies have been using the old Database policy UI (the one
with the Dialog), so it makes most sense to shift those files under the
`Storage` folder instead of keeping them under `Database`, so it's
clearer which files are being consumed by whom, and easier to clean
things up as well

As part of this clean up, also tore out all the RLS generation logic
from the Table Editor which are no longer used as they were affected by
the change in files.

Deprecated + deleted any unused code too

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Enhanced the policy editor with a centralized set of reusable
templates, including general table templates plus predefined Realtime
and queue access templates.
* Updated the table creation flow so it no longer auto-generates
additional RLS policy drafts.

* **Bug Fixes**
* Improved the policy table header badge layout for clearer RLS/API/lock
indicators.
* Simplified policy preview/save behavior so only meaningful edits are
reflected in the applied SQL.
* Streamlined the table-creation success messaging to remove conditional
failure details.

* **Tests**
* Updated/removal of policy and table-creation test coverage to match
the new behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Ivan Vasilov <vasilov.ivan@gmail.com>
2026-07-01 23:34:14 +08:00

190 lines
6.5 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useParams } from 'common'
import { noop } from 'lodash'
import { memo, useMemo } from 'react'
import {
Card,
CardContent,
CardHeader,
cn,
Table,
TableBody,
TableHead,
TableHeader,
TableRow,
} from 'ui'
import { Admonition } from 'ui-patterns/admonition'
import { ShimmeringLoader } from 'ui-patterns/ShimmeringLoader'
import { usePoliciesData } from '../PoliciesDataContext'
import { PolicyRow } from './PolicyRow'
import type { PolicyTable } from './PolicyTableRow.types'
import type { Policy } from './PolicyTableRow.utils'
import { getTableAdmonitionMessage, getTableDataApiStatus } from './PolicyTableRow.utils'
import { PolicyTableRowHeader } from './PolicyTableRowHeader'
import { AlertError } from '@/components/ui/AlertError'
import { InlineLink } from '@/components/ui/InlineLink'
import { useTableApiAccessQuery } from '@/data/privileges/table-api-access-query'
import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
export interface PolicyTableRowProps {
table: PolicyTable
isLocked: boolean
onSelectToggleRLS: (table: PolicyTable) => void
onSelectCreatePolicy: (table: PolicyTable) => void
onSelectEditPolicy: (policy: Policy) => void
onSelectDeletePolicy: (policy: Policy) => void
}
const PolicyTableRowComponent = ({
table,
isLocked,
onSelectToggleRLS = noop,
onSelectCreatePolicy = noop,
onSelectEditPolicy = noop,
onSelectDeletePolicy = noop,
}: PolicyTableRowProps) => {
const { ref } = useParams()
const { data: project } = useSelectedProjectQuery()
const { getPoliciesForTable, isPoliciesLoading, isPoliciesError, policiesError, exposedSchemas } =
usePoliciesData()
const policies = useMemo(
() => getPoliciesForTable(table.schema, table.name),
[getPoliciesForTable, table.schema, table.name]
)
// [Joshen] Classification is more granular than "RLS on/off" alone — it also considers
// schema exposure and whether anon/authenticated/service_role actually have grants.
// Ideally we'd rely on the security lints, but they only look at the public schema and
// ignore roles. Once the lints cover both, we can switch to them as the source of truth.
const tableNames = useMemo(() => [table.name], [table.name])
const {
data: apiAccessMap,
isPending: isLoadingRolesAccess,
isError: isRolesAccessError,
} = useTableApiAccessQuery({
projectRef: project?.ref,
connectionString: project?.connectionString,
schemaName: table.schema,
tableNames,
})
const status = useMemo(
() =>
getTableDataApiStatus({
isSchemaExposed: exposedSchemas.has(table.schema),
apiAccessData: apiAccessMap?.[table.name],
isRLSEnabled: table.rls_enabled,
policiesCount: policies.length,
}),
[exposedSchemas, apiAccessMap, table.schema, table.name, table.rls_enabled, policies.length]
)
const hasApiAccess =
status === 'publicly-readable' || status === 'locked-by-rls' || status === 'secured'
const isPubliclyReadable = status === 'publicly-readable'
const isRealtimeSchema = table.schema === 'realtime'
const isRealtimeMessagesTable = isRealtimeSchema && table.name === 'messages'
const isTableLocked = isRealtimeSchema ? !isRealtimeMessagesTable : isLocked
const showPolicies = !isPoliciesLoading && !isPoliciesError && !isLoadingRolesAccess
const admonitionMessage = useMemo(() => getTableAdmonitionMessage(status), [status])
return (
<Card className={cn(isPubliclyReadable && 'border-warning-500')}>
<CardHeader
className={cn('py-3 px-4', status !== 'secured' && status !== 'unknown' && 'border-b-0')}
>
<PolicyTableRowHeader
table={table}
isLocked={isLocked}
hasApiAccess={hasApiAccess}
isLoadingApiAccess={isLoadingRolesAccess}
onSelectToggleRLS={onSelectToggleRLS}
onSelectCreatePolicy={onSelectCreatePolicy}
/>
</CardHeader>
{!isLoadingRolesAccess && !isRolesAccessError && status === 'schema-not-exposed' && (
<Admonition
showIcon={false}
type="warning"
className="border-0 border-y rounded-none min-h-12 flex items-center"
>
<p className="text-foreground-light">
No data will be selectable via Supabase APIs as this schema is not exposed. You may
configure this in your project’s{' '}
<InlineLink href={`/project/${ref}/integrations/data_api/settings`}>
API settings
</InlineLink>
.
</p>
</Admonition>
)}
{!isLoadingRolesAccess && !isRolesAccessError && admonitionMessage !== null && (
<Admonition
showIcon={false}
type={isPubliclyReadable ? 'warning' : 'default'}
className="border-0 border-y rounded-none min-h-12 flex items-center"
>
<p>{admonitionMessage}</p>
</Admonition>
)}
{(isPoliciesLoading || isLoadingRolesAccess) && (
<CardContent>
<ShimmeringLoader />
</CardContent>
)}
{isPoliciesError && (
<CardContent>
<AlertError
className="border-0 rounded-none"
error={policiesError}
subject="Failed to retrieve policies"
/>
</CardContent>
)}
{showPolicies && (
<CardContent className="p-0">
{policies.length === 0 ? (
<p className="text-foreground-lighter text-sm p-4">No policies created yet</p>
) : (
<Table className="table-fixed">
<TableHeader>
<TableRow>
<TableHead className="w-[40%]">Name</TableHead>
<TableHead className="w-[20%]">Command</TableHead>
<TableHead className="w-[30%]">Applied to</TableHead>
<TableHead className="text-right">
<span className="sr-only">Actions</span>
</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{policies.map((policy) => (
<PolicyRow
key={policy.id}
policy={policy}
isLocked={isTableLocked}
onSelectEditPolicy={onSelectEditPolicy}
onSelectDeletePolicy={onSelectDeletePolicy}
/>
))}
</TableBody>
</Table>
)}
</CardContent>
)}
</Card>
)
}
export const PolicyTableRow = memo(PolicyTableRowComponent)
PolicyTableRow.displayName = 'PolicyTableRow'