@skyhook-io/radar-app 1.13.0 → 1.13.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/src/App.tsx +55 -9
- package/src/RadarApp.tsx +3 -3
- package/src/api/diagnose.test.ts +268 -0
- package/src/api/diagnose.ts +117 -14
- package/src/components/diagnose/AISettings.tsx +10 -5
- package/src/components/diagnose/AgentSetupNotice.tsx +5 -5
- package/src/components/diagnose/ApplyDialog.test.tsx +72 -0
- package/src/components/diagnose/DiagnoseContext.tsx +228 -65
- package/src/components/diagnose/DiagnoseSurface.test.tsx +317 -9
- package/src/components/diagnose/DiagnoseSurface.tsx +621 -132
- package/src/components/diagnose/Home.test.tsx +293 -0
- package/src/components/diagnose/Home.tsx +307 -108
- package/src/components/diagnose/InvestigationEvidencePane.test.tsx +2170 -0
- package/src/components/diagnose/InvestigationEvidencePane.tsx +2253 -0
- package/src/components/diagnose/InvestigationResourceEvidence.test.tsx +257 -0
- package/src/components/diagnose/InvestigationResourceEvidence.tsx +214 -0
- package/src/components/diagnose/InvestigationView.test.ts +17 -0
- package/src/components/diagnose/InvestigationView.tsx +1911 -338
- package/src/components/diagnose/LocalDiagnoseAction.tsx +42 -25
- package/src/components/diagnose/agentCatalog.ts +1 -1
- package/src/components/diagnose/diagnoseEvidenceTypes.ts +151 -0
- package/src/components/diagnose/investigationEvidence.test.ts +3109 -0
- package/src/components/diagnose/investigationEvidence.ts +3492 -0
- package/src/components/diagnose/investigationEvidencePresentation.test.ts +447 -0
- package/src/components/diagnose/investigationEvidencePresentation.ts +167 -0
- package/src/components/diagnose/investigationExplanation.test.ts +63 -0
- package/src/components/diagnose/investigationExplanation.ts +22 -0
- package/src/components/diagnose/investigationResourceEvidenceModel.ts +322 -0
- package/src/components/diagnose/investigationSourceFocus.test.ts +143 -0
- package/src/components/diagnose/investigationSourceFocus.ts +98 -0
- package/src/components/diagnose/investigationState.test.ts +695 -0
- package/src/components/diagnose/investigationState.ts +451 -0
- package/src/components/diagnose/parts.test.tsx +864 -3
- package/src/components/diagnose/parts.tsx +1340 -536
- package/src/components/diagnose/target.test.ts +39 -0
- package/src/components/diagnose/target.ts +36 -0
- package/src/components/diagnose/useDisclosureReveal.ts +117 -0
- package/src/components/execution/BatchExecutionView.render.test.tsx +35 -0
- package/src/components/execution/BatchExecutionView.tsx +2 -3
- package/src/components/execution/execution-definition.test.ts +19 -0
- package/src/components/execution/execution-definition.ts +2 -0
- package/src/components/gitops/GitOpsView.tsx +25 -3
- package/src/components/home/MCPSetupDialog.tsx +2 -2
- package/src/components/home/mcpToolCatalog.test.ts +22 -0
- package/src/components/home/mcpToolCatalog.ts +3 -2
- package/src/components/issues/IssuesPane.tsx +5 -1
- package/src/components/nav/navigation.test.ts +26 -0
- package/src/components/nav/navigation.ts +10 -0
- package/src/components/settings/SettingsDialog.tsx +11 -13
- package/src/components/workload/WorkloadView.tsx +1 -1
- package/src/context/DiagnoseCustomization.tsx +11 -8
- package/src/index.css +63 -79
- package/src/index.ts +1 -1
|
@@ -1,58 +1,111 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
|
|
4
|
-
import { Loader2, Sparkles } from "lucide-react";
|
|
5
|
-
import { StatusDot, type StatusTone } from "@skyhook-io/k8s-ui";
|
|
1
|
+
// Server-side runs keep background and running investigations visible in both
|
|
2
|
+
// the docked Home view and the maximized workspace's master pane.
|
|
3
|
+
import { CircleAlert, Loader2, Server, Sparkles, Square } from "lucide-react";
|
|
6
4
|
import { type RunSummary } from "../../api/diagnose";
|
|
5
|
+
import {
|
|
6
|
+
groupQualifiesLaneId,
|
|
7
|
+
pluralToKind,
|
|
8
|
+
} from "@skyhook-io/k8s-ui/utils/navigation";
|
|
9
|
+
import { parseContextName } from "../../utils/context-name";
|
|
10
|
+
import { formatInvestigationTarget } from "./target";
|
|
11
|
+
import { Tooltip } from "../ui/Tooltip";
|
|
7
12
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
if (
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
function historyDay(date: Date, now: Date): string {
|
|
14
|
+
if (date.toDateString() === now.toDateString()) return "Today";
|
|
15
|
+
const yesterday = new Date(now);
|
|
16
|
+
yesterday.setDate(yesterday.getDate() - 1);
|
|
17
|
+
if (date.toDateString() === yesterday.toDateString()) return "Yesterday";
|
|
18
|
+
return date.toLocaleDateString(undefined, {
|
|
19
|
+
month: "short",
|
|
20
|
+
day: "numeric",
|
|
21
|
+
...(date.getFullYear() === now.getFullYear() ? {} : { year: "numeric" }),
|
|
22
|
+
});
|
|
16
23
|
}
|
|
17
24
|
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
default: // stopped
|
|
29
|
-
return "unknown";
|
|
30
|
-
}
|
|
31
|
-
}
|
|
25
|
+
// Relative age makes the list easy to scan; a stable local timestamp makes two
|
|
26
|
+
// investigations of the same target distinguishable when they ran close
|
|
27
|
+
// together. Keep today's label compact because the date is redundant there.
|
|
28
|
+
export function absoluteTime(ts: number, now: number): string {
|
|
29
|
+
const date = new Date(ts);
|
|
30
|
+
const current = new Date(now);
|
|
31
|
+
const today =
|
|
32
|
+
date.getFullYear() === current.getFullYear() &&
|
|
33
|
+
date.getMonth() === current.getMonth() &&
|
|
34
|
+
date.getDate() === current.getDate();
|
|
32
35
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
36
|
+
return date.toLocaleString(
|
|
37
|
+
undefined,
|
|
38
|
+
today
|
|
39
|
+
? { hour: "numeric", minute: "2-digit" }
|
|
40
|
+
: {
|
|
41
|
+
month: "short",
|
|
42
|
+
day: "numeric",
|
|
43
|
+
...(date.getFullYear() === current.getFullYear()
|
|
44
|
+
? {}
|
|
45
|
+
: { year: "numeric" as const }),
|
|
46
|
+
hour: "numeric",
|
|
47
|
+
minute: "2-digit",
|
|
48
|
+
},
|
|
49
|
+
);
|
|
37
50
|
}
|
|
38
51
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
52
|
+
const historyStatuses = {
|
|
53
|
+
running: {
|
|
54
|
+
label: "Running",
|
|
55
|
+
short: "Running",
|
|
56
|
+
Icon: Loader2,
|
|
57
|
+
className: "text-accent-text",
|
|
58
|
+
},
|
|
59
|
+
stopping: {
|
|
60
|
+
label: "Stopping",
|
|
61
|
+
short: "Stopping",
|
|
62
|
+
Icon: Loader2,
|
|
63
|
+
className: "text-theme-text-tertiary",
|
|
64
|
+
},
|
|
65
|
+
done: {
|
|
66
|
+
label: "Completed",
|
|
67
|
+
short: "",
|
|
68
|
+
Icon: undefined,
|
|
69
|
+
className: "text-theme-text-tertiary",
|
|
70
|
+
},
|
|
71
|
+
error: {
|
|
72
|
+
label: "Investigation failed",
|
|
73
|
+
short: "Failed",
|
|
74
|
+
Icon: CircleAlert,
|
|
75
|
+
className: "text-theme-text-secondary",
|
|
76
|
+
},
|
|
77
|
+
stopped: {
|
|
78
|
+
label: "Stopped",
|
|
79
|
+
short: "Stopped",
|
|
80
|
+
Icon: Square,
|
|
81
|
+
className: "text-theme-text-secondary",
|
|
82
|
+
},
|
|
83
|
+
stale: {
|
|
84
|
+
label: "Read-only investigation",
|
|
85
|
+
short: "",
|
|
86
|
+
Icon: undefined,
|
|
87
|
+
className: "text-theme-text-tertiary",
|
|
88
|
+
},
|
|
89
|
+
} as const;
|
|
90
|
+
|
|
91
|
+
// A short text status means no run outcome relies on decoding a 6px colored dot.
|
|
92
|
+
export function statusWord(status: RunSummary["status"]): {
|
|
93
|
+
text: string;
|
|
94
|
+
cls: string;
|
|
95
|
+
} {
|
|
45
96
|
switch (status) {
|
|
97
|
+
case "running":
|
|
98
|
+
return { text: "Running", cls: "text-accent" };
|
|
99
|
+
case "done":
|
|
100
|
+
return { text: "Completed", cls: "text-theme-text-secondary" };
|
|
46
101
|
case "error":
|
|
47
102
|
return { text: "Failed", cls: "text-red-400" };
|
|
48
103
|
case "stopped":
|
|
49
104
|
return { text: "Stopped", cls: "text-theme-text-tertiary" };
|
|
105
|
+
case "stopping":
|
|
106
|
+
return { text: "Stopping", cls: "text-theme-text-tertiary" };
|
|
50
107
|
case "stale":
|
|
51
|
-
|
|
52
|
-
// about a cluster that's no longer connected.
|
|
53
|
-
return { text: "Different cluster", cls: "text-amber-500" };
|
|
54
|
-
default:
|
|
55
|
-
return null;
|
|
108
|
+
return { text: "Read-only", cls: "text-theme-text-tertiary" };
|
|
56
109
|
}
|
|
57
110
|
}
|
|
58
111
|
|
|
@@ -62,14 +115,58 @@ export function RecentList({
|
|
|
62
115
|
onSelect,
|
|
63
116
|
selectedId,
|
|
64
117
|
historyDegraded = false,
|
|
118
|
+
currentContext,
|
|
65
119
|
}: {
|
|
66
120
|
agentLabel: string;
|
|
67
121
|
runs: RunSummary[];
|
|
68
122
|
onSelect: (id: string) => void;
|
|
69
123
|
selectedId?: string | null;
|
|
70
124
|
historyDegraded?: boolean;
|
|
125
|
+
currentContext?: string;
|
|
71
126
|
}) {
|
|
72
|
-
const now = Date
|
|
127
|
+
const now = new Date();
|
|
128
|
+
const contexts = new Map(
|
|
129
|
+
runs.map((r) => [r.context, parseContextName(r.context)]),
|
|
130
|
+
);
|
|
131
|
+
const contextsByName = new Map<string, Set<string>>();
|
|
132
|
+
const groupsByKind = new Map<string, Set<string>>();
|
|
133
|
+
for (const [raw, parsed] of contexts) {
|
|
134
|
+
const names = contextsByName.get(parsed.clusterName) ?? new Set<string>();
|
|
135
|
+
names.add(raw);
|
|
136
|
+
contextsByName.set(parsed.clusterName, names);
|
|
137
|
+
}
|
|
138
|
+
for (const r of runs) {
|
|
139
|
+
const kind = pluralToKind(r.kind);
|
|
140
|
+
const groups = groupsByKind.get(kind) ?? new Set<string>();
|
|
141
|
+
// Match Radar's resource-lane display convention: built-in API groups
|
|
142
|
+
// share a readable kind label; custom groups may need disambiguation.
|
|
143
|
+
groups.add(groupQualifiesLaneId(r.group) ? r.group : "");
|
|
144
|
+
groupsByKind.set(kind, groups);
|
|
145
|
+
}
|
|
146
|
+
const organizationRuns = runs.filter(
|
|
147
|
+
(r) => r.trigger === "background" || r.ownedByMe === false,
|
|
148
|
+
);
|
|
149
|
+
const yourRuns = runs.filter((r) => !organizationRuns.includes(r));
|
|
150
|
+
const collections = organizationRuns.length
|
|
151
|
+
? [
|
|
152
|
+
{ label: "Your investigations", runs: yourRuns },
|
|
153
|
+
{ label: "Organization", runs: organizationRuns },
|
|
154
|
+
].filter((collection) => collection.runs.length > 0)
|
|
155
|
+
: [{ label: "", runs }];
|
|
156
|
+
// Status bookkeeping (including cluster switches) updates updatedAt. It must
|
|
157
|
+
// not change the apparent start time or reshuffle the navigation list.
|
|
158
|
+
const groupedCollections = collections.map((collection) => {
|
|
159
|
+
const days = new Map<string, RunSummary[]>();
|
|
160
|
+
for (const r of [...collection.runs].sort(
|
|
161
|
+
(a, b) => Date.parse(b.createdAt) - Date.parse(a.createdAt),
|
|
162
|
+
)) {
|
|
163
|
+
const day = historyDay(new Date(r.createdAt), now);
|
|
164
|
+
const entries = days.get(day) ?? [];
|
|
165
|
+
entries.push(r);
|
|
166
|
+
days.set(day, entries);
|
|
167
|
+
}
|
|
168
|
+
return { label: collection.label, days };
|
|
169
|
+
});
|
|
73
170
|
|
|
74
171
|
// Persistence broke (disk error) — without this the user reasonably assumes
|
|
75
172
|
// their history survives a restart, and it won't.
|
|
@@ -83,80 +180,182 @@ export function RecentList({
|
|
|
83
180
|
if (runs.length === 0) {
|
|
84
181
|
return (
|
|
85
182
|
<div>
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
183
|
+
{degradedNote}
|
|
184
|
+
<div className="flex flex-col items-center px-4 py-12 text-center">
|
|
185
|
+
<Sparkles className="mb-3 h-7 w-7 text-accent" />
|
|
186
|
+
<div className="text-sm font-medium text-theme-text-primary">
|
|
187
|
+
No investigations yet
|
|
188
|
+
</div>
|
|
189
|
+
<p className="mt-1 max-w-xs text-sm text-theme-text-tertiary">
|
|
190
|
+
Open a resource and use its{" "}
|
|
191
|
+
<Sparkles className="inline h-3.5 w-3.5 align-text-bottom text-accent" />{" "}
|
|
192
|
+
action to investigate it with {agentLabel} —{" "}
|
|
193
|
+
<span className="font-medium text-theme-text-secondary">
|
|
194
|
+
Investigate
|
|
195
|
+
</span>{" "}
|
|
196
|
+
a problem, or just ask about it. Investigations run in the
|
|
197
|
+
background and are kept in your history here.
|
|
198
|
+
</p>
|
|
91
199
|
</div>
|
|
92
|
-
<p className="mt-1 max-w-xs text-sm text-theme-text-tertiary">
|
|
93
|
-
Open a resource and use its{" "}
|
|
94
|
-
<Sparkles className="inline h-3.5 w-3.5 align-text-bottom text-accent" />{" "}
|
|
95
|
-
action to investigate it with {agentLabel} —{" "}
|
|
96
|
-
<span className="font-medium text-theme-text-secondary">Diagnose</span>{" "}
|
|
97
|
-
a problem, or just ask about it. Investigations run in the background
|
|
98
|
-
and are kept in your history here.
|
|
99
|
-
</p>
|
|
100
|
-
</div>
|
|
101
200
|
</div>
|
|
102
201
|
);
|
|
103
202
|
}
|
|
104
203
|
|
|
105
204
|
return (
|
|
106
|
-
<div className="space-y-
|
|
205
|
+
<div className="space-y-4">
|
|
107
206
|
{degradedNote}
|
|
108
|
-
<
|
|
207
|
+
<h2 className="px-2 text-sm font-medium text-theme-text-secondary">
|
|
109
208
|
Investigations
|
|
110
|
-
</
|
|
111
|
-
{
|
|
112
|
-
<
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
209
|
+
</h2>
|
|
210
|
+
{groupedCollections.map((collection) => (
|
|
211
|
+
<div key={collection.label} className="space-y-4">
|
|
212
|
+
{collection.label && (
|
|
213
|
+
<h3 className="px-2 text-xs font-semibold text-theme-text-secondary">
|
|
214
|
+
{collection.label}
|
|
215
|
+
</h3>
|
|
216
|
+
)}
|
|
217
|
+
{[...collection.days].map(([day, entries]) => (
|
|
218
|
+
<section key={day} aria-label={day} className="space-y-1">
|
|
219
|
+
<h3 className="px-2 pb-1 text-xs font-medium text-theme-text-tertiary">
|
|
220
|
+
{day}
|
|
221
|
+
</h3>
|
|
222
|
+
{entries.map((r) => {
|
|
223
|
+
const { label, short, Icon, className } =
|
|
224
|
+
historyStatuses[r.status];
|
|
225
|
+
const parsed = contexts.get(r.context)!;
|
|
226
|
+
const collision =
|
|
227
|
+
contextsByName.get(parsed.clusterName)!.size > 1;
|
|
228
|
+
const peers = [
|
|
229
|
+
...contextsByName.get(parsed.clusterName)!,
|
|
230
|
+
].filter((raw) => raw !== r.context);
|
|
231
|
+
const qualifier =
|
|
232
|
+
parsed.account &&
|
|
233
|
+
peers.every(
|
|
234
|
+
(raw) => contexts.get(raw)!.account !== parsed.account,
|
|
235
|
+
)
|
|
236
|
+
? parsed.account
|
|
237
|
+
: parsed.account &&
|
|
238
|
+
parsed.region &&
|
|
239
|
+
peers.every((raw) => {
|
|
240
|
+
const peer = contexts.get(raw)!;
|
|
241
|
+
return (
|
|
242
|
+
peer.account !== parsed.account ||
|
|
243
|
+
peer.region !== parsed.region
|
|
244
|
+
);
|
|
245
|
+
})
|
|
246
|
+
? `${parsed.account} · ${parsed.region}`
|
|
247
|
+
: r.context;
|
|
248
|
+
const readableKind = pluralToKind(r.kind);
|
|
249
|
+
const kind =
|
|
250
|
+
groupsByKind.get(readableKind)!.size > 1
|
|
251
|
+
? `${readableKind} · ${r.group || "core"}`
|
|
252
|
+
: readableKind;
|
|
253
|
+
const initialIssue = r.health?.topReason?.trim();
|
|
254
|
+
const isCurrentCluster = currentContext === r.context;
|
|
255
|
+
const visibility =
|
|
256
|
+
r.trigger === "background"
|
|
257
|
+
? "Automatic"
|
|
258
|
+
: r.visibility === "organization"
|
|
259
|
+
? "Shared"
|
|
260
|
+
: r.visibility === "private"
|
|
261
|
+
? "Private"
|
|
262
|
+
: "";
|
|
263
|
+
const identity = `${formatInvestigationTarget(r)} · ${r.context}${isCurrentCluster ? " · Current cluster" : ""} · ${label}${visibility ? ` · ${visibility}` : ""} · Started ${new Date(r.createdAt).toLocaleString()}${initialIssue ? ` · Started with ${initialIssue}` : ""}`;
|
|
264
|
+
return (
|
|
265
|
+
<button
|
|
266
|
+
key={r.id}
|
|
267
|
+
onClick={() => onSelect(r.id)}
|
|
268
|
+
aria-label={identity}
|
|
269
|
+
aria-current={r.id === selectedId ? "true" : undefined}
|
|
270
|
+
className={`flex w-full min-w-0 flex-col gap-0.5 rounded-md border-l-2 px-2 py-2 text-left focus-visible:outline-2 focus-visible:outline-accent ${
|
|
271
|
+
r.id === selectedId
|
|
272
|
+
? "border-accent bg-accent-muted"
|
|
273
|
+
: "border-transparent hover:bg-theme-hover"
|
|
274
|
+
}`}
|
|
275
|
+
>
|
|
276
|
+
<span className="flex w-full items-start gap-2">
|
|
277
|
+
<Tooltip
|
|
278
|
+
content={r.name}
|
|
279
|
+
position="right"
|
|
280
|
+
delay={600}
|
|
281
|
+
className="pointer-events-none"
|
|
282
|
+
wrapperClassName="min-w-0 flex-1"
|
|
283
|
+
>
|
|
284
|
+
<span className="min-w-0 flex-1 line-clamp-2 break-words text-sm font-medium leading-5 text-theme-text-primary">
|
|
285
|
+
{r.name}
|
|
286
|
+
</span>
|
|
287
|
+
</Tooltip>
|
|
288
|
+
{(Icon || short) && (
|
|
289
|
+
<span
|
|
290
|
+
aria-hidden="true"
|
|
291
|
+
className={`flex shrink-0 items-center gap-1 text-xs leading-5 ${className}`}
|
|
292
|
+
>
|
|
293
|
+
{Icon && (
|
|
294
|
+
<Icon
|
|
295
|
+
className={`mt-0.5 h-3.5 w-3.5 ${r.status === "running" || r.status === "stopping" ? "animate-spin motion-reduce:animate-none" : r.status === "error" ? "text-semantic-error" : ""}`}
|
|
296
|
+
/>
|
|
297
|
+
)}
|
|
298
|
+
{short}
|
|
299
|
+
</span>
|
|
300
|
+
)}
|
|
301
|
+
</span>
|
|
302
|
+
<span className="flex w-full items-baseline gap-2 text-xs leading-4 text-theme-text-secondary">
|
|
303
|
+
<span className="min-w-0 flex-1 truncate">
|
|
304
|
+
{r.namespace ? `${r.namespace} · ` : ""}
|
|
305
|
+
{kind}
|
|
137
306
|
</span>
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
307
|
+
<time
|
|
308
|
+
dateTime={r.createdAt}
|
|
309
|
+
className="shrink-0 tabular-nums text-theme-text-tertiary"
|
|
310
|
+
>
|
|
311
|
+
{new Date(r.createdAt).toLocaleTimeString(undefined, {
|
|
312
|
+
hour: "numeric",
|
|
313
|
+
minute: "2-digit",
|
|
314
|
+
})}
|
|
315
|
+
</time>
|
|
316
|
+
</span>
|
|
317
|
+
<Tooltip
|
|
318
|
+
content={`${r.context}${isCurrentCluster ? " · Current cluster" : ""}`}
|
|
319
|
+
position="right"
|
|
320
|
+
delay={600}
|
|
321
|
+
className="pointer-events-none"
|
|
322
|
+
wrapperClassName="w-full min-w-0"
|
|
323
|
+
>
|
|
324
|
+
<span
|
|
325
|
+
aria-label={
|
|
326
|
+
isCurrentCluster
|
|
327
|
+
? `Current cluster: ${parsed.clusterName}`
|
|
328
|
+
: `Cluster: ${parsed.clusterName}`
|
|
329
|
+
}
|
|
330
|
+
className={`flex w-full items-center gap-1 text-xs leading-4 ${isCurrentCluster ? "text-accent-text" : "text-theme-text-tertiary"}`}
|
|
331
|
+
>
|
|
332
|
+
<Server className="h-3 w-3 shrink-0" aria-hidden />
|
|
333
|
+
<span className="min-w-0 flex-1 truncate">
|
|
334
|
+
{parsed.clusterName}
|
|
335
|
+
</span>
|
|
336
|
+
{visibility && (
|
|
337
|
+
<span className="shrink-0 text-theme-text-tertiary">
|
|
338
|
+
{visibility}
|
|
339
|
+
</span>
|
|
340
|
+
)}
|
|
341
|
+
</span>
|
|
342
|
+
</Tooltip>
|
|
343
|
+
{collision && qualifier !== parsed.clusterName && (
|
|
344
|
+
<span className="w-full break-words text-xs leading-4 text-theme-text-secondary">
|
|
345
|
+
{qualifier}
|
|
346
|
+
</span>
|
|
347
|
+
)}
|
|
348
|
+
{initialIssue && (
|
|
349
|
+
<span className="line-clamp-1 w-full text-xs leading-4 text-theme-text-secondary">
|
|
350
|
+
Started with {initialIssue}
|
|
351
|
+
</span>
|
|
352
|
+
)}
|
|
353
|
+
</button>
|
|
354
|
+
);
|
|
355
|
+
})}
|
|
356
|
+
</section>
|
|
357
|
+
))}
|
|
358
|
+
</div>
|
|
160
359
|
))}
|
|
161
360
|
</div>
|
|
162
361
|
);
|