import { useEffect, useRef, useState } from 'react'; import { AnchorButton, Callout, Intent, ProgressBar, Spinner, Tab, Tabs, Tag } from '@blueprintjs/core'; import { subscribeToJobStream } from '../../api/provisioningApi'; import { tenantUrl } from '../../config'; import type { ProvisioningProgressEvent } from '../../types/provisioning'; const SAGA_STEPS = [ 'Infrastructure Provisioning', 'Identity Bootstrapping (Keycloak)', 'Cryptographic Pre-Flight (Vault)', 'Database Migration & Seeding (EF Core)', 'Handoff (Email Magic Link)', ]; type StepStatus = 'pending' | 'running' | 'complete' | 'failed'; interface Props { jobId: string; subdomain: string; } export default function DeploymentLiveStep({ jobId, subdomain }: Props) { const [stepStatuses, setStepStatuses] = useState>( Object.fromEntries(SAGA_STEPS.map((s) => [s, 'pending' as StepStatus])) ); const [logs, setLogs] = useState([]); const [diagnostics, setDiagnostics] = useState([]); const [finalStatus, setFinalStatus] = useState<'running' | 'complete' | 'failed'>('running'); const logEndRef = useRef(null); const diagEndRef = useRef(null); useEffect(() => { let terminal = false; const source = subscribeToJobStream(jobId, (evt) => { if (evt.type === 'diagnostic') { setDiagnostics((prev) => [...prev, evt]); return; } setLogs((prev) => [...prev, evt]); if (evt.type === 'step_started' && evt.step) setStepStatuses((p) => ({ ...p, [evt.step!]: 'running' })); else if (evt.type === 'step_complete' && evt.step) setStepStatuses((p) => ({ ...p, [evt.step!]: 'complete' })); else if (evt.type === 'step_failed' && evt.step) { setStepStatuses((p) => ({ ...p, [evt.step!]: 'failed' })); setFinalStatus('failed'); } else if (evt.type === 'job_complete') { terminal = true; setFinalStatus('complete'); } else if (evt.type === 'job_failed') { terminal = true; setFinalStatus('failed'); } }, () => { if (!terminal) setFinalStatus('failed'); }); return () => source.close(); }, [jobId]); useEffect(() => { logEndRef.current?.scrollIntoView({ behavior: 'smooth' }); }, [logs]); useEffect(() => { diagEndRef.current?.scrollIntoView({ behavior: 'smooth' }); }, [diagnostics]); const completedCount = Object.values(stepStatuses).filter((s) => s === 'complete').length; const clientUrl = tenantUrl(subdomain); const progressPanel = ( <>
{SAGA_STEPS.map((step) => { const status = stepStatuses[step]; return (
{status === 'running' && } {status === 'complete' && } {status === 'failed' && } {status === 'pending' && } {step}
); })}
{logs.map((log, i) => (
{new Date(log.timestamp).toLocaleTimeString()} {log.message ?? log.type}
))}
); const diagnosticsPanel = (
{diagnostics.length === 0 ? No diagnostics captured. : diagnostics.map((d, i) => (
{new Date(d.timestamp).toLocaleTimeString()} {d.step}
{d.detail ?? d.message}
)) }
); return (

{finalStatus === 'running' ? 'Provisioning in progress - do not close this window.' : finalStatus === 'complete' ? 'Deployment complete.' : 'Deployment failed. Rollback triggered.'}

Diagnostics {diagnostics.length > 0 && ( {diagnostics.length} )} } panel={diagnosticsPanel} /> {finalStatus === 'complete' && (

Tenant {subdomain} is live. The day-zero admin has been set up in Keycloak.

)} {finalStatus === 'failed' && ( A compensating rollback has been triggered. Check the Diagnostics tab for the full stack trace. )}
); }