'use client' import { useEffect, useState } from 'react' import { GitBranch, Play, RefreshCw, CheckCircle, XCircle, Clock, AlertCircle, ChevronRight, FileText } from 'lucide-react' interface Workflow { file: string; name: string; on: string; size: number; modifiedAt: string; content?: string } interface Run { name: string; status: string; conclusion: string; startedAt: string; updatedAt: string; url: string; displayTitle: string } const STATUS_ICON = (status: string, conclusion: string) => { if (status === 'completed') { if (conclusion === 'success') return if (conclusion === 'failure') return return } return } function timeAgo(iso?: string) { if (!iso) return '—' try { const diff = Date.now() - new Date(iso).getTime() const m = Math.floor(diff / 60000), h = Math.floor(m / 60), d = Math.floor(h / 24) if (d > 0) return `${d}d ago`; if (h > 0) return `${h}h ago`; if (m > 0) return `${m}m ago`; return 'just now' } catch { return '—' } } export default function PipelinePage() { const [workflows, setWorkflows] = useState([]) const [runs, setRuns] = useState([]) const [loading, setLoading] = useState(true) const [loadingRuns, setLoadingRuns] = useState(false) const [triggering, setTriggering] = useState(null) const [triggerOut, setTriggerOut] = useState('') const [selectedWf, setSelectedWf] = useState(null) const [tab, setTab] = useState<'workflows' | 'runs'>('workflows') useEffect(() => { fetch('/api/pipeline').then(r => r.json()).then(d => { setWorkflows(d.workflows || []); setLoading(false) }).catch(() => setLoading(false)) }, []) const loadRuns = () => { setLoadingRuns(true) fetch('/api/pipeline?action=runs').then(r => r.json()).then(d => { setRuns(d.runs || []); setLoadingRuns(false) }) } useEffect(() => { if (tab === 'runs') loadRuns() }, [tab]) const trigger = async (wf: Workflow) => { setTriggering(wf.file) const r = await fetch('/api/pipeline', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ action: 'trigger', workflow: wf.file }) }) const d = await r.json() setTriggerOut(d.output || '') setTriggering(null) setTimeout(() => setTriggerOut(''), 5000) } return (

CI/CD Pipeline

{workflows.length} workflows · GitHub Actions

{([['workflows', 'Workflows'], ['runs', 'Recent Runs']] as const).map(([key, label]) => ( ))}
{triggerOut && (
{triggerOut}
)} {tab === 'workflows' && (
{loading ?
Loading…
: workflows.map(wf => (
setSelectedWf(s => s?.file === wf.file ? null : wf)} style={{ display: 'flex', alignItems: 'center', gap: 12, padding: '12px 16px', marginBottom: 6, background: selectedWf?.file === wf.file ? 'rgba(34,197,94,0.08)' : 'rgba(255,255,255,0.03)', border: `1px solid ${selectedWf?.file === wf.file ? 'rgba(34,197,94,0.3)' : 'rgba(255,255,255,0.07)'}`, borderRadius: 10, cursor: 'pointer', }}>
{wf.name}
{wf.file} · {wf.on}
))}
{selectedWf && (
{selectedWf.file}
{selectedWf.content}
)}
)} {tab === 'runs' && (
{runs.length === 0 ? (
No runs found — requires gh CLI authenticated
) : runs.map((run, i) => ( {STATUS_ICON(run.status, run.conclusion)}
{run.displayTitle || run.name}
{run.name}
{timeAgo(run.updatedAt)}
))}
)}
) }