import { useMemo, useState } from 'react'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { cancelBuild, getBuildLogs, listBuilds, type BuildEntity, type BuildStatus, } from '@/lib/api-client'; import { useBuildUpdates } from '@/lib/use-build-updates'; import { useDemoMode } from '@/lib/demo-mode'; import { demoBuilds } from '@/lib/demo-data'; import { formatRelative } from '@/lib/time'; import { Check, X, Loader2, Clock, RefreshCw, FileText, Box, Sparkles, Filter, Circle, HardDrive, } from 'lucide-react'; const statusOptions: Array<{ value: '' | BuildStatus; label: string }> = [ { value: '', label: 'All statuses' }, { value: 'pending', label: 'Pending' }, { value: 'running', label: 'Running' }, { value: 'success', label: 'Success' }, { value: 'failed', label: 'Failed' }, { value: 'cancelled', label: 'Cancelled' }, ]; function StatusBadge({ status }: { status: BuildStatus }) { const config = { success: { color: 'var(--success)', bg: 'var(--success-soft)', Icon: Check, animate: false }, failed: { color: 'var(--error)', bg: 'var(--error-soft)', Icon: X, animate: false }, running: { color: 'var(--warning)', bg: 'var(--warning-soft)', Icon: Loader2, animate: true }, pending: { color: 'var(--text-tertiary)', bg: 'var(--surface-muted)', Icon: Clock, animate: false }, cancelled: { color: 'var(--text-tertiary)', bg: 'var(--surface-muted)', Icon: X, animate: false }, }[status] || { color: 'var(--text-tertiary)', bg: 'var(--surface-muted)', Icon: Circle, animate: false }; const { Icon } = config; return (
{status}
); } function bytesToHumanReadable(bytes: number): string { if (bytes <= 0) return '—'; const units = ['B', 'KB', 'MB', 'GB', 'TB']; let value = bytes; let index = 0; while (value >= 1024 && index < units.length - 1) { value /= 1024; index += 1; } return `${value.toFixed(index === 0 ? 0 : 1)} ${units[index]}`; } export function BuildsPage() { const queryClient = useQueryClient(); const isDemoMode = useDemoMode(); const [projectFilter, setProjectFilter] = useState(''); const [serviceFilter, setServiceFilter] = useState(''); const [statusFilter, setStatusFilter] = useState<'' | BuildStatus>(''); const [limit, setLimit] = useState(50); const [selectedBuild, setSelectedBuild] = useState(null); const buildsQuery = useQuery({ queryKey: ['builds-page', { projectFilter, serviceFilter, statusFilter, limit }], enabled: !isDemoMode, queryFn: () => listBuilds({ projectId: projectFilter || undefined, serviceId: serviceFilter || undefined, status: statusFilter || undefined, page: 1, limit, }), }); const builds = useMemo( () => (isDemoMode ? demoBuilds : buildsQuery.data?.builds ?? []), [isDemoMode, buildsQuery.data?.builds], ); const subscribedBuildIds = useMemo( () => (isDemoMode ? [] : builds.map((build) => build.id)), [isDemoMode, builds], ); const liveStatus = useBuildUpdates(subscribedBuildIds, ({ channel }) => { queryClient.invalidateQueries({ queryKey: ['builds-page'] }); queryClient.invalidateQueries({ queryKey: ['usage-builds'] }); if (selectedBuild && channel === `build:${selectedBuild.id}`) { queryClient.invalidateQueries({ queryKey: ['build-logs', selectedBuild.id] }); } }); const cancelMutation = useMutation({ mutationFn: (buildId: string) => cancelBuild(buildId), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['builds-page'] }); queryClient.invalidateQueries({ queryKey: ['usage-builds'] }); }, }); const logsQuery = useQuery({ queryKey: ['build-logs', selectedBuild?.id], enabled: Boolean(selectedBuild) && !isDemoMode, queryFn: () => getBuildLogs(selectedBuild!.id), }); // Stats const stats = useMemo(() => { const running = builds.filter(b => b.status === 'running' || b.status === 'pending').length; const success = builds.filter(b => b.status === 'success').length; const failed = builds.filter(b => b.status === 'failed').length; return { running, success, failed, total: builds.length }; }, [builds]); return (
{/* Header */}

Build Pipeline_

Monitor build progress and manage jobs

{liveStatus === 'live' ? 'Live' : liveStatus === 'offline' ? 'Reconnecting...' : 'Polling'}
{/* Demo Mode Banner */} {isDemoMode && (
Demo mode active — using sample data
)} {/* Stats Overview */}

{stats.total}

Total Builds

{stats.running}

In Progress

{stats.success}

Successful

{stats.failed}

Failed

{/* Filters */}
Filters
setProjectFilter(e.target.value)} className="w-full h-10 px-3 rounded-[var(--radius-md)] border border-[var(--border-subtle)] bg-[var(--surface-muted)] text-sm focus:border-[var(--accent-primary)] transition-colors" placeholder="project-123" />
setServiceFilter(e.target.value)} className="w-full h-10 px-3 rounded-[var(--radius-md)] border border-[var(--border-subtle)] bg-[var(--surface-muted)] text-sm focus:border-[var(--accent-primary)] transition-colors" placeholder="service-abc" />
{/* Build Table */}
{!isDemoMode && buildsQuery.isLoading ? (

Loading builds...

) : null} {!isDemoMode && buildsQuery.isError ? (

Failed to load builds

{buildsQuery.error instanceof Error ? buildsQuery.error.message : 'Unknown error'}

) : null} {builds.length === 0 && !buildsQuery.isLoading ? (

No builds match current filters

) : null} {builds.length > 0 ? (
{builds.map((build) => (
setSelectedBuild(build)} >
{build.status === 'success' && } {build.status === 'failed' && } {build.status === 'running' && } {build.status === 'pending' && } {build.status === 'cancelled' && }

{build.id}

{build.startedAt ? formatRelative(build.startedAt) : '—'}
{/* Image info */}
Image {build.imageName || '—'}:{build.imageTag || 'latest'}
{/* Progress bar */} {(build.status === 'running' || build.status === 'pending') && (
Progress {build.progress}%
)} {/* Size and service */}
{bytesToHumanReadable(build.size)}
{build.serviceId || '—'}
{/* Error message if present */} {build.error && (

{build.error}

)}
))}
) : null}
{/* Error Toast */} {cancelMutation.isError && (
{cancelMutation.error instanceof Error ? cancelMutation.error.message : 'Failed to cancel build.'}
)} {/* Logs Modal */} {selectedBuild && (
setSelectedBuild(null)} />

Build Logs

{selectedBuild.id}

                {isDemoMode
                  ? selectedBuild.log || '[demo] No logs available.'
                  : logsQuery.isLoading
                  ? 'Loading logs...'
                  : logsQuery.isError
                  ? 'Failed to load logs.'
                  : logsQuery.data || '(empty logs)'}
              
)}
); }