mirror of
https://github.com/supabase/supabase.git
synced 2026-06-12 08:29:15 +08:00
## Problem We have multiple Popover components ## Solution - [x] migrate Popover usages to Shadcn components - Migrated JSON and text editor in the `TableEditor` (inline row edition) - Migrated the template popover in the logs explorer templates page - [x] remove `_Shadcn_` suffix from Popover components (renaming + prettier) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Refactor** * Unified popover implementation across the app and design system; dropdowns, calendars, menus and tooltips now use a consistent popover API with no visual or interaction changes. * **Chores** * Minor prop typing update for the logs date-picker to align with the consolidated popover content type. <!-- review_stack_entry_start --> [](https://app.coderabbit.ai/change-stack/supabase/supabase/pull/45980) <!-- review_stack_entry_end --> <!-- end of auto-generated comment: release notes by coderabbit.ai -->
288 lines
11 KiB
TypeScript
288 lines
11 KiB
TypeScript
import { useParams } from 'common'
|
|
import { Lock, Paintbrush, PlusCircle, Trash2 } from 'lucide-react'
|
|
import Link from 'next/link'
|
|
import { parseAsBoolean, useQueryState } from 'nuqs'
|
|
import { useMemo, useState } from 'react'
|
|
import { toast } from 'sonner'
|
|
import { Button, cn, LoadingLine, Popover, PopoverContent, PopoverTrigger, Separator } from 'ui'
|
|
import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal'
|
|
import { ShimmeringLoader } from 'ui-patterns/ShimmeringLoader'
|
|
|
|
import { pgmqQueueTable } from './Queues.utils'
|
|
import { DeleteQueue } from '@/components/interfaces/Integrations/Queues/SingleQueue/DeleteQueue'
|
|
import { PurgeQueue } from '@/components/interfaces/Integrations/Queues/SingleQueue/PurgeQueue'
|
|
import { QUEUE_MESSAGE_TYPE } from '@/components/interfaces/Integrations/Queues/SingleQueue/Queue.utils'
|
|
import { QueueMessagesDataGrid } from '@/components/interfaces/Integrations/Queues/SingleQueue/QueueDataGrid'
|
|
import { QueueFilters } from '@/components/interfaces/Integrations/Queues/SingleQueue/QueueFilters'
|
|
import { QueueSettings } from '@/components/interfaces/Integrations/Queues/SingleQueue/QueueSettings'
|
|
import { SendMessageModal } from '@/components/interfaces/Integrations/Queues/SingleQueue/SendMessageModal'
|
|
import { Markdown } from '@/components/interfaces/Markdown'
|
|
import { ButtonTooltip } from '@/components/ui/ButtonTooltip'
|
|
import { useDatabasePoliciesQuery } from '@/data/database-policies/database-policies-query'
|
|
import { useQueueMessagesInfiniteQuery } from '@/data/database-queues/database-queue-messages-infinite-query'
|
|
import { useQueuesExposePostgrestStatusQuery } from '@/data/database-queues/database-queues-expose-postgrest-status-query'
|
|
import { useTableUpdateMutation } from '@/data/tables/table-update-mutation'
|
|
import { useTablesQuery } from '@/data/tables/tables-query'
|
|
import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
|
|
|
|
export const QueueTab = () => {
|
|
const { childId: queueName, ref } = useParams()
|
|
const { data: project } = useSelectedProjectQuery()
|
|
|
|
const [openRlsPopover, setOpenRlsPopover] = useState(false)
|
|
const [rlsConfirmModalOpen, setRlsConfirmModalOpen] = useState(false)
|
|
const [sendMessageModalShown, setSendMessageModalShown] = useQueryState(
|
|
'new-message',
|
|
parseAsBoolean.withDefault(false).withOptions({ history: 'push', clearOnDefault: true })
|
|
)
|
|
const [purgeQueueModalShown, setPurgeQueueModalShown] = useState(false)
|
|
const [deleteQueueModalShown, setDeleteQueueModalShown] = useState(false)
|
|
const [selectedTypes, setSelectedTypes] = useState<QUEUE_MESSAGE_TYPE[]>([])
|
|
|
|
const { data: tables, isPending: isLoadingTables } = useTablesQuery({
|
|
projectRef: project?.ref,
|
|
connectionString: project?.connectionString,
|
|
schema: 'pgmq',
|
|
})
|
|
const queueRelname = queueName ? pgmqQueueTable(queueName) : undefined
|
|
const queueTable = tables?.find((x) => x.name === queueRelname)
|
|
const isRlsEnabled = queueTable?.rls_enabled ?? false
|
|
|
|
const { data: policies } = useDatabasePoliciesQuery({
|
|
projectRef: project?.ref,
|
|
connectionString: project?.connectionString,
|
|
schema: 'pgmq',
|
|
})
|
|
const queuePolicies = (policies ?? []).filter((policy) => policy.table === queueRelname)
|
|
|
|
const { data: isExposed } = useQueuesExposePostgrestStatusQuery({
|
|
projectRef: project?.ref,
|
|
connectionString: project?.connectionString,
|
|
})
|
|
|
|
const {
|
|
data,
|
|
error,
|
|
isPending: isLoading,
|
|
fetchNextPage,
|
|
isFetching,
|
|
} = useQueueMessagesInfiniteQuery(
|
|
{
|
|
projectRef: project?.ref,
|
|
connectionString: project?.connectionString,
|
|
queueName: queueName!,
|
|
// when no types are selected, include all types of messages
|
|
status: selectedTypes.length === 0 ? ['archived', 'available', 'scheduled'] : selectedTypes,
|
|
},
|
|
{ staleTime: 30 }
|
|
)
|
|
const messages = useMemo(() => data?.pages.flatMap((p) => p), [data?.pages])
|
|
|
|
const { mutate: updateTable, isPending: isUpdatingTable } = useTableUpdateMutation({
|
|
onSettled: () => {
|
|
toast.success(`Successfully enabled RLS for ${queueName}`)
|
|
setRlsConfirmModalOpen(false)
|
|
},
|
|
})
|
|
|
|
const onToggleRLS = async () => {
|
|
if (!project) return console.error('Project is required')
|
|
if (!queueTable) return toast.error('Unable to toggle RLS: Queue table not found')
|
|
const payload = {
|
|
id: queueTable.id,
|
|
rls_enabled: true,
|
|
}
|
|
updateTable({
|
|
projectRef: project?.ref,
|
|
connectionString: project?.connectionString,
|
|
id: queueTable.id,
|
|
name: queueTable.name,
|
|
schema: 'pgmq',
|
|
payload: payload,
|
|
})
|
|
}
|
|
|
|
return (
|
|
<div className="h-full flex flex-col">
|
|
<div className="flex items-center justify-between gap-x-4 py-1.5 px-10 mb-0 bg-surface-200">
|
|
<QueueFilters selectedTypes={selectedTypes} setSelectedTypes={setSelectedTypes} />
|
|
<div className="flex gap-x-2">
|
|
<QueueSettings />
|
|
|
|
<ButtonTooltip
|
|
type="text"
|
|
className="px-1.5"
|
|
onClick={() => setPurgeQueueModalShown(true)}
|
|
icon={<Paintbrush />}
|
|
title="Purge messages"
|
|
aria-label="Purge messages"
|
|
tooltip={{ content: { side: 'bottom', text: 'Purge messages' } }}
|
|
/>
|
|
|
|
<ButtonTooltip
|
|
type="text"
|
|
className="px-1.5"
|
|
onClick={() => setDeleteQueueModalShown(true)}
|
|
icon={<Trash2 />}
|
|
title="Delete queue"
|
|
aria-label="Delete queue"
|
|
tooltip={{ content: { side: 'bottom', text: 'Delete queue' } }}
|
|
/>
|
|
|
|
<Separator orientation="vertical" className="h-[26px]" />
|
|
|
|
{isLoadingTables ? (
|
|
<ShimmeringLoader className="w-[123px]" />
|
|
) : isRlsEnabled ? (
|
|
<>
|
|
{queuePolicies.length === 0 ? (
|
|
<ButtonTooltip
|
|
asChild
|
|
type="default"
|
|
className="group"
|
|
icon={<PlusCircle strokeWidth={1.5} className="text-foreground-muted" />}
|
|
tooltip={{
|
|
content: {
|
|
side: 'bottom',
|
|
className: 'w-[280px]',
|
|
text: 'RLS is enabled for this queue, but no policies are set. Queue will not be accessible.',
|
|
},
|
|
}}
|
|
>
|
|
<Link
|
|
passHref
|
|
href={`/project/${ref}/auth/policies?search=${queueTable?.id}&schema=pgmq`}
|
|
>
|
|
Add RLS policy
|
|
</Link>
|
|
</ButtonTooltip>
|
|
) : (
|
|
<Button
|
|
asChild
|
|
type="default"
|
|
className="group"
|
|
icon={
|
|
<div
|
|
className={cn(
|
|
'flex items-center justify-center rounded-full bg-border-stronger h-[16px]',
|
|
queuePolicies.length > 9 ? ' px-1' : 'w-[16px]'
|
|
)}
|
|
>
|
|
<span className="text-[11px] text-foreground font-mono text-center">
|
|
{queuePolicies.length}
|
|
</span>
|
|
</div>
|
|
}
|
|
>
|
|
<Link
|
|
passHref
|
|
href={`/project/${ref}/auth/policies?search=${queueTable?.id}&schema=pgmq`}
|
|
>
|
|
Auth {queuePolicies.length > 1 ? 'policies' : 'policy'}
|
|
</Link>
|
|
</Button>
|
|
)}
|
|
</>
|
|
) : (
|
|
<Popover
|
|
modal={false}
|
|
open={openRlsPopover}
|
|
onOpenChange={() => setOpenRlsPopover(!openRlsPopover)}
|
|
>
|
|
<PopoverTrigger asChild>
|
|
<Button type={isExposed ? 'warning' : 'default'} icon={<Lock strokeWidth={1.5} />}>
|
|
RLS disabled
|
|
</Button>
|
|
</PopoverTrigger>
|
|
<PopoverContent className="w-80 text-sm" align="end">
|
|
<h3 className="text-xs flex items-center gap-x-2">
|
|
<Lock size={14} /> Row Level Security (RLS)
|
|
</h3>
|
|
<div className="grid gap-2 mt-2 text-foreground-light text-xs">
|
|
{isExposed ? (
|
|
<>
|
|
<p>
|
|
You can restrict and control who can manage this queue using Row Level
|
|
Security.
|
|
</p>
|
|
<p>With RLS enabled, anonymous users will not have access to this queue.</p>
|
|
<Button
|
|
type="default"
|
|
className="w-min"
|
|
onClick={() => setRlsConfirmModalOpen(!rlsConfirmModalOpen)}
|
|
>
|
|
Enable RLS for this queue
|
|
</Button>
|
|
</>
|
|
) : (
|
|
<>
|
|
<Markdown
|
|
className="[&>p]:leading-normal! text-xs [&>p]:m-0! flex flex-col gap-y-2"
|
|
content={`
|
|
RLS for queues is only relevant if exposure through PostgREST has been enabled, in which you can restrict and control who can manage this queue using Row Level Security.
|
|
|
|
You may opt to manage your queues via any Supabase client libraries or PostgREST endpoints by enabling this in the [queues settings](/project/${project?.ref}/integrations/queues/settings).`}
|
|
/>
|
|
<Button
|
|
type="default"
|
|
className="w-min"
|
|
onClick={() => setRlsConfirmModalOpen(!rlsConfirmModalOpen)}
|
|
>
|
|
Enable RLS for this queue
|
|
</Button>
|
|
</>
|
|
)}
|
|
</div>
|
|
</PopoverContent>
|
|
</Popover>
|
|
)}
|
|
|
|
<Button type="primary" onClick={() => setSendMessageModalShown(true)}>
|
|
Add message
|
|
</Button>
|
|
|
|
{/* <DocsButton href={docsUrl} />} */}
|
|
</div>
|
|
</div>
|
|
|
|
<LoadingLine loading={isFetching} />
|
|
|
|
<QueueMessagesDataGrid
|
|
error={error}
|
|
messages={messages || []}
|
|
isLoading={isLoading}
|
|
showMessageModal={() => setSendMessageModalShown(true)}
|
|
fetchNextPage={fetchNextPage}
|
|
/>
|
|
<SendMessageModal
|
|
visible={sendMessageModalShown}
|
|
onClose={() => setSendMessageModalShown(false)}
|
|
/>
|
|
<DeleteQueue
|
|
queueName={queueName!}
|
|
visible={deleteQueueModalShown}
|
|
onClose={() => setDeleteQueueModalShown(false)}
|
|
/>
|
|
<PurgeQueue
|
|
queueName={queueName!}
|
|
visible={purgeQueueModalShown}
|
|
onClose={() => setPurgeQueueModalShown(false)}
|
|
/>
|
|
|
|
<ConfirmationModal
|
|
visible={rlsConfirmModalOpen}
|
|
title="Enable Row Level Security"
|
|
confirmLabel="Enable RLS"
|
|
confirmLabelLoading="Enabling RLS"
|
|
loading={isUpdatingTable}
|
|
onCancel={() => setRlsConfirmModalOpen(false)}
|
|
onConfirm={() => onToggleRLS()}
|
|
>
|
|
<p className="text-sm text-foreground-light">
|
|
Are you sure you want to enable Row Level Security for the queue "{queueName}"?
|
|
</p>
|
|
</ConfirmationModal>
|
|
</div>
|
|
)
|
|
}
|