/** * DeployCertDialog -- Dialog for signing and deploying a certificate to a single device. * * Flow: select device -> sign cert -> deploy to device -> done. * Shows progress states: Signing... -> Deploying... -> Done. */ import { useState } from 'react' import { useQuery, useQueryClient } from '@tanstack/react-query' import { ShieldCheck, Loader2, CheckCircle, XCircle, } from 'lucide-react' import { certificatesApi } from '@/lib/certificatesApi' import { devicesApi, type DeviceResponse } from '@/lib/api' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' import { Dialog, DialogContent, DialogHeader, DialogTitle, } from '@/components/ui/dialog' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select' import { toast } from '@/components/ui/toast' type DeployStep = 'idle' | 'signing' | 'deploying' | 'done' | 'error' interface DeployCertDialogProps { open: boolean onClose: () => void tenantId: string } export function DeployCertDialog({ open, onClose, tenantId, }: DeployCertDialogProps) { const queryClient = useQueryClient() const [selectedDevice, setSelectedDevice] = useState('') const [validityDays, setValidityDays] = useState('730') const [step, setStep] = useState('idle') const [errorMsg, setErrorMsg] = useState('') // Fetch devices for the selector const { data: deviceList = [] } = useQuery({ queryKey: ['devices-for-cert', tenantId], queryFn: async () => { const result = await devicesApi.list(tenantId) // The list endpoint returns { items, total, ... } or an array return (result as { items?: DeviceResponse[] }).items ?? (result as DeviceResponse[]) }, enabled: !!tenantId && open, }) // Fetch existing device certs to filter out devices that already have deployed certs const { data: existingCerts = [] } = useQuery({ queryKey: ['deviceCerts', tenantId], queryFn: () => certificatesApi.getDeviceCerts(undefined, tenantId), enabled: !!tenantId && open, }) const deployedDeviceIds = new Set( existingCerts .filter((c) => c.status === 'deployed' || c.status === 'deploying') .map((c) => c.device_id), ) const availableDevices = (deviceList as DeviceResponse[]).filter( (d) => !deployedDeviceIds.has(d.id), ) const handleDeploy = async () => { if (!selectedDevice) return try { // Step 1: Sign setStep('signing') const cert = await certificatesApi.signCert( selectedDevice, Number(validityDays) || 730, tenantId, ) // Step 2: Deploy setStep('deploying') const result = await certificatesApi.deployCert(cert.id, tenantId) if (result.success) { setStep('done') void queryClient.invalidateQueries({ queryKey: ['deviceCerts'] }) toast({ title: 'Certificate signed and deployed' }) // Auto-close after a brief delay setTimeout(() => { onClose() resetState() }, 1500) } else { setStep('error') setErrorMsg(result.error ?? 'Deployment failed') toast({ title: result.error ?? 'Deployment failed', variant: 'destructive' }) } } catch (e: unknown) { setStep('error') const err = e as { response?: { data?: { detail?: string } } } const detail = err?.response?.data?.detail || 'Failed to deploy certificate' setErrorMsg(detail) toast({ title: detail, variant: 'destructive' }) } } const resetState = () => { setSelectedDevice('') setValidityDays('730') setStep('idle') setErrorMsg('') } const handleClose = () => { onClose() resetState() } return ( !v && handleClose()}> Sign & Deploy Certificate
{step === 'idle' && ( <>

Select a device to sign a TLS certificate and deploy it automatically.

{availableDevices.length === 0 ? (

All devices have certificates

Every device already has a deployed certificate. Use rotate to renew.

) : ( <>
setValidityDays(e.target.value)} />

Default: 730 days (2 years)

)} )} {step === 'signing' && (

Creating secure certificate for this device...

Generating device certificate with your CA

)} {step === 'deploying' && (

Deploying to device...

Uploading certificate via SFTP and configuring TLS

)} {step === 'done' && (

Certificate deployed successfully

)} {step === 'error' && (

Deployment failed

{errorMsg}

)}
) }