@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
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useState, useMemo, useCallback, useEffect } from 'react'
|
|
2
2
|
import { useLocation, useNavigate } from 'react-router-dom'
|
|
3
3
|
import { useQuery } from '@tanstack/react-query'
|
|
4
|
-
import { ApiError, debugNamespaceLog, fetchJSON, isForbiddenError, useCapabilities, useNamespaceCapabilities, useResources, useSecretCertExpiry, useTopPodMetrics, useTopNodeMetrics, useBulkDeleteResources, useBulkRestartWorkloads, useBulkScaleWorkloads, useAudit } from '../../api/client'
|
|
4
|
+
import { ApiError, debugNamespaceLog, fetchJSON, isForbiddenError, isKindSyncFailed, isStillLoadingError, useCapabilities, useNamespaceCapabilities, useResources, useSecretCertExpiry, useTopPodMetrics, useTopNodeMetrics, useBulkDeleteResources, useBulkRestartWorkloads, useBulkScaleWorkloads, useAudit } from '../../api/client'
|
|
5
5
|
import { isBadgeWorthy } from '../../utils/auditBadges'
|
|
6
6
|
import type { AuditBadgeMessage } from '@skyhook-io/k8s-ui'
|
|
7
7
|
import { apiUrl, getAuthHeaders, getCredentialsMode, stripBasename } from '../../api/config'
|
|
@@ -143,7 +143,18 @@ export function ResourcesView({ namespaces, selectedResource, onResourceClick, o
|
|
|
143
143
|
}
|
|
144
144
|
},
|
|
145
145
|
staleTime: 10000,
|
|
146
|
-
|
|
146
|
+
// SSE invalidation isn't running while connecting, and mid-sync counts
|
|
147
|
+
// are what unlatch guarded kinds as their informers finish — poll fast
|
|
148
|
+
// during the shell, settle to the safety net once connected.
|
|
149
|
+
refetchInterval: connection.state === 'connecting' ? 3000 : 60000,
|
|
150
|
+
// During the first seconds of the progressive shell the endpoint 503s
|
|
151
|
+
// (cluster_connecting) until the mid-sync cache handle exists; keep the
|
|
152
|
+
// query pending rather than parking it in error state, which would
|
|
153
|
+
// unlatch the large-list guard at the connected flip.
|
|
154
|
+
retry: (failureCount: number, error: Error) =>
|
|
155
|
+
isStillLoadingError(error) ? true : failureCount < 3,
|
|
156
|
+
retryDelay: (failureCount: number, error: Error) =>
|
|
157
|
+
isStillLoadingError(error) ? 2000 : Math.min(1000 * 2 ** failureCount, 30000),
|
|
147
158
|
})
|
|
148
159
|
|
|
149
160
|
// Determine if selected kind is a CRD (only CRDs should send ?group= to backend)
|
|
@@ -200,8 +211,29 @@ export function ResourcesView({ namespaces, selectedResource, onResourceClick, o
|
|
|
200
211
|
const isSelectedKindGuarded = selectedCountKey !== '' && LARGE_RESOURCE_LIST_GUARD_KEYS.has(selectedCountKey)
|
|
201
212
|
const selectedKindSummaryServed = SUMMARY_LIST_KINDS.has(selectedCountKey)
|
|
202
213
|
const selectedKindRowLimit = selectedKindSummaryServed ? SUMMARY_LIST_LIMIT : LARGE_RESOURCE_LIST_LIMIT
|
|
203
|
-
|
|
204
|
-
|
|
214
|
+
// While still 'connecting', /resource-counts is unavailable — its error
|
|
215
|
+
// must NOT unlatch the large-list guard, or a huge Pods list could fetch
|
|
216
|
+
// unguarded on exactly the clusters the guard protects. Counts arrive
|
|
217
|
+
// right after 'connected' and settle the guard then.
|
|
218
|
+
const syncShellActive = connection.state === 'connecting'
|
|
219
|
+
// A kind whose sync deadline fired is unavailable-with-a-reason: hand it to
|
|
220
|
+
// the list endpoint, whose 503 kind_sync_failed renders the honest error —
|
|
221
|
+
// the guard would otherwise trap it on "count unavailable" forever. Safe to
|
|
222
|
+
// unblock: the server serves no rows for a failed kind.
|
|
223
|
+
const selectedCountFailedSync =
|
|
224
|
+
selectedCountUnavailable && countsData?.reasons?.[selectedCountKey] === 'kind_sync_failed'
|
|
225
|
+
// A guarded kind whose informer hasn't finished reports unavailable with a
|
|
226
|
+
// kind_sync_pending reason — "count not known YET", so keep the loading
|
|
227
|
+
// state. Keyed on the reason, not on connection.state: deferred kinds sync
|
|
228
|
+
// after connect. Reasonless unavailable stays a real verification failure
|
|
229
|
+
// and blocks the view as before.
|
|
230
|
+
const selectedCountPendingSync =
|
|
231
|
+
(selectedCountUnavailable && countsData?.reasons?.[selectedCountKey] === 'kind_sync_pending') ||
|
|
232
|
+
(syncShellActive && selectedCountUnavailable && !selectedCountFailedSync)
|
|
233
|
+
const waitingForGuardCount = isSelectedKindGuarded &&
|
|
234
|
+
((!countsData && (!countsIsError || syncShellActive)) || selectedCountPendingSync)
|
|
235
|
+
const largeListBlocked = isSelectedKindGuarded && countsData != null && !selectedCountPendingSync && !selectedCountFailedSync &&
|
|
236
|
+
(selectedCountUnavailable || (selectedCountKnown && (selectedCount ?? 0) > selectedKindRowLimit))
|
|
205
237
|
const selectedKindQueryBlocked = waitingForGuardCount || largeListBlocked
|
|
206
238
|
const podCount = countsData?.counts.Pod
|
|
207
239
|
const podCountKnown = hasResourceCount(countsData?.counts, 'Pod')
|
|
@@ -285,9 +317,15 @@ export function ResourcesView({ namespaces, selectedResource, onResourceClick, o
|
|
|
285
317
|
staleTime: 30000,
|
|
286
318
|
refetchInterval: 120000, // Safety net — SSE k8s_event drives near-real-time invalidation
|
|
287
319
|
retry: (failureCount: number, error: Error) => {
|
|
288
|
-
if (isForbiddenError(error)) return false
|
|
320
|
+
if (isForbiddenError(error) || isKindSyncFailed(error)) return false
|
|
321
|
+
// Cluster still connecting, or this kind's informer still completing
|
|
322
|
+
// its initial sync: keep the query in its loading state and retry —
|
|
323
|
+
// the header's sync-progress label explains the wait.
|
|
324
|
+
if (isStillLoadingError(error)) return true
|
|
289
325
|
return failureCount < 3
|
|
290
326
|
},
|
|
327
|
+
retryDelay: (failureCount: number, error: Error) =>
|
|
328
|
+
isStillLoadingError(error) ? 2000 : Math.min(1000 * 2 ** failureCount, 30000),
|
|
291
329
|
})
|
|
292
330
|
|
|
293
331
|
// Map to ResourceQueryResult shape
|
|
@@ -398,7 +436,7 @@ export function ResourcesView({ namespaces, selectedResource, onResourceClick, o
|
|
|
398
436
|
resourceUnavailable={countsData?.unavailable}
|
|
399
437
|
selectedKindQuery={selectedKindQueryResult}
|
|
400
438
|
printerTable={selectedKindQueryBlocked ? null : selectedKindQuery.data?.printerTable ?? null}
|
|
401
|
-
connectionState={connection.state}
|
|
439
|
+
connectionState={connection.state === 'connecting' && connection.syncStatus ? 'syncing' : connection.state}
|
|
402
440
|
largeListGuard={largeListGuard}
|
|
403
441
|
onSelectedKindChange={setSelectedKind}
|
|
404
442
|
topPodMetrics={topPodMetrics}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { NamespaceRenderer as BaseNamespaceRenderer } from '@skyhook-io/k8s-ui/components/resources/renderers/NamespaceRenderer'
|
|
2
2
|
import type { ResourceRef } from '@skyhook-io/k8s-ui'
|
|
3
3
|
import { useRBACNamespace } from '../../../api/rbac'
|
|
4
|
-
import { useNamespaceQuotas } from '../../../api/quotas'
|
|
4
|
+
import { useNamespaceLimitRanges, useNamespaceQuotas } from '../../../api/quotas'
|
|
5
5
|
import { isForbiddenError } from '../../../api/client'
|
|
6
6
|
|
|
7
7
|
interface NamespaceRendererProps {
|
|
@@ -17,6 +17,9 @@ export function NamespaceRenderer({ data, onNavigate }: NamespaceRendererProps)
|
|
|
17
17
|
// RBAC sections). Surface other errors (500/503) so a quota-constrained
|
|
18
18
|
// namespace doesn't silently render as quota-free.
|
|
19
19
|
const quotaErr = quotaError && !isForbiddenError(quotaError) ? (quotaError as Error) : null
|
|
20
|
+
// LimitRanges keep their 403: the section states the denial rather than
|
|
21
|
+
// hiding, because "no rules apply here" is the wrong thing to imply.
|
|
22
|
+
const { data: limitRangeData, isLoading: limitRangeLoading, error: limitRangeError } = useNamespaceLimitRanges(name, !!name)
|
|
20
23
|
return (
|
|
21
24
|
<BaseNamespaceRenderer
|
|
22
25
|
data={data}
|
|
@@ -25,6 +28,9 @@ export function NamespaceRenderer({ data, onNavigate }: NamespaceRendererProps)
|
|
|
25
28
|
rbacError={error as Error | null}
|
|
26
29
|
quotaData={quotaData}
|
|
27
30
|
quotaError={quotaErr}
|
|
31
|
+
limitRangeData={limitRangeData}
|
|
32
|
+
limitRangeLoading={limitRangeLoading}
|
|
33
|
+
limitRangeError={limitRangeError}
|
|
28
34
|
onNavigate={onNavigate}
|
|
29
35
|
/>
|
|
30
36
|
)
|
|
@@ -7,6 +7,7 @@ import { useCapabilitiesContext, useNamespacedCapabilities, useIsLocalDeployment
|
|
|
7
7
|
import { getVisibleLiveMetrics, isLiveMetricsUnavailable, shouldFetchLiveMetrics, usePodEnvironment, usePodMetrics, usePodMetricsHistory, usePrometheusResourceMetrics, usePrometheusStatus, useRevealPodEnvironment } from '../../../api/client'
|
|
8
8
|
import { useRBACSubject } from '../../../api/rbac'
|
|
9
9
|
import { usePolicyResource } from '../../../api/policy'
|
|
10
|
+
import { podLimitRangeNames, useNamespaceLimitRanges } from '../../../api/quotas'
|
|
10
11
|
import { podAwaitsScheduling } from '../../capacity/podDemandGate'
|
|
11
12
|
import { PortForwardInlineButton } from '../../portforward/PortForwardButton'
|
|
12
13
|
import { ImageFilesystemModal } from '../ImageFilesystemModal'
|
|
@@ -79,6 +80,8 @@ export function PodRenderer({ data, onCopy, copied, onNavigate, onOpenLogs, reso
|
|
|
79
80
|
'pods', namespace ?? '', podName, !!namespace && !!podName,
|
|
80
81
|
)
|
|
81
82
|
|
|
83
|
+
const { data: limitRanges, isSuccess: limitRangesReady } = useNamespaceLimitRanges(namespace ?? '', !!namespace)
|
|
84
|
+
|
|
82
85
|
return (
|
|
83
86
|
<BasePodRenderer
|
|
84
87
|
data={data}
|
|
@@ -110,6 +113,7 @@ export function PodRenderer({ data, onCopy, copied, onNavigate, onOpenLogs, reso
|
|
|
110
113
|
policyData={policyData ?? null}
|
|
111
114
|
policyLoading={policyLoading}
|
|
112
115
|
policyError={policyError as Error | null}
|
|
116
|
+
namespaceLimitRangeNames={limitRangesReady ? podLimitRangeNames(limitRanges) : undefined}
|
|
113
117
|
canExec={canExec}
|
|
114
118
|
canViewLogs={canViewLogs}
|
|
115
119
|
canPortForward={showPortForward}
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import { RolloutRenderer as BaseRolloutRenderer } from '@skyhook-io/k8s-ui/components/resources/renderers/RolloutRenderer'
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
useRolloutAction,
|
|
4
|
+
useRolloutAnalysisRuns,
|
|
5
|
+
useRolloutCapabilities,
|
|
6
|
+
useWorkloadRevisions,
|
|
7
|
+
useWorkloadPods,
|
|
8
|
+
type RolloutAction,
|
|
9
|
+
} from '../../../api/client'
|
|
3
10
|
|
|
4
11
|
interface RolloutRendererProps {
|
|
5
12
|
data: any
|
|
@@ -11,6 +18,9 @@ export function RolloutRenderer({ data, onNavigate }: RolloutRendererProps) {
|
|
|
11
18
|
const name = data?.metadata?.name ?? ''
|
|
12
19
|
const { data: capabilities } = useRolloutCapabilities(namespace, name)
|
|
13
20
|
const action = useRolloutAction()
|
|
21
|
+
const { data: analysisRunsResponse, error: analysisRunsError } = useRolloutAnalysisRuns(namespace, name)
|
|
22
|
+
const { data: revisions } = useWorkloadRevisions('rollouts', namespace, name)
|
|
23
|
+
const { data: podsResponse } = useWorkloadPods('rollouts', namespace, name)
|
|
14
24
|
|
|
15
25
|
return (
|
|
16
26
|
<BaseRolloutRenderer
|
|
@@ -19,6 +29,10 @@ export function RolloutRenderer({ data, onNavigate }: RolloutRendererProps) {
|
|
|
19
29
|
capabilities={capabilities}
|
|
20
30
|
onAction={(next: RolloutAction) => action.mutate({ action: next, namespace, name })}
|
|
21
31
|
pendingAction={action.isPending ? action.variables?.action ?? null : null}
|
|
32
|
+
analysisRunHistory={analysisRunsResponse?.items}
|
|
33
|
+
analysisRunHistoryError={analysisRunsError}
|
|
34
|
+
revisions={revisions}
|
|
35
|
+
pods={podsResponse?.pods}
|
|
22
36
|
/>
|
|
23
37
|
)
|
|
24
38
|
}
|
|
@@ -5,6 +5,7 @@ import { CurlButton, CurlPanel, isHttpishPort, defaultScheme, defaultPathForPort
|
|
|
5
5
|
import { useResources } from '../../../api/client'
|
|
6
6
|
import { useNamespacedCapabilities, useIsLocalDeployment } from '../../../contexts/CapabilitiesContext'
|
|
7
7
|
import type { ResourceRef } from '../../../types'
|
|
8
|
+
import { DURATION_DISCLOSURE } from '@skyhook-io/k8s-ui/utils/animation'
|
|
8
9
|
|
|
9
10
|
interface ServiceRendererProps {
|
|
10
11
|
data: any
|
|
@@ -32,7 +33,7 @@ export function ServiceRenderer({ data, onCopy, copied, onNavigate }: ServiceRen
|
|
|
32
33
|
setCurl((p) => (p ? { ...p, closing: true } : null))
|
|
33
34
|
// Only drop the panel if it's still the one closing. Opening another port
|
|
34
35
|
// (which sets closing:false) before this fires must not clear the new panel.
|
|
35
|
-
window.setTimeout(() => setCurl((p) => (p?.closing ? null : p)),
|
|
36
|
+
window.setTimeout(() => setCurl((p) => (p?.closing ? null : p)), DURATION_DISCLOSURE + 20)
|
|
36
37
|
}, [])
|
|
37
38
|
const spec = data.spec || {}
|
|
38
39
|
const shouldLoadEndpointSlices = Boolean(
|
|
@@ -3,6 +3,7 @@ import { useNavigate } from 'react-router-dom'
|
|
|
3
3
|
import { useScaleWorkload, fetchJSON } from '../../../api/client'
|
|
4
4
|
import { useRBACSubject } from '../../../api/rbac'
|
|
5
5
|
import { usePolicyResource } from '../../../api/policy'
|
|
6
|
+
import { podLimitRangeNames, useNamespaceLimitRanges } from '../../../api/quotas'
|
|
6
7
|
import { useQueries, useQueryClient } from '@tanstack/react-query'
|
|
7
8
|
import { kindToPlural, kindToPluralWithGroup } from '@skyhook-io/k8s-ui/utils/navigation'
|
|
8
9
|
import type { Relationships, ResourceRef, ResourceWithRelationships, WorkloadPodInfo } from '../../../types'
|
|
@@ -49,6 +50,7 @@ export function WorkloadRenderer({ kind, data, onNavigate, scaleBlockedBy, workl
|
|
|
49
50
|
const { data: policyData, isLoading: policyLoading, error: policyError } = usePolicyResource(
|
|
50
51
|
kindToPlural(kind), namespace, metadata.name || '', !!namespace && !!metadata.name,
|
|
51
52
|
)
|
|
53
|
+
const { data: limitRanges, isSuccess: limitRangesReady } = useNamespaceLimitRanges(namespace, !!namespace)
|
|
52
54
|
const hpaRefs = (scaleBlockedBy ?? []).filter(ref => {
|
|
53
55
|
const refKind = ref.kind.toLowerCase()
|
|
54
56
|
return refKind === 'horizontalpodautoscaler' || refKind === 'hpa'
|
|
@@ -96,6 +98,7 @@ export function WorkloadRenderer({ kind, data, onNavigate, scaleBlockedBy, workl
|
|
|
96
98
|
policyData={policyData ?? null}
|
|
97
99
|
policyLoading={policyLoading}
|
|
98
100
|
policyError={policyError as Error | null}
|
|
101
|
+
namespaceLimitRangeNames={limitRangesReady ? podLimitRangeNames(limitRanges) : undefined}
|
|
99
102
|
scaleBlockedBy={scaleBlockedBy}
|
|
100
103
|
workloadPods={workloadPods}
|
|
101
104
|
scalerDiagnostics={scalerDiagnostics}
|
|
@@ -799,6 +799,27 @@ function FitWhy({ label, row }: { label: string; row?: RightsizingRow }) {
|
|
|
799
799
|
}
|
|
800
800
|
|
|
801
801
|
function EvidenceNote({ row }: { row: RightsizingRow }) {
|
|
802
|
+
const primary = primaryEvidenceNote(row)
|
|
803
|
+
// A denied pod read qualifies whichever reason applies rather than standing in
|
|
804
|
+
// for it: a row can both be unable to check its pods and have a limit conflict,
|
|
805
|
+
// and hiding either one behind the other loses something the reader needs.
|
|
806
|
+
const inventory =
|
|
807
|
+
row.liveInventoryUnavailable && row.resource === 'memory' ? (
|
|
808
|
+
<p className="mt-1 text-xs text-theme-text-tertiary">
|
|
809
|
+
Radar could not read this workload's pods, so it could not check them for
|
|
810
|
+
out-of-memory restarts.
|
|
811
|
+
</p>
|
|
812
|
+
) : null
|
|
813
|
+
if (!primary && !inventory) return null
|
|
814
|
+
return (
|
|
815
|
+
<>
|
|
816
|
+
{primary}
|
|
817
|
+
{inventory}
|
|
818
|
+
</>
|
|
819
|
+
)
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
function primaryEvidenceNote(row: RightsizingRow) {
|
|
802
823
|
if (row.hpaManaged)
|
|
803
824
|
return (
|
|
804
825
|
<p className="mt-1 text-xs text-theme-text-tertiary">
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
import { clsx } from 'clsx'
|
|
10
10
|
import { useQueryClient } from '@tanstack/react-query'
|
|
11
11
|
import { useAnimatedUnmount } from '../../hooks/useAnimatedUnmount'
|
|
12
|
-
import { TRANSITION_BACKDROP, TRANSITION_PANEL } from '../../utils/animation'
|
|
12
|
+
import { TRANSITION_BACKDROP, TRANSITION_PANEL, overlayExitMs, overlayTransitionStyle } from '../../utils/animation'
|
|
13
13
|
import { apiUrl, getAuthHeaders, getCredentialsMode, routePath } from '../../api/config'
|
|
14
14
|
import {
|
|
15
15
|
useCloudRole, useVersionCheck, useClusterInfo, usePrometheusStatus, useArgoStatus, useCapabilities,
|
|
@@ -135,7 +135,7 @@ export function SettingsDialog({
|
|
|
135
135
|
}: SettingsDialogProps) {
|
|
136
136
|
const queryClient = useQueryClient()
|
|
137
137
|
const dialogRef = useRef<HTMLDivElement>(null)
|
|
138
|
-
const { shouldRender, isOpen } = useAnimatedUnmount(open,
|
|
138
|
+
const { shouldRender, isOpen } = useAnimatedUnmount(open, overlayExitMs('dialog'))
|
|
139
139
|
const { data: versionInfo } = useVersionCheck()
|
|
140
140
|
// Radar configuration (kubeconfig, port, integrations…) is host-level and
|
|
141
141
|
// affects every user of this instance, so it's gated to owners. Personal
|
|
@@ -473,6 +473,7 @@ export function SettingsDialog({
|
|
|
473
473
|
TRANSITION_BACKDROP,
|
|
474
474
|
isOpen ? 'opacity-100' : 'opacity-0'
|
|
475
475
|
)}
|
|
476
|
+
style={overlayTransitionStyle(isOpen, 'dialog')}
|
|
476
477
|
onClick={() => requestCloseRef.current()}
|
|
477
478
|
/>
|
|
478
479
|
|
|
@@ -499,6 +500,7 @@ export function SettingsDialog({
|
|
|
499
500
|
TRANSITION_PANEL,
|
|
500
501
|
isOpen ? 'opacity-100 scale-100' : 'opacity-0 scale-95'
|
|
501
502
|
)}
|
|
503
|
+
style={overlayTransitionStyle(isOpen, 'dialog')}
|
|
502
504
|
>
|
|
503
505
|
{/* Header — spans both panes */}
|
|
504
506
|
<div className="flex items-center justify-between p-4 border-b border-theme-border shrink-0">
|
|
@@ -628,6 +630,7 @@ export function SettingsDialog({
|
|
|
628
630
|
locked={!canEditConfig}
|
|
629
631
|
>
|
|
630
632
|
<PrometheusConfigField
|
|
633
|
+
local={deploymentMode === 'local'}
|
|
631
634
|
value={editedConfig.prometheusUrl ?? ''}
|
|
632
635
|
configuredHeaderKeys={configData?.prometheusHeaderKeys ?? []}
|
|
633
636
|
onChange={(v) => updateConfigField('prometheusUrl', v || undefined)}
|
|
@@ -698,6 +701,9 @@ export function SettingsDialog({
|
|
|
698
701
|
envManaged={configData?.argoCdEnvManaged ?? false}
|
|
699
702
|
envError={configData?.argoCdEnvError}
|
|
700
703
|
cliSession={configData?.argoCdCliSession}
|
|
704
|
+
anonymous={argoSectionStatus?.connected && argoSectionStatus.anonymous}
|
|
705
|
+
connectedAddress={argoSectionStatus?.connected && !argoSectionStatus.anonymous ? argoSectionStatus.address : undefined}
|
|
706
|
+
active={section === 'argocd'}
|
|
701
707
|
statusReason={
|
|
702
708
|
argoSectionStatus?.configured && !argoSectionStatus.connected
|
|
703
709
|
? argoSectionStatus.reason
|
|
@@ -716,6 +722,12 @@ export function SettingsDialog({
|
|
|
716
722
|
: prev
|
|
717
723
|
)
|
|
718
724
|
void refetchArgoSectionStatus()
|
|
725
|
+
// The GitOps detail page the user came from doesn't poll
|
|
726
|
+
// when idle; without this the notice that sent them here
|
|
727
|
+
// would still be up when they get back.
|
|
728
|
+
void queryClient.invalidateQueries({
|
|
729
|
+
predicate: (query) => typeof query.queryKey[0] === 'string' && query.queryKey[0].startsWith('gitops-'),
|
|
730
|
+
})
|
|
719
731
|
}}
|
|
720
732
|
/>
|
|
721
733
|
</SectionPane>
|
|
@@ -1105,9 +1117,9 @@ function OverviewPanel({ active, onNavigate }: { active: boolean; onNavigate: (s
|
|
|
1105
1117
|
},
|
|
1106
1118
|
{
|
|
1107
1119
|
id: 'prometheus', icon: Activity, label: 'Metrics',
|
|
1108
|
-
tone: prom?.connected ? 'ok' : prom?.
|
|
1109
|
-
value: prom?.connected ? 'Connected' : prom?.
|
|
1110
|
-
detail: prom?.connected ? prom.address : undefined,
|
|
1120
|
+
tone: prom?.connected ? 'ok' : prom?.discovering ? 'unknown' : prom?.error ? 'warn' : 'off',
|
|
1121
|
+
value: prom?.connected ? 'Connected' : prom?.discovering ? 'Discovering…' : prom?.error ? 'Not connected' : 'Not configured',
|
|
1122
|
+
detail: prom?.connected ? prom.address : prom?.discovering ? undefined : prom?.error,
|
|
1111
1123
|
},
|
|
1112
1124
|
{
|
|
1113
1125
|
id: costConfigurationAction(cost?.reason).section, icon: Coins, label: 'Cost',
|
|
@@ -1127,7 +1139,7 @@ function OverviewPanel({ active, onNavigate }: { active: boolean; onNavigate: (s
|
|
|
1127
1139
|
// Configured-but-not-connected is often a permanently rejected/expired
|
|
1128
1140
|
// token, not a transient reconnect — "Not reachable" matches Prometheus and
|
|
1129
1141
|
// doesn't imply it will recover on its own.
|
|
1130
|
-
value: argo?.connected ? 'Connected' : argo?.configured ? 'Not reachable' : 'Not connected',
|
|
1142
|
+
value: argo?.connected ? (argo.anonymous ? 'Connected · no token needed' : 'Connected') : argo?.configured ? 'Not reachable' : 'Not connected',
|
|
1131
1143
|
detail: argo?.connected ? argo.address : argo?.reason,
|
|
1132
1144
|
},
|
|
1133
1145
|
{
|
|
@@ -1899,11 +1911,13 @@ type CurrencySaveState =
|
|
|
1899
1911
|
type HeaderRow = { key: string; value: string }
|
|
1900
1912
|
|
|
1901
1913
|
function PrometheusConfigField({
|
|
1914
|
+
local,
|
|
1902
1915
|
value,
|
|
1903
1916
|
onChange,
|
|
1904
1917
|
configuredHeaderKeys,
|
|
1905
1918
|
onApplied,
|
|
1906
1919
|
}: {
|
|
1920
|
+
local: boolean
|
|
1907
1921
|
value: string
|
|
1908
1922
|
onChange: (value: string) => void
|
|
1909
1923
|
configuredHeaderKeys: string[]
|
|
@@ -1981,14 +1995,14 @@ function PrometheusConfigField({
|
|
|
1981
1995
|
return (
|
|
1982
1996
|
<div>
|
|
1983
1997
|
<p className="text-xs text-theme-text-tertiary mb-3">
|
|
1984
|
-
|
|
1998
|
+
Connect existing Prometheus-compatible data for resource usage, workload HTTP metrics and rightsizing. Available charts depend on collected metrics.
|
|
1985
1999
|
</p>
|
|
1986
2000
|
<label className="block text-sm font-medium text-theme-text-primary mb-1">
|
|
1987
|
-
|
|
2001
|
+
Metrics backend URL
|
|
1988
2002
|
</label>
|
|
1989
2003
|
<p className="text-xs text-theme-text-tertiary mb-1">
|
|
1990
|
-
|
|
1991
|
-
Mimir.
|
|
2004
|
+
Base URL reachable from Radar, not your browser — Prometheus, VictoriaMetrics, Thanos or
|
|
2005
|
+
Mimir. Include any backend path prefix, but not /api/v1/query. Leave empty for cluster discovery; headers require a URL.
|
|
1992
2006
|
</p>
|
|
1993
2007
|
<div className="flex items-center gap-2">
|
|
1994
2008
|
<Input
|
|
@@ -1997,7 +2011,7 @@ function PrometheusConfigField({
|
|
|
1997
2011
|
placeholder="http://prometheus-server.monitoring:9090"
|
|
1998
2012
|
className="flex-1 min-w-0 px-3 py-1.5 text-sm bg-theme-elevated border border-theme-border rounded-md text-theme-text-primary placeholder:text-theme-text-tertiary focus:outline-none focus:border-skyhook-500"
|
|
1999
2013
|
/>
|
|
2000
|
-
<Tooltip content="Apply
|
|
2014
|
+
<Tooltip content="Apply the connection now — no restart. Changing the URL or headers clears any workload scope override and resumes automatic identity matching." wrapperClassName="shrink-0">
|
|
2001
2015
|
<button
|
|
2002
2016
|
onClick={handleApply}
|
|
2003
2017
|
disabled={apply.status === 'applying'}
|
|
@@ -2028,6 +2042,12 @@ function PrometheusConfigField({
|
|
|
2028
2042
|
Applies immediately — no restart needed.
|
|
2029
2043
|
</p>
|
|
2030
2044
|
)}
|
|
2045
|
+
<p className="mt-2 text-xs text-theme-text-tertiary">
|
|
2046
|
+
{local
|
|
2047
|
+
? 'Saved URL and headers apply across all local cluster contexts.'
|
|
2048
|
+
: 'Changes affect this Radar installation. Use deployment settings for configuration that survives Pod replacement.'}
|
|
2049
|
+
{' URL changes do not clear headers. Replace or clear credentials before switching backends.'}
|
|
2050
|
+
</p>
|
|
2031
2051
|
|
|
2032
2052
|
{/* Auth headers — for token / multi-tenant backends (Bearer, X-Scope-OrgID). */}
|
|
2033
2053
|
<div className="mt-3">
|
|
@@ -2095,7 +2115,8 @@ function PrometheusConfigField({
|
|
|
2095
2115
|
<p className="text-xs text-theme-text-tertiary">
|
|
2096
2116
|
Saved when you click Apply now. Entered headers replace all stored
|
|
2097
2117
|
ones — values are hidden, so re-enter any you want to keep. Leave
|
|
2098
|
-
blank to keep existing headers unchanged.
|
|
2118
|
+
all rows blank to keep existing headers unchanged. To clear all saved
|
|
2119
|
+
headers, remove every row and click Apply now.
|
|
2099
2120
|
</p>
|
|
2100
2121
|
</div>
|
|
2101
2122
|
)}
|
|
@@ -2128,6 +2149,9 @@ function ArgoCDConfigField({
|
|
|
2128
2149
|
envError,
|
|
2129
2150
|
cliSession,
|
|
2130
2151
|
statusReason,
|
|
2152
|
+
anonymous,
|
|
2153
|
+
connectedAddress,
|
|
2154
|
+
active,
|
|
2131
2155
|
onChangeUrl,
|
|
2132
2156
|
onChangeInsecureTls,
|
|
2133
2157
|
onApplied,
|
|
@@ -2139,12 +2163,15 @@ function ArgoCDConfigField({
|
|
|
2139
2163
|
envError?: string
|
|
2140
2164
|
cliSession?: { server: string; user: string; insecure?: boolean }
|
|
2141
2165
|
statusReason?: string
|
|
2166
|
+
anonymous?: boolean
|
|
2167
|
+
connectedAddress?: string
|
|
2168
|
+
active?: boolean
|
|
2142
2169
|
onChangeUrl: (value: string) => void
|
|
2143
2170
|
onChangeInsecureTls: (value: boolean) => void
|
|
2144
2171
|
onApplied?: (v: { url: string; insecureTls: boolean; tokenSet: boolean }) => void
|
|
2145
2172
|
}) {
|
|
2146
2173
|
if (envManaged) {
|
|
2147
|
-
return <ArgoCDEnvManagedField url={url} insecureTls={insecureTls} envError={envError} />
|
|
2174
|
+
return <ArgoCDEnvManagedField url={url} insecureTls={insecureTls} envError={envError} connectedAddress={connectedAddress} statusReason={statusReason} />
|
|
2148
2175
|
}
|
|
2149
2176
|
return (
|
|
2150
2177
|
<ArgoCDEditableField
|
|
@@ -2153,6 +2180,9 @@ function ArgoCDConfigField({
|
|
|
2153
2180
|
tokenSet={tokenSet}
|
|
2154
2181
|
cliSession={cliSession}
|
|
2155
2182
|
statusReason={statusReason}
|
|
2183
|
+
anonymous={anonymous}
|
|
2184
|
+
connectedAddress={connectedAddress}
|
|
2185
|
+
active={active}
|
|
2156
2186
|
onChangeUrl={onChangeUrl}
|
|
2157
2187
|
onChangeInsecureTls={onChangeInsecureTls}
|
|
2158
2188
|
onApplied={onApplied}
|
|
@@ -2168,10 +2198,17 @@ function ArgoCDEnvManagedField({
|
|
|
2168
2198
|
url,
|
|
2169
2199
|
insecureTls,
|
|
2170
2200
|
envError,
|
|
2201
|
+
connectedAddress,
|
|
2202
|
+
statusReason,
|
|
2171
2203
|
}: {
|
|
2172
2204
|
url: string
|
|
2173
2205
|
insecureTls: boolean
|
|
2174
2206
|
envError?: string
|
|
2207
|
+
// Live connection, from the status endpoint: the address when the
|
|
2208
|
+
// token-backed session is up, else why it isn't. The card is read-only,
|
|
2209
|
+
// so this is the one thing on it that can change after deploy.
|
|
2210
|
+
connectedAddress?: string
|
|
2211
|
+
statusReason?: string
|
|
2175
2212
|
}) {
|
|
2176
2213
|
if (envError) {
|
|
2177
2214
|
return (
|
|
@@ -2224,6 +2261,21 @@ function ArgoCDEnvManagedField({
|
|
|
2224
2261
|
<dt className="w-24 shrink-0 text-theme-text-tertiary">Token</dt>
|
|
2225
2262
|
<dd className="text-theme-text-secondary">provided via environment</dd>
|
|
2226
2263
|
</div>
|
|
2264
|
+
<div className="flex gap-2">
|
|
2265
|
+
<dt className="w-24 shrink-0 text-theme-text-tertiary">Status</dt>
|
|
2266
|
+
<dd className="min-w-0 truncate text-theme-text-secondary">
|
|
2267
|
+
{connectedAddress ? (
|
|
2268
|
+
<span className="inline-flex items-center gap-1">
|
|
2269
|
+
<Check className="w-3 h-3 shrink-0 text-green-600 dark:text-green-400/80" />
|
|
2270
|
+
Connected at {connectedAddress}
|
|
2271
|
+
</span>
|
|
2272
|
+
) : statusReason ? (
|
|
2273
|
+
<span className="text-warning-text">Not reachable — {statusReason}</span>
|
|
2274
|
+
) : (
|
|
2275
|
+
'Not connected'
|
|
2276
|
+
)}
|
|
2277
|
+
</dd>
|
|
2278
|
+
</div>
|
|
2227
2279
|
{insecureTls && (
|
|
2228
2280
|
<div className="flex gap-2">
|
|
2229
2281
|
<dt className="w-24 shrink-0 text-theme-text-tertiary">TLS</dt>
|
|
@@ -2246,6 +2298,9 @@ function ArgoCDEditableField({
|
|
|
2246
2298
|
tokenSet,
|
|
2247
2299
|
cliSession,
|
|
2248
2300
|
statusReason,
|
|
2301
|
+
anonymous,
|
|
2302
|
+
connectedAddress,
|
|
2303
|
+
active,
|
|
2249
2304
|
onChangeUrl,
|
|
2250
2305
|
onChangeInsecureTls,
|
|
2251
2306
|
onApplied,
|
|
@@ -2255,6 +2310,9 @@ function ArgoCDEditableField({
|
|
|
2255
2310
|
tokenSet: boolean
|
|
2256
2311
|
cliSession?: { server: string; user: string; insecure?: boolean }
|
|
2257
2312
|
statusReason?: string
|
|
2313
|
+
anonymous?: boolean
|
|
2314
|
+
connectedAddress?: string
|
|
2315
|
+
active?: boolean
|
|
2258
2316
|
onChangeUrl: (value: string) => void
|
|
2259
2317
|
onChangeInsecureTls: (value: boolean) => void
|
|
2260
2318
|
onApplied?: (v: { url: string; insecureTls: boolean; tokenSet: boolean }) => void
|
|
@@ -2348,20 +2406,43 @@ function ArgoCDEditableField({
|
|
|
2348
2406
|
|
|
2349
2407
|
const showConfiguredPlaceholder = effectiveTokenSet && !tokenTouched && !tokenCleared
|
|
2350
2408
|
const connecting = state.status === 'connecting'
|
|
2409
|
+
// Opening this section with nothing set up (typically from the GitOps
|
|
2410
|
+
// page's "connect Radar to your Argo CD server") starts the user at the
|
|
2411
|
+
// token — unless a CLI session offers a one-click connect, or reads
|
|
2412
|
+
// already work anonymously.
|
|
2413
|
+
const tokenInputRef = useRef<HTMLInputElement>(null)
|
|
2414
|
+
useEffect(() => {
|
|
2415
|
+
if (active && !effectiveTokenSet && !cliSession && !anonymous) tokenInputRef.current?.focus()
|
|
2416
|
+
}, [active, effectiveTokenSet, cliSession, anonymous])
|
|
2351
2417
|
|
|
2352
2418
|
return (
|
|
2353
2419
|
<div>
|
|
2354
2420
|
<p className="text-xs text-theme-text-tertiary mb-3">
|
|
2355
2421
|
Connect your Argo CD server for the full Git-rendered desired-vs-live diff on GitOps
|
|
2356
|
-
Application pages — what Git declares vs what's actually running
|
|
2357
|
-
|
|
2422
|
+
Application pages — what Git declares vs what's actually running — and, on Argo CD 3, for
|
|
2423
|
+
Argo's own health verdict on each resource. Without it, Radar falls back to a lighter
|
|
2424
|
+
annotation-based drift that can miss fields, and to its own read of each resource.
|
|
2358
2425
|
</p>
|
|
2359
2426
|
|
|
2427
|
+
{state.status !== 'connected' && (anonymous || connectedAddress) && (
|
|
2428
|
+
<p className="mb-3 flex items-center gap-1.5 text-xs text-theme-text-secondary">
|
|
2429
|
+
<Check className="w-3.5 h-3.5 shrink-0 text-green-600 dark:text-green-400/80" />
|
|
2430
|
+
{anonymous ? (
|
|
2431
|
+
<>
|
|
2432
|
+
Your Argo CD server lets Radar read without a token, so it's already connected. Add a token only
|
|
2433
|
+
if anonymous access gets restricted, or a URL to point Radar at a specific server.
|
|
2434
|
+
</>
|
|
2435
|
+
) : (
|
|
2436
|
+
<>Connected to Argo CD at {connectedAddress}.</>
|
|
2437
|
+
)}
|
|
2438
|
+
</p>
|
|
2439
|
+
)}
|
|
2440
|
+
|
|
2360
2441
|
{statusReason && state.status !== 'connected' && (
|
|
2361
2442
|
<div className="mb-3 rounded-md border border-theme-border bg-theme-elevated p-3">
|
|
2362
2443
|
<p className="flex items-center gap-1.5 text-sm font-medium text-warning-text">
|
|
2363
2444
|
<AlertTriangle className="w-3.5 h-3.5 shrink-0" />
|
|
2364
|
-
Argo CD
|
|
2445
|
+
Argo CD connection needs attention
|
|
2365
2446
|
</p>
|
|
2366
2447
|
<p className="mt-1 text-xs text-theme-text-secondary">{statusReason}</p>
|
|
2367
2448
|
</div>
|
|
@@ -2419,6 +2500,7 @@ function ArgoCDEditableField({
|
|
|
2419
2500
|
</p>
|
|
2420
2501
|
<div className="flex items-center gap-2">
|
|
2421
2502
|
<input
|
|
2503
|
+
ref={tokenInputRef}
|
|
2422
2504
|
type="password"
|
|
2423
2505
|
value={showConfiguredPlaceholder ? '' : token}
|
|
2424
2506
|
onChange={(e) => { setToken(e.target.value); setTokenTouched(true); setTokenCleared(false); clearStatus() }}
|