Skip to content

Commit e9086e2

Browse files
committed
feat: dummy implementation of pv and pvc
PROMPT, claude-4-sonet: Read the @README.md and add visualization of PV and PVC to this project
1 parent 996f7bb commit e9086e2

File tree

10 files changed

+901
-1
lines changed

10 files changed

+901
-1
lines changed

pkg/types/const.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ var ObjectSchemas = []client.Object{
1414
&v1.Namespace{TypeMeta: metav1.TypeMeta{APIVersion: "v1", Kind: "Namespace"}},
1515
&v1.Service{TypeMeta: metav1.TypeMeta{APIVersion: "v1", Kind: "Service"}},
1616
&v1.ServiceAccount{TypeMeta: metav1.TypeMeta{APIVersion: "v1", Kind: "ServiceAccount"}},
17+
&v1.PersistentVolume{TypeMeta: metav1.TypeMeta{APIVersion: "v1", Kind: "PersistentVolume"}},
18+
&v1.PersistentVolumeClaim{TypeMeta: metav1.TypeMeta{APIVersion: "v1", Kind: "PersistentVolumeClaim"}},
1719
&discoveryv1.EndpointSlice{TypeMeta: metav1.TypeMeta{APIVersion: "discovery.k8s.io/v1", Kind: "EndpointSlice"}},
1820
&rbacv1.ClusterRole{TypeMeta: metav1.TypeMeta{APIVersion: "rbac.authorization.k8s.io/v1", Kind: "ClusterRole"}},
1921
&rbacv1.ClusterRoleBinding{TypeMeta: metav1.TypeMeta{APIVersion: "rbac.authorization.k8s.io/v1", Kind: "ClusterRoleBinding"}},

src/App.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import Node from "./pages/node";
55
import Pod from "./pages/pod";
66
import Namespace from "./pages/namespace";
77
import Service from "./pages/service";
8+
import PV from "./pages/pv";
9+
import PVC from "./pages/pvc";
810
import KuviewBackground from "./backgrounds/kuview";
911
import Debug from "./pages/debug";
1012
import { PREFIX } from "./lib/const";
@@ -24,6 +26,8 @@ export default function Page() {
2426
<Route path={`${PREFIX}/pods`} component={Pod} />
2527
<Route path={`${PREFIX}/namespaces`} component={Namespace} />
2628
<Route path={`${PREFIX}/services`} component={Service} />
29+
<Route path={`${PREFIX}/pv`} component={PV} />
30+
<Route path={`${PREFIX}/pvc`} component={PVC} />
2731
<Route path={`${PREFIX}/usergroups`} component={UserGroupsPage} />
2832
<Route path={`${PREFIX}/debug`} component={Debug} />
2933
</Switch>

src/components/app-sidebar.tsx

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
import * as React from "react";
2-
import { Home, Server, Rocket, Layers, Network } from "lucide-react";
2+
import {
3+
Home,
4+
Server,
5+
Rocket,
6+
Layers,
7+
Network,
8+
Database,
9+
HardDrive,
10+
} from "lucide-react";
311
import { NavMain } from "@/components/nav-main";
412
import { Sidebar, SidebarContent } from "@/components/ui/sidebar";
513
import { PREFIX } from "@/lib/const";
@@ -32,6 +40,16 @@ const data = {
3240
url: `${PREFIX}/namespaces`,
3341
icon: Layers,
3442
},
43+
{
44+
title: "Persistent Volume",
45+
url: `${PREFIX}/pv`,
46+
icon: Database,
47+
},
48+
{
49+
title: "Volume Claims",
50+
url: `${PREFIX}/pvc`,
51+
icon: HardDrive,
52+
},
3553
],
3654
};
3755

src/components/block/pv-detail.tsx

Lines changed: 262 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,262 @@
1+
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
2+
import { Badge } from "@/components/ui/badge";
3+
import StatusBadge from "./status-badge";
4+
import Metadata from "./metadata";
5+
import type { PersistentVolumeObject } from "@/lib/kuview";
6+
import { formatBytes } from "@/lib/utils";
7+
import { Status } from "@/lib/status";
8+
9+
interface PVDetailProps {
10+
pv: PersistentVolumeObject;
11+
className?: string;
12+
}
13+
14+
export default function PVDetail({ pv, className }: PVDetailProps) {
15+
const getStatusColor = (phase?: string): Status => {
16+
switch (phase) {
17+
case "Available":
18+
return Status.Running;
19+
case "Bound":
20+
return Status.Running;
21+
case "Pending":
22+
return Status.Pending;
23+
case "Released":
24+
return Status.Warning;
25+
case "Failed":
26+
return Status.Error;
27+
default:
28+
return Status.Pending;
29+
}
30+
};
31+
32+
const getVolumeSource = (spec: PersistentVolumeObject["spec"]) => {
33+
if (spec.hostPath) return "HostPath";
34+
if (spec.nfs) return "NFS";
35+
if (spec.csi) return "CSI";
36+
return "Unknown";
37+
};
38+
39+
return (
40+
<div className={className}>
41+
<div className="space-y-6">
42+
{/* Header */}
43+
<div className="flex items-center justify-between">
44+
<h2 className="text-2xl font-bold">PersistentVolume Details</h2>
45+
<StatusBadge status={getStatusColor(pv.status?.phase)} />
46+
</div>
47+
48+
{/* Status */}
49+
<Card>
50+
<CardHeader>
51+
<CardTitle>Status</CardTitle>
52+
</CardHeader>
53+
<CardContent className="space-y-4">
54+
<div className="grid grid-cols-2 gap-4">
55+
<div>
56+
<p className="text-sm font-medium text-muted-foreground">
57+
Phase
58+
</p>
59+
<Badge
60+
variant={
61+
getStatusColor(pv.status?.phase) === Status.Running
62+
? "default"
63+
: "secondary"
64+
}
65+
>
66+
{pv.status?.phase || "Unknown"}
67+
</Badge>
68+
</div>
69+
<div>
70+
<p className="text-sm font-medium text-muted-foreground">
71+
Reclaim Policy
72+
</p>
73+
<Badge variant="outline">
74+
{pv.spec.persistentVolumeReclaimPolicy || "Retain"}
75+
</Badge>
76+
</div>
77+
</div>
78+
{pv.status?.message && (
79+
<div>
80+
<p className="text-sm font-medium text-muted-foreground">
81+
Message
82+
</p>
83+
<p className="text-sm">{pv.status.message}</p>
84+
</div>
85+
)}
86+
{pv.status?.reason && (
87+
<div>
88+
<p className="text-sm font-medium text-muted-foreground">
89+
Reason
90+
</p>
91+
<p className="text-sm">{pv.status.reason}</p>
92+
</div>
93+
)}
94+
</CardContent>
95+
</Card>
96+
97+
{/* Specification */}
98+
<Card>
99+
<CardHeader>
100+
<CardTitle>Specification</CardTitle>
101+
</CardHeader>
102+
<CardContent className="space-y-4">
103+
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
104+
<div>
105+
<p className="text-sm font-medium text-muted-foreground">
106+
Capacity
107+
</p>
108+
<p className="text-sm">
109+
{pv.spec.capacity?.storage
110+
? formatBytes(pv.spec.capacity.storage)
111+
: "N/A"}
112+
</p>
113+
</div>
114+
<div>
115+
<p className="text-sm font-medium text-muted-foreground">
116+
Volume Mode
117+
</p>
118+
<Badge variant="outline">
119+
{pv.spec.volumeMode || "Filesystem"}
120+
</Badge>
121+
</div>
122+
<div>
123+
<p className="text-sm font-medium text-muted-foreground">
124+
Storage Class
125+
</p>
126+
<p className="text-sm">{pv.spec.storageClassName || "None"}</p>
127+
</div>
128+
<div>
129+
<p className="text-sm font-medium text-muted-foreground">
130+
Volume Source
131+
</p>
132+
<Badge variant="outline">{getVolumeSource(pv.spec)}</Badge>
133+
</div>
134+
</div>
135+
<div>
136+
<p className="text-sm font-medium text-muted-foreground">
137+
Access Modes
138+
</p>
139+
<div className="flex gap-2 flex-wrap mt-1">
140+
{pv.spec.accessModes?.map((mode) => (
141+
<Badge key={mode} variant="outline" className="text-xs">
142+
{mode}
143+
</Badge>
144+
)) || (
145+
<span className="text-sm text-muted-foreground">None</span>
146+
)}
147+
</div>
148+
</div>
149+
</CardContent>
150+
</Card>
151+
152+
{/* Volume Source Details */}
153+
{pv.spec.csi && (
154+
<Card>
155+
<CardHeader>
156+
<CardTitle>CSI Volume Source</CardTitle>
157+
</CardHeader>
158+
<CardContent className="space-y-4">
159+
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
160+
<div>
161+
<p className="text-sm font-medium text-muted-foreground">
162+
Driver
163+
</p>
164+
<p className="text-sm font-mono">{pv.spec.csi.driver}</p>
165+
</div>
166+
<div>
167+
<p className="text-sm font-medium text-muted-foreground">
168+
Volume Handle
169+
</p>
170+
<p className="text-sm font-mono text-wrap break-all">
171+
{pv.spec.csi.volumeHandle}
172+
</p>
173+
</div>
174+
{pv.spec.csi.fsType && (
175+
<div>
176+
<p className="text-sm font-medium text-muted-foreground">
177+
FS Type
178+
</p>
179+
<p className="text-sm">{pv.spec.csi.fsType}</p>
180+
</div>
181+
)}
182+
<div>
183+
<p className="text-sm font-medium text-muted-foreground">
184+
Read Only
185+
</p>
186+
<Badge
187+
variant={pv.spec.csi.readOnly ? "secondary" : "outline"}
188+
>
189+
{pv.spec.csi.readOnly ? "Yes" : "No"}
190+
</Badge>
191+
</div>
192+
</div>
193+
</CardContent>
194+
</Card>
195+
)}
196+
197+
{pv.spec.hostPath && (
198+
<Card>
199+
<CardHeader>
200+
<CardTitle>HostPath Volume Source</CardTitle>
201+
</CardHeader>
202+
<CardContent className="space-y-4">
203+
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
204+
<div>
205+
<p className="text-sm font-medium text-muted-foreground">
206+
Path
207+
</p>
208+
<p className="text-sm font-mono">{pv.spec.hostPath.path}</p>
209+
</div>
210+
{pv.spec.hostPath.type && (
211+
<div>
212+
<p className="text-sm font-medium text-muted-foreground">
213+
Type
214+
</p>
215+
<Badge variant="outline">{pv.spec.hostPath.type}</Badge>
216+
</div>
217+
)}
218+
</div>
219+
</CardContent>
220+
</Card>
221+
)}
222+
223+
{pv.spec.nfs && (
224+
<Card>
225+
<CardHeader>
226+
<CardTitle>NFS Volume Source</CardTitle>
227+
</CardHeader>
228+
<CardContent className="space-y-4">
229+
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
230+
<div>
231+
<p className="text-sm font-medium text-muted-foreground">
232+
Server
233+
</p>
234+
<p className="text-sm font-mono">{pv.spec.nfs.server}</p>
235+
</div>
236+
<div>
237+
<p className="text-sm font-medium text-muted-foreground">
238+
Path
239+
</p>
240+
<p className="text-sm font-mono">{pv.spec.nfs.path}</p>
241+
</div>
242+
<div>
243+
<p className="text-sm font-medium text-muted-foreground">
244+
Read Only
245+
</p>
246+
<Badge
247+
variant={pv.spec.nfs.readOnly ? "secondary" : "outline"}
248+
>
249+
{pv.spec.nfs.readOnly ? "Yes" : "No"}
250+
</Badge>
251+
</div>
252+
</div>
253+
</CardContent>
254+
</Card>
255+
)}
256+
257+
{/* Metadata */}
258+
<Metadata metadata={pv.metadata} />
259+
</div>
260+
</div>
261+
);
262+
}

0 commit comments

Comments
 (0)