DockerStatus.tsx

72 lines
1 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>
    );
}