From 0100fb843057b935b402d2e57ea1aab4e8cdec7d Mon Sep 17 00:00:00 2001 From: oliver Date: Wed, 5 Aug 2026 11:34:22 +0800 Subject: [PATCH] Show collect job run details in the shared ops modal. Replace the inline expand row with the same detail dialog used by LLDP and config sync. Co-authored-by: Cursor --- web/src/i18n/en.ts | 1 + web/src/i18n/zh.ts | 1 + web/src/index.css | 6 + web/src/pages/CollectPage.tsx | 352 +++++++++++++++++++--------------- 4 files changed, 202 insertions(+), 158 deletions(-) diff --git a/web/src/i18n/en.ts b/web/src/i18n/en.ts index 321fc27..d5a4d44 100644 --- a/web/src/i18n/en.ts +++ b/web/src/i18n/en.ts @@ -648,6 +648,7 @@ const en = { fail: "fail", expand: "Details", collapse: "Hide", + detailTitle: "Job details", pause: "Pause", start: "Start", starting: "Starting…", diff --git a/web/src/i18n/zh.ts b/web/src/i18n/zh.ts index 473f40d..a6accf5 100644 --- a/web/src/i18n/zh.ts +++ b/web/src/i18n/zh.ts @@ -644,6 +644,7 @@ const zh = { fail: "失败", expand: "详情", collapse: "收起", + detailTitle: "任务明细", pause: "暂停", start: "开始", starting: "启动中…", diff --git a/web/src/index.css b/web/src/index.css index 917bd3a..57bcabe 100644 --- a/web/src/index.css +++ b/web/src/index.css @@ -2519,6 +2519,12 @@ pre { font-family: ui-monospace, Consolas, monospace; } +.collect-cmd-preview--modal { + flex: 0 0 auto; + margin: 0 0 10px; + max-height: 88px; +} + .collect-runs-panel { display: flex; flex-direction: column; diff --git a/web/src/pages/CollectPage.tsx b/web/src/pages/CollectPage.tsx index 8e711d1..ecc9f88 100644 --- a/web/src/pages/CollectPage.tsx +++ b/web/src/pages/CollectPage.tsx @@ -16,7 +16,7 @@ import { import { queryKeys } from "../constants/queryKeys"; import { useI18n } from "../i18n"; import { useToast } from "../hooks/useToast"; -import type { CollectionJobDetail, CollectionJobItem, EligibleNeItem } from "../types"; +import type { CollectionJobItem, EligibleNeItem } from "../types"; import { pageCount } from "../utils/display"; import { formatSystemTime } from "../utils/time"; @@ -498,9 +498,7 @@ export function CollectPage() { setExpandedJobId(expandedJobId === job.id ? "" : job.id)} + onOpenDetail={() => setExpandedJobId(job.id)} onPause={() => pauseMutation.mutate(job.id)} onStart={() => startJobMutation.mutate(job.id)} onRetryFailed={() => retryFailedMutation.mutate(job.id)} @@ -534,15 +532,65 @@ export function CollectPage() { + + {expandedJobId ? ( +
setExpandedJobId("")} + > +
e.stopPropagation()} + > +
+
+

{t("collect.jobs.detailTitle")}

+

+ {(() => { + const job = jobs.find((j) => j.id === expandedJobId) || detailQuery.data?.job; + return job + ? `${job.title} · ${job.status} · ${job.success_count}/${job.ne_count}` + : expandedJobId.slice(0, 8); + })()} +

+
+
+ +
+
+ {detailQuery.isLoading ?

{t("common.refreshing")}

: null} + j.id === expandedJobId)?.status || + detailQuery.data?.job.status || + "" + } + failCount={ + jobs.find((j) => j.id === expandedJobId)?.fail_count ?? + detailQuery.data?.job.fail_count ?? + 0 + } + commands={detailQuery.data?.job.commands ?? ""} + onRetryFailed={() => retryFailedMutation.mutate(expandedJobId)} + retryPending={actionPending} + /> +
+
+ ) : null} ); } function JobRow({ job, - expanded, - detail, - onToggle, + onOpenDetail, onPause, onStart, onRetryFailed, @@ -551,9 +599,7 @@ function JobRow({ startPending, }: { job: CollectionJobItem; - expanded: boolean; - detail?: CollectionJobDetail; - onToggle: () => void; + onOpenDetail: () => void; onPause: () => void; onStart: () => void; onRetryFailed: () => void; @@ -572,64 +618,48 @@ function JobRow({ window.location.assign(collectionJobDownloadUrl(job.id)); }; return ( - <> - - {job.title} - {job.status} - - {job.success_count}/{job.ne_count} {t("collect.jobs.ok")}, {job.fail_count} {t("collect.jobs.fail")} - - {formatSystemTime(job.created_at)} - {job.last_run_at ? formatSystemTime(job.last_run_at) : t("common.empty")} - - + {canPause ? ( + - {canPause ? ( - - ) : null} - {canStart ? ( - - ) : null} - {canRetryFailed ? ( - - ) : null} - - {canDelete ? ( - - ) : null} - - - {expanded ? ( - - - - - - ) : null} - + ) : null} + {canRetryFailed ? ( + + ) : null} + + {canDelete ? ( + + ) : null} + + ); } @@ -681,111 +711,117 @@ function JobRunsPanel({ const runs = runsQuery.data?.items ?? []; return ( -
+ <> {jobStatus === "running" ? (

{t("collect.jobs.runsInProgress")}

) : null} - {commands ?
{commands}
: null} -
- - + {commands ?
{commands}
: null} +
+ { + setRunKeyword(e.target.value); + setRunPage(1); + }} + /> + {runStatus || runKeyword ? ( - ) : null} {jobStatus !== "running" && failCount > 0 ? ( - ) : null}
- {runsQuery.isLoading ?

{t("common.refreshing")}

: null} - {!runsQuery.isLoading && runs.length === 0 ?

{t("common.empty")}

: null} - {runs.length > 0 ? ( - - - - - - - - - - - - - {runs.map((run) => ( - - - - - - - + {runsQuery.isLoading ?

{t("common.refreshing")}

: null} +
+
+
{t("managedNe.col.source")}{t("managedNe.col.name")}{t("managedNe.col.ip")}{t("collect.runs.status")}{t("collect.runs.message")}{t("collect.runs.download")}
- {run.ne_source || "managed"} - {run.ne_name}{run.ne_ip}{run.status} - {run.message ? ( -
- {run.message} -
- ) : ( - t("common.empty") - )} -
- {run.has_output ? ( - - {t("collect.runs.downloadFile")} - - ) : ( - t("common.empty") - )} -
+ + + + + + + + - ))} - -
{t("managedNe.col.source")}{t("managedNe.col.name")}{t("managedNe.col.ip")}{t("collect.runs.status")}{t("collect.runs.message")}{t("collect.runs.download")}
- ) : null} - {runTotal > 0 ? ( -
-
{t("common.pagerMeta", { total: runTotal, page: runPage, pages: runPages })}
-
- - -
+ + + {runs.map((run) => ( + + + {run.ne_source || "managed"} + + {run.ne_name} + {run.ne_ip} + {run.status} + + {run.message ? ( +
+ {run.message} +
+ ) : ( + t("common.empty") + )} + + + {run.has_output ? ( + + {t("collect.runs.downloadFile")} + + ) : ( + t("common.empty") + )} + + + ))} + {!runsQuery.isLoading && runs.length === 0 ? ( + + + {t("common.empty")} + + + ) : null} + +
- ) : null} -
+
+
+ + {t("common.pagerMeta", { + total: String(runTotal), + page: String(runPage), + pages: String(runPages), + })} + +
+ + +
+
+ ); }