From 28a453d87f327c18fa31206af02b71bebfe800eb Mon Sep 17 00:00:00 2001 From: Philipp Date: Thu, 4 Jun 2026 14:49:36 +0200 Subject: [PATCH] added ETA for Backupjob --- frontend/src/components/JobStatusPanel.jsx | 63 ++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/frontend/src/components/JobStatusPanel.jsx b/frontend/src/components/JobStatusPanel.jsx index 1cba0e1..dcc3235 100644 --- a/frontend/src/components/JobStatusPanel.jsx +++ b/frontend/src/components/JobStatusPanel.jsx @@ -1,5 +1,6 @@ export function JobStatusPanel({ job }) { const percent = Math.round(job?.progress?.percent || 0); + const transfer = transferStats(job); return (
@@ -14,6 +15,9 @@ export function JobStatusPanel({ job }) { + + + {job?.error ? : null} @@ -27,6 +31,13 @@ export function JobStatusPanel({ job }) {
+ {job ? ( +
+ + + +
+ ) : null}
             {(job?.logs || ['No logs yet.']).join('\n')}
@@ -37,6 +48,15 @@ export function JobStatusPanel({ job }) {
   );
 }
 
+function Metric({ label, value }) {
+  return (
+    
+
{label}
+
{value}
+
+ ); +} + function Info({ label, value, tone = 'text-zinc-300' }) { return (
@@ -46,6 +66,49 @@ function Info({ label, value, tone = 'text-zinc-300' }) { ); } +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';