Skip to content

Commit 98fdeb9

Browse files
committed
feat: implement pod index synchronization and optimize pod retrieval by node
1 parent 0ffd9d2 commit 98fdeb9

File tree

4 files changed

+124
-6
lines changed

4 files changed

+124
-6
lines changed

src/backgrounds/kuview.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
useGVKSyncHook,
66
useKubernetesAtomSyncHook,
77
useServiceEndpointSliceSyncHook,
8+
usePodIndexSyncHook,
89
} from "@/lib/kuviewAtom";
910

1011
import { useAtomValue } from "jotai";
@@ -60,6 +61,7 @@ export default function KuviewBackground() {
6061

6162
function SyncKubernetes() {
6263
useKubernetesAtomSyncHook();
64+
usePodIndexSyncHook(); // Added for Pod index synchronization
6365
const kubernetes = useAtomValue(kubernetesAtom);
6466
const gvks = Object.keys(kubernetes);
6567
const normalGVKs = gvks.filter(

src/components/block/node-detail.tsx

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { getStatusColor, getStatus } from "@/lib/status";
1414
import NodeResourceUsage from "./node-resource-usage";
1515
import PodsGrid from "./pods-grid";
1616
import NodePodList from "./node-pod-list";
17-
import { useKuview } from "@/hooks/useKuview";
17+
import { usePodsByNode } from "@/hooks/usePodsByNode";
1818
import PodsVolumeList from "./pods-volume-list";
1919

2020
interface NodeDetailProps {
@@ -24,12 +24,9 @@ interface NodeDetailProps {
2424

2525
export default function NodeDetail({ node, className }: NodeDetailProps) {
2626
const [jsonExpanded, setJsonExpanded] = useState(false);
27-
const podsData = useKuview("v1/Pod");
2827

29-
// Filter pods that belong to the selected node
30-
const nodePods = Object.values(podsData).filter(
31-
(pod) => pod.spec.nodeName === node.metadata.name,
32-
);
28+
// Use optimized hook to get pods for this node
29+
const nodePods = usePodsByNode(node.metadata.name);
3330

3431
return (
3532
<div className={cn("space-y-6", className)}>

src/hooks/usePodsByNode.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { useAtomValue } from "jotai";
2+
import { podsByNodeNameIndexAtom } from "@/lib/kuviewAtom";
3+
import { useMemo } from "react";
4+
import type { PodObject } from "@/lib/kuview";
5+
import { useKuview } from "@/hooks/useKuview";
6+
7+
export function usePodsByNode(nodeName: string | undefined): PodObject[] {
8+
const allPods = useKuview("v1/Pod");
9+
const podsByNodeIndex = useAtomValue(podsByNodeNameIndexAtom);
10+
11+
return useMemo(() => {
12+
if (!nodeName || !podsByNodeIndex[nodeName]) {
13+
return [];
14+
}
15+
16+
const podKeys = Object.keys(podsByNodeIndex[nodeName]);
17+
return podKeys.map((key) => allPods[key]).filter(Boolean);
18+
}, [allPods, podsByNodeIndex, nodeName]);
19+
}

src/lib/kuviewAtom.ts

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import type {
55
KuviewEvent,
66
KuviewExtra,
77
ServiceObject,
8+
PodObject,
89
} from "./kuview";
910
import { useEffect } from "react";
1011
import { calcStatus } from "./status";
@@ -21,6 +22,11 @@ export const kubernetesAtom = atom<
2122
"kuview.iwanhae.kr/v1/UserGroup": atom<Record<string, KubernetesObject>>({}), // virtual resource
2223
});
2324

25+
// Pod index atom: nodeName -> pod nn -> true
26+
export const podsByNodeNameIndexAtom = atom<
27+
Record<string, Record<string, true>>
28+
>({});
29+
2430
type ObjectAtom = PrimitiveAtom<
2531
Record<string /* NamespaceName */, KubernetesObject>
2632
>;
@@ -32,12 +38,46 @@ type _change_operation = {
3238

3339
const PENDING_CHANGES = new Map<string, _change_operation>();
3440

41+
// A dedicated queue for Pod index changes
42+
type PodIndexChange = {
43+
type: "DELETE" | "UPSERT";
44+
pod: PodObject;
45+
};
46+
47+
const POD_INDEX_CHANGES = new Map<string, PodIndexChange>();
48+
3549
function getObjectKey(object: KubernetesObject): string {
3650
// it is suprising that sometimes the uid is not unique, so we need to check the apiVersion and kind as well
3751
return `${object.apiVersion}/${object.kind}:${object.metadata.namespace}/${object.metadata.name}:${object.metadata.uid}`;
3852
}
3953

54+
// Function to update the Pod index
55+
function updatePodIndex(event: KuviewEvent) {
56+
if (event.object.kind !== "Pod" || event.object.apiVersion !== "v1") {
57+
return;
58+
}
59+
60+
const pod = event.object as PodObject;
61+
const nodeName = pod.spec?.nodeName;
62+
const nn = pod.metadata.namespace
63+
? `${pod.metadata.namespace}/${pod.metadata.name}`
64+
: pod.metadata.name;
65+
66+
// If nodeName doesn't exist, no need to index, so drop it here
67+
if (!nodeName || !nn) {
68+
return;
69+
}
70+
71+
POD_INDEX_CHANGES.set(nn, {
72+
type: event.type === "delete" ? "DELETE" : "UPSERT",
73+
pod,
74+
});
75+
}
76+
4077
export function handleEvent(event: KuviewEvent) {
78+
// Update Pod index (handled separately from the main logic)
79+
updatePodIndex(event);
80+
4181
const key = getObjectKey(event.object);
4282
switch (event.type) {
4383
case "create":
@@ -235,3 +275,63 @@ export function useServiceEndpointSliceSyncHook() {
235275
// eslint-disable-next-line react-hooks/exhaustive-deps
236276
}, []);
237277
}
278+
279+
// usePodIndexSyncHook: A hook to update the Pod index in real-time
280+
export function usePodIndexSyncHook() {
281+
const [podIndex, setPodIndex] = useAtom(podsByNodeNameIndexAtom);
282+
283+
const sync = () => {
284+
const changes: PodIndexChange[] = [];
285+
POD_INDEX_CHANGES.forEach((change, nn) => {
286+
changes.push(change);
287+
POD_INDEX_CHANGES.delete(nn);
288+
});
289+
290+
if (changes.length === 0) return;
291+
292+
const newPodIndex = { ...podIndex };
293+
let updated = false;
294+
295+
changes.forEach((change) => {
296+
const { type, pod } = change;
297+
const nodeName = pod.spec.nodeName!; // From here, we assume nodeName always exists
298+
const nn = pod.metadata.namespace
299+
? `${pod.metadata.namespace}/${pod.metadata.name}`
300+
: pod.metadata.name;
301+
302+
if (type === "DELETE") {
303+
if (newPodIndex[nodeName] && newPodIndex[nodeName][nn]) {
304+
const updatedNodePods = { ...newPodIndex[nodeName] };
305+
delete updatedNodePods[nn];
306+
if (Object.keys(updatedNodePods).length === 0) {
307+
delete newPodIndex[nodeName];
308+
} else {
309+
newPodIndex[nodeName] = updatedNodePods;
310+
}
311+
updated = true;
312+
}
313+
} else {
314+
// UPSERT
315+
if (!newPodIndex[nodeName]) {
316+
newPodIndex[nodeName] = {};
317+
}
318+
newPodIndex[nodeName] = {
319+
...newPodIndex[nodeName],
320+
[nn]: true,
321+
};
322+
updated = true;
323+
}
324+
});
325+
326+
if (updated) {
327+
console.log("Updated Pod index", changes.length, "operations");
328+
setPodIndex(newPodIndex);
329+
}
330+
};
331+
332+
useEffect(() => {
333+
const interval = setInterval(sync, DEBOUNCE_MS);
334+
return () => clearInterval(interval);
335+
// eslint-disable-next-line react-hooks/exhaustive-deps
336+
}, []);
337+
}

0 commit comments

Comments
 (0)