Add structured job progress tracking
This commit is contained in:
@@ -25,6 +25,16 @@ import "./styles.css";
|
||||
|
||||
const API_BASE = import.meta.env.VITE_API_BASE ?? "http://localhost:8010";
|
||||
|
||||
type JobProgress = {
|
||||
percent: number | null;
|
||||
label: string;
|
||||
stage: string;
|
||||
current: number | null;
|
||||
total: number | null;
|
||||
unit: string | null;
|
||||
source: string;
|
||||
};
|
||||
|
||||
type Job = {
|
||||
id: string;
|
||||
type: string;
|
||||
@@ -35,6 +45,7 @@ type Job = {
|
||||
finished_at?: string;
|
||||
log_tail?: string;
|
||||
params: Record<string, unknown>;
|
||||
progress?: JobProgress;
|
||||
};
|
||||
|
||||
type Catalog = {
|
||||
@@ -273,6 +284,21 @@ function StatusPill({ status }: { status: string }) {
|
||||
return <span className={`pill pill-${status}`}>{status}</span>;
|
||||
}
|
||||
|
||||
function JobProgressBar({ progress }: { progress?: JobProgress }) {
|
||||
const percent = typeof progress?.percent === "number" ? Math.max(0, Math.min(100, progress.percent)) : 0;
|
||||
return (
|
||||
<div className="progressBox" data-stage={progress?.stage ?? "unknown"}>
|
||||
<div className="progressTrack" aria-label={progress?.label ?? "job progress"}>
|
||||
<span style={{ width: `${percent}%` }} />
|
||||
</div>
|
||||
<div className="progressMeta">
|
||||
<span>{progress?.label ?? "等待日志"}</span>
|
||||
<strong>{typeof progress?.percent === "number" ? `${progress.percent.toFixed(progress.percent % 1 ? 1 : 0)}%` : "..."}</strong>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function App() {
|
||||
const { catalog, gpus, jobs, results, curves, datasets, datasetValidations, coverage, acceptance, deepAcceptance, error, refresh } = useData();
|
||||
const [taskType, setTaskType] = useState("mock.echo");
|
||||
@@ -553,9 +579,12 @@ function App() {
|
||||
<div className="jobList">
|
||||
{jobs.slice(0, 12).map((job) => (
|
||||
<button key={job.id} className="jobRow" onClick={() => inspectJob(job)}>
|
||||
<span>{job.type}</span>
|
||||
<StatusPill status={job.status} />
|
||||
<div className="jobRowTop">
|
||||
<span>{job.type}</span>
|
||||
<StatusPill status={job.status} />
|
||||
</div>
|
||||
<small>{job.description || job.id.slice(0, 8)}</small>
|
||||
<JobProgressBar progress={job.progress} />
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
@@ -836,6 +865,7 @@ function App() {
|
||||
<Square size={18} />
|
||||
</button>
|
||||
</div>
|
||||
{selectedJob && <JobProgressBar progress={selectedJob.progress} />}
|
||||
<pre>{log || "No log selected."}</pre>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -631,8 +631,87 @@ textarea {
|
||||
color: var(--ink);
|
||||
}
|
||||
|
||||
.jobRow {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.jobRowTop {
|
||||
min-width: 0;
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.jobRowTop span:first-child {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.jobRow small {
|
||||
grid-column: 1 / -1;
|
||||
display: block;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.progressBox {
|
||||
display: grid;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.progressTrack {
|
||||
width: 100%;
|
||||
height: 7px;
|
||||
overflow: hidden;
|
||||
border-radius: 999px;
|
||||
background: #080a08;
|
||||
border: 1px solid rgba(238, 242, 232, 0.1);
|
||||
}
|
||||
|
||||
.progressTrack span {
|
||||
display: block;
|
||||
min-width: 0;
|
||||
height: 100%;
|
||||
border-radius: inherit;
|
||||
background: var(--cyan);
|
||||
transition: width 180ms ease;
|
||||
}
|
||||
|
||||
.progressBox[data-stage="completed"] .progressTrack span,
|
||||
.progressBox[data-stage="saving"] .progressTrack span {
|
||||
background: var(--green);
|
||||
}
|
||||
|
||||
.progressBox[data-stage="failed"] .progressTrack span {
|
||||
background: var(--red);
|
||||
}
|
||||
|
||||
.progressBox[data-stage="cancelled"] .progressTrack span {
|
||||
background: var(--amber);
|
||||
}
|
||||
|
||||
.progressMeta {
|
||||
min-width: 0;
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
color: var(--muted);
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.progressMeta span {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.progressMeta strong {
|
||||
color: var(--ink);
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.pill {
|
||||
@@ -695,7 +774,7 @@ meter {
|
||||
min-height: 340px;
|
||||
max-height: 520px;
|
||||
overflow: auto;
|
||||
margin: 0;
|
||||
margin: 12px 0 0;
|
||||
padding: 14px;
|
||||
color: #dbe7d8;
|
||||
background: #080a08;
|
||||
|
||||
Reference in New Issue
Block a user