diff --git a/ui/src/components/atoms/StatusChip.tsx b/ui/src/components/atoms/StatusChip.tsx index 0f235f6a7..de0088a2e 100644 --- a/ui/src/components/atoms/StatusChip.tsx +++ b/ui/src/components/atoms/StatusChip.tsx @@ -9,12 +9,12 @@ type Props = { }; function StatusChip({ status, children }: Props) { - const style = React.useMemo(() => { + const style = () => { if (!status) { return {}; } return statusColorMapping[status] || {}; - }, [status]); + }; return ; } diff --git a/ui/src/components/molecules/Graph.tsx b/ui/src/components/molecules/Graph.tsx index 5d5bd3b87..be96f02c5 100644 --- a/ui/src/components/molecules/Graph.tsx +++ b/ui/src/components/molecules/Graph.tsx @@ -50,6 +50,10 @@ const Graph: React.FC = ({ minHeight: '200px', padding: '2em', borderRadius: '0.5em', + background: ` + linear-gradient(90deg, #f8fafc 1px, transparent 1px), + linear-gradient(180deg, #f8fafc 1px, transparent 1px) + `, backgroundSize: '20px 20px', }; @@ -88,7 +92,7 @@ const Graph: React.FC = ({ // Construct node label with icon if enabled const icon = showIcons ? statusIcons[status] || '' : ''; - const label = `${icon} ${step.Name}`; + const label = `${icon}   ${step.Name}`; // Add node definition dat.push(`${id}[${label}]${c};`); @@ -134,24 +138,12 @@ const Graph: React.FC = ({ } // Define node styles for different states with refined colors - dat.push( - 'classDef none color:#4a5568,fill:#f8fafc,stroke:#3b82f6,stroke-width:1.2px,white-space:nowrap,line-height:1.5' - ); - dat.push( - 'classDef running color:#4a5568,fill:#aaf2aa,stroke:#22c55e,stroke-width:1.2px,white-space:nowrap,line-height:1.5' - ); - dat.push( - 'classDef error color:#4a5568,fill:#fee2e2,stroke:#ef4444,stroke-width:1.2px,white-space:nowrap,line-height:1.5' - ); - dat.push( - 'classDef cancel color:#4a5568,fill:#fdf2f8,stroke:#ec4899,stroke-width:1.2px,white-space:nowrap,line-height:1.5' - ); - dat.push( - 'classDef done color:#4a5568,fill:#f0fdf4,stroke:#16a34a,stroke-width:1.2px,white-space:nowrap,line-height:1.5' - ); - dat.push( - 'classDef skipped color:#4a5568,fill:#f8fafc,stroke:#64748b,stroke-width:1.2px,white-space:nowrap,line-height:1.5' - ); + dat.push('classDef none fill:#f0f9ff,stroke:#93c5fd,color:#1e40af,stroke-width:1.2px,white-space:nowrap'); + dat.push('classDef running fill:#f0fdf4,stroke:#86efac,color:#166534,stroke-width:1.2px,white-space:nowrap'); + dat.push('classDef error fill:#fef2f2,stroke:#fca5a5,color:#aa1010,stroke-width:1.2px,white-space:nowrap'); + dat.push('classDef cancel fill:#fdf2f8,stroke:#f9a8d4,color:#9d174d,stroke-width:1.2px,white-space:nowrap'); + dat.push('classDef done fill:#f0fdf4,stroke:#86efac,color:#166534,stroke-width:1.2px,white-space:nowrap'); + dat.push('classDef skipped fill:#f8fafc,stroke:#cbd5e1,color:#475569,stroke-width:1.2px,white-space:nowrap'); // Add custom link styles dat.push(...linkStyles); @@ -228,4 +220,4 @@ const graphStatusMap = { [NodeStatus.Cancel]: ':::cancel', [NodeStatus.Success]: ':::done', [NodeStatus.Skipped]: ':::skipped', -}; +}; \ No newline at end of file diff --git a/ui/src/components/organizations/DAGStatus.tsx b/ui/src/components/organizations/DAGStatus.tsx index 837142291..b788480e4 100644 --- a/ui/src/components/organizations/DAGStatus.tsx +++ b/ui/src/components/organizations/DAGStatus.tsx @@ -127,6 +127,7 @@ function DAGStatus({ DAG, name, refresh }: Props) { type="status" flowchart={flowchart} onClickNode={onSelectStepOnGraph} + showIcons={DAG.Status.Status != SchedulerStatus.None} > ) : ( diff --git a/ui/src/pages/dags/dag/index.tsx b/ui/src/pages/dags/dag/index.tsx index 6e0dbef2e..25beb50e8 100644 --- a/ui/src/pages/dags/dag/index.tsx +++ b/ui/src/pages/dags/dag/index.tsx @@ -13,6 +13,10 @@ import DAGEditButtons from '../../../components/molecules/DAGEditButtons'; import LoadingIndicator from '../../../components/atoms/LoadingIndicator'; import { AppBarContext } from '../../../contexts/AppBarContext'; import useSWR from 'swr'; +import StatusChip from '../../../components/atoms/StatusChip'; +import { CalendarToday, TimerSharp } from '@mui/icons-material'; +import moment from 'moment-timezone'; +import { SchedulerStatus } from '../../../models'; type Params = { name: string; @@ -62,6 +66,21 @@ function DAGDetails() { name: params.name, }; + const formatDuration = (startDate: string, endDate: string) => { + if (!startDate || !endDate) return '--'; + const duration = moment.duration(moment(endDate).diff(moment(startDate))); + const hours = Math.floor(duration.asHours()); + const minutes = duration.minutes(); + const seconds = duration.seconds(); + + if (hours > 0) { + return `${hours}h ${minutes}m ${seconds}s`; + } else if (minutes > 0) { + return `${minutes}m ${seconds}s`; + } + return `${seconds}s`; + }; + return ( + {data.DAG?.Status?.Status != SchedulerStatus.None ? ( + + {data.DAG?.Status?.Status ? ( + + {data.DAG.Status.StatusText || ''} + + ) : null} + + + + {data?.DAG?.Status?.FinishedAt + ? moment(data.DAG.Status.FinishedAt).format( + 'MMM D, YYYY HH:mm:ss Z' + ) + : '--'} + + + + + {data?.DAG?.Status?.FinishedAt + ? formatDuration( + data?.DAG?.Status?.StartedAt, + data?.DAG?.Status?.FinishedAt + ) + : data?.DAG?.Status?.StartedAt + ? formatDuration( + data?.DAG?.Status?.StartedAt, + moment().toISOString() + ) + : '--'} + + + ) : null} +