@skyhook-io/radar-app 1.13.5 → 1.14.1
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 +9 -9
- package/src/App.tsx +66 -28
- package/src/api/client.authRedirect.test.ts +12 -1
- package/src/api/client.prometheusStatus.test.ts +24 -0
- package/src/api/client.ts +324 -16
- package/src/api/diagnose.ts +32 -1
- package/src/api/drain-plan.test.ts +86 -0
- package/src/api/quotas.test.ts +26 -0
- package/src/api/quotas.ts +20 -0
- package/src/api/workloadMetrics.test.ts +40 -0
- package/src/api/workloadMetrics.ts +68 -0
- package/src/components/CloudConnectFlow.tsx +343 -46
- package/src/components/CloudFunnelButton.tsx +110 -37
- package/src/components/ConnectionErrorView.tsx +9 -10
- package/src/components/DebugOverlay.tsx +10 -6
- package/src/components/SyncProgressPanel.tsx +73 -0
- package/src/components/audit/AuditView.tsx +2 -0
- package/src/components/cloudConnectHandoff.test.ts +245 -0
- package/src/components/cloudConnectHandoff.ts +276 -0
- package/src/components/curl/ServiceCurlButton.tsx +19 -26
- package/src/components/diagnose/AgentCase.tsx +18 -5
- package/src/components/diagnose/AnalysisStory.test.tsx +308 -0
- package/src/components/diagnose/AnalysisStory.tsx +564 -0
- package/src/components/diagnose/DiagnoseContext.test.ts +10 -0
- package/src/components/diagnose/DiagnoseContext.tsx +26 -8
- package/src/components/diagnose/DiagnoseSurface.tsx +20 -14
- package/src/components/diagnose/InvestigationEvidencePane.story.test.tsx +771 -0
- package/src/components/diagnose/InvestigationEvidencePane.test.tsx +8 -33
- package/src/components/diagnose/InvestigationEvidencePane.tsx +809 -168
- package/src/components/diagnose/InvestigationResourceEvidence.test.tsx +30 -0
- package/src/components/diagnose/InvestigationResourceEvidence.tsx +20 -0
- package/src/components/diagnose/InvestigationView.tsx +493 -529
- package/src/components/diagnose/investigationCase.test.tsx +267 -129
- package/src/components/diagnose/investigationCase.ts +122 -77
- package/src/components/diagnose/investigationEvidence/adapters/changes.ts +60 -0
- package/src/components/diagnose/investigationEvidence/adapters/diagnose.test.ts +573 -0
- package/src/components/diagnose/investigationEvidence/adapters/diagnose.ts +935 -0
- package/src/components/diagnose/investigationEvidence/adapters/events.ts +96 -0
- package/src/components/diagnose/investigationEvidence/adapters/helm.test.ts +242 -0
- package/src/components/diagnose/investigationEvidence/adapters/helm.ts +208 -0
- package/src/components/diagnose/investigationEvidence/adapters/issues.ts +172 -0
- package/src/components/diagnose/investigationEvidence/adapters/logs.test.ts +283 -0
- package/src/components/diagnose/investigationEvidence/adapters/logs.ts +183 -0
- package/src/components/diagnose/investigationEvidence/adapters/permissions.test.ts +528 -0
- package/src/components/diagnose/investigationEvidence/adapters/permissions.ts +292 -0
- package/src/components/diagnose/investigationEvidence/adapters/prometheus.test.ts +1641 -0
- package/src/components/diagnose/investigationEvidence/adapters/prometheus.ts +769 -0
- package/src/components/diagnose/investigationEvidence/adapters/resource.test.ts +81 -0
- package/src/components/diagnose/investigationEvidence/adapters/resource.ts +285 -0
- package/src/components/diagnose/investigationEvidence/adapters/topology.ts +207 -0
- package/src/components/diagnose/investigationEvidence/builder.ts +427 -0
- package/src/components/diagnose/investigationEvidence/evidenceFixtures.ts +179 -0
- package/src/components/diagnose/investigationEvidence/identity.test.ts +632 -0
- package/src/components/diagnose/investigationEvidence/identity.ts +261 -0
- package/src/components/diagnose/investigationEvidence/index.ts +55 -0
- package/src/components/diagnose/investigationEvidence/observations.ts +978 -0
- package/src/components/diagnose/investigationEvidence/parse.ts +467 -0
- package/src/components/diagnose/investigationEvidence/projection.test.ts +2736 -0
- package/src/components/diagnose/investigationEvidence/types.ts +514 -0
- package/src/components/diagnose/investigationEvidencePresentation.test.ts +2 -0
- package/src/components/diagnose/investigationEvidencePresentation.ts +3 -0
- package/src/components/diagnose/investigationState.test.ts +316 -58
- package/src/components/diagnose/investigationState.ts +257 -44
- package/src/components/diagnose/investigationStory.test.ts +161 -0
- package/src/components/diagnose/investigationStory.ts +207 -0
- package/src/components/diagnose/parts.test.tsx +9 -6
- package/src/components/diagnose/parts.tsx +977 -193
- package/src/components/diagnose/storyParts.test.tsx +426 -0
- package/src/components/diagnose/target.test.ts +19 -0
- package/src/components/diagnose/target.ts +3 -2
- package/src/components/diagnose/toolCallLabel.test.ts +51 -0
- package/src/components/diagnose/toolCallLabel.ts +135 -0
- package/src/components/diagnose/useDisclosureReveal.ts +10 -11
- package/src/components/gitops/GitOpsView.tsx +7 -0
- package/src/components/gitops/RemoteDestinationCloudHint.tsx +26 -0
- package/src/components/helm/HelmCompareRoute.tsx +18 -4
- package/src/components/helm/HelmReleaseDrawer.tsx +11 -26
- package/src/components/helm/InstallWizard.tsx +8 -3
- package/src/components/home/MCPSetupDialog.tsx +15 -9
- package/src/components/portforward/PortForwardManager.tsx +6 -13
- package/src/components/resource/PrometheusCharts.overlays.test.ts +22 -0
- package/src/components/resource/PrometheusCharts.tsx +4 -3
- package/src/components/resource/PrometheusChartsGrid.render.test.tsx +99 -0
- package/src/components/resource/PrometheusChartsGrid.tsx +75 -35
- package/src/components/resource/RightsizingStrip.test.ts +38 -0
- package/src/components/resource/RightsizingStrip.tsx +15 -3
- package/src/components/resource/WorkloadMetricsHelpDialog.test.tsx +79 -0
- package/src/components/resource/WorkloadMetricsHelpDialog.tsx +145 -0
- package/src/components/resource/WorkloadMetricsSection.render.test.tsx +351 -0
- package/src/components/resource/WorkloadMetricsSection.tsx +480 -0
- package/src/components/resource/workloadMetricValues.test.ts +65 -0
- package/src/components/resource/workloadMetricValues.ts +30 -0
- package/src/components/resources/PodFilePreview.tsx +3 -3
- package/src/components/resources/ResourcesView.tsx +44 -6
- package/src/components/resources/renderers/NamespaceRenderer.tsx +7 -1
- package/src/components/resources/renderers/PodRenderer.tsx +4 -0
- package/src/components/resources/renderers/RolloutRenderer.tsx +15 -1
- package/src/components/resources/renderers/ServiceRenderer.tsx +2 -1
- package/src/components/resources/renderers/WorkloadRenderer.tsx +3 -0
- package/src/components/rightsizing/RightsizingScanView.tsx +21 -0
- package/src/components/settings/SettingsDialog.tsx +98 -16
- package/src/components/traffic/TrafficFlowList.tsx +229 -70
- package/src/components/traffic/TrafficGraph.tsx +6 -6
- package/src/components/traffic/TrafficView.tsx +10 -9
- package/src/components/traffic/trafficFilters.test.ts +28 -1
- package/src/components/traffic/trafficFilters.ts +31 -0
- package/src/components/ui/CommandPalette.tsx +10 -6
- package/src/components/ui/DiagnosticsOverlay.tsx +10 -6
- package/src/components/ui/Disclosure.tsx +47 -0
- package/src/components/ui/Markdown.tsx +23 -11
- package/src/components/ui/ShortcutHelpOverlay.tsx +3 -1
- package/src/components/ui/UpdateNotification.tsx +18 -2
- package/src/components/workload/WorkloadView.tsx +19 -8
- package/src/context/ConnectionContext.tsx +48 -3
- package/src/context/DiagnoseCustomization.tsx +1 -0
- package/src/index.css +36 -6
- package/src/utils/navigation.ts +1 -1
- package/src/utils/topology-namespace.test.ts +64 -0
- package/src/utils/topology-namespace.ts +18 -0
- package/src/components/diagnose/investigationEvidence.test.ts +0 -6617
- package/src/components/diagnose/investigationEvidence.ts +0 -5572
- package/src/components/gitops/GitOpsActions.tsx +0 -1
- package/src/components/gitops/GitOpsStatusBadge.tsx +0 -1
- package/src/components/gitops/ManagedResourcesList.tsx +0 -1
- package/src/components/gitops/SyncCountdown.tsx +0 -1
- package/src/components/gitops/index.ts +0 -4
- package/src/components/helm/ManifestDiffViewer.tsx +0 -69
- package/src/components/logs/JsonLogLine.tsx +0 -1
- package/src/components/logs/LogCore.tsx +0 -2
- package/src/components/logs/useLogBuffer.ts +0 -2
- package/src/components/logs/useLogSearch.ts +0 -1
- package/src/components/resource-drawer/ResourceDrawer.tsx +0 -216
- package/src/components/resources/drawer-components.tsx +0 -1
- package/src/components/resources/renderers/AlertRenderer.tsx +0 -1
- package/src/components/resources/renderers/CNPGBackupRenderer.tsx +0 -1
- package/src/components/resources/renderers/CNPGPoolerRenderer.tsx +0 -1
- package/src/components/resources/renderers/CNPGScheduledBackupRenderer.tsx +0 -1
- package/src/components/resources/renderers/CertificateRenderer.tsx +0 -1
- package/src/components/resources/renderers/CertificateRequestRenderer.tsx +0 -1
- package/src/components/resources/renderers/ChallengeRenderer.tsx +0 -1
- package/src/components/resources/renderers/ClusterComplianceReportRenderer.tsx +0 -1
- package/src/components/resources/renderers/ClusterExternalSecretRenderer.tsx +0 -1
- package/src/components/resources/renderers/ClusterIssuerRenderer.tsx +0 -1
- package/src/components/resources/renderers/ConfigAuditReportRenderer.tsx +0 -1
- package/src/components/resources/renderers/ConfigMapRenderer.tsx +0 -1
- package/src/components/resources/renderers/CronJobRenderer.tsx +0 -1
- package/src/components/resources/renderers/CronWorkflowRenderer.tsx +0 -1
- package/src/components/resources/renderers/EventRenderer.tsx +0 -1
- package/src/components/resources/renderers/ExposedSecretReportRenderer.tsx +0 -1
- package/src/components/resources/renderers/ExternalSecretRenderer.tsx +0 -1
- package/src/components/resources/renderers/FluxHelmReleaseRenderer.tsx +0 -1
- package/src/components/resources/renderers/GRPCRouteRenderer.tsx +0 -1
- package/src/components/resources/renderers/GatewayClassRenderer.tsx +0 -1
- package/src/components/resources/renderers/GatewayRenderer.tsx +0 -1
- package/src/components/resources/renderers/GenericRenderer.tsx +0 -1
- package/src/components/resources/renderers/GitRepositoryRenderer.tsx +0 -1
- package/src/components/resources/renderers/HTTPRouteRenderer.tsx +0 -1
- package/src/components/resources/renderers/HelmRepositoryRenderer.tsx +0 -1
- package/src/components/resources/renderers/IngressClassRenderer.tsx +0 -1
- package/src/components/resources/renderers/IngressRenderer.tsx +0 -1
- package/src/components/resources/renderers/IstioAuthorizationPolicyRenderer.tsx +0 -1
- package/src/components/resources/renderers/IstioDestinationRuleRenderer.tsx +0 -1
- package/src/components/resources/renderers/IstioGatewayRenderer.tsx +0 -1
- package/src/components/resources/renderers/IstioPeerAuthenticationRenderer.tsx +0 -1
- package/src/components/resources/renderers/IstioServiceEntryRenderer.tsx +0 -1
- package/src/components/resources/renderers/IstioVirtualServiceRenderer.tsx +0 -1
- package/src/components/resources/renderers/JobRenderer.tsx +0 -1
- package/src/components/resources/renderers/KarpenterEC2NodeClassRenderer.tsx +0 -1
- package/src/components/resources/renderers/KarpenterNodeClaimRenderer.tsx +0 -1
- package/src/components/resources/renderers/KedaScaledJobRenderer.tsx +0 -1
- package/src/components/resources/renderers/KedaScaledObjectRenderer.tsx +0 -1
- package/src/components/resources/renderers/KedaTriggerAuthRenderer.tsx +0 -1
- package/src/components/resources/renderers/KnativeConfigurationRenderer.tsx +0 -1
- package/src/components/resources/renderers/KnativeEventingRenderer.tsx +0 -1
- package/src/components/resources/renderers/KnativeFlowRenderer.tsx +0 -1
- package/src/components/resources/renderers/KnativeNetworkingRenderer.tsx +0 -1
- package/src/components/resources/renderers/KnativeRevisionRenderer.tsx +0 -1
- package/src/components/resources/renderers/KnativeRouteRenderer.tsx +0 -1
- package/src/components/resources/renderers/KnativeServiceRenderer.tsx +0 -1
- package/src/components/resources/renderers/KnativeSourceRenderer.tsx +0 -1
- package/src/components/resources/renderers/KustomizationRenderer.tsx +0 -1
- package/src/components/resources/renderers/KyvernoPolicyReportRenderer.tsx +0 -1
- package/src/components/resources/renderers/LeaseRenderer.tsx +0 -1
- package/src/components/resources/renderers/NetworkPolicyRenderer.tsx +0 -1
- package/src/components/resources/renderers/OCIRepositoryRenderer.tsx +0 -1
- package/src/components/resources/renderers/OrderRenderer.tsx +0 -1
- package/src/components/resources/renderers/PersistentVolumeRenderer.tsx +0 -1
- package/src/components/resources/renderers/PodDisruptionBudgetRenderer.tsx +0 -1
- package/src/components/resources/renderers/PodMonitorRenderer.tsx +0 -1
- package/src/components/resources/renderers/PriorityClassRenderer.tsx +0 -1
- package/src/components/resources/renderers/PrometheusRuleRenderer.tsx +0 -1
- package/src/components/resources/renderers/ReplicaSetRenderer.tsx +0 -1
- package/src/components/resources/renderers/RuntimeClassRenderer.tsx +0 -1
- package/src/components/resources/renderers/SbomReportRenderer.tsx +0 -1
- package/src/components/resources/renderers/SealedSecretRenderer.tsx +0 -1
- package/src/components/resources/renderers/SecretRenderer.tsx +0 -1
- package/src/components/resources/renderers/SecretStoreRenderer.tsx +0 -1
- package/src/components/resources/renderers/ServiceMonitorRenderer.tsx +0 -1
- package/src/components/resources/renderers/SimpleRouteRenderer.tsx +0 -1
- package/src/components/resources/renderers/StorageClassRenderer.tsx +0 -1
- package/src/components/resources/renderers/TraefikIngressRouteRenderer.tsx +0 -1
- package/src/components/resources/renderers/VPARenderer.tsx +0 -1
- package/src/components/resources/renderers/VeleroScheduleRenderer.tsx +0 -1
- package/src/components/resources/renderers/VeleroVSLRenderer.tsx +0 -1
- package/src/components/resources/renderers/VulnerabilityReportRenderer.tsx +0 -1
- package/src/components/resources/renderers/WebhookConfigRenderer.tsx +0 -1
- package/src/components/resources/renderers/WorkflowRenderer.tsx +0 -1
- package/src/components/resources/renderers/WorkflowTemplateRenderer.tsx +0 -1
- package/src/components/resources/renderers/argo-cells.tsx +0 -1
- package/src/components/resources/renderers/certmanager-cells.tsx +0 -1
- package/src/components/resources/renderers/cnpg-cells.tsx +0 -1
- package/src/components/resources/renderers/eso-cells.tsx +0 -1
- package/src/components/resources/renderers/flux-cells.tsx +0 -1
- package/src/components/resources/renderers/index.ts +0 -94
- package/src/components/resources/renderers/istio-cells.tsx +0 -1
- package/src/components/resources/renderers/karpenter-cells.tsx +0 -1
- package/src/components/resources/renderers/keda-cells.tsx +0 -1
- package/src/components/resources/renderers/knative-cells.tsx +0 -1
- package/src/components/resources/renderers/kyverno-cells.tsx +0 -1
- package/src/components/resources/renderers/prometheus-cells.tsx +0 -1
- package/src/components/resources/renderers/traefik-cells.tsx +0 -1
- package/src/components/resources/renderers/trivy-cells.tsx +0 -1
- package/src/components/resources/renderers/trivy-shared.tsx +0 -1
- package/src/components/resources/renderers/velero-cells.tsx +0 -1
- package/src/components/resources/resource-utils-argo.ts +0 -2
- package/src/components/resources/resource-utils-certmanager.ts +0 -2
- package/src/components/resources/resource-utils-eso.ts +0 -2
- package/src/components/resources/resource-utils-flux.ts +0 -2
- package/src/components/resources/resource-utils-istio.ts +0 -2
- package/src/components/resources/resource-utils-karpenter.ts +0 -2
- package/src/components/resources/resource-utils-knative.ts +0 -2
- package/src/components/resources/resource-utils-kyverno.ts +0 -2
- package/src/components/resources/resource-utils-prometheus.ts +0 -2
- package/src/components/resources/resource-utils-traefik.ts +0 -1
- package/src/components/resources/resource-utils-trivy.ts +0 -2
- package/src/components/resources/resource-utils-velero.ts +0 -2
- package/src/components/shared/EditableYamlView.tsx +0 -24
- package/src/components/timeline/DiffViewer.tsx +0 -1
- package/src/components/traffic/index.ts +0 -3
- package/src/components/ui/ForceDeleteConfirmDialog.tsx +0 -1
- package/src/components/ui/MetricsChart.tsx +0 -1
- package/src/components/ui/ResourceBar.tsx +0 -1
|
@@ -5,6 +5,14 @@ import { Collapse, CollapseChevron } from '@skyhook-io/k8s-ui/components/ui/Coll
|
|
|
5
5
|
import { DialogPortal } from '@skyhook-io/k8s-ui/components/ui/DialogPortal'
|
|
6
6
|
import { Tooltip } from './ui/Tooltip'
|
|
7
7
|
import { CloudConnectFlow } from './CloudConnectFlow'
|
|
8
|
+
import {
|
|
9
|
+
type Handoff,
|
|
10
|
+
exitFor,
|
|
11
|
+
handoffForBlocked,
|
|
12
|
+
handoffForPrepareError,
|
|
13
|
+
isHandoffOutcome,
|
|
14
|
+
signupUrlFor as buildSignupUrl,
|
|
15
|
+
} from './cloudConnectHandoff'
|
|
8
16
|
import { showApiError } from './ui/Toast'
|
|
9
17
|
import {
|
|
10
18
|
ApiError,
|
|
@@ -27,8 +35,10 @@ import {
|
|
|
27
35
|
//
|
|
28
36
|
// The only outbound call is the Hub's own copy, fetched when the dialog opens
|
|
29
37
|
// (never on a poll or a timer) and falling back per-field to the constants
|
|
30
|
-
// below.
|
|
31
|
-
//
|
|
38
|
+
// below. The request names the lane the footer renders and where this Radar
|
|
39
|
+
// runs, both closed enums. The links into the Hub carry utm_content naming
|
|
40
|
+
// the link that was clicked and, after an in-app attempt that did not connect,
|
|
41
|
+
// radar_outcome saying what happened; see cloudConnectHandoff.ts.
|
|
32
42
|
const FALLBACK_APP_URL = 'https://app.radarhq.io'
|
|
33
43
|
|
|
34
44
|
// Rendered until (or unless) the Hub states its own. Keeping the compiled-in
|
|
@@ -47,7 +57,15 @@ const DEFAULT_ASSURANCES = [
|
|
|
47
57
|
'SOC 2 Type II',
|
|
48
58
|
'3 clusters free, no card required',
|
|
49
59
|
]
|
|
50
|
-
|
|
60
|
+
// Other OSS surfaces (a GitOps app that deploys to another cluster, say)
|
|
61
|
+
// point at Radar Cloud by asking this button to open its dialog, so there
|
|
62
|
+
// is one pitch and one flow. The button is mounted whenever Radar runs
|
|
63
|
+
// standalone; embedded hosts never mount it and never dispatch this.
|
|
64
|
+
const OPEN_EVENT = 'radar:open-cloud-funnel'
|
|
65
|
+
|
|
66
|
+
export function openCloudFunnel() {
|
|
67
|
+
window.dispatchEvent(new Event(OPEN_EVENT))
|
|
68
|
+
}
|
|
51
69
|
const ABOUT_URL = 'https://radarhq.io/about'
|
|
52
70
|
const PRICING_URL = 'https://radarhq.io/pricing'
|
|
53
71
|
const SELF_HOSTED_DOCS_URL = 'https://radarhq.io/docs/cloud/self-hosted/'
|
|
@@ -79,25 +97,33 @@ export function CloudFunnelButton() {
|
|
|
79
97
|
const [seen, setSeen] = useState(readSeen)
|
|
80
98
|
const [inFlowView, setInFlowView] = useState(false)
|
|
81
99
|
const [blocked, setBlocked] = useState<CloudInstallBlocked | null>(null)
|
|
82
|
-
// Set once an in-app attempt has
|
|
83
|
-
//
|
|
84
|
-
//
|
|
85
|
-
//
|
|
86
|
-
//
|
|
87
|
-
|
|
100
|
+
// Set once an in-app attempt has ended without connecting, naming what
|
|
101
|
+
// happened (a flow failure kind, a blocked plan, a canceled plan, or the
|
|
102
|
+
// prepare call itself failing) and whether it is worth trying again. It
|
|
103
|
+
// relabels the CTA "Try again" for a retryable failure and lets the browser
|
|
104
|
+
// link say what the person is coming from. Survives closing the modal (the
|
|
105
|
+
// outcome belongs to the cluster, not the dialog session) but not a reload,
|
|
106
|
+
// and is reset below when the kubeconfig context changes — it must never
|
|
107
|
+
// describe a cluster the user has switched away from.
|
|
108
|
+
const [handoff, setHandoff] = useState<Handoff | null>(null)
|
|
109
|
+
const prepareFailed = handoff?.retryable ?? false
|
|
88
110
|
|
|
89
111
|
const capabilities = useCapabilities()
|
|
90
112
|
const lane = capabilities.data?.cloudConnect?.lane ?? 'wizard'
|
|
91
113
|
const appUrl = capabilities.data?.cloudConnect?.appUrl || FALLBACK_APP_URL
|
|
92
|
-
// utm_content
|
|
93
|
-
//
|
|
94
|
-
|
|
95
|
-
|
|
114
|
+
// utm_content names the link that was clicked. It travels only in the link
|
|
115
|
+
// the user opens; Radar sends nothing on its own. Only the blocked card may
|
|
116
|
+
// deep-link the install page (see exitFor); the pitch buttons and the
|
|
117
|
+
// footer link go to signup.
|
|
118
|
+
const signupUrl = buildSignupUrl(appUrl, 'wizard-signup-button')
|
|
96
119
|
|
|
97
120
|
// Only while the dialog is open — never on the capabilities poll. The Hub
|
|
98
121
|
// learns that someone opened it, which is congruent with what the dialog is
|
|
99
122
|
// for; it must not learn that Radar is merely running.
|
|
100
|
-
const connectInfo = useCloudConnectInfo(capabilities.data?.cloudConnect?.apiUrl, open
|
|
123
|
+
const connectInfo = useCloudConnectInfo(capabilities.data?.cloudConnect?.apiUrl, open, {
|
|
124
|
+
lane,
|
|
125
|
+
mode: capabilities.data?.deployment?.mode,
|
|
126
|
+
})
|
|
101
127
|
|
|
102
128
|
// In-cluster Radar can't install its own connection, but it knows exactly
|
|
103
129
|
// which install it is — so the wizard link can carry the real target, and a
|
|
@@ -135,7 +161,7 @@ export function CloudFunnelButton() {
|
|
|
135
161
|
// Anything else failed before a flow existed. Return to the pitch rather
|
|
136
162
|
// than leaving the flow view armed, where a later status change would
|
|
137
163
|
// pull the user into a screen they did not ask for.
|
|
138
|
-
exitFlow(
|
|
164
|
+
exitFlow(handoffForPrepareError(err))
|
|
139
165
|
showApiError('Could not inspect this cluster for Cloud connect', err instanceof Error ? err.message : undefined)
|
|
140
166
|
},
|
|
141
167
|
// No meta.errorMessage: the global handler cannot tell a single-flight 409
|
|
@@ -153,33 +179,66 @@ export function CloudFunnelButton() {
|
|
|
153
179
|
|
|
154
180
|
const startConnect = () => {
|
|
155
181
|
setBlocked(null)
|
|
156
|
-
|
|
182
|
+
setHandoff(null)
|
|
157
183
|
setInFlowView(true)
|
|
158
184
|
prepare.mutate()
|
|
159
185
|
}
|
|
160
186
|
|
|
161
|
-
// A flow that ended
|
|
162
|
-
// like a first attempt
|
|
163
|
-
//
|
|
164
|
-
|
|
165
|
-
|
|
187
|
+
// A flow that ended without connecting returns to a pitch whose CTA must
|
|
188
|
+
// not read like a first attempt, and whose browser link knows what happened.
|
|
189
|
+
// The caller passes the outcome because dismissing already overwrote the
|
|
190
|
+
// status by this point. Every exit sets it, null included, so a connected
|
|
191
|
+
// flow clears what an earlier attempt left behind.
|
|
192
|
+
const exitFlow = (outcome: Handoff | null = null) => {
|
|
193
|
+
setHandoff(outcome)
|
|
166
194
|
setInFlowView(false)
|
|
167
195
|
setBlocked(null)
|
|
168
196
|
}
|
|
169
197
|
|
|
198
|
+
// What the person is coming from. A blocked plan is read from local state,
|
|
199
|
+
// not the status: the status query keeps serving its last polled value
|
|
200
|
+
// (idle, usually) while the blocked view is up. A blocked plan counts as an
|
|
201
|
+
// attempt: the user tried the in-app path and was refused, and the wizard
|
|
202
|
+
// is the right next step for a GitOps-owned install. A failed flow carries
|
|
203
|
+
// the server's kind and its own verdict on retrying — the failed card
|
|
204
|
+
// already honors retrySafe, and the pitch must not contradict it two clicks
|
|
205
|
+
// later. Leaving a live plan is the user canceling it, not a failure. Only
|
|
206
|
+
// a connected flow, or no flow at all, leaves nothing to carry.
|
|
207
|
+
const outcomeOf = (st: CloudInstallStatus): Handoff | null => {
|
|
208
|
+
if (blocked) return handoffForBlocked(blocked.reason, blocked.attempted)
|
|
209
|
+
if (st.state === 'failed') {
|
|
210
|
+
return {
|
|
211
|
+
outcome: isHandoffOutcome(st.failure?.kind) ? st.failure.kind : 'failure_kind_unknown',
|
|
212
|
+
retryable: st.failure?.retrySafe ?? false,
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
if (st.state === 'blocked' && st.blocked) return handoffForBlocked(st.blocked.reason, st.blocked.attempted)
|
|
216
|
+
if (st.state === 'connected' || st.state === 'idle') return null
|
|
217
|
+
return { outcome: 'install_plan_canceled', retryable: false }
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
useEffect(() => {
|
|
221
|
+
window.addEventListener(OPEN_EVENT, openModal)
|
|
222
|
+
return () => window.removeEventListener(OPEN_EVENT, openModal)
|
|
223
|
+
})
|
|
224
|
+
|
|
170
225
|
// Re-attach to a server-owned flow whenever one is observed while the modal
|
|
171
226
|
// is open — the status query may resolve after openModal ran.
|
|
172
227
|
useEffect(() => {
|
|
173
228
|
if (open && lane === 'driver' && flowLive) setInFlowView(true)
|
|
174
229
|
}, [open, lane, flowLive])
|
|
175
230
|
|
|
176
|
-
//
|
|
177
|
-
//
|
|
178
|
-
// stays mounted, so without the reset cluster A's
|
|
179
|
-
// CTA for cluster B
|
|
180
|
-
|
|
231
|
+
// handoff and a blocked plan outlive the dialog but must not outlive the
|
|
232
|
+
// cluster they describe: a context switch swaps every query cache, yet this
|
|
233
|
+
// component stays mounted, so without the reset cluster A's outcome would
|
|
234
|
+
// relabel the CTA (and the link) for cluster B, and a blocked view left
|
|
235
|
+
// armed would reopen on B with A's refusal, feeding it back into the
|
|
236
|
+
// outcome on Back.
|
|
237
|
+
const clusterInfo = useClusterInfo()
|
|
238
|
+
const contextName = clusterInfo.data?.context
|
|
181
239
|
useEffect(() => {
|
|
182
|
-
|
|
240
|
+
setHandoff(null)
|
|
241
|
+
setBlocked(null)
|
|
183
242
|
}, [contextName])
|
|
184
243
|
|
|
185
244
|
// The server owns the "nothing to pitch" decision: an already-tunneled
|
|
@@ -248,9 +307,10 @@ export function CloudFunnelButton() {
|
|
|
248
307
|
<CloudConnectFlow
|
|
249
308
|
status={flowForView}
|
|
250
309
|
blocked={blocked}
|
|
251
|
-
|
|
310
|
+
exit={exitFor(appUrl, 'driver-blocked-card-browser-link', outcomeOf(flowForView), clusterInfo.data?.context)}
|
|
311
|
+
where={{ context: clusterInfo.data?.context, cluster: clusterInfo.data?.cluster }}
|
|
252
312
|
onStatus={applyStatus}
|
|
253
|
-
onExit={() => exitFlow(flowForView
|
|
313
|
+
onExit={() => exitFlow(outcomeOf(flowForView))}
|
|
254
314
|
/>
|
|
255
315
|
</div>
|
|
256
316
|
) : (
|
|
@@ -261,10 +321,12 @@ export function CloudFunnelButton() {
|
|
|
261
321
|
<ModalFooter
|
|
262
322
|
lane={lane}
|
|
263
323
|
signupUrl={signupUrl}
|
|
264
|
-
//
|
|
265
|
-
//
|
|
266
|
-
|
|
324
|
+
// One link name whether or not an attempt preceded the click; the
|
|
325
|
+
// outcome, when present, is what says an attempt happened.
|
|
326
|
+
driverBrowserUrl={buildSignupUrl(appUrl, 'driver-footer-browser-link', handoff)}
|
|
267
327
|
prepareFailed={prepareFailed}
|
|
328
|
+
// A prepare error's reason, kept on the pitch after its toast is gone.
|
|
329
|
+
prepareError={handoff?.detail}
|
|
268
330
|
assurances={connectInfo.data?.assurances}
|
|
269
331
|
notice={connectInfo.data?.notice}
|
|
270
332
|
self={inCluster ? self.data : undefined}
|
|
@@ -316,8 +378,9 @@ function Eyebrow() {
|
|
|
316
378
|
function ModalFooter({
|
|
317
379
|
lane,
|
|
318
380
|
signupUrl,
|
|
319
|
-
|
|
381
|
+
driverBrowserUrl,
|
|
320
382
|
prepareFailed,
|
|
383
|
+
prepareError,
|
|
321
384
|
assurances,
|
|
322
385
|
notice,
|
|
323
386
|
self,
|
|
@@ -327,10 +390,11 @@ function ModalFooter({
|
|
|
327
390
|
}: {
|
|
328
391
|
lane: 'driver' | 'wizard'
|
|
329
392
|
signupUrl: string
|
|
330
|
-
// Same destination as signupUrl, distinct utm_content
|
|
331
|
-
//
|
|
332
|
-
|
|
393
|
+
// Same destination as signupUrl, distinct utm_content, and the outcome of
|
|
394
|
+
// an in-app attempt when one preceded this render.
|
|
395
|
+
driverBrowserUrl: string
|
|
333
396
|
prepareFailed: boolean
|
|
397
|
+
prepareError?: string
|
|
334
398
|
// Live copy from the Hub; undefined until (or unless) it arrives.
|
|
335
399
|
assurances?: string[]
|
|
336
400
|
notice?: string
|
|
@@ -409,7 +473,7 @@ function ModalFooter({
|
|
|
409
473
|
a recovery path — install-averse operators need the door before
|
|
410
474
|
anything fails, or they close the modal instead. */}
|
|
411
475
|
<a
|
|
412
|
-
href={
|
|
476
|
+
href={driverBrowserUrl}
|
|
413
477
|
target="_blank"
|
|
414
478
|
rel="noopener noreferrer"
|
|
415
479
|
className="whitespace-nowrap text-[12.5px] text-theme-text-secondary hover:text-theme-text-primary hover:underline underline-offset-2 transition-colors"
|
|
@@ -433,6 +497,15 @@ function ModalFooter({
|
|
|
433
497
|
Maybe later
|
|
434
498
|
</button>
|
|
435
499
|
</div>
|
|
500
|
+
{/* The last attempt's stop, when Radar could not even inspect the
|
|
501
|
+
cluster: the toast that first said so is gone in seconds, and "Try
|
|
502
|
+
again" alone does not say what to try again for. The footer link
|
|
503
|
+
above is the way around it. */}
|
|
504
|
+
{lane === 'driver' && prepareError && (
|
|
505
|
+
<p className="mt-2.5 text-[11.5px] leading-relaxed text-amber-600 dark:text-amber-400">
|
|
506
|
+
Radar couldn’t inspect this cluster: {prepareError}
|
|
507
|
+
</p>
|
|
508
|
+
)}
|
|
436
509
|
{/* Mechanics, not marketing: a falsifiable claim the plan card then
|
|
437
510
|
fulfills. Sits next to the button whose click it de-risks. */}
|
|
438
511
|
{lane === 'driver' && (
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ServerOff, RefreshCw, Loader2, Copy, Check, TerminalSquare
|
|
1
|
+
import { ServerOff, RefreshCw, Loader2, Copy, Check, TerminalSquare } from 'lucide-react'
|
|
2
2
|
import { useEffect, useState } from 'react'
|
|
3
3
|
import type { ConnectionState } from '../context/ConnectionContext'
|
|
4
4
|
import { ContextSwitcher } from './ContextSwitcher'
|
|
@@ -6,6 +6,7 @@ import { parseContextName } from '../utils/context-name'
|
|
|
6
6
|
import { useOpenLocalTerminal, ClusterName } from '@skyhook-io/k8s-ui'
|
|
7
7
|
import { useAuthMe, useContexts } from '../api/client'
|
|
8
8
|
import { Tooltip } from './ui/Tooltip'
|
|
9
|
+
import { Collapse, CollapseChevron } from '@skyhook-io/k8s-ui/components/ui/Collapse'
|
|
9
10
|
import { allShellSafe, awsProfileFlag } from '../utils/shell-safe'
|
|
10
11
|
import { apiUrl } from '../api/config'
|
|
11
12
|
import { useCapabilitiesContext } from '../contexts/CapabilitiesContext'
|
|
@@ -459,18 +460,16 @@ export function ConnectionErrorView({ connection, onRetry, isRetrying }: Connect
|
|
|
459
460
|
onClick={() => setShowRawError((open) => !open)}
|
|
460
461
|
className="flex items-center gap-1 text-xs font-medium text-theme-text-tertiary hover:text-theme-text-secondary transition-colors"
|
|
461
462
|
>
|
|
462
|
-
<
|
|
463
|
+
<CollapseChevron open={showRawError} className="h-3.5 w-3.5" />
|
|
463
464
|
Raw error
|
|
464
465
|
</button>
|
|
465
|
-
<
|
|
466
|
-
<div className="overflow-
|
|
467
|
-
<
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
</code>
|
|
471
|
-
</div>
|
|
466
|
+
<Collapse open={showRawError} id="connection-raw-error">
|
|
467
|
+
<div className="mt-2 bg-theme-elevated border border-theme-border rounded-md p-3 overflow-auto max-h-32">
|
|
468
|
+
<code className="text-xs text-theme-text-tertiary font-mono whitespace-pre-wrap break-words">
|
|
469
|
+
{connection.error}
|
|
470
|
+
</code>
|
|
472
471
|
</div>
|
|
473
|
-
</
|
|
472
|
+
</Collapse>
|
|
474
473
|
</div>
|
|
475
474
|
)}
|
|
476
475
|
</div>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useState } from 'react'
|
|
2
|
-
import { Bug, X
|
|
2
|
+
import { Bug, X } from 'lucide-react'
|
|
3
|
+
import { Collapse, CollapseChevron, useDisclosure } from '@skyhook-io/k8s-ui/components/ui/Collapse'
|
|
3
4
|
import { useRuntimeStats } from '../api/client'
|
|
4
5
|
import { Tooltip } from './ui/Tooltip'
|
|
5
6
|
|
|
@@ -14,6 +15,7 @@ function formatUptime(seconds: number): string {
|
|
|
14
15
|
export function DebugOverlay() {
|
|
15
16
|
const [visible, setVisible] = useState(true)
|
|
16
17
|
const [expanded, setExpanded] = useState(false)
|
|
18
|
+
const disclosure = useDisclosure(expanded)
|
|
17
19
|
const { data } = useRuntimeStats(visible)
|
|
18
20
|
|
|
19
21
|
if (!visible) {
|
|
@@ -40,9 +42,11 @@ export function DebugOverlay() {
|
|
|
40
42
|
<div className="flex-1" />
|
|
41
43
|
<button
|
|
42
44
|
onClick={() => setExpanded(!expanded)}
|
|
45
|
+
{...disclosure.buttonProps}
|
|
46
|
+
aria-label={expanded ? 'Collapse debug stats' : 'Expand debug stats'}
|
|
43
47
|
className="p-0.5 text-theme-text-tertiary hover:text-theme-text-secondary"
|
|
44
48
|
>
|
|
45
|
-
|
|
49
|
+
<CollapseChevron open={expanded} className="w-3 h-3" />
|
|
46
50
|
</button>
|
|
47
51
|
<button
|
|
48
52
|
onClick={() => setVisible(false)}
|
|
@@ -60,8 +64,8 @@ export function DebugOverlay() {
|
|
|
60
64
|
<span className="text-theme-text-tertiary">Heap</span>
|
|
61
65
|
<span className="text-theme-text-primary">{runtime.heapMB.toFixed(1)} MB</span>
|
|
62
66
|
</div>
|
|
63
|
-
{expanded
|
|
64
|
-
|
|
67
|
+
<Collapse open={expanded} id={disclosure.panelId}>
|
|
68
|
+
<div className="space-y-0.5">
|
|
65
69
|
<div className="flex justify-between gap-4">
|
|
66
70
|
<span className="text-theme-text-tertiary">Objects</span>
|
|
67
71
|
<span className="text-theme-text-primary">{runtime.heapObjectsK.toFixed(1)}K</span>
|
|
@@ -84,8 +88,8 @@ export function DebugOverlay() {
|
|
|
84
88
|
<span className="text-theme-text-tertiary">Resources</span>
|
|
85
89
|
<span className="text-theme-text-primary">{data?.resourceCount ?? '-'}</span>
|
|
86
90
|
</div>
|
|
87
|
-
|
|
88
|
-
|
|
91
|
+
</div>
|
|
92
|
+
</Collapse>
|
|
89
93
|
</>
|
|
90
94
|
) : (
|
|
91
95
|
<span className="text-theme-text-tertiary">Loading…</span>
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { useMemo } from 'react'
|
|
2
|
+
import { Loader2, ArrowRight } from 'lucide-react'
|
|
3
|
+
import { StatusDot } from '@skyhook-io/k8s-ui'
|
|
4
|
+
import type { SyncStatusSnapshot } from '../api/client'
|
|
5
|
+
|
|
6
|
+
// Shown in place of views that need the full cluster dataset while the
|
|
7
|
+
// initial informer sync is still running. Resource views for already-synced
|
|
8
|
+
// kinds work during this window — the per-kind list doubles as the map of
|
|
9
|
+
// what's usable right now.
|
|
10
|
+
export function SyncProgressPanel({
|
|
11
|
+
syncStatus,
|
|
12
|
+
onNavigateToKind,
|
|
13
|
+
}: {
|
|
14
|
+
syncStatus: SyncStatusSnapshot
|
|
15
|
+
onNavigateToKind: (key: string) => void
|
|
16
|
+
}) {
|
|
17
|
+
const total = syncStatus.criticalTotal + syncStatus.deferredTotal
|
|
18
|
+
const synced = syncStatus.criticalSynced + syncStatus.deferredSynced
|
|
19
|
+
const kinds = useMemo(
|
|
20
|
+
() => [...syncStatus.kinds].sort((a, b) => {
|
|
21
|
+
// Ready first, failed last, both alphabetical within their group.
|
|
22
|
+
const rank = (k: typeof a) => (k.synced ? 0 : k.failed ? 2 : 1)
|
|
23
|
+
if (rank(a) !== rank(b)) return rank(a) - rank(b)
|
|
24
|
+
return a.kind.localeCompare(b.kind)
|
|
25
|
+
}),
|
|
26
|
+
[syncStatus.kinds],
|
|
27
|
+
)
|
|
28
|
+
const failedCount = useMemo(
|
|
29
|
+
() => syncStatus.kinds.filter((k) => k.failed).length,
|
|
30
|
+
[syncStatus.kinds],
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<div className="flex-1 flex items-center justify-center overflow-y-auto bg-theme-base">
|
|
35
|
+
<div className="max-w-xl w-full px-8 py-10">
|
|
36
|
+
<div className="flex items-center gap-3 mb-1">
|
|
37
|
+
<Loader2 className="w-5 h-5 animate-spin text-theme-text-tertiary" />
|
|
38
|
+
<h2 className="text-lg font-semibold text-theme-text-primary">Loading cluster data</h2>
|
|
39
|
+
</div>
|
|
40
|
+
<p className="text-sm text-theme-text-secondary mb-6">
|
|
41
|
+
{synced} of {total} resource types ready — views open as their data finishes loading.
|
|
42
|
+
This view needs the full dataset and will appear automatically.
|
|
43
|
+
{failedCount > 0 && (
|
|
44
|
+
<span className="block mt-1 text-red-400">
|
|
45
|
+
{failedCount} resource {failedCount === 1 ? 'type' : 'types'} failed to load within the
|
|
46
|
+
sync deadline — affected views will show an error. Retry the connection to try again.
|
|
47
|
+
</span>
|
|
48
|
+
)}
|
|
49
|
+
</p>
|
|
50
|
+
<div className="card-inner grid grid-cols-2 gap-x-6 gap-y-1.5 max-h-80 overflow-y-auto">
|
|
51
|
+
{kinds.map((k) => (
|
|
52
|
+
<div key={k.key} className="flex items-center gap-2 min-w-0 text-sm">
|
|
53
|
+
<StatusDot tone={k.synced ? 'healthy' : k.failed ? 'unhealthy' : 'neutral'} size="sm" className="shrink-0" />
|
|
54
|
+
{k.synced ? (
|
|
55
|
+
<button
|
|
56
|
+
onClick={() => onNavigateToKind(k.key)}
|
|
57
|
+
className="group flex items-center gap-1 min-w-0 text-theme-text-primary hover:text-theme-brand transition-colors"
|
|
58
|
+
>
|
|
59
|
+
<span className="truncate">{k.kind}</span>
|
|
60
|
+
<ArrowRight className="w-3 h-3 opacity-0 group-hover:opacity-100 transition-opacity shrink-0" />
|
|
61
|
+
</button>
|
|
62
|
+
) : k.failed ? (
|
|
63
|
+
<span className="truncate text-red-400">{k.kind} — failed</span>
|
|
64
|
+
) : (
|
|
65
|
+
<span className="truncate text-theme-text-tertiary animate-pulse">{k.kind}</span>
|
|
66
|
+
)}
|
|
67
|
+
</div>
|
|
68
|
+
))}
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
)
|
|
73
|
+
}
|
|
@@ -124,6 +124,8 @@ function BestPracticesView({ namespaces, onNavigateToResource }: AuditViewProps)
|
|
|
124
124
|
checks={data.groupedChecks ?? []}
|
|
125
125
|
catalog={data.checks ?? {}}
|
|
126
126
|
anyData
|
|
127
|
+
evaluated={data.summary.passing + data.summary.warning + data.summary.danger}
|
|
128
|
+
missingInputs={data.missingInputs}
|
|
127
129
|
onResourceClick={onResourceClick}
|
|
128
130
|
onHideCheck={canEdit ? hideCheck : undefined}
|
|
129
131
|
onHideCategory={canEdit ? hideCategory : undefined}
|