+
Almacenamiento
+
+ {/* Capabilities */}
+
+
+ Capacidades detectadas
+
+
+
+
+
+
+
+ {caps && !caps.rsync && (
+
+ Instala rsync para usar el mover de archivos.
+
+ )}
+
+
+ {/* Config */}
+
+
+
+ Configuración del pool
+
+
+
+
+
+
+
+
+
+
+
+ {/* Mergerfs Mover */}
+
+
+ Mergerfs Mover
+
+
+
+
+ {activeJob && (activeJob.status === "running" || activeJob.status === "queued") && (
+
+ )}
+
+
+
+
+ {/* SnapRAID */}
+
+
+ SnapRAID
+
+
+
+
+
+
+
+
+ {activeJob && (activeJob.status === "running" || activeJob.status === "queued") && (
+
+ )}
+
+
+
+
+ {/* History */}
+
+
+ Historial de operaciones
+
+ {jobs.length === 0 ? (
+
Sin operaciones registradas.
+ ) : (
+
+
+
+
+ | Tipo |
+ Estado |
+ Exit |
+ Fecha |
+ Duración |
+
+
+
+ {jobs.slice(0, 20).map((j) => (
+ {
+ setActiveJob(j);
+ setOutputLines(j.output ? j.output.split("\n") : []);
+ if (j.status !== "running") {
+ if (esRef.current) {
+ esRef.current.close();
+ esRef.current = null;
+ }
+ }
+ }}
+ >
+ | {KIND_LABELS[j.kind] ?? j.kind} |
+
+
+ {j.status}
+
+ |
+ {j.exit_code >= 0 ? j.exit_code : "—"} |
+ {new Date(j.created_at).toLocaleString()} |
+
+ {j.started_at && j.finished_at
+ ? duration(new Date(j.started_at), new Date(j.finished_at))
+ : j.started_at
+ ? "en curso"
+ : "—"}
+ |
+
+ ))}
+
+
+
+ )}
+
+
+ );
+}
+
+function CapChip({ label, ok, hint }: { label: string; ok: boolean; hint: string }) {
+ return (
+ ;
+ filterKind: "mergerfs" | "snapraid";
+}) {
+ const filtered = job?.kind.startsWith(filterKind) ? lines : [];
+ if (!job || !job.kind.startsWith(filterKind)) {
+ return (
+
+ Sin salida aún. Ejecuta una operación para ver el resultado.
+
+ );
+ }
+ return (
+ <>
+
+
+ {job.status}
+
+ {job.kind}
+ {job.exit_code >= 0 && job.status !== "running" && (
+ exit={job.exit_code}
+ )}
+
+ }
+ className="max-h-80 overflow-auto rounded bg-slate-900 p-3 text-xs text-slate-300 font-mono"
+ >
+ {filtered.length === 0
+ ? job.output
+ ? job.output.split("\n").slice(-200).join("\n")
+ : "iniciando…"
+ : filtered.join("\n")}
+
+ >
+ );
+}
+
+function duration(start: Date, end: Date): string {
+ const ms = end.getTime() - start.getTime();
+ if (ms < 0) return "—";
+ const s = Math.floor(ms / 1000);
+ if (s < 60) return `${s}s`;
+ const m = Math.floor(s / 60);
+ if (m < 60) return `${m}m ${s % 60}s`;
+ const h = Math.floor(m / 60);
+ return `${h}h ${m % 60}m`;
+}