WrapperRow.tsx 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. import { PermissionAction } from '@supabase/shared-types/out/constants'
  2. import { useParams } from 'common'
  3. import { partition } from 'lodash'
  4. import { ChevronRight, Edit, ExternalLink, Table2, Trash } from 'lucide-react'
  5. import Link from 'next/link'
  6. import { parseAsString, useQueryState } from 'nuqs'
  7. import { Badge, TableCell, TableRow, Tooltip, TooltipContent, TooltipTrigger } from 'ui'
  8. import { INTEGRATIONS } from '../Landing/Integrations.constants'
  9. import { convertKVStringArrayToJson, formatWrapperTables } from './Wrappers.utils'
  10. import { ButtonTooltip } from '@/components/ui/ButtonTooltip'
  11. import type { FDW } from '@/data/fdw/fdws-query'
  12. import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions'
  13. interface WrapperRowProps {
  14. wrapper: FDW
  15. }
  16. export const WrapperRow = ({ wrapper }: WrapperRowProps) => {
  17. const { ref, id } = useParams()
  18. const { can: canManageWrappers } = useAsyncCheckPermissions(
  19. PermissionAction.TENANT_SQL_ADMIN_WRITE,
  20. 'wrappers'
  21. )
  22. const [, setSelectedWrapperToEdit] = useQueryState('edit', parseAsString)
  23. const [, setSelectedWrapperToDelete] = useQueryState('delete', parseAsString)
  24. const integration = INTEGRATIONS.find((i) => i.id === id)
  25. if (!integration || integration.type !== 'wrapper') {
  26. return <p className="text-foreground-lighter text-sm">A wrapper with this ID does not exist</p>
  27. }
  28. const serverOptions = convertKVStringArrayToJson(wrapper.server_options ?? [])
  29. const [encryptedMetadata, visibleMetadata] = partition(
  30. integration?.meta?.server.options.filter((option) => !option.hidden),
  31. 'secureEntry'
  32. )
  33. const _tables = formatWrapperTables(wrapper, integration?.meta)
  34. return (
  35. <TableRow>
  36. <TableCell className="gap-2 align-top py-3! min-w-80">
  37. {wrapper.name}
  38. {visibleMetadata.map((metadata) => (
  39. <div
  40. key={metadata.name}
  41. className="flex items-center space-x-2 text-sm text-foreground-light"
  42. >
  43. <span className="text-foreground-lighter text-nowrap">{metadata.label}:</span>
  44. <span className="truncate max-w-72" title={serverOptions[metadata.name]}>
  45. {serverOptions[metadata.name]}
  46. </span>
  47. </div>
  48. ))}
  49. </TableCell>
  50. <TableCell className="space-y-2 p-4!">
  51. {_tables?.map((table) => {
  52. const target = table.table ?? table.object ?? table.src_key
  53. return (
  54. <div key={table.id} className="flex items-center">
  55. <Badge className="bg-surface-300 gap-2 font-mono text-[0.75rem] h-6 text-foreground rounded-r-none">
  56. <div className="relative w-3 h-3 flex items-center justify-center">
  57. {integration.icon({ className: 'p-0' })}
  58. </div>
  59. <Tooltip>
  60. <TooltipTrigger className="truncate max-w-28">{target}</TooltipTrigger>
  61. <TooltipContent
  62. side="bottom"
  63. className="max-w-64 whitespace-pre-wrap wrap-break-word"
  64. >
  65. {target}
  66. </TooltipContent>
  67. </Tooltip>
  68. <ChevronRight size={12} strokeWidth={1.5} className="text-foreground-lighter/50" />
  69. </Badge>
  70. <Link href={`/project/${ref}/editor/${table.id}`}>
  71. <Badge className="transition hover:bg-surface-300 px-2 rounded-l-none gap-1.5 h-6 font-mono text-[0.75rem] border-l-0">
  72. <Table2 size={12} strokeWidth={1.5} className="text-foreground-lighter/50" />
  73. <Tooltip>
  74. <TooltipTrigger className="truncate max-w-28">
  75. {table.schema}.{table.table_name}
  76. </TooltipTrigger>
  77. <TooltipContent
  78. side="bottom"
  79. className="max-w-64 whitespace-pre-wrap wrap-break-word"
  80. >
  81. {table.schema}.{table.table_name}
  82. </TooltipContent>
  83. </Tooltip>
  84. </Badge>
  85. </Link>
  86. </div>
  87. )
  88. })}
  89. </TableCell>
  90. <TableCell>
  91. {encryptedMetadata.map((metadata) => (
  92. <div key={metadata.name} className="flex items-center space-x-2 text-sm">
  93. <Link
  94. href={`/project/${ref}/settings/vault/secrets?search=${encodeURIComponent(
  95. `${wrapper.name}_${metadata.name}`
  96. )}`}
  97. className="transition text-foreground-light hover:text-foreground flex items-center space-x-2 max-w-28"
  98. >
  99. <span className="truncate" title={metadata.label}>
  100. {metadata.label}
  101. </span>
  102. <div>
  103. <ExternalLink size={12} strokeWidth={1.5} className="text-foreground-lighter" />
  104. </div>
  105. </Link>
  106. </div>
  107. ))}
  108. </TableCell>
  109. <TableCell className="flex-nowrap">
  110. <div className="flex items-center gap-x-2">
  111. <ButtonTooltip
  112. disabled={!canManageWrappers}
  113. type="default"
  114. icon={<Edit strokeWidth={1.5} />}
  115. className="px-1.5"
  116. onClick={() => setSelectedWrapperToEdit(wrapper.id.toString())}
  117. tooltip={{
  118. content: {
  119. side: 'bottom',
  120. text: !canManageWrappers
  121. ? 'You need additional permissions to edit wrappers'
  122. : 'Edit wrapper',
  123. },
  124. }}
  125. />
  126. <ButtonTooltip
  127. type="default"
  128. disabled={!canManageWrappers}
  129. icon={<Trash strokeWidth={1.5} />}
  130. className="px-1.5"
  131. onClick={() => setSelectedWrapperToDelete(wrapper.id.toString())}
  132. tooltip={{
  133. content: {
  134. side: 'bottom',
  135. text: !canManageWrappers
  136. ? 'You need additional permissions to delete wrappers'
  137. : 'Delete wrapper',
  138. },
  139. }}
  140. />
  141. </div>
  142. </TableCell>
  143. </TableRow>
  144. )
  145. }