import { useState, useEffect } from 'react' import { useTranslation } from 'react-i18next' import { Key, Shield, AlertTriangle, CheckCircle, Clock, RefreshCw, AlertCircle } from 'lucide-react' import { useEncryptionStatus, useRotateKey, useRotationHistory, useValidateKeys, type RotationHistoryEntry, } from '../hooks/useEncryption' import { toast } from '../utils/toast' import { PageShell } from '../components/layout/PageShell' import { Card, CardHeader, CardTitle, CardDescription, CardContent, Button, Badge, Alert, Progress, Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter, Skeleton, } from '../components/ui' // Skeleton loader for status cards function StatusCardSkeleton() { return (
) } // Loading skeleton for the page function EncryptionPageSkeleton({ t }: { t: (key: string) => string }) { return (
) } // Confirmation dialog for key rotation interface RotationConfirmDialogProps { isOpen: boolean onClose: () => void onConfirm: () => void isPending: boolean } function RotationConfirmDialog({ isOpen, onClose, onConfirm, isPending }: RotationConfirmDialogProps) { const { t } = useTranslation() return ( {t('encryption.confirmRotationTitle')} {t('encryption.confirmRotationMessage')}

{t('encryption.rotationWarning1')}

{t('encryption.rotationWarning2')}

) } export default function EncryptionManagement() { const { t } = useTranslation() const [showConfirmDialog, setShowConfirmDialog] = useState(false) const [isRotating, setIsRotating] = useState(false) // Fetch status with auto-refresh during rotation const { data: status, isLoading } = useEncryptionStatus(isRotating ? 5000 : undefined) const { data: history } = useRotationHistory() const rotateMutation = useRotateKey() const validateMutation = useValidateKeys() // Stop auto-refresh when rotation completes useEffect(() => { if (isRotating && rotateMutation.isSuccess) { setIsRotating(false) } }, [isRotating, rotateMutation.isSuccess]) const handleRotateClick = () => { setShowConfirmDialog(true) } const handleConfirmRotation = () => { setShowConfirmDialog(false) setIsRotating(true) rotateMutation.mutate(undefined, { onSuccess: (result) => { toast.success( t('encryption.rotationSuccess', { count: result.success_count, total: result.total_providers, duration: result.duration, }) ) if (result.failure_count > 0) { toast.warning( t('encryption.rotationPartialFailure', { count: result.failure_count }) ) } }, onError: (error: unknown) => { const msg = error instanceof Error ? error.message : String(error) toast.error(t('encryption.rotationError', { error: msg })) setIsRotating(false) }, }) } const handleValidateClick = () => { validateMutation.mutate(undefined, { onSuccess: (result) => { if (result.valid) { toast.success(t('encryption.validationSuccess')) if (result.warnings && result.warnings.length > 0) { result.warnings.forEach((warning) => toast.warning(warning)) } } else { toast.error(t('encryption.validationError')) if (result.errors && result.errors.length > 0) { result.errors.forEach((error) => toast.error(error)) } } }, onError: (error: unknown) => { const msg = error instanceof Error ? error.message : String(error) toast.error(t('encryption.validationFailed', { error: msg })) }, }) } if (isLoading) { return } if (!status) { return ( {t('encryption.failedToLoadStatus')} ) } const hasOlderVersions = status.providers_on_older_versions > 0 const rotationDisabled = isRotating || !status.next_key_configured return ( <> {/* Status Overview Cards */}
{/* Current Key Version */}
{t('encryption.currentVersion')}
{t('encryption.versionNumber', { version: status.current_version })}

{t('encryption.activeEncryptionKey')}

{/* Providers on Current Version */}
{t('encryption.providersUpdated')}
{status.providers_on_current_version}

{t('encryption.providersOnCurrentVersion')}

{/* Providers on Older Versions */}
{t('encryption.providersOutdated')}
{status.providers_on_older_versions}

{t('encryption.providersNeedRotation')}

{/* Next Key Configured */}
{t('encryption.nextKey')}
{status.next_key_configured ? t('encryption.configured') : t('encryption.notConfigured')}

{t('encryption.nextKeyDescription')}

{/* Legacy Keys Warning */} {status.legacy_key_count > 0 && (

{t('encryption.legacyKeysMessage', { count: status.legacy_key_count })}

)} {/* Actions Section */} {t('encryption.actions')} {t('encryption.actionsDescription')}
{!status.next_key_configured && (

{t('encryption.nextKeyRequired')}

)} {isRotating && (
{t('encryption.rotationInProgress')}
)}
{/* Environment Variable Guide */} {t('encryption.environmentGuide')} {t('encryption.environmentGuideDescription')}
# Current encryption key (required)
CHARON_ENCRYPTION_KEY=<base64-encoded-32-byte-key>
# During rotation: new key
CHARON_ENCRYPTION_KEY_V2=<new-base64-encoded-key>
# Legacy keys for decryption
CHARON_ENCRYPTION_KEY_V1=<old-key>
{t('encryption.step1')}:{' '} {t('encryption.step1Description')}
{t('encryption.step2')}:{' '} {t('encryption.step2Description')}
{t('encryption.step3')}:{' '} {t('encryption.step3Description')}
{t('encryption.step4')}:{' '} {t('encryption.step4Description')}

{t('encryption.retentionWarning')}

{/* Rotation History */} {history && history.length > 0 && ( {t('encryption.rotationHistory')} {t('encryption.rotationHistoryDescription')}
{history.slice(0, 10).map((entry: RotationHistoryEntry) => { const details = entry.details ? JSON.parse(entry.details) : {} return ( ) })}
{t('encryption.date')} {t('encryption.actor')} {t('encryption.action')} {t('encryption.details')}
{new Date(entry.created_at).toLocaleString()} {entry.actor} {entry.action} {details.new_key_version && ( {t('encryption.versionNumber', { version: details.new_key_version })} )} {details.duration && ({details.duration})}
)}
{/* Confirmation Dialog */} setShowConfirmDialog(false)} onConfirm={handleConfirmRotation} isPending={rotateMutation.isPending} /> ) }