| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287 |
- 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 Briven 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>
- )
- }
|