diff --git a/frontend/src/pages/DashboardPage.tsx b/frontend/src/pages/DashboardPage.tsx index 9e5ff1d..397f03e 100644 --- a/frontend/src/pages/DashboardPage.tsx +++ b/frontend/src/pages/DashboardPage.tsx @@ -31,7 +31,9 @@ function BarMetric({ label, value, max, tone = "neutral" }: { label: string; val ); } -function MiniHistogram({ data }: { data: Array<{ label: string; value: number; tone: string }> }) { +type ChartPoint = { label: string; value: number; tone: string }; + +function MiniHistogram({ data }: { data: ChartPoint[] }) { const max = Math.max(...data.map((item) => item.value), 1); return (
@@ -51,6 +53,18 @@ function MiniHistogram({ data }: { data: Array<{ label: string; value: number; t ); } +function StatusStack({ data }: { data: ChartPoint[] }) { + const total = data.reduce((sum, item) => sum + item.value, 0); + return ( +
+ {data.map((item) => { + const width = total > 0 ? Math.max(6, Math.round((item.value / total) * 100)) : 25; + return ; + })} +
+ ); +} + export function DashboardPage() { const [jobs, setJobs] = useState([]); const [events, setEvents] = useState([]); @@ -110,6 +124,11 @@ export function DashboardPage() { .map(([type, value]) => ({ label: eventTitle(type), value, tone: type.includes("fail") ? "failed" : type.includes("success") ? "succeeded" : "running" })); }, [events]); const maxPressure = Math.max(...pressureData.map((item) => item.value), 1); + const activePressure = stats.by_status.queued + stats.by_status.running + stats.retries_pending + stats.overdue_running; + const completionRate = recentJobs.length + ? Math.round((recentJobs.filter((job) => job.status === "succeeded").length / recentJobs.length) * 100) + : 0; + const topEvent = eventData.at(0); return (
@@ -131,7 +150,7 @@ export function DashboardPage() {
-
+

Recent Jobs

@@ -140,6 +159,11 @@ export function DashboardPage() {
{recentJobs.length ? ( <> +
+ {recentJobs.length} latest jobs + {completionRate}% succeeded +
+
{recentJobs.slice(0, 16).map((job) => ( @@ -152,11 +176,15 @@ export function DashboardPage() { )}
-
+

Queue Pressure

+
+ Active pressure + {activePressure} +
{pressureData.map((item) => ( @@ -167,12 +195,22 @@ export function DashboardPage() {
-
+

Recent Events

- {eventData.length ? : } + {eventData.length ? ( + <> +
+ {events.length} latest events + {topEvent ? topEvent.label : "No events"} +
+ + + ) : ( + + )}
diff --git a/frontend/src/styles.css b/frontend/src/styles.css index f8687d1..1811f7c 100644 --- a/frontend/src/styles.css +++ b/frontend/src/styles.css @@ -442,6 +442,49 @@ label { grid-template-columns: repeat(2, minmax(0, 1fr)); } +.chart-panel { + overflow: hidden; + position: relative; +} + +.chart-panel::before { + background: linear-gradient(90deg, var(--cyan), var(--green), var(--orange), var(--red)); + content: ""; + height: 3px; + left: 14px; + opacity: 0.78; + position: absolute; + right: 14px; + top: 0; +} + +.chart-summary-row { + align-items: center; + background: var(--panel-raised); + border: 1px solid var(--border); + border-radius: 6px; + display: flex; + gap: 12px; + justify-content: space-between; + margin-bottom: 12px; + min-height: 42px; + padding: 9px 10px; +} + +.chart-summary-row span { + color: var(--muted); + font-size: 12px; + font-weight: 900; +} + +.chart-summary-row strong { + font-size: 14px; + overflow: hidden; + text-align: right; + text-overflow: ellipsis; + white-space: nowrap; +} + .detail-grid { display: grid; gap: 16px; @@ -645,9 +688,21 @@ label { display: flex; height: 112px; overflow: hidden; + position: relative; width: 100%; } +.histogram-column::before { + background: + linear-gradient(to top, transparent 24%, rgb(127 139 147 / 18%) 25%, transparent 26%), + linear-gradient(to top, transparent 49%, rgb(127 139 147 / 18%) 50%, transparent 51%), + linear-gradient(to top, transparent 74%, rgb(127 139 147 / 18%) 75%, transparent 76%); + content: ""; + inset: 0; + pointer-events: none; + position: absolute; +} + .histogram-column i, .metric-track i { background: var(--accent); @@ -656,7 +711,9 @@ label { .histogram-column i { border-radius: 6px 6px 0 0; + position: relative; width: 100%; + z-index: 1; } .histogram-column i.queued, @@ -703,6 +760,38 @@ label { margin-top: 14px; } +.status-stack { + background: var(--panel-raised); + border: 1px solid var(--border); + border-radius: 999px; + display: flex; + gap: 3px; + height: 14px; + margin-bottom: 12px; + overflow: hidden; + padding: 2px; +} + +.status-stack i { + background: var(--subtle); + border-radius: 999px; + display: block; + min-width: 6px; +} + +.status-stack i.queued, +.status-stack i.running { + background: var(--cyan); +} + +.status-stack i.succeeded { + background: var(--green); +} + +.status-stack i.failed { + background: var(--red); +} + .job-tick { background: var(--subtle); border-radius: 5px;