158 lines
4.8 KiB
JavaScript
158 lines
4.8 KiB
JavaScript
import React, { useState, useEffect } from 'react';
|
|
import {
|
|
ScrollText,
|
|
RefreshCw,
|
|
Clock,
|
|
User,
|
|
ChevronDown,
|
|
ChevronUp,
|
|
CheckCircle2,
|
|
XCircle,
|
|
Loader,
|
|
} from 'lucide-react';
|
|
import { getDeployments } from '../utils/api';
|
|
import StatusBadge from '../components/StatusBadge';
|
|
|
|
function formatDate(dateStr) {
|
|
if (!dateStr) return 'N/A';
|
|
try {
|
|
return new Date(dateStr).toLocaleString('fr-FR');
|
|
} catch {
|
|
return dateStr;
|
|
}
|
|
}
|
|
|
|
function DeploymentEntry({ deployment }) {
|
|
const [expanded, setExpanded] = useState(false);
|
|
|
|
const statusIcon = {
|
|
success: <CheckCircle2 className="w-5 h-5 text-emerald-400" />,
|
|
failed: <XCircle className="w-5 h-5 text-red-400" />,
|
|
running: <Loader className="w-5 h-5 text-amber-400 animate-spin" />,
|
|
};
|
|
|
|
return (
|
|
<div className="card overflow-hidden">
|
|
<div
|
|
className="p-4 flex items-center justify-between cursor-pointer hover:bg-dark-700/50 transition-colors"
|
|
onClick={() => setExpanded(!expanded)}
|
|
>
|
|
<div className="flex items-center gap-4">
|
|
{statusIcon[deployment.status] || statusIcon.running}
|
|
<div>
|
|
<p className="text-sm font-medium text-gray-200">
|
|
{deployment.message}
|
|
</p>
|
|
<div className="flex items-center gap-4 mt-1">
|
|
<span className="text-xs text-gray-500 flex items-center gap-1">
|
|
<Clock className="w-3 h-3" />
|
|
{formatDate(deployment.timestamp)}
|
|
</span>
|
|
{deployment.user && (
|
|
<span className="text-xs text-gray-500 flex items-center gap-1">
|
|
<User className="w-3 h-3" />
|
|
{deployment.user}
|
|
</span>
|
|
)}
|
|
{deployment.duration && (
|
|
<span className="text-xs text-gray-500">
|
|
Durée: {deployment.duration}s
|
|
</span>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className="flex items-center gap-3">
|
|
<StatusBadge status={deployment.status} />
|
|
{expanded ? (
|
|
<ChevronUp className="w-4 h-4 text-gray-500" />
|
|
) : (
|
|
<ChevronDown className="w-4 h-4 text-gray-500" />
|
|
)}
|
|
</div>
|
|
</div>
|
|
{expanded && deployment.logs && (
|
|
<div className="px-4 pb-4 border-t border-dark-700">
|
|
<div className="bg-dark-950 rounded-lg p-4 mt-3 max-h-96 overflow-auto">
|
|
<pre className="log-viewer text-xs text-gray-400">
|
|
{deployment.logs}
|
|
</pre>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default function DeploymentsPage() {
|
|
const [deployments, setDeployments] = useState([]);
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
const fetchDeployments = async () => {
|
|
setLoading(true);
|
|
try {
|
|
const res = await getDeployments();
|
|
setDeployments(res.data);
|
|
} catch (err) {
|
|
console.error('Erreur chargement déploiements:', err);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
fetchDeployments();
|
|
const interval = setInterval(fetchDeployments, 10000);
|
|
return () => clearInterval(interval);
|
|
}, []);
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
{/* Header */}
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<h2 className="text-2xl font-bold text-white flex items-center gap-3">
|
|
<ScrollText className="w-7 h-7 text-primary-400" />
|
|
Historique des déploiements
|
|
</h2>
|
|
<p className="text-gray-400 mt-1">
|
|
Consultez les logs des derniers déploiements
|
|
</p>
|
|
</div>
|
|
<button
|
|
onClick={fetchDeployments}
|
|
className="btn-secondary flex items-center gap-2"
|
|
>
|
|
<RefreshCw className={`w-4 h-4 ${loading ? 'animate-spin' : ''}`} />
|
|
Rafraîchir
|
|
</button>
|
|
</div>
|
|
|
|
{/* Deployments List */}
|
|
{loading && deployments.length === 0 ? (
|
|
<div className="card p-12 text-center">
|
|
<RefreshCw className="w-8 h-8 text-gray-600 mx-auto mb-4 animate-spin" />
|
|
<p className="text-gray-400">Chargement...</p>
|
|
</div>
|
|
) : deployments.length === 0 ? (
|
|
<div className="card p-12 text-center">
|
|
<ScrollText className="w-12 h-12 text-gray-600 mx-auto mb-4" />
|
|
<h3 className="text-lg font-medium text-gray-400">
|
|
Aucun déploiement
|
|
</h3>
|
|
<p className="text-gray-600 mt-2">
|
|
L'historique des déploiements apparaîtra ici après le premier
|
|
redéploiement
|
|
</p>
|
|
</div>
|
|
) : (
|
|
<div className="space-y-3">
|
|
{deployments.map((d) => (
|
|
<DeploymentEntry key={d.id} deployment={d} />
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|