@skyhook-io/radar-app 1.17.0 → 1.17.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/README.md +1 -1
- package/package.json +3 -3
- package/src/App.tsx +125 -87
- package/src/api/actions.errorCode.test.ts +40 -0
- package/src/api/actions.test.ts +48 -0
- package/src/api/actions.ts +79 -0
- package/src/api/apiResources.navigation.test.tsx +232 -0
- package/src/api/apiResources.ts +46 -3
- package/src/api/client.helmSource.test.ts +39 -0
- package/src/api/client.namespaceSwitch.hook.test.tsx +54 -0
- package/src/api/client.namespaceSwitch.test.ts +107 -0
- package/src/api/client.ts +266 -39
- package/src/api/cnpg-ha.live.test.ts +140 -0
- package/src/api/cnpg-ha.test.ts +78 -0
- package/src/api/cnpg-ha.ts +19 -0
- package/src/api/cnpg-history.ts +123 -0
- package/src/api/cnpg-inspect.ts +102 -0
- package/src/api/cnpg-protection.ts +112 -0
- package/src/api/cnpg-recovery.test.tsx +60 -0
- package/src/api/cnpg-recovery.ts +262 -0
- package/src/api/cnpg-sessions.ts +175 -0
- package/src/api/cnpg-storage.ts +138 -0
- package/src/api/cnpg.legacy-reads.test.ts +43 -0
- package/src/api/cnpg.pooler-runtime.test.ts +15 -0
- package/src/api/cnpg.ts +428 -10
- package/src/api/radarFeatures.render.test.tsx +47 -3
- package/src/api/radarFeatures.ts +4 -1
- package/src/api/reviewedActionMutation.test.tsx +164 -0
- package/src/api/reviewedActionMutation.ts +66 -0
- package/src/api/timelineSource.test.ts +9 -0
- package/src/api/timelineSource.ts +8 -0
- package/src/components/audit/AuditView.tsx +9 -0
- package/src/components/capacity/CapacityActivity.tsx +3 -31
- package/src/components/capacity/CapacityDemand.tsx +4 -36
- package/src/components/capacity/CapacityOverview.tsx +217 -56
- package/src/components/capacity/CapacityPoolDetail.tsx +6 -55
- package/src/components/capacity/CapacityView.test.tsx +323 -0
- package/src/components/capacity/ClusterSchedulingCard.tsx +2 -10
- package/src/components/capacity/certaintyGlyph.test.tsx +2 -9
- package/src/components/capacity/routes.ts +42 -0
- package/src/components/capacity/shared.tsx +11 -129
- package/src/components/capacity/workspaceRoute.ts +18 -0
- package/src/components/cnpg/CNPGActionHost.compatibility.test.tsx +30 -0
- package/src/components/cnpg/CNPGArchivingRepair.tsx +191 -0
- package/src/components/cnpg/CNPGBlockingSessions.test.tsx +50 -0
- package/src/components/cnpg/CNPGBlockingSessions.tsx +333 -0
- package/src/components/cnpg/CNPGClusterActivity.test.tsx +11 -0
- package/src/components/cnpg/CNPGClusterActivity.tsx +18 -9
- package/src/components/cnpg/CNPGClusterLogs.compatibility.test.tsx +130 -0
- package/src/components/cnpg/CNPGClusterLogs.empty.test.tsx +29 -0
- package/src/components/cnpg/CNPGClusterLogs.test.ts +9 -0
- package/src/components/cnpg/CNPGClusterLogs.tsx +81 -24
- package/src/components/cnpg/CNPGClusterTabs.test.tsx +152 -0
- package/src/components/cnpg/CNPGClusterTabs.tsx +187 -0
- package/src/components/cnpg/CNPGClustersKindView.tsx +46 -0
- package/src/components/cnpg/CNPGConfiguration.test.tsx +223 -0
- package/src/components/cnpg/CNPGConfiguration.tsx +190 -0
- package/src/components/cnpg/CNPGCreateClusterDialog.test.tsx +113 -0
- package/src/components/cnpg/CNPGCreateClusterDialog.tsx +74 -0
- package/src/components/cnpg/CNPGDeclarations.test.ts +107 -0
- package/src/components/cnpg/CNPGDeclarations.tsx +236 -78
- package/src/components/cnpg/CNPGDetailPage.test.tsx +62 -0
- package/src/components/cnpg/CNPGDetailPage.tsx +159 -98
- package/src/components/cnpg/CNPGDrawerTrail.tsx +2 -2
- package/src/components/cnpg/CNPGInvestigationAction.test.tsx +30 -0
- package/src/components/cnpg/CNPGOperator.test.tsx +34 -0
- package/src/components/cnpg/CNPGOperator.tsx +78 -30
- package/src/components/cnpg/CNPGOperatorBanner.tsx +39 -0
- package/src/components/cnpg/CNPGOperatorConditions.test.tsx +45 -0
- package/src/components/cnpg/CNPGOperatorConditions.tsx +16 -0
- package/src/components/cnpg/CNPGOperatorConfig.test.tsx +27 -0
- package/src/components/cnpg/CNPGOperatorDiagnosis.tsx +247 -0
- package/src/components/cnpg/CNPGOverview.test.tsx +171 -0
- package/src/components/cnpg/CNPGOverview.tsx +238 -63
- package/src/components/cnpg/CNPGParametersInEffect.test.tsx +51 -0
- package/src/components/cnpg/CNPGParametersInEffect.tsx +120 -0
- package/src/components/cnpg/CNPGPerformance.prerequisite.test.tsx +48 -0
- package/src/components/cnpg/CNPGPerformance.test.tsx +73 -0
- package/src/components/cnpg/CNPGPerformance.tsx +477 -0
- package/src/components/cnpg/CNPGPooling.test.tsx +89 -0
- package/src/components/cnpg/CNPGPooling.tsx +97 -40
- package/src/components/cnpg/CNPGProtection.test.tsx +145 -0
- package/src/components/cnpg/CNPGProtection.tsx +228 -122
- package/src/components/cnpg/CNPGReplicationTab.test.tsx +87 -0
- package/src/components/cnpg/CNPGReplicationTab.tsx +212 -0
- package/src/components/cnpg/CNPGReplicationView.tsx +264 -0
- package/src/components/cnpg/CNPGStorage.test.tsx +140 -0
- package/src/components/cnpg/CNPGStorage.tsx +619 -0
- package/src/components/cnpg/CNPGSummaryHost.overview.test.tsx +44 -0
- package/src/components/cnpg/CNPGSummaryHost.test.tsx +23 -0
- package/src/components/cnpg/CNPGSummaryHost.tsx +228 -54
- package/src/components/cnpg/CNPGTrends.denied.test.tsx +33 -0
- package/src/components/cnpg/CNPGTrends.missing.test.tsx +69 -0
- package/src/components/cnpg/CNPGTrends.test.ts +38 -0
- package/src/components/cnpg/CNPGTrends.tsx +702 -0
- package/src/components/cnpg/CNPGView.tsx +34 -21
- package/src/components/cnpg/CNPGWorkspace.compatibility.test.tsx +90 -0
- package/src/components/cnpg/actions/CNPGClusterActions.dialog.test.tsx +79 -0
- package/src/components/cnpg/actions/CNPGClusterActions.test.tsx +142 -0
- package/src/components/cnpg/actions/CNPGClusterActions.tsx +853 -0
- package/src/components/cnpg/actions/CNPGConnectButton.read.test.tsx +40 -0
- package/src/components/cnpg/actions/CNPGConnectButton.test.ts +25 -0
- package/src/components/cnpg/actions/CNPGConnectButton.tsx +131 -0
- package/src/components/cnpg/actions/CNPGDestroyInstanceDialog.tsx +185 -0
- package/src/components/cnpg/actions/CNPGInstanceActions.test.tsx +38 -0
- package/src/components/cnpg/actions/CNPGInstanceActions.tsx +74 -0
- package/src/components/cnpg/actions/CNPGMaintenanceBanner.tsx +53 -0
- package/src/components/cnpg/actions/CNPGPoolerActions.test.tsx +35 -0
- package/src/components/cnpg/actions/CNPGPoolerActions.tsx +79 -0
- package/src/components/cnpg/actions/CNPGReportDialog.test.tsx +12 -0
- package/src/components/cnpg/actions/CNPGReportDialog.tsx +91 -0
- package/src/components/cnpg/actions/CNPGScheduleActions.run.test.tsx +58 -0
- package/src/components/cnpg/actions/CNPGScheduleActions.test.tsx +54 -0
- package/src/components/cnpg/actions/CNPGScheduleActions.tsx +208 -0
- package/src/components/cnpg/actions/actionModel.test.ts +111 -0
- package/src/components/cnpg/actions/actionModel.ts +127 -0
- package/src/components/cnpg/actions/renderCNPGHeaderActions.tsx +60 -0
- package/src/components/cnpg/actions/useCNPGWriteGuard.tsx +58 -0
- package/src/components/cnpg/actions/useOpenCNPGPsql.ts +22 -0
- package/src/components/cnpg/archivingRepair.test.ts +104 -0
- package/src/components/cnpg/archivingRepair.ts +145 -0
- package/src/components/cnpg/baseBackup.test.ts +36 -0
- package/src/components/cnpg/baseBackup.ts +36 -0
- package/src/components/cnpg/blocking.test.ts +59 -0
- package/src/components/cnpg/blocking.ts +131 -0
- package/src/components/cnpg/coverageText.test.ts +27 -0
- package/src/components/cnpg/creation/CNPGTargetFields.tsx +110 -0
- package/src/components/cnpg/creation/targetModel.test.ts +137 -0
- package/src/components/cnpg/creation/targetModel.ts +149 -0
- package/src/components/cnpg/fleetStatus.test.ts +46 -0
- package/src/components/cnpg/fleetStatus.ts +43 -0
- package/src/components/cnpg/formFields.ts +2 -0
- package/src/components/cnpg/grantText.test.tsx +11 -0
- package/src/components/cnpg/host.test.tsx +24 -0
- package/src/components/cnpg/host.tsx +101 -0
- package/src/components/cnpg/inspectModel.test.ts +120 -0
- package/src/components/cnpg/inspectModel.ts +140 -0
- package/src/components/cnpg/logicalSlots.test.ts +30 -0
- package/src/components/cnpg/logicalSlots.ts +29 -0
- package/src/components/cnpg/lsn.test.ts +40 -0
- package/src/components/cnpg/lsn.ts +43 -0
- package/src/components/cnpg/operations/CNPGFleetOperation.tsx +30 -0
- package/src/components/cnpg/operations/CNPGOperationTracker.test.tsx +247 -0
- package/src/components/cnpg/operations/CNPGOperationTracker.tsx +255 -0
- package/src/components/cnpg/operations/model.test.ts +510 -0
- package/src/components/cnpg/operations/model.ts +499 -0
- package/src/components/cnpg/operations/presentation.test.ts +80 -0
- package/src/components/cnpg/operations/presentation.ts +84 -0
- package/src/components/cnpg/operations/store.ts +98 -0
- package/src/components/cnpg/operatorStatus.test.ts +134 -0
- package/src/components/cnpg/operatorStatus.ts +194 -0
- package/src/components/cnpg/paths.test.ts +54 -0
- package/src/components/cnpg/paths.ts +47 -4
- package/src/components/cnpg/protection/CNPGAttachArchiveDialog.tsx +185 -0
- package/src/components/cnpg/protection/CNPGCreateScheduleDialog.tsx +196 -0
- package/src/components/cnpg/protection/CNPGProtectionSetup.test.tsx +121 -0
- package/src/components/cnpg/protection/CNPGProtectionSetup.tsx +452 -0
- package/src/components/cnpg/protection/CNPGScheduleInput.tsx +115 -0
- package/src/components/cnpg/protection/CNPGScheduleRepairButton.tsx +120 -0
- package/src/components/cnpg/protection/scheduleModel.test.ts +19 -0
- package/src/components/cnpg/protection/scheduleModel.ts +16 -0
- package/src/components/cnpg/recovery/CNPGRecoveryPoint.tsx +220 -0
- package/src/components/cnpg/recovery/CNPGRestoreButton.test.tsx +29 -0
- package/src/components/cnpg/recovery/CNPGRestoreButton.tsx +26 -0
- package/src/components/cnpg/recovery/CNPGRestoreChecks.test.tsx +23 -0
- package/src/components/cnpg/recovery/CNPGRestoreChecks.tsx +153 -0
- package/src/components/cnpg/recovery/CNPGRestoreDialog.back.test.tsx +149 -0
- package/src/components/cnpg/recovery/CNPGRestoreDialog.test.tsx +46 -0
- package/src/components/cnpg/recovery/CNPGRestoreDialog.tsx +333 -0
- package/src/components/cnpg/recovery/CNPGRestoreProgress.tsx +182 -0
- package/src/components/cnpg/recovery/CNPGRestoreValidation.tsx +216 -0
- package/src/components/cnpg/recovery/restoreModel.test.ts +709 -0
- package/src/components/cnpg/recovery/restoreModel.ts +685 -0
- package/src/components/cnpg/recovery/restoreOperation.ts +56 -0
- package/src/components/cnpg/refresh.test.ts +38 -0
- package/src/components/cnpg/refresh.ts +17 -0
- package/src/components/cnpg/refreshNotice.test.tsx +29 -0
- package/src/components/cnpg/routes.test.ts +34 -3
- package/src/components/cnpg/routes.ts +60 -48
- package/src/components/cnpg/runtimeAssessment.ts +198 -0
- package/src/components/cnpg/runtimeModel.test.ts +175 -0
- package/src/components/cnpg/runtimeModel.ts +196 -0
- package/src/components/cnpg/runtimeParts.test.tsx +17 -0
- package/src/components/cnpg/runtimeParts.tsx +79 -0
- package/src/components/cnpg/shared.test.tsx +59 -0
- package/src/components/cnpg/shared.tsx +100 -182
- package/src/components/cnpg/storageModel.test.ts +107 -0
- package/src/components/cnpg/storageModel.ts +101 -0
- package/src/components/cnpg/trendSamples.test.ts +148 -0
- package/src/components/cnpg/trendSamples.ts +222 -0
- package/src/components/cnpg/useCNPGClusterAssessment.ts +30 -0
- package/src/components/cnpg/useCNPGKubectlContext.test.tsx +45 -0
- package/src/components/cnpg/useCNPGKubectlContext.ts +13 -0
- package/src/components/cnpg/useCNPGNavigate.test.tsx +58 -0
- package/src/components/cnpg/useCNPGNavigate.ts +21 -0
- package/src/components/cnpg/useCNPGPoolerLive.ts +46 -0
- package/src/components/cnpg/useCNPGScreenParams.ts +24 -0
- package/src/components/cnpg/useCNPGSidebarWorkspace.ts +66 -16
- package/src/components/cnpg/workspaceRoute.ts +23 -0
- package/src/components/cost/ApplicationCostTab.test.ts +7 -0
- package/src/components/cost/ApplicationCostTab.tsx +7 -7
- package/src/components/cost/CostView.test.ts +2 -1
- package/src/components/cost/CostView.tsx +0 -2
- package/src/components/cost/WorkloadCostTab.tsx +2 -4
- package/src/components/cost/errors.ts +3 -1
- package/src/components/cost/source.test.ts +10 -0
- package/src/components/cost/source.ts +5 -1
- package/src/components/diagnose/investigationEvidence/adapters/issues.ts +8 -0
- package/src/components/diagnose/investigationEvidence/projection.test.ts +16 -0
- package/src/components/dock/BottomDock.tsx +2 -0
- package/src/components/dock/TerminalTab.test.tsx +17 -0
- package/src/components/dock/TerminalTab.tsx +7 -3
- package/src/components/gitops/ArgoResourceDiffLoader.tsx +4 -2
- package/src/components/gitops/GitOpsView.tsx +16 -23
- package/src/components/helm/HelmCompareRoute.test.tsx +59 -0
- package/src/components/helm/HelmCompareRoute.tsx +57 -26
- package/src/components/helm/HelmReleaseDrawer.test.ts +55 -3
- package/src/components/helm/HelmReleaseDrawer.tsx +50 -12
- package/src/components/helm/TrackChartSourceDialog.render.test.tsx +120 -0
- package/src/components/helm/TrackChartSourceDialog.test.ts +50 -0
- package/src/components/helm/TrackChartSourceDialog.tsx +153 -176
- package/src/components/helm/ValuesViewer.test.tsx +301 -0
- package/src/components/helm/ValuesViewer.tsx +169 -78
- package/src/components/home/ActivitySummary.tsx +2 -1
- package/src/components/home/HomeView.tsx +20 -9
- package/src/components/home/ProblemsPanel.test.tsx +81 -0
- package/src/components/home/TopologyPreview.tsx +2 -2
- package/src/components/issues/IssuesPane.render.test.tsx +122 -0
- package/src/components/issues/IssuesPane.test.ts +65 -1
- package/src/components/issues/IssuesPane.tsx +188 -25
- package/src/components/issues/uncheckedSources.ts +21 -0
- package/src/components/resource/PVCUsageBar.render.test.tsx +32 -0
- package/src/components/resource/PVCUsageBar.test.tsx +24 -0
- package/src/components/resource/PVCUsageBar.tsx +15 -6
- package/src/components/resource/WorkloadMetricsHelpDialog.test.tsx +3 -1
- package/src/components/resource/WorkloadMetricsHelpDialog.tsx +1 -1
- package/src/components/resources/CompositeRenderer.tsx +4 -4
- package/src/components/resources/ResourceDetailDrawer.tsx +2 -2
- package/src/components/resources/ResourcesView.tsx +26 -12
- package/src/components/settings/LocalIntegrationStatus.test.tsx +52 -0
- package/src/components/settings/LocalIntegrationStatus.tsx +3 -4
- package/src/components/settings/SettingsDialog.tsx +28 -15
- package/src/components/timeline/TimelineList.tsx +22 -24
- package/src/components/timeline/TimelineLoadError.test.tsx +62 -0
- package/src/components/timeline/TimelineLoadError.tsx +96 -0
- package/src/components/timeline/TimelineView.tsx +18 -45
- package/src/components/timeline/ViewModeToggle.tsx +20 -0
- package/src/components/timeline/useStickyError.test.tsx +47 -0
- package/src/components/timeline/useStickyError.ts +15 -0
- package/src/components/ui/DiagnosticsOverlay.tsx +34 -2
- package/src/components/ui/Markdown.test.tsx +65 -0
- package/src/components/ui/Markdown.tsx +144 -127
- package/src/components/whats-new/releaseNotes.ts +63 -1
- package/src/components/workload/WorkloadView.test.ts +10 -1
- package/src/components/workload/WorkloadView.tsx +27 -239
- package/src/components/workspace/layout.test.tsx +29 -0
- package/src/components/workspace/layout.tsx +259 -0
- package/src/components/workspace/table.ts +7 -0
- package/src/contexts/CapabilitiesContext.tsx +1 -1
- package/src/integrations/resourceHost.test.tsx +55 -0
- package/src/integrations/resourceHost.ts +99 -0
- package/src/integrations/resourceHosts.test.tsx +141 -0
- package/src/integrations/resourceHosts.tsx +119 -0
- package/src/integrations/workspaceRoute.ts +20 -0
- package/src/integrations/workspaceRoutes.test.ts +40 -0
- package/src/integrations/workspaceRoutes.ts +22 -0
- package/src/integrations/workspaceScreens.tsx +14 -0
- package/src/utils/drawer-trail.ts +35 -0
- package/src/utils/kindFilterStore.test.ts +147 -0
- package/src/utils/kindFilterStore.ts +91 -0
- package/src/utils/navigation.test.ts +13 -1
- package/src/utils/navigation.ts +39 -2
- package/src/utils/page-links.ts +21 -0
- package/src/utils/tabStorage.ts +40 -0
- package/src/utils/viewMemory.test.ts +78 -0
- package/src/utils/viewMemory.ts +54 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useState, useCallback, useEffect } from 'react'
|
|
1
|
+
import { useState, useCallback, useEffect, useMemo } from 'react'
|
|
2
2
|
import { Copy, Check, Settings, Pencil, X, Eye, Play, Loader2 } from 'lucide-react'
|
|
3
3
|
import { PaneLoader } from '@skyhook-io/k8s-ui'
|
|
4
4
|
import { clsx } from 'clsx'
|
|
@@ -6,7 +6,7 @@ import yaml from 'yaml'
|
|
|
6
6
|
import type { HelmValues, ValuesPreviewResponse } from '../../types'
|
|
7
7
|
import { CodeViewer } from '../ui/CodeViewer'
|
|
8
8
|
import { YamlEditor } from '../ui/YamlEditor'
|
|
9
|
-
import { useHelmPreviewValues, useHelmApplyValues } from '../../api/client'
|
|
9
|
+
import { useHelmPreviewValues, useHelmApplyValues, useHelmValues } from '../../api/client'
|
|
10
10
|
import { useCanHelmAct } from '../../api/client'
|
|
11
11
|
import { ValuesDiffPreview } from './ValuesDiffPreview'
|
|
12
12
|
import { Tooltip } from '../ui/Tooltip'
|
|
@@ -14,8 +14,9 @@ import { Tooltip } from '../ui/Tooltip'
|
|
|
14
14
|
interface ValuesViewerProps {
|
|
15
15
|
values?: HelmValues
|
|
16
16
|
isLoading: boolean
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
error?: unknown
|
|
18
|
+
showEffectiveValues: boolean
|
|
19
|
+
onToggleEffectiveValues: (show: boolean) => void
|
|
19
20
|
onCopy: (text: string) => void
|
|
20
21
|
copied: boolean
|
|
21
22
|
// Required for editing
|
|
@@ -24,12 +25,14 @@ interface ValuesViewerProps {
|
|
|
24
25
|
revision?: number
|
|
25
26
|
currentRevision?: number
|
|
26
27
|
onApplySuccess?: () => void
|
|
28
|
+
onRetry?: () => void
|
|
27
29
|
}
|
|
28
30
|
export function ValuesViewer({
|
|
29
31
|
values,
|
|
30
32
|
isLoading,
|
|
31
|
-
|
|
32
|
-
|
|
33
|
+
error,
|
|
34
|
+
showEffectiveValues,
|
|
35
|
+
onToggleEffectiveValues,
|
|
33
36
|
onCopy,
|
|
34
37
|
copied,
|
|
35
38
|
namespace,
|
|
@@ -37,6 +40,7 @@ export function ValuesViewer({
|
|
|
37
40
|
revision,
|
|
38
41
|
currentRevision,
|
|
39
42
|
onApplySuccess,
|
|
43
|
+
onRetry,
|
|
40
44
|
}: ValuesViewerProps) {
|
|
41
45
|
const [isEditing, setIsEditing] = useState(false)
|
|
42
46
|
const [editedYaml, setEditedYaml] = useState('')
|
|
@@ -49,23 +53,32 @@ export function ValuesViewer({
|
|
|
49
53
|
const { allowed: canHelmWrite, reason: helmActReason } = useCanHelmAct(namespace)
|
|
50
54
|
const isHistoricalRevision = typeof revision === 'number' && typeof currentRevision === 'number' && revision !== currentRevision
|
|
51
55
|
|
|
52
|
-
|
|
56
|
+
// Edits replace every stored override on apply, so they start from, and wait for, a fresh
|
|
57
|
+
// overrides-only read. An older Radar's all=true response can carry empty overrides after a
|
|
58
|
+
// failed second read, and a cached copy can predate the last apply.
|
|
59
|
+
const overridesQuery = useHelmValues(namespace ?? '', name ?? '', false, Boolean(namespace && name), revision)
|
|
60
|
+
const overridesData = overridesQuery.data
|
|
61
|
+
const overridesBaseline = useMemo(() => (overridesData ? (overridesData.userSupplied ?? {}) : undefined), [overridesData])
|
|
62
|
+
const overridesReady = overridesBaseline !== undefined && !overridesQuery.error && !overridesQuery.isFetching
|
|
63
|
+
const canOfferEdit = Boolean(namespace && name) && canHelmWrite && !isHistoricalRevision
|
|
64
|
+
const canEdit = canOfferEdit && overridesReady
|
|
53
65
|
|
|
54
|
-
const displayValues =
|
|
66
|
+
const displayValues = showEffectiveValues ? values?.computed : overridesBaseline
|
|
55
67
|
const isEmpty = !displayValues || Object.keys(displayValues).length === 0
|
|
56
68
|
|
|
57
69
|
// Start editing mode
|
|
58
70
|
const handleStartEdit = useCallback(() => {
|
|
71
|
+
if (!overridesReady) return
|
|
59
72
|
// Allow editing even with no user-supplied values (start with empty YAML)
|
|
60
|
-
const yamlStr =
|
|
73
|
+
const yamlStr = overridesBaseline && Object.keys(overridesBaseline).length > 0 ? jsonToYaml(overridesBaseline) : ''
|
|
61
74
|
setEditedYaml(yamlStr)
|
|
62
75
|
setYamlError(null)
|
|
63
76
|
setIsEditing(true)
|
|
64
|
-
//
|
|
65
|
-
if (
|
|
66
|
-
|
|
77
|
+
// Only user overrides are editable.
|
|
78
|
+
if (showEffectiveValues) {
|
|
79
|
+
onToggleEffectiveValues(false)
|
|
67
80
|
}
|
|
68
|
-
}, [
|
|
81
|
+
}, [overridesBaseline, overridesReady, showEffectiveValues, onToggleEffectiveValues])
|
|
69
82
|
|
|
70
83
|
// Cancel editing
|
|
71
84
|
const handleCancelEdit = useCallback(() => {
|
|
@@ -96,7 +109,7 @@ export function ValuesViewer({
|
|
|
96
109
|
|
|
97
110
|
// Preview changes
|
|
98
111
|
const handlePreview = useCallback(async () => {
|
|
99
|
-
if (!namespace || !name || isHistoricalRevision) return
|
|
112
|
+
if (!namespace || !name || isHistoricalRevision || !overridesReady) return
|
|
100
113
|
const parsed = parseYaml(editedYaml)
|
|
101
114
|
if (!parsed) return
|
|
102
115
|
|
|
@@ -111,11 +124,11 @@ export function ValuesViewer({
|
|
|
111
124
|
} catch {
|
|
112
125
|
// Error is handled by mutation
|
|
113
126
|
}
|
|
114
|
-
}, [namespace, name, isHistoricalRevision, editedYaml, parseYaml, previewMutation])
|
|
127
|
+
}, [namespace, name, isHistoricalRevision, overridesReady, editedYaml, parseYaml, previewMutation])
|
|
115
128
|
|
|
116
129
|
// Apply changes
|
|
117
130
|
const handleApply = useCallback(async () => {
|
|
118
|
-
if (!namespace || !name || isHistoricalRevision) return
|
|
131
|
+
if (!namespace || !name || isHistoricalRevision || !overridesReady) return
|
|
119
132
|
const parsed = parseYaml(editedYaml)
|
|
120
133
|
if (!parsed) return
|
|
121
134
|
|
|
@@ -130,11 +143,11 @@ export function ValuesViewer({
|
|
|
130
143
|
} catch {
|
|
131
144
|
// Error is handled by mutation
|
|
132
145
|
}
|
|
133
|
-
}, [namespace, name, isHistoricalRevision, editedYaml, parseYaml, applyMutation, handleCancelEdit, onApplySuccess])
|
|
146
|
+
}, [namespace, name, isHistoricalRevision, overridesReady, editedYaml, parseYaml, applyMutation, handleCancelEdit, onApplySuccess])
|
|
134
147
|
|
|
135
148
|
// Apply from preview modal
|
|
136
149
|
const handleApplyFromPreview = useCallback(async () => {
|
|
137
|
-
if (!previewData || !namespace || !name || isHistoricalRevision) return
|
|
150
|
+
if (!previewData || !namespace || !name || isHistoricalRevision || !overridesReady) return
|
|
138
151
|
try {
|
|
139
152
|
await applyMutation.mutateAsync({
|
|
140
153
|
namespace,
|
|
@@ -147,48 +160,21 @@ export function ValuesViewer({
|
|
|
147
160
|
} catch {
|
|
148
161
|
// Error is handled by mutation
|
|
149
162
|
}
|
|
150
|
-
}, [previewData, namespace, name, isHistoricalRevision, applyMutation, handleCancelEdit, onApplySuccess])
|
|
163
|
+
}, [previewData, namespace, name, isHistoricalRevision, overridesReady, applyMutation, handleCancelEdit, onApplySuccess])
|
|
151
164
|
|
|
152
|
-
if (isLoading) {
|
|
165
|
+
if (!isEditing && (showEffectiveValues ? isLoading : overridesQuery.isLoading)) {
|
|
153
166
|
return <PaneLoader label="Loading values…" className="h-32" />
|
|
154
167
|
}
|
|
155
168
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
</div>
|
|
166
|
-
<div className="flex items-center gap-2">
|
|
167
|
-
<ToggleButton showAll={showAllValues} onToggle={onToggleAllValues} disabled={isEditing} />
|
|
168
|
-
{canEdit && (
|
|
169
|
-
<button
|
|
170
|
-
onClick={handleStartEdit}
|
|
171
|
-
className="flex items-center gap-1 px-2 py-1 text-xs text-theme-text-secondary hover:text-theme-text-primary hover:bg-theme-elevated rounded"
|
|
172
|
-
>
|
|
173
|
-
<Pencil className="w-3.5 h-3.5" />
|
|
174
|
-
Edit
|
|
175
|
-
</button>
|
|
176
|
-
)}
|
|
177
|
-
</div>
|
|
178
|
-
</div>
|
|
179
|
-
{isHistoricalRevision && (
|
|
180
|
-
<div className="mb-3 rounded border border-theme-border bg-theme-elevated/40 px-3 py-2 text-xs text-theme-text-secondary">
|
|
181
|
-
Viewing historical values. Switch back to the latest revision before editing or applying changes.
|
|
182
|
-
</div>
|
|
183
|
-
)}
|
|
184
|
-
<div className="flex flex-col items-center justify-center h-32 text-theme-text-tertiary gap-2">
|
|
185
|
-
<Settings className="w-8 h-8 text-theme-text-disabled" />
|
|
186
|
-
<span>{showAllValues ? 'No computed values' : 'No user-supplied values'}</span>
|
|
187
|
-
</div>
|
|
188
|
-
</div>
|
|
189
|
-
)
|
|
190
|
-
}
|
|
191
|
-
|
|
169
|
+
const readProblems = valuesReadProblems({
|
|
170
|
+
showEffectiveValues,
|
|
171
|
+
effectiveLoaded: values !== undefined,
|
|
172
|
+
effectiveFailed: Boolean(error),
|
|
173
|
+
overridesLoaded: overridesBaseline !== undefined,
|
|
174
|
+
overridesFailed: Boolean(overridesQuery.error),
|
|
175
|
+
editingAffected: canOfferEdit,
|
|
176
|
+
})
|
|
177
|
+
const viewLoadFailed = showEffectiveValues ? Boolean(error) && !values : Boolean(overridesQuery.error) && !overridesBaseline
|
|
192
178
|
const yamlContent = isEditing ? editedYaml : jsonToYaml(displayValues || {})
|
|
193
179
|
|
|
194
180
|
return (
|
|
@@ -197,7 +183,7 @@ export function ValuesViewer({
|
|
|
197
183
|
<div className="flex items-center justify-between mb-3">
|
|
198
184
|
<div className="flex items-center gap-2">
|
|
199
185
|
<span className="text-sm font-medium text-theme-text-secondary">
|
|
200
|
-
{isEditing ? 'Editing
|
|
186
|
+
{isEditing ? 'Editing User Overrides' : showEffectiveValues ? 'Effective Values' : 'User Overrides'}
|
|
201
187
|
</span>
|
|
202
188
|
{isHistoricalRevision && !isEditing && (
|
|
203
189
|
<span className="badge-sm bg-theme-hover/50 text-theme-text-secondary">revision {revision}</span>
|
|
@@ -211,21 +197,24 @@ export function ValuesViewer({
|
|
|
211
197
|
<div className="flex items-center gap-2">
|
|
212
198
|
{!isEditing && (
|
|
213
199
|
<>
|
|
214
|
-
<
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
200
|
+
<ValuesViewToggle showEffective={showEffectiveValues} onToggle={onToggleEffectiveValues} disabled={isEditing} />
|
|
201
|
+
{!isEmpty && (
|
|
202
|
+
<button
|
|
203
|
+
onClick={() => onCopy(yamlContent)}
|
|
204
|
+
className="flex items-center gap-1 px-2 py-1 text-xs text-theme-text-secondary hover:text-theme-text-primary hover:bg-theme-elevated rounded"
|
|
205
|
+
>
|
|
206
|
+
{copied ? <Check className="w-3.5 h-3.5 text-green-400" /> : <Copy className="w-3.5 h-3.5" />}
|
|
207
|
+
Copy
|
|
208
|
+
</button>
|
|
209
|
+
)}
|
|
210
|
+
{canOfferEdit && (
|
|
223
211
|
<button
|
|
224
212
|
onClick={handleStartEdit}
|
|
225
|
-
|
|
213
|
+
disabled={!canEdit}
|
|
214
|
+
className="flex items-center gap-1 px-2 py-1 text-xs text-blue-400 hover:text-blue-300 hover:bg-blue-500/10 rounded border border-blue-500/30 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
226
215
|
>
|
|
227
216
|
<Pencil className="w-3.5 h-3.5" />
|
|
228
|
-
Edit
|
|
217
|
+
Edit Overrides
|
|
229
218
|
</button>
|
|
230
219
|
)}
|
|
231
220
|
</>
|
|
@@ -241,7 +230,7 @@ export function ValuesViewer({
|
|
|
241
230
|
</button>
|
|
242
231
|
<button
|
|
243
232
|
onClick={handlePreview}
|
|
244
|
-
disabled={!!yamlError || previewMutation.isPending || isHistoricalRevision}
|
|
233
|
+
disabled={!!yamlError || previewMutation.isPending || isHistoricalRevision || !overridesReady}
|
|
245
234
|
className="flex items-center gap-1 px-2 py-1 text-xs text-theme-text-secondary hover:text-theme-text-primary hover:bg-theme-elevated rounded border border-theme-border disabled:opacity-50 disabled:cursor-not-allowed"
|
|
246
235
|
>
|
|
247
236
|
{previewMutation.isPending ? (
|
|
@@ -254,7 +243,7 @@ export function ValuesViewer({
|
|
|
254
243
|
<Tooltip content={!canHelmWrite ? helmActReason : ''}>
|
|
255
244
|
<button
|
|
256
245
|
onClick={handleApply}
|
|
257
|
-
disabled={!!yamlError || applyMutation.isPending || !canHelmWrite || isHistoricalRevision}
|
|
246
|
+
disabled={!!yamlError || applyMutation.isPending || !canHelmWrite || isHistoricalRevision || !overridesReady}
|
|
258
247
|
className="flex items-center gap-1 px-2.5 py-1 text-xs btn-brand rounded disabled:cursor-not-allowed disabled:pointer-events-none"
|
|
259
248
|
>
|
|
260
249
|
{applyMutation.isPending ? (
|
|
@@ -276,6 +265,44 @@ export function ValuesViewer({
|
|
|
276
265
|
</div>
|
|
277
266
|
)}
|
|
278
267
|
|
|
268
|
+
{!isEditing && readProblems.map((problem) => (
|
|
269
|
+
<div key={problem.read} role="alert" className="mb-3 rounded border border-red-500/30 bg-red-500/10 px-3 py-2 text-xs text-red-400">
|
|
270
|
+
{problem.message}{' '}
|
|
271
|
+
{(problem.read === 'overrides' || onRetry) && (
|
|
272
|
+
<button
|
|
273
|
+
type="button"
|
|
274
|
+
onClick={() => (problem.read === 'overrides' ? void overridesQuery.refetch() : onRetry?.())}
|
|
275
|
+
className="underline hover:text-red-300"
|
|
276
|
+
>
|
|
277
|
+
Try again
|
|
278
|
+
</button>
|
|
279
|
+
)}
|
|
280
|
+
</div>
|
|
281
|
+
))}
|
|
282
|
+
|
|
283
|
+
{isEditing && !overridesReady && (
|
|
284
|
+
<div
|
|
285
|
+
role={overridesQuery.error ? 'alert' : undefined}
|
|
286
|
+
className={clsx(
|
|
287
|
+
'mb-3 rounded border px-3 py-2 text-xs',
|
|
288
|
+
overridesQuery.error
|
|
289
|
+
? 'border-red-500/30 bg-red-500/10 text-red-400'
|
|
290
|
+
: 'border-theme-border bg-theme-elevated/40 text-theme-text-secondary',
|
|
291
|
+
)}
|
|
292
|
+
>
|
|
293
|
+
{overridesQuery.error ? (
|
|
294
|
+
<>
|
|
295
|
+
Couldn't reload user overrides, so Apply is off. Your edits are kept.{' '}
|
|
296
|
+
<button type="button" onClick={() => void overridesQuery.refetch()} className="underline hover:text-red-300">
|
|
297
|
+
Try again
|
|
298
|
+
</button>
|
|
299
|
+
</>
|
|
300
|
+
) : (
|
|
301
|
+
'Reloading user overrides. Your edits are kept.'
|
|
302
|
+
)}
|
|
303
|
+
</div>
|
|
304
|
+
)}
|
|
305
|
+
|
|
279
306
|
{/* Error message */}
|
|
280
307
|
{yamlError && (
|
|
281
308
|
<div className="mb-3 px-3 py-2 text-xs text-red-400 bg-red-500/10 border border-red-500/30 rounded">
|
|
@@ -292,7 +319,12 @@ export function ValuesViewer({
|
|
|
292
319
|
|
|
293
320
|
{/* Editor / Viewer */}
|
|
294
321
|
<div className="flex-1 min-h-0">
|
|
295
|
-
{isEditing ? (
|
|
322
|
+
{!isEditing && viewLoadFailed ? null : !isEditing && isEmpty ? (
|
|
323
|
+
<div className="flex flex-col items-center justify-center h-32 text-theme-text-tertiary gap-2">
|
|
324
|
+
<Settings className="w-8 h-8 text-theme-text-disabled" />
|
|
325
|
+
<span>{showEffectiveValues ? 'No effective values' : 'No user overrides'}</span>
|
|
326
|
+
</div>
|
|
327
|
+
) : isEditing ? (
|
|
296
328
|
<YamlEditor
|
|
297
329
|
value={editedYaml}
|
|
298
330
|
onChange={setEditedYaml}
|
|
@@ -325,28 +357,87 @@ export function ValuesViewer({
|
|
|
325
357
|
)
|
|
326
358
|
}
|
|
327
359
|
|
|
328
|
-
|
|
360
|
+
export interface ValuesReadProblem {
|
|
361
|
+
read: 'effective' | 'overrides'
|
|
362
|
+
message: string
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// The two reads fail independently, so each view only reports its own read,
|
|
366
|
+
// plus the overrides read wherever it switches editing off. "Refresh" wording
|
|
367
|
+
// is only used when the last loaded data for that view is still on screen.
|
|
368
|
+
export function valuesReadProblems({
|
|
369
|
+
showEffectiveValues,
|
|
370
|
+
effectiveLoaded,
|
|
371
|
+
effectiveFailed,
|
|
372
|
+
overridesLoaded,
|
|
373
|
+
overridesFailed,
|
|
374
|
+
editingAffected,
|
|
375
|
+
}: {
|
|
376
|
+
showEffectiveValues: boolean
|
|
377
|
+
effectiveLoaded: boolean
|
|
378
|
+
effectiveFailed: boolean
|
|
379
|
+
overridesLoaded: boolean
|
|
380
|
+
overridesFailed: boolean
|
|
381
|
+
editingAffected: boolean
|
|
382
|
+
}): ValuesReadProblem[] {
|
|
383
|
+
const problems: ValuesReadProblem[] = []
|
|
384
|
+
if (showEffectiveValues && effectiveFailed) {
|
|
385
|
+
problems.push({
|
|
386
|
+
read: 'effective',
|
|
387
|
+
message: effectiveLoaded
|
|
388
|
+
? "Couldn't refresh effective values. Showing the last ones that loaded."
|
|
389
|
+
: "Couldn't load effective values.",
|
|
390
|
+
})
|
|
391
|
+
}
|
|
392
|
+
if (overridesFailed && !showEffectiveValues) {
|
|
393
|
+
problems.push({
|
|
394
|
+
read: 'overrides',
|
|
395
|
+
message: overridesLoaded
|
|
396
|
+
? "Couldn't refresh your overrides. Showing the last ones that loaded."
|
|
397
|
+
+ (editingAffected ? ' Editing is off until they load again.' : '')
|
|
398
|
+
: "Couldn't load your overrides." + (editingAffected ? ' Editing is off until they load.' : ''),
|
|
399
|
+
})
|
|
400
|
+
} else if (overridesFailed && editingAffected) {
|
|
401
|
+
problems.push({
|
|
402
|
+
read: 'overrides',
|
|
403
|
+
message: overridesLoaded
|
|
404
|
+
? "Couldn't refresh your overrides, so editing is off."
|
|
405
|
+
: "Couldn't load your overrides, so editing is off.",
|
|
406
|
+
})
|
|
407
|
+
}
|
|
408
|
+
return problems
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
function ValuesViewToggle({
|
|
412
|
+
showEffective,
|
|
413
|
+
onToggle,
|
|
414
|
+
disabled,
|
|
415
|
+
}: {
|
|
416
|
+
showEffective: boolean
|
|
417
|
+
onToggle: (show: boolean) => void
|
|
418
|
+
disabled?: boolean
|
|
419
|
+
}) {
|
|
329
420
|
return (
|
|
330
421
|
<div className={clsx('flex items-center bg-theme-elevated/50 rounded-md p-0.5 text-xs', disabled && 'opacity-50 pointer-events-none')}>
|
|
331
422
|
<button
|
|
332
|
-
onClick={() => onToggle(
|
|
423
|
+
onClick={() => onToggle(true)}
|
|
333
424
|
disabled={disabled}
|
|
334
425
|
className={clsx(
|
|
335
426
|
'px-2 py-1 rounded transition-colors',
|
|
336
|
-
|
|
427
|
+
showEffective ? 'bg-theme-hover text-theme-text-primary' : 'text-theme-text-secondary hover:text-theme-text-primary'
|
|
337
428
|
)}
|
|
338
429
|
>
|
|
339
|
-
|
|
430
|
+
Effective Values
|
|
340
431
|
</button>
|
|
341
432
|
<button
|
|
342
|
-
onClick={() => onToggle(
|
|
433
|
+
onClick={() => onToggle(false)}
|
|
343
434
|
disabled={disabled}
|
|
344
435
|
className={clsx(
|
|
345
436
|
'px-2 py-1 rounded transition-colors',
|
|
346
|
-
|
|
437
|
+
!showEffective ? 'bg-theme-hover text-theme-text-primary' : 'text-theme-text-secondary hover:text-theme-text-primary'
|
|
347
438
|
)}
|
|
348
439
|
>
|
|
349
|
-
|
|
440
|
+
User Overrides
|
|
350
441
|
</button>
|
|
351
442
|
</div>
|
|
352
443
|
)
|
|
@@ -33,7 +33,7 @@ function scoreLane(lane: ResourceLane): number {
|
|
|
33
33
|
const kindScores: Record<string, number> = {
|
|
34
34
|
Deployment: 50, Rollout: 50, StatefulSet: 50, DaemonSet: 50,
|
|
35
35
|
Service: 45, Ingress: 45, Gateway: 45,
|
|
36
|
-
HTTPRoute: 42, GRPCRoute: 42, TCPRoute: 42, TLSRoute: 42,
|
|
36
|
+
HTTPRoute: 42, GRPCRoute: 42, TCPRoute: 42, TLSRoute: 42, UDPRoute: 42,
|
|
37
37
|
Job: 40, CronJob: 40,
|
|
38
38
|
Pod: 30, ReplicaSet: 20,
|
|
39
39
|
}
|
|
@@ -69,6 +69,7 @@ const KIND_SHORT: Record<string, string> = {
|
|
|
69
69
|
HTTPRoute: 'HR',
|
|
70
70
|
GRPCRoute: 'gRPC',
|
|
71
71
|
TCPRoute: 'TCP',
|
|
72
|
+
UDPRoute: 'UDP',
|
|
72
73
|
TLSRoute: 'TLS',
|
|
73
74
|
}
|
|
74
75
|
|
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
FreshnessControl,
|
|
21
21
|
PaneLoader,
|
|
22
22
|
StatusDot,
|
|
23
|
-
|
|
23
|
+
issueTitle,
|
|
24
24
|
groupLabel,
|
|
25
25
|
issueFirstSeenTitle,
|
|
26
26
|
issueTimingForDisplay,
|
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
import { formatCompactAge } from '@skyhook-io/k8s-ui/utils/format'
|
|
31
31
|
import { ClusterHealthCard } from './ClusterHealthCard'
|
|
32
32
|
import { AlertTriangle, CheckCircle, Loader2, Shield } from 'lucide-react'
|
|
33
|
+
import { uncheckedSourceLines } from '../issues/uncheckedSources'
|
|
33
34
|
import { clsx } from 'clsx'
|
|
34
35
|
import { getVersionUpdateStatus } from '../../utils/version'
|
|
35
36
|
import { RadarVersionLine } from './RadarVersionLine'
|
|
@@ -271,6 +272,7 @@ export function HomeView({ namespaces, topology, fallbackClusterLoadState, onNav
|
|
|
271
272
|
issues={issues}
|
|
272
273
|
issueCount={issueCount}
|
|
273
274
|
visibility={issuesData?.visibility}
|
|
275
|
+
uncheckedLines={uncheckedSourceLines(issuesData?.unchecked_sources)}
|
|
274
276
|
hasData={!!issuesData}
|
|
275
277
|
isLoading={issuesLoading && !issuesData}
|
|
276
278
|
isRefreshing={issuesFetching && !!issuesData}
|
|
@@ -300,6 +302,9 @@ interface ProblemsPanelProps {
|
|
|
300
302
|
issues: Issue[]
|
|
301
303
|
issueCount: number
|
|
302
304
|
visibility?: IssuesResponse['visibility']
|
|
305
|
+
/** One line per issue source Radar couldn't check. Any line means an
|
|
306
|
+
* empty list isn't an all-clear. */
|
|
307
|
+
uncheckedLines?: string[]
|
|
303
308
|
hasData: boolean
|
|
304
309
|
isLoading: boolean
|
|
305
310
|
isRefreshing: boolean
|
|
@@ -309,11 +314,11 @@ interface ProblemsPanelProps {
|
|
|
309
314
|
onResourceClick: (resource: SelectedResource) => void
|
|
310
315
|
}
|
|
311
316
|
|
|
312
|
-
|
|
313
|
-
function ProblemsPanel({
|
|
317
|
+
export function ProblemsPanel({
|
|
314
318
|
issues,
|
|
315
319
|
issueCount,
|
|
316
320
|
visibility,
|
|
321
|
+
uncheckedLines = [],
|
|
317
322
|
hasData,
|
|
318
323
|
isLoading,
|
|
319
324
|
isRefreshing,
|
|
@@ -322,6 +327,7 @@ function ProblemsPanel({
|
|
|
322
327
|
onNavigateToIssues,
|
|
323
328
|
onResourceClick,
|
|
324
329
|
}: ProblemsPanelProps) {
|
|
330
|
+
const unchecked = uncheckedLines.length > 0
|
|
325
331
|
const hasCriticalIssues = issues.some((issue) => issue.severity === 'critical')
|
|
326
332
|
const hasIssues = issueCount > 0
|
|
327
333
|
const hasHardError = !!error && !hasData
|
|
@@ -329,14 +335,14 @@ function ProblemsPanel({
|
|
|
329
335
|
const isTruncated = issueCount > totalReturned
|
|
330
336
|
const titleClass = hasCriticalIssues
|
|
331
337
|
? 'text-red-500'
|
|
332
|
-
: hasIssues || hasHardError || hasLimitedVisibility
|
|
338
|
+
: hasIssues || hasHardError || hasLimitedVisibility || unchecked
|
|
333
339
|
? 'text-amber-500'
|
|
334
340
|
: 'text-theme-text-secondary'
|
|
335
341
|
const countClass = hasHardError
|
|
336
342
|
? 'status-unknown'
|
|
337
343
|
: hasCriticalIssues
|
|
338
344
|
? 'status-unhealthy'
|
|
339
|
-
: hasIssues || hasLimitedVisibility
|
|
345
|
+
: hasIssues || hasLimitedVisibility || unchecked
|
|
340
346
|
? 'status-degraded'
|
|
341
347
|
: 'status-healthy'
|
|
342
348
|
const countLabel = isLoading ? '...' : hasHardError ? 'error' : String(issueCount)
|
|
@@ -387,6 +393,9 @@ function ProblemsPanel({
|
|
|
387
393
|
Limited visibility - {visibility.impact} Results may be incomplete.
|
|
388
394
|
</ProblemsPanelNotice>
|
|
389
395
|
)}
|
|
396
|
+
{(issues.length > 0 || hasLimitedVisibility) && uncheckedLines.map((line) => (
|
|
397
|
+
<ProblemsPanelNotice key={line} tone="warning">{line}</ProblemsPanelNotice>
|
|
398
|
+
))}
|
|
390
399
|
{isTruncated && (
|
|
391
400
|
<ProblemsPanelNotice tone="neutral">
|
|
392
401
|
Showing {totalReturned} of {issueCount} issues. Narrow by namespace to see the rest.
|
|
@@ -396,15 +405,17 @@ function ProblemsPanel({
|
|
|
396
405
|
{issues.length === 0 ? (
|
|
397
406
|
<ProblemsPanelState
|
|
398
407
|
icon={
|
|
399
|
-
hasLimitedVisibility
|
|
408
|
+
hasLimitedVisibility || unchecked
|
|
400
409
|
? <AlertTriangle className="h-5 w-5 text-amber-500" />
|
|
401
410
|
: <CheckCircle className="h-5 w-5 text-green-500" />
|
|
402
411
|
}
|
|
403
|
-
title={hasLimitedVisibility ? 'No visible active issues' : 'No active issues'}
|
|
412
|
+
title={hasLimitedVisibility ? 'No visible active issues' : unchecked ? 'No issues found' : 'No active issues'}
|
|
404
413
|
body={
|
|
405
414
|
hasLimitedVisibility
|
|
406
415
|
? 'Radar could not read every workload source in this scope.'
|
|
407
|
-
:
|
|
416
|
+
: unchecked
|
|
417
|
+
? uncheckedLines.join(' ')
|
|
418
|
+
: 'No critical or warning issues across the selected scope.'
|
|
408
419
|
}
|
|
409
420
|
/>
|
|
410
421
|
) : (
|
|
@@ -448,7 +459,7 @@ function ProblemsPanel({
|
|
|
448
459
|
)}
|
|
449
460
|
</div>
|
|
450
461
|
<div className="flex items-center gap-1.5 mt-0.5">
|
|
451
|
-
<span className="text-[11px] text-theme-text-secondary truncate">{
|
|
462
|
+
<span className="text-[11px] text-theme-text-secondary truncate">{issueTitle(issue)}</span>
|
|
452
463
|
<span className="text-[10px] text-theme-text-tertiary shrink-0">{groupLabel(issue.category_group)}</span>
|
|
453
464
|
{issue.namespace && <span className="text-[10px] text-theme-text-tertiary shrink-0">{issue.namespace}</span>}
|
|
454
465
|
</div>
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { renderToString } from 'react-dom/server'
|
|
3
|
+
import type { Issue } from '@skyhook-io/k8s-ui'
|
|
4
|
+
import { ProblemsPanel } from './HomeView'
|
|
5
|
+
import { uncheckedSourceLines } from '../issues/uncheckedSources'
|
|
6
|
+
import type { IssuesResponse } from '../../api/client'
|
|
7
|
+
|
|
8
|
+
const line = "Helm releases couldn't be checked, so failed or stuck releases may be missing."
|
|
9
|
+
const generic = "Some issue checks couldn't run, so issues may be missing."
|
|
10
|
+
const helm = [{ source: 'helm', reason: 'read_failed' }]
|
|
11
|
+
const issue: Issue = {
|
|
12
|
+
id: 'pod-failed', severity: 'critical' as const, source: 'problem', category: 'workload', category_group: 'workload', grouping_scope: 'resource',
|
|
13
|
+
kind: 'Pod', group: '', namespace: 'db', name: 'pg-1', reason: 'CrashLoopBackOff', message: 'The selected Pod keeps crashing',
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function render(props: { issues?: Issue[]; unchecked?: IssuesResponse['unchecked_sources']; visibility?: { impact?: string } }) {
|
|
17
|
+
const issues = props.issues ?? []
|
|
18
|
+
return renderToString(
|
|
19
|
+
<ProblemsPanel
|
|
20
|
+
issues={issues}
|
|
21
|
+
issueCount={issues.length}
|
|
22
|
+
uncheckedLines={uncheckedSourceLines(props.unchecked)}
|
|
23
|
+
visibility={props.visibility}
|
|
24
|
+
hasData
|
|
25
|
+
isLoading={false}
|
|
26
|
+
isRefreshing={false}
|
|
27
|
+
error={null}
|
|
28
|
+
totalReturned={issues.length}
|
|
29
|
+
onNavigateToIssues={() => {}}
|
|
30
|
+
onResourceClick={() => {}}
|
|
31
|
+
/>,
|
|
32
|
+
).replaceAll('<!-- -->', '').replace(/'|'/g, "'")
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
describe('Home Active Issues when Radar could not read Helm releases', () => {
|
|
36
|
+
it('gives no all-clear when nothing else was found', () => {
|
|
37
|
+
const html = render({ unchecked: helm })
|
|
38
|
+
expect(html).toContain('No issues found')
|
|
39
|
+
expect(html).toContain(line)
|
|
40
|
+
expect(html).not.toContain('No active issues')
|
|
41
|
+
expect(html).not.toContain('status-healthy')
|
|
42
|
+
expect(html).not.toContain('text-green-500')
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('keeps the line when limited visibility owns the empty state', () => {
|
|
46
|
+
const html = render({ unchecked: helm, visibility: { impact: 'Pods are not readable.' } })
|
|
47
|
+
expect(html).toContain('No visible active issues')
|
|
48
|
+
expect(html).toContain(line)
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('says so above the rows when there are other issues', () => {
|
|
52
|
+
const html = render({ issues: [issue], unchecked: helm })
|
|
53
|
+
expect(html).toContain(line)
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
it('keeps the all-clear when the field is absent', () => {
|
|
57
|
+
const html = render({})
|
|
58
|
+
expect(html).toContain('No active issues')
|
|
59
|
+
expect(html).toContain('status-healthy')
|
|
60
|
+
expect(html).not.toContain('couldn')
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it('gives no all-clear for a source this version does not know, and says so generically', () => {
|
|
64
|
+
const html = render({ unchecked: [{ source: 'some-new-source', reason: 'read_failed' }] })
|
|
65
|
+
expect(html).toContain('No issues found')
|
|
66
|
+
expect(html).toContain(generic)
|
|
67
|
+
expect(html).not.toContain('some-new-source')
|
|
68
|
+
expect(html).not.toContain('No active issues')
|
|
69
|
+
expect(html).not.toContain('status-healthy')
|
|
70
|
+
expect(html).not.toContain('text-green-500')
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
it('shows one line per source above the rows, without repeats', () => {
|
|
74
|
+
const html = render({
|
|
75
|
+
issues: [issue],
|
|
76
|
+
unchecked: [...helm, { source: 'a', reason: 'x' }, { source: 'b', reason: 'y' }, ...helm],
|
|
77
|
+
})
|
|
78
|
+
expect(html.split(line).length - 1).toBe(1)
|
|
79
|
+
expect(html.split(generic).length - 1).toBe(1)
|
|
80
|
+
})
|
|
81
|
+
})
|
|
@@ -108,7 +108,7 @@ function useTopologyStats(topology: Topology | null) {
|
|
|
108
108
|
const kindPriority: Record<string, number> = {
|
|
109
109
|
Deployment: 1, Rollout: 1, StatefulSet: 2, DaemonSet: 2,
|
|
110
110
|
Service: 3, Ingress: 4, Gateway: 4,
|
|
111
|
-
HTTPRoute: 4, GRPCRoute: 4, TCPRoute: 4, TLSRoute: 4,
|
|
111
|
+
HTTPRoute: 4, GRPCRoute: 4, TCPRoute: 4, TLSRoute: 4, UDPRoute: 4,
|
|
112
112
|
Pod: 5, PodGroup: 5,
|
|
113
113
|
Job: 6, CronJob: 6, ConfigMap: 7, Secret: 7,
|
|
114
114
|
}
|
|
@@ -125,7 +125,7 @@ const kindDotColors: Record<string, string> = {
|
|
|
125
125
|
Deployment: 'bg-emerald-400', Rollout: 'bg-emerald-400',
|
|
126
126
|
StatefulSet: 'bg-cyan-400', DaemonSet: 'bg-teal-400',
|
|
127
127
|
Service: 'bg-blue-400', Ingress: 'bg-violet-400', Gateway: 'bg-violet-400',
|
|
128
|
-
HTTPRoute: 'bg-purple-400', GRPCRoute: 'bg-purple-400', TCPRoute: 'bg-purple-400', TLSRoute: 'bg-purple-400',
|
|
128
|
+
HTTPRoute: 'bg-purple-400', GRPCRoute: 'bg-purple-400', TCPRoute: 'bg-purple-400', TLSRoute: 'bg-purple-400', UDPRoute: 'bg-purple-400',
|
|
129
129
|
Pod: 'bg-lime-500', PodGroup: 'bg-lime-500',
|
|
130
130
|
Job: 'bg-purple-400', CronJob: 'bg-purple-400',
|
|
131
131
|
ConfigMap: 'bg-amber-400', Secret: 'bg-red-400',
|