@@ -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 } 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,8 @@ 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 )
60+ const [ passwordGenerated , setPasswordGenerated ] = useState ( false )
5661
5762 // Form.
5863 const onSubmit = async ( values : FormValues ) => {
@@ -314,18 +319,65 @@ export const CreateClone = observer((props: Props) => {
314319 < TextField
315320 fullWidth
316321 label = "Database password *"
317- type = " password"
322+ type = { showPassword ? 'text' : ' password' }
318323 value = { formik . values . dbPassword }
319324 onChange = { ( e ) => {
320325 formik . setFieldValue ( 'dbPassword' , e . target . value )
326+ setPasswordGenerated ( false )
321327
322328 if ( formik . errors . dbPassword ) {
323329 formik . setFieldError ( 'dbPassword' , '' )
324330 }
325331 } }
326332 error = { Boolean ( formik . errors . dbPassword ) }
327333 disabled = { isCreatingClone }
334+ InputProps = { {
335+ endAdornment : (
336+ < InputAdornment position = "end" >
337+ < Tooltip content = { showPassword ? 'Hide password' : 'Show password' } >
338+ < IconButton
339+ size = "small"
340+ onClick = { ( ) => setShowPassword ( ! showPassword ) }
341+ disabled = { isCreatingClone }
342+ style = { { marginRight : 4 } }
343+ >
344+ { showPassword ? < Visibility fontSize = "small" /> : < VisibilityOff fontSize = "small" /> }
345+ </ IconButton >
346+ </ Tooltip >
347+ </ InputAdornment >
348+ ) ,
349+ } }
328350 />
351+ < div className = { styles . passwordActions } >
352+ < Button
353+ variant = "secondary"
354+ size = "small"
355+ onClick = { ( ) => copy ( formik . values . dbPassword ) }
356+ isDisabled = { isCreatingClone || ! formik . values . dbPassword }
357+ >
358+ Copy
359+ </ Button >
360+ < Button
361+ variant = "secondary"
362+ size = "small"
363+ onClick = { ( ) => {
364+ const newPassword = generatePassword ( 16 )
365+ formik . setFieldValue ( 'dbPassword' , newPassword )
366+ setPasswordGenerated ( true )
367+ if ( formik . errors . dbPassword ) {
368+ formik . setFieldError ( 'dbPassword' , '' )
369+ }
370+ } }
371+ isDisabled = { isCreatingClone }
372+ >
373+ Generate
374+ </ Button >
375+ { passwordGenerated && (
376+ < span className = { styles . passwordHint } >
377+ New password created. Copy and save it securely.
378+ </ span >
379+ ) }
380+ </ div >
329381 < p
330382 className = { cn (
331383 formik . errors . dbPassword && styles . error ,
@@ -336,7 +388,9 @@ export const CreateClone = observer((props: Props) => {
336388 </ p >
337389 </ div >
338390
339- < div className = { styles . form } >
391+ < div className = { styles . section } >
392+ < h2 className = { styles . title } > Clone protection</ h2 >
393+
340394 < FormControlLabel
341395 label = "Enable deletion protection"
342396 control = {
@@ -423,7 +477,7 @@ export const CreateClone = observer((props: Props) => {
423477 </ p >
424478 < SyntaxHighlight
425479 wrapLines
426- content = { getCliCreateCloneCommand ( formik . values ) }
480+ content = { getCliCreateCloneCommand ( formik . values , showPassword ) }
427481 />
428482
429483 < SectionTitle
0 commit comments