| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212 |
- import { PermissionAction } from '@supabase/shared-types/out/constants'
- import { AlertTriangle, Book, Github, Loader2 } from 'lucide-react'
- import Link from 'next/link'
- import { useState } from 'react'
- import { extensions } from 'shared-data'
- import { toast } from 'sonner'
- import { Button, Switch, TableCell, TableRow, Tooltip, TooltipContent, TooltipTrigger } from 'ui'
- import { Admonition } from 'ui-patterns'
- import { ConfirmationModal } from 'ui-patterns/Dialogs/ConfirmationModal'
- import { EnableExtensionModal } from './EnableExtensionModal'
- import { EXTENSION_DISABLE_WARNINGS } from './Extensions.constants'
- import { ButtonTooltip } from '@/components/ui/ButtonTooltip'
- import { useDatabaseExtensionDisableMutation } from '@/data/database-extensions/database-extension-disable-mutation'
- import { DatabaseExtension } from '@/data/database-extensions/database-extensions-query'
- import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions'
- import { useIsOrioleDb, useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
- import { DOCS_URL } from '@/lib/constants'
- interface ExtensionRowProps {
- extension: DatabaseExtension
- }
- export const ExtensionRow = ({ extension }: ExtensionRowProps) => {
- const { data: project } = useSelectedProjectQuery()
- const isOn = extension.installed_version !== null
- const isOrioleDb = useIsOrioleDb()
- const [isDisableModalOpen, setIsDisableModalOpen] = useState(false)
- const [showConfirmEnableModal, setShowConfirmEnableModal] = useState(false)
- const { can: canUpdateExtensions } = useAsyncCheckPermissions(
- PermissionAction.TENANT_SQL_ADMIN_WRITE,
- 'extensions'
- )
- const orioleDbCheck = isOrioleDb && extension.name === 'orioledb'
- const disabled = !canUpdateExtensions || orioleDbCheck
- const extensionMeta = extensions.find((item) => item.name === extension.name)
- const docsUrl = extensionMeta?.link.startsWith('/guides')
- ? `${DOCS_URL}${extensionMeta?.link}`
- : (extensionMeta?.link ?? undefined)
- const { mutate: disableExtension, isPending: isDisabling } = useDatabaseExtensionDisableMutation({
- onSuccess: () => {
- toast.success(`${extension.name} is off.`)
- setIsDisableModalOpen(false)
- },
- })
- const onConfirmDisable = () => {
- if (project === undefined) return console.error('Project is required')
- disableExtension({
- projectRef: project.ref,
- connectionString: project.connectionString,
- id: extension.name,
- })
- }
- return (
- <>
- <TableRow>
- <TableCell>
- <div className="flex items-center gap-x-2">
- <span title={extension.name} className="truncate inline-block max-w-48">
- {extension.name}
- </span>
- {extensionMeta?.deprecated && extensionMeta?.deprecated.length > 0 && (
- <ButtonTooltip
- type="warning"
- icon={<AlertTriangle />}
- className="rounded-full"
- tooltip={{
- content: {
- text: `The extension is deprecated and will be removed in ${extensionMeta.deprecated.join(', ')}.`,
- },
- }}
- >
- Deprecated
- </ButtonTooltip>
- )}
- </div>
- </TableCell>
- <TableCell className="w-28 font-mono tracking-tighter">
- {extension?.installed_version ?? extension.default_version}
- </TableCell>
- <TableCell className="truncate">{isOn ? extension.schema : '-'}</TableCell>
- <TableCell className="text-foreground-light">
- <p className="block" title={extension.comment ?? undefined}>
- {extension.comment}
- </p>
- </TableCell>
- <TableCell>
- {extensionMeta?.product ? (
- <div className="flex flex-col gap-1">
- {extensionMeta.product_url ? (
- <Link
- href={extensionMeta.product_url.replace('{ref}', project?.ref ?? '')}
- className="transition hover:text-foreground"
- >
- {extensionMeta.product}
- </Link>
- ) : (
- <span>{extensionMeta.product}</span>
- )}
- {!isOn && (
- <span className="text-foreground-lighter text-xs">
- Install extension to use {extensionMeta.product}
- </span>
- )}
- </div>
- ) : (
- <span className="text-foreground-lighter">-</span>
- )}
- </TableCell>
- <TableCell>
- <div className="flex gap-2 items-center">
- {extensionMeta?.github_url && (
- <Button asChild type="default" icon={<Github />} className="rounded-full">
- <a
- target="_blank"
- rel="noreferrer"
- href={extensionMeta.github_url}
- className="font-mono tracking-tighter"
- >
- {extensionMeta.github_url.split('/').slice(-2).join('/')}
- </a>
- </Button>
- )}
- {docsUrl !== undefined && (
- <Button asChild type="default" icon={<Book />} className="rounded-full">
- <a
- target="_blank"
- rel="noreferrer"
- className="font-mono tracking-tighter"
- href={docsUrl}
- >
- Docs
- </a>
- </Button>
- )}
- </div>
- </TableCell>
- {/*
- [Joshen] The div child here and all these classes is to properly add a left border
- to make the sticky column more distinct
- */}
- <TableCell className="w-20 sticky bg-surface-100 right-0 relative">
- <div className="absolute top-0 right-0 left-0 bottom-0 flex items-center justify-center border-l">
- {isDisabling ? (
- <Loader2 className="animate-spin" size={16} />
- ) : (
- <Tooltip>
- <TooltipTrigger>
- <Switch
- disabled={disabled}
- checked={isOn}
- onCheckedChange={() =>
- isOn ? setIsDisableModalOpen(true) : setShowConfirmEnableModal(true)
- }
- />
- </TooltipTrigger>
- {disabled && (
- <TooltipContent side="bottom">
- {!canUpdateExtensions
- ? 'You need additional permissions to toggle extensions'
- : orioleDbCheck
- ? 'Project is using OrioleDB and cannot be disabled'
- : null}
- </TooltipContent>
- )}
- </Tooltip>
- )}
- </div>
- </TableCell>
- </TableRow>
- <EnableExtensionModal
- visible={showConfirmEnableModal}
- extension={extension}
- onCancel={() => setShowConfirmEnableModal(false)}
- />
- <ConfirmationModal
- visible={isDisableModalOpen}
- title="Confirm to disable extension"
- confirmLabel="Disable"
- variant="destructive"
- confirmLabelLoading="Disabling"
- loading={isDisabling}
- onCancel={() => setIsDisableModalOpen(false)}
- onConfirm={() => onConfirmDisable()}
- >
- <div className="flex flex-col gap-y-3">
- <p className="text-sm text-foreground-light">
- Are you sure you want to turn OFF the "{extension.name}" extension?
- </p>
- {EXTENSION_DISABLE_WARNINGS[extension.name] && (
- <Admonition type="warning">{EXTENSION_DISABLE_WARNINGS[extension.name]}</Admonition>
- )}
- </div>
- </ConfirmationModal>
- </>
- )
- }
|