File size: 4,882 Bytes
d53c2af
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135

"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>
  );
}