Skip to content

Commit fa070e3

Browse files
committed
Add pod triage log jump
1 parent 6bda730 commit fa070e3

3 files changed

Lines changed: 85 additions & 33 deletions

File tree

src/components/AppShell.tsx

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useMemo, useRef, useState } from "react";
1+
import { useCallback, useMemo, useRef, useState } from "react";
22
import type { KiteData } from "../hooks/useKiteData";
33
import { defaultResourceSort, nextResourceSort, sortResources } from "../lib/resourceSort";
44
import { pinnedResourcesNavId } from "../theme/resourceTheme";
@@ -52,12 +52,14 @@ export function AppShell({ data, usesNativeWindowControls }: AppShellProps) {
5252
const clusterName = data.clusters[0]?.name ?? data.selectedContext ?? "No context";
5353
const detailResource = detailOpen ? data.selectedResource : null;
5454

55-
function openResource(id: string, intent: "logs" | null = null) {
55+
const openResource = useCallback((id: string, intent: "logs" | null = null) => {
5656
data.onSelectResource(id);
5757
setDetailIntent(intent);
5858
setDetailOpen(true);
5959
window.requestAnimationFrame(() => primaryPaneRef.current?.scrollTo({ top: 0 }));
60-
}
60+
}, [data.onSelectResource]);
61+
62+
const openResourceLogs = useCallback((id: string) => openResource(id, "logs"), [openResource]);
6163

6264
function selectNavigation(id: string) {
6365
setActiveId(id);
@@ -112,7 +114,11 @@ export function AppShell({ data, usesNativeWindowControls }: AppShellProps) {
112114
<>
113115
<SummaryStrip counts={counts} warningCount={warningCount} />
114116
{(!activeItem?.kind || activeItem.kind === "Pod") ? (
115-
<PodTriageRail pods={podTriageResources} onSelect={openResource} />
117+
<PodTriageRail
118+
pods={podTriageResources}
119+
onOpenLogs={openResourceLogs}
120+
onSelect={openResource}
121+
/>
116122
) : null}
117123
<ScopeTabs activeId={activeId} counts={counts} items={scopeTabs} onSelect={selectNavigation} />
118124
<ResourceTable

src/components/PodTriageRail.tsx

Lines changed: 34 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { memo, useCallback, useMemo, useState, type CSSProperties } from "react";
2-
import { AlertTriangle, Container, ImageOff, RotateCw, ServerCrash, Timer } from "lucide-react";
2+
import { AlertTriangle, Container, FileText, ImageOff, RotateCw, ServerCrash, Timer } from "lucide-react";
33
import type { HealthState, ResourceRow } from "../types/kube";
44
import { StatusDot } from "./status";
55

@@ -10,7 +10,15 @@ type PodTriageBucket = {
1010
tone: Exclude<HealthState, "healthy" | "syncing">;
1111
};
1212

13-
export function PodTriageRail({ pods, onSelect }: { pods: ResourceRow[]; onSelect: (id: string) => void }) {
13+
export function PodTriageRail({
14+
onOpenLogs,
15+
onSelect,
16+
pods,
17+
}: {
18+
onOpenLogs: (id: string) => void;
19+
onSelect: (id: string) => void;
20+
pods: ResourceRow[];
21+
}) {
1422
const [activeBucketId, setActiveBucketId] = useState<PodTriageBucket["id"] | null>(null);
1523
const buckets = useMemo(() => podTriageBuckets(pods), [pods]);
1624
const activeBucket = buckets.find((bucket) => bucket.id === activeBucketId && bucket.count > 0) ?? null;
@@ -51,7 +59,7 @@ export function PodTriageRail({ pods, onSelect }: { pods: ResourceRow[]; onSelec
5159
</div>
5260
<div className="pod-triage-items">
5361
{visiblePods.map((pod) => (
54-
<PodTriageButton key={pod.id} pod={pod} onSelect={onSelect} />
62+
<PodTriageButton key={pod.id} pod={pod} onOpenLogs={onOpenLogs} onSelect={onSelect} />
5563
))}
5664
</div>
5765
</div>
@@ -87,26 +95,40 @@ function TriageBucket({
8795
}
8896

8997
const PodTriageButton = memo(function PodTriageButton({
98+
onOpenLogs,
9099
onSelect,
91100
pod,
92101
}: {
102+
onOpenLogs: (id: string) => void;
93103
onSelect: (id: string) => void;
94104
pod: ResourceRow;
95105
}) {
96106
const handleSelect = useCallback(() => onSelect(pod.id), [onSelect, pod.id]);
107+
const handleOpenLogs = useCallback(() => onOpenLogs(pod.id), [onOpenLogs, pod.id]);
97108
const triageTone = pod.status === "healthy" && pod.restarts > 0 ? "warning" : pod.status;
98109
const diagnostic = pod.diagnostic || (pod.restarts > 0 ? `${pod.restarts} restarts` : pod.status);
99110

100111
return (
101-
<button className={`pod-triage-item ${triageTone}`} type="button" onClick={handleSelect}>
102-
<StatusDot state={triageTone} />
103-
<span>
104-
<strong title={pod.name}>{pod.name}</strong>
105-
<small title={pod.namespace}>{pod.namespace}</small>
106-
</span>
107-
<em title={diagnostic}>{diagnostic}</em>
108-
<small>{pod.restarts}r</small>
109-
</button>
112+
<div className={`pod-triage-item ${triageTone}`}>
113+
<button className="pod-triage-open" type="button" onClick={handleSelect}>
114+
<StatusDot state={triageTone} />
115+
<span>
116+
<strong title={pod.name}>{pod.name}</strong>
117+
<small title={pod.namespace}>{pod.namespace}</small>
118+
</span>
119+
<em title={diagnostic}>{diagnostic}</em>
120+
<small>{pod.restarts}r</small>
121+
</button>
122+
<button
123+
aria-label={`Open logs for ${pod.name}`}
124+
className="pod-triage-log"
125+
title="Open logs"
126+
type="button"
127+
onClick={handleOpenLogs}
128+
>
129+
<FileText size={13} />
130+
</button>
131+
</div>
110132
);
111133
});
112134

src/styles/base.css

Lines changed: 41 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -612,25 +612,46 @@ summary:focus-visible {
612612
}
613613

614614
.pod-triage-item {
615+
display: grid;
616+
grid-template-columns: minmax(0, 1fr) 34px;
617+
min-block-size: 78px;
618+
min-inline-size: 0;
619+
border-inline-start: 1px solid var(--line);
620+
background: transparent;
621+
}
622+
623+
.pod-triage-item:first-child {
624+
border-inline-start: 0;
625+
}
626+
627+
.pod-triage-open {
615628
display: grid;
616629
grid-template-columns: 10px minmax(0, 1fr) minmax(72px, 0.55fr) auto;
617630
gap: 9px;
618631
align-items: center;
619-
min-block-size: 78px;
620632
min-inline-size: 0;
621633
padding-inline: 12px;
622634
border: 0;
623-
border-inline-start: 1px solid var(--line);
624635
background: transparent;
625636
text-align: start;
626637
cursor: pointer;
627638
}
628639

629-
.pod-triage-item:first-child {
630-
border-inline-start: 0;
640+
.pod-triage-log {
641+
display: grid;
642+
place-items: center;
643+
align-self: stretch;
644+
inline-size: 34px;
645+
min-block-size: 100%;
646+
border: 0;
647+
border-inline-start: 1px solid var(--line);
648+
color: var(--green-deep);
649+
background: transparent;
650+
cursor: pointer;
631651
}
632652

633-
.pod-triage-item:hover {
653+
.pod-triage-open:hover,
654+
.pod-triage-log:hover {
634655
background: var(--surface-soft);
635656
}
636657

@@ -639,26 +660,26 @@ summary:focus-visible {
639660
box-shadow: inset 0 2px 0 color-mix(in srgb, var(--orange) 72%, transparent);
640661
}
641662

642-
.pod-triage-item > span {
663+
.pod-triage-open > span {
643664
display: grid;
644665
gap: 4px;
645666
min-inline-size: 0;
646667
}
647668

648-
.pod-triage-item strong,
649-
.pod-triage-item small,
650-
.pod-triage-item em {
669+
.pod-triage-open strong,
670+
.pod-triage-open small,
671+
.pod-triage-open em {
651672
overflow: hidden;
652673
text-overflow: ellipsis;
653674
white-space: nowrap;
654675
}
655676

656-
.pod-triage-item strong {
677+
.pod-triage-open strong {
657678
color: var(--text);
658679
font-weight: 500;
659680
}
660681

661-
.pod-triage-item em {
682+
.pod-triage-open em {
662683
justify-self: start;
663684
max-inline-size: 100%;
664685
padding: 4px 6px;
@@ -667,8 +688,8 @@ summary:focus-visible {
667688
background: var(--surface-soft);
668689
}
669690

670-
.pod-triage-item.warning em,
671-
.pod-triage-item.critical em {
691+
.pod-triage-item.warning .pod-triage-open em,
692+
.pod-triage-item.critical .pod-triage-open em {
672693
border-color: color-mix(in srgb, var(--orange) 42%, var(--line));
673694
color: #7d5317;
674695
background: color-mix(in srgb, var(--yellow) 10%, var(--surface));
@@ -3218,18 +3239,21 @@ summary:focus-visible {
32183239
}
32193240

32203241
.pod-triage-item {
3221-
grid-template-columns: 10px minmax(0, 1fr) auto;
3222-
gap: 6px 9px;
32233242
border-inline-start: 0;
32243243
border-block-end: 1px solid var(--line);
32253244
}
32263245

3227-
.pod-triage-item em {
3246+
.pod-triage-open {
3247+
grid-template-columns: 10px minmax(0, 1fr) auto;
3248+
gap: 6px 9px;
3249+
}
3250+
3251+
.pod-triage-open em {
32283252
grid-column: 2;
32293253
max-inline-size: 100%;
32303254
}
32313255

3232-
.pod-triage-item > small {
3256+
.pod-triage-open > small {
32333257
grid-column: 3;
32343258
grid-row: 1 / span 2;
32353259
}

0 commit comments

Comments
 (0)