'use client' import { useState, useEffect } from 'react' import { CheckCircle2, XCircle, Clock, Loader2, GitBranch, RefreshCw, ExternalLink, Play } from 'lucide-react' interface PipelineStep { name: string state: 'pending' | 'running' | 'success' | 'failure' | 'skipped' exit_code: number } interface Pipeline { id: number number: number status: 'pending' | 'running' | 'success' | 'failure' event: string branch: string commit: string message: string author: string created: number started: number finished: number steps: PipelineStep[] } interface CICDStatus { status: 'online' | 'offline' pipelines: Pipeline[] lastUpdate: string error?: string } const StatusIcon = ({ status }: { status: string }) => { switch (status) { case 'success': return case 'failure': return case 'running': return case 'pending': return default: return } } const StatusBadge = ({ status }: { status: string }) => { const colors: Record = { success: 'bg-green-100 text-green-800 border-green-200', failure: 'bg-red-100 text-red-800 border-red-200', running: 'bg-blue-100 text-blue-800 border-blue-200', pending: 'bg-yellow-100 text-yellow-800 border-yellow-200', } const labels: Record = { success: 'Erfolgreich', failure: 'Fehlgeschlagen', running: 'Läuft', pending: 'Wartend', } return ( {labels[status] || status} ) } function formatDuration(started: number, finished: number): string { if (!started) return '-' const end = finished || Date.now() / 1000 const duration = Math.floor(end - started) if (duration < 60) return `${duration}s` if (duration < 3600) return `${Math.floor(duration / 60)}m ${duration % 60}s` return `${Math.floor(duration / 3600)}h ${Math.floor((duration % 3600) / 60)}m` } function formatTimeAgo(timestamp: number): string { if (!timestamp) return '-' const seconds = Math.floor(Date.now() / 1000 - timestamp) if (seconds < 60) return 'Gerade eben' if (seconds < 3600) return `vor ${Math.floor(seconds / 60)} Min` if (seconds < 86400) return `vor ${Math.floor(seconds / 3600)} Std` return `vor ${Math.floor(seconds / 86400)} Tagen` } export default function CICDStatusWidget() { const [data, setData] = useState(null) const [loading, setLoading] = useState(true) const [triggering, setTriggering] = useState(false) const [expanded, setExpanded] = useState(false) const fetchStatus = async () => { try { const response = await fetch('/api/admin/cicd?limit=5') const result = await response.json() setData(result) } catch (error) { console.error('Failed to fetch CI/CD status:', error) setData({ status: 'offline', pipelines: [], lastUpdate: new Date().toISOString() }) } finally { setLoading(false) } } const triggerPipeline = async () => { setTriggering(true) try { const response = await fetch('/api/admin/cicd', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ branch: 'main' }) }) if (response.ok) { // Refresh status after triggering setTimeout(fetchStatus, 2000) } } catch (error) { console.error('Failed to trigger pipeline:', error) } finally { setTriggering(false) } } useEffect(() => { fetchStatus() // Auto-refresh every 30 seconds if a pipeline is running const interval = setInterval(() => { if (data?.pipelines.some(p => p.status === 'running')) { fetchStatus() } }, 30000) return () => clearInterval(interval) }, []) if (loading) { return (
Lade CI/CD Status...
) } const latestPipeline = data?.pipelines[0] return (
{/* Header */}

CI/CD Pipeline

{data?.status === 'online' ? ( ) : ( )}
{/* Content */}
{data?.status === 'offline' ? (
Woodpecker CI nicht erreichbar
) : latestPipeline ? (
{/* Latest Pipeline */}
#{latestPipeline.number}
{latestPipeline.branch} • {latestPipeline.commit}
{formatTimeAgo(latestPipeline.created)}
{formatDuration(latestPipeline.started, latestPipeline.finished)}
{/* Steps Progress */} {latestPipeline.steps.length > 0 && (
{latestPipeline.steps.map((step, i) => (
))}
)} {/* Expandable Steps */} {expanded && latestPipeline.steps.length > 0 && (
{latestPipeline.steps.map((step, i) => (
{step.name}
))}
)} {/* Actions */}
{/* Recent Pipelines */} {data.pipelines.length > 1 && (
Letzte Pipelines
{data.pipelines.slice(1, 4).map((p) => (
#{p.number} {p.branch} {formatTimeAgo(p.created)}
))}
)}
) : (
Keine Pipelines gefunden
)}
) }