Initial commit

This commit is contained in:
2025-07-10 21:59:56 -04:00
commit 4dec00d283
35 changed files with 10272 additions and 0 deletions

View File

@@ -0,0 +1,194 @@
import React, { useState, useEffect, useCallback, useMemo } from 'react';
import { formatDistanceToNow } from 'date-fns';
import { X, CheckCircle, XCircle, Clock, AlertCircle, Play, Pause, HelpCircle, Ban, Timer, GitBranch, User, ExternalLink } from 'lucide-react';
import { WorkflowRun } from '../types/github';
import { ApiService } from '../services/api';
interface WorkflowRunsModalProps {
isOpen: boolean;
onClose: () => void;
repository: {
owner: string;
name: string;
} | null;
}
const getStatusIcon = (status: string, conclusion: string | null) => {
switch (status) {
case 'in_progress':
return <Play className="w-4 h-4 text-ctp-blue" />;
case 'queued':
case 'requested':
case 'pending':
return <Clock className="w-4 h-4 text-ctp-overlay1" />;
case 'waiting':
return <Pause className="w-4 h-4 text-ctp-rosewater" />;
case 'completed':
switch (conclusion) {
case 'success':
return <CheckCircle className="w-4 h-4 text-ctp-green" />;
case 'failure':
return <XCircle className="w-4 h-4 text-ctp-red" />;
case 'cancelled':
return <Ban className="w-4 h-4 text-ctp-overlay1" />;
case 'action_required':
return <AlertCircle className="w-4 h-4 text-ctp-yellow" />;
case 'neutral':
return <HelpCircle className="w-4 h-4 text-ctp-overlay1" />;
case 'skipped':
return <AlertCircle className="w-4 h-4 text-ctp-overlay0" />;
case 'stale':
return <Clock className="w-4 h-4 text-ctp-overlay1" />;
case 'timed_out':
return <Timer className="w-4 h-4 text-ctp-yellow" />;
default:
return <HelpCircle className="w-4 h-4 text-ctp-overlay1" />;
}
default:
return <HelpCircle className="w-4 h-4 text-ctp-overlay1" />;
}
};
const getStatusText = (status: string, conclusion: string | null) => {
if (status === 'completed' && conclusion) {
return conclusion.charAt(0).toUpperCase() + conclusion.slice(1);
}
return status.charAt(0).toUpperCase() + status.slice(1);
};
export const WorkflowRunsModal: React.FC<WorkflowRunsModalProps> = ({ isOpen, onClose, repository }) => {
const [runs, setRuns] = useState<WorkflowRun[]>([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const apiService = useMemo(() => new ApiService(), []);
const handleBackdropClick = (e: React.MouseEvent) => {
if (e.target === e.currentTarget) {
onClose();
}
};
const fetchWorkflowRuns = useCallback(async () => {
if (!repository) return;
setLoading(true);
setError(null);
try {
const response = await apiService.getRepositoryWorkflowRuns(repository.owner, repository.name);
setRuns(response);
} catch (error) {
console.error('Error fetching workflow runs:', error);
setError('Failed to fetch workflow runs');
} finally {
setLoading(false);
}
}, [repository, apiService]);
useEffect(() => {
if (isOpen && repository) {
fetchWorkflowRuns();
}
}, [isOpen, repository, fetchWorkflowRuns]);
if (!isOpen) return null;
return (
<div
className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-50"
onClick={handleBackdropClick}
>
<div className="bg-ctp-base rounded-lg shadow-xl max-w-4xl w-full max-h-[90vh] overflow-y-auto">
<div className="flex items-center justify-between p-6 border-b border-ctp-surface0">
<div>
<h2 className="text-xl font-semibold text-ctp-text">
{repository?.owner}/{repository?.name}
</h2>
<p className="text-sm text-ctp-subtext0">Recent workflow runs</p>
</div>
<button
onClick={onClose}
className="text-ctp-subtext0 hover:text-ctp-text transition-colors"
>
<X className="w-6 h-6" />
</button>
</div>
<div className="p-6">
{loading ? (
<div className="flex justify-center py-12">
<Clock className="w-8 h-8 animate-spin text-ctp-blue" />
</div>
) : error ? (
<div className="text-center py-12">
<p className="text-ctp-red">{error}</p>
</div>
) : runs.length === 0 ? (
<div className="text-center py-12">
<p className="text-ctp-subtext0">No workflow runs found</p>
</div>
) : (
<div className="space-y-4">
{runs.map((run) => (
<div
key={run.id}
className="bg-ctp-mantle border border-ctp-surface0 rounded-lg p-4 hover:shadow-sm transition-shadow"
>
<div className="flex items-start justify-between mb-3">
<div className="flex items-center space-x-3">
{getStatusIcon(run.status, run.conclusion)}
<div>
<h3 className="font-medium text-ctp-text">{run.display_title}</h3>
<p className="text-sm text-ctp-subtext0">
{getStatusText(run.status, run.conclusion)} #{run.run_number}
</p>
</div>
</div>
<a
href={run.html_url}
target="_blank"
rel="noopener noreferrer"
className="text-ctp-blue hover:text-ctp-lavender transition-colors"
>
<ExternalLink className="w-4 h-4" />
</a>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 text-sm">
<div className="flex items-center space-x-2">
<GitBranch className="w-4 h-4 text-ctp-overlay1" />
<span className="text-ctp-subtext0">Branch:</span>
<span className="text-ctp-text font-mono">{run.head_branch}</span>
</div>
<div className="flex items-center space-x-2">
<User className="w-4 h-4 text-ctp-overlay1" />
<span className="text-ctp-subtext0">Author:</span>
<span className="text-ctp-text">{run.actor.login}</span>
</div>
</div>
<div className="mt-3 text-sm">
<p className="text-ctp-subtext0 mb-1">Commit:</p>
<div className="flex items-center space-x-2">
{run.head_commit && (
<>
<span className="font-mono text-xs bg-ctp-surface0 text-ctp-text px-2 py-1 rounded">
{run.head_commit.id.substring(0, 7)}
</span>
<span className="text-ctp-text truncate">{run.head_commit.message}</span>
</>
)}
</div>
</div>
<div className="mt-3 text-xs text-ctp-subtext1">
{formatDistanceToNow(new Date(run.created_at), { addSuffix: true })}
</div>
</div>
))}
</div>
)}
</div>
</div>
</div>
);
};