1+ import { PasswordInput } from '@inkjs/ui' ;
12import { Box , Text , useInput } from 'ink' ;
23import { useAtom } from 'jotai' ;
34import { EventEmitter } from 'node:events' ;
4- import React , { useState } from 'react' ;
5+ import React , { useLayoutEffect , useState } from 'react' ;
56
67import { ProcessName } from '../../../events/context.js' ;
78import { RenderEvent } from '../../reporters/reporter.js' ;
@@ -28,15 +29,59 @@ export function ProgressDisplay(props: { emitter: EventEmitter }) {
2829 const { emitter } = props ;
2930 const [ progress ] = useAtom ( store . progressState ) ;
3031 const [ isVerbose , setIsVerbose ] = useState ( false ) ;
32+ const [ isEnteringPassword , setIsEnteringPassword ] = useState ( false ) ;
33+ const [ passwordError , setPasswordError ] = useState ( false ) ;
34+ const [ passwordAttempts , setPasswordAttempts ] = useState ( 0 ) ;
35+ const [ passwordSaved , setPasswordSaved ] = useState ( false ) ;
36+ const [ passwordInputKey , setPasswordInputKey ] = useState ( 0 ) ;
3137
3238 const isApplyOrDestroy = progress ?. name === ProcessName . APPLY || progress ?. name === ProcessName . DESTROY ;
3339
40+ useLayoutEffect ( ( ) => {
41+ const onResult = ( { success } : { success : boolean } ) => {
42+ if ( success ) {
43+ setPasswordSaved ( true ) ;
44+ setIsEnteringPassword ( false ) ;
45+ setPasswordError ( false ) ;
46+ setPasswordAttempts ( 0 ) ;
47+ } else {
48+ setPasswordAttempts ( ( prev ) => {
49+ const next = prev + 1 ;
50+ if ( next >= 3 ) {
51+ setIsEnteringPassword ( false ) ;
52+ setPasswordError ( false ) ;
53+ return 0 ;
54+ }
55+ setPasswordError ( true ) ;
56+ setPasswordInputKey ( ( k ) => k + 1 ) ;
57+ return next ;
58+ } ) ;
59+ }
60+ } ;
61+
62+ const onPreSupplied = ( ) => setPasswordSaved ( true ) ;
63+
64+ emitter . on ( RenderEvent . SUDO_PASSWORD_RESULT , onResult ) ;
65+ emitter . on ( RenderEvent . SUDO_PASSWORD_PRE_SUPPLIED , onPreSupplied ) ;
66+
67+ return ( ) => {
68+ emitter . off ( RenderEvent . SUDO_PASSWORD_RESULT , onResult ) ;
69+ emitter . off ( RenderEvent . SUDO_PASSWORD_PRE_SUPPLIED , onPreSupplied ) ;
70+ } ;
71+ } , [ ] ) ;
72+
3473 useInput ( ( input ) => {
3574 if ( ! isApplyOrDestroy ) return ;
3675 if ( input === 'v' ) {
3776 setIsVerbose ( ( prev ) => ! prev ) ;
3877 emitter . emit ( RenderEvent . TOGGLE_VERBOSITY ) ;
3978 }
79+ if ( input === 'p' && ! passwordSaved ) {
80+ setIsEnteringPassword ( ( prev ) => ! prev ) ;
81+ setPasswordError ( false ) ;
82+ setPasswordAttempts ( 0 ) ;
83+ setPasswordInputKey ( ( k ) => k + 1 ) ;
84+ }
4085 } ) ;
4186
4287 if ( ! progress ) {
@@ -51,11 +96,35 @@ export function ProgressDisplay(props: { emitter: EventEmitter }) {
5196 ? < Spinner label = { label } type = "dots" />
5297 : < Text > < Text color = 'greenBright' > ✔</ Text > { label } </ Text >
5398 }
54- < Box flexDirection = "column" marginLeft = { 2 } >
55- < SubProgressDisplay subProgresses = { subProgresses } />
56- </ Box >
99+
100+ { ! isEnteringPassword && (
101+ < Box flexDirection = "column" marginLeft = { 2 } >
102+ < SubProgressDisplay subProgresses = { subProgresses } />
103+ </ Box >
104+ ) }
105+
106+ { isEnteringPassword && (
107+ < Box flexDirection = "column" marginTop = { 1 } >
108+ < Text color = { passwordError ? 'red' : 'cyan' } > { '─' . repeat ( 40 ) } </ Text >
109+ < Box >
110+ < Text > Password: </ Text >
111+ < PasswordInput key = { passwordInputKey } onSubmit = { ( pw ) => emitter . emit ( RenderEvent . SUDO_PASSWORD_SUBMITTED , pw ) } />
112+ </ Box >
113+ { passwordError && (
114+ < Text color = "red" > { ` Incorrect password, try again (${ passwordAttempts } /3)` } </ Text >
115+ ) }
116+ < Text color = { passwordError ? 'red' : 'cyan' } > { '─' . repeat ( 40 ) } </ Text >
117+ </ Box >
118+ ) }
119+
57120 { isApplyOrDestroy && (
58- < Text dimColor > { isVerbose ? '[v] Hide verbose logs' : '[v] Show verbose logs' } </ Text >
121+ < Box flexDirection = "column" >
122+ < Text dimColor > { isVerbose ? '[v] Hide verbose logs' : '[v] Show verbose logs' } </ Text >
123+ { passwordSaved
124+ ? < Text color = "green" > ✓ sudo password</ Text >
125+ : < Text dimColor > { isEnteringPassword ? '[p] Cancel' : '[p] Enter sudo password' } </ Text >
126+ }
127+ </ Box >
59128 ) }
60129 </ Box >
61130}
0 commit comments