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;