SpaceProbe1 / frontend /src /components /dashboard /RepositoryCard.tsx
a9's picture
Upload 23 files
d53c2af verified
"use client";
import { useEffect, useState } from "react";
import { Repository, RepoStatus, AggregatedMetrics, fetchRepoStatus, fetchRepoMetrics } from "@/lib/api";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { StatusBadge } from "./StatusBadge";
import { MetricChart } from "./MetricChart";
import { Button } from "@/components/ui/button";
import { Trash2, ExternalLink, Box, RefreshCw } from "lucide-react";
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog";
interface RepositoryCardProps {
repo: Repository;
onRemove: (id: string) => void;
timeRange: string;
}
export function RepositoryCard({ repo, onRemove, timeRange }: RepositoryCardProps) {
const [status, setStatus] = useState<RepoStatus | null>(null);
const [metrics, setMetrics] = useState<AggregatedMetrics | null>(null);
const [loading, setLoading] = useState(true);
const pollData = async () => {
try {
const [newStatus, newMetrics] = await Promise.all([
fetchRepoStatus(repo.id),
fetchRepoMetrics(repo.id, timeRange)
]);
setStatus(newStatus);
setMetrics(newMetrics);
} catch (err) {
console.error("Failed to poll card data", err);
} finally {
setLoading(false);
}
};
useEffect(() => {
pollData();
const interval = setInterval(pollData, 10000); // Poll every 10s
return () => clearInterval(interval);
}, [repo.id, timeRange]);
return (
<Card className="group border-border/50 overflow-hidden bg-card/40 transition-all hover:bg-card/60">
<CardHeader className="flex flex-row items-start justify-between space-y-0 p-6">
<div className="flex gap-4">
<div className="mt-1 flex h-10 w-10 items-center justify-center rounded-lg bg-primary/10 text-primary">
<Box className="h-6 w-6" />
</div>
<div className="space-y-1">
<CardTitle className="text-xl font-semibold flex items-center gap-2">
{repo.repo}
<a
href={`https://huggingface.co/spaces/${repo.namespace}/${repo.repo}`}
target="_blank"
rel="noreferrer"
className="text-muted-foreground hover:text-primary transition-colors"
>
<ExternalLink className="h-4 w-4" />
</a>
</CardTitle>
<p className="text-sm text-muted-foreground">{repo.namespace}</p>
</div>
</div>
<div className="flex items-center gap-2">
{status ? (
<>
<StatusBadge type="status" value={status.state} />
<StatusBadge type="stage" value={status.stage} />
</>
) : (
<div className="h-6 w-32 bg-secondary animate-pulse rounded-full" />
)}
<AlertDialog>
<AlertDialogTrigger asChild>
<Button variant="ghost" size="icon" className="h-8 w-8 text-muted-foreground hover:text-destructive">
<Trash2 className="h-4 w-4" />
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Delete Monitoring?</AlertDialogTitle>
<AlertDialogDescription>
This will stop monitoring the repository <strong>{repo.namespace}/{repo.repo}</strong>. This action cannot be undone.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction onClick={() => onRemove(repo.id)} className="bg-destructive text-destructive-foreground hover:bg-destructive/90">
Delete
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</div>
</CardHeader>
<CardContent className="p-6 pt-0">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<MetricChart
title="CPU Usage"
metrics={metrics?.cpu}
timestamps={metrics?.timestamps}
unit="%"
colorVar="hsl(var(--chart-1))"
gradientId={`cpu-gradient-${repo.id}`}
loading={loading}
/>
<MetricChart
title="Memory Usage"
metrics={metrics?.memory}
timestamps={metrics?.timestamps}
unit="%"
colorVar="hsl(var(--chart-2))"
gradientId={`mem-gradient-${repo.id}`}
loading={loading}
/>
</div>
</CardContent>
</Card>
);
}