57cbab916a
Clone repository (Files), Add workspace -> Clone and a workspace's extra-folder clones now start server-side jobs (background: true) and close at once; the app-wide BackgroundJobsDock (Layout) shows each job's queue place, git progress, Stop (click twice), and outcome: Open workspace, Retry, an inline login form on ECREDENTIALS, a Settings -> Git link on ESSHAUTH, dismiss. lib/cloneJobs.ts polls GET /workspaces/clone/jobs while any job is active, announces a finish it saw once (toast + agenthub:clone-job event + the starter's onDone), and treats an older server's synchronous answer as a finished clone. File Explorer uploads report to lib/uploadJobs.ts (Stop aborts the request in flight; a reload-cut upload shows as interrupted from localStorage). CloneProgress renders a job's fields instead of polling a token.
72 lines
2.4 KiB
TypeScript
72 lines
2.4 KiB
TypeScript
/**
|
|
* @file CloneProgress — the live line under a running clone: its place in the
|
|
* server's clone queue while it waits (clones run a few at a time machine-wide,
|
|
* `server/lib/clone-queue.js`), then git's own phase, percent, size and speed
|
|
* with a bar. It renders the progress fields of a background clone job
|
|
* (`lib/cloneJobs.ts`, polled by the store) — it fetches nothing itself.
|
|
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
|
*/
|
|
|
|
import { useTranslation } from "react-i18next";
|
|
import type { CloneJob } from "../../lib/api";
|
|
|
|
export type CloneProgressFields = Pick<
|
|
CloneJob,
|
|
"state" | "position" | "phase" | "label" | "percent" | "detail"
|
|
> & { running?: number; waiting?: number; limit?: number };
|
|
|
|
export function CloneProgress({ view }: { view: CloneProgressFields }) {
|
|
const { t } = useTranslation("lanes");
|
|
|
|
if (view.state !== "queued" && view.state !== "running") return null;
|
|
|
|
if (view.state === "queued") {
|
|
return (
|
|
<p data-testid="clone-progress" className="text-[11px] text-status-warning">
|
|
{view.waiting !== undefined && view.running !== undefined && view.limit !== undefined
|
|
? t("clone.queued", {
|
|
position: view.position ?? 1,
|
|
waiting: view.waiting,
|
|
running: view.running,
|
|
limit: view.limit,
|
|
})
|
|
: t("cloneJobs.queued", { position: view.position ?? 1 })}
|
|
</p>
|
|
);
|
|
}
|
|
|
|
const phase = view.phase
|
|
? view.phase === "other"
|
|
? view.label || ""
|
|
: t(`clone.phase.${view.phase}`)
|
|
: t("clone.connecting");
|
|
const percent = view.percent ?? 0;
|
|
return (
|
|
<div data-testid="clone-progress" className="space-y-1">
|
|
<p className="flex justify-between gap-2 text-[11px] text-fg-secondary">
|
|
<span className="truncate">
|
|
{phase}
|
|
{view.phase ? ` ${percent}%` : ""}
|
|
</span>
|
|
{view.detail && (
|
|
<span className="shrink-0 truncate font-mono text-[10px] text-fg-muted">
|
|
{view.detail}
|
|
</span>
|
|
)}
|
|
</p>
|
|
{view.phase && (
|
|
<div
|
|
className="h-1 overflow-hidden rounded bg-surface-2"
|
|
role="progressbar"
|
|
aria-valuenow={percent}
|
|
aria-valuemin={0}
|
|
aria-valuemax={100}
|
|
aria-label={phase}
|
|
>
|
|
<div className="h-full bg-accent-solid transition-all" style={{ width: `${percent}%` }} />
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|