'use client'; import { useEffect, useState } from 'react'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Badge } from '@/components/ui/badge'; import { Loader2, CheckCircle2, AlertTriangle, X } from 'lucide-react'; import { Button } from '@/components/ui/button'; interface ExecutionRow { id: string; scriptId: string; jobName: string; targetHostname: string | null; status: 'queued' | 'running' | 'complete' | 'failed' | 'timeout'; exitCode: number | null; rawStdout: string | null; rawStderr: string | null; parsedEvidence: unknown; parseError: string | null; errorMessage: string | null; queuedAt: string; completedAt: string | null; } const POLL_MS = 3000; const POLL_TIMEOUT_MS = 6 * 60 * 1000; // 6 min โ€” slightly longer than the server-side hard cap. export function RmmExecutionStream({ executionId, onComplete, }: { executionId: string; onComplete?: () => void; }) { const [exec, setExec] = useState(null); const [error, setError] = useState(null); const [closed, setClosed] = useState(false); useEffect(() => { if (closed) return; let cancelled = false; const start = Date.now(); async function tick() { if (cancelled) return; try { const res = await fetch(`/api/rmm/executions/${executionId}`); if (!res.ok) { throw new Error(`HTTP ${res.status}`); } const data = (await res.json()) as { execution: ExecutionRow }; if (cancelled) return; setExec(data.execution); if ( data.execution.status === 'complete' || data.execution.status === 'failed' || data.execution.status === 'timeout' ) { onComplete?.(); return; } if (Date.now() - start > POLL_TIMEOUT_MS) { setError('Polling timed out โ€” check execution status manually.'); return; } setTimeout(tick, POLL_MS); } catch (err) { if (cancelled) return; setError(err instanceof Error ? err.message : 'Unknown error'); } } void tick(); return () => { cancelled = true; }; }, [executionId, closed, onComplete]); if (closed) return null; const status = exec?.status ?? 'queued'; const isDone = status === 'complete' || status === 'failed' || status === 'timeout'; return (
{!isDone ? ( ) : status === 'complete' ? ( ) : ( )} {exec?.jobName ?? 'Discovery script'} {status}

{exec?.targetHostname ? `target: ${exec.targetHostname} ยท ` : ''} execution {executionId}

{error &&

{error}

} {isDone && exec?.parseError && (

Output parser failed: {exec.parseError}

)} {isDone && exec?.errorMessage && (

{exec.errorMessage}

)} {isDone && exec?.parsedEvidence !== undefined && exec.parsedEvidence !== null && (

Parsed evidence

              {JSON.stringify(exec.parsedEvidence, null, 2)}
            
)} {isDone && exec?.rawStdout && (
Raw stdout ({exec.rawStdout.length} chars)
              {exec.rawStdout.slice(0, 50000)}
            
)} {isDone && exec?.rawStderr && (
Raw stderr
              {exec.rawStderr.slice(0, 20000)}
            
)} {!isDone && (

Polling every {POLL_MS / 1000}s โ€” Datto typically returns within ~30-90s for asset-self scripts and ~60-180s for site-anchored.

)}
); }