DockerStatus.tsx
72 lines1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
import { useState, useEffect } from 'react';
interface ServiceStatus {
id: string;
name: string;
slug: string;
status: string;
port: number;
domain: string;
}
function StatusDot({ status }: { status: string }) {
const color = status === 'running' ? 'bg-success' :
status === 'building' ? 'bg-warning animate-pulse' :
status === 'failed' ? 'bg-error' : 'bg-base-content/20';
return <span className={`w-2 h-2 rounded-full ${color}`} />;
}
export function DockerStatus() {
const [services, setServices] = useState<ServiceStatus[] | null>(null);
useEffect(() => {
const fetchServices = () => {
fetch('/api/services/status')
.then(r => r.json())
.then(data => setServices(data || []))
.catch(() => {});
};
fetchServices();
const interval = setInterval(fetchServices, 5000);
return () => clearInterval(interval);
}, []);
if (services === null) {
return (
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-3">
{[0, 1, 2].map(i => (
<div key={i} className="h-24 rounded-xl bg-base-100/30 animate-pulse" />
))}
</div>
);
}
if (services.length === 0) {
return null;
}
return (
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-3">
{services.map(svc => (
<a key={svc.id} href={`/services/${svc.id}`}
className="rounded-xl bg-base-100/50 backdrop-blur-sm border border-white/[0.06] p-4 hover:border-white/[0.12] hover:bg-base-100/70 transition-all duration-300">
<div className="flex items-center gap-2 mb-2">
<StatusDot status={svc.status} />
<span className="font-semibold text-sm truncate">{svc.name}</span>
<span className={`badge badge-xs ml-auto ${
svc.status === 'running' ? 'badge-success' :
svc.status === 'failed' ? 'badge-error' :
svc.status === 'building' ? 'badge-warning' : 'badge-ghost'
}`}>{svc.status}</span>
</div>
<div className="text-xs text-base-content/30 font-mono">
svc-{svc.slug} · :{svc.port}
</div>
{svc.domain && (
<div className="text-xs text-base-content/40 mt-1 truncate">{svc.domain}</div>
)}
</a>
))}
</div>
);
}