Skip to content
File

Blob: src/client/islands/repo-admin/RepoOverviewCard.tsx

typescript147 lines
1import { useState } from "react";
2import { Clipboard } from "lucide-react";
3import { shortRefName } from "@/shared/git/ref-display";
4import { Card } from "@/client/components/ui/card";
5import { formatSampleBytes, shortValue } from "./format";
6import type { AdminState } from "./types";
7 
8export type RepoOverviewCardProps = {
9 storageSize: string;
10 packCount: number;
11 supersededPackCount: number;
12 compactionStatus: string;
13 state: AdminState;
14 head?: { target?: string; unborn?: boolean };
15 branchCount: number;
16 tagCount: number;
17};
18 
19export function RepoOverviewCard({
20 storageSize,
21 packCount,
22 supersededPackCount,
23 compactionStatus,
24 state,
25 head,
26 branchCount,
27 tagCount,
28}: RepoOverviewCardProps) {
29 const [copiedDoId, setCopiedDoId] = useState(false);
30 const activePackCount = Array.isArray(state.activePacks) ? state.activePacks.length : packCount;
31 
32 async function copyDoId(doId: string) {
33 try {
34 await navigator.clipboard.writeText(doId);
35 setCopiedDoId(true);
36 window.setTimeout(() => setCopiedDoId(false), 1200);
37 } catch (error) {
38 window.alert(`Failed to copy DO ID: ${String(error)}`);
39 }
40 }
41 
42 return (
43 <Card>
44 <h2 className="mb-4 text-xl font-semibold">Repository Overview</h2>
45 <div className="grid grid-cols-2 gap-x-6 gap-y-3 md:grid-cols-3 lg:grid-cols-4">
46 <div>
47 <div className="mb-1 text-xs uppercase tracking-wide text-zinc-500 dark:text-zinc-400">
48 R2 Packs Storage
49 </div>
50 <div className="font-mono text-sm">{storageSize || "0 bytes"}</div>
51 </div>
52 <div>
53 <div className="mb-1 text-xs uppercase tracking-wide text-zinc-500 dark:text-zinc-400">
54 Active Packs
55 </div>
56 <div className="font-mono text-sm">
57 {activePackCount}
58 {supersededPackCount > 0 ? (
59 <span className="ml-1 text-xs text-zinc-500">({supersededPackCount} superseded)</span>
60 ) : null}
61 </div>
62 </div>
63 {state?.meta?.doId ? (
64 <div>
65 <div className="mb-1 text-xs uppercase tracking-wide text-zinc-500 dark:text-zinc-400">
66 Durable Object ID
67 </div>
68 <div className="relative font-mono text-sm">
69 <span title={state.meta.doId}>{shortValue(String(state.meta.doId), 12)}</span>
70 <button
71 type="button"
72 className="absolute -top-0.5 ml-2 rounded border border-zinc-300 p-1 transition-colors hover:bg-zinc-100 dark:border-zinc-700 dark:hover:bg-zinc-800"
73 title="Copy DO ID"
74 aria-label="Copy DO ID"
75 style={{ marginLeft: "0.5rem", top: "-2px" }}
76 onClick={() => void copyDoId(String(state.meta?.doId || ""))}
77 >
78 <Clipboard className="block h-4 w-4" aria-hidden="true" />
79 </button>
80 <span className="invisible">{copiedDoId ? "copied" : "___copy"}</span>
81 </div>
82 </div>
83 ) : null}
84 {state.looseR2SampleBytes ? (
85 <div>
86 <div className="mb-1 text-xs uppercase tracking-wide text-zinc-500 dark:text-zinc-400">
87 R2 Loose (sample)
88 </div>
89 <div className="font-mono text-sm">
90 {formatSampleBytes(state.looseR2SampleBytes)}
91 <span className="ml-1 text-xs text-zinc-500">
92 ({state.looseR2SampleCount || 0} objs{state.looseR2Truncated ? ", truncated" : ""})
93 </span>
94 </div>
95 </div>
96 ) : null}
97 {state.dbSizeBytes ? (
98 <div>
99 <div className="mb-1 text-xs uppercase tracking-wide text-zinc-500 dark:text-zinc-400">
100 Durable Object Storage
101 </div>
102 <div className="font-mono text-sm">{formatSampleBytes(state.dbSizeBytes)}</div>
103 </div>
104 ) : null}
105 <div>
106 <div className="mb-1 text-xs uppercase tracking-wide text-zinc-500 dark:text-zinc-400">
107 Receive Lease
108 </div>
109 <div className="font-mono text-sm">{state.receiveLease ? "Active" : "Idle"}</div>
110 </div>
111 <div>
112 <div className="mb-1 text-xs uppercase tracking-wide text-zinc-500 dark:text-zinc-400">
113 Compaction
114 </div>
115 <div className="font-mono text-sm text-accent-600 dark:text-accent-400">
116 {compactionStatus}
117 </div>
118 </div>
119 <div>
120 <div className="mb-1 text-xs uppercase tracking-wide text-zinc-500 dark:text-zinc-400">
121 Catalog Version
122 </div>
123 <div className="font-mono text-sm">
124 {typeof state.packCatalogVersion === "number" ? state.packCatalogVersion : "n/a"}
125 </div>
126 </div>
127 <div>
128 <div className="mb-1 text-xs uppercase tracking-wide text-zinc-500 dark:text-zinc-400">
129 HEAD
130 </div>
131 <div className="font-mono text-sm">
132 {head?.target ? shortRefName(String(head.target)) : head?.unborn ? "unborn" : "unknown"}
133 </div>
134 </div>
135 <div>
136 <div className="mb-1 text-xs uppercase tracking-wide text-zinc-500 dark:text-zinc-400">
137 Branches/Tags
138 </div>
139 <div className="font-mono text-sm">
140 {branchCount}/{tagCount}
141 </div>
142 </div>
143 </div>
144 </Card>
145 );
146}