From 28aa667c77ddcdc0969cee7501143c7fae9194f8 Mon Sep 17 00:00:00 2001 From: viettranx Date: Wed, 1 Apr 2026 23:24:48 +0700 Subject: [PATCH] refactor(cron): redesigned run history with cleaner layout + status icons - StatusIcon component with CheckCircle2/XCircle for visual feedback - Compact timeline layout: icon, timestamp, duration badge, summary, tokens, status - Error messages in monospace
 blocks with error-specific styling
- Mobile: duration + tokens move to expanded section
- Hide pagination when single page
---
 .../cron/cron-detail/cron-run-history-tab.tsx | 142 ++++++++++--------
 1 file changed, 77 insertions(+), 65 deletions(-)

diff --git a/ui/web/src/pages/cron/cron-detail/cron-run-history-tab.tsx b/ui/web/src/pages/cron/cron-detail/cron-run-history-tab.tsx
index c192e32e..7e539074 100644
--- a/ui/web/src/pages/cron/cron-detail/cron-run-history-tab.tsx
+++ b/ui/web/src/pages/cron/cron-detail/cron-run-history-tab.tsx
@@ -1,6 +1,6 @@
 import { useState, useEffect, useCallback } from "react";
 import { useTranslation } from "react-i18next";
-import { RefreshCw, Clock, AlertTriangle, ChevronDown, ChevronUp } from "lucide-react";
+import { RefreshCw, Clock, AlertTriangle, CheckCircle2, XCircle, ChevronDown, Zap } from "lucide-react";
 import { Button } from "@/components/ui/button";
 import { Badge } from "@/components/ui/badge";
 import { Pagination } from "@/components/shared/pagination";
@@ -21,6 +21,12 @@ function formatDuration(ms?: number): string {
   return `${(ms / 60000).toFixed(1)}m`;
 }
 
+function StatusIcon({ status }: { status?: string }) {
+  const isSuccess = status === "ok" || status === "success";
+  if (isSuccess) return ;
+  return ;
+}
+
 function RunEntry({ entry }: { entry: CronRunLogEntry }) {
   const { t } = useTranslation("cron");
   const [expanded, setExpanded] = useState(false);
@@ -28,80 +34,81 @@ function RunEntry({ entry }: { entry: CronRunLogEntry }) {
   const hasDetails = !!(entry.summary || entry.error);
 
   return (
-    
- {/* Header row — always visible */} +
+ {/* Row */} - {/* Expanded details */} + {/* Expanded */} {expanded && ( -
+
{entry.summary && ( -
+
)} {entry.error && (
-
- - {t("detail.lastError")} +
+ + {t("detail.lastError")}
- -
- )} - {/* Token details on mobile */} - {entry.inputTokens != null && entry.inputTokens > 0 && ( -
- {t("detail.inOut", { - input: formatTokens(entry.inputTokens), - output: formatTokens(entry.outputTokens ?? 0), - })} +
{entry.error}
)} + {/* Mobile: duration + tokens */} +
+ {entry.durationMs != null && entry.durationMs > 0 && ( + {formatDuration(entry.durationMs)} + )} + {entry.inputTokens != null && entry.inputTokens > 0 && ( + {formatTokens(entry.inputTokens)} in / {formatTokens(entry.outputTokens ?? 0)} out + )} +
)}
@@ -149,42 +156,47 @@ export function CronRunHistoryTab({ job, getRunLog, onRefresh }: CronRunHistoryT
-

{t("detail.runHistory")}

+

{t("detail.runHistory")}

{runLogTotal > 0 && ( - ({runLogTotal}) + {runLogTotal} )}
-
{runLogLoading && runLog.length === 0 ? ( -
+
) : runLog.length === 0 ? ( -

{t("detail.noHistory")}

+
+ +

{t("detail.noHistory")}

+
) : ( <> -
+
{runLog.map((entry, i) => ( ))}
-
- { setRunLogPage(p); loadRunLog(p); }} - onPageSizeChange={(s) => { setRunLogPageSize(s); setRunLogPage(1); loadRunLog(1, s); }} - pageSizes={[10, 20, 50]} - /> -
+ {runLogTotalPages > 1 && ( +
+ { setRunLogPage(p); loadRunLog(p); }} + onPageSizeChange={(s) => { setRunLogPageSize(s); setRunLogPage(1); loadRunLog(1, s); }} + pageSizes={[10, 20, 50]} + /> +
+ )} )}