You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tooling.apache.org by ak...@apache.org on 2026/06/05 07:37:32 UTC
(tooling-gofannon) 08/25: add home page running jobs module
This is an automated email from the ASF dual-hosted git repository.
andrewmusselman pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tooling-gofannon.git
commit 3f149952dfd290adabf939d8ce7501b05751c4eb
Author: Andrew Musselman <ak...@apache.org>
AuthorDate: Wed Jun 3 12:00:00 2026 +0000
add home page running jobs module
ISSUE-006
Adds a cross-agent Running Jobs widget to the home page that lists
in-flight runs across all agents the user has access to. Each row
links to its agent's /runs page where ISSUE-005's useRun hook takes
over streaming.
Depends on ISSUE-003's registry endpoints. Small component, no
backend changes -- the data comes from GET /runs?status=running.
---
.../webui/src/components/RunningJobsModule.jsx | 117 +++++++++++++++++++++
webapp/packages/webui/src/pages/HomePage.jsx | 3 +
2 files changed, 120 insertions(+)
diff --git a/webapp/packages/webui/src/components/RunningJobsModule.jsx b/webapp/packages/webui/src/components/RunningJobsModule.jsx
new file mode 100644
index 0000000..09283ae
--- /dev/null
+++ b/webapp/packages/webui/src/components/RunningJobsModule.jsx
@@ -0,0 +1,117 @@
+// webapp/packages/webui/src/components/RunningJobsModule.jsx
+//
+// ISSUE-006 — cross-agent view of "what's running" on the home page.
+// Polls GET /runs every 5s and renders each non-terminal run plus the
+// most recent N completed runs. Status chip color encodes terminal state.
+//
+// Deliberately uses polling rather than per-row SSE — this is an
+// at-a-glance overview, and 30+ concurrent EventSources for users with
+// many agents would be wasteful.
+
+import React, { useEffect, useState } from 'react';
+import { Paper, Box, Typography, Chip, CircularProgress, Stack, Link } from '@mui/material';
+import { Link as RouterLink } from 'react-router-dom';
+import { listRuns } from '../services/runService';
+
+const POLL_INTERVAL_MS = 5000;
+const MAX_COMPLETED_SHOWN = 5;
+
+const STATUS_CHIP = {
+ running: { label: 'running', color: 'info' },
+ success: { label: 'success', color: 'success' },
+ error: { label: 'error', color: 'error' },
+ stopped: { label: 'stopped', color: 'default' },
+};
+
+export default function RunningJobsModule() {
+ const [runs, setRuns] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+
+ useEffect(() => {
+ let cancelled = false;
+ const fetchOnce = async () => {
+ try {
+ const data = await listRuns();
+ if (cancelled) return;
+ setRuns(data.runs || []);
+ setError(null);
+ } catch (e) {
+ if (!cancelled) setError(e.message || String(e));
+ } finally {
+ if (!cancelled) setLoading(false);
+ }
+ };
+ fetchOnce();
+ const id = setInterval(fetchOnce, POLL_INTERVAL_MS);
+ return () => {
+ cancelled = true;
+ clearInterval(id);
+ };
+ }, []);
+
+ const running = runs.filter((r) => r.status === 'running');
+ const recent = runs.filter((r) => r.status !== 'running').slice(0, MAX_COMPLETED_SHOWN);
+
+ return (
+ <Paper sx={{ p: 2, mb: 3 }}>
+ <Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 1.5 }}>
+ <Typography variant="h6">Running Jobs</Typography>
+ {loading && <CircularProgress size={16} />}
+ </Box>
+
+ {error && (
+ <Typography variant="body2" color="error" sx={{ mb: 1 }}>
+ {error}
+ </Typography>
+ )}
+
+ {!loading && running.length === 0 && recent.length === 0 && (
+ <Typography variant="body2" color="text.secondary">
+ No runs yet. Kick off an agent to see live status here.
+ </Typography>
+ )}
+
+ {running.length > 0 && (
+ <Box sx={{ mb: 1.5 }}>
+ <Typography variant="caption" color="text.secondary">In flight</Typography>
+ <Stack spacing={0.5} sx={{ mt: 0.5 }}>
+ {running.map((r) => <RunRow key={r.runId} run={r} />)}
+ </Stack>
+ </Box>
+ )}
+
+ {recent.length > 0 && (
+ <Box>
+ <Typography variant="caption" color="text.secondary">Recent</Typography>
+ <Stack spacing={0.5} sx={{ mt: 0.5 }}>
+ {recent.map((r) => <RunRow key={r.runId} run={r} />)}
+ </Stack>
+ </Box>
+ )}
+ </Paper>
+ );
+}
+
+function RunRow({ run }) {
+ const cfg = STATUS_CHIP[run.status] || { label: run.status, color: 'default' };
+ return (
+ <Box sx={{ display: 'flex', alignItems: 'center', gap: 1, fontSize: '0.875rem' }}>
+ <Chip size="small" label={cfg.label} color={cfg.color} />
+ <Link component={RouterLink} to={`/runs/${run.runId}`} sx={{ flex: 1, textDecoration: 'none' }}>
+ {run.agentName}
+ </Link>
+ <Typography variant="caption" color="text.secondary">
+ {formatTimeAgo(run.startedAt)}
+ </Typography>
+ </Box>
+ );
+}
+
+function formatTimeAgo(iso) {
+ if (!iso) return '';
+ const diff = (Date.now() - new Date(iso).getTime()) / 1000;
+ if (diff < 60) return `${Math.round(diff)}s ago`;
+ if (diff < 3600) return `${Math.round(diff / 60)}m ago`;
+ return `${Math.round(diff / 3600)}h ago`;
+}
diff --git a/webapp/packages/webui/src/pages/HomePage.jsx b/webapp/packages/webui/src/pages/HomePage.jsx
index 472d28c..2da34fc 100644
--- a/webapp/packages/webui/src/pages/HomePage.jsx
+++ b/webapp/packages/webui/src/pages/HomePage.jsx
@@ -1,4 +1,5 @@
import React, { useState, useEffect } from 'react';
+import RunningJobsModule from '../components/RunningJobsModule'; // ISSUE-006
import { useNavigate } from 'react-router-dom';
import {
Box,
@@ -117,6 +118,8 @@ const HomePage = () => {
return (
<Box sx={{ p: 3, maxWidth: 1800, margin: '0 auto' }}>
+ {/* ISSUE-006: cross-agent runs overview */}
+ <RunningJobsModule />
<Box sx={{
display: 'grid',
gridTemplateColumns: { xs: '1fr', lg: '1fr 1fr', xl: '1fr 1fr 1fr' },
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@tooling.apache.org
For additional commands, e-mail: commits-help@tooling.apache.org