@@ -3,15 +3,17 @@ import React, { useEffect, useState } from 'react'
33import { useHistory } from 'react-router-dom'
44import { observer } from 'mobx-react-lite'
55import { useTimer } from 'use-timer'
6- import { Paper , FormControlLabel , Checkbox } from '@material-ui/core'
7- import { Info as InfoIcon } from '@material-ui/icons'
6+ import { Paper , FormControlLabel , Checkbox , IconButton , InputAdornment } from '@material-ui/core'
7+ import { Info as InfoIcon , Visibility , VisibilityOff , FileCopy , Autorenew } from '@material-ui/icons'
8+ import copy from 'copy-to-clipboard'
89
910import { StubSpinner } from '@postgres.ai/shared/components/StubSpinnerFlex'
1011import { TextField } from '@postgres.ai/shared/components/TextField'
1112import { Select } from '@postgres.ai/shared/components/Select'
1213import { Button } from '@postgres.ai/shared/components/Button'
1314import { Spinner } from '@postgres.ai/shared/components/Spinner'
1415import { ErrorStub } from '@postgres.ai/shared/components/ErrorStub'
16+ import { Tooltip } from '@postgres.ai/shared/components/Tooltip'
1517import { round } from '@postgres.ai/shared/utils/numbers'
1618import { formatBytesIEC } from '@postgres.ai/shared/utils/units'
1719import { SectionTitle } from '@postgres.ai/shared/components/SectionTitle'
@@ -20,6 +22,7 @@ import {
2022 MIN_ENTROPY ,
2123 getEntropy ,
2224 validatePassword ,
25+ generatePassword ,
2326} from '@postgres.ai/shared/helpers/getEntropy'
2427
2528import { Snapshot } from '@postgres.ai/shared/types/api/entities/snapshot'
@@ -53,6 +56,7 @@ export const CreateClone = observer((props: Props) => {
5356 const [ snapshots , setSnapshots ] = useState ( [ ] as Snapshot [ ] )
5457 const [ isLoadingSnapshots , setIsLoadingSnapshots ] = useState ( false )
5558 const [ selectedBranchKey , setSelectedBranchKey ] = useState < string > ( '' )
59+ const [ showPassword , setShowPassword ] = useState ( false )
5660
5761 // Form.
5862 const onSubmit = async ( values : FormValues ) => {
@@ -314,7 +318,7 @@ export const CreateClone = observer((props: Props) => {
314318 < TextField
315319 fullWidth
316320 label = "Database password *"
317- type = " password"
321+ type = { showPassword ? 'text' : ' password' }
318322 value = { formik . values . dbPassword }
319323 onChange = { ( e ) => {
320324 formik . setFieldValue ( 'dbPassword' , e . target . value )
@@ -325,6 +329,46 @@ export const CreateClone = observer((props: Props) => {
325329 } }
326330 error = { Boolean ( formik . errors . dbPassword ) }
327331 disabled = { isCreatingClone }
332+ InputProps = { {
333+ endAdornment : (
334+ < InputAdornment position = "end" >
335+ < Tooltip content = "Toggle visibility" >
336+ < IconButton
337+ size = "small"
338+ onClick = { ( ) => setShowPassword ( ! showPassword ) }
339+ disabled = { isCreatingClone }
340+ >
341+ { showPassword ? < VisibilityOff fontSize = "small" /> : < Visibility fontSize = "small" /> }
342+ </ IconButton >
343+ </ Tooltip >
344+ < Tooltip content = "Copy password" >
345+ < IconButton
346+ size = "small"
347+ onClick = { ( ) => copy ( formik . values . dbPassword ) }
348+ disabled = { isCreatingClone || ! formik . values . dbPassword }
349+ >
350+ < FileCopy fontSize = "small" />
351+ </ IconButton >
352+ </ Tooltip >
353+ < Tooltip content = "Generate password" >
354+ < IconButton
355+ size = "small"
356+ onClick = { ( ) => {
357+ const newPassword = generatePassword ( 16 )
358+ formik . setFieldValue ( 'dbPassword' , newPassword )
359+ setShowPassword ( true )
360+ if ( formik . errors . dbPassword ) {
361+ formik . setFieldError ( 'dbPassword' , '' )
362+ }
363+ } }
364+ disabled = { isCreatingClone }
365+ >
366+ < Autorenew fontSize = "small" />
367+ </ IconButton >
368+ </ Tooltip >
369+ </ InputAdornment >
370+ ) ,
371+ } }
328372 />
329373 < p
330374 className = { cn (
0 commit comments