export function JobStatusPanel({ job }) { const percent = Math.round(job?.progress?.percent || 0); const transfer = transferStats(job); return (

Job Status

{job?.status || 'idle'}
{job?.error ? : null}
{job?.progress?.detail || job?.currentStep || 'No active job'} {job ? `${percent}%` : '-'}
{job ? (
) : null}
            {(job?.logs || ['No logs yet.']).join('\n')}
          
); } function Metric({ label, value }) { return (
{label}
{value}
); } function Info({ label, value, tone = 'text-zinc-300' }) { return (
{label}
{value}
); } function transferStats(job) { const currentBytes = Number(job?.progress?.currentBytes || 0); const totalBytes = Number(job?.progress?.totalBytes || 0); const startedAt = job?.startedAt ? new Date(job.startedAt).getTime() : 0; const finishedAt = job?.finishedAt ? new Date(job.finishedAt).getTime() : Date.now(); const elapsedSeconds = startedAt ? Math.max(1, (finishedAt - startedAt) / 1000) : 0; const bytesPerSecond = currentBytes && elapsedSeconds ? currentBytes / elapsedSeconds : 0; const remainingBytes = totalBytes && currentBytes ? Math.max(0, totalBytes - currentBytes) : 0; const remainingSeconds = bytesPerSecond && remainingBytes ? remainingBytes / bytesPerSecond : 0; return { transferred: currentBytes && totalBytes ? `${formatBytes(currentBytes)} / ${formatBytes(totalBytes)}` : currentBytes ? formatBytes(currentBytes) : '-', rate: bytesPerSecond ? `${formatBytes(bytesPerSecond)}/s` : '-', eta: remainingSeconds ? formatDuration(remainingSeconds) : job?.status === 'success' ? 'Done' : '-', }; } function formatBytes(value) { if (!value) return '-'; const units = ['B', 'KiB', 'MiB', 'GiB', 'TiB']; let size = Number(value); let unitIndex = 0; while (size >= 1024 && unitIndex < units.length - 1) { size /= 1024; unitIndex += 1; } return `${size.toFixed(unitIndex === 0 ? 0 : 1)} ${units[unitIndex]}`; } function formatDuration(secondsValue) { const totalSeconds = Math.max(0, Math.round(secondsValue)); const hours = Math.floor(totalSeconds / 3600); const minutes = Math.floor((totalSeconds % 3600) / 60); const seconds = totalSeconds % 60; if (hours) return `${hours}h ${minutes}m`; if (minutes) return `${minutes}m ${seconds}s`; return `${seconds}s`; } function statusClass(status) { if (status === 'success') return 'border-emerald-800 bg-emerald-950 text-emerald-300'; if (status === 'failed') return 'border-red-800 bg-red-950 text-red-300'; if (status === 'running' || status === 'queued') return 'border-cyan-800 bg-cyan-950 text-cyan-300'; return 'border-zinc-800 bg-zinc-950 text-zinc-400'; } function formatTime(value) { if (!value) return '-'; return new Intl.DateTimeFormat(undefined, { dateStyle: 'medium', timeStyle: 'medium' }).format(new Date(value)); }