DecryptedReadOnlyInput.tsx 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  1. import { useParams } from 'common'
  2. import { ExternalLink, Eye, EyeOff, Loader } from 'lucide-react'
  3. import Link from 'next/link'
  4. import { useState } from 'react'
  5. import { Button, CardContent, Tooltip, TooltipContent, TooltipTrigger } from 'ui'
  6. import { Input } from 'ui-patterns/DataInputs/Input'
  7. import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
  8. import { useVaultSecretDecryptedValueQuery } from '@/data/vault/vault-secret-decrypted-value-query'
  9. import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
  10. export const DecryptedReadOnlyInput = ({
  11. value,
  12. secureEntry,
  13. descriptionText,
  14. label,
  15. }: {
  16. value?: string
  17. secureEntry: boolean
  18. descriptionText: string
  19. label: string
  20. }) => {
  21. const { ref } = useParams()
  22. const { data: project } = useSelectedProjectQuery()
  23. const [showHidden, setShowHidden] = useState(false)
  24. const { data: decryptedValue, isPending: isDecryptedValueLoading } =
  25. useVaultSecretDecryptedValueQuery(
  26. {
  27. projectRef: project?.ref,
  28. connectionString: project?.connectionString,
  29. id: value ?? '',
  30. },
  31. { enabled: secureEntry && showHidden }
  32. )
  33. const isLoading = isDecryptedValueLoading && showHidden
  34. const renderedValue = secureEntry
  35. ? isLoading
  36. ? 'Fetching value from Vault...'
  37. : showHidden
  38. ? decryptedValue
  39. : value
  40. : value
  41. return (
  42. <CardContent className="py-6 border-b border-panel-border-interior-light">
  43. <FormItemLayout
  44. layout="horizontal"
  45. label={
  46. <div className="flex items-center gap-x-2">
  47. <span>{label}</span>
  48. {secureEntry && (
  49. <Tooltip>
  50. <TooltipTrigger asChild>
  51. <Link
  52. target="_blank"
  53. rel="noreferrer noopener"
  54. href={`/project/${ref}/integrations/vault/secrets?search=${value}`}
  55. >
  56. <ExternalLink
  57. size={14}
  58. className="text-foreground-lighter hover:text-foreground transition"
  59. />
  60. </Link>
  61. </TooltipTrigger>
  62. <TooltipContent side="bottom">Open in Vault</TooltipContent>
  63. </Tooltip>
  64. )}
  65. </div>
  66. }
  67. description={descriptionText}
  68. isReactForm={false}
  69. >
  70. <Input
  71. readOnly
  72. // If the value is secure, allow copying to clipboard if the value is revealed. Otherwise, always allow copying
  73. copy={!secureEntry || (!isDecryptedValueLoading && showHidden)}
  74. value={renderedValue}
  75. type={secureEntry ? (isLoading ? 'text' : showHidden ? 'text' : 'password') : 'text'}
  76. actions={
  77. secureEntry ? (
  78. isLoading ? (
  79. <div className="flex items-center justify-center">
  80. <Button
  81. disabled
  82. type="default"
  83. className="w-7"
  84. icon={<Loader className="animate-spin" />}
  85. />
  86. </div>
  87. ) : (
  88. <div className="flex items-center justify-center">
  89. <Button
  90. type="default"
  91. className="w-7"
  92. loading={showHidden && isDecryptedValueLoading}
  93. icon={showHidden ? <Eye /> : <EyeOff />}
  94. onClick={() => setShowHidden(!showHidden)}
  95. />
  96. </div>
  97. )
  98. ) : null
  99. }
  100. />
  101. </FormItemLayout>
  102. </CardContent>
  103. )
  104. }