mirror of
https://github.com/supabase/supabase.git
synced 2026-10-15 06:09:08 +03:00
160 lines
5.5 KiB
TypeScript
160 lines
5.5 KiB
TypeScript
import { isEmpty } from 'lodash'
|
|
import { useEffect, useState } from 'react'
|
|
import { toast } from 'sonner'
|
|
|
|
import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
|
|
import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader'
|
|
import { useVaultSecretDecryptedValueQuery } from 'data/vault/vault-secret-decrypted-value-query'
|
|
import { useVaultSecretUpdateMutation } from 'data/vault/vault-secret-update-mutation'
|
|
import type { VaultSecret } from 'types'
|
|
import { Button, Form, Input, Modal } from 'ui'
|
|
import { EyeOff, Eye } from 'lucide-react'
|
|
|
|
interface EditSecretModalProps {
|
|
selectedSecret: VaultSecret | undefined
|
|
onClose: () => void
|
|
}
|
|
|
|
const EditSecretModal = ({ selectedSecret, onClose }: EditSecretModalProps) => {
|
|
const [showSecretValue, setShowSecretValue] = useState(false)
|
|
const { project } = useProjectContext()
|
|
|
|
const { mutateAsync: updateSecret } = useVaultSecretUpdateMutation()
|
|
|
|
let INITIAL_VALUES = {
|
|
name: selectedSecret?.name ?? '',
|
|
description: selectedSecret?.description ?? '',
|
|
secret: selectedSecret?.decryptedSecret ?? '',
|
|
}
|
|
|
|
useEffect(() => {
|
|
if (selectedSecret !== undefined) {
|
|
setShowSecretValue(false)
|
|
}
|
|
}, [selectedSecret])
|
|
|
|
const validate = (values: any) => {
|
|
const errors: any = {}
|
|
if (values.name.length === 0) errors.name = 'Please provide a name for your secret'
|
|
if (values.secret.length === 0) errors.secret = 'Please enter your secret value'
|
|
return errors
|
|
}
|
|
|
|
const onUpdateSecret = async (values: any, { setSubmitting }: any) => {
|
|
if (!project) return console.error('Project is required')
|
|
|
|
try {
|
|
const payload: Partial<VaultSecret> = {}
|
|
if (values.name !== selectedSecret?.name) payload.name = values.name
|
|
if (values.description !== selectedSecret?.description)
|
|
payload.description = values.description
|
|
payload.secret = values.secret
|
|
|
|
if (!isEmpty(payload) && selectedSecret) {
|
|
setSubmitting(true)
|
|
const res = await updateSecret({
|
|
projectRef: project.ref,
|
|
connectionString: project?.connectionString,
|
|
id: selectedSecret.id,
|
|
...payload,
|
|
})
|
|
if (!res.error) {
|
|
toast.success('Successfully updated secret')
|
|
setSubmitting(false)
|
|
onClose()
|
|
} else {
|
|
toast.error(`Failed to update secret: ${res.error.message}`)
|
|
setSubmitting(false)
|
|
}
|
|
}
|
|
} finally {
|
|
}
|
|
}
|
|
|
|
return (
|
|
<Modal
|
|
hideFooter
|
|
size="medium"
|
|
visible={selectedSecret !== undefined}
|
|
onCancel={onClose}
|
|
header={<h5 className="text-sm text-foreground">Edit secret</h5>}
|
|
>
|
|
<Form
|
|
id="add-new-secret-form"
|
|
initialValues={INITIAL_VALUES}
|
|
validate={validate}
|
|
validateOnBlur={false}
|
|
onSubmit={onUpdateSecret}
|
|
>
|
|
{({ isSubmitting, resetForm }: any) => {
|
|
const {
|
|
data,
|
|
isLoading: isLoadingSecretValue,
|
|
isSuccess: isSuccessSecretValue,
|
|
// [Joshen] JFYI this is breaking rules of hooks, will be fixed once we move to
|
|
// using react hook form instead
|
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
} = useVaultSecretDecryptedValueQuery(
|
|
{
|
|
projectRef: project?.ref!,
|
|
id: selectedSecret?.id!,
|
|
connectionString: project?.connectionString,
|
|
},
|
|
{ enabled: selectedSecret !== undefined && !!(project?.ref && selectedSecret?.id) }
|
|
)
|
|
|
|
// [Joshen] JFYI this is breaking rules of hooks, will be fixed once we move to
|
|
// using react hook form instead
|
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
useEffect(() => {
|
|
if (selectedSecret !== undefined && isSuccessSecretValue) {
|
|
resetForm({
|
|
values: { ...INITIAL_VALUES, secret: data },
|
|
initialValues: { ...INITIAL_VALUES, secret: data },
|
|
})
|
|
}
|
|
}, [selectedSecret, isSuccessSecretValue])
|
|
|
|
return isLoadingSecretValue ? (
|
|
<Modal.Content>
|
|
<GenericSkeletonLoader />
|
|
</Modal.Content>
|
|
) : (
|
|
<>
|
|
<Modal.Content className="space-y-4">
|
|
<Input id="name" label="Name" />
|
|
<Input id="description" label="Description" labelOptional="Optional" />
|
|
<Input
|
|
id="secret"
|
|
type={showSecretValue ? 'text' : 'password'}
|
|
label="Secret value"
|
|
actions={
|
|
<div className="mr-1">
|
|
<Button
|
|
type="default"
|
|
icon={showSecretValue ? <EyeOff /> : <Eye />}
|
|
onClick={() => setShowSecretValue(!showSecretValue)}
|
|
/>
|
|
</div>
|
|
}
|
|
/>
|
|
</Modal.Content>
|
|
<Modal.Separator />
|
|
<Modal.Content className="flex items-center justify-end space-x-2">
|
|
<Button type="default" disabled={isSubmitting} onClick={() => onClose()}>
|
|
Cancel
|
|
</Button>
|
|
<Button htmlType="submit" disabled={isSubmitting} loading={isSubmitting}>
|
|
Update secret
|
|
</Button>
|
|
</Modal.Content>
|
|
</>
|
|
)
|
|
}}
|
|
</Form>
|
|
</Modal>
|
|
)
|
|
}
|
|
|
|
export default EditSecretModal
|