feat: découverte dynamique apps - portail dynamique - fix URLs
This commit is contained in:
164
frontend/src/pages/DockerPage.jsx
Normal file
164
frontend/src/pages/DockerPage.jsx
Normal file
@@ -0,0 +1,164 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Container, RefreshCw, Clock } from 'lucide-react';
|
||||
import { getContainers } from '../utils/api';
|
||||
|
||||
function formatDate(dateStr) {
|
||||
if (!dateStr) return 'N/A';
|
||||
try {
|
||||
return new Date(dateStr).toLocaleString('fr-FR');
|
||||
} catch {
|
||||
return dateStr;
|
||||
}
|
||||
}
|
||||
|
||||
export default function DockerPage() {
|
||||
const [containers, setContainers] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
const fetchContainers = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const res = await getContainers();
|
||||
setContainers(res.data);
|
||||
} catch (err) {
|
||||
console.error('Erreur chargement conteneurs:', err);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchContainers();
|
||||
const interval = setInterval(fetchContainers, 15000);
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
|
||||
const stateColors = {
|
||||
running: 'text-emerald-400 bg-emerald-500/10 border-emerald-500/20',
|
||||
exited: 'text-red-400 bg-red-500/10 border-red-500/20',
|
||||
created: 'text-amber-400 bg-amber-500/10 border-amber-500/20',
|
||||
restarting: 'text-amber-400 bg-amber-500/10 border-amber-500/20',
|
||||
};
|
||||
|
||||
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">
|
||||
<Container className="w-7 h-7 text-primary-400" />
|
||||
Conteneurs Docker
|
||||
</h2>
|
||||
<p className="text-gray-400 mt-1">
|
||||
Vue d'ensemble de tous les conteneurs sur le serveur
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={fetchContainers}
|
||||
className="btn-secondary flex items-center gap-2"
|
||||
>
|
||||
<RefreshCw className={`w-4 h-4 ${loading ? 'animate-spin' : ''}`} />
|
||||
Rafraîchir
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Containers Table */}
|
||||
<div className="card overflow-hidden">
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full">
|
||||
<thead>
|
||||
<tr className="border-b border-dark-700">
|
||||
<th className="text-left text-xs font-medium text-gray-500 uppercase tracking-wider px-6 py-4">
|
||||
Nom
|
||||
</th>
|
||||
<th className="text-left text-xs font-medium text-gray-500 uppercase tracking-wider px-6 py-4">
|
||||
Image
|
||||
</th>
|
||||
<th className="text-left text-xs font-medium text-gray-500 uppercase tracking-wider px-6 py-4">
|
||||
État
|
||||
</th>
|
||||
<th className="text-left text-xs font-medium text-gray-500 uppercase tracking-wider px-6 py-4">
|
||||
Statut
|
||||
</th>
|
||||
<th className="text-left text-xs font-medium text-gray-500 uppercase tracking-wider px-6 py-4">
|
||||
ID
|
||||
</th>
|
||||
<th className="text-left text-xs font-medium text-gray-500 uppercase tracking-wider px-6 py-4">
|
||||
Créé
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-dark-700">
|
||||
{loading && containers.length === 0 ? (
|
||||
<tr>
|
||||
<td colSpan="6" className="px-6 py-8 text-center">
|
||||
<RefreshCw className="w-6 h-6 text-gray-600 mx-auto animate-spin" />
|
||||
</td>
|
||||
</tr>
|
||||
) : containers.length === 0 ? (
|
||||
<tr>
|
||||
<td
|
||||
colSpan="6"
|
||||
className="px-6 py-8 text-center text-gray-500"
|
||||
>
|
||||
Aucun conteneur trouvé
|
||||
</td>
|
||||
</tr>
|
||||
) : (
|
||||
containers.map((c) => (
|
||||
<tr
|
||||
key={c.id}
|
||||
className="hover:bg-dark-700/30 transition-colors"
|
||||
>
|
||||
<td className="px-6 py-3">
|
||||
<span className="text-sm font-medium text-gray-200">
|
||||
{c.names.join(', ')}
|
||||
</span>
|
||||
</td>
|
||||
<td className="px-6 py-3">
|
||||
<code className="text-xs text-gray-400 bg-dark-700 px-2 py-0.5 rounded">
|
||||
{c.image}
|
||||
</code>
|
||||
</td>
|
||||
<td className="px-6 py-3">
|
||||
<span
|
||||
className={`inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium border ${
|
||||
stateColors[c.state] || stateColors.created
|
||||
}`}
|
||||
>
|
||||
<span
|
||||
className={`w-1.5 h-1.5 rounded-full mr-1.5 ${
|
||||
c.state === 'running'
|
||||
? 'bg-emerald-400'
|
||||
: c.state === 'exited'
|
||||
? 'bg-red-400'
|
||||
: 'bg-amber-400'
|
||||
}`}
|
||||
/>
|
||||
{c.state}
|
||||
</span>
|
||||
</td>
|
||||
<td className="px-6 py-3">
|
||||
<span className="text-xs text-gray-400">{c.status}</span>
|
||||
</td>
|
||||
<td className="px-6 py-3">
|
||||
<code className="text-xs text-gray-500 font-mono">
|
||||
{c.id}
|
||||
</code>
|
||||
</td>
|
||||
<td className="px-6 py-3">
|
||||
<span className="text-xs text-gray-500 flex items-center gap-1">
|
||||
<Clock className="w-3 h-3" />
|
||||
{formatDate(c.created)}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
))
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user