@skyhook-io/radar-app 1.14.2 → 1.14.3

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.
Files changed (74) hide show
  1. package/package.json +2 -2
  2. package/src/api/client.ts +30 -0
  3. package/src/components/CloudConnectFlow.tsx +21 -7
  4. package/src/components/CloudFunnelButton.tsx +85 -8
  5. package/src/components/cloudConnectHandoff.test.ts +10 -1
  6. package/src/components/cloudConnectHandoff.ts +15 -1
  7. package/src/components/diagnose/AIMarkdown.tsx +87 -0
  8. package/src/components/diagnose/ActivityTurn.tsx +1244 -0
  9. package/src/components/diagnose/AgentControls.tsx +603 -0
  10. package/src/components/diagnose/AnalysisStory.test.tsx +31 -25
  11. package/src/components/diagnose/AnalysisStory.tsx +13 -18
  12. package/src/components/diagnose/ApplyDialog.tsx +289 -0
  13. package/src/components/diagnose/AssessmentCard.tsx +1560 -0
  14. package/src/components/diagnose/DiagnoseSurface.test.tsx +9 -9
  15. package/src/components/diagnose/DiagnoseSurface.tsx +5 -1
  16. package/src/components/diagnose/EvidenceCard.tsx +611 -0
  17. package/src/components/diagnose/Home.test.tsx +1 -3
  18. package/src/components/diagnose/Home.tsx +2 -1
  19. package/src/components/diagnose/InvestigationEvidencePane.story.test.tsx +6 -8
  20. package/src/components/diagnose/InvestigationEvidencePane.test.tsx +5 -3
  21. package/src/components/diagnose/InvestigationEvidencePane.tsx +31 -3445
  22. package/src/components/diagnose/InvestigationEvidenceSections.tsx +523 -0
  23. package/src/components/diagnose/InvestigationView.tsx +11 -28
  24. package/src/components/diagnose/StoryExcerpt.tsx +118 -0
  25. package/src/components/diagnose/assessmentCopy.ts +169 -0
  26. package/src/components/diagnose/investigationCase.test.tsx +631 -2
  27. package/src/components/diagnose/investigationCase.ts +132 -24
  28. package/src/components/diagnose/investigationEvidence/adapters/coverage.test.tsx +660 -0
  29. package/src/components/diagnose/investigationEvidence/adapters/{helm.test.ts → helm.test.tsx} +63 -0
  30. package/src/components/diagnose/investigationEvidence/adapters/helm.ts +70 -1
  31. package/src/components/diagnose/investigationEvidence/adapters/listings.ts +134 -0
  32. package/src/components/diagnose/investigationEvidence/adapters/{permissions.test.ts → permissions.test.tsx} +50 -0
  33. package/src/components/diagnose/investigationEvidence/adapters/permissions.ts +18 -0
  34. package/src/components/diagnose/investigationEvidence/adapters/posture.ts +272 -0
  35. package/src/components/diagnose/investigationEvidence/adapters/prometheus.ts +49 -0
  36. package/src/components/diagnose/investigationEvidence/adapters/rankings.ts +147 -0
  37. package/src/components/diagnose/investigationEvidence/adapters/resource.ts +20 -10
  38. package/src/components/diagnose/investigationEvidence/bodies/index.tsx +127 -0
  39. package/src/components/diagnose/investigationEvidence/bodies/inventory.tsx +158 -0
  40. package/src/components/diagnose/investigationEvidence/bodies/metrics.tsx +373 -0
  41. package/src/components/diagnose/investigationEvidence/bodies/network.tsx +274 -0
  42. package/src/components/diagnose/investigationEvidence/bodies/platform.tsx +475 -0
  43. package/src/components/diagnose/investigationEvidence/bodies/posture.tsx +60 -0
  44. package/src/components/diagnose/investigationEvidence/bodies/ranking.tsx +76 -0
  45. package/src/components/diagnose/investigationEvidence/bodies/streams.tsx +212 -0
  46. package/src/components/diagnose/investigationEvidence/bodies/workload.tsx +335 -0
  47. package/src/components/diagnose/investigationEvidence/builder.ts +15 -0
  48. package/src/components/diagnose/investigationEvidence/cardParts.tsx +312 -0
  49. package/src/components/diagnose/investigationEvidence/excerpt.test.ts +105 -0
  50. package/src/components/diagnose/investigationEvidence/excerpt.ts +92 -0
  51. package/src/components/diagnose/investigationEvidence/identity.test.ts +5 -7
  52. package/src/components/diagnose/investigationEvidence/index.ts +1 -1
  53. package/src/components/diagnose/investigationEvidence/navigation.tsx +26 -0
  54. package/src/components/diagnose/investigationEvidence/observations.ts +11 -0
  55. package/src/components/diagnose/investigationEvidence/projection.test.ts +1 -1
  56. package/src/components/diagnose/investigationEvidence/types.ts +97 -2
  57. package/src/components/diagnose/investigationEvidenceKinds.ts +22 -0
  58. package/src/components/diagnose/investigationEvidencePartition.ts +296 -0
  59. package/src/components/diagnose/parts.test.tsx +3 -1
  60. package/src/components/diagnose/parts.tsx +26 -3904
  61. package/src/components/diagnose/target.ts +9 -3
  62. package/src/components/diagnose/toolCallLabel.test.ts +47 -1
  63. package/src/components/diagnose/toolCallLabel.ts +41 -2
  64. package/src/components/helm/HelmReleaseDrawer.tsx +1 -1
  65. package/src/components/home/MCPSetupDialog.tsx +1 -1
  66. package/src/components/resource/PrometheusChartsGrid.tsx +1 -1
  67. package/src/components/resource/WorkloadMetricsHelpDialog.tsx +1 -1
  68. package/src/components/resource/WorkloadMetricsSection.tsx +1 -1
  69. package/src/components/resources/PodFilePreview.tsx +1 -1
  70. package/src/components/ui/CommandPalette.tsx +12 -8
  71. package/src/components/ui/DiagnosticsOverlay.tsx +7 -5
  72. package/src/components/ui/Omnibar.tsx +33 -10
  73. package/src/components/ui/ShortcutHelpOverlay.tsx +8 -5
  74. package/src/components/ui/Disclosure.tsx +0 -47
@@ -0,0 +1,212 @@
1
+ import { VISIBLE_LOG_EVIDENCE_LINES } from "../excerpt";
2
+ import { useContext } from "react";
3
+ import {
4
+ Badge,
5
+ DiffViewer,
6
+ TerminalBlock,
7
+ formatRelativeAgeTime,
8
+ ResourceLink,
9
+ stripAnsi,
10
+ } from "@skyhook-io/k8s-ui";
11
+ import { apiVersionToGroup } from "../../../../utils/navigation";
12
+ import { Tooltip } from "../../../ui/Tooltip";
13
+ import { EvidenceNavigationContext } from "../navigation";
14
+ import type { EvidenceDataOf } from "../cardParts";
15
+
16
+ export { VISIBLE_LOG_EVIDENCE_LINES } from "../excerpt";
17
+
18
+ export function LogsBody({
19
+ data,
20
+ condensed = false,
21
+ }: {
22
+ data: EvidenceDataOf<"logs">;
23
+ condensed?: boolean;
24
+ }) {
25
+ const lines = data.logs?.lines ?? [];
26
+ const visibleLines = lines
27
+ .slice(-VISIBLE_LOG_EVIDENCE_LINES)
28
+ .map((line) => stripAnsi(line));
29
+ const omittedLines = lines.length - visibleLines.length;
30
+ return (
31
+ <div className="space-y-2">
32
+ <div className="flex flex-wrap items-center gap-1.5">
33
+ {condensed ? null : (
34
+ <>
35
+ <Badge tone="structural" size="sm">
36
+ {data.pod} / {data.container}
37
+ </Badge>
38
+ <Badge severity="neutral" size="sm">
39
+ {data.previous ? "previous instance" : "current instance"}
40
+ </Badge>
41
+ </>
42
+ )}
43
+ {data.logs?.fallback ? (
44
+ <Badge severity="neutral" size="sm">
45
+ Log tail · filter matched nothing
46
+ </Badge>
47
+ ) : null}
48
+ {data.logs ? (
49
+ <span className="text-xs text-theme-text-tertiary">
50
+ {data.logs.matchedLines} of {data.logs.totalLines} read lines
51
+ matched the filter
52
+ </span>
53
+ ) : null}
54
+ </div>
55
+ {condensed ? (
56
+ omittedLines > 0 ? (
57
+ <p className="text-xs text-theme-text-tertiary">
58
+ Showing the last {visibleLines.length} of {lines.length} lines.
59
+ </p>
60
+ ) : null
61
+ ) : visibleLines.length > 0 ? (
62
+ <TerminalBlock
63
+ label={
64
+ omittedLines > 0
65
+ ? `Selected log excerpt · last ${visibleLines.length} of ${lines.length} lines`
66
+ : "Selected log excerpt"
67
+ }
68
+ >
69
+ {visibleLines.join("\n")}
70
+ </TerminalBlock>
71
+ ) : (
72
+ <p className="text-xs italic text-theme-text-tertiary">
73
+ No lines were captured from this stream.
74
+ </p>
75
+ )}
76
+ {data.error ? (
77
+ <p className="text-xs leading-relaxed text-red-400">{data.error}</p>
78
+ ) : null}
79
+ {data.warnings.map((warning) => (
80
+ <p key={warning} className="text-xs text-warning-text">
81
+ {warning}
82
+ </p>
83
+ ))}
84
+ </div>
85
+ );
86
+ }
87
+
88
+ export function EventsBody({ data }: { data: EvidenceDataOf<"events"> }) {
89
+ return (
90
+ <div>
91
+ <p className="mb-2 text-xs text-theme-text-tertiary">{data.scope}</p>
92
+ <ol className="max-h-[28rem] space-y-0 overflow-y-auto pr-1">
93
+ {data.events.map((event, index) => (
94
+ <li
95
+ key={`${event.reason}-${event.lastTimestamp}-${index}`}
96
+ className="relative flex gap-3 pb-3 last:pb-0"
97
+ >
98
+ {index < data.events.length - 1 ? (
99
+ <span className="absolute bottom-0 left-[5px] top-3 w-px bg-theme-border" />
100
+ ) : null}
101
+ <span className="relative mt-1.5 h-2.5 w-2.5 shrink-0 rounded-full border-2 border-amber-500 bg-theme-surface" />
102
+ <div className="min-w-0 flex-1">
103
+ <div className="flex flex-wrap items-baseline justify-between gap-x-3 gap-y-0.5">
104
+ <span className="text-xs font-semibold text-theme-text-primary">
105
+ {event.reason}
106
+ {event.count > 1 ? (
107
+ <span className="ml-1.5 font-mono text-[11px] font-normal text-theme-text-tertiary">
108
+ ×{event.count}
109
+ </span>
110
+ ) : null}
111
+ </span>
112
+ <Tooltip
113
+ content={new Date(event.lastTimestamp).toLocaleString()}
114
+ delay={150}
115
+ position="left"
116
+ >
117
+ <time
118
+ dateTime={event.lastTimestamp}
119
+ className="text-xs text-theme-text-tertiary"
120
+ >
121
+ {formatRelativeAgeTime(event.lastTimestamp)}
122
+ </time>
123
+ </Tooltip>
124
+ </div>
125
+ <p className="mt-0.5 text-xs leading-relaxed text-theme-text-secondary">
126
+ {event.message}
127
+ </p>
128
+ </div>
129
+ </li>
130
+ ))}
131
+ </ol>
132
+ </div>
133
+ );
134
+ }
135
+
136
+ export function ChangesBody({ data }: { data: EvidenceDataOf<"changes"> }) {
137
+ const { onOpenResource } = useContext(EvidenceNavigationContext);
138
+ return (
139
+ <div className="space-y-2.5">
140
+ {data.changeContext?.evidence ? (
141
+ <p className="text-xs leading-relaxed text-theme-text-secondary [overflow-wrap:anywhere]">
142
+ {data.changeContext.evidence}
143
+ </p>
144
+ ) : null}
145
+ {data.changes.map((change, index) => (
146
+ <div
147
+ key={`${change.kind}-${change.namespace ?? ""}-${change.name}-${change.timestamp}-${index}`}
148
+ className="rounded-md border border-theme-border/70 bg-theme-base/30 p-2.5"
149
+ >
150
+ <div className="flex flex-wrap items-center gap-1.5">
151
+ <Badge tone="structural" size="sm">
152
+ {change.kind}
153
+ </Badge>
154
+ <span className="font-mono text-xs">
155
+ <ResourceLink
156
+ name={change.name}
157
+ kind={change.kind}
158
+ namespace={change.namespace ?? ""}
159
+ group={
160
+ change.apiVersion
161
+ ? apiVersionToGroup(change.apiVersion)
162
+ : undefined
163
+ }
164
+ label={`${change.namespace ? `${change.namespace}/` : ""}${change.name}`}
165
+ onNavigate={
166
+ change.apiVersion && onOpenResource
167
+ ? (ref) => onOpenResource(ref)
168
+ : undefined
169
+ }
170
+ />
171
+ </span>
172
+ <Badge tone="note" size="sm">
173
+ {change.changeType.replaceAll("_", " ")}
174
+ </Badge>
175
+ <Tooltip
176
+ content={new Date(change.timestamp).toLocaleString()}
177
+ delay={150}
178
+ position="left"
179
+ wrapperClassName="ml-auto"
180
+ >
181
+ <time
182
+ dateTime={change.timestamp}
183
+ className="text-xs text-theme-text-tertiary"
184
+ >
185
+ {formatRelativeAgeTime(change.timestamp)}
186
+ </time>
187
+ </Tooltip>
188
+ </div>
189
+ {change.summary ? (
190
+ <p className="mt-1.5 text-xs text-theme-text-secondary">
191
+ {change.summary}
192
+ </p>
193
+ ) : null}
194
+ {change.fields?.length ? (
195
+ <div className="mt-2">
196
+ <DiffViewer
197
+ diff={{
198
+ summary: `${change.fields.length} changed ${change.fields.length === 1 ? "field" : "fields"}`,
199
+ fields: change.fields.map((field) => ({
200
+ path: field.path,
201
+ oldValue: field.oldValue ?? null,
202
+ newValue: field.newValue ?? null,
203
+ })),
204
+ }}
205
+ />
206
+ </div>
207
+ ) : null}
208
+ </div>
209
+ ))}
210
+ </div>
211
+ );
212
+ }
@@ -0,0 +1,335 @@
1
+ import { useContext } from "react";
2
+ import { clsx } from "clsx";
3
+ import { Info } from "lucide-react";
4
+ import {
5
+ Badge,
6
+ StatusDot,
7
+ TerminalBlock,
8
+ displayKind,
9
+ HPADiagnosisSummary,
10
+ ResourceLink,
11
+ } from "@skyhook-io/k8s-ui";
12
+ import type {
13
+ DiagnosisResourceContext,
14
+ DiagnosisResourceRef,
15
+ DiagnosisScalerRef,
16
+ } from "../../diagnoseEvidenceTypes";
17
+ import { InvestigationResourceEvidence } from "../../InvestigationResourceEvidence";
18
+ import { EvidenceNavigationContext } from "../navigation";
19
+ import {
20
+ type EvidenceDataOf,
21
+ ResourceFact,
22
+ conditionStatusTone,
23
+ gitOpsValueSeverity,
24
+ severityBadge,
25
+ } from "../cardParts";
26
+
27
+ export function IssueBody({
28
+ data,
29
+ cardSummary,
30
+ }: {
31
+ data: EvidenceDataOf<"issue">;
32
+ cardSummary?: string;
33
+ }) {
34
+ const issue = data.issue;
35
+ const showCause =
36
+ Boolean(issue.cause) && issue.cause?.trim() !== cardSummary?.trim();
37
+ const showMessage =
38
+ Boolean(issue.message) &&
39
+ issue.message?.trim() !== cardSummary?.trim() &&
40
+ issue.message?.trim() !== issue.cause?.trim();
41
+ return (
42
+ <div className="space-y-2">
43
+ <div className="flex flex-wrap gap-1.5">
44
+ <Badge
45
+ severity={issue.severity === "critical" ? "error" : "warning"}
46
+ size="sm"
47
+ >
48
+ {issue.severity}
49
+ </Badge>
50
+ <Badge tone="structural" size="sm">
51
+ {issue.kind}
52
+ </Badge>
53
+ <Badge tone="structural" size="sm">
54
+ {issue.namespace ? `${issue.namespace}/` : ""}
55
+ {issue.name}
56
+ </Badge>
57
+ {data.pods && data.pods.length > 0 ? (
58
+ <>
59
+ <Badge tone="structural" size="sm">
60
+ {data.pods.length === 1 ? "1 Pod" : `${data.pods.length} Pods`}
61
+ </Badge>
62
+ {data.pods.map((pod) => (
63
+ <Badge key={pod} tone="structural" size="sm">
64
+ {pod}
65
+ </Badge>
66
+ ))}
67
+ </>
68
+ ) : null}
69
+ </div>
70
+ {showCause ? (
71
+ <p className="text-sm font-medium leading-relaxed text-theme-text-primary">
72
+ {issue.cause}
73
+ </p>
74
+ ) : null}
75
+ {showMessage ? (
76
+ <p className="text-xs leading-relaxed text-theme-text-secondary">
77
+ {issue.message}
78
+ </p>
79
+ ) : null}
80
+ </div>
81
+ );
82
+ }
83
+
84
+ export function StartupBody({ data }: { data: EvidenceDataOf<"startup"> }) {
85
+ const blocker = data.blocker;
86
+ const pods = data.pods && data.pods.length > 1 ? data.pods : [blocker.name];
87
+ return (
88
+ <div className="space-y-2">
89
+ <div className="flex flex-wrap gap-1.5">
90
+ <Badge tone="structural" size="sm">
91
+ {pods.length > 1 ? `${pods.length} ${blocker.kind}s` : blocker.kind}
92
+ </Badge>
93
+ {pods.map((pod) => (
94
+ <Badge key={pod} tone="structural" size="sm">
95
+ {pod}
96
+ </Badge>
97
+ ))}
98
+ <Badge severity={severityBadge(blocker.severity)} size="sm">
99
+ {blocker.severity}
100
+ </Badge>
101
+ </div>
102
+ <p className="text-sm leading-relaxed text-theme-text-primary">
103
+ {blocker.message}
104
+ </p>
105
+ </div>
106
+ );
107
+ }
108
+
109
+ export function CrashBody({ data }: { data: EvidenceDataOf<"crash"> }) {
110
+ const crash = data.crash;
111
+ return (
112
+ <div className="space-y-2.5">
113
+ <div className="flex flex-wrap gap-1.5">
114
+ <Badge severity="error" size="sm">
115
+ {crash.reason || crash.state}
116
+ </Badge>
117
+ <Badge tone="structural" size="sm">
118
+ exit {crash.exitCode}
119
+ </Badge>
120
+ <Badge tone="structural" size="sm">
121
+ {crash.container}
122
+ </Badge>
123
+ </div>
124
+ <p className="text-xs text-theme-text-tertiary">
125
+ {crash.pods.join(", ")} · {crash.logSource.replaceAll("_", " ")}
126
+ </p>
127
+ <TerminalBlock label="Selected crash line">{crash.logLine}</TerminalBlock>
128
+ </div>
129
+ );
130
+ }
131
+
132
+ export function ResourceBody({ data }: { data: EvidenceDataOf<"resource"> }) {
133
+ const replicas = data.resourceContext?.workloadSummary?.replicas;
134
+ // SealedSecret's dedicated body renders the resource's conditions beside
135
+ // its controller state, so repeating the derived summary here adds noise.
136
+ const conditions =
137
+ data.resource.kind.toLowerCase() === "sealedsecret"
138
+ ? []
139
+ : (data.resourceContext?.statusSummary?.conditions ?? []);
140
+ const desired = replicas?.desired;
141
+ const ready = replicas ? (replicas.ready ?? 0) : undefined;
142
+ const shortfall =
143
+ desired !== undefined && ready !== undefined && ready < desired;
144
+ const scalers = diagnosedScalers(data.resourceContext);
145
+ return (
146
+ <div className="space-y-3">
147
+ <InvestigationResourceEvidence resource={data.resource} />
148
+ {data.gitOpsDiagnosis ? (
149
+ <GitOpsStatusBody status={data.gitOpsDiagnosis} />
150
+ ) : null}
151
+ {desired !== undefined && ready !== undefined ? (
152
+ <dl className="flex flex-wrap gap-x-6 gap-y-2 text-xs">
153
+ <div>
154
+ <dt className="text-theme-text-tertiary">Ready replicas</dt>
155
+ <dd
156
+ className={clsx(
157
+ "font-mono font-semibold tabular-nums",
158
+ shortfall ? "text-warning-text" : "text-theme-text-primary",
159
+ )}
160
+ >
161
+ {ready}/{desired}
162
+ </dd>
163
+ </div>
164
+ <ResourceFact label="Available" value={replicas?.available} />
165
+ <ResourceFact label="Updated" value={replicas?.updated} />
166
+ <ResourceFact label="Unavailable" value={replicas?.unavailable} />
167
+ <ResourceFact
168
+ label="Phase"
169
+ value={data.resourceContext?.statusSummary?.phase}
170
+ />
171
+ </dl>
172
+ ) : null}
173
+ {conditions.length > 0 ? (
174
+ <div>
175
+ <div className="mb-1.5 text-xs font-semibold uppercase tracking-wide text-theme-text-tertiary">
176
+ Conditions
177
+ </div>
178
+ <div className="max-h-52 space-y-1.5 overflow-y-auto pr-1">
179
+ {conditions.map((condition) => (
180
+ <div
181
+ key={`${condition.type}-${condition.status}-${condition.reason ?? ""}`}
182
+ className="flex min-w-0 items-start gap-2 text-xs"
183
+ >
184
+ <StatusDot
185
+ tone={conditionStatusTone(condition)}
186
+ className="mt-1 shrink-0"
187
+ />
188
+ <p className="min-w-0 text-theme-text-secondary">
189
+ <span className="font-medium text-theme-text-primary">
190
+ {condition.type}={condition.status}
191
+ </span>
192
+ {condition.reason ? ` · ${condition.reason}` : ""}
193
+ {condition.message ? ` — ${condition.message}` : ""}
194
+ </p>
195
+ </div>
196
+ ))}
197
+ </div>
198
+ </div>
199
+ ) : null}
200
+ {scalers.length > 0 ? (
201
+ <ScaledBySection
202
+ scalers={scalers}
203
+ allScalers={data.resourceContext?.scaledBy ?? []}
204
+ />
205
+ ) : null}
206
+ {data.warnings.length > 0 ? (
207
+ <ul className="space-y-1 text-xs text-theme-text-secondary">
208
+ {data.warnings.map((warning) => (
209
+ <li key={warning} className="flex items-start gap-1.5">
210
+ <Info className="mt-0.5 h-3.5 w-3.5 shrink-0 text-theme-text-tertiary" />
211
+ <span>{warning}</span>
212
+ </li>
213
+ ))}
214
+ </ul>
215
+ ) : null}
216
+ </div>
217
+ );
218
+ }
219
+
220
+ type DiagnosedScaler = DiagnosisScalerRef &
221
+ Required<Pick<DiagnosisScalerRef, "hpaSummary">>;
222
+
223
+ export function diagnosedScalers(
224
+ context: DiagnosisResourceContext | undefined,
225
+ ): DiagnosedScaler[] {
226
+ return (context?.scaledBy ?? []).filter(
227
+ (scaler): scaler is DiagnosedScaler => scaler.hpaSummary !== undefined,
228
+ );
229
+ }
230
+
231
+ function ScaledBySection({
232
+ scalers,
233
+ allScalers,
234
+ }: {
235
+ scalers: DiagnosedScaler[];
236
+ allScalers: DiagnosisScalerRef[];
237
+ }) {
238
+ const { onOpenResource } = useContext(EvidenceNavigationContext);
239
+ // The ScaledObject is only a link when Radar listed it as a scaler too; a
240
+ // name read off the HPA's owner reference alone is not a resource we hold.
241
+ const listedScaler = (ref: DiagnosisResourceRef) =>
242
+ allScalers.some(
243
+ (scaler) =>
244
+ scaler.kind === ref.kind &&
245
+ scaler.name === ref.name &&
246
+ (scaler.namespace ?? "") === (ref.namespace ?? ""),
247
+ );
248
+ return (
249
+ <div>
250
+ <div className="mb-1.5 text-xs font-semibold uppercase tracking-wide text-theme-text-tertiary">
251
+ Scaled by
252
+ </div>
253
+ <div className="space-y-2">
254
+ {scalers.map((scaler) => (
255
+ <HPADiagnosisSummary
256
+ key={`${scaler.namespace ?? ""}/${scaler.name}`}
257
+ diagnosis={scaler.hpaSummary}
258
+ variant="inline"
259
+ header={
260
+ <>
261
+ <span className="font-medium text-theme-text-primary">
262
+ {displayKind(scaler.kind)} {scaler.name}
263
+ </span>
264
+ {scaler.managedBy ? (
265
+ <span className="text-theme-text-tertiary">
266
+ managed by KEDA {displayKind(scaler.managedBy.kind)}{" "}
267
+ <ResourceLink
268
+ name={scaler.managedBy.name}
269
+ kind={scaler.managedBy.kind}
270
+ namespace={scaler.managedBy.namespace ?? ""}
271
+ group={scaler.managedBy.group}
272
+ onNavigate={
273
+ onOpenResource && listedScaler(scaler.managedBy)
274
+ ? (ref) => onOpenResource(ref)
275
+ : undefined
276
+ }
277
+ />
278
+ </span>
279
+ ) : null}
280
+ </>
281
+ }
282
+ />
283
+ ))}
284
+ </div>
285
+ </div>
286
+ );
287
+ }
288
+
289
+ function GitOpsStatusBody({
290
+ status,
291
+ }: {
292
+ status: NonNullable<EvidenceDataOf<"resource">["gitOpsDiagnosis"]>;
293
+ }) {
294
+ const fields = [
295
+ ["Sync", status.sync],
296
+ ["Health", status.health],
297
+ ["Operation", status.operationPhase],
298
+ ["Ready", status.ready],
299
+ ] as const;
300
+ return (
301
+ <div className="rounded-md border border-theme-border bg-theme-base/40 p-2.5">
302
+ <div className="mb-2 flex flex-wrap items-center gap-1.5">
303
+ <span className="text-xs font-semibold uppercase tracking-wide text-theme-text-tertiary">
304
+ GitOps controller status
305
+ </span>
306
+ <Badge tone="note" size="sm">
307
+ {status.tool === "argocd" ? "Argo CD" : "Flux"}
308
+ </Badge>
309
+ {status.suspended ? (
310
+ <Badge severity="info" size="sm">
311
+ Suspended
312
+ </Badge>
313
+ ) : null}
314
+ </div>
315
+ <div className="flex flex-wrap gap-1.5">
316
+ {fields.map(([label, value]) =>
317
+ value ? (
318
+ <Badge
319
+ key={label}
320
+ severity={gitOpsValueSeverity(label, value)}
321
+ size="sm"
322
+ >
323
+ {label}: {value}
324
+ </Badge>
325
+ ) : null,
326
+ )}
327
+ {status.appliedRevision ? (
328
+ <Badge tone="structural" size="sm">
329
+ {status.appliedRevision}
330
+ </Badge>
331
+ ) : null}
332
+ </div>
333
+ </div>
334
+ );
335
+ }
@@ -6,11 +6,19 @@ import { adaptChanges } from "./adapters/changes";
6
6
  import { adaptDiagnose } from "./adapters/diagnose";
7
7
  import { adaptEvents } from "./adapters/events";
8
8
  import { adaptHelmRelease } from "./adapters/helm";
9
+ import {
10
+ adaptListHelmReleases,
11
+ adaptListPackages,
12
+ adaptSearch,
13
+ } from "./adapters/listings";
14
+ import { adaptTopResources } from "./adapters/rankings";
15
+ import { adaptClusterAudit, adaptUpgradeReadiness } from "./adapters/posture";
9
16
  import { adaptIssues } from "./adapters/issues";
10
17
  import { adaptPodLogs, adaptWorkloadLogs } from "./adapters/logs";
11
18
  import { adaptSubjectPermissions } from "./adapters/permissions";
12
19
  import {
13
20
  adaptPrometheusRules,
21
+ adaptDiscoverMetrics,
14
22
  adaptQueryPrometheus,
15
23
  } from "./adapters/prometheus";
16
24
  import {
@@ -66,6 +74,13 @@ const ADAPTERS: Record<
66
74
  get_resource: adaptGetResource,
67
75
  list_resources: adaptListResources,
68
76
  list_namespaces: adaptListNamespaces,
77
+ list_helm_releases: adaptListHelmReleases,
78
+ list_packages: adaptListPackages,
79
+ search: adaptSearch,
80
+ top_resources: adaptTopResources,
81
+ get_cluster_audit: adaptClusterAudit,
82
+ get_cluster_upgrade_readiness: adaptUpgradeReadiness,
83
+ discover_metrics: adaptDiscoverMetrics,
69
84
  get_events: adaptEvents,
70
85
  get_pod_logs: adaptPodLogs,
71
86
  get_changes: adaptChanges,