@skyhook-io/radar-app 1.13.5 → 1.14.0

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 (201) hide show
  1. package/package.json +8 -8
  2. package/src/App.tsx +60 -24
  3. package/src/api/client.authRedirect.test.ts +12 -1
  4. package/src/api/client.prometheusStatus.test.ts +24 -0
  5. package/src/api/client.ts +321 -16
  6. package/src/api/diagnose.ts +1 -0
  7. package/src/api/drain-plan.test.ts +86 -0
  8. package/src/api/quotas.test.ts +26 -0
  9. package/src/api/quotas.ts +20 -0
  10. package/src/api/workloadMetrics.test.ts +40 -0
  11. package/src/api/workloadMetrics.ts +68 -0
  12. package/src/components/CloudConnectFlow.tsx +179 -42
  13. package/src/components/CloudFunnelButton.tsx +107 -36
  14. package/src/components/SyncProgressPanel.tsx +73 -0
  15. package/src/components/audit/AuditView.tsx +2 -0
  16. package/src/components/cloudConnectHandoff.test.ts +140 -0
  17. package/src/components/cloudConnectHandoff.ts +124 -0
  18. package/src/components/diagnose/DiagnoseContext.tsx +2 -1
  19. package/src/components/diagnose/investigationEvidence/adapters/changes.ts +60 -0
  20. package/src/components/diagnose/investigationEvidence/adapters/diagnose.test.ts +573 -0
  21. package/src/components/diagnose/investigationEvidence/adapters/diagnose.ts +935 -0
  22. package/src/components/diagnose/investigationEvidence/adapters/events.ts +96 -0
  23. package/src/components/diagnose/investigationEvidence/adapters/helm.test.ts +242 -0
  24. package/src/components/diagnose/investigationEvidence/adapters/helm.ts +208 -0
  25. package/src/components/diagnose/investigationEvidence/adapters/issues.ts +172 -0
  26. package/src/components/diagnose/investigationEvidence/adapters/logs.test.ts +283 -0
  27. package/src/components/diagnose/investigationEvidence/adapters/logs.ts +183 -0
  28. package/src/components/diagnose/investigationEvidence/adapters/permissions.test.ts +528 -0
  29. package/src/components/diagnose/investigationEvidence/adapters/permissions.ts +292 -0
  30. package/src/components/diagnose/investigationEvidence/adapters/prometheus.test.ts +1641 -0
  31. package/src/components/diagnose/investigationEvidence/adapters/prometheus.ts +769 -0
  32. package/src/components/diagnose/investigationEvidence/adapters/resource.test.ts +54 -0
  33. package/src/components/diagnose/investigationEvidence/adapters/resource.ts +257 -0
  34. package/src/components/diagnose/investigationEvidence/adapters/topology.ts +207 -0
  35. package/src/components/diagnose/investigationEvidence/builder.ts +418 -0
  36. package/src/components/diagnose/investigationEvidence/evidenceFixtures.ts +179 -0
  37. package/src/components/diagnose/investigationEvidence/identity.test.ts +614 -0
  38. package/src/components/diagnose/investigationEvidence/identity.ts +261 -0
  39. package/src/components/diagnose/investigationEvidence/index.ts +55 -0
  40. package/src/components/diagnose/investigationEvidence/observations.ts +954 -0
  41. package/src/components/diagnose/investigationEvidence/parse.ts +467 -0
  42. package/src/components/diagnose/investigationEvidence/projection.test.ts +2703 -0
  43. package/src/components/diagnose/investigationEvidence/types.ts +512 -0
  44. package/src/components/diagnose/target.test.ts +19 -0
  45. package/src/components/diagnose/target.ts +3 -2
  46. package/src/components/gitops/GitOpsView.tsx +7 -0
  47. package/src/components/gitops/RemoteDestinationCloudHint.tsx +26 -0
  48. package/src/components/resource/PrometheusCharts.overlays.test.ts +22 -0
  49. package/src/components/resource/PrometheusCharts.tsx +4 -3
  50. package/src/components/resource/PrometheusChartsGrid.render.test.tsx +99 -0
  51. package/src/components/resource/PrometheusChartsGrid.tsx +75 -35
  52. package/src/components/resource/RightsizingStrip.test.ts +38 -0
  53. package/src/components/resource/RightsizingStrip.tsx +15 -3
  54. package/src/components/resource/WorkloadMetricsHelpDialog.test.tsx +79 -0
  55. package/src/components/resource/WorkloadMetricsHelpDialog.tsx +145 -0
  56. package/src/components/resource/WorkloadMetricsSection.render.test.tsx +351 -0
  57. package/src/components/resource/WorkloadMetricsSection.tsx +486 -0
  58. package/src/components/resource/workloadMetricValues.test.ts +65 -0
  59. package/src/components/resource/workloadMetricValues.ts +30 -0
  60. package/src/components/resources/ResourcesView.tsx +44 -6
  61. package/src/components/resources/renderers/NamespaceRenderer.tsx +7 -1
  62. package/src/components/resources/renderers/PodRenderer.tsx +4 -0
  63. package/src/components/resources/renderers/RolloutRenderer.tsx +15 -1
  64. package/src/components/resources/renderers/WorkloadRenderer.tsx +3 -0
  65. package/src/components/rightsizing/RightsizingScanView.tsx +21 -0
  66. package/src/components/settings/SettingsDialog.tsx +94 -14
  67. package/src/components/traffic/TrafficFlowList.tsx +229 -70
  68. package/src/components/traffic/TrafficGraph.tsx +6 -6
  69. package/src/components/traffic/TrafficView.tsx +10 -9
  70. package/src/components/traffic/trafficFilters.test.ts +28 -1
  71. package/src/components/traffic/trafficFilters.ts +31 -0
  72. package/src/components/workload/WorkloadView.tsx +19 -8
  73. package/src/context/ConnectionContext.tsx +48 -3
  74. package/src/context/DiagnoseCustomization.tsx +1 -0
  75. package/src/index.css +17 -0
  76. package/src/utils/navigation.ts +1 -1
  77. package/src/utils/topology-namespace.test.ts +64 -0
  78. package/src/utils/topology-namespace.ts +18 -0
  79. package/src/components/diagnose/investigationEvidence.test.ts +0 -6617
  80. package/src/components/diagnose/investigationEvidence.ts +0 -5572
  81. package/src/components/gitops/GitOpsActions.tsx +0 -1
  82. package/src/components/gitops/GitOpsStatusBadge.tsx +0 -1
  83. package/src/components/gitops/ManagedResourcesList.tsx +0 -1
  84. package/src/components/gitops/SyncCountdown.tsx +0 -1
  85. package/src/components/gitops/index.ts +0 -4
  86. package/src/components/helm/ManifestDiffViewer.tsx +0 -69
  87. package/src/components/logs/JsonLogLine.tsx +0 -1
  88. package/src/components/logs/LogCore.tsx +0 -2
  89. package/src/components/logs/useLogBuffer.ts +0 -2
  90. package/src/components/logs/useLogSearch.ts +0 -1
  91. package/src/components/resource-drawer/ResourceDrawer.tsx +0 -216
  92. package/src/components/resources/drawer-components.tsx +0 -1
  93. package/src/components/resources/renderers/AlertRenderer.tsx +0 -1
  94. package/src/components/resources/renderers/CNPGBackupRenderer.tsx +0 -1
  95. package/src/components/resources/renderers/CNPGPoolerRenderer.tsx +0 -1
  96. package/src/components/resources/renderers/CNPGScheduledBackupRenderer.tsx +0 -1
  97. package/src/components/resources/renderers/CertificateRenderer.tsx +0 -1
  98. package/src/components/resources/renderers/CertificateRequestRenderer.tsx +0 -1
  99. package/src/components/resources/renderers/ChallengeRenderer.tsx +0 -1
  100. package/src/components/resources/renderers/ClusterComplianceReportRenderer.tsx +0 -1
  101. package/src/components/resources/renderers/ClusterExternalSecretRenderer.tsx +0 -1
  102. package/src/components/resources/renderers/ClusterIssuerRenderer.tsx +0 -1
  103. package/src/components/resources/renderers/ConfigAuditReportRenderer.tsx +0 -1
  104. package/src/components/resources/renderers/ConfigMapRenderer.tsx +0 -1
  105. package/src/components/resources/renderers/CronJobRenderer.tsx +0 -1
  106. package/src/components/resources/renderers/CronWorkflowRenderer.tsx +0 -1
  107. package/src/components/resources/renderers/EventRenderer.tsx +0 -1
  108. package/src/components/resources/renderers/ExposedSecretReportRenderer.tsx +0 -1
  109. package/src/components/resources/renderers/ExternalSecretRenderer.tsx +0 -1
  110. package/src/components/resources/renderers/FluxHelmReleaseRenderer.tsx +0 -1
  111. package/src/components/resources/renderers/GRPCRouteRenderer.tsx +0 -1
  112. package/src/components/resources/renderers/GatewayClassRenderer.tsx +0 -1
  113. package/src/components/resources/renderers/GatewayRenderer.tsx +0 -1
  114. package/src/components/resources/renderers/GenericRenderer.tsx +0 -1
  115. package/src/components/resources/renderers/GitRepositoryRenderer.tsx +0 -1
  116. package/src/components/resources/renderers/HTTPRouteRenderer.tsx +0 -1
  117. package/src/components/resources/renderers/HelmRepositoryRenderer.tsx +0 -1
  118. package/src/components/resources/renderers/IngressClassRenderer.tsx +0 -1
  119. package/src/components/resources/renderers/IngressRenderer.tsx +0 -1
  120. package/src/components/resources/renderers/IstioAuthorizationPolicyRenderer.tsx +0 -1
  121. package/src/components/resources/renderers/IstioDestinationRuleRenderer.tsx +0 -1
  122. package/src/components/resources/renderers/IstioGatewayRenderer.tsx +0 -1
  123. package/src/components/resources/renderers/IstioPeerAuthenticationRenderer.tsx +0 -1
  124. package/src/components/resources/renderers/IstioServiceEntryRenderer.tsx +0 -1
  125. package/src/components/resources/renderers/IstioVirtualServiceRenderer.tsx +0 -1
  126. package/src/components/resources/renderers/JobRenderer.tsx +0 -1
  127. package/src/components/resources/renderers/KarpenterEC2NodeClassRenderer.tsx +0 -1
  128. package/src/components/resources/renderers/KarpenterNodeClaimRenderer.tsx +0 -1
  129. package/src/components/resources/renderers/KedaScaledJobRenderer.tsx +0 -1
  130. package/src/components/resources/renderers/KedaScaledObjectRenderer.tsx +0 -1
  131. package/src/components/resources/renderers/KedaTriggerAuthRenderer.tsx +0 -1
  132. package/src/components/resources/renderers/KnativeConfigurationRenderer.tsx +0 -1
  133. package/src/components/resources/renderers/KnativeEventingRenderer.tsx +0 -1
  134. package/src/components/resources/renderers/KnativeFlowRenderer.tsx +0 -1
  135. package/src/components/resources/renderers/KnativeNetworkingRenderer.tsx +0 -1
  136. package/src/components/resources/renderers/KnativeRevisionRenderer.tsx +0 -1
  137. package/src/components/resources/renderers/KnativeRouteRenderer.tsx +0 -1
  138. package/src/components/resources/renderers/KnativeServiceRenderer.tsx +0 -1
  139. package/src/components/resources/renderers/KnativeSourceRenderer.tsx +0 -1
  140. package/src/components/resources/renderers/KustomizationRenderer.tsx +0 -1
  141. package/src/components/resources/renderers/KyvernoPolicyReportRenderer.tsx +0 -1
  142. package/src/components/resources/renderers/LeaseRenderer.tsx +0 -1
  143. package/src/components/resources/renderers/NetworkPolicyRenderer.tsx +0 -1
  144. package/src/components/resources/renderers/OCIRepositoryRenderer.tsx +0 -1
  145. package/src/components/resources/renderers/OrderRenderer.tsx +0 -1
  146. package/src/components/resources/renderers/PersistentVolumeRenderer.tsx +0 -1
  147. package/src/components/resources/renderers/PodDisruptionBudgetRenderer.tsx +0 -1
  148. package/src/components/resources/renderers/PodMonitorRenderer.tsx +0 -1
  149. package/src/components/resources/renderers/PriorityClassRenderer.tsx +0 -1
  150. package/src/components/resources/renderers/PrometheusRuleRenderer.tsx +0 -1
  151. package/src/components/resources/renderers/ReplicaSetRenderer.tsx +0 -1
  152. package/src/components/resources/renderers/RuntimeClassRenderer.tsx +0 -1
  153. package/src/components/resources/renderers/SbomReportRenderer.tsx +0 -1
  154. package/src/components/resources/renderers/SealedSecretRenderer.tsx +0 -1
  155. package/src/components/resources/renderers/SecretRenderer.tsx +0 -1
  156. package/src/components/resources/renderers/SecretStoreRenderer.tsx +0 -1
  157. package/src/components/resources/renderers/ServiceMonitorRenderer.tsx +0 -1
  158. package/src/components/resources/renderers/SimpleRouteRenderer.tsx +0 -1
  159. package/src/components/resources/renderers/StorageClassRenderer.tsx +0 -1
  160. package/src/components/resources/renderers/TraefikIngressRouteRenderer.tsx +0 -1
  161. package/src/components/resources/renderers/VPARenderer.tsx +0 -1
  162. package/src/components/resources/renderers/VeleroScheduleRenderer.tsx +0 -1
  163. package/src/components/resources/renderers/VeleroVSLRenderer.tsx +0 -1
  164. package/src/components/resources/renderers/VulnerabilityReportRenderer.tsx +0 -1
  165. package/src/components/resources/renderers/WebhookConfigRenderer.tsx +0 -1
  166. package/src/components/resources/renderers/WorkflowRenderer.tsx +0 -1
  167. package/src/components/resources/renderers/WorkflowTemplateRenderer.tsx +0 -1
  168. package/src/components/resources/renderers/argo-cells.tsx +0 -1
  169. package/src/components/resources/renderers/certmanager-cells.tsx +0 -1
  170. package/src/components/resources/renderers/cnpg-cells.tsx +0 -1
  171. package/src/components/resources/renderers/eso-cells.tsx +0 -1
  172. package/src/components/resources/renderers/flux-cells.tsx +0 -1
  173. package/src/components/resources/renderers/index.ts +0 -94
  174. package/src/components/resources/renderers/istio-cells.tsx +0 -1
  175. package/src/components/resources/renderers/karpenter-cells.tsx +0 -1
  176. package/src/components/resources/renderers/keda-cells.tsx +0 -1
  177. package/src/components/resources/renderers/knative-cells.tsx +0 -1
  178. package/src/components/resources/renderers/kyverno-cells.tsx +0 -1
  179. package/src/components/resources/renderers/prometheus-cells.tsx +0 -1
  180. package/src/components/resources/renderers/traefik-cells.tsx +0 -1
  181. package/src/components/resources/renderers/trivy-cells.tsx +0 -1
  182. package/src/components/resources/renderers/trivy-shared.tsx +0 -1
  183. package/src/components/resources/renderers/velero-cells.tsx +0 -1
  184. package/src/components/resources/resource-utils-argo.ts +0 -2
  185. package/src/components/resources/resource-utils-certmanager.ts +0 -2
  186. package/src/components/resources/resource-utils-eso.ts +0 -2
  187. package/src/components/resources/resource-utils-flux.ts +0 -2
  188. package/src/components/resources/resource-utils-istio.ts +0 -2
  189. package/src/components/resources/resource-utils-karpenter.ts +0 -2
  190. package/src/components/resources/resource-utils-knative.ts +0 -2
  191. package/src/components/resources/resource-utils-kyverno.ts +0 -2
  192. package/src/components/resources/resource-utils-prometheus.ts +0 -2
  193. package/src/components/resources/resource-utils-traefik.ts +0 -1
  194. package/src/components/resources/resource-utils-trivy.ts +0 -2
  195. package/src/components/resources/resource-utils-velero.ts +0 -2
  196. package/src/components/shared/EditableYamlView.tsx +0 -24
  197. package/src/components/timeline/DiffViewer.tsx +0 -1
  198. package/src/components/traffic/index.ts +0 -3
  199. package/src/components/ui/ForceDeleteConfirmDialog.tsx +0 -1
  200. package/src/components/ui/MetricsChart.tsx +0 -1
  201. package/src/components/ui/ResourceBar.tsx +0 -1
@@ -0,0 +1,512 @@
1
+ import { type Issue, type IssueRecentChange } from "@skyhook-io/k8s-ui";
2
+ import { type TimeSeries } from "@skyhook-io/k8s-ui/components/charts";
3
+ import {
4
+ type DiagnosisChangeContext,
5
+ type DiagnosisCrashCause,
6
+ type DiagnosisDNSContext,
7
+ type DiagnosisEvidenceLimitationBase,
8
+ type DiagnosisEvidenceTone,
9
+ type DiagnosisFilteredLogs,
10
+ type DiagnosisResourceContext,
11
+ type DiagnosisResourceRef,
12
+ type DiagnosisStartupBlocker,
13
+ } from "../diagnoseEvidenceTypes";
14
+ /**
15
+ * The projection deliberately consumes only the small, structural portion of
16
+ * Turn that it needs. TimelineItem is private to parts.tsx today; Turn[] is
17
+ * structurally assignable to this type without coupling evidence extraction to
18
+ * the transcript renderer.
19
+ */
20
+ export interface InvestigationEvidenceTurn {
21
+ timeline: readonly InvestigationEvidenceTimelineItem[];
22
+ question?: string;
23
+ apply?: boolean;
24
+ verify?: boolean;
25
+ status?: "running" | "done" | "error";
26
+ }
27
+ /** The resource the investigation was opened for. */
28
+ export interface InvestigationEvidenceTarget {
29
+ kind: string;
30
+ /** Kubernetes API group; empty means core. */
31
+ group: string;
32
+ namespace?: string;
33
+ name: string;
34
+ }
35
+ export type InvestigationEvidencePhase =
36
+ "initial" | "followup" | "verification" | "apply";
37
+ export type InvestigationEvidenceTimelineItem =
38
+ | { kind: "thinking"; text: string }
39
+ | {
40
+ kind: "tool";
41
+ id: string;
42
+ tool: string;
43
+ status: string;
44
+ summary?: string;
45
+ result?: string;
46
+ evidenceRef?: string;
47
+ radarEvidence?: boolean;
48
+ truncated?: boolean;
49
+ isError?: boolean;
50
+ };
51
+ export type InvestigationEvidenceTier =
52
+ "key" | "supporting" | "context" | "checked";
53
+ export type InvestigationEvidenceRelevance =
54
+ "target" | "producer-related" | "broader";
55
+ export type InvestigationEvidenceKind =
56
+ | "issue"
57
+ | "startup"
58
+ | "crash"
59
+ | "resource"
60
+ | "logs"
61
+ | "events"
62
+ | "changes"
63
+ | "dns"
64
+ | "network"
65
+ | "relationships"
66
+ | "topology"
67
+ | "inventory"
68
+ | "receipt"
69
+ | "alerts"
70
+ | "helm"
71
+ | "permissions"
72
+ | "metrics";
73
+ export type InvestigationSemanticDomain = "issue" | "startup" | "crash" | "dns";
74
+ export interface InvestigationEvidenceSource {
75
+ /** DOM-safe stable identity derived from turn index + the agent step ID. */
76
+ id: string;
77
+ turnIndex: number;
78
+ timelineIndex: number;
79
+ stepId: string;
80
+ tool: string;
81
+ /** The exact agent-emitted tool input shown in Activity. */
82
+ args?: string;
83
+ /** Stable flattened transcript order. */
84
+ order: number;
85
+ /** Which chronological phase of the run produced this source. */
86
+ phase: InvestigationEvidencePhase;
87
+ /** True only when the agent transport explicitly marked the tool result successful. */
88
+ confirmedSuccess: boolean;
89
+ /** Server-issued, turn-scoped identity for this exact retained result. */
90
+ evidenceRef?: string;
91
+ /**
92
+ * The highest-priority evidence group produced by this call. Evidence panes
93
+ * use it for one unique Activity → Evidence anchor even when a bundle fans
94
+ * out into several cards.
95
+ */
96
+ primaryGroupId?: string;
97
+ }
98
+ export interface InvestigationResourceContext extends DiagnosisResourceContext {
99
+ issueSummary?: {
100
+ count: number;
101
+ highestSeverity?: string;
102
+ topReason?: string;
103
+ bySource?: Record<string, number>;
104
+ };
105
+ auditSummary?: {
106
+ count: number;
107
+ highestSeverity?: string;
108
+ topFinding?: string;
109
+ };
110
+ policySummary?: unknown;
111
+ podSummary?: unknown;
112
+ }
113
+ export interface InvestigationGitOpsDiagnosis {
114
+ tool: "argocd" | "flux";
115
+ sync?: string;
116
+ health?: string;
117
+ operationPhase?: string;
118
+ suspended?: boolean;
119
+ ready?: string;
120
+ appliedRevision?: string;
121
+ }
122
+ export interface InvestigationKubernetesResource {
123
+ apiVersion: string;
124
+ kind: string;
125
+ metadata: {
126
+ name: string;
127
+ namespace?: string;
128
+ [key: string]: unknown;
129
+ };
130
+ spec?: unknown;
131
+ status?: unknown;
132
+ summaryContext?: unknown;
133
+ [key: string]: unknown;
134
+ }
135
+ /** Current `list_resources` row shape (`ai/context.ResourceSummary`). */
136
+ export interface InvestigationResourceSummary {
137
+ kind: string;
138
+ name: string;
139
+ namespace?: string;
140
+ status?: string;
141
+ ready?: string;
142
+ issue?: string;
143
+ age?: string;
144
+ terminating?: boolean;
145
+ restarts?: number;
146
+ lastTerminatedReason?: string;
147
+ lastRestartedAge?: string;
148
+ summaryContext?: {
149
+ health?: string;
150
+ issueCount?: number;
151
+ managedBy?: {
152
+ kind: string;
153
+ source: string;
154
+ name: string;
155
+ namespace?: string;
156
+ };
157
+ };
158
+ [key: string]: unknown;
159
+ }
160
+ export interface InvestigationEventEvidence {
161
+ reason: string;
162
+ message: string;
163
+ type: string;
164
+ count: number;
165
+ lastTimestamp: string;
166
+ }
167
+ export interface InvestigationTopologyNode {
168
+ id: string;
169
+ kind: string;
170
+ name: string;
171
+ status?: string;
172
+ data?: Record<string, unknown> | null;
173
+ }
174
+ export interface InvestigationTopologyEdge {
175
+ id?: string;
176
+ source: string;
177
+ target: string;
178
+ type: string;
179
+ label?: string;
180
+ }
181
+ export interface InvestigationNetworkRoute {
182
+ route: string;
183
+ target?: string;
184
+ outcome: string;
185
+ failedLayer?: string;
186
+ confidence?: string;
187
+ evidence?: string;
188
+ benign?: boolean;
189
+ }
190
+ export interface InvestigationNetworkEvidence {
191
+ subject: DiagnosisResourceRef;
192
+ verdict: "healthy" | "degraded" | "broken" | "unknown";
193
+ reason?: string;
194
+ diagnosis?: {
195
+ class?: string;
196
+ severity?: string;
197
+ summary: string;
198
+ route?: string;
199
+ nextAction?: string;
200
+ };
201
+ summary: {
202
+ tested: number;
203
+ passed: number;
204
+ failed: number;
205
+ derived?: number;
206
+ skipped: number;
207
+ headline: string;
208
+ };
209
+ routes: InvestigationNetworkRoute[];
210
+ }
211
+ /** One Prometheus rule as `get_prometheus_rules` flattens it (group stamped on). */
212
+ export interface InvestigationAlertRule {
213
+ group: string;
214
+ name: string;
215
+ type: string;
216
+ state?: string;
217
+ health?: string;
218
+ query?: string;
219
+ labels: Record<string, string>;
220
+ }
221
+ /** One active instance of an alerting rule, with its own label set. */
222
+ export interface InvestigationAlertInstance {
223
+ state: string;
224
+ activeAt?: string;
225
+ value?: string;
226
+ labels: Record<string, string>;
227
+ /** The instance's labels name the investigated resource. */
228
+ namesTarget: boolean;
229
+ }
230
+ export interface InvestigationHelmOperation {
231
+ kind: string;
232
+ status: string;
233
+ message: string;
234
+ revision?: number;
235
+ failedRevision?: number;
236
+ rollbackRevision?: number;
237
+ updated?: string;
238
+ }
239
+ export interface InvestigationHelmOwnedResource {
240
+ kind: string;
241
+ apiVersion?: string;
242
+ name: string;
243
+ namespace: string;
244
+ status?: string;
245
+ ready?: string;
246
+ message?: string;
247
+ summary?: string;
248
+ issue?: string;
249
+ }
250
+ export interface InvestigationHelmRelease {
251
+ name: string;
252
+ namespace: string;
253
+ /** Set only when Helm stores the release metadata elsewhere. */
254
+ storageNamespace?: string;
255
+ chart: string;
256
+ chartVersion: string;
257
+ appVersion?: string;
258
+ status: string;
259
+ revision: number;
260
+ updated: string;
261
+ description?: string;
262
+ resourceHealth?: string;
263
+ healthIssue?: string;
264
+ healthSummary?: string;
265
+ managedByFluxHelmRelease?: string;
266
+ lastOperation?: InvestigationHelmOperation;
267
+ resources: InvestigationHelmOwnedResource[];
268
+ }
269
+ export interface InvestigationPermissionSubject {
270
+ kind: string;
271
+ namespace?: string;
272
+ name: string;
273
+ }
274
+ export interface InvestigationAccessCheck {
275
+ verb: string;
276
+ group?: string;
277
+ resource: string;
278
+ subresource?: string;
279
+ /** Empty means a cluster-scoped resource or cluster-wide request. */
280
+ namespace: string;
281
+ resourceName?: string;
282
+ allowed: boolean;
283
+ denied: boolean;
284
+ reason?: string;
285
+ evaluationError?: string;
286
+ }
287
+ export interface InvestigationPermissionBinding {
288
+ bindingKind: string;
289
+ bindingNamespace?: string;
290
+ bindingName: string;
291
+ roleKind: string;
292
+ roleNamespace?: string;
293
+ roleName: string;
294
+ rulesCount: number;
295
+ inheritedFromGroup?: string;
296
+ }
297
+ export type InvestigationEvidenceData =
298
+ | {
299
+ type: "issue";
300
+ issue: Issue;
301
+ /**
302
+ * A broad `issues` query can return failures from other resources. Keep
303
+ * those factual observations, but do not let agent selection alone imply
304
+ * that they explain the resource under investigation.
305
+ */
306
+ relevance: "target" | "producer-related" | "broader";
307
+ /** Pods whose startup blocker repeats this issue word for word. */
308
+ pods?: string[];
309
+ }
310
+ | {
311
+ type: "startup";
312
+ blocker: DiagnosisStartupBlocker;
313
+ /** Exact blocker object when the diagnosis producer established it. */
314
+ subject?: DiagnosisResourceRef;
315
+ /**
316
+ * Every pod the producer reported with this exact blocker. A DaemonSet
317
+ * with seven pending pods is one finding, not seven; the card carries the
318
+ * pod list instead of repeating itself.
319
+ */
320
+ pods?: string[];
321
+ }
322
+ | {
323
+ type: "crash";
324
+ crash: DiagnosisCrashCause;
325
+ /** Namespace of the producing check's subject; pods live there. */
326
+ namespace?: string;
327
+ }
328
+ | {
329
+ type: "resource";
330
+ resource: InvestigationKubernetesResource;
331
+ resourceContext?: InvestigationResourceContext;
332
+ warnings: string[];
333
+ gitOpsDiagnosis?: InvestigationGitOpsDiagnosis;
334
+ }
335
+ | {
336
+ type: "logs";
337
+ pod: string;
338
+ container: string;
339
+ /** Namespace the producing call actually read; absent when unstated. */
340
+ namespace?: string;
341
+ previous: boolean;
342
+ logs?: DiagnosisFilteredLogs;
343
+ warnings: string[];
344
+ error?: string;
345
+ }
346
+ | {
347
+ type: "events";
348
+ events: InvestigationEventEvidence[];
349
+ scope: string;
350
+ }
351
+ | {
352
+ type: "changes";
353
+ changes: IssueRecentChange[];
354
+ scope: string;
355
+ changeContext?: DiagnosisChangeContext;
356
+ /** The resource whose change history the producer read, when it named one. */
357
+ subject?: { kind?: string; namespace?: string; name: string };
358
+ }
359
+ | { type: "dns"; dns: DiagnosisDNSContext }
360
+ | { type: "network"; network: InvestigationNetworkEvidence }
361
+ | {
362
+ type: "relationships";
363
+ root: DiagnosisResourceRef;
364
+ nodes: InvestigationTopologyNode[];
365
+ edges: InvestigationTopologyEdge[];
366
+ truncated: boolean;
367
+ }
368
+ | {
369
+ type: "topology";
370
+ stats: { nodes: number; edges: number };
371
+ namespaces: Array<{ namespace: string; chains: string[] }>;
372
+ problems: string[];
373
+ warnings: string[];
374
+ }
375
+ | {
376
+ type: "inventory";
377
+ resources: InvestigationResourceSummary[];
378
+ scope: string;
379
+ }
380
+ | {
381
+ type: "receipt";
382
+ checked:
383
+ "issues" | "events" | "changes" | "inventory" | "logs" | "alerts";
384
+ scope: string;
385
+ /**
386
+ * Only when there is something to add. The card already shows the title
387
+ * and the scope, so a line that restates either costs the reader a read
388
+ * and returns nothing; this carries the reason the answer is what it is,
389
+ * or the limit of what it proves.
390
+ */
391
+ message?: string;
392
+ }
393
+ | {
394
+ type: "alerts";
395
+ rule: InvestigationAlertRule;
396
+ instances: InvestigationAlertInstance[];
397
+ annotations: Record<string, string>;
398
+ }
399
+ | { type: "helm"; release: InvestigationHelmRelease }
400
+ | {
401
+ type: "permissions";
402
+ subject: InvestigationPermissionSubject;
403
+ /** Present for the access-check response shape. */
404
+ accessCheck?: InvestigationAccessCheck;
405
+ /** Present for the subject-permissions response shape. */
406
+ bindings?: InvestigationPermissionBinding[];
407
+ flatRulesCount?: number;
408
+ truncated?: boolean;
409
+ usedByPods?: string[];
410
+ podsTotal?: number;
411
+ }
412
+ | InvestigationMetricsEvidence;
413
+ /** One PromQL vector selector as the producer tokenized it. */
414
+ export interface InvestigationMetricsSelector {
415
+ metric: string;
416
+ matchers: Array<{ label: string; op: string; value: string }>;
417
+ }
418
+ /**
419
+ * A Prometheus result captured during the investigation. `origin` says which
420
+ * producer ran the query; the shape is shared so every metrics card renders
421
+ * the same way. `subject` is set only when the query's selectors are all
422
+ * scoped to the investigation target.
423
+ */
424
+ export interface InvestigationMetricsEvidence {
425
+ type: "metrics";
426
+ origin: "query" | "diagnose";
427
+ query: string;
428
+ mode: "range" | "instant";
429
+ start?: string;
430
+ end?: string;
431
+ step?: string;
432
+ unit?: string;
433
+ label?: string;
434
+ series: TimeSeries[];
435
+ truncated: boolean;
436
+ summary?: unknown;
437
+ note?: string;
438
+ selectors?: InvestigationMetricsSelector[];
439
+ selectorsUnknown?: boolean;
440
+ subject?: DiagnosisResourceRef;
441
+ pods?: number;
442
+ partial?: boolean;
443
+ }
444
+ export interface InvestigationEvidenceObservation {
445
+ source: InvestigationEvidenceSource;
446
+ revision: number;
447
+ /** This exact observation predates a later successful verification of its proof scope. */
448
+ historical: boolean;
449
+ /** Whether this semantic item differs from its immediately previous observation. */
450
+ changedFromPrevious: boolean;
451
+ /**
452
+ * How this producer-backed observation relates to the resource being
453
+ * investigated. Broader observations remain useful context, but agent
454
+ * selection alone must never promote them as support for this target.
455
+ */
456
+ relevance: InvestigationEvidenceRelevance;
457
+ tier: InvestigationEvidenceTier;
458
+ tone: DiagnosisEvidenceTone;
459
+ title: string;
460
+ summary?: string;
461
+ data: InvestigationEvidenceData;
462
+ }
463
+ export interface InvestigationEvidenceGroup {
464
+ /** Stable DOM-safe identity for this semantic evidence item. */
465
+ id: string;
466
+ /** Raw deterministic identity used to merge repeated observations. */
467
+ identity: string;
468
+ kind: InvestigationEvidenceKind;
469
+ /** Latest observation predates the most recent completed verification turn. */
470
+ historical: boolean;
471
+ firstOrder: number;
472
+ observations: InvestigationEvidenceObservation[];
473
+ /** Strongest-provenance observation, newest when provenance is equal. */
474
+ latest: InvestigationEvidenceObservation;
475
+ /** Newest observation regardless of proof strength; used for chronology. */
476
+ chronologicalLatest: InvestigationEvidenceObservation;
477
+ }
478
+ export interface InvestigationEvidenceLimitation extends DiagnosisEvidenceLimitationBase {
479
+ /** A qualified history result, not a failed collection. */
480
+ presentation?: "history";
481
+ firstOrder: number;
482
+ sources: InvestigationEvidenceSource[];
483
+ }
484
+ export interface InvestigationEvidenceCoverage {
485
+ /** Completed calls to a tool with a typed evidence adapter. */
486
+ attempted: number;
487
+ /** Adapted calls that contributed at least one evidence group. */
488
+ projected: number;
489
+ /** Adapted calls with a producer-declared or transport limitation. */
490
+ limited: number;
491
+ /** Calls that produced a strict, successful zero-result receipt. */
492
+ checked: number;
493
+ }
494
+ export interface InvestigationEvidenceProjection {
495
+ groups: InvestigationEvidenceGroup[];
496
+ limitations: InvestigationEvidenceLimitation[];
497
+ sources: InvestigationEvidenceSource[];
498
+ /** Every retained tool item carrying a server-issued ref, eligible or not. */
499
+ evidenceRefSources: InvestigationEvidenceSource[];
500
+ /** Complete confirmed-success sources eligible for server-authored links. */
501
+ citableSources: InvestigationEvidenceSource[];
502
+ coverage: InvestigationEvidenceCoverage;
503
+ }
504
+ export interface InvestigationRootCauseEvidenceLink {
505
+ source: InvestigationEvidenceSource;
506
+ /** Canonical semantic group containing this source’s primary observation. */
507
+ originalGroupId?: string;
508
+ }
509
+ export interface InvestigationRootCauseEvidenceResolution {
510
+ status: "linked" | "missing" | "invalid";
511
+ links: InvestigationRootCauseEvidenceLink[];
512
+ }
@@ -26,6 +26,25 @@ describe("investigation target identity", () => {
26
26
  ).toBe("Rollout.argoproj.io prod/checkout");
27
27
  });
28
28
 
29
+ it("formats saved plural targets as Kubernetes Kinds", () => {
30
+ expect(
31
+ formatInvestigationTarget({
32
+ kind: "deployments",
33
+ group: "apps",
34
+ namespace: "prod",
35
+ name: "api",
36
+ }),
37
+ ).toBe("Deployment.apps prod/api");
38
+ expect(
39
+ formatInvestigationTarget({
40
+ kind: "pods",
41
+ group: "metrics.k8s.io",
42
+ namespace: "prod",
43
+ name: "api",
44
+ }),
45
+ ).toBe("PodMetrics.metrics.k8s.io prod/api");
46
+ });
47
+
29
48
  it("keeps the core target label compact", () => {
30
49
  expect(
31
50
  formatInvestigationTarget({
@@ -1,6 +1,6 @@
1
1
  import { resourceKey } from "@skyhook-io/k8s-ui";
2
2
 
3
- import { kindToPluralWithGroup } from "../../utils/navigation";
3
+ import { kindToPluralWithGroup, knownKindForPluralWithGroup } from "../../utils/navigation";
4
4
 
5
5
  export interface InvestigationTargetIdentity {
6
6
  kind: string;
@@ -31,6 +31,7 @@ export function runTargetKey(
31
31
  export function formatInvestigationTarget(
32
32
  target: InvestigationTargetIdentity,
33
33
  ): string {
34
- const kind = target.group ? `${target.kind}.${target.group}` : target.kind;
34
+ const canonicalKind = knownKindForPluralWithGroup(target.kind, target.group) ?? target.kind;
35
+ const kind = target.group ? `${canonicalKind}.${target.group}` : canonicalKind;
35
36
  return `${kind} ${target.namespace ? `${target.namespace}/` : ""}${target.name}`;
36
37
  }
@@ -72,6 +72,9 @@ import { useRegisterShortcut } from '../../hooks/useKeyboardShortcuts'
72
72
  import { CodeViewer } from '../ui/CodeViewer'
73
73
  import { ArgoResourceDiffLoader } from './ArgoResourceDiffLoader'
74
74
  import { RevisionMetaChip } from './RevisionMetaChip'
75
+ import { RemoteDestinationCloudHint } from './RemoteDestinationCloudHint'
76
+
77
+ const GITOPS_HEALTH_DOCS_URL = 'https://radarhq.io/docs/features/gitops#per-resource-health'
75
78
  import type { GitOpsHistoryItem } from '@skyhook-io/k8s-ui'
76
79
 
77
80
  const GITOPS_KINDS: APIResource[] = [
@@ -634,6 +637,9 @@ function GitOpsDetailView({ namespaces, onOpenResource, onOpenSettings }: GitOps
634
637
  detail={detail}
635
638
  insight={insightsQ.data ?? null}
636
639
  insightLoading={insightsQ.isLoading}
640
+ healthDocsUrl={GITOPS_HEALTH_DOCS_URL}
641
+ remoteDestinationHint={<RemoteDestinationCloudHint />}
642
+ onOpenSettings={onOpenSettings}
637
643
  renderRevisionMeta={
638
644
  isArgoApp && insightsQ.data?.capabilities?.revisionMetadataAvailable
639
645
  ? (revision) => (
@@ -808,6 +814,7 @@ function GitOpsDetailView({ namespaces, onOpenResource, onOpenSettings }: GitOps
808
814
  onQueryChange={setGraphSearch}
809
815
  filters={graphFilters}
810
816
  showToolbar={false}
817
+ issues={insightsQ.data?.issues}
811
818
  />
812
819
  </div>
813
820
  </div>
@@ -0,0 +1,26 @@
1
+ import { useNavCustomization } from '../../context/NavCustomization'
2
+ import { useCapabilities } from '../../api/client'
3
+ import { openCloudFunnel } from '../CloudFunnelButton'
4
+
5
+ // The one sentence a standalone Radar adds after "this application deploys
6
+ // to another cluster": the thing the user just ran into is what Radar Cloud
7
+ // does. "Radar Cloud" opens the same dialog as the top-right button, so
8
+ // there is one pitch and one connect flow. That dialog connects THIS
9
+ // cluster; the destination is a second connect from Cloud, which is why the
10
+ // copy says "both clusters" and promises the view in Cloud, not here. Rendered only when that button
11
+ // is: the server decides whether there is anything to pitch (cloudConnect
12
+ // capability), and embedded hosts (Radar Cloud itself) mount neither.
13
+ export function RemoteDestinationCloudHint() {
14
+ const { embedded } = useNavCustomization()
15
+ const capabilities = useCapabilities()
16
+ if (embedded || !capabilities.data?.cloudConnect) return null
17
+ return (
18
+ <>
19
+ Connect both clusters to{' '}
20
+ <button type="button" onClick={openCloudFunnel} className="underline decoration-theme-border underline-offset-2 hover:text-theme-text-primary">
21
+ Radar Cloud
22
+ </button>{' '}
23
+ to see this app's resources together.
24
+ </>
25
+ )
26
+ }
@@ -0,0 +1,22 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { computeRequestLimitLines } from './PrometheusCharts'
3
+
4
+ describe('Pod request and limit overlays', () => {
5
+ it('does not draw a Pod limit when one runtime container is unlimited', () => {
6
+ const resource = { spec: { containers: [
7
+ { resources: { requests: { cpu: '100m' }, limits: { cpu: '1' } } },
8
+ { resources: { requests: { cpu: '200m' } } },
9
+ ] } }
10
+ const lines = computeRequestLimitLines(resource, 'Pod', 'cpu')!
11
+ expect(lines).toHaveLength(1)
12
+ expect(lines[0].kind).toBe('request')
13
+ expect(lines[0].value).toBeCloseTo(0.3)
14
+ })
15
+ it('sums fully limited runtime containers at per-Pod scale', () => {
16
+ const resource = { spec: { replicas: 5, template: { spec: {
17
+ containers: [{ resources: { limits: { cpu: '1' } } }],
18
+ initContainers: [{ restartPolicy: 'Always', resources: { limits: { cpu: '200m' } } }],
19
+ } } } }
20
+ expect(computeRequestLimitLines(resource, 'Deployment', 'cpu')![0].value).toBeCloseTo(1.2)
21
+ })
22
+ })
@@ -121,6 +121,7 @@ export function PrometheusCharts({ kind, namespace, name, showEmptyState = false
121
121
  statusError={status?.error}
122
122
  onConnect={() => connectMutation.mutate()}
123
123
  connecting={connectMutation.isPending}
124
+ discovering={status?.discovering === true}
124
125
  category={activeCategory}
125
126
  onCategoryChange={setActiveCategory}
126
127
  range={timeRange}
@@ -161,12 +162,12 @@ export function computeRequestLimitLines(
161
162
  if (runtimeContainers.length === 0) return undefined
162
163
 
163
164
  let reqSum = 0, reqAny = false
164
- let limSum = 0, limAny = false
165
+ let limSum = 0, limAll = true
165
166
  for (const c of runtimeContainers) {
166
167
  const req = readQuantity(c.resources?.requests?.[category], category)
167
168
  const lim = readQuantity(c.resources?.limits?.[category], category)
168
169
  if (req != null) { reqSum += req; reqAny = true }
169
- if (lim != null) { limSum += lim; limAny = true }
170
+ if (lim != null && lim > 0) { limSum += lim } else { limAll = false }
170
171
  }
171
172
 
172
173
  const lines: ReferenceLine[] = []
@@ -177,7 +178,7 @@ export function computeRequestLimitLines(
177
178
  kind: 'request',
178
179
  })
179
180
  }
180
- if (limAny) {
181
+ if (limAll) {
181
182
  lines.push({
182
183
  value: limSum,
183
184
  label: `limit ${formatRequestLimitLabel(limSum, category)}`,