Files
supabase/apps/studio/components/interfaces/Storage/AnalyticBucketDetails/DecryptedReadOnlyInput.tsx
T
Danny WhiteandJoshen Lim 44f0eff125 studio(chore): improve analytics bucket page (#39545)
* specialized dialog for analytics (and vector) bucket creation

* handle missing wrapper extension and iceberg wrapper integration

* bucket creation improvements

* extension and integration checks

* dialog header consistency

* simplify bucket dialog creation button trigger

* fix subtitle ternary

* example bucket without dynamic route

* create dynamic segment

* fancy connection table

* improve bucket page

* connection table polish

* polish

* delete bucket modal

* improve missing installations design

* new table modal

* fix copy icon

* copy all env button placeholder

* list all analytics buckets

* specialised bucket modal

* shared installer hooks and components

* make iceberg wrapper not dependent on wrapper extension

There are cases where the Iceberg Wrapper can be installed but the Wrapper extension has since been removed

* better admonition copywriting

* todo

* misc

* simplify

* simplifications

* work within existing page

* improvements to existing route

* v2 tables

* simplify

* new table modal

* change route to thin wrapper

* reduce size of PR

* remove double scaffold element

* minor nits

---------

Co-authored-by: Joshen Lim <joshenlimek@gmail.com>
2025-10-17 11:08:04 +11:00

107 lines
3.5 KiB
TypeScript

import { ExternalLink, Eye, EyeOff, Loader } from 'lucide-react'
import { useState } from 'react'
import { useParams } from 'common'
import { useVaultSecretDecryptedValueQuery } from 'data/vault/vault-secret-decrypted-value-query'
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
import { Button, CardContent, Tooltip, TooltipContent, TooltipTrigger } from 'ui'
import { Input } from 'ui-patterns/DataInputs/Input'
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
export const DecryptedReadOnlyInput = ({
value,
secureEntry,
descriptionText,
label,
}: {
value?: string
secureEntry: boolean
descriptionText: string
label: string
}) => {
const { ref } = useParams()
const { data: project } = useSelectedProjectQuery()
const [showHidden, setShowHidden] = useState(false)
const { data: decryptedValue, isLoading: isDecryptedValueLoading } =
useVaultSecretDecryptedValueQuery(
{
projectRef: project?.ref,
connectionString: project?.connectionString,
id: value ?? '',
},
{ enabled: secureEntry && showHidden }
)
const isLoading = isDecryptedValueLoading && showHidden
const renderedValue = secureEntry
? isLoading
? 'Fetching value from Vault...'
: showHidden
? decryptedValue
: value
: value
return (
<CardContent className="py-6 border-b border-panel-border-interior-light">
<FormItemLayout
layout="horizontal"
label={
<div className="flex items-center gap-x-2">
<span>{label}</span>
<Tooltip>
<TooltipTrigger asChild>
<a
target="_blank"
rel="noreferrer noopener"
href={`/project/${ref}/integrations/vault/secrets?search=${value}`}
>
<ExternalLink
size={14}
className="text-foreground-muted hover:text-foreground-lighter transition"
/>
</a>
</TooltipTrigger>
<TooltipContent side="bottom">Open in Vault</TooltipContent>
</Tooltip>
</div>
}
description={descriptionText}
isReactForm={false}
>
<Input
readOnly
// If the value is secure, allow copying to clipboard if the value is revealed. Otherwise, always allow copying
copy={!secureEntry || (!isDecryptedValueLoading && showHidden)}
value={renderedValue}
type={secureEntry ? (isLoading ? 'text' : showHidden ? 'text' : 'password') : 'text'}
actions={
secureEntry ? (
isLoading ? (
<div className="flex items-center justify-center">
<Button
disabled
type="default"
className="w-7"
icon={<Loader className="animate-spin" />}
/>
</div>
) : (
<div className="flex items-center justify-center">
<Button
type="default"
className="w-7"
loading={showHidden && isDecryptedValueLoading}
icon={showHidden ? <Eye /> : <EyeOff />}
onClick={() => setShowHidden(!showHidden)}
/>
</div>
)
) : null
}
/>
</FormItemLayout>
</CardContent>
)
}