@skyhook-io/radar-app 1.14.2 → 1.14.4
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/api/client.ts +32 -0
- package/src/api/diagnose.ts +7 -1
- package/src/components/CloudConnectFlow.tsx +21 -7
- package/src/components/CloudFunnelButton.tsx +85 -8
- package/src/components/cloudConnectHandoff.test.ts +10 -1
- package/src/components/cloudConnectHandoff.ts +15 -1
- package/src/components/diagnose/AIMarkdown.tsx +87 -0
- package/src/components/diagnose/ActivityTurn.tsx +1244 -0
- package/src/components/diagnose/AgentControls.tsx +603 -0
- package/src/components/diagnose/AnalysisStory.test.tsx +31 -25
- package/src/components/diagnose/AnalysisStory.tsx +13 -18
- package/src/components/diagnose/ApplyDialog.tsx +289 -0
- package/src/components/diagnose/AssessmentCard.tsx +1579 -0
- package/src/components/diagnose/DiagnoseSurface.test.tsx +9 -9
- package/src/components/diagnose/DiagnoseSurface.tsx +5 -1
- package/src/components/diagnose/EvidenceCard.tsx +611 -0
- package/src/components/diagnose/Home.test.tsx +1 -3
- package/src/components/diagnose/Home.tsx +2 -1
- package/src/components/diagnose/InvestigationEvidencePane.story.test.tsx +6 -8
- package/src/components/diagnose/InvestigationEvidencePane.test.tsx +5 -3
- package/src/components/diagnose/InvestigationEvidencePane.tsx +31 -3445
- package/src/components/diagnose/InvestigationEvidenceSections.tsx +523 -0
- package/src/components/diagnose/InvestigationView.tsx +39 -51
- package/src/components/diagnose/StoryExcerpt.tsx +118 -0
- package/src/components/diagnose/assessmentCopy.ts +169 -0
- package/src/components/diagnose/investigationCase.test.tsx +658 -2
- package/src/components/diagnose/investigationCase.ts +135 -26
- package/src/components/diagnose/investigationEvidence/adapters/coverage.test.tsx +682 -0
- package/src/components/diagnose/investigationEvidence/adapters/diagnose.test.ts +8 -2
- package/src/components/diagnose/investigationEvidence/adapters/diagnose.ts +1 -1
- package/src/components/diagnose/investigationEvidence/adapters/{helm.test.ts → helm.test.tsx} +63 -0
- package/src/components/diagnose/investigationEvidence/adapters/helm.ts +70 -1
- package/src/components/diagnose/investigationEvidence/adapters/listings.ts +136 -0
- package/src/components/diagnose/investigationEvidence/adapters/{permissions.test.ts → permissions.test.tsx} +50 -0
- package/src/components/diagnose/investigationEvidence/adapters/permissions.ts +18 -0
- package/src/components/diagnose/investigationEvidence/adapters/posture.ts +272 -0
- package/src/components/diagnose/investigationEvidence/adapters/prometheus.ts +49 -0
- package/src/components/diagnose/investigationEvidence/adapters/rankings.ts +147 -0
- package/src/components/diagnose/investigationEvidence/adapters/resource.ts +20 -10
- package/src/components/diagnose/investigationEvidence/bodies/index.tsx +127 -0
- package/src/components/diagnose/investigationEvidence/bodies/inventory.tsx +158 -0
- package/src/components/diagnose/investigationEvidence/bodies/metrics.tsx +373 -0
- package/src/components/diagnose/investigationEvidence/bodies/network.tsx +274 -0
- package/src/components/diagnose/investigationEvidence/bodies/platform.tsx +475 -0
- package/src/components/diagnose/investigationEvidence/bodies/posture.tsx +60 -0
- package/src/components/diagnose/investigationEvidence/bodies/ranking.tsx +76 -0
- package/src/components/diagnose/investigationEvidence/bodies/streams.tsx +212 -0
- package/src/components/diagnose/investigationEvidence/bodies/workload.tsx +335 -0
- package/src/components/diagnose/investigationEvidence/builder.ts +15 -0
- package/src/components/diagnose/investigationEvidence/cardParts.tsx +312 -0
- package/src/components/diagnose/investigationEvidence/excerpt.test.ts +105 -0
- package/src/components/diagnose/investigationEvidence/excerpt.ts +92 -0
- package/src/components/diagnose/investigationEvidence/identity.test.ts +5 -7
- package/src/components/diagnose/investigationEvidence/index.ts +1 -1
- package/src/components/diagnose/investigationEvidence/navigation.tsx +26 -0
- package/src/components/diagnose/investigationEvidence/observations.ts +13 -0
- package/src/components/diagnose/investigationEvidence/projection.test.ts +1 -1
- package/src/components/diagnose/investigationEvidence/types.ts +97 -2
- package/src/components/diagnose/investigationEvidenceKinds.ts +22 -0
- package/src/components/diagnose/investigationEvidencePartition.ts +296 -0
- package/src/components/diagnose/investigationState.test.ts +2 -2
- package/src/components/diagnose/investigationState.ts +2 -2
- package/src/components/diagnose/investigationStory.ts +1 -1
- package/src/components/diagnose/parts.test.tsx +15 -1
- package/src/components/diagnose/parts.tsx +26 -3904
- package/src/components/diagnose/target.ts +9 -3
- package/src/components/diagnose/toolCallLabel.test.ts +47 -1
- package/src/components/diagnose/toolCallLabel.ts +41 -2
- package/src/components/helm/HelmReleaseDrawer.tsx +1 -1
- package/src/components/home/MCPSetupDialog.tsx +1 -1
- package/src/components/home/mcpToolCatalog.ts +27 -0
- package/src/components/resource/PrometheusChartsGrid.tsx +1 -1
- package/src/components/resource/WorkloadMetricsHelpDialog.tsx +1 -1
- package/src/components/resource/WorkloadMetricsSection.tsx +1 -1
- package/src/components/resources/PodFilePreview.tsx +1 -1
- package/src/components/rightsizing/RightsizingScanView.tsx +20 -1
- package/src/components/ui/CommandPalette.tsx +12 -8
- package/src/components/ui/DiagnosticsOverlay.tsx +7 -5
- package/src/components/ui/Omnibar.tsx +96 -60
- package/src/components/ui/ShortcutHelpOverlay.tsx +8 -5
- package/src/components/workload/WorkloadView.tsx +7 -1
- package/src/components/ui/Disclosure.tsx +0 -47
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { pluralToKind } from "@skyhook-io/k8s-ui";
|
|
2
|
-
|
|
3
1
|
import type {
|
|
4
2
|
DiagnosisEvidenceItem,
|
|
5
3
|
DiagnosisEvidenceRole,
|
|
@@ -10,6 +8,8 @@ import {
|
|
|
10
8
|
investigationEvidenceSubjectRef,
|
|
11
9
|
investigationSourceArgs,
|
|
12
10
|
isInvestigationEvidenceRef,
|
|
11
|
+
sameKind,
|
|
12
|
+
type InvestigationEvidenceData,
|
|
13
13
|
type InvestigationEvidenceGroup,
|
|
14
14
|
type InvestigationEvidenceObservation,
|
|
15
15
|
type InvestigationEvidenceProjection,
|
|
@@ -54,7 +54,7 @@ export interface InvestigationCaseResolution {
|
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
/**
|
|
57
|
-
* One half of a Go↔TS contract: `
|
|
57
|
+
* One half of a Go↔TS contract: `Roles` in pkg/investigation/verdict.go and the
|
|
58
58
|
* DiagnosisEvidenceRole union in api/diagnose.ts must list exactly these roles.
|
|
59
59
|
* A role the parser accepts but this set omits is bound server-side and then
|
|
60
60
|
* silently dropped here. Change all three together.
|
|
@@ -140,22 +140,23 @@ export function resolveInvestigationCase(
|
|
|
140
140
|
source,
|
|
141
141
|
placement: "source",
|
|
142
142
|
};
|
|
143
|
+
const produced = projection.groups.flatMap((group) =>
|
|
144
|
+
group.observations
|
|
145
|
+
.filter((observation) => observation.source.id === source.id)
|
|
146
|
+
.map((observation) => ({ group, observation })),
|
|
147
|
+
);
|
|
143
148
|
const candidates = subjectUnusable
|
|
144
149
|
? []
|
|
145
|
-
:
|
|
146
|
-
group
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
targetPods,
|
|
156
|
-
)),
|
|
157
|
-
)
|
|
158
|
-
.map((observation) => ({ group, observation })),
|
|
150
|
+
: produced.filter(
|
|
151
|
+
({ group, observation }) =>
|
|
152
|
+
!subject ||
|
|
153
|
+
observationMatchesSubject(
|
|
154
|
+
group,
|
|
155
|
+
observation,
|
|
156
|
+
subject,
|
|
157
|
+
targetPods,
|
|
158
|
+
produced.length === 1,
|
|
159
|
+
),
|
|
159
160
|
);
|
|
160
161
|
if (candidates.length === 1) {
|
|
161
162
|
const { group, observation } = candidates[0];
|
|
@@ -276,6 +277,19 @@ function observationSubjectIdentity(
|
|
|
276
277
|
const args = investigationSourceArgs(observation.source);
|
|
277
278
|
// A listing whose call names no kind (list_namespaces) is still a listing
|
|
278
279
|
// of the one kind its rows carry.
|
|
280
|
+
// A ranking is a listing of what it ranked; a posture card is a listing of
|
|
281
|
+
// the resources its findings are about.
|
|
282
|
+
if (data.type === "ranking" || data.type === "posture") {
|
|
283
|
+
const rows = data.type === "ranking" ? data.rows : data.findings;
|
|
284
|
+
const kinds = new Set(rows.map((row) => row.kind));
|
|
285
|
+
if (kinds.size !== 1) return undefined;
|
|
286
|
+
return {
|
|
287
|
+
kind: [...kinds][0],
|
|
288
|
+
namespace: nonEmptyString(args?.namespace) ? args.namespace : undefined,
|
|
289
|
+
name: "",
|
|
290
|
+
listing: true,
|
|
291
|
+
};
|
|
292
|
+
}
|
|
279
293
|
if (data.type === "inventory" && !nonEmptyString(args?.kind)) {
|
|
280
294
|
const kinds = new Set(data.resources.map((resource) => resource.kind));
|
|
281
295
|
if (kinds.size !== 1) return undefined;
|
|
@@ -311,31 +325,76 @@ function isBuiltInGroup(group: string): boolean {
|
|
|
311
325
|
return LEGACY_BUILT_IN_GROUPS.has(group) || group.endsWith(".k8s.io");
|
|
312
326
|
}
|
|
313
327
|
|
|
314
|
-
export
|
|
315
|
-
|
|
328
|
+
export { sameKind };
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Whether a listing row is the entry a citation names. A package row carries
|
|
332
|
+
* a kind of Radar's own, so the agent names it by the kind it knows the
|
|
333
|
+
* package as (the Flux HelmRelease, the chart); every other row is named by
|
|
334
|
+
* its kind.
|
|
335
|
+
*/
|
|
336
|
+
export function listingRowNamesSubject(
|
|
337
|
+
row: { kind: string; name: string },
|
|
338
|
+
subject: { kind: string; name?: string },
|
|
339
|
+
): boolean {
|
|
340
|
+
return (
|
|
341
|
+
subject.name !== undefined &&
|
|
342
|
+
row.name === subject.name &&
|
|
343
|
+
(row.kind === "Package" || sameKind(row.kind, subject.kind))
|
|
344
|
+
);
|
|
316
345
|
}
|
|
317
346
|
|
|
347
|
+
// The vocabulary the agent places with names evidence kinds a reader would
|
|
348
|
+
// distinguish (logs, events, changes, metrics, alerts, issue); everything a
|
|
349
|
+
// call returns about a resource is "resource" to it, whether Radar draws that
|
|
350
|
+
// as a resource card, a listing, a ranking, a posture card, a Helm release, a
|
|
351
|
+
// permissions check or a neighborhood. The kinds a diagnose bundle yields
|
|
352
|
+
// beside its resource (startup, crash, dns, network, receipts) stay out so
|
|
353
|
+
// "resource" still picks one observation of that bundle.
|
|
354
|
+
const RESOURCE_SHAPED: ReadonlySet<InvestigationEvidenceData["type"]> = new Set<
|
|
355
|
+
InvestigationEvidenceData["type"]
|
|
356
|
+
>([
|
|
357
|
+
"resource",
|
|
358
|
+
"inventory",
|
|
359
|
+
"ranking",
|
|
360
|
+
"posture",
|
|
361
|
+
"helm",
|
|
362
|
+
"permissions",
|
|
363
|
+
"topology",
|
|
364
|
+
"relationships",
|
|
365
|
+
]);
|
|
366
|
+
|
|
318
367
|
/**
|
|
319
368
|
* A discriminator the agent omits is a wildcard, and so is one the producer
|
|
320
369
|
* did not state; every discriminator both sides supply must match. Uniqueness
|
|
321
370
|
* of the match, not completeness of the subject, is what places a claim.
|
|
371
|
+
*
|
|
372
|
+
* The evidence word tells observations of one call apart, and a receipt is
|
|
373
|
+
* not a kind of its own: it is what a call of any kind returned when it found
|
|
374
|
+
* nothing (a metric search, a scan with nothing about the target). A call
|
|
375
|
+
* whose only observation is a receipt is not held to the word; what the
|
|
376
|
+
* subject names still has to match.
|
|
322
377
|
*/
|
|
323
378
|
function observationMatchesSubject(
|
|
324
379
|
group: InvestigationEvidenceGroup,
|
|
325
380
|
observation: InvestigationEvidenceObservation,
|
|
326
381
|
subject: DiagnosisEvidenceSubject,
|
|
327
382
|
targetPods: ReadonlySet<string>,
|
|
383
|
+
sole: boolean,
|
|
328
384
|
): boolean {
|
|
329
|
-
|
|
385
|
+
const soleReceipt = sole && observation.data.type === "receipt";
|
|
386
|
+
if (subject.observation !== undefined && !soleReceipt) {
|
|
330
387
|
// A diagnose bundle captures several vitals charts for one resource, so
|
|
331
388
|
// "metrics" alone cannot name one; "metrics:<category>" picks the chart
|
|
332
389
|
// whose identity ends in that category. An agent-run query is one chart,
|
|
333
390
|
// so a qualifier on it carries no meaning.
|
|
334
|
-
const [
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
kind
|
|
338
|
-
|
|
391
|
+
const [word, qualifier] = subject.observation.toLowerCase().split(":", 2);
|
|
392
|
+
const kind = word === "issues" ? "issue" : word;
|
|
393
|
+
if (
|
|
394
|
+
kind !== observation.data.type &&
|
|
395
|
+
!(kind === "resource" && RESOURCE_SHAPED.has(observation.data.type))
|
|
396
|
+
)
|
|
397
|
+
return false;
|
|
339
398
|
if (
|
|
340
399
|
qualifier !== undefined &&
|
|
341
400
|
observation.data.type === "metrics" &&
|
|
@@ -371,6 +430,36 @@ function observationIdentities(
|
|
|
371
430
|
): ObservationSubjectIdentity[] {
|
|
372
431
|
const stated = observationSubjectIdentity(observation);
|
|
373
432
|
const identities = stated ? [stated] : [];
|
|
433
|
+
// A ranking is also each row it ranks and the workload each pod belongs
|
|
434
|
+
// to; a posture card each resource its findings name and the workload that
|
|
435
|
+
// owns it: a citation of the workload reaches its pods' rows and the
|
|
436
|
+
// finding on its HPA, and a card of mixed kinds accepts no name it does
|
|
437
|
+
// not hold.
|
|
438
|
+
if (
|
|
439
|
+
observation.data.type === "ranking" ||
|
|
440
|
+
observation.data.type === "posture"
|
|
441
|
+
) {
|
|
442
|
+
const rows =
|
|
443
|
+
observation.data.type === "ranking"
|
|
444
|
+
? observation.data.rows.flatMap((row) =>
|
|
445
|
+
row.owner ? [row, row.owner] : [row],
|
|
446
|
+
)
|
|
447
|
+
: observation.data.findings.flatMap((finding) =>
|
|
448
|
+
finding.managedBy ? [finding, finding.managedBy] : [finding],
|
|
449
|
+
);
|
|
450
|
+
const seen = new Set<string>();
|
|
451
|
+
for (const row of rows) {
|
|
452
|
+
const key = `${row.kind}/${row.namespace ?? ""}/${row.name}`;
|
|
453
|
+
if (seen.has(key)) continue;
|
|
454
|
+
seen.add(key);
|
|
455
|
+
identities.push({
|
|
456
|
+
kind: row.kind,
|
|
457
|
+
group: "group" in row ? row.group : undefined,
|
|
458
|
+
namespace: row.namespace ?? "",
|
|
459
|
+
name: row.name,
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
}
|
|
374
463
|
if (observation.data.type === "logs" && stated) {
|
|
375
464
|
const args = investigationSourceArgs(observation.source);
|
|
376
465
|
if (args && nonEmptyString(args.kind) && nonEmptyString(args.name)) {
|
|
@@ -432,7 +521,15 @@ function identityMatchesSubject(
|
|
|
432
521
|
))
|
|
433
522
|
)
|
|
434
523
|
return true;
|
|
435
|
-
|
|
524
|
+
// Naming an entry the listing holds is naming the listing, whatever kind
|
|
525
|
+
// the listing itself goes by.
|
|
526
|
+
const namesHeldEntry =
|
|
527
|
+
identity.listing &&
|
|
528
|
+
observation.data.type === "inventory" &&
|
|
529
|
+
observation.data.resources.some((resource) =>
|
|
530
|
+
listingRowNamesSubject(resource, subject),
|
|
531
|
+
);
|
|
532
|
+
if (!namesHeldEntry && !sameKind(identity.kind, subject.kind)) return false;
|
|
436
533
|
// A subject with no name (a listing cited for what it does not contain) is
|
|
437
534
|
// a wildcard that uniqueness still gates. A listing has no name of its own; the agent naming the entry it means
|
|
438
535
|
// ("ConfigMap kube-root-ca.crt" in the ConfigMaps of a namespace) still
|
|
@@ -456,7 +553,19 @@ function identityMatchesSubject(
|
|
|
456
553
|
) {
|
|
457
554
|
return false;
|
|
458
555
|
}
|
|
556
|
+
// The object declaring a package lives where it lives, not in the
|
|
557
|
+
// listing's scope; every other held entry is named in the scope it has.
|
|
558
|
+
const namesDeclaredPackage =
|
|
559
|
+
namesHeldEntry &&
|
|
560
|
+
!sameKind(subject.kind, "Package") &&
|
|
561
|
+
observation.data.type === "inventory" &&
|
|
562
|
+
observation.data.resources.some(
|
|
563
|
+
(resource) =>
|
|
564
|
+
resource.kind === "Package" &&
|
|
565
|
+
listingRowNamesSubject(resource, subject),
|
|
566
|
+
);
|
|
459
567
|
if (
|
|
568
|
+
!namesDeclaredPackage &&
|
|
460
569
|
subject.namespace !== undefined &&
|
|
461
570
|
identity.namespace !== undefined &&
|
|
462
571
|
subject.namespace !== identity.namespace
|