mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
183 lines
6.1 KiB
TypeScript
183 lines
6.1 KiB
TypeScript
import Link from 'next/link'
|
|
import dayjs from 'dayjs'
|
|
import { FC, useState } from 'react'
|
|
import {
|
|
Button,
|
|
IconEye,
|
|
IconEyeOff,
|
|
IconTrash,
|
|
Input,
|
|
Dropdown,
|
|
IconEdit3,
|
|
IconKey,
|
|
IconMoreVertical,
|
|
IconLoader,
|
|
} from 'ui'
|
|
import * as Tooltip from '@radix-ui/react-tooltip'
|
|
import { PermissionAction } from '@supabase/shared-types/out/constants'
|
|
import { VaultSecret } from 'types'
|
|
import { useStore, useCheckPermissions } from 'hooks'
|
|
import { useParams } from 'common/hooks'
|
|
|
|
interface Props {
|
|
secret: VaultSecret
|
|
onSelectEdit: (secret: any) => void
|
|
onSelectRemove: (secret: any) => void
|
|
}
|
|
|
|
const SecretRow: FC<Props> = ({ secret, onSelectEdit, onSelectRemove }) => {
|
|
const { vault } = useStore()
|
|
const { ref } = useParams()
|
|
|
|
const [isLoading, setIsLoading] = useState(false)
|
|
const [revealedValue, setRevealedValue] = useState<string>()
|
|
const name = secret?.name ?? 'No name provided'
|
|
|
|
const canManageSecrets = useCheckPermissions(PermissionAction.TENANT_SQL_ADMIN_WRITE, 'tables')
|
|
|
|
const revealSecret = async () => {
|
|
setIsLoading(true)
|
|
if (revealedValue === undefined) {
|
|
setRevealedValue(
|
|
secret.decryptedSecret !== undefined
|
|
? secret.decryptedSecret
|
|
: await vault.fetchSecretValue(secret.id)
|
|
)
|
|
} else {
|
|
setRevealedValue(undefined)
|
|
}
|
|
setIsLoading(false)
|
|
}
|
|
|
|
return (
|
|
<div className="px-6 py-4 flex items-center space-x-4">
|
|
<div className="space-y-1 min-w-[35%] max-w-[35%]">
|
|
<div>
|
|
<p className="text-sm text-scale-1200" title={name}>
|
|
{name}
|
|
</p>
|
|
{secret.description !== undefined && (
|
|
<p className="text-sm text-scale-1100" title={secret.description}>
|
|
{secret.description}
|
|
</p>
|
|
)}
|
|
</div>
|
|
<div className="flex items-center space-x-2 group">
|
|
<IconKey
|
|
size={14}
|
|
strokeWidth={2}
|
|
className="text-scale-1000 transition group-hover:text-brand"
|
|
/>
|
|
<Link href={`/project/${ref}/settings/vault/keys?id=${secret.key_id}`}>
|
|
<a
|
|
className="text-scale-1100 font-mono text-xs cursor-pointer transition group-hover:text-brand"
|
|
title={secret.key_id}
|
|
>
|
|
{secret.key_id}
|
|
</a>
|
|
</Link>
|
|
</div>
|
|
</div>
|
|
<div className="flex items-center space-x-2 w-[40%]">
|
|
<Button
|
|
type="text"
|
|
className="px-1.5"
|
|
icon={
|
|
isLoading ? (
|
|
<IconLoader className="animate-spin" size={16} strokeWidth={1.5} />
|
|
) : revealedValue === undefined ? (
|
|
<IconEye size={16} strokeWidth={1.5} />
|
|
) : (
|
|
<IconEyeOff size={16} strokeWidth={1.5} />
|
|
)
|
|
}
|
|
onClick={() => revealSecret()}
|
|
/>
|
|
<div className="flex-grow">
|
|
{revealedValue === undefined ? (
|
|
<p className="text-sm font-mono">••••••••••••••••••</p>
|
|
) : (
|
|
<Input copy size="small" className="font-mono" value={revealedValue} />
|
|
)}
|
|
</div>
|
|
</div>
|
|
<div className="flex items-center justify-end w-[25%] space-x-4">
|
|
<p className="text-sm text-scale-1100">
|
|
{secret.updated_at === secret.created_at ? 'Added' : 'Updated'} on{' '}
|
|
{dayjs(secret.updated_at).format('MMM D, YYYY')}
|
|
</p>
|
|
<Dropdown
|
|
side="bottom"
|
|
className="w-[120px]"
|
|
overlay={
|
|
<>
|
|
<Tooltip.Root delayDuration={0}>
|
|
<Tooltip.Trigger>
|
|
<Dropdown.Item
|
|
icon={<IconEdit3 size="tiny" />}
|
|
disabled={!canManageSecrets}
|
|
onClick={() => onSelectEdit(secret)}
|
|
>
|
|
Edit
|
|
</Dropdown.Item>
|
|
</Tooltip.Trigger>
|
|
{!canManageSecrets && (
|
|
<Tooltip.Portal>
|
|
<Tooltip.Content side="bottom">
|
|
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
|
<div
|
|
className={[
|
|
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
|
'border border-scale-200',
|
|
].join(' ')}
|
|
>
|
|
<span className="text-xs text-scale-1200">
|
|
You need additional permissions to edit secrets
|
|
</span>
|
|
</div>
|
|
</Tooltip.Content>
|
|
</Tooltip.Portal>
|
|
)}
|
|
</Tooltip.Root>
|
|
<Tooltip.Root delayDuration={0}>
|
|
<Tooltip.Trigger>
|
|
<Dropdown.Item
|
|
disabled={!canManageSecrets}
|
|
icon={<IconTrash stroke="red" size="tiny" />}
|
|
onClick={() => onSelectRemove(secret)}
|
|
>
|
|
Delete
|
|
</Dropdown.Item>
|
|
</Tooltip.Trigger>
|
|
{!canManageSecrets && (
|
|
<Tooltip.Portal>
|
|
<Tooltip.Content side="bottom">
|
|
<Tooltip.Arrow className="radix-tooltip-arrow" />
|
|
<div
|
|
className={[
|
|
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
|
|
'border border-scale-200',
|
|
].join(' ')}
|
|
>
|
|
<span className="text-xs text-scale-1200">
|
|
You need additional permissions to delete secrets
|
|
</span>
|
|
</div>
|
|
</Tooltip.Content>
|
|
</Tooltip.Portal>
|
|
)}
|
|
</Tooltip.Root>
|
|
</>
|
|
}
|
|
>
|
|
<Button asChild type="text" className="px-1" icon={<IconMoreVertical />}>
|
|
<span></span>
|
|
</Button>
|
|
</Dropdown>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export default SecretRow
|