@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
|
@@ -137,20 +137,25 @@ import {
|
|
|
137
137
|
parseColumnFilters,
|
|
138
138
|
serializeColumnFilters,
|
|
139
139
|
parseColumnFilterExcludes,
|
|
140
|
+
kindFiltersFromSearch,
|
|
141
|
+
kindFiltersToSearch,
|
|
142
|
+
columnFiltersParam,
|
|
143
|
+
RESOURCE_LIST_FILTER_PARAMS,
|
|
140
144
|
podMatchesProblemCategory,
|
|
141
145
|
SEVERITY_DOT_COLOR,
|
|
142
146
|
healthColors,
|
|
143
147
|
} from './resource-utils'
|
|
144
148
|
import { getGenericResourceStatus } from './generic-status'
|
|
149
|
+
import { ActiveFilterBar } from './ActiveFilterBar'
|
|
145
150
|
import { getIstioGatewayServerCount, getIstioGatewaySelectorString } from './resource-utils-istio'
|
|
146
151
|
import {
|
|
147
152
|
type PrinterColumnDef, type PrinterTable, PRINTER_COLUMN_PREFIX, formatPrinterCell, printerCellSortValue,
|
|
148
153
|
printerCellTone, printerColumnKey, printerColumnWidth, printerTableKey, printerTableMatchesKind, readPrinterCell,
|
|
149
154
|
} from './printer-columns'
|
|
150
|
-
import { SEVERITY_BADGE, EVENT_TYPE_COLORS } from '../../utils/badge-colors'
|
|
155
|
+
import { SEVERITY_BADGE, SEVERITY_TEXT, healthToSeverity, EVENT_TYPE_COLORS } from '../../utils/badge-colors'
|
|
151
156
|
import { pluralize } from '../../utils/pluralize'
|
|
152
157
|
import { getPodGpuCount, getNodeGpuCount } from '../../utils/extended-resources'
|
|
153
|
-
import { parseQuantityToNumber } from '../../utils/format'
|
|
158
|
+
import { formatRelativeAgeTime, parseQuantityToNumber } from '../../utils/format'
|
|
154
159
|
import { type CustomColumnDef, type CustomColumnSource, customColumnKey, readCustomColumnValue, sanitizeCustomColumnDefs } from '../../utils/custom-columns'
|
|
155
160
|
import { isRolloutActivityVisible } from '../../utils/workload-rollout'
|
|
156
161
|
import { FreshnessControl, type FreshnessConnection } from '../ui/FreshnessControl'
|
|
@@ -203,7 +208,7 @@ import { GCPManagedControlPlaneCell, GCPManagedMachinePoolCell, GCPMachineCell,
|
|
|
203
208
|
import { AzureManagedControlPlaneCell, AzureManagedMachinePoolCell, AzureMachineCell, AzureMachineTemplateCell, AzureManagedClusterCell } from './renderers/azure-capi-cells'
|
|
204
209
|
import { CalicoInfraCell, CalicoPolicyCell } from './renderers/calico-cells'
|
|
205
210
|
import { isCalicoPolicyResource, isCoreNetworkPolicyKind } from './resource-utils-calico'
|
|
206
|
-
import { useRegisterShortcut, useRegisterShortcuts } from '../../hooks/useKeyboardShortcuts'
|
|
211
|
+
import { useRegisterShortcut, useRegisterShortcuts, type KeyboardShortcut } from '../../hooks/useKeyboardShortcuts'
|
|
207
212
|
import { ResourcesSidebar } from './ResourcesSidebar'
|
|
208
213
|
import type { SelectedKindInfo, SidebarCategoryWorkspace } from './ResourcesSidebar'
|
|
209
214
|
import { CompareTray, togglePick, pickIndex, refToParam, SIDE_TONES, type CompareTrayPick, type NamespacedRef } from '../compare'
|
|
@@ -1460,6 +1465,15 @@ const KNOWN_COLUMNS: Record<string, Column[]> = {
|
|
|
1460
1465
|
{ key: 'rules', label: 'Rules', width: 'w-20' },
|
|
1461
1466
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
1462
1467
|
],
|
|
1468
|
+
udproutes: [
|
|
1469
|
+
{ key: 'name', label: 'Name' },
|
|
1470
|
+
{ key: 'namespace', label: 'Namespace', width: 'w-48' },
|
|
1471
|
+
{ key: 'status', label: 'Status', width: 'w-28', tooltip: 'Accepted = controllers accepted this route and confirmed it can use the backends and other objects it names. Traffic has not been tested. Degraded or Not Accepted: open the route to see which Gateway or backend failed. Pending or Unknown: check the named Gateways or Services and their controllers.' },
|
|
1472
|
+
{ key: 'parents', label: 'Gateways', width: 'w-36' },
|
|
1473
|
+
{ key: 'backends', label: 'Backends', width: 'w-48', tooltip: 'Backend services receiving traffic' },
|
|
1474
|
+
{ key: 'rules', label: 'Rules', width: 'w-20' },
|
|
1475
|
+
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
1476
|
+
],
|
|
1463
1477
|
tlsroutes: [
|
|
1464
1478
|
{ key: 'name', label: 'Name' },
|
|
1465
1479
|
{ key: 'namespace', label: 'Namespace', width: 'w-48' },
|
|
@@ -2884,6 +2898,7 @@ const CURATED_COLUMN_GROUPS: Record<string, readonly string[]> = {
|
|
|
2884
2898
|
grpcroutes: ['gateway.networking.k8s.io'],
|
|
2885
2899
|
httproutes: ['gateway.networking.k8s.io'],
|
|
2886
2900
|
tcproutes: ['gateway.networking.k8s.io'],
|
|
2901
|
+
udproutes: ['gateway.networking.k8s.io'],
|
|
2887
2902
|
tlsroutes: ['gateway.networking.k8s.io'],
|
|
2888
2903
|
helmreleases: ['helm.toolkit.fluxcd.io'],
|
|
2889
2904
|
awsmachines: ['infrastructure.cluster.x-k8s.io'],
|
|
@@ -3415,6 +3430,11 @@ interface ResourcesViewProps {
|
|
|
3415
3430
|
sidebarCategoryWorkspaces?: Record<string, SidebarCategoryWorkspace>
|
|
3416
3431
|
/** Callback when the [+] create button is clicked. Receives the currently selected kind info. */
|
|
3417
3432
|
onCreateResource?: (kind: { name: string; kind: string; group: string } | null) => void
|
|
3433
|
+
/** A kind's own view, rendered in place of the table: the sidebar, kind
|
|
3434
|
+
* selection and the drawer (`?resource=`) stay; the table, its toolbar and
|
|
3435
|
+
* its row/search/bulk shortcuts do not run. Sidebar kind navigation stays.
|
|
3436
|
+
* Return null or false to keep the table. */
|
|
3437
|
+
renderKindView?: (kind: SelectedKindInfo) => React.ReactNode | null
|
|
3418
3438
|
/** Default kind when the URL does not include one. */
|
|
3419
3439
|
defaultKind?: SelectedKindInfo
|
|
3420
3440
|
/** Columns prepended to KNOWN_COLUMNS for every kind. For example, a
|
|
@@ -3480,6 +3500,29 @@ interface ResourcesViewProps {
|
|
|
3480
3500
|
* the switcher lives outside this component and may persist server-side.
|
|
3481
3501
|
*/
|
|
3482
3502
|
onClearNamespaces?: () => void
|
|
3503
|
+
/**
|
|
3504
|
+
* A kind's saved filters, as last reported through `onKindFiltersChange`
|
|
3505
|
+
* (host-owned, because this view unmounts on section switches). A user kind
|
|
3506
|
+
* switch, or a URL with no list state of its own (see `hasFilterIntent`),
|
|
3507
|
+
* restores that kind's column and problem filters from it. Pass undefined
|
|
3508
|
+
* until the host can answer: the first defined value is still applied to a
|
|
3509
|
+
* bare URL.
|
|
3510
|
+
*/
|
|
3511
|
+
rememberedKindSearch?: (kind: { name: string; group: string }) => string | undefined
|
|
3512
|
+
/**
|
|
3513
|
+
* Called with a kind's serialized `filters` + `problems` query ('' when
|
|
3514
|
+
* none) whenever the filters the kind shows change, including the ones a
|
|
3515
|
+
* link arrives with. A list that arrives with no filters does not report
|
|
3516
|
+
* '', so a link to one resource keeps what was saved.
|
|
3517
|
+
*/
|
|
3518
|
+
onKindFiltersChange?: (kind: { name: string; group: string }, search: string) => void
|
|
3519
|
+
/**
|
|
3520
|
+
* Promotes the Namespace column's included values to the global namespace
|
|
3521
|
+
* selection (replacing it). When wired, the column's filter dropdown offers
|
|
3522
|
+
* "Apply to all sections" and clears the column filter once the returned
|
|
3523
|
+
* promise resolves (a rejection keeps it).
|
|
3524
|
+
*/
|
|
3525
|
+
onApplyNamespacesToAllSections?: (namespaces: string[]) => Promise<unknown> | void
|
|
3483
3526
|
// Bulk operations
|
|
3484
3527
|
onBulkDelete?: (items: BulkResourceItem[], options?: { force?: boolean; onSuccess?: () => void }) => void
|
|
3485
3528
|
isBulkDeleting?: boolean
|
|
@@ -3603,19 +3646,24 @@ export function deriveSidebarResourceCounts(
|
|
|
3603
3646
|
return results
|
|
3604
3647
|
}
|
|
3605
3648
|
|
|
3649
|
+
const FILTER_INTENT_PARAMS = [...RESOURCE_LIST_FILTER_PARAMS, 'resource']
|
|
3650
|
+
|
|
3651
|
+
/**
|
|
3652
|
+
* Whether a URL states its own list state. Only a URL without any of these
|
|
3653
|
+
* (a bare `/resources/<kind>`) gets the kind's remembered filters.
|
|
3654
|
+
*/
|
|
3655
|
+
export function hasFilterIntent(search: string): boolean {
|
|
3656
|
+
const params = new URLSearchParams(search)
|
|
3657
|
+
return FILTER_INTENT_PARAMS.some((key) => params.has(key))
|
|
3658
|
+
}
|
|
3659
|
+
|
|
3606
3660
|
// Get initial filters from URL
|
|
3607
3661
|
function getInitialFiltersFromURL() {
|
|
3608
3662
|
const params = new URLSearchParams(window.location.search)
|
|
3609
|
-
// Parse generic column filters
|
|
3610
|
-
const filtersParam = params.get('filters')
|
|
3611
|
-
const columnFilters = parseColumnFilters(filtersParam)
|
|
3612
|
-
const columnFilterExcludes = parseColumnFilterExcludes(filtersParam)
|
|
3613
3663
|
const result = {
|
|
3614
3664
|
search: params.get('search') || '',
|
|
3615
3665
|
regex: params.get('regex') === 'true',
|
|
3616
|
-
|
|
3617
|
-
columnFilterExcludes,
|
|
3618
|
-
problemFilters: params.get('problems')?.split(',').filter(Boolean) || [],
|
|
3666
|
+
...kindFiltersFromSearch(window.location.search),
|
|
3619
3667
|
showInactive: params.get('showInactive') === 'true',
|
|
3620
3668
|
labelSelector: params.get('labels') || '', // e.g., "app=caretta,version=v1"
|
|
3621
3669
|
ownerKind: params.get('ownerKind') || '', // e.g., "DaemonSet"
|
|
@@ -3657,6 +3705,7 @@ export function ResourcesView({
|
|
|
3657
3705
|
hideSidebar = false,
|
|
3658
3706
|
sidebarCategoryWorkspaces,
|
|
3659
3707
|
onCreateResource,
|
|
3708
|
+
renderKindView,
|
|
3660
3709
|
defaultKind = DEFAULT_KIND_INFO,
|
|
3661
3710
|
extraLeadingColumns,
|
|
3662
3711
|
printerTable,
|
|
@@ -3665,6 +3714,9 @@ export function ResourcesView({
|
|
|
3665
3714
|
onCompareSubmit,
|
|
3666
3715
|
resolveRowCluster,
|
|
3667
3716
|
onClearNamespaces,
|
|
3717
|
+
rememberedKindSearch,
|
|
3718
|
+
onKindFiltersChange,
|
|
3719
|
+
onApplyNamespacesToAllSections,
|
|
3668
3720
|
onBulkDelete,
|
|
3669
3721
|
isBulkDeleting = false,
|
|
3670
3722
|
onBulkRestart,
|
|
@@ -3685,16 +3737,23 @@ export function ResourcesView({
|
|
|
3685
3737
|
kindFromURL.name !== prev.name || kindFromURL.group !== prev.group ? kindFromURL : prev,
|
|
3686
3738
|
)
|
|
3687
3739
|
}, [basePath, defaultKind, locationPathname, locationSearch])
|
|
3740
|
+
const contributedKindView = renderKindView?.(selectedKind)
|
|
3741
|
+
const kindView = contributedKindView === false ? null : contributedKindView ?? null
|
|
3742
|
+
const tableActive = kindView === null
|
|
3743
|
+
|
|
3688
3744
|
// Notify parent of selected kind changes (including initial mount)
|
|
3689
3745
|
useEffect(() => {
|
|
3690
3746
|
onSelectedKindChange?.(selectedKind)
|
|
3747
|
+
}, [selectedKind.name, selectedKind.group]) // eslint-disable-line react-hooks/exhaustive-deps
|
|
3748
|
+
|
|
3749
|
+
useEffect(() => {
|
|
3691
3750
|
setBulkMode(false)
|
|
3692
3751
|
setCheckedResources(new Set())
|
|
3693
3752
|
setShowBulkDeleteConfirm(false)
|
|
3694
3753
|
setShowBulkRestartConfirm(false)
|
|
3695
3754
|
setShowBulkScaleDialog(false)
|
|
3696
3755
|
setBulkForceDelete(false)
|
|
3697
|
-
}, [selectedKind.name, selectedKind.group])
|
|
3756
|
+
}, [selectedKind.name, selectedKind.group, tableActive])
|
|
3698
3757
|
const [searchTerm, setSearchTerm] = useState(initialFilters.search)
|
|
3699
3758
|
// Typing must never wait on the list or the router. The input renders raw
|
|
3700
3759
|
// searchTerm; filtering consumes the deferred copy (React yields to keep
|
|
@@ -3712,6 +3771,30 @@ export function ResourcesView({
|
|
|
3712
3771
|
const [columnFilters, setColumnFilters] = useState<Record<string, string[]>>(initialFilters.columnFilters)
|
|
3713
3772
|
const [columnFilterExcludes, setColumnFilterExcludes] = useState<Record<string, boolean>>(initialFilters.columnFilterExcludes)
|
|
3714
3773
|
const [problemFilters, setProblemFilters] = useState<string[]>(initialFilters.problemFilters)
|
|
3774
|
+
// The kind the filter state belongs to. During a kind switch one render
|
|
3775
|
+
// pairs the new kind with the old kind's filters; saving then would file
|
|
3776
|
+
// them under the wrong kind.
|
|
3777
|
+
const [filtersKind, setFiltersKind] = useState(() => selectedKindIdentityOf(selectedKind))
|
|
3778
|
+
// Where the current filter state came from when it wasn't a user edit.
|
|
3779
|
+
const filtersArrivedRef = useRef<'url' | 'restore' | null>('url')
|
|
3780
|
+
// The serialized filters last restored from rememberedKindSearch ('' when none).
|
|
3781
|
+
const [restoredKindSearch, setRestoredKindSearch] = useState('')
|
|
3782
|
+
// A bare URL at mount takes the kind's saved filters. Adjusted during render
|
|
3783
|
+
// (not in an effect) so the unfiltered list never paints, and so it still
|
|
3784
|
+
// applies when the host's lookup arrives after mount.
|
|
3785
|
+
const [mountRestorePending, setMountRestorePending] = useState(() => !hasFilterIntent(locationSearch ?? window.location.search))
|
|
3786
|
+
if (mountRestorePending && rememberedKindSearch) {
|
|
3787
|
+
setMountRestorePending(false)
|
|
3788
|
+
const restored = kindFiltersToSearch(kindFiltersFromSearch(rememberedKindSearch(selectedKind)))
|
|
3789
|
+
if (restored && !hasFilterIntent(locationSearch ?? window.location.search)) {
|
|
3790
|
+
const remembered = kindFiltersFromSearch(restored)
|
|
3791
|
+
setColumnFilters(remembered.columnFilters)
|
|
3792
|
+
setColumnFilterExcludes(remembered.columnFilterExcludes)
|
|
3793
|
+
setProblemFilters(remembered.problemFilters)
|
|
3794
|
+
setRestoredKindSearch(restored)
|
|
3795
|
+
filtersArrivedRef.current = 'restore'
|
|
3796
|
+
}
|
|
3797
|
+
}
|
|
3715
3798
|
const [openColumnFilter, setOpenColumnFilter] = useState<string | null>(null)
|
|
3716
3799
|
const [columnFilterSearch, setColumnFilterSearch] = useState('')
|
|
3717
3800
|
const columnFilterDropdownRef = useRef<HTMLDivElement>(null)
|
|
@@ -3935,6 +4018,8 @@ export function ResourcesView({
|
|
|
3935
4018
|
// depends on visibleColumns) and instantly reverts the user's hide.
|
|
3936
4019
|
const gpuAutoShownKinds = useRef<Set<string>>(new Set())
|
|
3937
4020
|
useEffect(() => {
|
|
4021
|
+
// The table's own settings: nothing to load (or clear) while a kind view replaces it.
|
|
4022
|
+
if (!tableActive) return
|
|
3938
4023
|
// Re-arm the skip-initial-save guard per kind. This effect repopulates
|
|
3939
4024
|
// visible/widths/custom for the new kind via async setState, so the save
|
|
3940
4025
|
// effect (also keyed to selectedKind) that runs in this same commit still
|
|
@@ -4019,10 +4104,11 @@ export function ResourcesView({
|
|
|
4019
4104
|
// identity on every refetch, and re-running this effect would reset the
|
|
4020
4105
|
// user's in-session column choices each time the list polls. The key only
|
|
4021
4106
|
// changes when the kind changes or an operator upgrade changes the CRD.
|
|
4022
|
-
}, [selectedKind.name, selectedKind.group, extraLeadingColumns, printerColumnsKey])
|
|
4107
|
+
}, [selectedKind.name, selectedKind.group, extraLeadingColumns, printerColumnsKey, tableActive])
|
|
4023
4108
|
|
|
4024
4109
|
// Save column settings when they change (skip the initial load of each kind)
|
|
4025
4110
|
useEffect(() => {
|
|
4111
|
+
if (!tableActive) return
|
|
4026
4112
|
if (visibleColumns.size === 0) return // not loaded yet
|
|
4027
4113
|
for (const k of allColumnKeys) knownColumnKeys.current.add(k)
|
|
4028
4114
|
if (!isColumnSettingsLoaded.current) {
|
|
@@ -4039,7 +4125,7 @@ export function ResourcesView({
|
|
|
4039
4125
|
// as across sessions, so a later data refresh can't override user choices.
|
|
4040
4126
|
hadSavedColumnSettings.current = true
|
|
4041
4127
|
// allColumnKeysSig, not allColumnKeys: see the memo above.
|
|
4042
|
-
}, [visibleColumns, columnWidths, customColumns, selectedKind.name, selectedKind.group, allColumnKeysSig])
|
|
4128
|
+
}, [visibleColumns, columnWidths, customColumns, selectedKind.name, selectedKind.group, allColumnKeysSig, tableActive])
|
|
4043
4129
|
|
|
4044
4130
|
// Close column picker on outside click or Escape
|
|
4045
4131
|
useEffect(() => {
|
|
@@ -4208,6 +4294,7 @@ export function ResourcesView({
|
|
|
4208
4294
|
category: 'Search',
|
|
4209
4295
|
scope: 'resources',
|
|
4210
4296
|
handler: () => searchInputRef.current?.focus(),
|
|
4297
|
+
enabled: tableActive,
|
|
4211
4298
|
})
|
|
4212
4299
|
|
|
4213
4300
|
// Keyboard navigation: highlighted row state
|
|
@@ -4227,7 +4314,7 @@ export function ResourcesView({
|
|
|
4227
4314
|
useEffect(() => {
|
|
4228
4315
|
setComparePicks([])
|
|
4229
4316
|
setCompareMode(false)
|
|
4230
|
-
}, [selectedKind.name, selectedKind.group])
|
|
4317
|
+
}, [selectedKind.name, selectedKind.group, tableActive])
|
|
4231
4318
|
|
|
4232
4319
|
const exitCompareMode = useCallback(() => {
|
|
4233
4320
|
setCompareMode(false)
|
|
@@ -4275,6 +4362,15 @@ export function ResourcesView({
|
|
|
4275
4362
|
}
|
|
4276
4363
|
}, [highlightedIndex])
|
|
4277
4364
|
|
|
4365
|
+
useEffect(() => {
|
|
4366
|
+
if (tableActive) return
|
|
4367
|
+
setHighlightedIndex(-1)
|
|
4368
|
+
setShowColumnPicker(false)
|
|
4369
|
+
setOpenColumnFilter(null)
|
|
4370
|
+
setShowProblemsDropdown(false)
|
|
4371
|
+
setShowLabelsDropdown(false)
|
|
4372
|
+
}, [tableActive])
|
|
4373
|
+
|
|
4278
4374
|
// Open logs for pod / workload resources — provided via props
|
|
4279
4375
|
const openLogs = onOpenLogs
|
|
4280
4376
|
const openWorkloadLogs = onOpenWorkloadLogs
|
|
@@ -4307,8 +4403,7 @@ export function ResourcesView({
|
|
|
4307
4403
|
onResourceClick?.(isSelected ? null : stripped)
|
|
4308
4404
|
}, [onRowSelect, onResourceClick, selectedKind.name, selectedKind.group])
|
|
4309
4405
|
|
|
4310
|
-
|
|
4311
|
-
useRegisterShortcuts([
|
|
4406
|
+
useRegisterShortcuts(([
|
|
4312
4407
|
{
|
|
4313
4408
|
id: 'resources-nav-down',
|
|
4314
4409
|
keys: 'j',
|
|
@@ -4484,7 +4579,7 @@ export function ResourcesView({
|
|
|
4484
4579
|
else searchInputRef.current?.blur()
|
|
4485
4580
|
},
|
|
4486
4581
|
},
|
|
4487
|
-
])
|
|
4582
|
+
] as KeyboardShortcut[]).map((sc) => ({ ...sc, enabled: tableActive && (sc.enabled ?? true) })))
|
|
4488
4583
|
|
|
4489
4584
|
// Refs for accessing filteredResources inside shortcuts (computed later in component)
|
|
4490
4585
|
const filteredResourceCountRef = useRef(0)
|
|
@@ -4564,8 +4659,21 @@ export function ResourcesView({
|
|
|
4564
4659
|
const newFilters = getInitialFiltersFromURL()
|
|
4565
4660
|
|
|
4566
4661
|
// Update kind if it changed
|
|
4567
|
-
|
|
4662
|
+
const kindChanged = newKind.name !== selectedKind.name || newKind.group !== selectedKind.group
|
|
4663
|
+
let restored = ''
|
|
4664
|
+
if (kindChanged) {
|
|
4568
4665
|
setSelectedKind(newKind)
|
|
4666
|
+
// A bare URL for another kind links to the list, not to a state of it:
|
|
4667
|
+
// take that kind's saved filters and put them in the address bar. Never
|
|
4668
|
+
// for the same kind, where a bare URL is the echo of Clear filters.
|
|
4669
|
+
restored = hasFilterIntent(locationSearch ?? window.location.search)
|
|
4670
|
+
? ''
|
|
4671
|
+
: kindFiltersToSearch(kindFiltersFromSearch(rememberedKindSearch?.(newKind)))
|
|
4672
|
+
setRestoredKindSearch(restored)
|
|
4673
|
+
if (restored) {
|
|
4674
|
+
Object.assign(newFilters, kindFiltersFromSearch(restored))
|
|
4675
|
+
updateURL(newKind, newFilters.search, newFilters.regex, newFilters.columnFilters, newFilters.columnFilterExcludes, newFilters.problemFilters, newFilters.showInactive)
|
|
4676
|
+
}
|
|
4569
4677
|
}
|
|
4570
4678
|
|
|
4571
4679
|
// Update owner filter if it changed
|
|
@@ -4606,6 +4714,13 @@ export function ResourcesView({
|
|
|
4606
4714
|
setColumnFilterExcludes(newFilters.columnFilterExcludes)
|
|
4607
4715
|
}
|
|
4608
4716
|
|
|
4717
|
+
if (newFilters.problemFilters.join(',') !== problemFilters.join(',')) {
|
|
4718
|
+
setProblemFilters(newFilters.problemFilters)
|
|
4719
|
+
}
|
|
4720
|
+
if (kindChanged || kindFiltersToSearch(newFilters) !== kindFiltersSearch) {
|
|
4721
|
+
filtersArrivedRef.current = restored ? 'restore' : 'url'
|
|
4722
|
+
}
|
|
4723
|
+
|
|
4609
4724
|
// Reset the flag after a tick to allow normal URL updates
|
|
4610
4725
|
requestAnimationFrame(() => {
|
|
4611
4726
|
isSyncingFromURL.current = false
|
|
@@ -4660,11 +4775,7 @@ export function ResourcesView({
|
|
|
4660
4775
|
}
|
|
4661
4776
|
// Write column filters as `filters` param; the exclude operator is folded
|
|
4662
4777
|
// into each column entry, so it can never drift from the values it negates.
|
|
4663
|
-
|
|
4664
|
-
const activeExcludes = Object.fromEntries(
|
|
4665
|
-
Object.entries(colExcludes).filter(([k, on]) => on && (colFilters[k]?.length ?? 0) > 0)
|
|
4666
|
-
)
|
|
4667
|
-
const filtersStr = serializeColumnFilters(colFilters, activeExcludes)
|
|
4778
|
+
const filtersStr = columnFiltersParam(colFilters, colExcludes)
|
|
4668
4779
|
if (filtersStr) {
|
|
4669
4780
|
params.set('filters', filtersStr)
|
|
4670
4781
|
} else {
|
|
@@ -4736,13 +4847,19 @@ export function ResourcesView({
|
|
|
4736
4847
|
// params are out of scope here; the host's onClearNamespaces (and its
|
|
4737
4848
|
// own state→URL sync) owns namespace cleanup.
|
|
4738
4849
|
const params = new URLSearchParams(window.location.search)
|
|
4739
|
-
for (const key of
|
|
4850
|
+
for (const key of RESOURCE_LIST_FILTER_PARAMS) {
|
|
4740
4851
|
params.delete(key)
|
|
4741
4852
|
}
|
|
4742
4853
|
navigate({ pathname: window.location.pathname, search: params.toString() }, { replace: true })
|
|
4743
4854
|
onClearNamespaces?.()
|
|
4744
4855
|
}, [navigate, onClearNamespaces])
|
|
4745
4856
|
|
|
4857
|
+
const clearKindFilters = useCallback(() => {
|
|
4858
|
+
setColumnFilters({})
|
|
4859
|
+
setColumnFilterExcludes({})
|
|
4860
|
+
setProblemFilters([])
|
|
4861
|
+
}, [])
|
|
4862
|
+
|
|
4746
4863
|
// Update URL when any filter changes
|
|
4747
4864
|
useEffect(() => {
|
|
4748
4865
|
// Skip URL update if we're syncing FROM the URL (e.g., browser back button)
|
|
@@ -4755,6 +4872,8 @@ export function ResourcesView({
|
|
|
4755
4872
|
prevSelectedResourceRef.current = selectedResource ?? null
|
|
4756
4873
|
return
|
|
4757
4874
|
}
|
|
4875
|
+
// Right after a kind switch the filters are still the previous kind's.
|
|
4876
|
+
if (filtersKind !== selectedKindIdentityOf(selectedKind)) return
|
|
4758
4877
|
// Skip URL update if selectedResource's kind doesn't match selectedKind (still syncing)
|
|
4759
4878
|
if (selectedResource) {
|
|
4760
4879
|
const resourceKindLower = selectedResource.kind.toLowerCase()
|
|
@@ -4780,7 +4899,12 @@ export function ResourcesView({
|
|
|
4780
4899
|
: typeof window !== 'undefined'
|
|
4781
4900
|
? window.location.pathname
|
|
4782
4901
|
: ''
|
|
4783
|
-
|
|
4902
|
+
// The router commits a navigation as a transition, so the injected path can
|
|
4903
|
+
// still be the previous kind's right after a push to this one (e.g. the
|
|
4904
|
+
// write of filters restored on a kind switch); the address bar is already
|
|
4905
|
+
// there, and pushing again would add a second entry for the same visit.
|
|
4906
|
+
const pathChanged = currentPath !== targetPath &&
|
|
4907
|
+
!(typeof window !== 'undefined' && window.location.pathname.endsWith(targetPath))
|
|
4784
4908
|
const prev = prevSelectedResourceRef.current
|
|
4785
4909
|
const current = selectedResource ?? null
|
|
4786
4910
|
const drawerSwitched =
|
|
@@ -4794,7 +4918,7 @@ export function ResourcesView({
|
|
|
4794
4918
|
prevSelectedResourceRef.current = current
|
|
4795
4919
|
|
|
4796
4920
|
updateURL(selectedKind, debouncedSearchTerm, regexMode, columnFilters, columnFilterExcludes, problemFilters, showInactiveReplicaSets, selectedResource?.namespace, selectedResource?.name, pushHistory)
|
|
4797
|
-
}, [selectedKind, debouncedSearchTerm, regexMode, columnFilters, columnFilterExcludes, problemFilters, showInactiveReplicaSets, selectedResource, updateURL, basePath, locationPathname])
|
|
4921
|
+
}, [selectedKind, filtersKind, debouncedSearchTerm, regexMode, columnFilters, columnFilterExcludes, problemFilters, showInactiveReplicaSets, selectedResource, updateURL, basePath, locationPathname])
|
|
4798
4922
|
|
|
4799
4923
|
// Handle resource click from URL on mount
|
|
4800
4924
|
useEffect(() => {
|
|
@@ -4815,6 +4939,12 @@ export function ResourcesView({
|
|
|
4815
4939
|
// Signal that initial resource param has been processed — URL update effect can now run
|
|
4816
4940
|
hasProcessedInitialResource.current = true
|
|
4817
4941
|
|
|
4942
|
+
// Filters restored at mount go into the address bar (with the kind segment).
|
|
4943
|
+
if (restoredKindSearch) {
|
|
4944
|
+
updateURL(selectedKind, searchTerm, regexMode, columnFilters, columnFilterExcludes, problemFilters, showInactiveReplicaSets)
|
|
4945
|
+
return
|
|
4946
|
+
}
|
|
4947
|
+
|
|
4818
4948
|
// If the URL has no kind segment (e.g., /resources), update to include the default kind.
|
|
4819
4949
|
// Route through `navigate` so an onNavigate-suppressing host can opt out.
|
|
4820
4950
|
const base = basePath.replace(/\/$/, '')
|
|
@@ -5083,7 +5213,7 @@ export function ResourcesView({
|
|
|
5083
5213
|
isForbiddenError(selectedQueryError) ||
|
|
5084
5214
|
(!selectedHasRows && !selectedQueryError && forbiddenKinds.has(selectedKindCountKey))
|
|
5085
5215
|
|
|
5086
|
-
//
|
|
5216
|
+
// On a kind change, restore that kind's remembered filters, or start empty (but not when syncing from URL navigation)
|
|
5087
5217
|
// Track previous kind to skip on mount (where the effect fires but kind hasn't actually changed)
|
|
5088
5218
|
// Keyed on group as well as plural: two CRDs can share a plural across API
|
|
5089
5219
|
// groups, and their printer columns are unrelated. Resetting on the plural
|
|
@@ -5097,12 +5227,39 @@ export function ResourcesView({
|
|
|
5097
5227
|
}
|
|
5098
5228
|
prevKindRef.current = selectedKindIdentity
|
|
5099
5229
|
setOpenColumnFilter(null)
|
|
5100
|
-
|
|
5101
|
-
|
|
5102
|
-
|
|
5103
|
-
|
|
5104
|
-
|
|
5105
|
-
|
|
5230
|
+
setFiltersKind(selectedKindIdentity)
|
|
5231
|
+
// The URL sync already set this kind's filters (and restoredKindSearch).
|
|
5232
|
+
if (isSyncingFromURL.current) return
|
|
5233
|
+
filtersArrivedRef.current = 'restore'
|
|
5234
|
+
const restored = kindFiltersToSearch(kindFiltersFromSearch(rememberedKindSearch?.(selectedKind)))
|
|
5235
|
+
const remembered = kindFiltersFromSearch(restored)
|
|
5236
|
+
setColumnFilters(remembered.columnFilters)
|
|
5237
|
+
setColumnFilterExcludes(remembered.columnFilterExcludes)
|
|
5238
|
+
setProblemFilters(remembered.problemFilters)
|
|
5239
|
+
setRestoredKindSearch(restored)
|
|
5240
|
+
}, [selectedKindIdentity, selectedKind, rememberedKindSearch])
|
|
5241
|
+
|
|
5242
|
+
const kindFiltersSearch = useMemo(
|
|
5243
|
+
() => kindFiltersToSearch({ columnFilters, columnFilterExcludes, problemFilters }),
|
|
5244
|
+
[columnFilters, columnFilterExcludes, problemFilters],
|
|
5245
|
+
)
|
|
5246
|
+
// Saved filters are the last ones chosen for a kind: by the user, or by a
|
|
5247
|
+
// link that brought its own. A restore only shows what was saved (saving it
|
|
5248
|
+
// again would overwrite another tab's newer choice); a list that arrives
|
|
5249
|
+
// with no filters (a link to one resource, an owner's pods) keeps what was
|
|
5250
|
+
// saved; Clear filters saves none.
|
|
5251
|
+
const lastSeenKindFiltersRef = useRef<{ kind: string; search: string } | null>(null)
|
|
5252
|
+
useEffect(() => {
|
|
5253
|
+
// The host may wire saving after mount; until then nothing counts as seen.
|
|
5254
|
+
if (!onKindFiltersChange || filtersKind !== selectedKindIdentity) return
|
|
5255
|
+
const last = lastSeenKindFiltersRef.current
|
|
5256
|
+
lastSeenKindFiltersRef.current = { kind: filtersKind, search: kindFiltersSearch }
|
|
5257
|
+
const arrived = filtersArrivedRef.current
|
|
5258
|
+
filtersArrivedRef.current = null
|
|
5259
|
+
if (last?.kind === filtersKind && last.search === kindFiltersSearch) return
|
|
5260
|
+
if (arrived === 'restore' || (arrived === 'url' && !kindFiltersSearch)) return
|
|
5261
|
+
onKindFiltersChange(selectedKind, kindFiltersSearch)
|
|
5262
|
+
}, [filtersKind, selectedKindIdentity, kindFiltersSearch, selectedKind, onKindFiltersChange])
|
|
5106
5263
|
|
|
5107
5264
|
// Toggle sort for a column
|
|
5108
5265
|
const handleSort = useCallback((column: string) => {
|
|
@@ -5278,7 +5435,7 @@ export function ResourcesView({
|
|
|
5278
5435
|
|
|
5279
5436
|
// Filter resources by search term, status, problems, and sort
|
|
5280
5437
|
const filteredResources = useMemo(() => {
|
|
5281
|
-
if (!resources) return []
|
|
5438
|
+
if (!tableActive || !resources) return []
|
|
5282
5439
|
|
|
5283
5440
|
let result = resources
|
|
5284
5441
|
|
|
@@ -5464,7 +5621,7 @@ export function ResourcesView({
|
|
|
5464
5621
|
}
|
|
5465
5622
|
|
|
5466
5623
|
return result
|
|
5467
|
-
}, [resources, deferredSearchTerm, regexMode, searchRegex, columnFilters, columnFilterExcludes, problemFilters, showInactiveReplicaSets, labelSelector, ownerKind, ownerName, selectedKind.name, effectiveSortColumn, sortDirection, getSortValue, extraColumnsByKey, podMatchesProblemFilter])
|
|
5624
|
+
}, [tableActive, resources, deferredSearchTerm, regexMode, searchRegex, columnFilters, columnFilterExcludes, problemFilters, showInactiveReplicaSets, labelSelector, ownerKind, ownerName, selectedKind.name, effectiveSortColumn, sortDirection, getSortValue, extraColumnsByKey, podMatchesProblemFilter])
|
|
5468
5625
|
|
|
5469
5626
|
// For nodes table: compute the majority minor version so outliers can be highlighted
|
|
5470
5627
|
const majorityNodeMinorVersion = useMemo(() => {
|
|
@@ -5660,7 +5817,7 @@ export function ResourcesView({
|
|
|
5660
5817
|
|
|
5661
5818
|
// Calculate filter options with counts based on current resources (before filtering)
|
|
5662
5819
|
const filterOptions = useMemo(() => {
|
|
5663
|
-
if (!resources || resources.length === 0) return null
|
|
5820
|
+
if (!tableActive || !resources || resources.length === 0) return null
|
|
5664
5821
|
|
|
5665
5822
|
const kindLower = normalizeKindToPlural(selectedKind.name, selectedKind.group)
|
|
5666
5823
|
const cellFilterKind = getCellFilterKind(selectedKind.name, selectedKind.group)
|
|
@@ -5773,7 +5930,7 @@ export function ResourcesView({
|
|
|
5773
5930
|
|
|
5774
5931
|
if (filterableColumns.length === 0 && !problems && labelValues.length === 0) return null
|
|
5775
5932
|
return { columns: filterableColumns, problems, labels: labelValues }
|
|
5776
|
-
}, [resources, selectedKind.name, selectedKind.group, allColumns])
|
|
5933
|
+
}, [tableActive, resources, selectedKind.name, selectedKind.group, allColumns])
|
|
5777
5934
|
|
|
5778
5935
|
// Map filterable columns by key for O(1) lookup in header rendering
|
|
5779
5936
|
const filterableColumnMap = useMemo(() => {
|
|
@@ -5795,15 +5952,16 @@ export function ResourcesView({
|
|
|
5795
5952
|
// Namespace contribution gated on a host-wired clearer: without it the
|
|
5796
5953
|
// Clear filters button can't drop the namespace, so showing it would be
|
|
5797
5954
|
// a no-op for that case.
|
|
5798
|
-
const
|
|
5799
|
-
|
|
5800
|
-
|
|
5801
|
-
|
|
5802
|
-
|
|
5803
|
-
|
|
5804
|
-
|
|
5805
|
-
|
|
5806
|
-
|
|
5955
|
+
const activeFilterCount =
|
|
5956
|
+
Object.values(columnFilters).filter((vals) => vals.length > 0).length +
|
|
5957
|
+
(problemFilters.length > 0 ? 1 : 0) +
|
|
5958
|
+
(labelSelector ? 1 : 0) +
|
|
5959
|
+
(searchTerm || regexMode ? 1 : 0) +
|
|
5960
|
+
(hasOwnerFilter ? 1 : 0) +
|
|
5961
|
+
(showInactiveReplicaSets ? 1 : 0) +
|
|
5962
|
+
(!!onClearNamespaces && namespaces.length > 0 ? 1 : 0)
|
|
5963
|
+
const hasAnyFilter = activeFilterCount > 0
|
|
5964
|
+
const filtersRestored = !!kindFiltersSearch && kindFiltersSearch === restoredKindSearch
|
|
5807
5965
|
|
|
5808
5966
|
|
|
5809
5967
|
// Toggle problem filter
|
|
@@ -5879,7 +6037,9 @@ export function ResourcesView({
|
|
|
5879
6037
|
/>
|
|
5880
6038
|
)}
|
|
5881
6039
|
|
|
5882
|
-
{
|
|
6040
|
+
{kindView !== null ? (
|
|
6041
|
+
<div className="flex-1 flex flex-col overflow-hidden min-w-0 bg-theme-surface">{kindView}</div>
|
|
6042
|
+
) : (
|
|
5883
6043
|
<div className="flex-1 flex flex-col overflow-hidden min-w-0 bg-theme-surface">
|
|
5884
6044
|
{/* Toolbar */}
|
|
5885
6045
|
<div className="flex items-center gap-3 px-4 py-3 border-b border-theme-border bg-theme-base shrink-0">
|
|
@@ -6119,16 +6279,16 @@ export function ResourcesView({
|
|
|
6119
6279
|
<button
|
|
6120
6280
|
type="button"
|
|
6121
6281
|
onClick={clearAllFilters}
|
|
6122
|
-
className="flex items-center gap-1.5 px-2.5 py-1 rounded-lg text-xs
|
|
6282
|
+
className="flex items-center gap-1.5 px-2.5 py-1 rounded-lg text-xs selection selection-text hover:selection-strong transition-colors"
|
|
6123
6283
|
>
|
|
6124
6284
|
<RotateCcw className="w-3.5 h-3.5" />
|
|
6125
|
-
<span>Clear filters</span>
|
|
6285
|
+
<span>Clear filters ({activeFilterCount})</span>
|
|
6126
6286
|
</button>
|
|
6127
6287
|
</Tooltip>
|
|
6128
6288
|
)}
|
|
6129
6289
|
|
|
6130
6290
|
{/* Column picker */}
|
|
6131
|
-
<div className="relative" ref={columnPickerRef}>
|
|
6291
|
+
<div className="relative flex" ref={columnPickerRef}>
|
|
6132
6292
|
<Tooltip content="Configure columns">
|
|
6133
6293
|
<button
|
|
6134
6294
|
onClick={() => setShowColumnPicker(prev => !prev)}
|
|
@@ -6354,6 +6514,21 @@ export function ResourcesView({
|
|
|
6354
6514
|
</div>
|
|
6355
6515
|
)}
|
|
6356
6516
|
|
|
6517
|
+
{!isLoading && !isSelectedForbidden && !largeListGuard && filteredResources.length > 0 && (
|
|
6518
|
+
<ActiveFilterBar
|
|
6519
|
+
columnFilters={columnFilters}
|
|
6520
|
+
columnFilterExcludes={columnFilterExcludes}
|
|
6521
|
+
problemFilters={problemFilters}
|
|
6522
|
+
labelSelector={labelSelector}
|
|
6523
|
+
onClearColumn={clearColumnFilter}
|
|
6524
|
+
onClearProblems={() => setProblemFilters([])}
|
|
6525
|
+
onClearLabels={() => setLabelSelector('')}
|
|
6526
|
+
restored={filtersRestored}
|
|
6527
|
+
onClearAll={clearKindFilters}
|
|
6528
|
+
className="shrink-0 px-4 py-1.5 border-b border-theme-border"
|
|
6529
|
+
/>
|
|
6530
|
+
)}
|
|
6531
|
+
|
|
6357
6532
|
{/* Table */}
|
|
6358
6533
|
<div
|
|
6359
6534
|
className="flex-1 overflow-auto relative"
|
|
@@ -6430,46 +6605,17 @@ export function ResourcesView({
|
|
|
6430
6605
|
</button>
|
|
6431
6606
|
)}
|
|
6432
6607
|
{namespaces.length > 0 && <p className="text-sm mt-1 text-theme-text-disabled">Searching in {namespaces.length === 1 ? `namespace: ${namespaces[0]}` : `${namespaces.length} namespaces`}</p>}
|
|
6433
|
-
|
|
6434
|
-
|
|
6435
|
-
|
|
6436
|
-
|
|
6437
|
-
|
|
6438
|
-
|
|
6439
|
-
|
|
6440
|
-
|
|
6441
|
-
|
|
6442
|
-
|
|
6443
|
-
|
|
6444
|
-
>
|
|
6445
|
-
<ListFilter className="w-3 h-3" />
|
|
6446
|
-
<span>{key}: {columnFilterExcludes[key] ? 'not ' : ''}{vals.join(', ')}</span>
|
|
6447
|
-
<X className="w-3 h-3" />
|
|
6448
|
-
</button>
|
|
6449
|
-
))}
|
|
6450
|
-
{problemFilters.length > 0 && (
|
|
6451
|
-
<button
|
|
6452
|
-
onClick={() => setProblemFilters([])}
|
|
6453
|
-
className="flex items-center gap-1 px-2 py-1 text-xs bg-red-500/15 text-red-700 dark:text-red-300 rounded-md hover:bg-red-500/25 transition-colors"
|
|
6454
|
-
>
|
|
6455
|
-
<AlertTriangle className="w-3 h-3" />
|
|
6456
|
-
<span>Problems: {problemFilters.join(', ')}</span>
|
|
6457
|
-
<X className="w-3 h-3" />
|
|
6458
|
-
</button>
|
|
6459
|
-
)}
|
|
6460
|
-
{labelSelector && (
|
|
6461
|
-
<button
|
|
6462
|
-
onClick={() => setLabelSelector('')}
|
|
6463
|
-
className="flex items-center gap-1 px-2 py-1 text-xs bg-green-500/15 text-green-700 dark:text-green-300 rounded-md hover:bg-green-500/25 transition-colors"
|
|
6464
|
-
>
|
|
6465
|
-
<Tag className="w-3 h-3" />
|
|
6466
|
-
<span>{labelSelector}</span>
|
|
6467
|
-
<X className="w-3 h-3" />
|
|
6468
|
-
</button>
|
|
6469
|
-
)}
|
|
6470
|
-
</div>
|
|
6471
|
-
)
|
|
6472
|
-
})()}
|
|
6608
|
+
<ActiveFilterBar
|
|
6609
|
+
columnFilters={columnFilters}
|
|
6610
|
+
columnFilterExcludes={columnFilterExcludes}
|
|
6611
|
+
problemFilters={problemFilters}
|
|
6612
|
+
labelSelector={labelSelector}
|
|
6613
|
+
onClearColumn={clearColumnFilter}
|
|
6614
|
+
onClearProblems={() => setProblemFilters([])}
|
|
6615
|
+
onClearLabels={() => setLabelSelector('')}
|
|
6616
|
+
restored={filtersRestored}
|
|
6617
|
+
className="mt-3"
|
|
6618
|
+
/>
|
|
6473
6619
|
{hasAnyFilter && (
|
|
6474
6620
|
<button
|
|
6475
6621
|
type="button"
|
|
@@ -6697,6 +6843,22 @@ export function ResourcesView({
|
|
|
6697
6843
|
<div className="px-3 py-2 text-xs text-theme-text-disabled">No matches</div>
|
|
6698
6844
|
)}
|
|
6699
6845
|
</div>
|
|
6846
|
+
{col.key === 'namespace' && onApplyNamespacesToAllSections && activeFilterValues.length > 0 && !columnFilterExcludes[col.key] && (
|
|
6847
|
+
<div className="p-1 border-t border-theme-border">
|
|
6848
|
+
<button
|
|
6849
|
+
onClick={() => {
|
|
6850
|
+
setOpenColumnFilter(null)
|
|
6851
|
+
// Keep the column filter until the scope change lands; a failed one keeps the user's filter.
|
|
6852
|
+
Promise.resolve(onApplyNamespacesToAllSections(activeFilterValues))
|
|
6853
|
+
.then(() => clearColumnFilter(col.key), () => {})
|
|
6854
|
+
}}
|
|
6855
|
+
className="w-full text-left px-2 py-1.5 text-xs rounded text-theme-text-secondary hover:bg-theme-elevated hover:text-theme-text-primary transition-colors"
|
|
6856
|
+
title="Scope every section to these namespaces in the header namespace picker"
|
|
6857
|
+
>
|
|
6858
|
+
Apply to all sections
|
|
6859
|
+
</button>
|
|
6860
|
+
</div>
|
|
6861
|
+
)}
|
|
6700
6862
|
</div>
|
|
6701
6863
|
)
|
|
6702
6864
|
})()}
|
|
@@ -6775,11 +6937,12 @@ export function ResourcesView({
|
|
|
6775
6937
|
/>
|
|
6776
6938
|
)}
|
|
6777
6939
|
</div>
|
|
6940
|
+
)}
|
|
6778
6941
|
</div>
|
|
6779
6942
|
|
|
6780
6943
|
{/* Bulk delete confirmation */}
|
|
6781
6944
|
<ConfirmDialog
|
|
6782
|
-
open={showBulkDeleteConfirm}
|
|
6945
|
+
open={tableActive && showBulkDeleteConfirm}
|
|
6783
6946
|
onClose={() => { setShowBulkDeleteConfirm(false); setBulkForceDelete(false) }}
|
|
6784
6947
|
onConfirm={() => {
|
|
6785
6948
|
onBulkDelete?.(checkedBulkItems, {
|
|
@@ -6810,7 +6973,7 @@ export function ResourcesView({
|
|
|
6810
6973
|
</label>
|
|
6811
6974
|
</ConfirmDialog>
|
|
6812
6975
|
<ConfirmDialog
|
|
6813
|
-
open={showBulkRestartConfirm}
|
|
6976
|
+
open={tableActive && showBulkRestartConfirm}
|
|
6814
6977
|
onClose={() => setShowBulkRestartConfirm(false)}
|
|
6815
6978
|
onConfirm={() => {
|
|
6816
6979
|
onBulkRestart?.(checkedBulkItems, {
|
|
@@ -6829,7 +6992,7 @@ export function ResourcesView({
|
|
|
6829
6992
|
isClosable
|
|
6830
6993
|
/>
|
|
6831
6994
|
<ConfirmDialog
|
|
6832
|
-
open={showBulkScaleDialog}
|
|
6995
|
+
open={tableActive && showBulkScaleDialog}
|
|
6833
6996
|
onClose={() => setShowBulkScaleDialog(false)}
|
|
6834
6997
|
onConfirm={() => {
|
|
6835
6998
|
onBulkScale?.(checkedBulkItems, bulkScaleReplicas, {
|
|
@@ -7235,6 +7398,7 @@ function CellContent({ resource, kind, column, group, majorityNodeMinorVersion,
|
|
|
7235
7398
|
case 'grpcroutes':
|
|
7236
7399
|
case 'tcproutes':
|
|
7237
7400
|
case 'tlsroutes':
|
|
7401
|
+
case 'udproutes':
|
|
7238
7402
|
return <RouteCell resource={resource} column={column} />
|
|
7239
7403
|
case 'gatewayclasses':
|
|
7240
7404
|
return <GatewayClassCell resource={resource} column={column} />
|
|
@@ -7925,9 +8089,9 @@ function PodCell({ resource, column }: { resource: any; column: string }) {
|
|
|
7925
8089
|
const label = isCPU ? 'CPU' : 'Memory'
|
|
7926
8090
|
const format = isCPU ? formatCPU : formatMemoryShort
|
|
7927
8091
|
|
|
7928
|
-
// Multi-container pods
|
|
7929
|
-
// pods fall back to the
|
|
7930
|
-
//
|
|
8092
|
+
// Multi-container pods (and any pod with a pod-level budget) carry a
|
|
8093
|
+
// per-container breakdown; other single-container pods fall back to the
|
|
8094
|
+
// pod-level fields as one synthetic container so the logic is uniform.
|
|
7931
8095
|
const list: ContainerResourceMetrics[] = (m.containers && m.containers.length > 0)
|
|
7932
8096
|
? m.containers
|
|
7933
8097
|
: [{
|
|
@@ -7936,7 +8100,15 @@ function PodCell({ resource, column }: { resource: any; column: string }) {
|
|
|
7936
8100
|
memory: m.memory, memoryRequest: m.memoryRequest, memoryLimit: m.memoryLimit,
|
|
7937
8101
|
}]
|
|
7938
8102
|
|
|
7939
|
-
|
|
8103
|
+
// The pod totals already carry any pod-level value (overlaid server-side
|
|
8104
|
+
// with exact quantity parsing); spec.resources only says whether one is set.
|
|
8105
|
+
const podLevel = resource.spec?.resources
|
|
8106
|
+
const podRequest = podLevel?.requests?.[kind] ? (isCPU ? m.cpuRequest : m.memoryRequest) : undefined
|
|
8107
|
+
const podLimit = podLevel?.limits?.[kind] ? (isCPU ? m.cpuLimit : m.memoryLimit) : undefined
|
|
8108
|
+
const { mode, totalUsage, denom, markerPct, unlimitedCount } = podAggregate(list, kind, {
|
|
8109
|
+
request: podRequest,
|
|
8110
|
+
limit: podLimit,
|
|
8111
|
+
})
|
|
7940
8112
|
if (totalUsage === 0) return <span className="text-sm text-theme-text-tertiary">-</span>
|
|
7941
8113
|
|
|
7942
8114
|
// Pod-vs-node context: how full the pod's node is (the risk signal) and
|
|
@@ -7956,7 +8128,12 @@ function PodCell({ resource, column }: { resource: any; column: string }) {
|
|
|
7956
8128
|
}
|
|
7957
8129
|
: undefined
|
|
7958
8130
|
|
|
7959
|
-
|
|
8131
|
+
// The synthetic single-container row already carries the pod totals, so
|
|
8132
|
+
// only a real per-container breakdown needs the shared budget spelled out.
|
|
8133
|
+
const podBudget = podLimit && m.containers?.length
|
|
8134
|
+
? { usage: totalUsage, limit: podLimit, request: podRequest }
|
|
8135
|
+
: undefined
|
|
8136
|
+
const tip = buildContainerResourceTooltip(label, list, kind, format, nodeCtx, podBudget)
|
|
7960
8137
|
|
|
7961
8138
|
// Every container is limited → aggregate bar against the summed limit; a
|
|
7962
8139
|
// real pod-level ceiling, so it may go red.
|
|
@@ -8581,12 +8758,16 @@ interface PodAggregate {
|
|
|
8581
8758
|
// headline renders — total usage measured against a pod-level yardstick. It
|
|
8582
8759
|
// keeps the dominant consumer visible instead of demoting it behind a small
|
|
8583
8760
|
// limited container, and stays honest about partial limits (which don't form a
|
|
8584
|
-
// real ceiling).
|
|
8585
|
-
|
|
8761
|
+
// real ceiling). A pod-level budget (spec.resources) bounds every container, so
|
|
8762
|
+
// its limit is a real ceiling regardless of what the containers set.
|
|
8763
|
+
export function podAggregate(
|
|
8764
|
+
list: ContainerResourceMetrics[],
|
|
8765
|
+
kind: 'cpu' | 'memory',
|
|
8766
|
+
podLevel?: { request?: number; limit?: number },
|
|
8767
|
+
): PodAggregate {
|
|
8586
8768
|
const isCPU = kind === 'cpu'
|
|
8587
8769
|
let totalUsage = 0
|
|
8588
8770
|
let limitedCount = 0
|
|
8589
|
-
let requestedCount = 0
|
|
8590
8771
|
let unlimitedCount = 0
|
|
8591
8772
|
let summedLimit = 0
|
|
8592
8773
|
let summedRequest = 0
|
|
@@ -8601,25 +8782,28 @@ export function podAggregate(list: ContainerResourceMetrics[], kind: 'cpu' | 'me
|
|
|
8601
8782
|
} else {
|
|
8602
8783
|
unlimitedCount++
|
|
8603
8784
|
}
|
|
8604
|
-
if (request > 0)
|
|
8605
|
-
requestedCount++
|
|
8606
|
-
summedRequest += request
|
|
8607
|
-
}
|
|
8785
|
+
if (request > 0) summedRequest += request
|
|
8608
8786
|
}
|
|
8787
|
+
if (podLevel?.request) summedRequest = podLevel.request
|
|
8609
8788
|
const allLimited = list.length > 0 && limitedCount === list.length
|
|
8610
|
-
|
|
8789
|
+
// Container limits still apply inside a pod-level budget, so when every
|
|
8790
|
+
// container has one the tighter of the two totals is the real ceiling.
|
|
8791
|
+
const ceiling = podLevel?.limit && allLimited
|
|
8792
|
+
? Math.min(podLevel.limit, summedLimit)
|
|
8793
|
+
: podLevel?.limit || (allLimited ? summedLimit : 0)
|
|
8794
|
+
if (ceiling) {
|
|
8611
8795
|
return {
|
|
8612
8796
|
mode: 'limit',
|
|
8613
8797
|
totalUsage,
|
|
8614
|
-
denom:
|
|
8615
|
-
markerPct: summedRequest > 0 ? (summedRequest /
|
|
8798
|
+
denom: ceiling,
|
|
8799
|
+
markerPct: summedRequest > 0 ? (summedRequest / ceiling) * 100 : undefined,
|
|
8616
8800
|
unlimitedCount,
|
|
8617
8801
|
}
|
|
8618
8802
|
}
|
|
8619
8803
|
if (limitedCount > 0) {
|
|
8620
8804
|
return { mode: 'partial', totalUsage, denom: 0, unlimitedCount }
|
|
8621
8805
|
}
|
|
8622
|
-
if (
|
|
8806
|
+
if (summedRequest > 0) {
|
|
8623
8807
|
return { mode: 'request', totalUsage, denom: summedRequest, unlimitedCount }
|
|
8624
8808
|
}
|
|
8625
8809
|
return { mode: 'none', totalUsage, denom: 0, unlimitedCount }
|
|
@@ -8647,12 +8831,13 @@ function formatSharePct(pct: number): string {
|
|
|
8647
8831
|
return `${Math.round(pct)}%`
|
|
8648
8832
|
}
|
|
8649
8833
|
|
|
8650
|
-
function buildContainerResourceTooltip(
|
|
8834
|
+
export function buildContainerResourceTooltip(
|
|
8651
8835
|
label: 'CPU' | 'Memory',
|
|
8652
8836
|
containers: ContainerResourceMetrics[],
|
|
8653
8837
|
kind: 'cpu' | 'memory',
|
|
8654
8838
|
formatFn: (n: number) => string,
|
|
8655
8839
|
nodeCtx?: NodeContext,
|
|
8840
|
+
podBudget?: { usage: number; limit: number; request?: number },
|
|
8656
8841
|
) {
|
|
8657
8842
|
const rows = [...containers].sort((a, b) => {
|
|
8658
8843
|
const diff = readContainer(b, kind).pct - readContainer(a, kind).pct
|
|
@@ -8675,7 +8860,7 @@ function buildContainerResourceTooltip(
|
|
|
8675
8860
|
? `${formatFn(r.usage)} · ${Math.round(r.pct)}% · ${formatFn(r.denom)} limit${r.request > 0 ? ` · req ${formatFn(r.request)}` : ''}`
|
|
8676
8861
|
: r.yardstick === 'request'
|
|
8677
8862
|
? `${formatFn(r.usage)} · ${Math.round(r.pct)}% · ${formatFn(r.denom)} request`
|
|
8678
|
-
: `${formatFn(r.usage)} · no limit`
|
|
8863
|
+
: `${formatFn(r.usage)} · ${podBudget ? 'pod limit' : 'no limit'}`
|
|
8679
8864
|
return (
|
|
8680
8865
|
<div key={c.name} className="flex flex-col leading-snug">
|
|
8681
8866
|
<span className="text-[11px] text-theme-text-tertiary truncate">{c.name}</span>
|
|
@@ -8689,6 +8874,14 @@ function buildContainerResourceTooltip(
|
|
|
8689
8874
|
+{remaining} more → open pod
|
|
8690
8875
|
</div>
|
|
8691
8876
|
)}
|
|
8877
|
+
{podBudget && (
|
|
8878
|
+
<div className="flex flex-col leading-snug border-t border-theme-border/50 pt-1">
|
|
8879
|
+
<span className="text-[11px] text-theme-text-tertiary truncate">Pod (shared)</span>
|
|
8880
|
+
<span className="text-xs font-mono text-theme-text-primary">
|
|
8881
|
+
{`${formatFn(podBudget.usage)} · ${Math.round((podBudget.usage / podBudget.limit) * 100)}% · ${formatFn(podBudget.limit)} limit${podBudget.request ? ` · req ${formatFn(podBudget.request)}` : ''}`}
|
|
8882
|
+
</span>
|
|
8883
|
+
</div>
|
|
8884
|
+
)}
|
|
8692
8885
|
{nodeCtx && (
|
|
8693
8886
|
<div className="flex flex-col leading-snug border-t border-theme-border/50 pt-1">
|
|
8694
8887
|
<span className="text-[11px] text-theme-text-tertiary truncate">Node {nodeCtx.name}</span>
|
|
@@ -8722,7 +8915,7 @@ function NodeCell({ resource, column, majorityNodeMinorVersion }: { resource: an
|
|
|
8722
8915
|
</span>
|
|
8723
8916
|
{problems.length > 0 && (
|
|
8724
8917
|
<Tooltip content={problems.join(', ')}>
|
|
8725
|
-
<span className=
|
|
8918
|
+
<span className={SEVERITY_TEXT[healthToSeverity(status.level ?? 'unknown')]}>
|
|
8726
8919
|
<AlertTriangle className="w-3.5 h-3.5" />
|
|
8727
8920
|
</span>
|
|
8728
8921
|
</Tooltip>
|
|
@@ -8735,7 +8928,8 @@ function NodeCell({ resource, column, majorityNodeMinorVersion }: { resource: an
|
|
|
8735
8928
|
return <span className="text-sm text-theme-text-secondary">{roles}</span>
|
|
8736
8929
|
}
|
|
8737
8930
|
case 'conditions': {
|
|
8738
|
-
const { problems, healthy } = getNodeConditions(resource)
|
|
8931
|
+
const { problems, healthy, readinessLabel } = getNodeConditions(resource)
|
|
8932
|
+
if (problems.length === 0 && !healthy) return <span className="text-theme-text-secondary">{readinessLabel}</span>
|
|
8739
8933
|
if (healthy) {
|
|
8740
8934
|
return <span className="text-sm text-green-400">Healthy</span>
|
|
8741
8935
|
}
|
|
@@ -9129,7 +9323,7 @@ function GatewayClassCell({ resource, column }: { resource: any; column: string
|
|
|
9129
9323
|
}
|
|
9130
9324
|
}
|
|
9131
9325
|
|
|
9132
|
-
// Shared cell renderer for all Gateway API route types (HTTPRoute, GRPCRoute, TCPRoute, TLSRoute)
|
|
9326
|
+
// Shared cell renderer for all Gateway API route types (HTTPRoute, GRPCRoute, TCPRoute, TLSRoute, UDPRoute)
|
|
9133
9327
|
function RouteCell({ resource, column }: { resource: any; column: string }) {
|
|
9134
9328
|
switch (column) {
|
|
9135
9329
|
case 'status': {
|
|
@@ -9318,9 +9512,7 @@ function LeaseCell({ resource, column }: { resource: any; column: string }) {
|
|
|
9318
9512
|
const elapsed = (Date.now() - new Date(renewTime).getTime()) / 1000
|
|
9319
9513
|
const duration = resource.spec?.leaseDurationSeconds || 0
|
|
9320
9514
|
const isStale = duration > 0 && elapsed > duration
|
|
9321
|
-
const
|
|
9322
|
-
const seconds = Math.floor(diff / 1000)
|
|
9323
|
-
const label = seconds < 60 ? `${seconds}s ago` : seconds < 3600 ? `${Math.floor(seconds / 60)}m ago` : seconds < 86400 ? `${Math.floor(seconds / 3600)}h ago` : `${Math.floor(seconds / 86400)}d ago`
|
|
9515
|
+
const label = formatRelativeAgeTime(renewTime)
|
|
9324
9516
|
return <span className={clsx('text-sm', isStale ? 'text-red-400' : 'text-green-400')} title={new Date(renewTime).toLocaleString()}>{label}</span>
|
|
9325
9517
|
}
|
|
9326
9518
|
default:
|