mirror of
https://github.com/supabase/supabase.git
synced 2026-10-09 19:35:06 +03:00
Just a tiny nit i came across - realised that if the schema is not exposed via the API, the warning that we show on the policies page RE data not being selectable is repeated for each table which imo seems unnecessary. Opting for a single admonition at the top instead ## Before <img width="1085" height="744" alt="image" src="https://github.com/user-attachments/assets/7bd8cf7b-f5a8-47d6-b41f-13fc4782ed8b" /> ### After <img width="1080" height="673" alt="image" src="https://github.com/user-attachments/assets/c839f502-42ff-4184-ad07-0ff2fd2d2676" /> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added an on-screen warning for tables whose schemas aren’t exposed via the project Data APIs, including a link to Data API settings. * **UI Improvements** * Refined the “filter entity types” control’s tooltip behavior and updated the popover header text. * **Bug Fixes** * Improved Data API/RLS status messaging by removing the prior “schema not exposed” outcome and showing “unknown” when access can’t be determined. * Consolidated policy warning rendering to avoid duplicated or inconsistent messages. * **Tests** * Updated policy/admonition helper tests to match the revised status and message behavior. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
171 lines
5.7 KiB
TypeScript
171 lines
5.7 KiB
TypeScript
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 { 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 } =
|
|
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({
|
|
apiAccessData: apiAccessMap?.[table.name],
|
|
isRLSEnabled: table.rls_enabled,
|
|
policiesCount: policies.length,
|
|
}),
|
|
[apiAccessMap, 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, ref }), [status, ref])
|
|
|
|
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 && admonitionMessage !== null && (
|
|
<Admonition
|
|
showIcon={false}
|
|
type={isPubliclyReadable ? 'warning' : 'default'}
|
|
className="border-0 border-y rounded-none min-h-12 flex items-center"
|
|
>
|
|
{admonitionMessage}
|
|
</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'
|