@skyhook-io/k8s-ui 1.16.3 → 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 +41 -0
- package/package.json +3 -3
- package/src/components/applications/ApplicationDetail.test.tsx +15 -0
- package/src/components/applications/ApplicationDetail.tsx +17 -15
- package/src/components/charts/AreaChart.interaction.test.tsx +118 -0
- package/src/components/charts/AreaChart.test.tsx +25 -0
- package/src/components/charts/AreaChart.tsx +141 -12
- package/src/components/charts/index.ts +1 -0
- package/src/components/checks/ChecksView.tsx +18 -1
- package/src/components/checks/types.ts +2 -13
- package/src/components/cnpg/CNPGBackupSummary.tsx +57 -9
- package/src/components/cnpg/CNPGClusterHASection.test.tsx +90 -0
- package/src/components/cnpg/CNPGClusterHASection.tsx +331 -0
- package/src/components/cnpg/CNPGClusterSummary.test.tsx +275 -0
- package/src/components/cnpg/CNPGClusterSummary.tsx +310 -96
- package/src/components/cnpg/CNPGConnectSection.test.tsx +64 -0
- package/src/components/cnpg/CNPGConnectSection.tsx +178 -0
- package/src/components/cnpg/CNPGDeclarativeSummary.tsx +157 -26
- package/src/components/cnpg/CNPGImageCatalogSummary.tsx +7 -4
- package/src/components/cnpg/CNPGLogicalPath.test.tsx +22 -0
- package/src/components/cnpg/CNPGLogicalPath.tsx +150 -0
- package/src/components/cnpg/CNPGObjectStoreSummary.tsx +9 -6
- package/src/components/cnpg/CNPGObjectSummary.test.tsx +193 -2
- package/src/components/cnpg/CNPGPoolerScheduling.test.tsx +36 -0
- package/src/components/cnpg/CNPGPoolerSummary.tsx +241 -14
- package/src/components/cnpg/CNPGSharedSummary.tsx +15 -8
- package/src/components/cnpg/CNPGWALArchivingFact.test.tsx +39 -0
- package/src/components/cnpg/backupRuns.test.ts +17 -0
- package/src/components/cnpg/backupRuns.ts +19 -0
- package/src/components/cnpg/connect.test.ts +124 -0
- package/src/components/cnpg/connect.ts +155 -0
- package/src/components/cnpg/databaseRole.test.ts +38 -0
- package/src/components/cnpg/databaseRole.ts +80 -0
- package/src/components/cnpg/ha.test.ts +446 -0
- package/src/components/cnpg/ha.ts +621 -0
- package/src/components/cnpg/index.ts +18 -0
- package/src/components/cnpg/logicalReplication.test.ts +121 -0
- package/src/components/cnpg/logicalReplication.ts +252 -0
- package/src/components/cnpg/pooler.test.ts +84 -0
- package/src/components/cnpg/pooler.ts +225 -0
- package/src/components/cnpg/primitives.tsx +21 -117
- package/src/components/cnpg/problems.ts +12 -0
- package/src/components/cnpg/relations.test.ts +63 -13
- package/src/components/cnpg/relations.ts +61 -52
- package/src/components/cnpg/schedule.ts +35 -0
- package/src/components/cnpg/workspace-disk.test.ts +97 -0
- package/src/components/cnpg/workspace-fleet-metrics.test.ts +250 -0
- package/src/components/cnpg/workspace-problems.test.ts +175 -0
- package/src/components/cnpg/workspace.test.ts +380 -9
- package/src/components/cnpg/workspace.ts +1137 -136
- package/src/components/dock/DockContext.test.tsx +32 -0
- package/src/components/dock/DockContext.tsx +18 -4
- package/src/components/dock/LocalTerminalTab.test.tsx +40 -0
- package/src/components/dock/LocalTerminalTab.tsx +4 -3
- package/src/components/dock/TerminalTab.tsx +20 -8
- package/src/components/facts/certainty.tsx +36 -0
- package/src/components/facts/facts.test.tsx +24 -0
- package/src/components/facts/facts.tsx +62 -0
- package/src/components/facts/index.ts +6 -0
- package/src/components/facts/managed-by.test.tsx +17 -0
- package/src/components/facts/managed-by.tsx +36 -0
- package/src/components/facts/public-exports.test.tsx +35 -0
- package/src/components/gitops/GitOpsGraphFilterRail.tsx +6 -2
- package/src/components/gitops/GitOpsHealthSourceNotice.test.tsx +38 -0
- package/src/components/gitops/GitOpsHealthSourceNotice.tsx +13 -3
- package/src/components/gitops/GitOpsTableView.tsx +9 -24
- package/src/components/gitops/detail-helpers.ts +1 -1
- package/src/components/gitops/insights/ArgoResourceDiff.test.tsx +115 -0
- package/src/components/gitops/insights/ArgoResourceDiff.tsx +22 -5
- package/src/components/gitops/insights/DriftPanel.tsx +62 -0
- package/src/components/gitops/insights/GitOpsInsightViews.tsx +29 -83
- package/src/components/gitops/tree/GitOpsTreeGraph.test.ts +26 -1
- package/src/components/gitops/tree/GitOpsTreeGraph.tsx +40 -60
- package/src/components/gitops/tree/layout.test.ts +111 -0
- package/src/components/gitops/tree/layout.ts +128 -0
- package/src/components/gitops/tree/tree-helpers.test.ts +17 -0
- package/src/components/gitops/tree/tree-helpers.ts +7 -1
- package/src/components/issues/ConditionalCleanup.test.tsx +82 -0
- package/src/components/issues/IssuesView.tsx +40 -5
- package/src/components/issues/ResourceIssuesSection.test.tsx +28 -0
- package/src/components/issues/ResourceIssuesSection.tsx +3 -0
- package/src/components/issues/index.ts +3 -1
- package/src/components/issues/issue-timing.ts +14 -3
- package/src/components/issues/issues.test.ts +50 -2
- package/src/components/issues/severity.ts +21 -0
- package/src/components/issues/types.ts +23 -0
- package/src/components/logs/LogCore.theme.test.tsx +81 -3
- package/src/components/logs/LogCore.tsx +25 -17
- package/src/components/logs/LogToolbarSelects.tsx +3 -1
- package/src/components/logs/LogsViewer.test.tsx +291 -0
- package/src/components/logs/LogsViewer.tsx +72 -11
- package/src/components/logs/StructuredLogLine.test.tsx +76 -0
- package/src/components/logs/StructuredLogLine.tsx +22 -30
- package/src/components/logs/WorkloadLogsViewer.notice.test.tsx +59 -0
- package/src/components/logs/WorkloadLogsViewer.test.tsx +65 -0
- package/src/components/logs/WorkloadLogsViewer.tsx +44 -10
- package/src/components/logs/useLogBuffer.test.ts +8 -0
- package/src/components/logs/useLogStream.ts +28 -1
- package/src/components/problems/index.ts +1 -0
- package/src/components/problems/problems.test.tsx +63 -0
- package/src/components/problems/problems.tsx +165 -0
- package/src/components/resources/ActiveFilterBar.test.tsx +78 -0
- package/src/components/resources/ActiveFilterBar.tsx +86 -0
- package/src/components/resources/ResourcesSidebar.keyboard.test.tsx +42 -0
- package/src/components/resources/ResourcesSidebar.test.tsx +36 -2
- package/src/components/resources/ResourcesSidebar.tsx +92 -41
- package/src/components/resources/ResourcesView.kind-filter-memory.test.ts +52 -0
- package/src/components/resources/ResourcesView.kind-view.test.tsx +149 -0
- package/src/components/resources/ResourcesView.per-container.test.ts +56 -1
- package/src/components/resources/ResourcesView.tsx +309 -117
- package/src/components/resources/curated-column-ownership.test.ts +3 -3
- package/src/components/resources/renderers/AnalysisRunRenderer.tsx +3 -3
- package/src/components/resources/renderers/ArgoApplicationRenderer.tsx +6 -5
- package/src/components/resources/renderers/AutoscalingReferences.test.tsx +286 -0
- package/src/components/resources/renderers/CAPIClusterRenderer.tsx +5 -4
- package/src/components/resources/renderers/CAPIExactReferenceNavigation.test.tsx +27 -0
- package/src/components/resources/renderers/CAPIKubeadmControlPlaneRenderer.tsx +2 -2
- package/src/components/resources/renderers/CAPIMachineDeploymentRenderer.tsx +2 -2
- package/src/components/resources/renderers/CAPIMachinePoolRenderer.tsx +2 -2
- package/src/components/resources/renderers/CAPIMachineRenderer.tsx +2 -2
- package/src/components/resources/renderers/CAPIMachineSetRenderer.tsx +2 -2
- package/src/components/resources/renderers/CNPGBackupRenderer.test.tsx +23 -0
- package/src/components/resources/renderers/CNPGBackupRenderer.tsx +8 -19
- package/src/components/resources/renderers/CNPGClusterRenderer.tsx +7 -8
- package/src/components/resources/renderers/CNPGDeclarativeRenderer.tsx +25 -0
- package/src/components/resources/renderers/CNPGScheduledBackupRenderer.test.tsx +32 -0
- package/src/components/resources/renderers/CNPGScheduledBackupRenderer.tsx +9 -17
- package/src/components/resources/renderers/CNPGShared.tsx +2 -2
- package/src/components/resources/renderers/ClusterComplianceReportRenderer.tsx +2 -2
- package/src/components/resources/renderers/CompositeRenderer.navigation.test.tsx +35 -0
- package/src/components/resources/renderers/CompositeRenderer.tsx +3 -3
- package/src/components/resources/renderers/CompositionRenderer.tsx +3 -3
- package/src/components/resources/renderers/ConfigAuditReportRenderer.tsx +2 -2
- package/src/components/resources/renderers/ContourExactReferenceNavigation.test.tsx +34 -0
- package/src/components/resources/renderers/ContourHTTPProxyRenderer.tsx +11 -10
- package/src/components/resources/renderers/CronJobRenderer.tsx +6 -4
- package/src/components/resources/renderers/CronWorkflowRenderer.tsx +3 -2
- package/src/components/resources/renderers/EndpointSliceRenderer.test.tsx +62 -0
- package/src/components/resources/renderers/EndpointSliceRenderer.tsx +15 -14
- package/src/components/resources/renderers/EventRenderer.tsx +3 -3
- package/src/components/resources/renderers/ExperimentRenderer.tsx +2 -2
- package/src/components/resources/renderers/ExposedSecretReportRenderer.tsx +2 -2
- package/src/components/resources/renderers/ExternalSecretRenderer.tsx +2 -2
- package/src/components/resources/renderers/FluxHelmReleaseRenderer.tsx +4 -4
- package/src/components/resources/renderers/GRPCRouteRenderer.tsx +11 -24
- package/src/components/resources/renderers/GatewayRouteReferences.test.tsx +63 -0
- package/src/components/resources/renderers/GitRepositoryRenderer.tsx +3 -3
- package/src/components/resources/renderers/HPARenderer.tsx +57 -17
- package/src/components/resources/renderers/HTTPRouteRenderer.tsx +12 -25
- package/src/components/resources/renderers/HelmRepositoryRenderer.tsx +3 -3
- package/src/components/resources/renderers/IngressClassReferences.test.tsx +52 -0
- package/src/components/resources/renderers/IngressClassRenderer.tsx +10 -3
- package/src/components/resources/renderers/IngressRenderer.defaultBackend.test.tsx +33 -0
- package/src/components/resources/renderers/IngressRenderer.tsx +36 -5
- package/src/components/resources/renderers/IstioDestinationRuleRenderer.tsx +9 -9
- package/src/components/resources/renderers/IstioExactReferenceNavigation.test.tsx +34 -0
- package/src/components/resources/renderers/IstioVirtualServiceRenderer.tsx +5 -8
- package/src/components/resources/renderers/KarpenterNodeClaimRenderer.tsx +2 -14
- package/src/components/resources/renderers/KedaCredentials.test.tsx +85 -0
- package/src/components/resources/renderers/KedaScaledObjectRenderer.tsx +15 -13
- package/src/components/resources/renderers/KedaTriggerAuthRenderer.tsx +6 -3
- package/src/components/resources/renderers/KnativeEventingRenderer.tsx +24 -31
- package/src/components/resources/renderers/KnativeExactReferenceNavigation.test.tsx +47 -0
- package/src/components/resources/renderers/KnativeFlowRenderer.tsx +6 -7
- package/src/components/resources/renderers/KnativeNetworkingRenderer.tsx +7 -9
- package/src/components/resources/renderers/KnativeReferenceLink.tsx +14 -0
- package/src/components/resources/renderers/KnativeReferenceLinks.test.tsx +60 -0
- package/src/components/resources/renderers/KnativeSourceRenderer.tsx +9 -11
- package/src/components/resources/renderers/KustomizationRenderer.tsx +2 -2
- package/src/components/resources/renderers/KyvernoGlobalContextRenderer.tsx +3 -2
- package/src/components/resources/renderers/KyvernoQueueRenderers.tsx +4 -3
- package/src/components/resources/renderers/LeaseRenderer.tsx +3 -15
- package/src/components/resources/renderers/ManagedResourceRenderer.tsx +2 -2
- package/src/components/resources/renderers/MetricsUnavailableNotice.tsx +3 -2
- package/src/components/resources/renderers/NamespaceRenderer.test.ts +69 -2
- package/src/components/resources/renderers/NamespaceRenderer.tsx +6 -1
- package/src/components/resources/renderers/NodeRenderer.test.tsx +81 -1
- package/src/components/resources/renderers/NodeRenderer.tsx +56 -15
- package/src/components/resources/renderers/OCIRepositoryRenderer.tsx +3 -3
- package/src/components/resources/renderers/PodClassReferences.test.tsx +42 -0
- package/src/components/resources/renderers/PodNomination.test.tsx +57 -0
- package/src/components/resources/renderers/PodRenderer.test.tsx +32 -0
- package/src/components/resources/renderers/PodRenderer.tsx +62 -36
- package/src/components/resources/renderers/RolloutRenderer.test.tsx +1 -1
- package/src/components/resources/renderers/RolloutRenderer.tsx +3 -2
- package/src/components/resources/renderers/SbomReportRenderer.tsx +2 -2
- package/src/components/resources/renderers/SecretRenderer.test.tsx +88 -0
- package/src/components/resources/renderers/SecretRenderer.tsx +34 -5
- package/src/components/resources/renderers/ServiceRenderer.test.tsx +43 -0
- package/src/components/resources/renderers/ServiceRenderer.tsx +11 -3
- package/src/components/resources/renderers/SimpleRouteRenderer.tsx +9 -22
- package/src/components/resources/renderers/VPARenderer.tsx +17 -11
- package/src/components/resources/renderers/VeleroBSLRenderer.tsx +5 -7
- package/src/components/resources/renderers/VeleroBackupRenderer.tsx +3 -3
- package/src/components/resources/renderers/VeleroBackupRepositoryRenderer.tsx +2 -2
- package/src/components/resources/renderers/VeleroRestoreRenderer.tsx +3 -3
- package/src/components/resources/renderers/VeleroScheduleRenderer.tsx +2 -2
- package/src/components/resources/renderers/VulnerabilityReportRenderer.tsx +2 -2
- package/src/components/resources/renderers/WorkflowRenderer.tsx +4 -3
- package/src/components/resources/renderers/kyverno-modern-cells.tsx +1 -1
- package/src/components/resources/renderers/rollout/ReplicaSetProgression.tsx +2 -2
- package/src/components/resources/resource-utils-capi.ts +1 -1
- package/src/components/resources/resource-utils-cnpg.test.ts +35 -10
- package/src/components/resources/resource-utils-cnpg.ts +51 -24
- package/src/components/resources/resource-utils-contour.test.ts +44 -0
- package/src/components/resources/resource-utils-contour.ts +16 -0
- package/src/components/resources/resource-utils-istio.ts +12 -8
- package/src/components/resources/resource-utils-keda.ts +1 -7
- package/src/components/resources/resource-utils-knative.ts +18 -0
- package/src/components/resources/resource-utils-kyverno.ts +1 -7
- package/src/components/resources/resource-utils-velero.ts +0 -12
- package/src/components/resources/resource-utils.ts +99 -39
- package/src/components/resources/rules-column-width.test.ts +1 -1
- package/src/components/resources/summarize-scheduler-message.test.ts +30 -0
- package/src/components/shared/ActionConfirmDialog.test.tsx +154 -0
- package/src/components/shared/ActionConfirmDialog.tsx +246 -0
- package/src/components/shared/CreateResourceDialog.test.tsx +330 -0
- package/src/components/shared/CreateResourceDialog.tsx +126 -80
- package/src/components/shared/DetailShell.test.tsx +80 -0
- package/src/components/shared/DetailShell.tsx +85 -10
- package/src/components/shared/DrainPlanDialog.tsx +1 -0
- package/src/components/shared/EditableYamlView.test.tsx +7 -0
- package/src/components/shared/EditableYamlView.tsx +1 -1
- package/src/components/shared/GitOpsWriteWarning.test.tsx +86 -0
- package/src/components/shared/GitOpsWriteWarning.tsx +135 -0
- package/src/components/shared/ResourceActionsBar.tsx +20 -24
- package/src/components/shared/ResourceRendererDispatch.tsx +13 -10
- package/src/components/shared/SetImageDialog.test.ts +75 -6
- package/src/components/shared/SetImageDialog.tsx +92 -84
- package/src/components/shared/index.ts +4 -1
- package/src/components/timeline/TimelineList.tsx +2 -15
- package/src/components/timeline/TimelineSwimlanes.tsx +3 -11
- package/src/components/timeline/timeline-filters.ts +1 -0
- package/src/components/topology/K8sResourceNode.test.ts +18 -1
- package/src/components/topology/K8sResourceNode.tsx +44 -2
- package/src/components/topology/TopologyFilterSidebar.tsx +1 -0
- package/src/components/topology/TopologyGraph.tsx +8 -74
- package/src/components/topology/TopologySearch.tsx +1 -0
- package/src/components/topology/internet-groups.test.ts +35 -0
- package/src/components/topology/internet-groups.ts +55 -0
- package/src/components/topology/layout.ts +1 -1
- package/src/components/ui/Badge.tsx +1 -0
- package/src/components/ui/FoldSection.tsx +68 -0
- package/src/components/ui/ForceDeleteConfirmDialog.test.tsx +62 -0
- package/src/components/ui/ForceDeleteConfirmDialog.tsx +24 -12
- package/src/components/ui/RefLink.tsx +20 -0
- package/src/components/ui/RowActionMenu.tsx +8 -3
- package/src/components/ui/ScheduleValue.test.tsx +5 -0
- package/src/components/ui/ScheduleValue.tsx +3 -2
- package/src/components/ui/crd-definition-navigation.test.tsx +160 -0
- package/src/components/ui/drawer-components.test.tsx +175 -1
- package/src/components/ui/drawer-components.tsx +103 -36
- package/src/components/ui/index.ts +3 -1
- package/src/components/ui/monacoEditorFeatures.ts +77 -0
- package/src/components/ui/monacoRuntime.ts +14 -19
- package/src/components/ui/status-tone.test.ts +10 -1
- package/src/components/ui/status-tone.tsx +29 -0
- package/src/components/workload/WorkloadView.deleted.test.tsx +101 -0
- package/src/components/workload/WorkloadView.deletedDialog.test.tsx +45 -0
- package/src/components/workload/WorkloadView.relationships.test.tsx +37 -0
- package/src/components/workload/WorkloadView.secretSave.test.tsx +62 -0
- package/src/components/workload/WorkloadView.spec.test.tsx +149 -0
- package/src/components/workload/WorkloadView.tsx +297 -104
- package/src/index.ts +14 -1
- package/src/monaco-deep.d.ts +19 -12
- package/src/theme/components.css +13 -0
- package/src/types/capacity.ts +21 -2
- package/src/types/core.ts +60 -2
- package/src/types/gitops-flux-status.test.ts +32 -1
- package/src/types/gitops-insights.ts +19 -2
- package/src/types/gitops-tree.ts +10 -0
- package/src/types/gitops.ts +26 -6
- package/src/utils/api-resources.ts +7 -0
- package/src/utils/application-history.ts +1 -0
- package/src/utils/cnpg-backup.test.ts +26 -0
- package/src/utils/cnpg-backup.ts +70 -0
- package/src/utils/format.test.ts +49 -2
- package/src/utils/format.ts +9 -1
- package/src/utils/gateway-references.test.ts +17 -0
- package/src/utils/gateway-references.ts +29 -0
- package/src/utils/gitops-owner.ts +3 -2
- package/src/utils/gitops-write-guard.test.ts +401 -0
- package/src/utils/gitops-write-guard.ts +509 -0
- package/src/utils/grant.test.ts +19 -0
- package/src/utils/grant.ts +35 -0
- package/src/utils/index.ts +6 -0
- package/src/utils/ingress-class-references.ts +22 -0
- package/src/utils/k8s-errors.ts +7 -7
- package/src/utils/log-format.test.ts +41 -0
- package/src/utils/log-format.ts +71 -15
- package/src/utils/log-level.ts +12 -8
- package/src/utils/navigation.test.ts +70 -0
- package/src/utils/navigation.ts +91 -8
- package/src/utils/node-lifecycle.test.ts +36 -0
- package/src/utils/node-lifecycle.ts +77 -0
- package/src/utils/object-reference-navigation.test.ts +43 -0
- package/src/utils/resource-hierarchy.test.ts +69 -0
- package/src/utils/resource-hierarchy.ts +41 -7
- package/src/utils/resource-icons.ts +2 -0
- package/src/utils/resource-refs.test.ts +23 -0
- package/src/utils/resource-refs.ts +30 -0
- package/src/utils/skeleton-yaml.test.ts +21 -0
- package/src/utils/skeleton-yaml.ts +16 -0
- package/src/utils/url-path.test.ts +35 -0
- package/src/utils/url-path.ts +21 -0
- package/src/utils/validators.test.ts +4 -13
- package/src/utils/validators.ts +5 -19
- package/src/utils/view-transition.test.ts +35 -1
- package/src/utils/view-transition.ts +16 -1
|
@@ -23,11 +23,13 @@ import {
|
|
|
23
23
|
getCNPGWALArchivingFailure,
|
|
24
24
|
getCNPGLastBackupFailure,
|
|
25
25
|
classifyCNPGClusterPhase,
|
|
26
|
+
cnpgBlockedPhaseExplanation,
|
|
26
27
|
getCNPGClusterAvailability,
|
|
27
28
|
CNPG_BARMAN_OBJECTSTORE_GROUP,
|
|
28
29
|
} from '../resource-utils-cnpg'
|
|
29
30
|
import { formatAge } from '../resource-utils'
|
|
30
31
|
import { RecoveryTime } from './CNPGShared'
|
|
32
|
+
import { formatRelativeAgeTime } from '../../../utils/format'
|
|
31
33
|
|
|
32
34
|
interface CNPGClusterRendererProps {
|
|
33
35
|
/** Filled by the host with the Databases, Publications and Subscriptions
|
|
@@ -99,6 +101,7 @@ export function CNPGClusterRenderer({ data, onNavigate, declared}: CNPGClusterRe
|
|
|
99
101
|
const isFailover = phaseBucket === 'failing'
|
|
100
102
|
const isSwitchover = phase === 'Switchover in progress'
|
|
101
103
|
const isTerminal = phaseBucket === 'terminal'
|
|
104
|
+
const blocked = cnpgBlockedPhaseExplanation(phase, data.status?.phaseReason)
|
|
102
105
|
// "The cluster is otherwise serving normally, so nothing else here will look
|
|
103
106
|
// wrong" is a claim about the REST OF THIS DRAWER, so any other banner
|
|
104
107
|
// falsifies it. It can no longer lean on isDegraded now that a
|
|
@@ -112,11 +115,7 @@ export function CNPGClusterRenderer({ data, onNavigate, declared}: CNPGClusterRe
|
|
|
112
115
|
<>
|
|
113
116
|
{/* Problem alerts */}
|
|
114
117
|
{isTerminal && (
|
|
115
|
-
<AlertBanner
|
|
116
|
-
variant="error"
|
|
117
|
-
title="Reconciliation has stopped"
|
|
118
|
-
message={`${phase}. This state does not resolve on its own — the operator has stopped reconciling this cluster and it needs manual intervention.`}
|
|
119
|
-
/>
|
|
118
|
+
<AlertBanner variant="error" title={blocked.title} message={blocked.message} />
|
|
120
119
|
)}
|
|
121
120
|
{hasSplitBrain && (
|
|
122
121
|
<AlertBanner
|
|
@@ -195,7 +194,7 @@ export function CNPGClusterRenderer({ data, onNavigate, declared}: CNPGClusterRe
|
|
|
195
194
|
variant="warning"
|
|
196
195
|
title="Last backup failed"
|
|
197
196
|
message={
|
|
198
|
-
`The most recent backup attempt failed${lastBackupFailure.lastTransitionTime ? ` ${
|
|
197
|
+
`The most recent backup attempt failed${lastBackupFailure.lastTransitionTime ? ` ${formatRelativeAgeTime(lastBackupFailure.lastTransitionTime)}` : ''}.` +
|
|
199
198
|
(lastBackupFailure.message ? ` Operator reported: ${lastBackupFailure.message}` : '')
|
|
200
199
|
}
|
|
201
200
|
/>
|
|
@@ -342,7 +341,7 @@ export function CNPGClusterRenderer({ data, onNavigate, declared}: CNPGClusterRe
|
|
|
342
341
|
{/* Storage */}
|
|
343
342
|
<Section title="Storage" icon={HardDrive} defaultExpanded>
|
|
344
343
|
<PropertyList>
|
|
345
|
-
<Property label="
|
|
344
|
+
<Property label="Requested size" value={getCNPGClusterStorage(data)} />
|
|
346
345
|
<Property label="Storage Class" value={getCNPGClusterStorageClass(data)} />
|
|
347
346
|
{/* Requested size and class are spec-side intentions. These are what the
|
|
348
347
|
operator reports about the volumes that actually exist. */}
|
|
@@ -363,7 +362,7 @@ export function CNPGClusterRenderer({ data, onNavigate, declared}: CNPGClusterRe
|
|
|
363
362
|
<div className="mt-2 pt-2 border-t border-theme-border">
|
|
364
363
|
<div className="text-xs font-medium text-theme-text-secondary uppercase tracking-wider mb-1">WAL Storage</div>
|
|
365
364
|
<PropertyList>
|
|
366
|
-
{walStorage.size && <Property label="
|
|
365
|
+
{walStorage.size && <Property label="Requested size" value={walStorage.size} />}
|
|
367
366
|
{walStorage.storageClass && <Property label="Storage Class" value={walStorage.storageClass} />}
|
|
368
367
|
</PropertyList>
|
|
369
368
|
</div>
|
|
@@ -195,6 +195,31 @@ export function CNPGSubscriptionRenderer({
|
|
|
195
195
|
)
|
|
196
196
|
}
|
|
197
197
|
|
|
198
|
+
/**
|
|
199
|
+
* DatabaseRole (CNPG 1.30+) — one PostgreSQL role as its own object. The
|
|
200
|
+
* Cluster's spec.managed.roles wins for the same name; the operator then
|
|
201
|
+
* reports this object not applied, and that message renders verbatim above.
|
|
202
|
+
*/
|
|
203
|
+
export function CNPGDatabaseRoleRenderer({ data, onNavigate }: { data: any; onNavigate?: Nav }) {
|
|
204
|
+
const spec = data?.spec ?? {}
|
|
205
|
+
const details: Array<{ label: string; value: React.ReactNode }> = [
|
|
206
|
+
{ label: 'Role', value: spec.name ?? '-' },
|
|
207
|
+
{ label: 'Login', value: spec.login === true ? 'Allowed' : 'Not allowed' },
|
|
208
|
+
]
|
|
209
|
+
if (spec.superuser === true) details.push({ label: 'Superuser', value: 'Yes' })
|
|
210
|
+
if (spec.disablePassword === true) details.push({ label: 'Password', value: 'Disabled' })
|
|
211
|
+
else if (spec.passwordSecret?.name) details.push({ label: 'Password Secret', value: spec.passwordSecret.name })
|
|
212
|
+
if (spec.validUntil) details.push({ label: 'Valid Until', value: spec.validUntil })
|
|
213
|
+
if (Array.isArray(spec.inRoles) && spec.inRoles.length > 0) details.push({ label: 'Member Of', value: spec.inRoles.join(', ') })
|
|
214
|
+
if (spec.connectionLimit != null && spec.connectionLimit !== -1) details.push({ label: 'Connection Limit', value: String(spec.connectionLimit) })
|
|
215
|
+
const cc = spec.clientCertificate
|
|
216
|
+
if (cc && cc.enabled !== false) {
|
|
217
|
+
const exp = data?.status?.clientCertificate?.expiration
|
|
218
|
+
details.push({ label: 'Client Certificate', value: `${data?.metadata?.name}-client-cert · ${exp ? `expires ${exp}` : 'expiry not reported'}` })
|
|
219
|
+
}
|
|
220
|
+
return <CNPGDeclarativeRenderer data={data} onNavigate={onNavigate} details={details} />
|
|
221
|
+
}
|
|
222
|
+
|
|
198
223
|
/**
|
|
199
224
|
* ImageCatalog and ClusterImageCatalog — the PostgreSQL images a Cluster may
|
|
200
225
|
* use, pinned per major version.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { renderToString } from 'react-dom/server'
|
|
3
|
+
import { CNPGScheduledBackupRenderer } from './CNPGScheduledBackupRenderer'
|
|
4
|
+
|
|
5
|
+
const schedule = (name: string, parameters?: { barmanObjectName?: string; serverName?: string }) => ({
|
|
6
|
+
apiVersion: 'postgresql.cnpg.io/v1',
|
|
7
|
+
kind: 'ScheduledBackup',
|
|
8
|
+
metadata: { name: 'nightly', namespace: 'db' },
|
|
9
|
+
spec: { cluster: { name: 'pg' }, schedule: '0 0 0 * * *', method: 'plugin', pluginConfiguration: { name, parameters } },
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
describe('CNPGScheduledBackupRenderer plugin destination', () => {
|
|
13
|
+
it.each([undefined, { barmanObjectName: 'ignored-store', serverName: 'ignored-server' }, { serverName: 'ignored-server' }])('explains the Cluster destination and ignored parameters: %j', (parameters) => {
|
|
14
|
+
const out = renderToString(<CNPGScheduledBackupRenderer data={schedule('barman-cloud.cloudnative-pg.io', parameters)} onNavigate={() => {}} />)
|
|
15
|
+
expect(out).toContain('From the Cluster')
|
|
16
|
+
expect(out).toContain('barman-cloud plugin')
|
|
17
|
+
expect(out).not.toContain('Object Store')
|
|
18
|
+
expect(out).not.toContain('ignored-store')
|
|
19
|
+
expect(out).not.toContain('ignored-server')
|
|
20
|
+
expect(out.match(/<button[^>]*>(?:pg|ignored-store|ignored-server)<\/button>/g)).toHaveLength(1)
|
|
21
|
+
expect(out.includes('Ignored by the barman-cloud plugin')).toBe(!!parameters)
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
it('does not assert a third-party parameter is an ObjectStore destination', () => {
|
|
25
|
+
const out = renderToString(<CNPGScheduledBackupRenderer data={schedule('other.example.com', { barmanObjectName: 'custom-store' })} />)
|
|
26
|
+
expect(out).toContain('other.example.com')
|
|
27
|
+
expect(out).toContain('Unknown: Radar does not model this plugin')
|
|
28
|
+
expect(out).not.toContain('custom-store')
|
|
29
|
+
expect(out).not.toContain('Object Store')
|
|
30
|
+
expect(out).not.toContain('Ignored by')
|
|
31
|
+
})
|
|
32
|
+
})
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import { Clock, Database } from 'lucide-react'
|
|
2
2
|
import { Section, PropertyList, Property, AlertBanner, ResourceLink } from '../../ui/drawer-components'
|
|
3
3
|
import { CronValue } from '../../ui/ScheduleValue'
|
|
4
|
+
import { TimeValue } from '../../ui/ScheduleValue'
|
|
4
5
|
import {
|
|
5
6
|
getCNPGScheduledBackupCluster,
|
|
6
7
|
getCNPGScheduleCron,
|
|
7
8
|
getCNPGScheduledBackupMethod,
|
|
8
|
-
getCNPGScheduledBackupLastSchedule,
|
|
9
9
|
getCNPGScheduledBackupNextSchedule,
|
|
10
10
|
getCNPGScheduledBackupIsSuspended,
|
|
11
11
|
getCNPGScheduledBackupIsImmediate,
|
|
12
12
|
getCNPGBackupPlugin,
|
|
13
13
|
getCNPGScheduledBackupOwnerRef,
|
|
14
|
-
|
|
14
|
+
CNPG_BARMAN_PLUGIN_NAME,
|
|
15
15
|
} from '../resource-utils-cnpg'
|
|
16
16
|
|
|
17
17
|
interface CNPGScheduledBackupRendererProps {
|
|
@@ -39,7 +39,7 @@ export function CNPGScheduledBackupRenderer({ data, onNavigate }: CNPGScheduledB
|
|
|
39
39
|
<Section title="Schedule" icon={Clock} defaultExpanded>
|
|
40
40
|
<PropertyList>
|
|
41
41
|
<Property label="Cron Expression" value={<CronValue cron={getCNPGScheduleCron(data)} dialect="seconds" />} />
|
|
42
|
-
<Property label="Last Schedule" value={
|
|
42
|
+
<Property label="Last Schedule" value={<TimeValue timestamp={data.status?.lastScheduleTime} />} />
|
|
43
43
|
<Property label="Next Schedule" value={getCNPGScheduledBackupNextSchedule(data)} />
|
|
44
44
|
<Property label="Suspended" value={isSuspended ? 'Yes' : 'No'} />
|
|
45
45
|
<Property label="Immediate" value={getCNPGScheduledBackupIsImmediate(data) ? 'Yes' : 'No'} />
|
|
@@ -64,24 +64,16 @@ export function CNPGScheduledBackupRenderer({ data, onNavigate }: CNPGScheduledB
|
|
|
64
64
|
return clusterName
|
|
65
65
|
})()} />
|
|
66
66
|
<Property label="Method" value={getCNPGScheduledBackupMethod(data)} />
|
|
67
|
-
{/* Under the plugin method the destination lives on an ObjectStore in
|
|
68
|
-
another API group, so naming it is the only route from here to
|
|
69
|
-
where these backups will actually land. */}
|
|
70
67
|
{schedulePlugin && <Property label="Plugin" value={schedulePlugin.name} />}
|
|
71
|
-
{schedulePlugin
|
|
68
|
+
{schedulePlugin && (
|
|
72
69
|
<Property
|
|
73
|
-
label="
|
|
74
|
-
value={
|
|
75
|
-
<ResourceLink
|
|
76
|
-
name={schedulePlugin.parameters.barmanObjectName}
|
|
77
|
-
kind="objectstores"
|
|
78
|
-
group={CNPG_BARMAN_OBJECTSTORE_GROUP}
|
|
79
|
-
namespace={data.metadata?.namespace || ''}
|
|
80
|
-
onNavigate={onNavigate}
|
|
81
|
-
/>
|
|
82
|
-
}
|
|
70
|
+
label="Destination"
|
|
71
|
+
value={schedulePlugin.name === CNPG_BARMAN_PLUGIN_NAME ? "From the Cluster's barman-cloud plugin" : 'Unknown: Radar does not model this plugin’s destination'}
|
|
83
72
|
/>
|
|
84
73
|
)}
|
|
74
|
+
{schedulePlugin?.name === CNPG_BARMAN_PLUGIN_NAME && schedulePlugin.parameters && Object.keys(schedulePlugin.parameters).length > 0 && (
|
|
75
|
+
<Property label="Plugin parameters" value="Ignored by the barman-cloud plugin; the destination comes from the Cluster" />
|
|
76
|
+
)}
|
|
85
77
|
<Property label="Owner Reference" value={getCNPGScheduledBackupOwnerRef(data)} />
|
|
86
78
|
</PropertyList>
|
|
87
79
|
</Section>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { formatRelativeAgeTime } from '../../../utils/format'
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* A recovery point, stated absolutely with the interval beside it.
|
|
@@ -21,7 +21,7 @@ export function RecoveryTime({ at }: { at: string }) {
|
|
|
21
21
|
<span>{d.toISOString().replace('T', ' ').replace(/\.\d+Z$/, ' UTC')}</span>
|
|
22
22
|
{/* One interpolation, so the age and its unit stay a single text node
|
|
23
23
|
rather than being split by a renderer comment marker. */}
|
|
24
|
-
<span className="text-xs text-theme-text-tertiary shrink-0">{
|
|
24
|
+
<span className="text-xs text-theme-text-tertiary shrink-0">{formatRelativeAgeTime(at)}</span>
|
|
25
25
|
</span>
|
|
26
26
|
)
|
|
27
27
|
}
|
|
@@ -4,10 +4,10 @@ import { clsx } from 'clsx'
|
|
|
4
4
|
import { Section, PropertyList, Property, AlertBanner } from '../../ui/drawer-components'
|
|
5
5
|
import { Collapse, CollapseChevron, useDisclosure, disclosurePanelId } from '../../ui/Collapse'
|
|
6
6
|
import { Input } from '../../ui/Input'
|
|
7
|
-
import { formatAge } from '../resource-utils'
|
|
8
7
|
import { SEVERITY_BADGE_COLORS, SEVERITY_ORDER } from './trivy-shared'
|
|
9
8
|
import { pluralize } from '../../../utils/pluralize'
|
|
10
9
|
import { BADGE_INACTIVE } from '../../../utils/badge-colors'
|
|
10
|
+
import { TimeValue } from '../../ui/ScheduleValue'
|
|
11
11
|
|
|
12
12
|
interface ClusterComplianceReportRendererProps {
|
|
13
13
|
data: any
|
|
@@ -97,7 +97,7 @@ export function ClusterComplianceReportRenderer({ data }: ClusterComplianceRepor
|
|
|
97
97
|
{compliance.description && <Property label="Description" value={compliance.description} />}
|
|
98
98
|
{compliance.version && <Property label="Version" value={compliance.version} />}
|
|
99
99
|
{compliance.platform && <Property label="Platform" value={compliance.platform} />}
|
|
100
|
-
<Property label="Last Updated" value={
|
|
100
|
+
<Property label="Last Updated" value={<TimeValue timestamp={status.updateTimestamp} />} />
|
|
101
101
|
</PropertyList>
|
|
102
102
|
</Section>
|
|
103
103
|
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
import { act } from 'react'
|
|
3
|
+
import { createRoot } from 'react-dom/client'
|
|
4
|
+
import { describe, expect, it, vi } from 'vitest'
|
|
5
|
+
import { CompositeRenderer } from './CompositeRenderer'
|
|
6
|
+
import { initNavigationMap, resetNavigationMap } from '../../../utils/navigation'
|
|
7
|
+
|
|
8
|
+
describe('CompositeRenderer composed-resource links', () => {
|
|
9
|
+
it('resolves a composed Kind within its own API group, not the core Kind it reuses', async () => {
|
|
10
|
+
Object.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: true })
|
|
11
|
+
initNavigationMap([
|
|
12
|
+
{ group: '', version: 'v1', kind: 'Node', name: 'nodes', namespaced: false, isCrd: false, verbs: ['get'] },
|
|
13
|
+
{ group: 'relationships.radar.test', version: 'v1', kind: 'Node', name: 'workerhosts', namespaced: false, isCrd: true, verbs: ['get'] },
|
|
14
|
+
])
|
|
15
|
+
const container = document.createElement('div')
|
|
16
|
+
const root = createRoot(container)
|
|
17
|
+
const onNavigate = vi.fn()
|
|
18
|
+
try {
|
|
19
|
+
await act(async () => root.render(<CompositeRenderer onNavigate={onNavigate} data={{
|
|
20
|
+
apiVersion: 'platform.example.org/v1alpha1',
|
|
21
|
+
kind: 'Database',
|
|
22
|
+
metadata: { name: 'example' },
|
|
23
|
+
spec: { resourceRefs: [{ apiVersion: 'relationships.radar.test/v1', kind: 'Node', name: 'worker' }] },
|
|
24
|
+
}} />))
|
|
25
|
+
const link = [...container.querySelectorAll('button')].find(button => button.textContent === 'worker')
|
|
26
|
+
expect(link).toBeDefined()
|
|
27
|
+
await act(async () => link!.click())
|
|
28
|
+
expect(onNavigate).toHaveBeenLastCalledWith(expect.objectContaining({ kind: 'workerhosts', group: 'relationships.radar.test', name: 'worker' }))
|
|
29
|
+
} finally {
|
|
30
|
+
await act(async () => root.unmount())
|
|
31
|
+
resetNavigationMap()
|
|
32
|
+
Object.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: false })
|
|
33
|
+
}
|
|
34
|
+
})
|
|
35
|
+
})
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
isCrossplanePaused,
|
|
15
15
|
type CrossplaneResourceRef,
|
|
16
16
|
} from '../resource-utils-crossplane'
|
|
17
|
-
import {
|
|
17
|
+
import { kindToPluralWithGroup } from '../../../utils/navigation'
|
|
18
18
|
import type { StatusBadge } from '../resource-utils'
|
|
19
19
|
|
|
20
20
|
/**
|
|
@@ -158,7 +158,7 @@ export function CompositeRenderer({ data, onNavigate, composedRefStatuses, compo
|
|
|
158
158
|
value={
|
|
159
159
|
<ResourceLink
|
|
160
160
|
name={boundXR.name}
|
|
161
|
-
kind={
|
|
161
|
+
kind={kindToPluralWithGroup(boundXR.kind, groupFromApiVersion(boundXR.apiVersion))}
|
|
162
162
|
namespace={boundXR.namespace || ''}
|
|
163
163
|
group={groupFromApiVersion(boundXR.apiVersion) || undefined}
|
|
164
164
|
onNavigate={onNavigate}
|
|
@@ -242,8 +242,8 @@ function ComposedRefRow({
|
|
|
242
242
|
statusEntry?: ComposedRefStatus
|
|
243
243
|
onNavigate?: (ref: { kind: string; namespace: string; name: string; group?: string }) => void
|
|
244
244
|
}) {
|
|
245
|
-
const kindPlural = kindToPlural(ref_.kind)
|
|
246
245
|
const group = groupFromApiVersion(ref_.apiVersion)
|
|
246
|
+
const kindPlural = kindToPluralWithGroup(ref_.kind, group)
|
|
247
247
|
return (
|
|
248
248
|
<div className="flex items-center gap-2 py-1 px-2 rounded hover:bg-theme-hover text-sm">
|
|
249
249
|
<Box className="w-3.5 h-3.5 text-theme-text-tertiary shrink-0" />
|
|
@@ -4,7 +4,7 @@ import { Layers, GitBranch, FileText, Boxes, ScrollText } from 'lucide-react'
|
|
|
4
4
|
import { Section, PropertyList, Property, AlertBanner, ResourceLink } from '../../ui/drawer-components'
|
|
5
5
|
import { Collapse, CollapseChevron, useDisclosure } from '../../ui/Collapse'
|
|
6
6
|
import { CodeViewer } from '../../ui/CodeViewer'
|
|
7
|
-
import {
|
|
7
|
+
import { kindToPluralWithGroup } from '../../../utils/navigation'
|
|
8
8
|
|
|
9
9
|
interface CompositionRendererProps {
|
|
10
10
|
data: any
|
|
@@ -74,11 +74,11 @@ function CompositionBody({ data, onNavigate, revision }: CompositionRendererProp
|
|
|
74
74
|
<div className="mt-2 text-xs text-theme-text-secondary">
|
|
75
75
|
Backed by XRD:{' '}
|
|
76
76
|
<ResourceLink
|
|
77
|
-
name={`${
|
|
77
|
+
name={`${kindToPluralWithGroup(xrdKind, xrdGroup)}.${xrdGroup}`}
|
|
78
78
|
kind="compositeresourcedefinitions"
|
|
79
79
|
namespace=""
|
|
80
80
|
onNavigate={onNavigate}
|
|
81
|
-
label={<span className="font-mono">{
|
|
81
|
+
label={<span className="font-mono">{kindToPluralWithGroup(xrdKind, xrdGroup)}.{xrdGroup}</span>}
|
|
82
82
|
/>
|
|
83
83
|
</div>
|
|
84
84
|
)}
|
|
@@ -3,9 +3,9 @@ import { ShieldCheck, CheckCircle2, XCircle } from 'lucide-react'
|
|
|
3
3
|
import { clsx } from 'clsx'
|
|
4
4
|
import { Section, PropertyList, Property } from '../../ui/drawer-components'
|
|
5
5
|
import { Collapse, CollapseChevron, useDisclosure, disclosurePanelId } from '../../ui/Collapse'
|
|
6
|
-
import { formatAge } from '../resource-utils'
|
|
7
6
|
import { SEVERITY_BADGE_COLORS, SEVERITY_ORDER, TrivyAlertBanner } from './trivy-shared'
|
|
8
7
|
import { BADGE_INACTIVE } from '../../../utils/badge-colors'
|
|
8
|
+
import { TimeValue } from '../../ui/ScheduleValue'
|
|
9
9
|
|
|
10
10
|
interface ConfigAuditReportRendererProps {
|
|
11
11
|
data: any
|
|
@@ -115,7 +115,7 @@ export function ConfigAuditReportRenderer({ data }: ConfigAuditReportRendererPro
|
|
|
115
115
|
<Section title="Report Overview" icon={ShieldCheck}>
|
|
116
116
|
<PropertyList>
|
|
117
117
|
<Property label="Scanner" value={scanner.name ? `${scanner.name} ${scanner.version || ''}`.trim() : '-'} />
|
|
118
|
-
<Property label="Scanned" value={
|
|
118
|
+
<Property label="Scanned" value={<TimeValue timestamp={report.updateTimestamp} />} />
|
|
119
119
|
</PropertyList>
|
|
120
120
|
</Section>
|
|
121
121
|
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
import { act, type ReactNode } from 'react'
|
|
3
|
+
import { createRoot } from 'react-dom/client'
|
|
4
|
+
import { describe, expect, it, vi } from 'vitest'
|
|
5
|
+
import { ContourHTTPProxyRenderer } from './ContourHTTPProxyRenderer'
|
|
6
|
+
|
|
7
|
+
async function rendered(element: ReactNode, check: (container: HTMLDivElement) => Promise<void> | void) {
|
|
8
|
+
Object.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: true })
|
|
9
|
+
const container = document.createElement('div')
|
|
10
|
+
const root = createRoot(container)
|
|
11
|
+
try { await act(async () => root.render(element)); await check(container) }
|
|
12
|
+
finally { await act(async () => root.unmount()); Object.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: false }) }
|
|
13
|
+
}
|
|
14
|
+
async function click(container: HTMLDivElement, text: string) {
|
|
15
|
+
const button = [...container.querySelectorAll('button')].find(button => button.textContent === text)
|
|
16
|
+
expect(button, `missing link ${text}`).toBeDefined()
|
|
17
|
+
await act(async () => button!.click())
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
describe('exact reference navigation', () => {
|
|
21
|
+
it.each([['tls/', 'tls'], ['/', 'app']])('resolves Contour TLS secret prefix %s to namespace %s',async(prefix, namespace)=>{
|
|
22
|
+
const navigate=vi.fn()
|
|
23
|
+
await rendered(<ContourHTTPProxyRenderer data={{metadata:{namespace:'app'},spec:{virtualhost:{fqdn:'example.test',tls:{secretName:`${prefix}server`,clientValidation:{caSecret:`${prefix}ca`,crlSecret:`${prefix}crl`}}}}}} onNavigate={navigate}/>,async container=>{
|
|
24
|
+
for(const name of ['server','ca','crl']) {await click(container,`${prefix}${name}`);expect(navigate).toHaveBeenLastCalledWith({kind:'secrets',group:'',namespace,name})}
|
|
25
|
+
})
|
|
26
|
+
})
|
|
27
|
+
it('links valid long Contour Secret names',async()=>{
|
|
28
|
+
const navigate=vi.fn()
|
|
29
|
+
const name='a'.repeat(70)
|
|
30
|
+
await rendered(<ContourHTTPProxyRenderer data={{metadata:{namespace:'app'},spec:{virtualhost:{tls:{secretName:name}}}}} onNavigate={navigate}/>,async container=>{
|
|
31
|
+
await click(container,name);expect(navigate).toHaveBeenCalledWith({kind:'secrets',group:'',namespace:'app',name})
|
|
32
|
+
})
|
|
33
|
+
})
|
|
34
|
+
})
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Globe, Route, Layers, Lock } from 'lucide-react'
|
|
2
2
|
import { Section, PropertyList, Property, AlertBanner, ConditionsSection, ResourceLink } from '../../ui/drawer-components'
|
|
3
3
|
import {
|
|
4
|
+
getContourSecretReference,
|
|
4
5
|
getHTTPProxyFQDN,
|
|
5
6
|
getHTTPProxyRouteCount,
|
|
6
7
|
getHTTPProxyServiceCount,
|
|
@@ -174,16 +175,16 @@ export function ContourHTTPProxyRenderer({ data, onNavigate }: ContourHTTPProxyR
|
|
|
174
175
|
{tls && (
|
|
175
176
|
<Section title="TLS" icon={Lock} defaultExpanded>
|
|
176
177
|
<PropertyList>
|
|
177
|
-
{
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
/>
|
|
185
|
-
|
|
186
|
-
)}
|
|
178
|
+
{[
|
|
179
|
+
{ label: 'Secret', name: tls.secretName },
|
|
180
|
+
{ label: 'Client CA Secret', name: tls.clientValidation?.caSecret },
|
|
181
|
+
{ label: 'Client CRL Secret', name: tls.clientValidation?.crlSecret },
|
|
182
|
+
].filter(item => item.name).map(item => {
|
|
183
|
+
const ref = getContourSecretReference(item.name, ns)
|
|
184
|
+
return <Property key={item.label} label={item.label} value={ref
|
|
185
|
+
? <ResourceLink {...ref} kind="secrets" group="" label={item.name} onNavigate={onNavigate} />
|
|
186
|
+
: item.name} />
|
|
187
|
+
})}
|
|
187
188
|
{tls.minimumProtocolVersion && (
|
|
188
189
|
<Property label="Min Protocol" value={tls.minimumProtocolVersion} />
|
|
189
190
|
)}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { Clock, Pause } from 'lucide-react'
|
|
2
2
|
import { Section, PropertyList, Property, AlertBanner, ResourceLink } from '../../ui/drawer-components'
|
|
3
|
-
import {
|
|
3
|
+
import { cronToHuman } from '../resource-utils'
|
|
4
|
+
import { TimeValue } from '../../ui/ScheduleValue'
|
|
5
|
+
import { formatRelativeAgeTime } from '../../../utils/format'
|
|
4
6
|
|
|
5
7
|
interface CronJobRendererProps {
|
|
6
8
|
data: any
|
|
@@ -53,7 +55,7 @@ export function CronJobRenderer({ data, onNavigate }: CronJobRendererProps) {
|
|
|
53
55
|
<AlertBanner
|
|
54
56
|
variant="error"
|
|
55
57
|
title="Recent Jobs Failing"
|
|
56
|
-
message={<>Jobs have been scheduled but haven't succeeded recently. Last success: {
|
|
58
|
+
message={<>Jobs have been scheduled but haven't succeeded recently. Last success: {formatRelativeAgeTime(status.lastSuccessfulTime, 'never')}. Check job history and pod logs.</>}
|
|
57
59
|
/>
|
|
58
60
|
)}
|
|
59
61
|
|
|
@@ -62,8 +64,8 @@ export function CronJobRenderer({ data, onNavigate }: CronJobRendererProps) {
|
|
|
62
64
|
<Property label="Schedule" value={spec.schedule} />
|
|
63
65
|
<Property label="Human" value={cronToHuman(spec.schedule)} />
|
|
64
66
|
<Property label="Suspend" value={spec.suspend ? 'Yes' : 'No'} />
|
|
65
|
-
<Property label="Last Schedule" value={
|
|
66
|
-
<Property label="Last Success" value={
|
|
67
|
+
<Property label="Last Schedule" value={<TimeValue timestamp={status.lastScheduleTime} fallback="Never" />} />
|
|
68
|
+
<Property label="Last Success" value={<TimeValue timestamp={status.lastSuccessfulTime} fallback="Never" />} />
|
|
67
69
|
<Property label="Active Jobs" value={status.active?.length || 0} />
|
|
68
70
|
</PropertyList>
|
|
69
71
|
</Section>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Clock, Pause } from 'lucide-react'
|
|
2
2
|
import { Section, PropertyList, Property, AlertBanner, ResourceLink, ConditionsSection } from '../../ui/drawer-components'
|
|
3
|
-
import {
|
|
3
|
+
import { cronToHuman } from '../resource-utils'
|
|
4
|
+
import { TimeValue } from '../../ui/ScheduleValue'
|
|
4
5
|
|
|
5
6
|
interface CronWorkflowRendererProps {
|
|
6
7
|
data: any
|
|
@@ -45,7 +46,7 @@ export function CronWorkflowRenderer({ data, onNavigate }: CronWorkflowRendererP
|
|
|
45
46
|
<Property label="Timezone" value={spec.timezone || 'Cluster default'} />
|
|
46
47
|
<Property label="Suspend" value={isSuspended ? 'Yes' : 'No'} />
|
|
47
48
|
<Property label="Concurrency" value={spec.concurrencyPolicy || 'Allow'} />
|
|
48
|
-
<Property label="Last Scheduled" value={
|
|
49
|
+
<Property label="Last Scheduled" value={<TimeValue timestamp={status.lastScheduledTime} fallback="Never" />} />
|
|
49
50
|
<Property label="Active Workflows" value={active.length} />
|
|
50
51
|
</PropertyList>
|
|
51
52
|
</Section>
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
import { act } from 'react'
|
|
3
|
+
import { createRoot } from 'react-dom/client'
|
|
4
|
+
import { describe, expect, it, vi } from 'vitest'
|
|
5
|
+
import { EndpointSliceRenderer } from './EndpointSliceRenderer'
|
|
6
|
+
import { initNavigationMap, resetNavigationMap } from '../../../utils/navigation'
|
|
7
|
+
|
|
8
|
+
vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true)
|
|
9
|
+
|
|
10
|
+
it('navigates to the labeled Service as a core-group resource', async () => {
|
|
11
|
+
const onNavigate = vi.fn()
|
|
12
|
+
const element = document.createElement('div')
|
|
13
|
+
const root = createRoot(element)
|
|
14
|
+
const slice = {
|
|
15
|
+
apiVersion: 'discovery.k8s.io/v1',
|
|
16
|
+
kind: 'EndpointSlice',
|
|
17
|
+
metadata: { name: 'web-abc', namespace: 'team', labels: { 'kubernetes.io/service-name': 'web' } },
|
|
18
|
+
}
|
|
19
|
+
await act(async () => { root.render(<EndpointSliceRenderer data={slice} onNavigate={onNavigate} />) })
|
|
20
|
+
const button = [...element.querySelectorAll('button')].find(b => b.textContent === 'web')
|
|
21
|
+
expect(button).toBeDefined()
|
|
22
|
+
await act(async () => { button!.click() })
|
|
23
|
+
expect(onNavigate).toHaveBeenCalledWith({ kind: 'Service', group: '', namespace: 'team', name: 'web' })
|
|
24
|
+
await act(async () => root.unmount())
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
describe('EndpointSliceRenderer target navigation', () => {
|
|
28
|
+
it('navigates exact target identity, controller-written Pod targets and Node placement', async () => {
|
|
29
|
+
Object.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: true })
|
|
30
|
+
initNavigationMap([{ group: 'custom.example.io', version: 'v1', kind: 'Node', name: 'nodes', namespaced: false, isCrd: true, verbs: ['get'] }])
|
|
31
|
+
const container = document.createElement('div')
|
|
32
|
+
const root = createRoot(container)
|
|
33
|
+
const onNavigate = vi.fn()
|
|
34
|
+
const click = async (label: string) => {
|
|
35
|
+
const button = [...container.querySelectorAll('button')].find(button => button.textContent === label)
|
|
36
|
+
expect(button).toBeDefined()
|
|
37
|
+
await act(async () => button!.click())
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
await act(async () => root.render(<EndpointSliceRenderer onNavigate={onNavigate} data={{ metadata: { namespace: 'slice-team' }, endpoints: [
|
|
41
|
+
{ addresses: ['10.0.0.1'], nodeName: 'worker', targetRef: { apiVersion: 'custom.example.io/v1', kind: 'Node', namespace: 'irrelevant', name: 'custom-target' } },
|
|
42
|
+
{ addresses: ['10.0.0.2'], targetRef: { apiVersion: 'v1', kind: 'Pod', namespace: 'target-team', name: 'backend' } },
|
|
43
|
+
{ addresses: ['10.0.0.3'], targetRef: { kind: 'Pod', namespace: 'slice-team', name: 'controller-written', uid: 'pod-uid' } },
|
|
44
|
+
{ addresses: ['10.0.0.4'], targetRef: { kind: 'Widget', namespace: 'slice-team', name: 'unknown-api' } },
|
|
45
|
+
] }} />))
|
|
46
|
+
await click('Node custom-target')
|
|
47
|
+
expect(onNavigate).toHaveBeenLastCalledWith({ kind: 'Node', group: 'custom.example.io', namespace: '', name: 'custom-target' })
|
|
48
|
+
await click('Pod target-team/backend')
|
|
49
|
+
expect(onNavigate).toHaveBeenLastCalledWith({ kind: 'Pod', group: '', namespace: 'target-team', name: 'backend' })
|
|
50
|
+
await click('Pod slice-team/controller-written')
|
|
51
|
+
expect(onNavigate).toHaveBeenLastCalledWith({ kind: 'Pod', group: '', namespace: 'slice-team', name: 'controller-written' })
|
|
52
|
+
await click('worker')
|
|
53
|
+
expect(onNavigate).toHaveBeenLastCalledWith({ kind: 'Node', group: '', namespace: '', name: 'worker' })
|
|
54
|
+
expect([...container.querySelectorAll('button')].some(button => button.textContent?.includes('unknown-api'))).toBe(false)
|
|
55
|
+
expect(container.textContent).toContain('unknown-api')
|
|
56
|
+
} finally {
|
|
57
|
+
await act(async () => root.unmount())
|
|
58
|
+
resetNavigationMap()
|
|
59
|
+
Object.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: false })
|
|
60
|
+
}
|
|
61
|
+
})
|
|
62
|
+
})
|
|
@@ -3,6 +3,7 @@ import { clsx } from 'clsx'
|
|
|
3
3
|
import { Section, PropertyList, Property } from '../../ui/drawer-components'
|
|
4
4
|
import { Badge } from '../../ui/Badge'
|
|
5
5
|
import type { ResourceRef } from '../../../types'
|
|
6
|
+
import { objectReferenceToResourceRef } from '../../../utils/navigation'
|
|
6
7
|
|
|
7
8
|
interface EndpointSliceRendererProps {
|
|
8
9
|
data: any
|
|
@@ -17,8 +18,7 @@ function endpointAddressCount(endpoints: any[]): number {
|
|
|
17
18
|
return endpoints.reduce((total, endpoint) => total + (endpoint.addresses?.length || 0), 0)
|
|
18
19
|
}
|
|
19
20
|
|
|
20
|
-
function endpointTargetLabel(
|
|
21
|
-
const target = endpoint.targetRef
|
|
21
|
+
function endpointTargetLabel(target: any): string | null {
|
|
22
22
|
if (!target?.kind || !target?.name) return null
|
|
23
23
|
return target.namespace ? `${target.kind} ${target.namespace}/${target.name}` : `${target.kind} ${target.name}`
|
|
24
24
|
}
|
|
@@ -47,7 +47,7 @@ export function EndpointSliceRenderer({ data, onNavigate }: EndpointSliceRendere
|
|
|
47
47
|
<button
|
|
48
48
|
type="button"
|
|
49
49
|
className="text-sm text-accent-text hover:underline font-medium"
|
|
50
|
-
onClick={() => onNavigate({ kind: 'Service', namespace: metadata.namespace, name: serviceName })}
|
|
50
|
+
onClick={() => onNavigate({ kind: 'Service', group: '', namespace: metadata.namespace, name: serviceName })}
|
|
51
51
|
>
|
|
52
52
|
{serviceName}
|
|
53
53
|
</button>
|
|
@@ -85,8 +85,8 @@ export function EndpointSliceRenderer({ data, onNavigate }: EndpointSliceRendere
|
|
|
85
85
|
<div className="space-y-2">
|
|
86
86
|
{endpoints.map((endpoint: any, index: number) => {
|
|
87
87
|
const ready = isEndpointReady(endpoint)
|
|
88
|
-
const
|
|
89
|
-
const
|
|
88
|
+
const targetRef = objectReferenceToResourceRef(endpoint.targetRef)
|
|
89
|
+
const targetLabel = endpointTargetLabel(targetRef || endpoint.targetRef)
|
|
90
90
|
return (
|
|
91
91
|
<div key={`${endpoint.addresses?.join(',') || 'endpoint'}-${index}`} className="card-inner text-sm space-y-3">
|
|
92
92
|
<div className="flex items-start justify-between gap-3">
|
|
@@ -98,20 +98,15 @@ export function EndpointSliceRenderer({ data, onNavigate }: EndpointSliceRendere
|
|
|
98
98
|
</Badge>
|
|
99
99
|
))}
|
|
100
100
|
</div>
|
|
101
|
-
{targetLabel && (
|
|
101
|
+
{targetLabel && (onNavigate && targetRef ? (
|
|
102
102
|
<button
|
|
103
103
|
type="button"
|
|
104
104
|
className="text-xs text-theme-text-secondary hover:text-accent-text"
|
|
105
|
-
|
|
106
|
-
onClick={() => onNavigate?.({
|
|
107
|
-
kind: target.kind,
|
|
108
|
-
namespace: target.namespace || metadata.namespace,
|
|
109
|
-
name: target.name,
|
|
110
|
-
})}
|
|
105
|
+
onClick={() => onNavigate(targetRef)}
|
|
111
106
|
>
|
|
112
107
|
{targetLabel}
|
|
113
108
|
</button>
|
|
114
|
-
)}
|
|
109
|
+
) : <span className="text-xs text-theme-text-secondary">{targetLabel}</span>)}
|
|
115
110
|
</div>
|
|
116
111
|
<div className="flex flex-wrap justify-end gap-1.5 shrink-0">
|
|
117
112
|
<span className={clsx('badge-sm', ready ? 'status-healthy' : 'status-unhealthy')}>
|
|
@@ -127,7 +122,13 @@ export function EndpointSliceRenderer({ data, onNavigate }: EndpointSliceRendere
|
|
|
127
122
|
</div>
|
|
128
123
|
{(endpoint.nodeName || endpoint.zone) && (
|
|
129
124
|
<div className="flex flex-wrap gap-x-4 gap-y-1 text-xs text-theme-text-tertiary">
|
|
130
|
-
{endpoint.nodeName && <span>Node: {
|
|
125
|
+
{endpoint.nodeName && <span>Node: {onNavigate ? (
|
|
126
|
+
<button
|
|
127
|
+
type="button"
|
|
128
|
+
className="hover:text-accent-text hover:underline"
|
|
129
|
+
onClick={() => onNavigate({ kind: 'Node', group: '', namespace: '', name: endpoint.nodeName })}
|
|
130
|
+
>{endpoint.nodeName}</button>
|
|
131
|
+
) : endpoint.nodeName}</span>}
|
|
131
132
|
{endpoint.zone && <span>Zone: {endpoint.zone}</span>}
|
|
132
133
|
</div>
|
|
133
134
|
)}
|
|
@@ -2,9 +2,9 @@ import { AlertTriangle, Info, Clock, Target, Server, Hash } from 'lucide-react'
|
|
|
2
2
|
import { clsx } from 'clsx'
|
|
3
3
|
import { Badge } from '../../ui/Badge'
|
|
4
4
|
import { Section, PropertyList, Property } from '../../ui/drawer-components'
|
|
5
|
-
import { formatAge } from '../resource-utils'
|
|
6
5
|
import type { NavigateToResource } from '../../../utils/navigation'
|
|
7
6
|
import { apiVersionToGroup, kindToPluralWithGroup } from '../../../utils/navigation'
|
|
7
|
+
import { TimeValue } from '../../ui/ScheduleValue'
|
|
8
8
|
|
|
9
9
|
interface EventRendererProps {
|
|
10
10
|
data: any
|
|
@@ -118,10 +118,10 @@ export function EventRenderer({ data, onNavigate }: EventRendererProps) {
|
|
|
118
118
|
<PropertyList>
|
|
119
119
|
{count > 1 && <Property label="Count" value={count} />}
|
|
120
120
|
{firstTimestamp && (
|
|
121
|
-
<Property label="First Seen" value={
|
|
121
|
+
<Property label="First Seen" value={<TimeValue timestamp={firstTimestamp} />} />
|
|
122
122
|
)}
|
|
123
123
|
{lastTimestamp && (
|
|
124
|
-
<Property label="Last Seen" value={
|
|
124
|
+
<Property label="Last Seen" value={<TimeValue timestamp={lastTimestamp} />} />
|
|
125
125
|
)}
|
|
126
126
|
{firstTimestamp && lastTimestamp && firstTimestamp !== lastTimestamp && (
|
|
127
127
|
<Property label="Duration" value={calculateDuration(firstTimestamp, lastTimestamp)} />
|