@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.
- package/package.json +2 -2
- package/src/api/client.ts +30 -0
- 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 +1560 -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 +11 -28
- package/src/components/diagnose/StoryExcerpt.tsx +118 -0
- package/src/components/diagnose/assessmentCopy.ts +169 -0
- package/src/components/diagnose/investigationCase.test.tsx +631 -2
- package/src/components/diagnose/investigationCase.ts +132 -24
- package/src/components/diagnose/investigationEvidence/adapters/coverage.test.tsx +660 -0
- 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 +134 -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 +11 -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/parts.test.tsx +3 -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/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/ui/CommandPalette.tsx +12 -8
- package/src/components/ui/DiagnosticsOverlay.tsx +7 -5
- package/src/components/ui/Omnibar.tsx +33 -10
- package/src/components/ui/ShortcutHelpOverlay.tsx +8 -5
- package/src/components/ui/Disclosure.tsx +0 -47
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { clsx } from "clsx";
|
|
2
|
+
import {
|
|
3
|
+
Collapse,
|
|
4
|
+
TerminalBlock,
|
|
5
|
+
TerminalBlockLabel,
|
|
6
|
+
} from "@skyhook-io/k8s-ui";
|
|
7
|
+
import { InventoryRow } from "./investigationEvidence/bodies/inventory";
|
|
8
|
+
import { RankingRow } from "./investigationEvidence/bodies/ranking";
|
|
9
|
+
import { PostureFindingRow } from "./investigationEvidence/bodies/posture";
|
|
10
|
+
import { MetricsBody } from "./investigationEvidence/bodies/metrics";
|
|
11
|
+
import type { StoryExcerpt as Excerpt } from "./investigationEvidence/excerpt";
|
|
12
|
+
import type { InvestigationEvidenceData } from "./investigationEvidence";
|
|
13
|
+
import type { MetricsChangeCoverage } from "./investigationMetrics";
|
|
14
|
+
|
|
15
|
+
/** The inline slice of a placed card, rendered from Radar's record. */
|
|
16
|
+
export function StoryExcerpt({
|
|
17
|
+
excerpt,
|
|
18
|
+
data,
|
|
19
|
+
prominence,
|
|
20
|
+
open,
|
|
21
|
+
canExpand,
|
|
22
|
+
changeCoverage,
|
|
23
|
+
}: {
|
|
24
|
+
excerpt: Excerpt;
|
|
25
|
+
data: InvestigationEvidenceData;
|
|
26
|
+
prominence: "primary" | "supporting" | "secondary";
|
|
27
|
+
open: boolean;
|
|
28
|
+
canExpand: boolean;
|
|
29
|
+
changeCoverage?: MetricsChangeCoverage;
|
|
30
|
+
}) {
|
|
31
|
+
const padding = prominence === "primary" ? "px-3 pb-2.5" : "px-2.5 pb-2";
|
|
32
|
+
switch (excerpt.kind) {
|
|
33
|
+
case "lines":
|
|
34
|
+
return (
|
|
35
|
+
<div data-story-log-lines className={padding}>
|
|
36
|
+
<TerminalBlock
|
|
37
|
+
footer={
|
|
38
|
+
excerpt.rest.length > 0 ? (
|
|
39
|
+
<Collapse open={open && canExpand}>
|
|
40
|
+
<TerminalBlockLabel divider>Earlier lines</TerminalBlockLabel>
|
|
41
|
+
<pre className="overflow-x-auto whitespace-pre-wrap break-words px-3 pb-2.5 font-mono text-xs leading-relaxed text-[var(--terminal-text)]">
|
|
42
|
+
{excerpt.rest.join("\n")}
|
|
43
|
+
</pre>
|
|
44
|
+
</Collapse>
|
|
45
|
+
) : null
|
|
46
|
+
}
|
|
47
|
+
>
|
|
48
|
+
{excerpt.head.join("\n")}
|
|
49
|
+
</TerminalBlock>
|
|
50
|
+
</div>
|
|
51
|
+
);
|
|
52
|
+
case "rows":
|
|
53
|
+
return (
|
|
54
|
+
<div data-story-inventory-rows className={padding}>
|
|
55
|
+
<div className="rounded-md border border-theme-border">
|
|
56
|
+
{excerpt.entries.map((entry, index) =>
|
|
57
|
+
entry.row ? (
|
|
58
|
+
<InventoryRow
|
|
59
|
+
key={entry.key}
|
|
60
|
+
resource={entry.row}
|
|
61
|
+
divider={index > 0}
|
|
62
|
+
/>
|
|
63
|
+
) : (
|
|
64
|
+
<p
|
|
65
|
+
key={entry.key}
|
|
66
|
+
data-story-inventory-absent={
|
|
67
|
+
entry.matches === 0 ? "" : undefined
|
|
68
|
+
}
|
|
69
|
+
className={clsx(
|
|
70
|
+
"px-2.5 py-1.5 font-mono text-xs text-theme-text-secondary",
|
|
71
|
+
index > 0 && "border-t border-theme-border/60",
|
|
72
|
+
)}
|
|
73
|
+
>
|
|
74
|
+
{entry.matches === 0
|
|
75
|
+
? `Not in this listing: ${entry.label}`
|
|
76
|
+
: `${entry.label}: ${entry.matches} entries match in this listing`}
|
|
77
|
+
</p>
|
|
78
|
+
),
|
|
79
|
+
)}
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
);
|
|
83
|
+
case "ranking":
|
|
84
|
+
return (
|
|
85
|
+
<div data-story-ranking-rows className={padding}>
|
|
86
|
+
<div className="rounded-md border border-theme-border">
|
|
87
|
+
{excerpt.rows.map((row, index) => (
|
|
88
|
+
<RankingRow
|
|
89
|
+
key={`${row.namespace ?? ""}/${row.name}`}
|
|
90
|
+
row={row}
|
|
91
|
+
divider={index > 0}
|
|
92
|
+
/>
|
|
93
|
+
))}
|
|
94
|
+
</div>
|
|
95
|
+
</div>
|
|
96
|
+
);
|
|
97
|
+
case "posture":
|
|
98
|
+
return (
|
|
99
|
+
<div data-story-posture-findings className={padding}>
|
|
100
|
+
<div className="rounded-md border border-theme-border">
|
|
101
|
+
{excerpt.findings.map((finding, index) => (
|
|
102
|
+
<PostureFindingRow
|
|
103
|
+
key={`${finding.name}-${finding.check}`}
|
|
104
|
+
finding={finding}
|
|
105
|
+
divider={index > 0}
|
|
106
|
+
/>
|
|
107
|
+
))}
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
);
|
|
111
|
+
case "chart":
|
|
112
|
+
return data.type === "metrics" ? (
|
|
113
|
+
<div data-story-metrics className={padding}>
|
|
114
|
+
<MetricsBody data={data} changeCoverage={changeCoverage} />
|
|
115
|
+
</div>
|
|
116
|
+
) : null;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import type { Diagnosis } from "../../api/diagnose";
|
|
2
|
+
import { storyPlainText } from "./investigationStory";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The whole assessment as text: headline, certainty, what is unresolved, the
|
|
6
|
+
* technical cause, the story without its markers, and the steps. Copying only
|
|
7
|
+
* the headline would ship the persuasive half without its caveats.
|
|
8
|
+
*/
|
|
9
|
+
/** What Radar adds to the agent's verdict when the assessment is copied for a channel. */
|
|
10
|
+
export interface AssessmentCopyRadar {
|
|
11
|
+
/** Reads Radar could not complete, as shown in Still open. */
|
|
12
|
+
limits?: readonly string[];
|
|
13
|
+
/** Adverse Radar cards the agent explained, as shown in Still open. */
|
|
14
|
+
signals?: readonly string[];
|
|
15
|
+
/** Adverse Radar cards nothing explains, as shown above a healthy verdict. */
|
|
16
|
+
flags?: readonly string[];
|
|
17
|
+
/** Where and when: the header line a reader in a channel needs first. */
|
|
18
|
+
context?: {
|
|
19
|
+
target: string;
|
|
20
|
+
cluster?: string;
|
|
21
|
+
startedAt?: string;
|
|
22
|
+
agent?: string;
|
|
23
|
+
};
|
|
24
|
+
/** The placed cards, as short excerpts: the receipts behind the headline. */
|
|
25
|
+
receipts?: readonly {
|
|
26
|
+
title: string;
|
|
27
|
+
role?: string;
|
|
28
|
+
lines: readonly string[];
|
|
29
|
+
/** The agent's statement of what this result does not cover. */
|
|
30
|
+
gap?: string;
|
|
31
|
+
}[];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* The assessment as Markdown for a channel or a ticket: context, headline,
|
|
36
|
+
* cause, the receipts, what is still open, the next step. The full story and
|
|
37
|
+
* every captured result stay in Radar; the text says so.
|
|
38
|
+
*/
|
|
39
|
+
export function assessmentCopyText(
|
|
40
|
+
diagnosis: Diagnosis,
|
|
41
|
+
radar: AssessmentCopyRadar = {},
|
|
42
|
+
): string {
|
|
43
|
+
const parts: string[] = [];
|
|
44
|
+
if (radar.context) {
|
|
45
|
+
const when = radar.context.startedAt
|
|
46
|
+
? new Date(radar.context.startedAt).toLocaleString(undefined, {
|
|
47
|
+
dateStyle: "medium",
|
|
48
|
+
timeStyle: "short",
|
|
49
|
+
})
|
|
50
|
+
: undefined;
|
|
51
|
+
parts.push(
|
|
52
|
+
[
|
|
53
|
+
`**${radar.context.target}**`,
|
|
54
|
+
radar.context.cluster,
|
|
55
|
+
when,
|
|
56
|
+
radar.context.agent ? `via ${radar.context.agent}` : undefined,
|
|
57
|
+
]
|
|
58
|
+
.filter(Boolean)
|
|
59
|
+
.join(" · "),
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
if (diagnosis.summary) {
|
|
63
|
+
const certainty = diagnosis.healthy
|
|
64
|
+
? "Healthy"
|
|
65
|
+
: diagnosis.certainty
|
|
66
|
+
? CERTAINTY_LABEL[diagnosis.certainty]
|
|
67
|
+
: undefined;
|
|
68
|
+
parts.push(
|
|
69
|
+
`**${diagnosis.summary.trim()}**${certainty ? ` — ${certainty} (agent's word)` : ""}`,
|
|
70
|
+
);
|
|
71
|
+
} else if (diagnosis.certainty) {
|
|
72
|
+
parts.push(`Certainty (agent): ${CERTAINTY_LABEL[diagnosis.certainty]}`);
|
|
73
|
+
}
|
|
74
|
+
// The pasted text must not read more confident than the screen: Radar's
|
|
75
|
+
// own qualifications travel with the agent's.
|
|
76
|
+
const flags = (radar.flags ?? []).filter((item) => item.trim());
|
|
77
|
+
if (flags.length > 0)
|
|
78
|
+
parts.push(
|
|
79
|
+
[
|
|
80
|
+
"Radar flagged:",
|
|
81
|
+
...flags.map((item) => `- ${item}`),
|
|
82
|
+
"Read those cards before treating this as an all-clear.",
|
|
83
|
+
].join("\n"),
|
|
84
|
+
);
|
|
85
|
+
const unresolved = [
|
|
86
|
+
...(diagnosis.unresolved ?? []),
|
|
87
|
+
...(radar.limits ?? []),
|
|
88
|
+
...(radar.signals ?? []),
|
|
89
|
+
].filter((item) => item.trim());
|
|
90
|
+
if (unresolved.length > 0)
|
|
91
|
+
parts.push(
|
|
92
|
+
["Still open:", ...unresolved.map((item) => `- ${item}`)].join("\n"),
|
|
93
|
+
);
|
|
94
|
+
if (diagnosis.rootCause) parts.push(storyPlainText(diagnosis.rootCause));
|
|
95
|
+
const receipts = (radar.receipts ?? []).filter((receipt) => receipt.title);
|
|
96
|
+
if (receipts.length > 0) {
|
|
97
|
+
parts.push(
|
|
98
|
+
[
|
|
99
|
+
"Evidence (Radar):",
|
|
100
|
+
...receipts.map((receipt) =>
|
|
101
|
+
[
|
|
102
|
+
`- **${receipt.title}**${receipt.role ? ` · ${receipt.role}` : ""}`,
|
|
103
|
+
...receipt.lines
|
|
104
|
+
.filter((line) => line.trim())
|
|
105
|
+
.map((line) => ` > ${line.trim()}`),
|
|
106
|
+
...(receipt.gap ? [` Not shown: ${receipt.gap}`] : []),
|
|
107
|
+
].join("\n"),
|
|
108
|
+
),
|
|
109
|
+
].join("\n"),
|
|
110
|
+
);
|
|
111
|
+
} else if (!radar.context) {
|
|
112
|
+
const story = storyPlainText(diagnosis.report ?? "");
|
|
113
|
+
if (story) parts.push(story);
|
|
114
|
+
}
|
|
115
|
+
const steps = diagnosis.steps?.length
|
|
116
|
+
? diagnosis.steps.map(
|
|
117
|
+
(step, index) =>
|
|
118
|
+
`${index + 1}. [${STEP_KIND_LABEL[step.kind]}] ${step.text}${step.precondition ? ` (only if ${step.precondition})` : ""}`,
|
|
119
|
+
)
|
|
120
|
+
: (diagnosis.remediation ?? []).map(
|
|
121
|
+
(text, index) => `${index + 1}. ${text}`,
|
|
122
|
+
);
|
|
123
|
+
if (steps.length > 0) {
|
|
124
|
+
if (radar.context) {
|
|
125
|
+
// A channel gets the recommended step; the rest wait in Radar.
|
|
126
|
+
const lead = diagnosis.recommendedIndex
|
|
127
|
+
? diagnosis.recommendedIndex - 1
|
|
128
|
+
: 0;
|
|
129
|
+
const first = steps[lead] ?? steps[0];
|
|
130
|
+
const rest = steps.length - 1;
|
|
131
|
+
parts.push(
|
|
132
|
+
[
|
|
133
|
+
`Next step: ${first.replace(/^\d+\. /, "")}`,
|
|
134
|
+
diagnosis.recommendedReason
|
|
135
|
+
? `Why: ${diagnosis.recommendedReason}`
|
|
136
|
+
: undefined,
|
|
137
|
+
rest > 0
|
|
138
|
+
? `${rest} more ${rest === 1 ? "step" : "steps"} in Radar.`
|
|
139
|
+
: undefined,
|
|
140
|
+
]
|
|
141
|
+
.filter(Boolean)
|
|
142
|
+
.join("\n"),
|
|
143
|
+
);
|
|
144
|
+
} else parts.push(["Next steps:", ...steps].join("\n"));
|
|
145
|
+
}
|
|
146
|
+
if (radar.context)
|
|
147
|
+
parts.push(
|
|
148
|
+
"Full analysis and every captured result: open the investigation in Radar.",
|
|
149
|
+
);
|
|
150
|
+
return parts.join("\n\n");
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export const CERTAINTY_LABEL: Record<
|
|
154
|
+
NonNullable<Diagnosis["certainty"]>,
|
|
155
|
+
string
|
|
156
|
+
> = {
|
|
157
|
+
established: "Established",
|
|
158
|
+
likely: "Likely",
|
|
159
|
+
suspected: "Suspected",
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
export const STEP_KIND_LABEL: Record<
|
|
163
|
+
NonNullable<Diagnosis["steps"]>[number]["kind"],
|
|
164
|
+
string
|
|
165
|
+
> = {
|
|
166
|
+
mitigate: "Mitigate",
|
|
167
|
+
verify: "Verify",
|
|
168
|
+
investigate: "Investigate",
|
|
169
|
+
};
|