@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
|
@@ -552,7 +552,7 @@ export function EditableYamlView({
|
|
|
552
552
|
{!readOnly && (
|
|
553
553
|
<button
|
|
554
554
|
onClick={handleStartEdit}
|
|
555
|
-
className="flex items-center gap-1 px-2 py-1 text-xs text-
|
|
555
|
+
className="flex items-center gap-1 px-2 py-1 text-xs text-accent-text hover:text-accent-light hover:bg-theme-elevated rounded"
|
|
556
556
|
>
|
|
557
557
|
<Pencil className="w-3.5 h-3.5" />
|
|
558
558
|
Edit
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { renderToStaticMarkup } from 'react-dom/server'
|
|
2
|
+
import { describe, expect, it } from 'vitest'
|
|
3
|
+
|
|
4
|
+
import { evaluateGitOpsWriteGuard, type GitOpsWriteGuardInput } from '../../utils/gitops-write-guard'
|
|
5
|
+
import { GitOpsWriteWarning } from './GitOpsWriteWarning'
|
|
6
|
+
|
|
7
|
+
const base: GitOpsWriteGuardInput = {
|
|
8
|
+
target: { kind: 'Deployment', group: 'apps', namespace: 'prod', name: 'api' },
|
|
9
|
+
owner: { tool: 'argocd', kind: 'applications', namespace: 'argocd', name: 'api' },
|
|
10
|
+
writes: [{ scope: 'spec', paths: ['spec.replicas'] }],
|
|
11
|
+
evidence: {
|
|
12
|
+
uid: 'u',
|
|
13
|
+
resourceVersion: '1',
|
|
14
|
+
owner: null,
|
|
15
|
+
policy: { tool: 'argocd', auto: true, selfHeal: true, prune: false, suspended: null },
|
|
16
|
+
paths: [{ path: 'spec.replicas', lastApplied: 'present', ownedBy: [], ownedByGitOps: false }],
|
|
17
|
+
},
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function render(input: GitOpsWriteGuardInput, onOpenOwner?: () => void) {
|
|
21
|
+
return renderToStaticMarkup(
|
|
22
|
+
<GitOpsWriteWarning
|
|
23
|
+
guard={evaluateGitOpsWriteGuard(input)}
|
|
24
|
+
acknowledged={false}
|
|
25
|
+
onAcknowledgedChange={() => {}}
|
|
26
|
+
onOpenOwner={onOpenOwner}
|
|
27
|
+
/>,
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
describe('GitOpsWriteWarning', () => {
|
|
32
|
+
it('renders the revert warning, owner link and required acknowledgment', () => {
|
|
33
|
+
const html = render(base, () => {})
|
|
34
|
+
expect(html).toContain('Argo CD Application argocd/api will revert this change.')
|
|
35
|
+
expect(html).toContain('overwritten at the next sync')
|
|
36
|
+
expect(html).toContain('change it in the GitOps source')
|
|
37
|
+
expect(html).toContain('View Argo CD Application')
|
|
38
|
+
expect(html).toContain('type="checkbox"')
|
|
39
|
+
expect(html).toContain('I understand Argo CD will revert this.')
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
it('asks the acknowledgment in the verdict’s own terms', () => {
|
|
43
|
+
const html = render({
|
|
44
|
+
...base,
|
|
45
|
+
evidence: { ...base.evidence!, policy: { tool: 'argocd', auto: false, selfHeal: false, prune: false, suspended: null } },
|
|
46
|
+
})
|
|
47
|
+
expect(html).toContain('Argo CD Application argocd/api may revert this change.')
|
|
48
|
+
expect(html).toContain('I understand Argo CD may revert this.')
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('shows a checking state while ownership resolves', () => {
|
|
52
|
+
const html = render({ ...base, ownerPending: true })
|
|
53
|
+
expect(html).toContain('Checking GitOps ownership…')
|
|
54
|
+
expect(html).not.toContain('type="checkbox"')
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it('renders a quiet note without acknowledgment at info level', () => {
|
|
58
|
+
const html = render({ ...base, writes: [{ scope: 'create-child' }] })
|
|
59
|
+
expect(html).toContain('Managed by Argo CD Application argocd/api.')
|
|
60
|
+
expect(html).not.toContain('type="checkbox"')
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it('leaves vertical spacing to the parent', () => {
|
|
64
|
+
for (const input of [base, { ...base, ownerPending: true }, { ...base, writes: [{ scope: 'create-child' as const }] }]) {
|
|
65
|
+
const root = render(input).match(/^<div class="([^"]*)"/)?.[1] ?? ''
|
|
66
|
+
expect(root).not.toMatch(/(^|\s)m[bt]-/)
|
|
67
|
+
}
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
it('renders nothing when unmanaged', () => {
|
|
71
|
+
expect(render({ ...base, owner: null })).toBe('')
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
it('lists per-write reasons when they differ', () => {
|
|
75
|
+
const html = render({
|
|
76
|
+
...base,
|
|
77
|
+
writes: [
|
|
78
|
+
{ scope: 'spec', paths: ['spec.replicas'], description: 'Replicas' },
|
|
79
|
+
{ scope: 'delete', description: 'Delete' },
|
|
80
|
+
],
|
|
81
|
+
evidence: { ...base.evidence!, policy: { tool: 'argocd', auto: false, selfHeal: false, prune: false, suspended: null } },
|
|
82
|
+
})
|
|
83
|
+
expect(html).toContain('Replicas:')
|
|
84
|
+
expect(html).toContain('Delete:')
|
|
85
|
+
})
|
|
86
|
+
})
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { ExternalLink, GitBranch, Info, Loader2 } from 'lucide-react'
|
|
2
|
+
import { clsx } from 'clsx'
|
|
3
|
+
|
|
4
|
+
import { AlertBanner } from '../ui/drawer-components'
|
|
5
|
+
import {
|
|
6
|
+
canConfirmGitOpsWrite,
|
|
7
|
+
gitOpsWriteHeadline,
|
|
8
|
+
guardToolLabel,
|
|
9
|
+
type GitOpsWriteAssessment,
|
|
10
|
+
type GitOpsWriteGuard,
|
|
11
|
+
} from '../../utils/gitops-write-guard'
|
|
12
|
+
|
|
13
|
+
export { canConfirmGitOpsWrite }
|
|
14
|
+
|
|
15
|
+
export interface GitOpsWriteWarningProps {
|
|
16
|
+
guard: GitOpsWriteGuard
|
|
17
|
+
acknowledged: boolean
|
|
18
|
+
onAcknowledgedChange: (acknowledged: boolean) => void
|
|
19
|
+
/** Opens the GitOps owner (or Helm release) the guard names. */
|
|
20
|
+
onOpenOwner?: () => void
|
|
21
|
+
disabled?: boolean
|
|
22
|
+
className?: string
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const OWNER_LINK_LABEL = {
|
|
26
|
+
applications: 'View Argo CD Application',
|
|
27
|
+
kustomizations: 'View Flux Kustomization',
|
|
28
|
+
helmreleases: 'View Flux HelmRelease',
|
|
29
|
+
} as const
|
|
30
|
+
|
|
31
|
+
function reasonLines(perWrite: GitOpsWriteAssessment[], levels: GitOpsWriteGuard['level'][]): string[] {
|
|
32
|
+
const relevant = perWrite.filter((entry) => levels.includes(entry.level) && entry.reason)
|
|
33
|
+
const distinct = new Set(relevant.map((entry) => entry.reason))
|
|
34
|
+
if (distinct.size <= 1) return [...distinct]
|
|
35
|
+
return relevant.map((entry) =>
|
|
36
|
+
entry.write.description ? `${entry.write.description}: ${entry.reason}` : entry.reason,
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function GitOpsWriteWarning({
|
|
41
|
+
guard,
|
|
42
|
+
acknowledged,
|
|
43
|
+
onAcknowledgedChange,
|
|
44
|
+
onOpenOwner,
|
|
45
|
+
disabled = false,
|
|
46
|
+
// Not undefined: AlertBanner would add its own mb-4, and a margin utility
|
|
47
|
+
// overrides the parent's space-y gap.
|
|
48
|
+
className = '',
|
|
49
|
+
}: GitOpsWriteWarningProps) {
|
|
50
|
+
if (guard.pending) {
|
|
51
|
+
return (
|
|
52
|
+
<div
|
|
53
|
+
className={clsx(
|
|
54
|
+
'flex items-center gap-2 rounded-lg border border-theme-border bg-theme-base px-3 py-2 text-xs text-theme-text-secondary',
|
|
55
|
+
className,
|
|
56
|
+
)}
|
|
57
|
+
>
|
|
58
|
+
<Loader2 className="h-3.5 w-3.5 animate-spin" />
|
|
59
|
+
Checking GitOps ownership…
|
|
60
|
+
</div>
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
if (guard.level === 'none') return null
|
|
64
|
+
|
|
65
|
+
const headline = gitOpsWriteHeadline(guard)
|
|
66
|
+
const ownerLink = onOpenOwner ? (
|
|
67
|
+
<button
|
|
68
|
+
type="button"
|
|
69
|
+
onClick={onOpenOwner}
|
|
70
|
+
className="inline-flex items-center gap-1 text-xs font-medium text-skyhook-600 hover:underline dark:text-skyhook-400"
|
|
71
|
+
>
|
|
72
|
+
{guard.owner ? OWNER_LINK_LABEL[guard.owner.kind] : 'View Helm release'}
|
|
73
|
+
<ExternalLink className="h-3 w-3" />
|
|
74
|
+
</button>
|
|
75
|
+
) : null
|
|
76
|
+
|
|
77
|
+
if (guard.level === 'info') {
|
|
78
|
+
const lines = reasonLines(guard.perWrite, ['info'])
|
|
79
|
+
return (
|
|
80
|
+
<div
|
|
81
|
+
className={clsx(
|
|
82
|
+
'flex items-start gap-2 rounded-lg border border-theme-border bg-theme-base px-3 py-2 text-xs text-theme-text-secondary',
|
|
83
|
+
className,
|
|
84
|
+
)}
|
|
85
|
+
>
|
|
86
|
+
<Info className="mt-0.5 h-3.5 w-3.5 shrink-0 text-theme-text-tertiary" />
|
|
87
|
+
<div className="min-w-0 space-y-1">
|
|
88
|
+
<p>
|
|
89
|
+
<span className="font-medium text-theme-text-primary">{headline}</span> {lines.join(' ')}
|
|
90
|
+
</p>
|
|
91
|
+
{ownerLink}
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
94
|
+
)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const tool = guardToolLabel(guard)
|
|
98
|
+
const lines = reasonLines(guard.perWrite, ['will-revert', 'may-revert'])
|
|
99
|
+
const durableFix = guard.owner
|
|
100
|
+
? 'For a lasting change, change it in the GitOps source.'
|
|
101
|
+
: guard.helmRelease
|
|
102
|
+
? 'For a lasting change, change the release’s chart values.'
|
|
103
|
+
: null
|
|
104
|
+
return (
|
|
105
|
+
<AlertBanner
|
|
106
|
+
variant="warning"
|
|
107
|
+
icon={GitBranch}
|
|
108
|
+
title={headline}
|
|
109
|
+
className={className}
|
|
110
|
+
message={
|
|
111
|
+
lines.length === 1 ? (
|
|
112
|
+
<>
|
|
113
|
+
{lines[0]}
|
|
114
|
+
{durableFix ? ` ${durableFix}` : ''}
|
|
115
|
+
</>
|
|
116
|
+
) : durableFix ?? undefined
|
|
117
|
+
}
|
|
118
|
+
items={lines.length > 1 ? lines : undefined}
|
|
119
|
+
>
|
|
120
|
+
{ownerLink && <div className="mt-2">{ownerLink}</div>}
|
|
121
|
+
<label className="mt-2 flex cursor-pointer items-start gap-2 text-xs text-theme-text-primary">
|
|
122
|
+
<input
|
|
123
|
+
type="checkbox"
|
|
124
|
+
checked={acknowledged}
|
|
125
|
+
onChange={(event) => onAcknowledgedChange(event.target.checked)}
|
|
126
|
+
disabled={disabled}
|
|
127
|
+
className="mt-0.5 h-3.5 w-3.5 accent-skyhook-500"
|
|
128
|
+
/>
|
|
129
|
+
<span>
|
|
130
|
+
I understand {tool} {guard.level === 'will-revert' ? 'will' : 'may'} revert this.
|
|
131
|
+
</span>
|
|
132
|
+
</label>
|
|
133
|
+
</AlertBanner>
|
|
134
|
+
)
|
|
135
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getNodeLifecycle } from '../../utils/node-lifecycle'
|
|
1
2
|
import { useEffect, useState, useRef, useId } from 'react'
|
|
2
3
|
import {
|
|
3
4
|
RefreshCw,
|
|
@@ -28,11 +29,12 @@ import type { SelectedResource, WorkloadRevision } from '../../types'
|
|
|
28
29
|
import type { RadarUpgradeRequirement } from '../../types/fetch-error'
|
|
29
30
|
import { displayKindName } from '../ui/drawer-components'
|
|
30
31
|
import { getDefaultContainerName } from '../resources/resource-utils'
|
|
31
|
-
import { SetImageDialog, type
|
|
32
|
+
import { SetImageDialog, type SetImageOwnership } from './SetImageDialog'
|
|
32
33
|
import { DrainPlanDialog, DEFAULT_DRAIN_DIALOG_OPTIONS, type DrainDialogOptions, type DrainPlan } from './DrainPlanDialog'
|
|
33
34
|
import type { WorkloadImageInventory, WorkloadImageUpdate } from '../../types/core'
|
|
34
35
|
import { isArgoRolloutResource } from '../../utils/workload-rollout'
|
|
35
36
|
import { isCoreBatchJob } from '../../utils/api-resources'
|
|
37
|
+
import { formatRelativeAgeTime } from '../../utils/format'
|
|
36
38
|
|
|
37
39
|
// ============================================================================
|
|
38
40
|
// ACTIONS BAR - Interactive buttons that change based on resource kind
|
|
@@ -40,6 +42,7 @@ import { isCoreBatchJob } from '../../utils/api-resources'
|
|
|
40
42
|
|
|
41
43
|
interface ResourceActionsBarProps {
|
|
42
44
|
resource: SelectedResource
|
|
45
|
+
leadingActions?: React.ReactNode
|
|
43
46
|
data: any
|
|
44
47
|
onClose?: () => void
|
|
45
48
|
hideLogs?: boolean
|
|
@@ -78,6 +81,7 @@ interface ResourceActionsBarProps {
|
|
|
78
81
|
cascadeDependents?: CascadeDependent[]
|
|
79
82
|
cascadeLoading?: boolean
|
|
80
83
|
cascadeRootResolved?: boolean
|
|
84
|
+
cascadePreviewFailed?: boolean
|
|
81
85
|
|
|
82
86
|
// Workload restart
|
|
83
87
|
onRestart?: (params: { kind: string; namespace: string; name: string }, callbacks?: { onSuccess?: () => void; onError?: (err: unknown) => void }) => void
|
|
@@ -86,7 +90,7 @@ interface ResourceActionsBarProps {
|
|
|
86
90
|
onLoadImages?: (params: { kind: string; namespace: string; name: string }) => Promise<WorkloadImageInventory>
|
|
87
91
|
onSetImages?: (params: { kind: string; namespace: string; name: string; updates: WorkloadImageUpdate[] }) => Promise<unknown>
|
|
88
92
|
isSettingImages?: boolean
|
|
89
|
-
|
|
93
|
+
imageOwnership?: SetImageOwnership
|
|
90
94
|
|
|
91
95
|
// Rollback
|
|
92
96
|
revisions?: WorkloadRevision[]
|
|
@@ -153,15 +157,15 @@ interface ResourceActionsBarProps {
|
|
|
153
157
|
}
|
|
154
158
|
|
|
155
159
|
export function ResourceActionsBar({
|
|
156
|
-
resource, data, onClose, hideLogs, showYaml, onToggleYaml,
|
|
160
|
+
resource, data, leadingActions, onClose, hideLogs, showYaml, onToggleYaml,
|
|
157
161
|
onCompareTo,
|
|
158
162
|
onCompareAcrossClusters,
|
|
159
163
|
canExec, canViewLogs, canPortForward,
|
|
160
164
|
onOpenTerminal, onOpenLogs: openLogs, onOpenWorkloadLogs: openWorkloadLogs, onCopyCommand,
|
|
161
165
|
renderPortForward,
|
|
162
|
-
onDelete, isDeleting, cascadeDependents, cascadeLoading, cascadeRootResolved,
|
|
166
|
+
onDelete, isDeleting, cascadeDependents, cascadeLoading, cascadeRootResolved, cascadePreviewFailed,
|
|
163
167
|
onRestart, isRestarting,
|
|
164
|
-
onLoadImages, onSetImages, isSettingImages,
|
|
168
|
+
onLoadImages, onSetImages, isSettingImages, imageOwnership,
|
|
165
169
|
revisions: revisionsList, revisionsLoading, revisionsError, onRollback, isRollingBack,
|
|
166
170
|
onRolloutPromoteFull,
|
|
167
171
|
onTriggerCronJob, isTriggeringCronJob,
|
|
@@ -289,6 +293,8 @@ export function ResourceActionsBar({
|
|
|
289
293
|
|
|
290
294
|
return (
|
|
291
295
|
<div className="flex items-center gap-1.5 px-4 py-2 flex-wrap">
|
|
296
|
+
<div className="flex min-w-0 flex-wrap items-center gap-1.5 empty:hidden" data-action-group="object">
|
|
297
|
+
{leadingActions}
|
|
292
298
|
{/* Kind-specific actions (left) */}
|
|
293
299
|
{kind === 'pods' && (
|
|
294
300
|
<>
|
|
@@ -358,7 +364,7 @@ export function ResourceActionsBar({
|
|
|
358
364
|
</button>
|
|
359
365
|
)}
|
|
360
366
|
|
|
361
|
-
{canNodeWrite && (
|
|
367
|
+
{canNodeWrite && !getNodeLifecycle(data).removing && (
|
|
362
368
|
<>
|
|
363
369
|
{data?.spec?.unschedulable ? (
|
|
364
370
|
onUncordonNode && (
|
|
@@ -584,8 +590,8 @@ export function ResourceActionsBar({
|
|
|
584
590
|
</button>
|
|
585
591
|
)}
|
|
586
592
|
|
|
587
|
-
|
|
588
|
-
<div className="flex-1"
|
|
593
|
+
</div>
|
|
594
|
+
<div className="ml-auto flex flex-wrap items-center gap-1.5" data-action-group="utility">
|
|
589
595
|
|
|
590
596
|
{/* Universal actions (right-aligned). The AI/Diagnose action is NOT here — it
|
|
591
597
|
lives in the detail header chrome (see WorkloadView), set apart from these
|
|
@@ -660,7 +666,7 @@ export function ResourceActionsBar({
|
|
|
660
666
|
</div>
|
|
661
667
|
)}
|
|
662
668
|
|
|
663
|
-
{onDelete && (
|
|
669
|
+
{onDelete && !((kind === 'node' || kind === 'nodes') && data?.metadata?.deletionTimestamp) && (
|
|
664
670
|
<Tooltip content="Delete resource">
|
|
665
671
|
<button
|
|
666
672
|
onClick={() => setShowDeleteConfirm(true)}
|
|
@@ -671,6 +677,8 @@ export function ResourceActionsBar({
|
|
|
671
677
|
</Tooltip>
|
|
672
678
|
)}
|
|
673
679
|
|
|
680
|
+
</div>
|
|
681
|
+
|
|
674
682
|
<ForceDeleteConfirmDialog
|
|
675
683
|
open={showDeleteConfirm}
|
|
676
684
|
onClose={() => setShowDeleteConfirm(false)}
|
|
@@ -682,6 +690,7 @@ export function ResourceActionsBar({
|
|
|
682
690
|
cascadeDependents={cascadeDependents}
|
|
683
691
|
cascadeLoading={cascadeLoading}
|
|
684
692
|
cascadeRootResolved={cascadeRootResolved}
|
|
693
|
+
cascadePreviewFailed={cascadePreviewFailed}
|
|
685
694
|
/>
|
|
686
695
|
|
|
687
696
|
{onLoadImages && onSetImages && (
|
|
@@ -690,7 +699,7 @@ export function ResourceActionsBar({
|
|
|
690
699
|
workloadLabel={`${displayKindName(resource.kind, data?.kind)} ${resource.namespace}/${resource.name}`}
|
|
691
700
|
workloadName={resource.name}
|
|
692
701
|
workloadResource={resource.kind}
|
|
693
|
-
|
|
702
|
+
ownership={imageOwnership}
|
|
694
703
|
pending={isSettingImages}
|
|
695
704
|
onClose={() => setShowSetImage(false)}
|
|
696
705
|
onLoad={() => onLoadImages({ kind: resource.kind, namespace: resource.namespace, name: resource.name })}
|
|
@@ -1127,19 +1136,6 @@ export function RevisionHistoryDialog({ kind, namespace, name, open, onClose, re
|
|
|
1127
1136
|
setPromoteError(outcome.error ?? { message: 'The promotion did not go through.', controllerLagging: false })
|
|
1128
1137
|
}
|
|
1129
1138
|
|
|
1130
|
-
function formatTimeAgo(dateStr: string): string {
|
|
1131
|
-
const date = new Date(dateStr)
|
|
1132
|
-
const now = new Date()
|
|
1133
|
-
const seconds = Math.floor((now.getTime() - date.getTime()) / 1000)
|
|
1134
|
-
if (seconds < 60) return `${seconds}s ago`
|
|
1135
|
-
const minutes = Math.floor(seconds / 60)
|
|
1136
|
-
if (minutes < 60) return `${minutes}m ago`
|
|
1137
|
-
const hours = Math.floor(minutes / 60)
|
|
1138
|
-
if (hours < 24) return `${hours}h ago`
|
|
1139
|
-
const days = Math.floor(hours / 24)
|
|
1140
|
-
return `${days}d ago`
|
|
1141
|
-
}
|
|
1142
|
-
|
|
1143
1139
|
function getImageTag(image: string): string {
|
|
1144
1140
|
if (!image) return '-'
|
|
1145
1141
|
const parts = image.split(':')
|
|
@@ -1231,7 +1227,7 @@ export function RevisionHistoryDialog({ kind, namespace, name, open, onClose, re
|
|
|
1231
1227
|
<RevisionImage image={rev.image} displayImage={getImageTag(rev.image)} />
|
|
1232
1228
|
</td>
|
|
1233
1229
|
<td className="py-2 pr-3 text-theme-text-secondary whitespace-nowrap">
|
|
1234
|
-
{
|
|
1230
|
+
{formatRelativeAgeTime(rev.createdAt)}
|
|
1235
1231
|
</td>
|
|
1236
1232
|
<td className="py-2 text-right">
|
|
1237
1233
|
<div className="flex items-center gap-1 justify-end">
|
|
@@ -193,6 +193,7 @@ import {
|
|
|
193
193
|
CNPGDatabaseRenderer,
|
|
194
194
|
CNPGPublicationRenderer,
|
|
195
195
|
CNPGSubscriptionRenderer,
|
|
196
|
+
CNPGDatabaseRoleRenderer,
|
|
196
197
|
CNPGImageCatalogRenderer,
|
|
197
198
|
KyvernoGlobalContextRenderer,
|
|
198
199
|
KyvernoUpdateRequestRenderer,
|
|
@@ -454,7 +455,7 @@ const KNOWN_KINDS = new Set([
|
|
|
454
455
|
'rollouts', 'analysisruns', 'analysistemplates', 'clusteranalysistemplates', 'experiments', 'certificates', 'workflows', 'persistentvolumes',
|
|
455
456
|
'storageclasses', 'certificaterequests', 'clusterissuers', 'issuers',
|
|
456
457
|
'orders', 'challenges',
|
|
457
|
-
'gateways', 'gatewayclasses', 'httproutes', 'grpcroutes', 'tcproutes', 'tlsroutes', 'sealedsecrets', 'workflowtemplates', 'clusterworkflowtemplates',
|
|
458
|
+
'gateways', 'gatewayclasses', 'httproutes', 'grpcroutes', 'tcproutes', 'tlsroutes', 'udproutes', 'sealedsecrets', 'workflowtemplates', 'clusterworkflowtemplates',
|
|
458
459
|
'networkpolicies', 'networkpolicy',
|
|
459
460
|
'globalnetworkpolicies', 'globalnetworkpolicy',
|
|
460
461
|
'stagednetworkpolicies', 'stagednetworkpolicy',
|
|
@@ -494,7 +495,7 @@ const KNOWN_KINDS = new Set([
|
|
|
494
495
|
'externalsecrets', 'clusterexternalsecrets', 'secretstores', 'clustersecretstores',
|
|
495
496
|
'clusters', 'scheduledbackups', 'poolers', 'objectstores',
|
|
496
497
|
'globalcontextentries', 'updaterequests', 'ephemeralreports', 'clusterephemeralreports',
|
|
497
|
-
'databases', 'publications', 'imagecatalogs', 'clusterimagecatalogs',
|
|
498
|
+
'databases', 'publications', 'imagecatalogs', 'clusterimagecatalogs', 'databaseroles',
|
|
498
499
|
'virtualservices', 'destinationrules', 'serviceentries',
|
|
499
500
|
'peerauthentications', 'authorizationpolicies',
|
|
500
501
|
'mutatingwebhookconfigurations', 'validatingwebhookconfigurations',
|
|
@@ -702,7 +703,7 @@ export function ResourceRendererDispatch({
|
|
|
702
703
|
const isGroupGatedKind =
|
|
703
704
|
kind === 'clusters' || kind === 'backups' || kind === 'scheduledbackups' || kind === 'poolers'
|
|
704
705
|
|| kind === 'objectstores' || kind === 'databases' || kind === 'publications'
|
|
705
|
-
|| kind === 'subscriptions' || kind === 'imagecatalogs' || kind === 'clusterimagecatalogs' || kind === 'jobsets'
|
|
706
|
+
|| kind === 'subscriptions' || kind === 'imagecatalogs' || kind === 'clusterimagecatalogs' || kind === 'databaseroles' || kind === 'jobsets'
|
|
706
707
|
|| kind === 'policies' || kind === 'rollouts' || kind === 'experiments'
|
|
707
708
|
|| kind === 'rayjobs' || kind === 'rayclusters' || kind === 'rayservices' || kind === 'admissionchecks' || kind === 'provisioningrequests'
|
|
708
709
|
|| kind === 'machines' || kind === 'machinesets' || kind === 'workloads' || kind === 'localqueues' || kind === 'clusterqueues'
|
|
@@ -713,7 +714,7 @@ export function ResourceRendererDispatch({
|
|
|
713
714
|
|| ((kind === 'scheduledbackups' || kind === 'poolers') && isCNPGApiVersion)
|
|
714
715
|
|| (kind === 'objectstores' && isApiGroup(data?.apiVersion, 'barmancloud.cnpg.io'))
|
|
715
716
|
|| ((kind === 'databases' || kind === 'publications' || kind === 'imagecatalogs'
|
|
716
|
-
|| kind === 'clusterimagecatalogs') && isCNPGApiVersion)
|
|
717
|
+
|| kind === 'clusterimagecatalogs' || kind === 'databaseroles') && isCNPGApiVersion)
|
|
717
718
|
|| (kind === 'subscriptions'
|
|
718
719
|
&& (isCNPGApiVersion || isApiGroup(data?.apiVersion, 'messaging.knative.dev')))
|
|
719
720
|
|| (kind === 'policies' && isApiGroup(data?.apiVersion, 'kyverno.io'))
|
|
@@ -800,7 +801,7 @@ export function ResourceRendererDispatch({
|
|
|
800
801
|
{kind !== 'events' && <EventsSection fullscreen={!!renderSidebar} events={events || []} updates={updates || []} isLoading={eventsLoading ?? false} eventsError={eventsError ?? null} updatesError={updatesError ?? null} hint={eventsHint} />}
|
|
801
802
|
<LabelsSection data={data} />
|
|
802
803
|
<AnnotationsSection data={data} />
|
|
803
|
-
<MetadataSection data={data} />
|
|
804
|
+
<MetadataSection data={data} onNavigate={onNavigate} />
|
|
804
805
|
</>
|
|
805
806
|
)
|
|
806
807
|
|
|
@@ -860,6 +861,7 @@ export function ResourceRendererDispatch({
|
|
|
860
861
|
{kind === 'grpcroutes' && <GRPCRouteRenderer data={data} onNavigate={onNavigate} />}
|
|
861
862
|
{kind === 'tcproutes' && <SimpleRouteRenderer data={data} kind="TCPRoute" onNavigate={onNavigate} />}
|
|
862
863
|
{kind === 'tlsroutes' && <SimpleRouteRenderer data={data} kind="TLSRoute" onNavigate={onNavigate} />}
|
|
864
|
+
{kind === 'udproutes' && <SimpleRouteRenderer data={data} kind="UDPRoute" onNavigate={onNavigate} />}
|
|
863
865
|
{kind === 'sealedsecrets' && <SealedSecretRenderer data={data} onNavigate={onNavigate} />}
|
|
864
866
|
{(kind === 'workflowtemplates' || kind === 'clusterworkflowtemplates') && <WorkflowTemplateRenderer data={data} />}
|
|
865
867
|
{isCoreNetworkPolicy && <NetworkPolicyRenderer data={data} />}
|
|
@@ -926,6 +928,7 @@ export function ResourceRendererDispatch({
|
|
|
926
928
|
{kind === 'databases' && isApiGroup(data.apiVersion, CNPG_GROUP) && <CNPGDatabaseComp data={data} onNavigate={onNavigate} />}
|
|
927
929
|
{kind === 'publications' && isApiGroup(data.apiVersion, CNPG_GROUP) && <CNPGPublicationComp data={data} onNavigate={onNavigate} />}
|
|
928
930
|
{kind === 'subscriptions' && isApiGroup(data.apiVersion, CNPG_GROUP) && <CNPGSubscriptionComp data={data} onNavigate={onNavigate} />}
|
|
931
|
+
{kind === 'databaseroles' && isApiGroup(data.apiVersion, CNPG_GROUP) && <CNPGDatabaseRoleRenderer data={data} onNavigate={onNavigate} />}
|
|
929
932
|
{(kind === 'imagecatalogs' || kind === 'clusterimagecatalogs') && isApiGroup(data.apiVersion, CNPG_GROUP) && <CNPGImageCatalogComp data={data} onNavigate={onNavigate} />}
|
|
930
933
|
{kind === 'backups' && isApiGroup(data.apiVersion, CNPG_GROUP) && <CNPGBackupRenderer data={data} onNavigate={onNavigate} />}
|
|
931
934
|
{kind === 'backups' && isApiGroup(data.apiVersion, 'velero.io') && <VeleroBackupComp data={data} onNavigate={onNavigate} />}
|
|
@@ -971,14 +974,14 @@ export function ResourceRendererDispatch({
|
|
|
971
974
|
{kind === 'azuremanagedcontrolplanes' && <AzureManagedControlPlaneRenderer data={data} onNavigate={onNavigate} />}
|
|
972
975
|
{kind === 'azuremanagedmachinepools' && <AzureManagedMachinePoolRenderer data={data} onNavigate={onNavigate} />}
|
|
973
976
|
{kind === 'azuremachines' && <AzureMachineRenderer data={data} onNavigate={onNavigate} />}
|
|
974
|
-
{kind === 'virtualservices' && <IstioVirtualServiceRenderer data={data} onNavigate={onNavigate} />}
|
|
975
|
-
{kind === 'destinationrules' && <IstioDestinationRuleRenderer data={data} onNavigate={onNavigate} />}
|
|
977
|
+
{kind === 'virtualservices' && <IstioVirtualServiceRenderer data={data} onNavigate={onNavigate} serviceDomain={relationships?.istioServiceDomain} />}
|
|
978
|
+
{kind === 'destinationrules' && <IstioDestinationRuleRenderer data={data} onNavigate={onNavigate} serviceDomain={relationships?.istioServiceDomain} />}
|
|
976
979
|
{kind === 'serviceentries' && <IstioServiceEntryRenderer data={data} />}
|
|
977
980
|
{kind === 'peerauthentications' && <IstioPeerAuthenticationRenderer data={data} />}
|
|
978
981
|
{kind === 'authorizationpolicies' && <IstioAuthorizationPolicyRenderer data={data} />}
|
|
979
982
|
{kind === 'mutatingwebhookconfigurations' && <WebhookConfigRenderer data={data} isMutating />}
|
|
980
983
|
{kind === 'validatingwebhookconfigurations' && <WebhookConfigRenderer data={data} />}
|
|
981
|
-
{kind === 'ingressclasses' && <IngressClassRenderer data={data} />}
|
|
984
|
+
{kind === 'ingressclasses' && <IngressClassRenderer data={data} onNavigate={onNavigate} />}
|
|
982
985
|
{kind === 'priorityclasses' && <PriorityClassRenderer data={data} />}
|
|
983
986
|
{kind === 'runtimeclasses' && <RuntimeClassRenderer data={data} />}
|
|
984
987
|
{kind === 'leases' && <LeaseRenderer data={data} />}
|
|
@@ -1190,7 +1193,7 @@ export function getResourceStatus(kind: string, data: any): { text: string; colo
|
|
|
1190
1193
|
return getGatewayStatus(data)
|
|
1191
1194
|
}
|
|
1192
1195
|
if (k === 'gatewayclasses') return getGatewayClassStatus(data)
|
|
1193
|
-
if (k === 'httproutes' || k === 'grpcroutes' || k === 'tcproutes' || k === 'tlsroutes') return getRouteStatus(data)
|
|
1196
|
+
if (k === 'httproutes' || k === 'grpcroutes' || k === 'tcproutes' || k === 'tlsroutes' || k === 'udproutes') return getRouteStatus(data)
|
|
1194
1197
|
if (k === 'sealedsecrets') return getSealedSecretStatus(data)
|
|
1195
1198
|
if (k === 'poddisruptionbudgets') return getPDBStatus(data)
|
|
1196
1199
|
if (k === 'gitrepositories') return getGitRepositoryStatus(data)
|
|
@@ -1247,7 +1250,7 @@ export function getResourceStatus(kind: string, data: any): { text: string; colo
|
|
|
1247
1250
|
}
|
|
1248
1251
|
// Group-guarded for the same reason as the renderers: these plurals are
|
|
1249
1252
|
// generic enough that another operator can serve them.
|
|
1250
|
-
if ((k === 'databases' || k === 'publications' || k === 'subscriptions') && isApiGroup(data.apiVersion, CNPG_GROUP)) {
|
|
1253
|
+
if ((k === 'databases' || k === 'publications' || k === 'subscriptions' || k === 'databaseroles') && isApiGroup(data.apiVersion, CNPG_GROUP)) {
|
|
1251
1254
|
return getCNPGDeclarativeStatus(data)
|
|
1252
1255
|
}
|
|
1253
1256
|
if (k === 'globalcontextentries') return getKyvernoGlobalContextStatus(data)
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest'
|
|
2
2
|
|
|
3
3
|
import type { WorkloadContainerImage } from '../../types/core'
|
|
4
|
+
import { canConfirmGitOpsWrite, evaluateGitOpsWriteGuard, gitOpsWriteGuardKey } from '../../utils/gitops-write-guard'
|
|
4
5
|
import {
|
|
6
|
+
SET_IMAGE_WRITES,
|
|
5
7
|
canSubmitImageUpdates,
|
|
6
8
|
changedImageUpdates,
|
|
7
9
|
describeImageUpdateBehavior,
|
|
8
10
|
reconcileRefreshedImageDrafts,
|
|
11
|
+
setImageWrites,
|
|
9
12
|
} from './SetImageDialog'
|
|
10
13
|
|
|
11
14
|
const containers: WorkloadContainerImage[] = [
|
|
@@ -104,26 +107,92 @@ describe('reconcileRefreshedImageDrafts', () => {
|
|
|
104
107
|
})
|
|
105
108
|
|
|
106
109
|
describe('canSubmitImageUpdates', () => {
|
|
110
|
+
const target = { kind: 'Deployment', group: 'apps', namespace: 'prod', name: 'api' }
|
|
111
|
+
const managedGuard = evaluateGitOpsWriteGuard({
|
|
112
|
+
target,
|
|
113
|
+
owner: { tool: 'argocd', kind: 'applications', namespace: 'argocd', name: 'api' },
|
|
114
|
+
writes: SET_IMAGE_WRITES,
|
|
115
|
+
})
|
|
116
|
+
const unmanagedGuard = evaluateGitOpsWriteGuard({ target, owner: null, writes: SET_IMAGE_WRITES })
|
|
117
|
+
const pendingGuard = evaluateGitOpsWriteGuard({ target, owner: null, ownerPending: true, writes: SET_IMAGE_WRITES })
|
|
107
118
|
const ready = {
|
|
108
119
|
updateCount: 1,
|
|
109
120
|
hasEmptyImage: false,
|
|
110
|
-
|
|
111
|
-
ownershipResolved: true,
|
|
112
|
-
acknowledged: false,
|
|
121
|
+
ownershipConfirmable: canConfirmGitOpsWrite(unmanagedGuard, false),
|
|
113
122
|
busy: false,
|
|
114
123
|
}
|
|
115
124
|
|
|
116
125
|
it('requires break-glass acknowledgement for managed resources', () => {
|
|
117
|
-
expect(
|
|
126
|
+
expect(managedGuard.requiresAck).toBe(true)
|
|
127
|
+
expect(
|
|
128
|
+
canSubmitImageUpdates({ ...ready, ownershipConfirmable: canConfirmGitOpsWrite(managedGuard, false) }),
|
|
129
|
+
).toBe(false)
|
|
118
130
|
expect(
|
|
119
|
-
canSubmitImageUpdates({ ...ready,
|
|
131
|
+
canSubmitImageUpdates({ ...ready, ownershipConfirmable: canConfirmGitOpsWrite(managedGuard, true) }),
|
|
120
132
|
).toBe(true)
|
|
121
133
|
})
|
|
122
134
|
|
|
135
|
+
it('gates until ownership resolves', () => {
|
|
136
|
+
expect(canConfirmGitOpsWrite(undefined, true)).toBe(false)
|
|
137
|
+
expect(canConfirmGitOpsWrite(pendingGuard, true)).toBe(false)
|
|
138
|
+
expect(canSubmitImageUpdates(ready)).toBe(true)
|
|
139
|
+
})
|
|
140
|
+
|
|
123
141
|
it('blocks empty images and concurrent submissions', () => {
|
|
124
142
|
expect(canSubmitImageUpdates({ ...ready, hasEmptyImage: true })).toBe(false)
|
|
125
143
|
expect(canSubmitImageUpdates({ ...ready, busy: true })).toBe(false)
|
|
126
144
|
expect(canSubmitImageUpdates({ ...ready, loadFailed: true })).toBe(false)
|
|
127
|
-
expect(canSubmitImageUpdates({ ...ready,
|
|
145
|
+
expect(canSubmitImageUpdates({ ...ready, ownershipConfirmable: false })).toBe(false)
|
|
146
|
+
})
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
describe('setImageWrites', () => {
|
|
150
|
+
it('names each container, so the verdict is for exactly the images being changed', () => {
|
|
151
|
+
expect(setImageWrites(containers).map((w) => w.paths)).toEqual([
|
|
152
|
+
['spec.template.spec.containers[name=app].image'],
|
|
153
|
+
['spec.template.spec.containers[name=sidecar].image'],
|
|
154
|
+
['spec.template.spec.initContainers[name=migrate].image'],
|
|
155
|
+
])
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
it("asks again when the evidence doesn't cover a changed container", () => {
|
|
159
|
+
// Evidence read before the dialog listed the init container covers only
|
|
160
|
+
// the app image, which the owner ignores.
|
|
161
|
+
const guard = evaluateGitOpsWriteGuard({
|
|
162
|
+
target: { kind: 'Deployment', group: 'apps', namespace: 'prod', name: 'api' },
|
|
163
|
+
owner: { tool: 'argocd', kind: 'applications', namespace: 'argocd', name: 'api' },
|
|
164
|
+
writes: setImageWrites([{ type: 'initContainer', name: 'migrate' }]),
|
|
165
|
+
evidence: {
|
|
166
|
+
uid: 'u',
|
|
167
|
+
resourceVersion: '1',
|
|
168
|
+
owner: null,
|
|
169
|
+
policy: { tool: 'argocd', auto: true, selfHeal: true, prune: false, suspended: null, respectIgnoreDifferences: true },
|
|
170
|
+
paths: [{ path: 'spec.template.spec.containers[name=app].image', lastApplied: 'present', ownedBy: [], ownedByGitOps: false, ignored: 'effective', ignoredBy: 'spec.ignoreDifferences' }],
|
|
171
|
+
},
|
|
172
|
+
})
|
|
173
|
+
expect(guard.level).toBe('may-revert')
|
|
174
|
+
expect(canConfirmGitOpsWrite(guard, false)).toBe(false)
|
|
175
|
+
})
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
describe('acknowledgment scope', () => {
|
|
179
|
+
it("an acknowledgment for one container's image doesn't cover another's with the same verdict", () => {
|
|
180
|
+
const guardFor = (name: string) =>
|
|
181
|
+
evaluateGitOpsWriteGuard({
|
|
182
|
+
target: { kind: 'Deployment', group: 'apps', namespace: 'prod', name: 'api' },
|
|
183
|
+
owner: { tool: 'argocd', kind: 'applications', namespace: 'argocd', name: 'api' },
|
|
184
|
+
writes: setImageWrites([{ type: 'container', name }]),
|
|
185
|
+
evidence: {
|
|
186
|
+
uid: 'u',
|
|
187
|
+
resourceVersion: '1',
|
|
188
|
+
owner: null,
|
|
189
|
+
policy: { tool: 'argocd', auto: true, selfHeal: true, prune: false, suspended: null },
|
|
190
|
+
paths: ['app', 'sidecar'].map((n) => ({ path: `spec.template.spec.containers[name=${n}].image`, lastApplied: 'present' as const, ownedBy: [], ownedByGitOps: false })),
|
|
191
|
+
},
|
|
192
|
+
})
|
|
193
|
+
const sidecar = guardFor('sidecar')
|
|
194
|
+
const app = guardFor('app')
|
|
195
|
+
expect(app.level).toBe(sidecar.level)
|
|
196
|
+
expect(gitOpsWriteGuardKey(app)).not.toBe(gitOpsWriteGuardKey(sidecar))
|
|
128
197
|
})
|
|
129
198
|
})
|