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