Skip to content

Commit 19882ff

Browse files
committed
Add pod controller rail
1 parent 5e8e10c commit 19882ff

2 files changed

Lines changed: 154 additions & 0 deletions

File tree

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
import { useMemo } from "react";
2+
import { FileText, GitCommitHorizontal } from "lucide-react";
3+
import { ownsPod } from "../lib/resourceRelationships";
4+
import type { ResourceRow } from "../types/kube";
5+
import { StatusDot } from "./status";
6+
7+
type PodControllerRailProps = {
8+
onOpenResource: (id: string, intent?: "logs" | null) => void;
9+
pod: ResourceRow;
10+
resources: ResourceRow[];
11+
};
12+
13+
const parentOwnerKinds = new Set(["ReplicaSet", "Job"]);
14+
15+
export function PodControllerRail({ onOpenResource, pod, resources }: PodControllerRailProps) {
16+
const ownerRef = useMemo(() => resourceOwnerRef(pod), [pod]);
17+
const owner = useMemo(
18+
() => ownerRef
19+
? resources.find((item) => item.kind === ownerRef.kind && item.namespace === pod.namespace && item.name === ownerRef.name)
20+
: undefined,
21+
[ownerRef, pod.namespace, resources],
22+
);
23+
const parentRef = useMemo(() => owner && parentOwnerKinds.has(owner.kind) ? resourceOwnerRef(owner) : null, [owner]);
24+
const parent = useMemo(
25+
() => parentRef
26+
? resources.find((item) => item.kind === parentRef.kind && item.namespace === pod.namespace && item.name === parentRef.name)
27+
: undefined,
28+
[parentRef, pod.namespace, resources],
29+
);
30+
const siblingPods = useMemo(
31+
() => owner
32+
? resources
33+
.filter((item) => item.kind === "Pod" && item.id !== pod.id && item.namespace === pod.namespace && ownsPod(owner, item))
34+
.sort(compareRuntimePods)
35+
: [],
36+
[owner, pod.id, pod.namespace, resources],
37+
);
38+
39+
if (!ownerRef) {
40+
return null;
41+
}
42+
43+
const visibleSiblings = siblingPods.slice(0, parent ? 2 : 3);
44+
const tone = parent?.status ?? owner?.status ?? "warning";
45+
const controllerLabel = parent ? `${ownerRef.kind} -> ${parent.kind}` : ownerRef.kind;
46+
47+
return (
48+
<section className={`workload-pod-rail service-backend-rail owner-rail ${tone}`} aria-label="Pod controller">
49+
<header>
50+
<span>
51+
<GitCommitHorizontal size={15} />
52+
Controller
53+
</span>
54+
<strong>{controllerLabel}</strong>
55+
<small>{siblingPods.length ? `${siblingPods.length} siblings` : pod.owner}</small>
56+
</header>
57+
<div>
58+
{owner ? (
59+
<LinkedResourceTile meta="owner" resource={owner} onOpenResource={onOpenResource} />
60+
) : (
61+
<div className="service-backend-empty">
62+
<span>Owner missing</span>
63+
<strong>{pod.owner} is not in this snapshot.</strong>
64+
</div>
65+
)}
66+
{parent ? <LinkedResourceTile meta="parent" resource={parent} onOpenResource={onOpenResource} /> : null}
67+
{visibleSiblings.map((sibling) => (
68+
<LinkedPodTile key={sibling.id} meta={sibling.nodeName || sibling.namespace} pod={sibling} onOpenResource={onOpenResource} />
69+
))}
70+
</div>
71+
</section>
72+
);
73+
}
74+
75+
function resourceOwnerRef(resource: ResourceRow) {
76+
const [kind, name] = resource.owner.split("/", 2);
77+
if (!kind || !name) {
78+
return null;
79+
}
80+
81+
return { kind, name };
82+
}
83+
84+
function LinkedResourceTile({
85+
meta,
86+
onOpenResource,
87+
resource,
88+
}: {
89+
meta: string;
90+
onOpenResource: (id: string, intent?: "logs" | null) => void;
91+
resource: ResourceRow;
92+
}) {
93+
return (
94+
<button className={resource.status} type="button" onClick={() => onOpenResource(resource.id)}>
95+
<StatusDot state={resource.status} />
96+
<strong title={resource.name}>{resource.name}</strong>
97+
<em title={resource.diagnostic || resource.status}>{resource.diagnostic || resource.status}</em>
98+
<small title={resource.kind}>{resource.kind}</small>
99+
<small>{meta}</small>
100+
</button>
101+
);
102+
}
103+
104+
function LinkedPodTile({
105+
meta,
106+
onOpenResource,
107+
pod,
108+
}: {
109+
meta: string;
110+
onOpenResource: (id: string, intent?: "logs" | null) => void;
111+
pod: ResourceRow;
112+
}) {
113+
return (
114+
<article className={`workload-pod-tile ${pod.status}`}>
115+
<button className="workload-pod-open" type="button" onClick={() => onOpenResource(pod.id)}>
116+
<StatusDot state={pod.status} />
117+
<strong title={pod.name}>{pod.name}</strong>
118+
<em title={pod.diagnostic || pod.status}>{pod.diagnostic || pod.status}</em>
119+
<small title={meta}>{meta}</small>
120+
<small>{pod.restarts}r</small>
121+
</button>
122+
<button
123+
aria-label={`Open logs for ${pod.name}`}
124+
className="workload-pod-log"
125+
title="Open logs"
126+
type="button"
127+
onClick={() => onOpenResource(pod.id, "logs")}
128+
>
129+
<FileText size={13} />
130+
</button>
131+
</article>
132+
);
133+
}
134+
135+
function compareRuntimePods(left: ResourceRow, right: ResourceRow) {
136+
return podRuntimeRank(left) - podRuntimeRank(right) ||
137+
right.restarts - left.restarts ||
138+
left.name.localeCompare(right.name);
139+
}
140+
141+
function podRuntimeRank(pod: ResourceRow) {
142+
switch (pod.status) {
143+
case "critical":
144+
return 0;
145+
case "warning":
146+
return 1;
147+
case "syncing":
148+
return 2;
149+
case "healthy":
150+
return 3;
151+
}
152+
}

src/components/ResourceDetail.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { PodIssueStrip } from "./PodIssueStrip";
1212
import { PodLifecycleRail } from "./PodLifecycleRail";
1313
import { PodLinkStrip } from "./PodLinkStrip";
1414
import { PodPlacementStrip } from "./PodPlacementStrip";
15+
import { PodControllerRail } from "./PodControllerRail";
1516
import { PodTerminal, type LogMode } from "./PodTerminal";
1617
import { RelatedEventRail } from "./RelatedEventRail";
1718
import { StatusDot } from "./status";
@@ -150,6 +151,7 @@ export function ResourceDetail({
150151
onOpenContainerPreviousLogs={(containerName) => openLogs("previous", containerName)}
151152
onExecContainer={(containerName) => onRunPodAction(`exec:${containerName}`)}
152153
/>
154+
<PodControllerRail pod={resource} resources={allResources} onOpenResource={onOpenResource} />
153155
<PodLifecycleRail details={details} />
154156
<PodPlacementStrip allResources={allResources} pod={details.pod} onOpenResource={onOpenResource} />
155157
<PodLinkStrip

0 commit comments

Comments
 (0)