jtlevine Claude Opus 4.6 (1M context) commited on
Commit
2a03552
·
1 Parent(s): 9f98264

Add live pipeline tracker to Dashboard

Browse files

Shows a blue progress banner with step name and progress bar while
pipeline is running. Polls /api/pipeline/status every 5s. Disappears
when pipeline completes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

frontend/src/lib/api.ts CHANGED
@@ -313,6 +313,29 @@ export function usePipelineStats() {
313
  })
314
  }
315
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
316
  export function useEnrolled() {
317
  return useQuery<EnrolledResponse>({
318
  queryKey: ['enrolled'],
 
313
  })
314
  }
315
 
316
+ export interface PipelineStatus {
317
+ running: boolean
318
+ current_step: string | null
319
+ current_step_index: number
320
+ total_steps: number
321
+ last_result: {
322
+ run_id: string
323
+ status: string
324
+ zones_processed: number
325
+ triggers_found: number
326
+ duration_s: number
327
+ } | null
328
+ last_run: string | null
329
+ }
330
+
331
+ export function usePipelineStatus() {
332
+ return useQuery<PipelineStatus>({
333
+ queryKey: ['pipeline-status'],
334
+ queryFn: () => fetchJson('/api/pipeline/status'),
335
+ refetchInterval: 5000, // poll every 5s while running
336
+ })
337
+ }
338
+
339
  export function useEnrolled() {
340
  return useQuery<EnrolledResponse>({
341
  queryKey: ['enrolled'],
frontend/src/pages/Dashboard.tsx CHANGED
@@ -4,13 +4,23 @@ import { Satellite, Thermometer, SlidersHorizontal, ChevronDown, ChevronRight, A
4
  import MetricCard from '../components/MetricCard'
5
  import StatusBadge from '../components/StatusBadge'
6
  import { LoadingSpinner, ErrorState } from '../components/LoadingState'
7
- import { usePipelineStats, usePipelineRuns, useTriggers } from '../lib/api'
8
  import { REGION } from '../regionConfig'
9
 
 
 
 
 
 
 
 
 
 
10
  export default function Dashboard() {
11
  const stats = usePipelineStats()
12
  const runs = usePipelineRuns()
13
  const triggers = useTriggers()
 
14
  const [showRuns, setShowRuns] = useState(false)
15
 
16
  if (stats.isLoading) return <LoadingSpinner />
@@ -33,6 +43,36 @@ export default function Dashboard() {
33
  <p className="page-caption">{REGION.heroCaption}</p>
34
  </div>
35
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
  {/* Danger banner — shows when there are active triggers */}
37
  {activeTriggers.length > 0 && (
38
  <div className="danger-banner">
 
4
  import MetricCard from '../components/MetricCard'
5
  import StatusBadge from '../components/StatusBadge'
6
  import { LoadingSpinner, ErrorState } from '../components/LoadingState'
7
+ import { usePipelineStats, usePipelineRuns, useTriggers, usePipelineStatus } from '../lib/api'
8
  import { REGION } from '../regionConfig'
9
 
10
+ const STEP_LABELS: Record<string, string> = {
11
+ ingest: 'Collecting climate data',
12
+ heal: 'Fixing data issues',
13
+ downscale: 'Adjusting for urban heat',
14
+ predict: 'Forecasting heat danger',
15
+ explain: 'Writing alert messages',
16
+ notify: 'Notifying workers',
17
+ }
18
+
19
  export default function Dashboard() {
20
  const stats = usePipelineStats()
21
  const runs = usePipelineRuns()
22
  const triggers = useTriggers()
23
+ const pipelineStatus = usePipelineStatus()
24
  const [showRuns, setShowRuns] = useState(false)
25
 
26
  if (stats.isLoading) return <LoadingSpinner />
 
43
  <p className="page-caption">{REGION.heroCaption}</p>
44
  </div>
45
 
46
+ {/* Pipeline running banner */}
47
+ {pipelineStatus.data?.running && (
48
+ <div className="mb-6 rounded-[10px] flex items-center gap-4" style={{
49
+ background: 'linear-gradient(135deg, rgba(21,101,192,0.06) 0%, rgba(42,157,143,0.04) 100%)',
50
+ border: '1px solid rgba(21,101,192,0.15)',
51
+ padding: '14px 20px',
52
+ }}>
53
+ <div className="w-2.5 h-2.5 rounded-full flex-shrink-0" style={{
54
+ background: '#1565C0',
55
+ boxShadow: '0 0 0 3px rgba(21,101,192,0.2)',
56
+ animation: 'pulse 2s infinite',
57
+ }} />
58
+ <div className="flex-1">
59
+ <div className="text-sm font-sans font-medium" style={{ color: '#1565C0' }}>
60
+ Pipeline running — step {pipelineStatus.data.current_step_index}/{pipelineStatus.data.total_steps}:{' '}
61
+ {STEP_LABELS[pipelineStatus.data.current_step ?? ''] ?? pipelineStatus.data.current_step}
62
+ </div>
63
+ <div className="mt-1.5 w-full h-1.5 rounded-full bg-warm-border overflow-hidden">
64
+ <div
65
+ className="h-full rounded-full transition-all duration-500"
66
+ style={{
67
+ width: `${(pipelineStatus.data.current_step_index / pipelineStatus.data.total_steps) * 100}%`,
68
+ background: '#1565C0',
69
+ }}
70
+ />
71
+ </div>
72
+ </div>
73
+ </div>
74
+ )}
75
+
76
  {/* Danger banner — shows when there are active triggers */}
77
  {activeTriggers.length > 0 && (
78
  <div className="danger-banner">