Add SSH-managed node registry with connection testing and reauth.
Register hosts under Containers/VMs/Docker with encrypted key storage, and require re-authentication for sensitive account changes.
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
import { useState, type FormEvent } from 'react'
|
||||
import type { ReauthCredentials } from './api/client'
|
||||
|
||||
type ReauthModalProps = {
|
||||
title: string
|
||||
hint?: string
|
||||
requireTotp: boolean
|
||||
confirmLabel?: string
|
||||
onCancel: () => void
|
||||
onConfirm: (credentials: ReauthCredentials) => Promise<void>
|
||||
}
|
||||
|
||||
export function ReauthModal({
|
||||
title,
|
||||
hint,
|
||||
requireTotp,
|
||||
confirmLabel = 'Confirm',
|
||||
onCancel,
|
||||
onConfirm,
|
||||
}: ReauthModalProps) {
|
||||
const [password, setPassword] = useState('')
|
||||
const [totpCode, setTotpCode] = useState('')
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [isSubmitting, setIsSubmitting] = useState(false)
|
||||
|
||||
async function handleSubmit(event: FormEvent) {
|
||||
event.preventDefault()
|
||||
setError(null)
|
||||
setIsSubmitting(true)
|
||||
try {
|
||||
await onConfirm({
|
||||
current_password: password,
|
||||
totp_code: totpCode,
|
||||
})
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Re-authentication failed')
|
||||
setIsSubmitting(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="reauth-backdrop" role="presentation">
|
||||
<div
|
||||
className="reauth-modal"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="reauth-title"
|
||||
>
|
||||
<h2 id="reauth-title">{title}</h2>
|
||||
{hint ? <p className="config-hint">{hint}</p> : null}
|
||||
<form className="reauth-form" onSubmit={(event) => void handleSubmit(event)}>
|
||||
<label htmlFor="reauth-password">Password</label>
|
||||
<input
|
||||
id="reauth-password"
|
||||
type="password"
|
||||
autoComplete="current-password"
|
||||
value={password}
|
||||
onChange={(event) => setPassword(event.target.value)}
|
||||
required
|
||||
/>
|
||||
{requireTotp ? (
|
||||
<>
|
||||
<label htmlFor="reauth-totp">Authenticator code</label>
|
||||
<input
|
||||
id="reauth-totp"
|
||||
type="text"
|
||||
inputMode="numeric"
|
||||
autoComplete="one-time-code"
|
||||
value={totpCode}
|
||||
onChange={(event) => setTotpCode(event.target.value)}
|
||||
required
|
||||
/>
|
||||
</>
|
||||
) : null}
|
||||
{error ? (
|
||||
<p className="groups-error" role="alert">
|
||||
{error}
|
||||
</p>
|
||||
) : null}
|
||||
<div className="reauth-actions">
|
||||
<button type="button" className="config-action-revert" onClick={onCancel}>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
className="config-action-save"
|
||||
disabled={isSubmitting || !password}
|
||||
>
|
||||
{isSubmitting ? 'Checking…' : confirmLabel}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user