@skyhook-io/k8s-ui 1.10.4 → 1.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +5 -5
- package/src/components/applications/ApplicationDetail.test.tsx +46 -0
- package/src/components/applications/ApplicationDetail.tsx +14 -15
- package/src/components/issues/IssuesView.tsx +9 -0
- package/src/components/issues/diagnostic.ts +3 -0
- package/src/components/issues/issues.test.ts +11 -0
- package/src/components/issues/severity.ts +3 -0
- package/src/components/issues/types.ts +3 -0
- package/src/components/resources/ResourcesSidebar.tsx +1 -1
- package/src/components/resources/ResourcesView.tsx +259 -30
- package/src/components/resources/index.ts +2 -0
- package/src/components/resources/kyverno-cell-gating.test.ts +93 -0
- package/src/components/resources/kyverno-modern-posture.test.ts +290 -0
- package/src/components/resources/renderers/CNPGClusterRenderer.test.tsx +85 -0
- package/src/components/resources/renderers/CNPGClusterRenderer.tsx +109 -9
- package/src/components/resources/renderers/CNPGPoolerRenderer.tsx +8 -4
- package/src/components/resources/renderers/EventRenderer.test.tsx +23 -0
- package/src/components/resources/renderers/EventRenderer.tsx +10 -18
- package/src/components/resources/renderers/KyvernoCELPolicyRenderers.tsx +317 -0
- package/src/components/resources/renderers/KyvernoExceptionRenderers.tsx +264 -0
- package/src/components/resources/renderers/KyvernoPolicyShared.tsx +212 -0
- package/src/components/resources/renderers/VeleroBSLRenderer.tsx +2 -4
- package/src/components/resources/renderers/VeleroBackupRenderer.tsx +32 -9
- package/src/components/resources/renderers/VeleroRestoreRenderer.tsx +30 -9
- package/src/components/resources/renderers/VeleroScheduleRenderer.tsx +12 -6
- package/src/components/resources/renderers/badge-no-handrolled.test.tsx +1 -1
- package/src/components/resources/renderers/cnpg-cells.tsx +37 -2
- package/src/components/resources/renderers/index.ts +4 -0
- package/src/components/resources/renderers/kyverno-modern-cells.tsx +152 -0
- package/src/components/resources/renderers/velero-cells.tsx +128 -9
- package/src/components/resources/renderers/velero-phase-recovery.test.tsx +79 -0
- package/src/components/resources/resource-utils-cnpg.golden.test.ts +112 -0
- package/src/components/resources/resource-utils-cnpg.test.ts +527 -1
- package/src/components/resources/resource-utils-cnpg.ts +373 -56
- package/src/components/resources/resource-utils-kyverno-exceptions.ts +182 -0
- package/src/components/resources/resource-utils-kyverno-modern.ts +588 -0
- package/src/components/resources/resource-utils-velero.test.ts +237 -0
- package/src/components/resources/resource-utils-velero.ts +214 -22
- package/src/components/resources/resource-utils.ts +70 -3
- package/src/components/shared/ResourceActionsBar.tsx +3 -1
- package/src/components/shared/ResourceRendererDispatch.test.tsx +119 -1
- package/src/components/shared/ResourceRendererDispatch.tsx +116 -22
- package/src/components/topology/K8sResourceNode.tsx +11 -0
- package/src/components/topology/TopologyGraph.tsx +85 -5
- package/src/components/trace/ReachabilityGraph.tsx +517 -0
- package/src/components/trace/ReachabilityView.tsx +1173 -0
- package/src/components/trace/TracePanel.test.ts +172 -0
- package/src/components/trace/TracePanel.tsx +558 -0
- package/src/components/trace/TraceSummary.test.ts +213 -0
- package/src/components/trace/TraceSummary.tsx +168 -0
- package/src/components/trace/inClusterSummary.test.ts +98 -0
- package/src/components/trace/inClusterSummary.ts +81 -0
- package/src/components/trace/index.ts +19 -0
- package/src/components/trace/podReach.test.ts +71 -0
- package/src/components/trace/podReach.ts +37 -0
- package/src/components/trace/probe-display.test.ts +107 -0
- package/src/components/trace/probe-display.ts +37 -0
- package/src/components/trace/problemRows.test.ts +107 -0
- package/src/components/trace/reachFixtures.ts +261 -0
- package/src/components/trace/reachGraphModel.test.ts +1423 -0
- package/src/components/trace/reachGraphModel.ts +1521 -0
- package/src/components/trace/reachInspector.test.ts +892 -0
- package/src/components/trace/reachInspector.ts +797 -0
- package/src/components/trace/reachMarks.test.ts +698 -0
- package/src/components/trace/reachMarks.ts +623 -0
- package/src/components/trace/reachOrigins.test.ts +392 -0
- package/src/components/trace/reachOrigins.ts +449 -0
- package/src/components/trace/reachVerdict.test.ts +537 -0
- package/src/components/trace/reachVerdict.ts +601 -0
- package/src/components/trace/traceFingerprint.test.ts +214 -0
- package/src/components/trace/traceFingerprint.ts +122 -0
- package/src/components/trace/traceToSubgraph.test.ts +623 -0
- package/src/components/trace/traceToSubgraph.ts +463 -0
- package/src/components/trace/types.ts +414 -0
- package/src/components/ui/Badge.tsx +36 -0
- package/src/components/ui/ForceDeleteConfirmDialog.tsx +12 -1
- package/src/components/ui/InClusterConsentDialog.test.tsx +83 -0
- package/src/components/ui/InClusterConsentDialog.tsx +141 -0
- package/src/components/ui/index.ts +1 -0
- package/src/components/workload/WorkloadView.tsx +58 -5
- package/src/components/workload/index.ts +1 -1
- package/src/components/workload/reachabilityTab.test.ts +47 -0
- package/src/index.ts +5 -0
- package/src/theme/components.css +64 -0
- package/src/theme/variables.css +5 -0
- package/src/types/core.ts +8 -0
- package/src/utils/api-resources.ts +2 -0
- package/src/utils/application-history.test.ts +1 -1
- package/src/utils/applications.test.ts +63 -1
- package/src/utils/applications.ts +27 -0
- package/src/utils/inClusterConsent.test.ts +109 -0
- package/src/utils/inClusterConsent.ts +72 -0
- package/src/utils/index.ts +1 -0
- package/src/utils/navigation.test.ts +61 -0
- package/src/utils/navigation.ts +11 -9
- package/src/utils/pluralize.test.ts +80 -1
- package/src/utils/pluralize.ts +34 -0
- package/src/utils/resource-icons.test.ts +33 -0
- package/src/utils/resource-icons.ts +65 -2
|
@@ -12,9 +12,10 @@ import { getArgoApplicationStatus, getArgoApplicationSetStatus, getArgoApplicati
|
|
|
12
12
|
import { getPolicyReportStatus as _getPolicyReportStatus, getKyvernoPolicyStatus as _getKyvernoPolicyStatus } from './resource-utils-kyverno'
|
|
13
13
|
import { getResourceClaimStatus as _getResourceClaimStatus, getResourceClaimDeviceClasses as _getResourceClaimDeviceClasses, getResourceClaimTemplateDeviceClasses as _getResourceClaimTemplateDeviceClasses, getResourceClaimAllocation as _getResourceClaimAllocation, getResourceClaimReservedFor as _getResourceClaimReservedFor } from './resource-utils-dra'
|
|
14
14
|
import { getNvidiaClusterPolicyStatus as _getNvidiaClusterPolicyStatus, getNvidiaClusterPolicyEnabledComponents as _getNvidiaClusterPolicyEnabledComponents, getNvidiaDriverStatus as _getNvidiaDriverStatus } from './resource-utils-nvidia'
|
|
15
|
-
import { getBackupStatus as _getBackupStatus, getRestoreStatus as _getRestoreStatus, getScheduleStatus as _getScheduleStatus, getBSLStatus as _getBSLStatus } from './resource-utils-velero'
|
|
15
|
+
import { getBackupStatus as _getBackupStatus, getRestoreStatus as _getRestoreStatus, getScheduleStatus as _getScheduleStatus, getBSLStatus as _getBSLStatus, getBackupRepositoryStatus as _getBackupRepositoryStatus } from './resource-utils-velero'
|
|
16
16
|
import { getExternalSecretStatus as _getExternalSecretStatus, getClusterExternalSecretStatus as _getClusterExternalSecretStatus, getSecretStoreStatus as _getSecretStoreStatus, getClusterSecretStoreStatus as _getClusterSecretStoreStatus, getSecretStoreProviderType as _getSecretStoreProviderType } from './resource-utils-eso'
|
|
17
17
|
import { getHPATableState, hpaStatusFromState } from './resource-utils-hpa'
|
|
18
|
+
import { getCNPGClusterStatus as _getCNPGClusterStatus, getCNPGBackupStatus as _getCNPGBackupStatus, getCNPGScheduledBackupStatus as _getCNPGScheduledBackupStatus, getCNPGPoolerStatus as _getCNPGPoolerStatus, isApiGroup as _isApiGroup, CNPG_GROUP as _CNPG_GROUP } from './resource-utils-cnpg'
|
|
18
19
|
|
|
19
20
|
// ============================================================================
|
|
20
21
|
// STATUS & HEALTH UTILITIES
|
|
@@ -291,6 +292,14 @@ export function getPodStatus(pod: any): StatusBadge {
|
|
|
291
292
|
if (unsettled > 0) {
|
|
292
293
|
return { text: `Running (${ready}/${total})`, color: healthColors.degraded, level: 'degraded' }
|
|
293
294
|
}
|
|
295
|
+
// Batch work in flight is not an all-clear. The Job itself already grades
|
|
296
|
+
// neutral while running; without the same call here its pods would read
|
|
297
|
+
// healthy — one piece of work graded two ways. Last, after every failure
|
|
298
|
+
// check above, so a broken Job pod keeps its real verdict.
|
|
299
|
+
// Mirrors pkg/health.classifyPodLevel.
|
|
300
|
+
if (isOwnedByJob(pod)) {
|
|
301
|
+
return { text: 'Running', color: healthColors.neutral, level: 'neutral' }
|
|
302
|
+
}
|
|
294
303
|
return { text: 'Running', color: healthColors.healthy, level: 'healthy' }
|
|
295
304
|
}
|
|
296
305
|
case 'Succeeded':
|
|
@@ -741,6 +750,30 @@ export function getContainerSquareStates(pod: any): ContainerSquareState[] {
|
|
|
741
750
|
// WORKLOAD UTILITIES (Deployment, StatefulSet, DaemonSet, ReplicaSet)
|
|
742
751
|
// ============================================================================
|
|
743
752
|
|
|
753
|
+
/**
|
|
754
|
+
* Whether the controller has not yet acted on this workload's current spec, so
|
|
755
|
+
* grading against that spec would report controller lag as an outage. The
|
|
756
|
+
* scale-up case: spec says 10, the controller is still working from the previous
|
|
757
|
+
* generation and has created 3.
|
|
758
|
+
*
|
|
759
|
+
* Deliberately NOT age-based — "created recently" guesses wrong exactly when a
|
|
760
|
+
* deploy is broken on arrival. Mirrors pkg/health.converging; keep in step.
|
|
761
|
+
*/
|
|
762
|
+
function isConverging(resource: any): boolean {
|
|
763
|
+
const generation = resource?.metadata?.generation
|
|
764
|
+
const observed = resource?.status?.observedGeneration
|
|
765
|
+
return typeof generation === 'number' && typeof observed === 'number' && observed > 0 && observed < generation
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
/** Whether the pod was created by a batch Job. The owner's API group is checked
|
|
769
|
+
* so a CRD that merely uses Kind "Job" doesn't match. Mirrors
|
|
770
|
+
* pkg/health.isOwnedByJob. */
|
|
771
|
+
function isOwnedByJob(pod: any): boolean {
|
|
772
|
+
const refs = pod?.metadata?.ownerReferences
|
|
773
|
+
if (!Array.isArray(refs)) return false
|
|
774
|
+
return refs.some((r: any) => r?.kind === 'Job' && typeof r?.apiVersion === 'string' && r.apiVersion.startsWith('batch/'))
|
|
775
|
+
}
|
|
776
|
+
|
|
744
777
|
export function getWorkloadStatus(resource: any, kind: string): StatusBadge {
|
|
745
778
|
const status = resource.status || {}
|
|
746
779
|
const spec = resource.spec || {}
|
|
@@ -756,6 +789,12 @@ export function getWorkloadStatus(resource: any, kind: string): StatusBadge {
|
|
|
756
789
|
if (ready === desired && updated === desired) {
|
|
757
790
|
return { text: `${ready}/${desired}`, color: healthColors.healthy, level: 'healthy' }
|
|
758
791
|
}
|
|
792
|
+
// Convergence grace, same as the replica kinds below. pkg/health.Workload
|
|
793
|
+
// passes this signal for DaemonSets too, so omitting it here would render a
|
|
794
|
+
// mid-reconcile DaemonSet unhealthy while the backend calls it neutral.
|
|
795
|
+
if (isConverging(resource)) {
|
|
796
|
+
return { text: `${ready}/${desired}`, color: healthColors.neutral, level: 'neutral' }
|
|
797
|
+
}
|
|
759
798
|
if (ready > 0) {
|
|
760
799
|
return { text: `${ready}/${desired}`, color: healthColors.degraded, level: 'degraded' }
|
|
761
800
|
}
|
|
@@ -772,6 +811,16 @@ export function getWorkloadStatus(resource: any, kind: string): StatusBadge {
|
|
|
772
811
|
return { text: 'Scaled to 0', color: healthColors.neutral, level: 'neutral' }
|
|
773
812
|
}
|
|
774
813
|
|
|
814
|
+
// Convergence grace — mirrors pkg/health.replicaVerdict. A workload whose
|
|
815
|
+
// spec just changed (or that was created moments ago) has not had a chance to
|
|
816
|
+
// reach its target, so grading against that target reports controller lag as
|
|
817
|
+
// an outage. Neutral, never healthy: declining to call it broken is not the
|
|
818
|
+
// same as calling it fine. Guarded on "not already fully ready" so a
|
|
819
|
+
// converged workload still takes the healthy path below, matching Go's order.
|
|
820
|
+
if (isConverging(resource) && !(ready === desired && available === desired)) {
|
|
821
|
+
return { text: `${ready}/${desired}`, color: healthColors.neutral, level: 'neutral' }
|
|
822
|
+
}
|
|
823
|
+
|
|
775
824
|
// Check if updating
|
|
776
825
|
if (updated < desired && updated > 0) {
|
|
777
826
|
return { text: `Updating ${updated}/${desired}`, color: healthColors.degraded, level: 'degraded' }
|
|
@@ -2091,13 +2140,31 @@ export function getCellFilterValue(resource: any, column: string, kind: string):
|
|
|
2091
2140
|
if (kindLower === 'nvidiadrivers') return _getNvidiaDriverStatus(resource).text
|
|
2092
2141
|
if (kindLower === 'resourceclaims') return _getResourceClaimStatus(resource).text
|
|
2093
2142
|
if (kindLower === 'backups') return _getBackupStatus(resource).text
|
|
2094
|
-
|
|
2095
|
-
|
|
2143
|
+
// Velero's Restore/Schedule keys are group-qualified (see
|
|
2144
|
+
// GROUP_QUALIFIED_COLUMN_KEYS) because those plurals are shared with
|
|
2145
|
+
// rancher/backup-restore-operator and others. The unqualified plural
|
|
2146
|
+
// deliberately does not match: a foreign CRD falls through to the generic
|
|
2147
|
+
// reader below rather than being filtered as though it were Velero.
|
|
2148
|
+
if (kindLower === 'velerorestores') return _getRestoreStatus(resource).text
|
|
2149
|
+
if (kindLower === 'veleroschedules') return _getScheduleStatus(resource).text
|
|
2096
2150
|
if (kindLower === 'backupstoragelocations') return _getBSLStatus(resource).text
|
|
2151
|
+
if (kindLower === 'backuprepositories') return _getBackupRepositoryStatus(resource).text
|
|
2097
2152
|
if (kindLower === 'externalsecrets') return _getExternalSecretStatus(resource).text
|
|
2098
2153
|
if (kindLower === 'clusterexternalsecrets') return _getClusterExternalSecretStatus(resource).text
|
|
2099
2154
|
if (kindLower === 'secretstores') return _getSecretStoreStatus(resource).text
|
|
2100
2155
|
if (kindLower === 'clustersecretstores') return _getClusterSecretStoreStatus(resource).text
|
|
2156
|
+
// CNPG. The filter must read the same text the cell renders, or the
|
|
2157
|
+
// dropdown offers strings that appear on no row: CNPG's phase is prose
|
|
2158
|
+
// ("Cluster in healthy state") while the badge is a short state
|
|
2159
|
+
// ("Healthy"). Worse for `cnpgclusters` — a WAL-archiving failure leaves
|
|
2160
|
+
// the phase healthy, so filtering on the raw phase cannot express the
|
|
2161
|
+
// badge at all. `cnpgclusters`/`cnpgbackups` arrive group-qualified;
|
|
2162
|
+
// `scheduledbackups`/`poolers` are bare plurals, so gate those on the
|
|
2163
|
+
// group rather than assume nobody else ships the name.
|
|
2164
|
+
if (kindLower === 'cnpgclusters') return _getCNPGClusterStatus(resource).text
|
|
2165
|
+
if (kindLower === 'cnpgbackups') return _getCNPGBackupStatus(resource).text
|
|
2166
|
+
if (kindLower === 'scheduledbackups' && _isApiGroup(resource.apiVersion, _CNPG_GROUP)) return _getCNPGScheduledBackupStatus(resource).text
|
|
2167
|
+
if (kindLower === 'poolers' && _isApiGroup(resource.apiVersion, _CNPG_GROUP)) return _getCNPGPoolerStatus(resource).text
|
|
2101
2168
|
// Generic CRDs: try status.phase, then Ready condition
|
|
2102
2169
|
if (resource.status?.phase) return resource.status.phase
|
|
2103
2170
|
{
|
|
@@ -70,6 +70,7 @@ interface ResourceActionsBarProps {
|
|
|
70
70
|
isDeleting?: boolean
|
|
71
71
|
cascadeDependents?: CascadeDependent[]
|
|
72
72
|
cascadeLoading?: boolean
|
|
73
|
+
cascadeRootResolved?: boolean
|
|
73
74
|
|
|
74
75
|
// Workload restart
|
|
75
76
|
onRestart?: (params: { kind: string; namespace: string; name: string }, callbacks?: { onSuccess?: () => void; onError?: (err: unknown) => void }) => void
|
|
@@ -130,7 +131,7 @@ export function ResourceActionsBar({
|
|
|
130
131
|
canExec, canViewLogs, canPortForward,
|
|
131
132
|
onOpenTerminal, onOpenLogs: openLogs, onOpenWorkloadLogs: openWorkloadLogs, onCopyCommand,
|
|
132
133
|
renderPortForward,
|
|
133
|
-
onDelete, isDeleting, cascadeDependents, cascadeLoading,
|
|
134
|
+
onDelete, isDeleting, cascadeDependents, cascadeLoading, cascadeRootResolved,
|
|
134
135
|
onRestart, isRestarting,
|
|
135
136
|
revisions: revisionsList, revisionsLoading, revisionsError, onRollback, isRollingBack,
|
|
136
137
|
onTriggerCronJob, isTriggeringCronJob,
|
|
@@ -608,6 +609,7 @@ export function ResourceActionsBar({
|
|
|
608
609
|
isLoading={isDeleting ?? false}
|
|
609
610
|
cascadeDependents={cascadeDependents}
|
|
610
611
|
cascadeLoading={cascadeLoading}
|
|
612
|
+
cascadeRootResolved={cascadeRootResolved}
|
|
611
613
|
/>
|
|
612
614
|
|
|
613
615
|
{/* Node cordon confirmation */}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest'
|
|
2
2
|
import { renderToString } from 'react-dom/server'
|
|
3
|
-
import { ResourceRendererDispatch, type RendererOverrides } from './ResourceRendererDispatch'
|
|
3
|
+
import { ResourceRendererDispatch, getResourceStatus, type RendererOverrides } from './ResourceRendererDispatch'
|
|
4
4
|
import type { ResourceRef } from '../../types'
|
|
5
5
|
|
|
6
6
|
function renderWithScalers(scalers: ResourceRef[]): string {
|
|
@@ -146,3 +146,121 @@ describe('DRA renderers dispatch', () => {
|
|
|
146
146
|
expect(html).toContain('device.driver')
|
|
147
147
|
})
|
|
148
148
|
})
|
|
149
|
+
|
|
150
|
+
// ============================================================================
|
|
151
|
+
// COLLIDING CRD PLURALS — `clusters` (CNPG / CAPI / third parties) and
|
|
152
|
+
// `backups` (CNPG / Velero). Both used to be resolved with a negative guard,
|
|
153
|
+
// so any third operator's CRD inherited whichever branch was the fallback.
|
|
154
|
+
// ============================================================================
|
|
155
|
+
|
|
156
|
+
function renderCollidingKind(kind: string, apiVersion: string): string {
|
|
157
|
+
return renderToString(
|
|
158
|
+
<ResourceRendererDispatch
|
|
159
|
+
resource={{ kind, namespace: 'default', name: 'thing' }}
|
|
160
|
+
data={{ apiVersion, kind: 'Cluster', metadata: { name: 'thing', namespace: 'default' }, spec: {}, status: {} }}
|
|
161
|
+
onCopy={() => {}}
|
|
162
|
+
copied={null}
|
|
163
|
+
showCommonSections={false}
|
|
164
|
+
/>,
|
|
165
|
+
)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
describe('getResourceStatus — colliding plurals', () => {
|
|
169
|
+
it('fabricates no PostgreSQL status for a third-party clusters CRD', () => {
|
|
170
|
+
// KubeBlocks, Redis/Valkey operators and friends all ship `clusters`. With
|
|
171
|
+
// no status of their own they get nothing; with a phase they get the
|
|
172
|
+
// generic phase badge, never a CNPG-derived one.
|
|
173
|
+
expect(getResourceStatus('clusters', { apiVersion: 'apps.kubeblocks.io/v1alpha1', status: {} })).toBeNull()
|
|
174
|
+
expect(getResourceStatus('clusters', { apiVersion: 'apps.kubeblocks.io/v1alpha1', status: { phase: 'Running' } }))
|
|
175
|
+
.toMatchObject({ text: 'Running' })
|
|
176
|
+
// CNPG would have rendered "Not Ready" from the absent instance counts.
|
|
177
|
+
expect(getResourceStatus('clusters', { apiVersion: 'redis.redis.opstreelabs.in/v1beta2', spec: { instances: 3 }, status: {} })).toBeNull()
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
it('still resolves both known clusters engines positively', () => {
|
|
181
|
+
expect(getResourceStatus('clusters', {
|
|
182
|
+
apiVersion: 'postgresql.cnpg.io/v1',
|
|
183
|
+
spec: { instances: 2 },
|
|
184
|
+
status: { phase: 'Cluster in healthy state', readyInstances: 2 },
|
|
185
|
+
})).toMatchObject({ text: 'Healthy' })
|
|
186
|
+
|
|
187
|
+
expect(getResourceStatus('clusters', {
|
|
188
|
+
apiVersion: 'cluster.x-k8s.io/v1beta1',
|
|
189
|
+
status: { phase: 'Provisioned' },
|
|
190
|
+
})).not.toBeNull()
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
it('fabricates no engine status for a third-party backups CRD', () => {
|
|
194
|
+
expect(getResourceStatus('backups', { apiVersion: 'kubevirt.io/v1', status: {} })).toBeNull()
|
|
195
|
+
expect(getResourceStatus('backups', { apiVersion: 'kubevirt.io/v1', status: { phase: 'Running' } }))
|
|
196
|
+
.toMatchObject({ text: 'Running' })
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
it('still resolves both known backups engines positively', () => {
|
|
200
|
+
expect(getResourceStatus('backups', {
|
|
201
|
+
apiVersion: 'postgresql.cnpg.io/v1',
|
|
202
|
+
status: { phase: 'completed' },
|
|
203
|
+
})).toMatchObject({ text: 'Completed' })
|
|
204
|
+
|
|
205
|
+
expect(getResourceStatus('backups', {
|
|
206
|
+
apiVersion: 'velero.io/v1',
|
|
207
|
+
status: { phase: 'Completed' },
|
|
208
|
+
})).not.toBeNull()
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
it('gives no CNPG badge to third-party scheduledbackups and poolers', () => {
|
|
212
|
+
// CNPG's Pooler getter would have said "Not Scheduled" from spec.instances.
|
|
213
|
+
expect(getResourceStatus('poolers', { apiVersion: 'other.io/v1', spec: { instances: 2 }, status: {} })).toBeNull()
|
|
214
|
+
expect(getResourceStatus('scheduledbackups', { apiVersion: 'other.io/v1', spec: {}, status: {} })).toBeNull()
|
|
215
|
+
})
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
describe('ResourceRendererDispatch — colliding plurals fall through', () => {
|
|
219
|
+
// These plurals are in KNOWN_KINDS, which suppresses the generic renderer.
|
|
220
|
+
// Making every render line apiVersion-gated without an explicit fall-through
|
|
221
|
+
// renders a blank drawer for a foreign CRD — the trap the Crossplane
|
|
222
|
+
// collision block documents.
|
|
223
|
+
it.each([
|
|
224
|
+
['clusters', 'apps.kubeblocks.io/v1alpha1'],
|
|
225
|
+
['backups', 'kubevirt.io/v1'],
|
|
226
|
+
['scheduledbackups', 'other.io/v1'],
|
|
227
|
+
['poolers', 'other.io/v1'],
|
|
228
|
+
])('renders something for a foreign %s CRD', (kind, apiVersion) => {
|
|
229
|
+
expect(renderCollidingKind(kind, apiVersion).trim()).not.toBe('')
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
it('does not double-render when a known engine matches', () => {
|
|
233
|
+
const html = renderCollidingKind('clusters', 'postgresql.cnpg.io/v1')
|
|
234
|
+
expect(html).toContain('Cluster Overview')
|
|
235
|
+
// The generic renderer's raw-spec dump must not appear alongside it.
|
|
236
|
+
expect(html.match(/Cluster Overview/g)).toHaveLength(1)
|
|
237
|
+
})
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
describe('colliding plurals — near-match API groups', () => {
|
|
241
|
+
// A substring guard would hand these to CNPG/Velero. They are different groups.
|
|
242
|
+
it.each([
|
|
243
|
+
['clusters', 'extension.postgresql.cnpg.io/v1'],
|
|
244
|
+
['clusters', 'barmancloud.cnpg.io/v1'],
|
|
245
|
+
['backups', 'extension.postgresql.cnpg.io/v1'],
|
|
246
|
+
['backups', 'backup.velero.io/v1'],
|
|
247
|
+
['poolers', 'sub.postgresql.cnpg.io/v1'],
|
|
248
|
+
['scheduledbackups', 'sub.postgresql.cnpg.io/v1'],
|
|
249
|
+
])('does not give %s/%s an engine-specific status', (kind, apiVersion) => {
|
|
250
|
+
const s = getResourceStatus(kind, { apiVersion, spec: { instances: 2 }, status: { phase: 'completed' } })
|
|
251
|
+
// The generic fallback echoes the phase verbatim; the CNPG/Velero getters
|
|
252
|
+
// would have mapped it to "Completed" or read the instance counts.
|
|
253
|
+
expect(s?.text).not.toBe('Completed')
|
|
254
|
+
expect(s?.text).not.toBe('Not Scheduled')
|
|
255
|
+
expect(s?.text).not.toBe('Not Ready')
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
it.each([
|
|
259
|
+
['clusters', 'extension.postgresql.cnpg.io/v1'],
|
|
260
|
+
['backups', 'backup.velero.io/v1'],
|
|
261
|
+
])('falls %s/%s through to the generic renderer', (kind, apiVersion) => {
|
|
262
|
+
const html = renderCollidingKind(kind, apiVersion)
|
|
263
|
+
expect(html.trim()).not.toBe('')
|
|
264
|
+
expect(html).not.toContain('Cluster Overview')
|
|
265
|
+
})
|
|
266
|
+
})
|
|
@@ -51,9 +51,19 @@ import { getNodePoolStatus, getNodeClaimStatus, getEC2NodeClassStatus } from '..
|
|
|
51
51
|
import { getScaledObjectStatus, getScaledJobStatus } from '../resources/resource-utils-keda'
|
|
52
52
|
import { getServiceMonitorStatus, getPrometheusRuleStatus, getPodMonitorStatus } from '../resources/resource-utils-prometheus'
|
|
53
53
|
import { getPolicyReportStatus, getKyvernoPolicyStatus } from '../resources/resource-utils-kyverno'
|
|
54
|
+
import {
|
|
55
|
+
KYVERNO_MODERN_PLURALS,
|
|
56
|
+
getModernKyvernoPolicyStatus,
|
|
57
|
+
isModernKyvernoPolicy,
|
|
58
|
+
} from '../resources/resource-utils-kyverno-modern'
|
|
59
|
+
import {
|
|
60
|
+
getKyvernoCleanupPolicyStatus,
|
|
61
|
+
getKyvernoPolicyExceptionStatus,
|
|
62
|
+
isAnyKyvernoPolicyException,
|
|
63
|
+
} from '../resources/resource-utils-kyverno-exceptions'
|
|
54
64
|
import { getResourceClaimStatus, getResourceClaimTemplateStatus, getDeviceClassStatus, getResourceSliceStatus } from '../resources/resource-utils-dra'
|
|
55
65
|
import { getNvidiaClusterPolicyStatus, getNvidiaDriverStatus } from '../resources/resource-utils-nvidia'
|
|
56
|
-
import { getBackupStatus, getRestoreStatus, getScheduleStatus, getBSLStatus } from '../resources/resource-utils-velero'
|
|
66
|
+
import { getBackupStatus, getRestoreStatus, getScheduleStatus, getBSLStatus, isVeleroResource } from '../resources/resource-utils-velero'
|
|
57
67
|
import {
|
|
58
68
|
getVirtualServiceStatus,
|
|
59
69
|
getDestinationRuleStatus,
|
|
@@ -62,7 +72,7 @@ import {
|
|
|
62
72
|
getPeerAuthenticationStatus,
|
|
63
73
|
getAuthorizationPolicyStatus,
|
|
64
74
|
} from '../resources/resource-utils-istio'
|
|
65
|
-
import { getCNPGClusterStatus, getCNPGBackupStatus, getCNPGScheduledBackupStatus, getCNPGPoolerStatus } from '../resources/resource-utils-cnpg'
|
|
75
|
+
import { getCNPGClusterStatus, getCNPGBackupStatus, getCNPGScheduledBackupStatus, getCNPGPoolerStatus, isApiGroup, CNPG_GROUP } from '../resources/resource-utils-cnpg'
|
|
66
76
|
import { getExternalSecretStatus, getClusterExternalSecretStatus, getSecretStoreStatus, getClusterSecretStoreStatus } from '../resources/resource-utils-eso'
|
|
67
77
|
import {
|
|
68
78
|
getKnativeConditionStatus,
|
|
@@ -140,6 +150,13 @@ import {
|
|
|
140
150
|
PodMonitorRenderer,
|
|
141
151
|
PolicyReportRenderer,
|
|
142
152
|
KyvernoPolicyRenderer,
|
|
153
|
+
KyvernoValidatingPolicyRenderer,
|
|
154
|
+
KyvernoImageValidatingPolicyRenderer,
|
|
155
|
+
KyvernoMutatingPolicyRenderer,
|
|
156
|
+
KyvernoGeneratingPolicyRenderer,
|
|
157
|
+
KyvernoDeletingPolicyRenderer,
|
|
158
|
+
KyvernoPolicyExceptionRenderer,
|
|
159
|
+
KyvernoCleanupPolicyRenderer,
|
|
143
160
|
VeleroBackupRenderer,
|
|
144
161
|
VeleroRestoreRenderer,
|
|
145
162
|
VeleroScheduleRenderer,
|
|
@@ -339,6 +356,13 @@ const KNOWN_KINDS = new Set([
|
|
|
339
356
|
'triggerauthentications', 'clustertriggerauthentications',
|
|
340
357
|
'servicemonitors', 'prometheusrules', 'podmonitors',
|
|
341
358
|
'policyreports', 'clusterpolicyreports', 'kyvernopolicies', 'clusterpolicies',
|
|
359
|
+
// Kyverno modern CEL family (policies.kyverno.io) + its namespaced twins.
|
|
360
|
+
'validatingpolicies', 'namespacedvalidatingpolicies',
|
|
361
|
+
'imagevalidatingpolicies', 'namespacedimagevalidatingpolicies',
|
|
362
|
+
'mutatingpolicies', 'namespacedmutatingpolicies',
|
|
363
|
+
'generatingpolicies', 'namespacedgeneratingpolicies',
|
|
364
|
+
'deletingpolicies', 'namespaceddeletingpolicies',
|
|
365
|
+
'policyexceptions', 'cleanuppolicies', 'clustercleanuppolicies',
|
|
342
366
|
'resourceclaims', 'resourceclaimtemplates', 'deviceclasses', 'resourceslices',
|
|
343
367
|
'nvidiadrivers',
|
|
344
368
|
'vulnerabilityreports', 'configauditreports', 'exposedsecretreports',
|
|
@@ -488,6 +512,54 @@ export function ResourceRendererDispatch({
|
|
|
488
512
|
)
|
|
489
513
|
const crossplaneCollisionFallthrough = isCollisionGatedKind && !crossplaneApiVersionMatched
|
|
490
514
|
|
|
515
|
+
// Kyverno's modern CEL family uses plurals generic enough that another
|
|
516
|
+
// vendor could ship the same ones (`validatingpolicies`, `mutatingpolicies`,
|
|
517
|
+
// `generatingpolicies`, ...), and `policyexceptions` collides with Kyverno
|
|
518
|
+
// ITSELF — the same Kind and plural exists in both kyverno.io and
|
|
519
|
+
// policies.kyverno.io with different spec shapes. Every render line below is
|
|
520
|
+
// therefore group-gated, which means each needs the same fall-through the
|
|
521
|
+
// Crossplane block documents above: without it a foreign CR with a colliding
|
|
522
|
+
// plural matches no renderer and, being in KNOWN_KINDS, renders blank.
|
|
523
|
+
const isKyvernoModernPlural = KYVERNO_MODERN_PLURALS.has(kind)
|
|
524
|
+
const kyvernoModernMatched = isKyvernoModernPlural && isModernKyvernoPolicy(data)
|
|
525
|
+
const isKyvernoLegacyExtraPlural = kind === 'cleanuppolicies' || kind === 'clustercleanuppolicies'
|
|
526
|
+
const kyvernoLegacyExtraMatched =
|
|
527
|
+
isKyvernoLegacyExtraPlural && data?.apiVersion?.startsWith('kyverno.io/')
|
|
528
|
+
// PolicyException is served by both families; either group is a match.
|
|
529
|
+
const isPolicyExceptionPlural = kind === 'policyexceptions'
|
|
530
|
+
const policyExceptionMatched = isPolicyExceptionPlural && isAnyKyvernoPolicyException(data)
|
|
531
|
+
const kyvernoCollisionFallthrough =
|
|
532
|
+
(isKyvernoModernPlural && !kyvernoModernMatched) ||
|
|
533
|
+
(isKyvernoLegacyExtraPlural && !kyvernoLegacyExtraMatched) ||
|
|
534
|
+
(isPolicyExceptionPlural && !policyExceptionMatched)
|
|
535
|
+
|
|
536
|
+
// Same shape as the Crossplane fall-through above: these Velero plurals are
|
|
537
|
+
// in KNOWN_KINDS but their render lines are gated on the velero.io group, so
|
|
538
|
+
// a foreign CR with the same plural (rancher/backup-restore-operator ships
|
|
539
|
+
// restores.resources.cattle.io; several operators ship `schedules`) matches
|
|
540
|
+
// no renderer AND is suppressed from GenericRenderer — i.e. renders blank.
|
|
541
|
+
// `backups` is deliberately absent here — it is shared with CNPG, so both of
|
|
542
|
+
// its render lines are positively group-gated and it falls through via
|
|
543
|
+
// isGroupGatedKind below rather than through this Velero-only check.
|
|
544
|
+
const isVeleroCollisionGatedKind =
|
|
545
|
+
kind === 'restores' || kind === 'schedules'
|
|
546
|
+
|| kind === 'backupstoragelocations' || kind === 'volumesnapshotlocations'
|
|
547
|
+
const veleroCollisionFallthrough = isVeleroCollisionGatedKind && !isVeleroResource(data)
|
|
548
|
+
|
|
549
|
+
// Same rule as the Crossplane block above, for the plurals shared by two
|
|
550
|
+
// named operators: `clusters` (CNPG / CAPI — plus KubeBlocks, Redis/Valkey
|
|
551
|
+
// and friends in the wild) and `backups` (CNPG / Velero). Both render lines
|
|
552
|
+
// are positively apiVersion-gated, so a third CRD with the plural matches
|
|
553
|
+
// neither and needs an explicit fall-through or the drawer renders blank.
|
|
554
|
+
const isGroupGatedKind =
|
|
555
|
+
kind === 'clusters' || kind === 'backups' || kind === 'scheduledbackups' || kind === 'poolers'
|
|
556
|
+
const isCNPGApiVersion = isApiGroup(data?.apiVersion, CNPG_GROUP)
|
|
557
|
+
const groupGatedMatched =
|
|
558
|
+
(kind === 'clusters' && (isCNPGApiVersion || isApiGroup(data?.apiVersion, 'cluster.x-k8s.io')))
|
|
559
|
+
|| (kind === 'backups' && (isCNPGApiVersion || isApiGroup(data?.apiVersion, 'velero.io')))
|
|
560
|
+
|| ((kind === 'scheduledbackups' || kind === 'poolers') && isCNPGApiVersion)
|
|
561
|
+
const groupGatedFallthrough = isGroupGatedKind && !groupGatedMatched
|
|
562
|
+
|
|
491
563
|
const isKnownKind = KNOWN_KINDS.has(kind) || isCrossplaneMR || isCrossplaneClaim || isCrossplaneXR
|
|
492
564
|
|
|
493
565
|
const PodComp = rendererOverrides?.PodRenderer ?? PodRenderer
|
|
@@ -594,25 +666,34 @@ export function ResourceRendererDispatch({
|
|
|
594
666
|
{(kind === 'policyreports' || kind === 'clusterpolicyreports') && <PolicyReportRenderer data={data} />}
|
|
595
667
|
{(kind === 'kyvernopolicies' || (kind === 'clusterpolicies' && !data?.apiVersion?.startsWith('nvidia.com/'))) && <KyvernoPolicyRenderer data={data} />}
|
|
596
668
|
{(kind === 'clusterpolicies' && data?.apiVersion?.startsWith('nvidia.com/')) && <NvidiaClusterPolicyRenderer data={data} />}
|
|
669
|
+
{/* Kyverno modern CEL family. Each Namespaced* twin shares its
|
|
670
|
+
cluster-scoped counterpart's renderer — same spec, narrower scope. */}
|
|
671
|
+
{kyvernoModernMatched && (kind === 'validatingpolicies' || kind === 'namespacedvalidatingpolicies') && <KyvernoValidatingPolicyRenderer data={data} />}
|
|
672
|
+
{kyvernoModernMatched && (kind === 'imagevalidatingpolicies' || kind === 'namespacedimagevalidatingpolicies') && <KyvernoImageValidatingPolicyRenderer data={data} />}
|
|
673
|
+
{kyvernoModernMatched && (kind === 'mutatingpolicies' || kind === 'namespacedmutatingpolicies') && <KyvernoMutatingPolicyRenderer data={data} />}
|
|
674
|
+
{kyvernoModernMatched && (kind === 'generatingpolicies' || kind === 'namespacedgeneratingpolicies') && <KyvernoGeneratingPolicyRenderer data={data} />}
|
|
675
|
+
{kyvernoModernMatched && (kind === 'deletingpolicies' || kind === 'namespaceddeletingpolicies') && <KyvernoDeletingPolicyRenderer data={data} />}
|
|
676
|
+
{policyExceptionMatched && <KyvernoPolicyExceptionRenderer data={data} />}
|
|
677
|
+
{kyvernoLegacyExtraMatched && <KyvernoCleanupPolicyRenderer data={data} />}
|
|
597
678
|
{kind === 'nvidiadrivers' && <NvidiaDriverRenderer data={data} />}
|
|
598
679
|
{/* DRA (resource.k8s.io) */}
|
|
599
680
|
{kind === 'resourceclaims' && <ResourceClaimRenderer data={data} onNavigate={onNavigate} />}
|
|
600
681
|
{kind === 'resourceclaimtemplates' && <ResourceClaimTemplateRenderer data={data} />}
|
|
601
682
|
{kind === 'deviceclasses' && <DeviceClassRenderer data={data} />}
|
|
602
683
|
{kind === 'resourceslices' && <ResourceSliceRenderer data={data} onNavigate={onNavigate} />}
|
|
603
|
-
{kind === 'backups' && data.apiVersion
|
|
604
|
-
{kind === 'backups' &&
|
|
605
|
-
{kind === 'restores' && <VeleroRestoreRenderer data={data} />}
|
|
606
|
-
{kind === 'schedules' && <VeleroScheduleRenderer data={data} />}
|
|
607
|
-
{kind === 'backupstoragelocations' && <VeleroBSLRenderer data={data} />}
|
|
608
|
-
{kind === 'volumesnapshotlocations' && <VeleroVSLRenderer data={data} />}
|
|
684
|
+
{kind === 'backups' && isApiGroup(data.apiVersion, CNPG_GROUP) && <CNPGBackupRenderer data={data} onNavigate={onNavigate} />}
|
|
685
|
+
{kind === 'backups' && isApiGroup(data.apiVersion, 'velero.io') && <VeleroBackupRenderer data={data} />}
|
|
686
|
+
{kind === 'restores' && isVeleroResource(data) && <VeleroRestoreRenderer data={data} />}
|
|
687
|
+
{kind === 'schedules' && isVeleroResource(data) && <VeleroScheduleRenderer data={data} />}
|
|
688
|
+
{kind === 'backupstoragelocations' && isVeleroResource(data) && <VeleroBSLRenderer data={data} />}
|
|
689
|
+
{kind === 'volumesnapshotlocations' && isVeleroResource(data) && <VeleroVSLRenderer data={data} />}
|
|
609
690
|
{kind === 'externalsecrets' && <ExternalSecretRenderer data={data} onNavigate={onNavigate} />}
|
|
610
691
|
{kind === 'clusterexternalsecrets' && <ClusterExternalSecretRenderer data={data} onNavigate={onNavigate} />}
|
|
611
692
|
{(kind === 'secretstores' || kind === 'clustersecretstores') && <SecretStoreRenderer data={data} />}
|
|
612
|
-
{kind === 'clusters' &&
|
|
613
|
-
{kind === 'clusters' && data?.apiVersion
|
|
614
|
-
{kind === 'scheduledbackups' && <CNPGScheduledBackupRenderer data={data} onNavigate={onNavigate} />}
|
|
615
|
-
{kind === 'poolers' && <CNPGPoolerRenderer data={data} onNavigate={onNavigate} />}
|
|
693
|
+
{kind === 'clusters' && isApiGroup(data?.apiVersion, CNPG_GROUP) && <CNPGClusterRenderer data={data} onNavigate={onNavigate} />}
|
|
694
|
+
{kind === 'clusters' && isApiGroup(data?.apiVersion, 'cluster.x-k8s.io') && <CAPIClusterRenderer data={data} onNavigate={onNavigate} />}
|
|
695
|
+
{kind === 'scheduledbackups' && isApiGroup(data?.apiVersion, CNPG_GROUP) && <CNPGScheduledBackupRenderer data={data} onNavigate={onNavigate} />}
|
|
696
|
+
{kind === 'poolers' && isApiGroup(data?.apiVersion, CNPG_GROUP) && <CNPGPoolerRenderer data={data} onNavigate={onNavigate} />}
|
|
616
697
|
{/* Cluster API (CAPI) */}
|
|
617
698
|
{'topology.cluster.x-k8s.io/owned' in (data?.metadata?.labels ?? {}) && data?.apiVersion?.includes('cluster.x-k8s.io') && (
|
|
618
699
|
<AlertBanner
|
|
@@ -706,7 +787,7 @@ export function ResourceRendererDispatch({
|
|
|
706
787
|
for known-plural collisions where no apiVersion-gated renderer
|
|
707
788
|
matched (e.g. a Knative Configuration sharing the `configurations`
|
|
708
789
|
plural with Crossplane Configuration). */}
|
|
709
|
-
{(!isKnownKind || crossplaneCollisionFallthrough) && <GenericRenderer data={data} />}
|
|
790
|
+
{(!isKnownKind || crossplaneCollisionFallthrough || kyvernoCollisionFallthrough || veleroCollisionFallthrough || groupGatedFallthrough) && <GenericRenderer data={data} />}
|
|
710
791
|
|
|
711
792
|
{/* Common sections - can be disabled when parent handles them separately */}
|
|
712
793
|
{showCommonSections && (
|
|
@@ -833,25 +914,38 @@ export function getResourceStatus(kind: string, data: any): { text: string; colo
|
|
|
833
914
|
if (data?.apiVersion?.startsWith('nvidia.com/')) return getNvidiaClusterPolicyStatus(data)
|
|
834
915
|
return getKyvernoPolicyStatus(data)
|
|
835
916
|
}
|
|
917
|
+
// Modern CEL family — group-gated so a foreign CRD sharing one of these
|
|
918
|
+
// generic plurals falls through to the default status rather than being
|
|
919
|
+
// described in Kyverno's vocabulary.
|
|
920
|
+
if (KYVERNO_MODERN_PLURALS.has(k) && isModernKyvernoPolicy(data)) return getModernKyvernoPolicyStatus(data)
|
|
921
|
+
if (k === 'policyexceptions' && isAnyKyvernoPolicyException(data)) return getKyvernoPolicyExceptionStatus(data)
|
|
922
|
+
if ((k === 'cleanuppolicies' || k === 'clustercleanuppolicies') && data?.apiVersion?.startsWith('kyverno.io/')) {
|
|
923
|
+
return getKyvernoCleanupPolicyStatus(data)
|
|
924
|
+
}
|
|
836
925
|
if (k === 'nvidiadrivers') return getNvidiaDriverStatus(data)
|
|
837
926
|
if (k === 'resourceclaims') return getResourceClaimStatus(data)
|
|
838
927
|
if (k === 'resourceclaimtemplates') return getResourceClaimTemplateStatus(data)
|
|
839
928
|
if (k === 'deviceclasses') return getDeviceClassStatus(data)
|
|
840
929
|
if (k === 'resourceslices') return getResourceSliceStatus(data)
|
|
930
|
+
// Positive guards both ways. A third `backups` CRD matches neither and falls
|
|
931
|
+
// through to the generic phase/conditions handling at the end of this
|
|
932
|
+
// function, rather than borrowing whichever engine used to be the fallback.
|
|
841
933
|
if (k === 'backups') {
|
|
842
|
-
if (data.apiVersion
|
|
843
|
-
return getBackupStatus(data)
|
|
934
|
+
if (isApiGroup(data.apiVersion, CNPG_GROUP)) return getCNPGBackupStatus(data)
|
|
935
|
+
if (isApiGroup(data.apiVersion, 'velero.io')) return getBackupStatus(data)
|
|
844
936
|
}
|
|
845
|
-
if (k === 'restores') return getRestoreStatus(data)
|
|
846
|
-
if (k === 'schedules') return getScheduleStatus(data)
|
|
847
|
-
if (k === 'backupstoragelocations') return getBSLStatus(data)
|
|
937
|
+
if (k === 'restores' && isVeleroResource(data)) return getRestoreStatus(data)
|
|
938
|
+
if (k === 'schedules' && isVeleroResource(data)) return getScheduleStatus(data)
|
|
939
|
+
if (k === 'backupstoragelocations' && isVeleroResource(data)) return getBSLStatus(data)
|
|
848
940
|
if (k === 'externalsecrets') return getExternalSecretStatus(data)
|
|
849
941
|
if (k === 'clusterexternalsecrets') return getClusterExternalSecretStatus(data)
|
|
850
942
|
if (k === 'secretstores') return getSecretStoreStatus(data)
|
|
851
943
|
if (k === 'clustersecretstores') return getClusterSecretStoreStatus(data)
|
|
852
944
|
if (k === 'clusters') {
|
|
853
|
-
if (data.apiVersion
|
|
854
|
-
return getCNPGClusterStatus(data)
|
|
945
|
+
if (isApiGroup(data.apiVersion, 'cluster.x-k8s.io')) return getCAPIClusterStatus(data)
|
|
946
|
+
if (isApiGroup(data.apiVersion, CNPG_GROUP)) return getCNPGClusterStatus(data)
|
|
947
|
+
// Third-party `clusters` CRDs (KubeBlocks, Redis/Valkey) fall through to the
|
|
948
|
+
// generic handling below instead of getting a fabricated PostgreSQL status.
|
|
855
949
|
}
|
|
856
950
|
if (k === 'machines' && data.apiVersion?.includes('cluster.x-k8s.io')) return getMachineStatus(data)
|
|
857
951
|
if (k === 'machinedeployments') return getMachineDeploymentStatus(data)
|
|
@@ -875,8 +969,8 @@ export function getResourceStatus(kind: string, data: any): { text: string; colo
|
|
|
875
969
|
if (k === 'azuremanagedmachinepools') return getAzureMMPStatus(data)
|
|
876
970
|
if (k === 'azuremachines') return getAzureMachineStatus(data)
|
|
877
971
|
if (k === 'azuremanagedclusters') return getAzureManagedClusterStatus(data)
|
|
878
|
-
if (k === 'scheduledbackups') return getCNPGScheduledBackupStatus(data)
|
|
879
|
-
if (k === 'poolers') return getCNPGPoolerStatus(data)
|
|
972
|
+
if (k === 'scheduledbackups' && isApiGroup(data.apiVersion, CNPG_GROUP)) return getCNPGScheduledBackupStatus(data)
|
|
973
|
+
if (k === 'poolers' && isApiGroup(data.apiVersion, CNPG_GROUP)) return getCNPGPoolerStatus(data)
|
|
880
974
|
if (k === 'virtualservices') return getVirtualServiceStatus(data)
|
|
881
975
|
if (k === 'destinationrules') return getDestinationRuleStatus(data)
|
|
882
976
|
if (k === 'serviceentries') return getServiceEntryStatus(data)
|
|
@@ -128,6 +128,12 @@ function getIssueTooltip(issue: string | undefined): React.ReactNode {
|
|
|
128
128
|
"A validating/mutating admission webhook rejected pod creation.",
|
|
129
129
|
action: "Check the webhook policy that denied the request.",
|
|
130
130
|
},
|
|
131
|
+
WebhookUnavailable: {
|
|
132
|
+
title: "Admission Webhook Unavailable",
|
|
133
|
+
description:
|
|
134
|
+
"Pod creation could not reach a required admission webhook backend.",
|
|
135
|
+
action: "Restore the webhook Service and its ready endpoints.",
|
|
136
|
+
},
|
|
131
137
|
Evicted: {
|
|
132
138
|
title: "Pod Evicted",
|
|
133
139
|
description:
|
|
@@ -282,6 +288,11 @@ function getSubtitle(
|
|
|
282
288
|
kind: NodeKind,
|
|
283
289
|
nodeData: Record<string, unknown>,
|
|
284
290
|
): string {
|
|
291
|
+
// An explicit override wins over the per-kind computed subtitle: the Reachability
|
|
292
|
+
// diagram precomputes a functional one-liner (port→targetPort, readiness) the
|
|
293
|
+
// generic per-kind logic can't know. '' is a deliberate "no subtitle", honored as
|
|
294
|
+
// such. App-topology nodes never set this, so their behavior is unchanged.
|
|
295
|
+
if (typeof nodeData.subtitleOverride === 'string') return nodeData.subtitleOverride;
|
|
285
296
|
const base = baseSubtitle(kind, nodeData);
|
|
286
297
|
const ps = nodeData.podSummary as PodSummary | undefined;
|
|
287
298
|
if (ps && SUMMARY_POD_KINDS.has(kind)) {
|