| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211 |
- import { useParams } from 'common'
- import { isEmpty } from 'lodash'
- import Link from 'next/link'
- import { useCallback, useState } from 'react'
- import { toast } from 'sonner'
- import { Button, Card, CardContent } from 'ui'
- import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal'
- import {
- PolicyTableRow,
- PolicyTableRowProps,
- } from '@/components/interfaces/Auth/Policies/PolicyTableRow'
- import type { Policy } from '@/components/interfaces/Auth/Policies/PolicyTableRow/PolicyTableRow.utils'
- import { ProtectedSchemaWarning } from '@/components/interfaces/Database/ProtectedSchemaWarning'
- import { NoSearchResults } from '@/components/ui/NoSearchResults'
- import { useDatabasePolicyDeleteMutation } from '@/data/database-policies/database-policy-delete-mutation'
- import { useTableUpdateMutation } from '@/data/tables/table-update-mutation'
- import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
- interface PoliciesProps {
- search?: string
- schema: string
- tables: PolicyTableRowProps['table'][]
- hasTables: boolean
- isLocked: boolean
- visibleTableIds: Set<number>
- onSelectCreatePolicy: (table: string) => void
- onSelectEditPolicy: (policy: Policy) => void
- onResetSearch?: () => void
- }
- export const Policies = ({
- search,
- schema,
- tables,
- hasTables,
- isLocked,
- visibleTableIds,
- onSelectCreatePolicy,
- onSelectEditPolicy: onSelectEditPolicyAI,
- onResetSearch,
- }: PoliciesProps) => {
- const { ref } = useParams()
- const { data: project } = useSelectedProjectQuery()
- const [selectedTableToToggleRLS, setSelectedTableToToggleRLS] = useState<{
- id: number
- schema: string
- name: string
- rls_enabled: boolean
- }>()
- const [selectedPolicyToDelete, setSelectedPolicyToDelete] = useState<any>({})
- const { mutate: updateTable, isPending: isUpdatingTable } = useTableUpdateMutation({
- onError: (error) => {
- toast.error(`Failed to toggle RLS: ${error.message}`)
- },
- onSettled: () => {
- closeConfirmModal()
- },
- })
- const { mutate: deleteDatabasePolicy, isPending: isDeletingPolicy } =
- useDatabasePolicyDeleteMutation({
- onSuccess: () => {
- toast.success('Successfully deleted policy!')
- },
- onSettled: () => {
- closeConfirmModal()
- },
- })
- const closeConfirmModal = useCallback(() => {
- setSelectedPolicyToDelete({})
- setSelectedTableToToggleRLS(undefined)
- }, [])
- const onSelectToggleRLS = useCallback(
- (table: { id: number; schema: string; name: string; rls_enabled: boolean }) => {
- setSelectedTableToToggleRLS(table)
- },
- []
- )
- const onSelectEditPolicy = useCallback(
- (policy: Policy) => {
- onSelectEditPolicyAI(policy)
- },
- [onSelectEditPolicyAI]
- )
- const onSelectDeletePolicy = useCallback((policy: Policy) => {
- setSelectedPolicyToDelete(policy)
- }, [])
- // Methods that involve some API
- const onToggleRLS = async () => {
- if (!selectedTableToToggleRLS) return console.error('Table is required')
- const payload = {
- id: selectedTableToToggleRLS.id,
- rls_enabled: !selectedTableToToggleRLS.rls_enabled,
- }
- updateTable({
- projectRef: project?.ref!,
- connectionString: project?.connectionString,
- id: selectedTableToToggleRLS.id,
- name: selectedTableToToggleRLS.name,
- schema: selectedTableToToggleRLS.schema,
- payload: payload,
- })
- }
- const onDeletePolicy = async () => {
- if (!project) return console.error('Project is required')
- deleteDatabasePolicy({
- projectRef: project.ref,
- connectionString: project.connectionString,
- originalPolicy: selectedPolicyToDelete,
- })
- }
- const handleCreatePolicy = useCallback(
- (tableData: PolicyTableRowProps['table']) => {
- onSelectCreatePolicy(tableData.name)
- },
- [onSelectCreatePolicy]
- )
- if (!hasTables) {
- return (
- <Card className="w-full bg-transparent">
- <CardContent className="flex flex-col items-center justify-center p-8">
- <h2 className="heading-default">No tables to create policies for</h2>
- <p className="text-sm text-foreground-light text-center mb-4">
- RLS Policies control per-user access to table rows. Create a table in this schema first
- before creating a policy.
- </p>
- <Button asChild type="default">
- <Link href={`/project/${ref}/editor`}>Create a table</Link>
- </Button>
- </CardContent>
- </Card>
- )
- }
- return (
- <>
- <div className="flex flex-col gap-y-4 pb-4">
- {isLocked && <ProtectedSchemaWarning schema={schema} entity="policies" />}
- {tables.length > 0 ? (
- <>
- {tables.map((table) => {
- const isVisible = visibleTableIds.has(table.id)
- return (
- <section
- key={table.id}
- hidden={!isVisible}
- aria-hidden={!isVisible}
- data-testid={`policy-table-${table.name}`}
- >
- <PolicyTableRow
- table={table}
- isLocked={schema === 'realtime' ? true : isLocked}
- onSelectToggleRLS={onSelectToggleRLS}
- onSelectCreatePolicy={handleCreatePolicy}
- onSelectEditPolicy={onSelectEditPolicy}
- onSelectDeletePolicy={onSelectDeletePolicy}
- />
- </section>
- )
- })}
- {!!search && visibleTableIds.size === 0 && (
- <NoSearchResults searchString={search ?? ''} onResetFilter={onResetSearch} />
- )}
- </>
- ) : hasTables ? (
- <NoSearchResults searchString={search ?? ''} onResetFilter={onResetSearch} />
- ) : null}
- </div>
- <ConfirmationModal
- visible={!isEmpty(selectedPolicyToDelete)}
- variant="destructive"
- title="Delete policy"
- description={`Are you sure you want to delete the policy “${selectedPolicyToDelete.name}”? This action cannot be undone.`}
- confirmLabel="Delete"
- confirmLabelLoading="Deleting"
- loading={isDeletingPolicy}
- onCancel={closeConfirmModal}
- onConfirm={onDeletePolicy}
- />
- <ConfirmationModal
- visible={selectedTableToToggleRLS !== undefined}
- variant={selectedTableToToggleRLS?.rls_enabled ? 'destructive' : 'default'}
- title={`${selectedTableToToggleRLS?.rls_enabled ? 'Disable' : 'Enable'} Row Level Security`}
- description={`Are you sure you want to ${
- selectedTableToToggleRLS?.rls_enabled ? 'disable' : 'enable'
- } Row Level Security (RLS) for the table “${selectedTableToToggleRLS?.name}”?`}
- confirmLabel={`${selectedTableToToggleRLS?.rls_enabled ? 'Disable' : 'Enable'} RLS`}
- confirmLabelLoading={`${selectedTableToToggleRLS?.rls_enabled ? 'Disabling' : 'Enabling'} RLS`}
- loading={isUpdatingTable}
- onCancel={closeConfirmModal}
- onConfirm={onToggleRLS}
- />
- </>
- )
- }
|