@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
|
@@ -40,6 +40,7 @@ import { isArgoOperationInProgress, isArgoSuspendedByRadar } from '../resources/
|
|
|
40
40
|
import { toggleSet } from './GitOpsGraphFilterRail'
|
|
41
41
|
import { useFilterState, defineFilterSchema } from '../../filter-state'
|
|
42
42
|
import { parseContextName } from '../../utils/context-name'
|
|
43
|
+
import { formatCompactAge, formatRelativeAgeTime } from '../../utils/format'
|
|
43
44
|
|
|
44
45
|
// =============================================================================
|
|
45
46
|
// GitOpsTableView — the canonical GitOps fleet list (Argo + Flux).
|
|
@@ -1373,8 +1374,8 @@ function GitOpsTable({
|
|
|
1373
1374
|
)}
|
|
1374
1375
|
<TableCell>
|
|
1375
1376
|
{row.terminating
|
|
1376
|
-
? <span className="text-orange-400/80">
|
|
1377
|
-
:
|
|
1377
|
+
? <span className="text-orange-400/80">{pendingFor(row.terminationStartedAt)}</span>
|
|
1378
|
+
: row.lastSync ? formatRelativeAgeTime(row.lastSync, '') : row.createdAt ? `Created ${formatRelativeAgeTime(row.createdAt, '')}` : ''}
|
|
1378
1379
|
</TableCell>
|
|
1379
1380
|
{showActions && onRowAction && (
|
|
1380
1381
|
<td
|
|
@@ -1603,9 +1604,9 @@ function GitOpsTile({
|
|
|
1603
1604
|
<div className="truncate font-mono text-[11px] text-theme-text-tertiary">{shortRevision(revision)}</div>
|
|
1604
1605
|
)}
|
|
1605
1606
|
{row.terminating ? (
|
|
1606
|
-
<div className="font-medium text-orange-400/80">
|
|
1607
|
+
<div className="font-medium text-orange-400/80">{pendingFor(row.terminationStartedAt)}</div>
|
|
1607
1608
|
) : (
|
|
1608
|
-
lastSyncRaw && <div className={clsx('font-medium', recencyClass)}>{
|
|
1609
|
+
lastSyncRaw && <div className={clsx('font-medium', recencyClass)}>{formatRelativeAgeTime(lastSyncRaw, '')}</div>
|
|
1609
1610
|
)}
|
|
1610
1611
|
</div>
|
|
1611
1612
|
{(dest || ns || row.project) && (
|
|
@@ -1926,26 +1927,10 @@ function compactClusterURL(dest: string): string {
|
|
|
1926
1927
|
.replace(/^kubernetes\.default\.svc(:\d+)?\/?$/, 'in-cluster')
|
|
1927
1928
|
}
|
|
1928
1929
|
|
|
1929
|
-
//
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
if (!rfc3339) return ''
|
|
1934
|
-
const time = Date.parse(rfc3339)
|
|
1935
|
-
if (!Number.isFinite(time)) return ''
|
|
1936
|
-
const diffMs = Date.now() - time
|
|
1937
|
-
const s = Math.max(0, Math.floor(diffMs / 1000))
|
|
1938
|
-
if (s < 60) return `${s}s ago`
|
|
1939
|
-
const m = Math.floor(s / 60)
|
|
1940
|
-
if (m < 60) return `${m}m ago`
|
|
1941
|
-
const h = Math.floor(m / 60)
|
|
1942
|
-
if (h < 24) return `${h}h ago`
|
|
1943
|
-
const d = Math.floor(h / 24)
|
|
1944
|
-
if (d < 30) return `${d}d ago`
|
|
1945
|
-
const mo = Math.floor(d / 30)
|
|
1946
|
-
if (mo < 12) return `${mo}mo ago`
|
|
1947
|
-
const y = Math.floor(d / 365)
|
|
1948
|
-
return `${y}y ago`
|
|
1930
|
+
// How long deletion has been pending, not when it started.
|
|
1931
|
+
function pendingFor(startedAt?: string): string {
|
|
1932
|
+
const age = formatCompactAge(startedAt)
|
|
1933
|
+
return age ? `Pending for ${age}` : 'Pending'
|
|
1949
1934
|
}
|
|
1950
1935
|
|
|
1951
1936
|
// =============================================================================
|
|
@@ -82,7 +82,7 @@ export function describeGitOpsTerminating(summary?: {
|
|
|
82
82
|
finalizers?: string[]
|
|
83
83
|
}): { chipTooltip: string; actionDisabledTooltip: string } {
|
|
84
84
|
const ageText = formatCompactAge(summary?.terminationStartedAt)
|
|
85
|
-
const ageSuffix = ageText ? ` ${ageText}
|
|
85
|
+
const ageSuffix = ageText ? ` for ${ageText}` : ''
|
|
86
86
|
const finalizers = summary?.finalizers ?? []
|
|
87
87
|
const finSuffix = finalizers.length > 0 ? ` Finalizers: ${finalizers.join(', ')}.` : ''
|
|
88
88
|
const ageInline = ageText ? ` (${ageText})` : ''
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { renderToStaticMarkup } from 'react-dom/server'
|
|
2
|
+
import { describe, expect, test } from 'vitest'
|
|
3
|
+
|
|
4
|
+
import type { GitOpsChange, GitOpsDrift, GitOpsInsight, GitOpsResourceDiff } from '../../../types'
|
|
5
|
+
import { ArgoResourceDiff } from './ArgoResourceDiff'
|
|
6
|
+
import { GitOpsChangesView } from './GitOpsInsightViews'
|
|
7
|
+
|
|
8
|
+
// Shape seen live on Argo CD 3.5.2: the Deployment was scaled by hand (1 -> 3)
|
|
9
|
+
// and a Git commit bumping the image hasn't synced yet. last-applied holds the
|
|
10
|
+
// manifest from the last sync, so it only sees the scale.
|
|
11
|
+
const lastApplied: GitOpsDrift = {
|
|
12
|
+
source: 'lastAppliedAnnotation',
|
|
13
|
+
entries: [{ path: 'spec.replicas', op: 'changed', desired: '1', live: '3' }],
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const argoDiff: GitOpsResourceDiff = {
|
|
17
|
+
source: 'argocd-api',
|
|
18
|
+
desired: 'spec:\n replicas: 1\n image: nginx:1.28-alpine\n',
|
|
19
|
+
live: 'spec:\n replicas: 3\n image: nginx:1.27-alpine\n',
|
|
20
|
+
fieldEntries: [
|
|
21
|
+
{ path: 'spec.replicas', op: 'changed', desired: '1', live: '3' },
|
|
22
|
+
{ path: 'spec.template.spec.containers.[0].image', op: 'changed', desired: '"nginx:1.28-alpine"', live: '"nginx:1.27-alpine"' },
|
|
23
|
+
],
|
|
24
|
+
redacted: false,
|
|
25
|
+
hook: false,
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const IMAGE_PATH = 'spec.template.spec.containers.[0].image'
|
|
29
|
+
|
|
30
|
+
const count = (html: string, needle: string) => html.split(needle).length - 1
|
|
31
|
+
|
|
32
|
+
describe('ArgoResourceDiff field list', () => {
|
|
33
|
+
test("shows Argo's field list above the YAML diff, including the unsynced Git change", () => {
|
|
34
|
+
const html = renderToStaticMarkup(<ArgoResourceDiff diff={argoDiff} loading={false} fallbackDrift={lastApplied} />)
|
|
35
|
+
expect(html).toContain('Field diff')
|
|
36
|
+
expect(html).toContain(IMAGE_PATH)
|
|
37
|
+
expect(html).toContain('via Argo CD API')
|
|
38
|
+
expect(html.indexOf('Field diff')).toBeLessThan(html.indexOf('desired (Git-rendered)'))
|
|
39
|
+
// One list, not Argo's plus the last-applied fallback.
|
|
40
|
+
expect(count(html, 'Field diff')).toBe(1)
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
test('renders no field list box when Argo reports no field differences', () => {
|
|
44
|
+
const html = renderToStaticMarkup(<ArgoResourceDiff diff={{ ...argoDiff, fieldEntries: [] }} loading={false} fallbackDrift={lastApplied} />)
|
|
45
|
+
expect(html).not.toContain('Field diff')
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
test('treats a null field list (sent by older Radars) as empty instead of crashing', () => {
|
|
49
|
+
const html = renderToStaticMarkup(<ArgoResourceDiff diff={{ ...argoDiff, fieldEntries: null }} loading={false} fallbackDrift={lastApplied} />)
|
|
50
|
+
expect(html).not.toContain('Field diff')
|
|
51
|
+
expect(html).toContain('desired (Git-rendered)')
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
test('says the list was cut short instead of presenting the cap as the count', () => {
|
|
55
|
+
const html = renderToStaticMarkup(<ArgoResourceDiff diff={{ ...argoDiff, fieldEntriesTruncated: true }} loading={false} />)
|
|
56
|
+
expect(html).toContain('showing first 50 entries')
|
|
57
|
+
expect(html).not.toContain('2 fields')
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
test("falls back to the last-applied list when Argo's diff fails to load", () => {
|
|
61
|
+
const html = renderToStaticMarkup(<ArgoResourceDiff loading={false} error="Failed to fetch the diff from the Argo CD API server." fallbackDrift={lastApplied} />)
|
|
62
|
+
expect(html).toContain('Field diff')
|
|
63
|
+
expect(html).toContain('spec.replicas')
|
|
64
|
+
expect(html).not.toContain('via Argo CD API')
|
|
65
|
+
expect(html).toContain('Failed to fetch the diff from the Argo CD API server.')
|
|
66
|
+
})
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
describe('GitOpsChangesView expanded Argo row', () => {
|
|
70
|
+
const change: GitOpsChange = {
|
|
71
|
+
ref: { group: 'apps', kind: 'Deployment', namespace: 'web', name: 'web' },
|
|
72
|
+
category: 'OutOfSync',
|
|
73
|
+
sync: 'OutOfSync',
|
|
74
|
+
health: 'Healthy',
|
|
75
|
+
hasDesired: true,
|
|
76
|
+
hasLive: true,
|
|
77
|
+
drift: lastApplied,
|
|
78
|
+
partial: false,
|
|
79
|
+
}
|
|
80
|
+
const insight = (argoDiffAvailable: boolean): GitOpsInsight => ({
|
|
81
|
+
summary: { tool: 'argocd', kind: 'Application', namespace: 'argocd', name: 'web', sync: 'OutOfSync', health: 'Healthy' },
|
|
82
|
+
changes: [change],
|
|
83
|
+
capabilities: {
|
|
84
|
+
sync: true, refresh: true, terminate: false, suspend: false, resume: false,
|
|
85
|
+
syncWithSource: false, selectiveSync: true, rollback: false, argoDiffAvailable,
|
|
86
|
+
},
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
test("hands the last-applied list to the Argo diff instead of rendering it alongside", () => {
|
|
90
|
+
let passed: GitOpsDrift | undefined
|
|
91
|
+
const html = renderToStaticMarkup(
|
|
92
|
+
<GitOpsChangesView
|
|
93
|
+
insight={insight(true)}
|
|
94
|
+
focusKey="Deployment/web/web"
|
|
95
|
+
renderResourceDiff={(_ref, fallbackDrift) => {
|
|
96
|
+
passed = fallbackDrift
|
|
97
|
+
return <div data-testid="argo-diff" />
|
|
98
|
+
}}
|
|
99
|
+
/>,
|
|
100
|
+
)
|
|
101
|
+
expect(html).toContain('argo-diff')
|
|
102
|
+
expect(html).not.toContain('Field diff')
|
|
103
|
+
expect(passed).toEqual(lastApplied)
|
|
104
|
+
// The row's "N diff" badge counts the last-applied list, which can
|
|
105
|
+
// undercount here, so it isn't shown.
|
|
106
|
+
expect(html).not.toContain('1 diff')
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
test('without the Argo diff, the row still shows the last-applied list', () => {
|
|
110
|
+
const html = renderToStaticMarkup(<GitOpsChangesView insight={insight(false)} focusKey="Deployment/web/web" />)
|
|
111
|
+
expect(html).toContain('Field diff')
|
|
112
|
+
expect(html).toContain('spec.replicas')
|
|
113
|
+
expect(html).toContain('1 diff')
|
|
114
|
+
})
|
|
115
|
+
})
|
|
@@ -2,18 +2,23 @@ import { useState } from 'react'
|
|
|
2
2
|
import { createTwoFilesPatch } from 'diff'
|
|
3
3
|
import { clsx } from 'clsx'
|
|
4
4
|
import { FileText, GitCompare, Maximize2, Rows, ShieldOff, X } from 'lucide-react'
|
|
5
|
-
import type { GitOpsResourceDiff } from '../../../types'
|
|
5
|
+
import type { GitOpsDrift, GitOpsResourceDiff } from '../../../types'
|
|
6
6
|
import { DiffLine, hasDiffBodyChange } from '../../shared/UnifiedDiff'
|
|
7
7
|
import { CodeViewer } from '../../ui/CodeViewer'
|
|
8
8
|
import { DialogPortal } from '../../ui/DialogPortal'
|
|
9
9
|
import { YamlDiffEditor } from '../../ui/YamlEditor'
|
|
10
10
|
import { Tooltip } from '../../ui/Tooltip'
|
|
11
|
+
import { DriftPanel } from './DriftPanel'
|
|
11
12
|
|
|
12
13
|
interface ArgoResourceDiffProps {
|
|
13
14
|
diff?: GitOpsResourceDiff | null
|
|
14
15
|
loading: boolean
|
|
15
16
|
// Server error string ({"error"} body), surfaced inline — not a toast.
|
|
16
17
|
error?: string | null
|
|
18
|
+
// The row's last-applied field list, shown only when Argo's diff can't be
|
|
19
|
+
// loaded. A loaded diff carries Argo's own field list, which also covers Git
|
|
20
|
+
// changes that haven't synced yet.
|
|
21
|
+
fallbackDrift?: GitOpsDrift | null
|
|
17
22
|
}
|
|
18
23
|
|
|
19
24
|
// ArgoResourceDiff renders the full Git-rendered desired-vs-live diff for a
|
|
@@ -21,7 +26,8 @@ interface ArgoResourceDiffProps {
|
|
|
21
26
|
// fetch and passes data / loading / error. The inline view is a compact unified
|
|
22
27
|
// preview; the maximize control opens the full view (Diff / Live manifest /
|
|
23
28
|
// Desired manifest, with the Monaco-based YamlDiffEditor powering the Diff tab).
|
|
24
|
-
|
|
29
|
+
// Argo's per-field list sits above the preview, and only when it has entries.
|
|
30
|
+
export function ArgoResourceDiff({ diff, loading, error, fallbackDrift }: ArgoResourceDiffProps) {
|
|
25
31
|
const [maximized, setMaximized] = useState(false)
|
|
26
32
|
|
|
27
33
|
if (loading) {
|
|
@@ -42,19 +48,30 @@ export function ArgoResourceDiff({ diff, loading, error }: ArgoResourceDiffProps
|
|
|
42
48
|
</div>
|
|
43
49
|
)
|
|
44
50
|
}
|
|
51
|
+
const fallbackPanel = fallbackDrift && fallbackDrift.entries.length > 0 ? <DriftPanel drift={fallbackDrift} /> : null
|
|
45
52
|
if (error) {
|
|
46
53
|
return (
|
|
47
|
-
<div className="
|
|
48
|
-
{
|
|
54
|
+
<div className="space-y-3">
|
|
55
|
+
{fallbackPanel}
|
|
56
|
+
<div className="rounded-md border border-red-500/40 bg-red-500/5 px-3 py-2 text-xs text-red-700 dark:text-red-400">
|
|
57
|
+
{error}
|
|
58
|
+
</div>
|
|
49
59
|
</div>
|
|
50
60
|
)
|
|
51
61
|
}
|
|
52
|
-
if (!diff) return
|
|
62
|
+
if (!diff) return fallbackPanel
|
|
53
63
|
|
|
54
64
|
const unchanged = !docsDiffer(diff.desired, diff.live)
|
|
65
|
+
// Older Radars send null for an empty list.
|
|
66
|
+
const fieldEntries = diff.fieldEntries ?? []
|
|
55
67
|
|
|
56
68
|
return (
|
|
57
69
|
<div>
|
|
70
|
+
{fieldEntries.length > 0 && (
|
|
71
|
+
<div className="mb-3">
|
|
72
|
+
<DriftPanel drift={{ entries: fieldEntries, source: diff.source, truncated: diff.fieldEntriesTruncated }} />
|
|
73
|
+
</div>
|
|
74
|
+
)}
|
|
58
75
|
<div className="mb-2 flex items-center justify-between gap-2">
|
|
59
76
|
<div className="flex items-center gap-2">
|
|
60
77
|
<GitCompare className="h-3.5 w-3.5 shrink-0 text-theme-text-tertiary" />
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { GitOpsDrift, GitOpsDriftEntry } from '../../../types'
|
|
2
|
+
|
|
3
|
+
// DriftPanel renders the structured per-field diff. Format mimics a
|
|
4
|
+
// `diff -u` summary: removed paths in red, added in green, changed shown
|
|
5
|
+
// inline as "old → new". Path is monospace; values are JSON-encoded and
|
|
6
|
+
// pre-wrapped so structured values (objects, arrays) render readably.
|
|
7
|
+
export function DriftPanel({ drift }: { drift: GitOpsDrift }) {
|
|
8
|
+
const sourceLabel = driftSourceLabel(drift.source)
|
|
9
|
+
return (
|
|
10
|
+
<div>
|
|
11
|
+
<div className="mb-2 flex items-baseline justify-between gap-2">
|
|
12
|
+
<h4 className="text-[11px] font-semibold uppercase tracking-wide text-theme-text-tertiary">Field diff</h4>
|
|
13
|
+
<span className="text-[10px] text-theme-text-tertiary">
|
|
14
|
+
desired (Git) → live ·
|
|
15
|
+
{drift.truncated ? ' showing first 50 entries' : ` ${drift.entries.length} field${drift.entries.length === 1 ? '' : 's'}`}
|
|
16
|
+
{sourceLabel && ` · ${sourceLabel}`}
|
|
17
|
+
</span>
|
|
18
|
+
</div>
|
|
19
|
+
<div className="space-y-1 font-mono text-[11px]">
|
|
20
|
+
{drift.entries.map((entry, i) => (
|
|
21
|
+
<DriftEntryRow key={`${entry.path}-${i}`} entry={entry} />
|
|
22
|
+
))}
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Provenance label for a Drift's field entries. Only Argo-sourced drift gets an
|
|
29
|
+
// explicit label; the built-in last-applied path renders without one (the
|
|
30
|
+
// "desired (Git) → live" copy already implies it). Unknown sources (a library
|
|
31
|
+
// consumer lagging the backend) fall through to no label rather than surfacing
|
|
32
|
+
// a raw enum string.
|
|
33
|
+
function driftSourceLabel(source: GitOpsDrift['source']): string {
|
|
34
|
+
return source === 'argocd-api' ? 'via Argo CD API' : ''
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function DriftEntryRow({ entry }: { entry: GitOpsDriftEntry }) {
|
|
38
|
+
if (entry.op === 'removed') {
|
|
39
|
+
return (
|
|
40
|
+
<div>
|
|
41
|
+
<span className="text-red-600 dark:text-red-400">- {entry.path}</span>
|
|
42
|
+
{entry.desired && <span className="ml-2 text-theme-text-secondary">{entry.desired}</span>}
|
|
43
|
+
</div>
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
if (entry.op === 'added') {
|
|
47
|
+
return (
|
|
48
|
+
<div>
|
|
49
|
+
<span className="text-emerald-700 dark:text-emerald-400">+ {entry.path}</span>
|
|
50
|
+
{entry.live && <span className="ml-2 text-theme-text-secondary">{entry.live}</span>}
|
|
51
|
+
</div>
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
return (
|
|
55
|
+
<div>
|
|
56
|
+
<span className="text-amber-700 dark:text-amber-400">~ {entry.path}</span>
|
|
57
|
+
<span className="ml-2 text-theme-text-tertiary">{entry.desired}</span>
|
|
58
|
+
<span className="mx-1 text-theme-text-tertiary">→</span>
|
|
59
|
+
<span className="text-theme-text-primary">{entry.live}</span>
|
|
60
|
+
</div>
|
|
61
|
+
)
|
|
62
|
+
}
|
|
@@ -2,7 +2,7 @@ import { AlertTriangle, ArrowUpDown, ChevronDown, ChevronRight, ChevronUp, Circl
|
|
|
2
2
|
import { PaneLoader } from '../../ui/PaneLoader'
|
|
3
3
|
import { clsx } from 'clsx'
|
|
4
4
|
import { Fragment, useEffect, useRef, useState, type ReactNode } from 'react'
|
|
5
|
-
import type { GitOpsChange, GitOpsHistoryItem, GitOpsInsight, GitOpsInsightRef, GitOpsIssue, GitOpsPlanItem, GitOpsRemediation, GitOpsResourceTree, GitOpsTreeNode } from '../../../types'
|
|
5
|
+
import type { GitOpsChange, GitOpsDrift, GitOpsHistoryItem, GitOpsInsight, GitOpsInsightRef, GitOpsIssue, GitOpsPlanItem, GitOpsRemediation, GitOpsResourceTree, GitOpsTreeNode } from '../../../types'
|
|
6
6
|
import { HealthStatusBadge, SyncStatusBadge } from '../GitOpsStatusBadge'
|
|
7
7
|
import { radarHealthNote } from '../health-provenance'
|
|
8
8
|
import { SEVERITY_BADGE, SEVERITY_TEXT } from '../../../utils/badge-colors'
|
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
type ResourceSortKey,
|
|
31
31
|
type ResourceStatusFacet,
|
|
32
32
|
} from './insights-helpers'
|
|
33
|
+
import { DriftPanel } from './DriftPanel'
|
|
33
34
|
|
|
34
35
|
interface GitOpsStatusStripProps {
|
|
35
36
|
insight?: GitOpsInsight | null
|
|
@@ -72,7 +73,7 @@ export function GitOpsStatusStrip({ insight, loading, renderRevisionMeta }: GitO
|
|
|
72
73
|
const revision = summary.lastRevision || summary.targetRevision || ''
|
|
73
74
|
const shortRev = shortRevisionForCommit(revision)
|
|
74
75
|
const commitUrl = revision ? commitURLForRepo(summary.source, revision) : null
|
|
75
|
-
const reconcileAge =
|
|
76
|
+
const reconcileAge = formatRelativeAgeTime(summary.lastReconcile)
|
|
76
77
|
const healthSummary = buildHealthSummary(insight.changes ?? [])
|
|
77
78
|
// STUCK belongs with the operation chip — it's a property of the same
|
|
78
79
|
// state ("failed AND won't self-recover"). Co-locating FAILED + STUCK ·
|
|
@@ -222,7 +223,10 @@ function commitURLForRepo(source: string | undefined, revision: string): string
|
|
|
222
223
|
// "did everything come up?" summary. All-healthy reads green and explicit
|
|
223
224
|
// ("4/4 resources healthy"); mixed states list the unhealthy buckets so
|
|
224
225
|
// the user sees at a glance what didn't make it.
|
|
225
|
-
function buildHealthSummary(
|
|
226
|
+
function buildHealthSummary(allChanges: GitOpsChange[]): { text: string; tone: string } | null {
|
|
227
|
+
// Dependencies are consumed, not deployed, so they don't count toward
|
|
228
|
+
// "did everything come up"; a failing one has its own Issue.
|
|
229
|
+
const changes = allChanges.filter((c) => !c.dependency)
|
|
226
230
|
if (changes.length === 0) return null
|
|
227
231
|
let healthy = 0
|
|
228
232
|
let degraded = 0
|
|
@@ -257,7 +261,7 @@ function buildHealthSummary(changes: GitOpsChange[]): { text: string; tone: stri
|
|
|
257
261
|
// disclosure — still available for forensics but not noise during deletion.
|
|
258
262
|
function TerminatingStatusStrip({ summary }: { summary: NonNullable<GitOpsInsight['summary']> }) {
|
|
259
263
|
const [showHistorical, setShowHistorical] = useState(false)
|
|
260
|
-
const pending =
|
|
264
|
+
const pending = formatRelativeAgeTime(summary.terminationStartedAt) || 'recently'
|
|
261
265
|
const finalizers = summary.finalizers ?? []
|
|
262
266
|
return (
|
|
263
267
|
<div className="border-b border-orange-500/20 bg-orange-500/[0.04] px-4 py-2">
|
|
@@ -282,7 +286,7 @@ function TerminatingStatusStrip({ summary }: { summary: NonNullable<GitOpsInsigh
|
|
|
282
286
|
{(summary.lastRevision || summary.targetRevision) && (
|
|
283
287
|
<MetaFact label="Revision" value={summary.lastRevision || summary.targetRevision || '-'} mono />
|
|
284
288
|
)}
|
|
285
|
-
{summary.lastReconcile && <MetaFact label="Last reconcile" value={
|
|
289
|
+
{summary.lastReconcile && <MetaFact label="Last reconcile" value={formatRelativeAgeTime(summary.lastReconcile)} />}
|
|
286
290
|
{summary.autoSyncMode && <MetaFact label="Sync mode" value={summary.autoSyncMode} />}
|
|
287
291
|
</div>
|
|
288
292
|
</Collapse>
|
|
@@ -735,7 +739,10 @@ interface GitOpsChangesViewProps {
|
|
|
735
739
|
// returns the loaded <ArgoResourceDiff/>. When present AND
|
|
736
740
|
// insight.capabilities.argoDiffAvailable, resolvable rows expose a "Full
|
|
737
741
|
// diff" toggle. Library consumers that don't wire it get no such affordance.
|
|
738
|
-
|
|
742
|
+
// fallbackDrift is the row's last-applied field list. The loaded diff shows
|
|
743
|
+
// Argo's own field list instead, and falls back to this one when the diff
|
|
744
|
+
// can't be loaded.
|
|
745
|
+
renderResourceDiff?: (ref: GitOpsInsightRef, fallbackDrift?: GitOpsDrift) => ReactNode
|
|
739
746
|
// Opens the global Settings dialog. Backs the "Connect Argo CD" hint shown
|
|
740
747
|
// when the root is an Argo Application without the diff integration.
|
|
741
748
|
onOpenSettings?: () => void
|
|
@@ -1005,7 +1012,7 @@ export function GitOpsChangesView({ insight, error, onOpenResource, onSyncResour
|
|
|
1005
1012
|
)}
|
|
1006
1013
|
</div>
|
|
1007
1014
|
)}
|
|
1008
|
-
{totalCount > 0 && isArgoRoot && !insight.capabilities?.argoDiffAvailable && onOpenSettings && (
|
|
1015
|
+
{totalCount > 0 && isArgoRoot && !insight.capabilities?.argoDiffAvailable && !insight.capabilities?.argoConnecting && onOpenSettings && (
|
|
1009
1016
|
<div className="border-b border-theme-border bg-theme-base/40 px-4 py-2 text-[11px] text-theme-text-tertiary">
|
|
1010
1017
|
{insight.capabilities?.argoConfigured ? (
|
|
1011
1018
|
<>
|
|
@@ -1330,7 +1337,7 @@ function ChangeRow({
|
|
|
1330
1337
|
// When true, the expanded panel offers a "Full diff" toggle that mounts the
|
|
1331
1338
|
// host-wired Argo CD diff loader for this resource.
|
|
1332
1339
|
canFullDiff: boolean
|
|
1333
|
-
renderResourceDiff?: (ref: GitOpsInsightRef) => ReactNode
|
|
1340
|
+
renderResourceDiff?: (ref: GitOpsInsightRef, fallbackDrift?: GitOpsDrift) => ReactNode
|
|
1334
1341
|
onOpenResource?: (ref: GitOpsChange['ref']) => void
|
|
1335
1342
|
onSyncResource?: (ref: GitOpsChange['ref']) => void
|
|
1336
1343
|
syncResourceDisabledReason?: string
|
|
@@ -1422,8 +1429,11 @@ function ChangeRow({
|
|
|
1422
1429
|
)}
|
|
1423
1430
|
{/* Detail badges: surface that there's something to see in
|
|
1424
1431
|
the expanded panel. Without these the user has no signal
|
|
1425
|
-
that clicking will reveal anything useful.
|
|
1426
|
-
|
|
1432
|
+
that clicking will reveal anything useful. The field count
|
|
1433
|
+
comes from the last-applied list, which can undercount on an
|
|
1434
|
+
Argo row (Argo's own list loads only on expand), so it is
|
|
1435
|
+
left off there. The OutOfSync badge already marks the row. */}
|
|
1436
|
+
{!canFullDiff && driftEntries.length > 0 && (
|
|
1427
1437
|
<Tooltip content={`${driftEntries.length} field${driftEntries.length === 1 ? '' : 's'} differ from Git`} delay={200} wrapperClassName="shrink-0">
|
|
1428
1438
|
<span className="rounded border border-amber-500/40 bg-amber-500/10 px-1.5 py-0.5 text-[10px] font-medium text-amber-700 dark:text-amber-400">
|
|
1429
1439
|
{driftEntries.length} diff
|
|
@@ -1523,15 +1533,19 @@ function ChangeRow({
|
|
|
1523
1533
|
{expandable && (
|
|
1524
1534
|
<Collapse open={expanded} mountLazily>
|
|
1525
1535
|
<div className="border-t border-theme-border bg-theme-base/40 px-4 py-3">
|
|
1526
|
-
{
|
|
1536
|
+
{/* The last-applied list only knows what was applied at the last
|
|
1537
|
+
sync, so it misses a Git change that hasn't synced yet. When
|
|
1538
|
+
Argo's diff can load, it owns the field list and shows this
|
|
1539
|
+
one only if that load fails. */}
|
|
1540
|
+
{!canFullDiff && driftEntries.length > 0 && <DriftPanel drift={change.drift!} />}
|
|
1527
1541
|
{events.length > 0 && <RecentEventsPanel events={events} />}
|
|
1528
1542
|
{/* Expanding a row IS the request to see its diff — render it inline
|
|
1529
1543
|
rather than behind a second "Full diff" click. The capability is
|
|
1530
1544
|
already gated on a live Argo connection, so this only mounts (and
|
|
1531
1545
|
fetches) when the diff can actually be served. */}
|
|
1532
1546
|
{canFullDiff && (
|
|
1533
|
-
<div className={clsx(
|
|
1534
|
-
{renderResourceDiff?.(change.ref)}
|
|
1547
|
+
<div className={clsx(events.length > 0 && 'mt-3')}>
|
|
1548
|
+
{renderResourceDiff?.(change.ref, change.drift)}
|
|
1535
1549
|
</div>
|
|
1536
1550
|
)}
|
|
1537
1551
|
</div>
|
|
@@ -1541,67 +1555,6 @@ function ChangeRow({
|
|
|
1541
1555
|
)
|
|
1542
1556
|
}
|
|
1543
1557
|
|
|
1544
|
-
// DriftPanel renders the structured per-field diff. Format mimics a
|
|
1545
|
-
// `diff -u` summary: removed paths in red, added in green, changed shown
|
|
1546
|
-
// inline as "old → new". Path is monospace; values are JSON-encoded and
|
|
1547
|
-
// pre-wrapped so structured values (objects, arrays) render readably.
|
|
1548
|
-
function DriftPanel({ drift }: { drift: NonNullable<GitOpsChange['drift']> }) {
|
|
1549
|
-
const sourceLabel = driftSourceLabel(drift.source)
|
|
1550
|
-
return (
|
|
1551
|
-
<div>
|
|
1552
|
-
<div className="mb-2 flex items-baseline justify-between gap-2">
|
|
1553
|
-
<h4 className="text-[11px] font-semibold uppercase tracking-wide text-theme-text-tertiary">Field diff</h4>
|
|
1554
|
-
<span className="text-[10px] text-theme-text-tertiary">
|
|
1555
|
-
desired (Git) → live ·
|
|
1556
|
-
{drift.truncated ? ' showing first 50 entries' : ` ${drift.entries.length} field${drift.entries.length === 1 ? '' : 's'}`}
|
|
1557
|
-
{sourceLabel && ` · ${sourceLabel}`}
|
|
1558
|
-
</span>
|
|
1559
|
-
</div>
|
|
1560
|
-
<div className="space-y-1 font-mono text-[11px]">
|
|
1561
|
-
{drift.entries.map((entry, i) => (
|
|
1562
|
-
<DriftEntryRow key={`${entry.path}-${i}`} entry={entry} />
|
|
1563
|
-
))}
|
|
1564
|
-
</div>
|
|
1565
|
-
</div>
|
|
1566
|
-
)
|
|
1567
|
-
}
|
|
1568
|
-
|
|
1569
|
-
// Provenance label for a Drift's field entries. Only Argo-sourced drift gets an
|
|
1570
|
-
// explicit label; the built-in last-applied path renders without one (the
|
|
1571
|
-
// "desired (Git) → live" copy already implies it). Unknown sources (a library
|
|
1572
|
-
// consumer lagging the backend) fall through to no label rather than surfacing
|
|
1573
|
-
// a raw enum string.
|
|
1574
|
-
function driftSourceLabel(source: NonNullable<GitOpsChange['drift']>['source']): string {
|
|
1575
|
-
return source === 'argocd-api' ? 'via Argo CD API' : ''
|
|
1576
|
-
}
|
|
1577
|
-
|
|
1578
|
-
function DriftEntryRow({ entry }: { entry: NonNullable<GitOpsChange['drift']>['entries'][number] }) {
|
|
1579
|
-
if (entry.op === 'removed') {
|
|
1580
|
-
return (
|
|
1581
|
-
<div>
|
|
1582
|
-
<span className="text-red-600 dark:text-red-400">- {entry.path}</span>
|
|
1583
|
-
{entry.desired && <span className="ml-2 text-theme-text-secondary">{entry.desired}</span>}
|
|
1584
|
-
</div>
|
|
1585
|
-
)
|
|
1586
|
-
}
|
|
1587
|
-
if (entry.op === 'added') {
|
|
1588
|
-
return (
|
|
1589
|
-
<div>
|
|
1590
|
-
<span className="text-emerald-700 dark:text-emerald-400">+ {entry.path}</span>
|
|
1591
|
-
{entry.live && <span className="ml-2 text-theme-text-secondary">{entry.live}</span>}
|
|
1592
|
-
</div>
|
|
1593
|
-
)
|
|
1594
|
-
}
|
|
1595
|
-
return (
|
|
1596
|
-
<div>
|
|
1597
|
-
<span className="text-amber-700 dark:text-amber-400">~ {entry.path}</span>
|
|
1598
|
-
<span className="ml-2 text-theme-text-tertiary">{entry.desired}</span>
|
|
1599
|
-
<span className="mx-1 text-theme-text-tertiary">→</span>
|
|
1600
|
-
<span className="text-theme-text-primary">{entry.live}</span>
|
|
1601
|
-
</div>
|
|
1602
|
-
)
|
|
1603
|
-
}
|
|
1604
|
-
|
|
1605
1558
|
// RecentEventsPanel surfaces the last few events involving this resource.
|
|
1606
1559
|
// Warning events get a red bar so the eye lands on them; normals are
|
|
1607
1560
|
// muted. Aggregation count (when present) is a critical signal — "this
|
|
@@ -1628,7 +1581,7 @@ function RecentEventsPanel({ events }: { events: NonNullable<GitOpsChange['recen
|
|
|
1628
1581
|
{e.count && e.count > 1 && (
|
|
1629
1582
|
<span className="text-[10px] text-theme-text-tertiary">×{e.count}</span>
|
|
1630
1583
|
)}
|
|
1631
|
-
<span className="ml-auto text-[10px] text-theme-text-tertiary">{
|
|
1584
|
+
<span className="ml-auto text-[10px] text-theme-text-tertiary">{formatRelativeAgeTime(e.lastTimestamp, '')}</span>
|
|
1632
1585
|
</div>
|
|
1633
1586
|
<p className="mt-0.5 text-theme-text-secondary">{e.message}</p>
|
|
1634
1587
|
</div>
|
|
@@ -1639,10 +1592,6 @@ function RecentEventsPanel({ events }: { events: NonNullable<GitOpsChange['recen
|
|
|
1639
1592
|
)
|
|
1640
1593
|
}
|
|
1641
1594
|
|
|
1642
|
-
function formatRelativeTime(value: string): string {
|
|
1643
|
-
return formatRelativeAgeTime(value, '')
|
|
1644
|
-
}
|
|
1645
|
-
|
|
1646
1595
|
interface GitOpsActivityInsightViewProps {
|
|
1647
1596
|
insight?: GitOpsInsight | null
|
|
1648
1597
|
error?: Error | null
|
|
@@ -1729,7 +1678,7 @@ function HistoryRows({
|
|
|
1729
1678
|
<div className="flex flex-wrap items-baseline gap-x-2 gap-y-0.5">
|
|
1730
1679
|
<span className="font-mono text-xs text-theme-text-primary">{item.revision || item.phase || '-'}</span>
|
|
1731
1680
|
<PhaseChip phase={item.phase} message={item.message} />
|
|
1732
|
-
<span className="text-[11px] text-theme-text-tertiary">{
|
|
1681
|
+
<span className="text-[11px] text-theme-text-tertiary">{formatRelativeAgeTime(item.deployedAt)}</span>
|
|
1733
1682
|
{item.initiatedBy && (
|
|
1734
1683
|
<span className="text-[11px] text-theme-text-tertiary">by {item.initiatedBy}</span>
|
|
1735
1684
|
)}
|
|
@@ -1831,6 +1780,3 @@ function InsightErrorState({ error }: { error: Error }) {
|
|
|
1831
1780
|
)
|
|
1832
1781
|
}
|
|
1833
1782
|
|
|
1834
|
-
function formatRelative(value?: string) {
|
|
1835
|
-
return formatRelativeAgeTime(value)
|
|
1836
|
-
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest'
|
|
2
2
|
|
|
3
|
-
import { getSubtitle } from './GitOpsTreeGraph'
|
|
3
|
+
import { getSubtitle, nodeCause } from './GitOpsTreeGraph'
|
|
4
4
|
import type { GitOpsTreeNode } from '../../../types'
|
|
5
5
|
|
|
6
6
|
function node(extras: Partial<GitOpsTreeNode> = {}): GitOpsTreeNode {
|
|
@@ -90,3 +90,28 @@ describe('getSubtitle', () => {
|
|
|
90
90
|
expect(getSubtitle(node({ ref: { kind: 'ClusterRole', name: 'podinfo', namespace: '' } }))).toBe('')
|
|
91
91
|
})
|
|
92
92
|
})
|
|
93
|
+
|
|
94
|
+
describe('nodeCause', () => {
|
|
95
|
+
// Shapes as the tree API sends them: the Go side maps Argo's Degraded and
|
|
96
|
+
// Missing to topologyStatus 'unhealthy', Progressing and Suspended to
|
|
97
|
+
// 'degraded' (pkg/gitops/tree healthToTopology).
|
|
98
|
+
const CAUSE = 'Back-off restarting failed container main'
|
|
99
|
+
|
|
100
|
+
it('shows the cause on a node the controller calls Degraded', () => {
|
|
101
|
+
expect(nodeCause(node({ health: 'Degraded', healthSource: 'controller', topologyStatus: 'unhealthy' }), CAUSE)).toBe(CAUSE)
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
it('hides it on a Progressing node, even when an issue matched it', () => {
|
|
105
|
+
// A rollout in flight is not broken; a cause tooltip on it says it is.
|
|
106
|
+
expect(nodeCause(node({ health: 'Progressing', healthSource: 'controller', topologyStatus: 'degraded' }), CAUSE)).toBeUndefined()
|
|
107
|
+
expect(nodeCause(node({ health: 'Progressing', healthSource: 'radar', topologyStatus: 'degraded' }), CAUSE)).toBeUndefined()
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
it('hides it on a Suspended node, even when an issue matched it', () => {
|
|
111
|
+
expect(nodeCause(node({ health: 'Suspended', healthSource: 'controller', topologyStatus: 'degraded' }), CAUSE)).toBeUndefined()
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
it('never shows it on a healthy node', () => {
|
|
115
|
+
expect(nodeCause(node({ health: 'Healthy', healthSource: 'controller', topologyStatus: 'healthy' }), CAUSE)).toBeUndefined()
|
|
116
|
+
})
|
|
117
|
+
})
|