diff --git a/frontends/nextjs/src/components/misc/github/views/RunList.tsx b/frontends/nextjs/src/components/misc/github/views/RunList.tsx
index d508ed6da..25b511d03 100644
--- a/frontends/nextjs/src/components/misc/github/views/RunList.tsx
+++ b/frontends/nextjs/src/components/misc/github/views/RunList.tsx
@@ -1,60 +1,15 @@
import { Box, Stack, Typography } from '@mui/material'
-import { alpha } from '@mui/material/styles'
-import {
- Autorenew as RunningIcon,
- Cancel as FailureIcon,
- CheckCircle as SuccessIcon,
- Download as DownloadIcon,
- OpenInNew as OpenInNewIcon,
- Refresh as RefreshIcon,
-} from '@mui/icons-material'
-import { Alert, AlertDescription, AlertTitle, Badge, Button, Card, CardContent, CardDescription, CardHeader, CardTitle, Skeleton } from '@/components/ui'
+import { CheckCircle as SuccessIcon } from '@mui/icons-material'
-import { WorkflowRun } from '../types'
+import { Button, Card, CardContent, CardDescription, CardHeader, CardTitle, Skeleton } from '@/components/ui'
-const spinSx = {
- animation: 'spin 1s linear infinite',
- '@keyframes spin': {
- from: { transform: 'rotate(0deg)' },
- to: { transform: 'rotate(360deg)' },
- },
-}
-
-interface PipelineSummary {
- cancelled: number
- completed: number
- failed: number
- health: 'healthy' | 'warning' | 'critical'
- inProgress: number
- mostRecentFailed: boolean
- mostRecentPassed: boolean
- mostRecentRunning: boolean
- recentWorkflows: WorkflowRun[]
- successRate: number
- successful: number
- total: number
-}
-
-interface RunListProps {
- runs: WorkflowRun[] | null
- isLoading: boolean
- error: string | null
- needsAuth: boolean
- repoLabel: string
- lastFetched: Date | null
- autoRefreshEnabled: boolean
- secondsUntilRefresh: number
- onToggleAutoRefresh: () => void
- onRefresh: () => void
- getStatusColor: (status: string, conclusion: string | null) => string
- onDownloadLogs: (runId: number, runName: string) => void
- onDownloadJson: () => void
- isLoadingLogs: boolean
- conclusion: PipelineSummary | null
- summaryTone: 'success' | 'error' | 'warning'
- selectedRunId: number | null
-}
+import type { WorkflowRun } from '../types'
+import { RefreshControls } from './run-list/RefreshControls'
+import { RunItemCard } from './run-list/RunItemCard'
+import { RunListAlerts } from './run-list/RunListAlerts'
+import { RunListEmptyState } from './run-list/RunListEmptyState'
+import type { RunListProps } from './run-list/run-list.types'
export function RunList({
runs,
@@ -111,191 +66,25 @@ export function RunList({
)}
-
-
-
-
- Auto-refresh {autoRefreshEnabled ? 'ON' : 'OFF'}
-
- {autoRefreshEnabled && (
-
- Next refresh: {secondsUntilRefresh}s
-
- )}
-
-
-
-
- }
- >
- Download JSON
-
-
- }
- >
- {isLoading ? 'Fetching...' : 'Refresh'}
-
-
+
- {error && (
-
- Error
- {error}
-
- )}
-
- {needsAuth && (
-
- Authentication Required
-
- GitHub API requires authentication for this request. Please configure credentials and retry.
-
-
- )}
-
- {conclusion && (
- ({
- borderWidth: 2,
- borderColor: theme.palette[summaryTone].main,
- bgcolor: alpha(theme.palette[summaryTone].main, 0.08),
- alignItems: 'flex-start',
- mb: 2,
- })}
- >
-
- {summaryTone === 'success' && (
-
- )}
- {summaryTone === 'error' && (
-
- )}
- {summaryTone === 'warning' && (
-
- )}
-
-
-
- {conclusion.mostRecentPassed && 'Most Recent Builds: ALL PASSED'}
- {conclusion.mostRecentFailed && 'Most Recent Builds: FAILURES DETECTED'}
- {conclusion.mostRecentRunning && 'Most Recent Builds: RUNNING'}
-
-
-
-
-
- {conclusion.recentWorkflows.length > 1
- ? `Showing ${conclusion.recentWorkflows.length} workflows from the most recent run:`
- : 'Most recent workflow:'}
-
-
- {conclusion.recentWorkflows.map((workflow: WorkflowRun) => {
- const statusLabel = workflow.status === 'completed'
- ? workflow.conclusion
- : workflow.status
- const badgeVariant = workflow.conclusion === 'success'
- ? 'default'
- : workflow.conclusion === 'failure'
- ? 'destructive'
- : 'outline'
-
- return (
-
-
-
- {workflow.status === 'completed' && workflow.conclusion === 'success' && (
-
- )}
- {workflow.status === 'completed' && workflow.conclusion === 'failure' && (
-
- )}
- {workflow.status !== 'completed' && (
-
- )}
- {workflow.name}
-
- {statusLabel}
-
-
-
-
- Branch:
-
- {workflow.head_branch}
-
-
-
- Updated:
- {new Date(workflow.updated_at).toLocaleString()}
-
-
-
-
- )
- })}
-
-
- }
- >
- View All Workflows on GitHub
-
-
-
-
-
-
-
- )}
+
@@ -320,92 +109,16 @@ export function RunList({
{runs && runs.length > 0 ? (
- {runs.map((run) => {
- const statusIcon = getStatusColor(run.status, run.conclusion)
- return (
-
-
-
-
-
-
- {run.name}
-
- {run.event}
-
-
-
-
-
- Branch:
-
- {run.head_branch}
-
-
-
- Event:
- {run.event}
-
-
- Status:
-
- {run.status === 'completed' ? run.conclusion : run.status}
-
-
-
-
- Updated: {new Date(run.updated_at).toLocaleString()}
-
-
-
-
-
- }
- >
- View
-
-
-
-
-
- )
- })}
+ {runs.map((run: WorkflowRun) => (
+
+ ))}
) : (
-
- {isLoading ? 'Loading workflow runs...' : 'No workflow runs found. Click refresh to fetch data.'}
-
+
)}
diff --git a/frontends/nextjs/src/components/misc/github/views/run-list/RefreshControls.tsx b/frontends/nextjs/src/components/misc/github/views/run-list/RefreshControls.tsx
new file mode 100644
index 000000000..81b4421d3
--- /dev/null
+++ b/frontends/nextjs/src/components/misc/github/views/run-list/RefreshControls.tsx
@@ -0,0 +1,73 @@
+import { Stack, Typography } from '@mui/material'
+import { Download as DownloadIcon, Refresh as RefreshIcon } from '@mui/icons-material'
+
+import { Badge, Button } from '@/components/ui'
+
+import type { RunListProps } from './run-list.types'
+import { spinSx } from './run-list.types'
+
+type RefreshControlsProps = Pick<
+ RunListProps,
+ |
+ 'autoRefreshEnabled'
+ | 'secondsUntilRefresh'
+ | 'onToggleAutoRefresh'
+ | 'onDownloadJson'
+ | 'onRefresh'
+ | 'runs'
+ | 'isLoading'
+>
+
+export const RefreshControls = ({
+ autoRefreshEnabled,
+ secondsUntilRefresh,
+ onToggleAutoRefresh,
+ onDownloadJson,
+ onRefresh,
+ runs,
+ isLoading,
+}: RefreshControlsProps) => (
+
+
+
+
+ Auto-refresh {autoRefreshEnabled ? 'ON' : 'OFF'}
+
+ {autoRefreshEnabled && (
+
+ Next refresh: {secondsUntilRefresh}s
+
+ )}
+
+
+
+
+ }
+ >
+ Download JSON
+
+
+ }
+ >
+ {isLoading ? 'Fetching...' : 'Refresh'}
+
+
+)
diff --git a/frontends/nextjs/src/components/misc/github/views/run-list/RunItemCard.tsx b/frontends/nextjs/src/components/misc/github/views/run-list/RunItemCard.tsx
new file mode 100644
index 000000000..798877a59
--- /dev/null
+++ b/frontends/nextjs/src/components/misc/github/views/run-list/RunItemCard.tsx
@@ -0,0 +1,109 @@
+import { Box, Stack, Typography } from '@mui/material'
+import { Download as DownloadIcon, OpenInNew as OpenInNewIcon, Autorenew as RunningIcon } from '@mui/icons-material'
+
+import { Badge, Button, Card, CardContent } from '@/components/ui'
+
+import type { WorkflowRun } from '../types'
+import type { RunListProps } from './run-list.types'
+import { spinSx } from './run-list.types'
+
+type RunItemCardProps = Pick<
+ RunListProps,
+ 'getStatusColor' | 'onDownloadLogs' | 'isLoadingLogs' | 'selectedRunId'
+> & {
+ run: WorkflowRun
+}
+
+export const RunItemCard = ({
+ run,
+ getStatusColor,
+ onDownloadLogs,
+ isLoadingLogs,
+ selectedRunId,
+}: RunItemCardProps) => {
+ const statusIcon = getStatusColor(run.status, run.conclusion)
+
+ return (
+
+
+
+
+
+
+ {run.name}
+
+ {run.event}
+
+
+
+
+
+ Branch:
+
+ {run.head_branch}
+
+
+
+ Event:
+ {run.event}
+
+
+ Status:
+
+ {run.status === 'completed' ? run.conclusion : run.status}
+
+
+
+
+ Updated: {new Date(run.updated_at).toLocaleString()}
+
+
+
+
+
+ }
+ >
+ View
+
+
+
+
+
+ )
+}
diff --git a/frontends/nextjs/src/components/misc/github/views/run-list/RunListAlerts.tsx b/frontends/nextjs/src/components/misc/github/views/run-list/RunListAlerts.tsx
new file mode 100644
index 000000000..b7419f2c9
--- /dev/null
+++ b/frontends/nextjs/src/components/misc/github/views/run-list/RunListAlerts.tsx
@@ -0,0 +1,171 @@
+import { Box, Stack, Typography } from '@mui/material'
+import { alpha } from '@mui/material/styles'
+import {
+ Autorenew as RunningIcon,
+ Cancel as FailureIcon,
+ CheckCircle as SuccessIcon,
+ OpenInNew as OpenInNewIcon,
+} from '@mui/icons-material'
+
+import { Alert, AlertDescription, AlertTitle, Badge, Button } from '@/components/ui'
+
+import type { WorkflowRun } from '../types'
+import type { PipelineSummary, RunListProps } from './run-list.types'
+import { spinSx } from './run-list.types'
+
+type RunListAlertsProps = Pick<
+ RunListProps,
+ 'error' | 'needsAuth' | 'conclusion' | 'summaryTone'
+>
+
+type SummaryAlertProps = {
+ conclusion: PipelineSummary
+ summaryTone: RunListProps['summaryTone']
+}
+
+const SummaryAlert = ({ conclusion, summaryTone }: SummaryAlertProps) => (
+ ({
+ borderWidth: 2,
+ borderColor: theme.palette[summaryTone].main,
+ bgcolor: alpha(theme.palette[summaryTone].main, 0.08),
+ alignItems: 'flex-start',
+ mb: 2,
+ })}
+ >
+
+ {summaryTone === 'success' && (
+
+ )}
+ {summaryTone === 'error' && (
+
+ )}
+ {summaryTone === 'warning' && (
+
+ )}
+
+
+
+ {conclusion.mostRecentPassed && 'Most Recent Builds: ALL PASSED'}
+ {conclusion.mostRecentFailed && 'Most Recent Builds: FAILURES DETECTED'}
+ {conclusion.mostRecentRunning && 'Most Recent Builds: RUNNING'}
+
+
+
+
+
+ {conclusion.recentWorkflows.length > 1
+ ? `Showing ${conclusion.recentWorkflows.length} workflows from the most recent run:`
+ : 'Most recent workflow:'}
+
+
+ {conclusion.recentWorkflows.map((workflow: WorkflowRun) => {
+ const statusLabel = workflow.status === 'completed'
+ ? workflow.conclusion
+ : workflow.status
+ const badgeVariant = workflow.conclusion === 'success'
+ ? 'default'
+ : workflow.conclusion === 'failure'
+ ? 'destructive'
+ : 'outline'
+
+ return (
+
+
+
+ {workflow.status === 'completed' && workflow.conclusion === 'success' && (
+
+ )}
+ {workflow.status === 'completed' && workflow.conclusion === 'failure' && (
+
+ )}
+ {workflow.status !== 'completed' && (
+
+ )}
+ {workflow.name}
+
+ {statusLabel}
+
+
+
+
+ Branch:
+
+ {workflow.head_branch}
+
+
+
+ Updated:
+ {new Date(workflow.updated_at).toLocaleString()}
+
+
+
+
+ )
+ })}
+
+
+ }
+ >
+ View All Workflows on GitHub
+
+
+
+
+
+
+
+)
+
+export const RunListAlerts = ({ error, needsAuth, conclusion, summaryTone }: RunListAlertsProps) => (
+ <>
+ {error && (
+
+ Error
+ {error}
+
+ )}
+
+ {needsAuth && (
+
+ Authentication Required
+
+ GitHub API requires authentication for this request. Please configure credentials and retry.
+
+
+ )}
+
+ {conclusion && (
+
+ )}
+ >
+)
diff --git a/frontends/nextjs/src/components/misc/github/views/run-list/RunListEmptyState.tsx b/frontends/nextjs/src/components/misc/github/views/run-list/RunListEmptyState.tsx
new file mode 100644
index 000000000..aae0930d0
--- /dev/null
+++ b/frontends/nextjs/src/components/misc/github/views/run-list/RunListEmptyState.tsx
@@ -0,0 +1,11 @@
+import { Box } from '@mui/material'
+
+import type { RunListProps } from './run-list.types'
+
+type RunListEmptyStateProps = Pick
+
+export const RunListEmptyState = ({ isLoading }: RunListEmptyStateProps) => (
+
+ {isLoading ? 'Loading workflow runs...' : 'No workflow runs found. Click refresh to fetch data.'}
+
+)
diff --git a/frontends/nextjs/src/components/misc/github/views/run-list/run-list.types.ts b/frontends/nextjs/src/components/misc/github/views/run-list/run-list.types.ts
new file mode 100644
index 000000000..f0775def7
--- /dev/null
+++ b/frontends/nextjs/src/components/misc/github/views/run-list/run-list.types.ts
@@ -0,0 +1,48 @@
+import { SxProps, Theme } from '@mui/material/styles'
+
+import type { WorkflowRun } from '../types'
+
+type SummaryTone = 'success' | 'error' | 'warning'
+
+export interface PipelineSummary {
+ cancelled: number
+ completed: number
+ failed: number
+ health: 'healthy' | 'warning' | 'critical'
+ inProgress: number
+ mostRecentFailed: boolean
+ mostRecentPassed: boolean
+ mostRecentRunning: boolean
+ recentWorkflows: WorkflowRun[]
+ successRate: number
+ successful: number
+ total: number
+}
+
+export interface RunListProps {
+ runs: WorkflowRun[] | null
+ isLoading: boolean
+ error: string | null
+ needsAuth: boolean
+ repoLabel: string
+ lastFetched: Date | null
+ autoRefreshEnabled: boolean
+ secondsUntilRefresh: number
+ onToggleAutoRefresh: () => void
+ onRefresh: () => void
+ getStatusColor: (status: string, conclusion: string | null) => string
+ onDownloadLogs: (runId: number, runName: string) => void
+ onDownloadJson: () => void
+ isLoadingLogs: boolean
+ conclusion: PipelineSummary | null
+ summaryTone: SummaryTone
+ selectedRunId: number | null
+}
+
+export const spinSx: SxProps = {
+ animation: 'spin 1s linear infinite',
+ '@keyframes spin': {
+ from: { transform: 'rotate(0deg)' },
+ to: { transform: 'rotate(360deg)' },
+ },
+}