mirror of
https://github.com/supabase/supabase.git
synced 2026-10-14 13:49:09 +03:00
Move EditSecretModal to be child of SecretRow
In the accompanying PR for `DeleteSecretModal`, that one will also be moved over to `SecretRow`
This commit is contained in:
1 parent
f24fb8c7aa
commit
9104e47079
4 files changed
+81
-75
No files matched your search
@@ -27,7 +27,7 @@ import { EyeOff, Eye } from 'lucide-react'
|
||||
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
|
||||
|
||||
interface EditSecretModalProps {
|
||||
selectedSecret: VaultSecret | undefined
|
||||
secret: VaultSecret | undefined
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
@@ -37,39 +37,39 @@ const SecretSchema = z.object({
|
||||
secret: z.string().min(1, 'Please enter your secret value'),
|
||||
})
|
||||
|
||||
const EditSecretModal = ({ selectedSecret, onClose }: EditSecretModalProps) => {
|
||||
const EditSecretModal = ({ secret, onClose }: EditSecretModalProps) => {
|
||||
const [showSecretValue, setShowSecretValue] = useState(false)
|
||||
const { project } = useProjectContext()
|
||||
const formId = 'edit-vault-secret-form'
|
||||
const { data, isLoading: isLoadingSecretValue } = useVaultSecretDecryptedValueQuery(
|
||||
{
|
||||
projectRef: project?.ref!,
|
||||
id: selectedSecret?.id!,
|
||||
id: secret?.id!,
|
||||
connectionString: project?.connectionString,
|
||||
},
|
||||
{ enabled: selectedSecret !== undefined && !!(project?.ref && selectedSecret?.id) }
|
||||
{ enabled: secret !== undefined && !!(project?.ref && secret?.id) }
|
||||
)
|
||||
const form = useForm<z.infer<typeof SecretSchema>>({
|
||||
resolver: zodResolver(SecretSchema),
|
||||
defaultValues: {
|
||||
name: selectedSecret?.name ?? '',
|
||||
description: selectedSecret?.description ?? '',
|
||||
secret: selectedSecret?.decryptedSecret ?? data ?? '',
|
||||
name: secret?.name ?? '',
|
||||
description: secret?.description ?? '',
|
||||
secret: secret?.decryptedSecret ?? data ?? '',
|
||||
},
|
||||
values: {
|
||||
name: selectedSecret?.name ?? '',
|
||||
description: selectedSecret?.description ?? '',
|
||||
secret: selectedSecret?.decryptedSecret ?? data ?? '',
|
||||
name: secret?.name ?? '',
|
||||
description: secret?.description ?? '',
|
||||
secret: secret?.decryptedSecret ?? data ?? '',
|
||||
},
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedSecret !== undefined) {
|
||||
if (secret !== undefined) {
|
||||
setShowSecretValue(false)
|
||||
}
|
||||
}, [selectedSecret])
|
||||
}, [secret])
|
||||
|
||||
const { mutateAsync: updateSecret, isLoading: isSubmitting } = useVaultSecretUpdateMutation()
|
||||
const { mutate: updateSecret, isLoading: isSubmitting } = useVaultSecretUpdateMutation()
|
||||
|
||||
const onSubmit: SubmitHandler<z.infer<typeof SecretSchema>> = async (values) => {
|
||||
if (!project) return console.error('Project is required')
|
||||
@@ -77,15 +77,15 @@ const EditSecretModal = ({ selectedSecret, onClose }: EditSecretModalProps) => {
|
||||
const payload: Partial<VaultSecret> = {
|
||||
secret: values.secret,
|
||||
}
|
||||
if (values.name !== selectedSecret?.name) payload.name = values.name
|
||||
if (values.description !== selectedSecret?.description) payload.description = values.description
|
||||
if (values.name !== secret?.name) payload.name = values.name
|
||||
if (values.description !== secret?.description) payload.description = values.description
|
||||
|
||||
if (!isEmpty(payload) && selectedSecret) {
|
||||
if (!isEmpty(payload) && secret) {
|
||||
updateSecret(
|
||||
{
|
||||
projectRef: project.ref,
|
||||
connectionString: project?.connectionString,
|
||||
id: selectedSecret.id,
|
||||
id: secret.id,
|
||||
...payload,
|
||||
},
|
||||
{
|
||||
@@ -103,7 +103,7 @@ const EditSecretModal = ({ selectedSecret, onClose }: EditSecretModalProps) => {
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={selectedSecret !== undefined}
|
||||
open
|
||||
onOpenChange={(open) => {
|
||||
if (!open) {
|
||||
form.reset()
|
||||
|
||||
@@ -20,17 +20,17 @@ import { useVaultSecretDecryptedValueQuery } from 'data/vault/vault-secret-decry
|
||||
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
|
||||
import { Edit3, Eye, EyeOff, Key, Loader, MoreVertical, Trash } from 'lucide-react'
|
||||
import type { VaultSecret } from 'types'
|
||||
import EditSecretModal from './EditSecretModal'
|
||||
|
||||
interface SecretRowProps {
|
||||
secret: VaultSecret
|
||||
onSelectEdit: (secret: VaultSecret) => void
|
||||
onSelectRemove: (secret: VaultSecret) => void
|
||||
}
|
||||
|
||||
const SecretRow = ({ secret, onSelectEdit, onSelectRemove }: SecretRowProps) => {
|
||||
const { ref } = useParams()
|
||||
const { project } = useProjectContext()
|
||||
|
||||
const [modal, setModal] = useState<string | null>(null)
|
||||
const [revealSecret, setRevealSecret] = useState(false)
|
||||
const name = secret?.name ?? 'No name provided'
|
||||
|
||||
@@ -46,6 +46,15 @@ const SecretRow = ({ secret, onSelectEdit, onSelectRemove }: SecretRowProps) =>
|
||||
enabled: !!(ref! && secret.id) && revealSecret,
|
||||
}
|
||||
)
|
||||
const renderModal = () => {
|
||||
const onClose = () => setModal(null)
|
||||
switch (modal) {
|
||||
case `edit`:
|
||||
return <EditSecretModal secret={secret} onClose={onClose} />
|
||||
default:
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="px-6 py-4 flex items-center space-x-4">
|
||||
@@ -95,31 +104,25 @@ const SecretRow = ({ secret, onSelectEdit, onSelectRemove }: SecretRowProps) =>
|
||||
{secret.updated_at === secret.created_at ? 'Added' : 'Updated'} on{' '}
|
||||
{dayjs(secret.updated_at).format('MMM D, YYYY')}
|
||||
</p>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button title="Manage Secret" type="text" className="px-1" icon={<MoreVertical />} />
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent side="bottom" align="end" className="w-32">
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<DropdownMenuItem
|
||||
className="space-x-2"
|
||||
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
title="Manage Secret"
|
||||
type="text"
|
||||
className="px-1"
|
||||
disabled={!canManageSecrets}
|
||||
onClick={() => onSelectEdit(secret)}
|
||||
>
|
||||
icon={<MoreVertical />}
|
||||
/>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent side="bottom" align="end" className="w-32">
|
||||
<DropdownMenuItem className="space-x-2" onClick={() => setModal(`edit`)}>
|
||||
<Edit3 size="14" />
|
||||
<p>Edit</p>
|
||||
</DropdownMenuItem>
|
||||
</TooltipTrigger>
|
||||
{!canManageSecrets && (
|
||||
<TooltipContent side="bottom">
|
||||
You need additional permissions to edit secrets
|
||||
</TooltipContent>
|
||||
)}
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<DropdownMenuItem
|
||||
className="space-x-2"
|
||||
disabled={!canManageSecrets}
|
||||
@@ -128,15 +131,16 @@ const SecretRow = ({ secret, onSelectEdit, onSelectRemove }: SecretRowProps) =>
|
||||
<Trash stroke="red" size="14" />
|
||||
<p className="text-foreground-light">Delete</p>
|
||||
</DropdownMenuItem>
|
||||
</TooltipTrigger>
|
||||
{!canManageSecrets && (
|
||||
<TooltipContent side="bottom">
|
||||
You need additional permissions to delete secrets
|
||||
</TooltipContent>
|
||||
)}
|
||||
</Tooltip>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</TooltipTrigger>
|
||||
{!canManageSecrets && (
|
||||
<TooltipContent side="bottom">
|
||||
You need additional permissions to manage secrets
|
||||
</TooltipContent>
|
||||
)}
|
||||
</Tooltip>
|
||||
{renderModal()}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
+27
-25
@@ -9,22 +9,29 @@ import { ButtonTooltip } from 'components/ui/ButtonTooltip'
|
||||
import { DocsButton } from 'components/ui/DocsButton'
|
||||
import { useVaultSecretsQuery } from 'data/vault/vault-secrets-query'
|
||||
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
|
||||
import type { VaultSecret } from 'types'
|
||||
import { Button, Input, Listbox, Separator } from 'ui'
|
||||
import {
|
||||
Button,
|
||||
Input,
|
||||
Select_Shadcn_,
|
||||
SelectContent_Shadcn_,
|
||||
SelectItem_Shadcn_,
|
||||
SelectTrigger_Shadcn_,
|
||||
SelectValue_Shadcn_,
|
||||
Separator,
|
||||
} from 'ui'
|
||||
import AddNewSecretModal from './AddNewSecretModal'
|
||||
import DeleteSecretModal from './DeleteSecretModal'
|
||||
import EditSecretModal from './EditSecretModal'
|
||||
import SecretRow from './SecretRow'
|
||||
import { VaultSecret } from 'types'
|
||||
|
||||
export const SecretsManagement = () => {
|
||||
const { search } = useParams()
|
||||
const { project } = useProjectContext()
|
||||
|
||||
const [searchValue, setSearchValue] = useState<string>('')
|
||||
const [selectedSort, setSelectedSort] = useState<'updated_at' | 'name'>('updated_at')
|
||||
const [showAddSecretModal, setShowAddSecretModal] = useState(false)
|
||||
const [selectedSecretToEdit, setSelectedSecretToEdit] = useState<VaultSecret>()
|
||||
const [selectedSecretToRemove, setSelectedSecretToRemove] = useState<VaultSecret>()
|
||||
const [selectedSort, setSelectedSort] = useState('updated_at')
|
||||
|
||||
const canManageSecrets = useCheckPermissions(PermissionAction.TENANT_SQL_ADMIN_WRITE, 'tables')
|
||||
|
||||
@@ -49,7 +56,7 @@ export const SecretsManagement = () => {
|
||||
if (selectedSort === 'updated_at') {
|
||||
return Number(new Date(s.updated_at))
|
||||
} else {
|
||||
return s[selectedSort]
|
||||
return s[selectedSort as keyof VaultSecret]
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -81,26 +88,26 @@ export const SecretsManagement = () => {
|
||||
: []
|
||||
}
|
||||
/>
|
||||
<div className="md:w-44">
|
||||
<Listbox size="tiny" value={selectedSort} onChange={setSelectedSort}>
|
||||
<Listbox.Option
|
||||
id="updated_at"
|
||||
className="max-w-[180px]"
|
||||
value="updated_at"
|
||||
label="Sort by updated at"
|
||||
>
|
||||
<Select_Shadcn_ value={selectedSort} onValueChange={setSelectedSort}>
|
||||
<SelectTrigger_Shadcn_ size="tiny" className="md:w-44">
|
||||
<SelectValue_Shadcn_ asChild>
|
||||
<>Sort by {selectedSort}</>
|
||||
</SelectValue_Shadcn_>
|
||||
</SelectTrigger_Shadcn_>
|
||||
<SelectContent_Shadcn_>
|
||||
<SelectItem_Shadcn_ value="updated_at" className="text-xs">
|
||||
Updated at
|
||||
</Listbox.Option>
|
||||
<Listbox.Option
|
||||
</SelectItem_Shadcn_>
|
||||
<SelectItem_Shadcn_ value="name" className="text-xs">
|
||||
id="name"
|
||||
className="max-w-[180px]"
|
||||
value="name"
|
||||
label="Sort by name"
|
||||
>
|
||||
Name
|
||||
</Listbox.Option>
|
||||
</Listbox>
|
||||
</div>
|
||||
</SelectItem_Shadcn_>
|
||||
</SelectContent_Shadcn_>
|
||||
</Select_Shadcn_>
|
||||
</div>
|
||||
<div className="flex items-center gap-x-2">
|
||||
<DocsButton href="https://supabase.com/docs/guides/database/vault" />
|
||||
@@ -136,7 +143,6 @@ export const SecretsManagement = () => {
|
||||
<Fragment key={`secret-${idx}`}>
|
||||
<SecretRow
|
||||
secret={secret}
|
||||
onSelectEdit={setSelectedSecretToEdit}
|
||||
onSelectRemove={setSelectedSecretToRemove}
|
||||
/>
|
||||
{idx !== secrets.length - 1 && <Separator />}
|
||||
@@ -167,12 +173,8 @@ export const SecretsManagement = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<EditSecretModal
|
||||
selectedSecret={selectedSecretToEdit}
|
||||
onClose={() => setSelectedSecretToEdit(undefined)}
|
||||
/>
|
||||
<DeleteSecretModal
|
||||
selectedSecret={selectedSecretToRemove}
|
||||
secret={selectedSecretToRemove}
|
||||
onClose={() => setSelectedSecretToRemove(undefined)}
|
||||
/>
|
||||
<AddNewSecretModal
|
||||
|
||||
+1
-1
@@ -47,7 +47,7 @@ const Page = ({ onClose }: { onClose: () => void }) => {
|
||||
</DropdownMenu>
|
||||
|
||||
<EditSecretModal
|
||||
selectedSecret={selectedSecretToEdit}
|
||||
secret={selectedSecretToEdit}
|
||||
onClose={() => {
|
||||
setSelectedSecretToEdit(undefined)
|
||||
onClose()
|
||||
|
||||
Reference in new issue
Block a user