@skyhook-io/k8s-ui 1.14.1 → 1.14.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/components/issues/IssuesView.tsx +1 -1
- package/src/components/issues/types.ts +2 -0
- package/src/components/resources/ResourcesView.tsx +129 -67
- package/src/components/resources/column-settings-merge.test.ts +104 -0
- package/src/components/resources/istio-gateway-filter.test.ts +5 -3
- package/src/components/resources/istio-policy-accessors.test.ts +193 -0
- package/src/components/resources/renderers/IstioAuthorizationPolicyRenderer.tsx +11 -24
- package/src/components/resources/renderers/eso-cells.tsx +0 -5
- package/src/components/resources/renderers/istio-cells.tsx +36 -26
- package/src/components/resources/renderers/kueue-cells.tsx +0 -9
- package/src/components/resources/renderers/kyverno-cells.tsx +1 -1
- package/src/components/resources/renderers/prometheus-cells.tsx +3 -3
- package/src/components/resources/renderers/ray-cells.tsx +12 -4
- package/src/components/resources/renderers/volcano-cells.tsx +0 -9
- package/src/components/resources/resource-utils-cnpg.ts +5 -0
- package/src/components/resources/resource-utils-eso.test.ts +56 -0
- package/src/components/resources/resource-utils-eso.ts +37 -5
- package/src/components/resources/resource-utils-istio.ts +152 -44
- package/src/components/resources/resource-utils-kyverno.ts +3 -6
- package/src/components/resources/resource-utils-prometheus.ts +51 -23
- package/src/components/resources/resource-utils-ray.test.ts +36 -0
- package/src/components/resources/resource-utils-ray.ts +6 -0
- package/src/components/resources/resource-utils.ts +166 -18
- package/src/components/resources/status-accessors.test.ts +363 -0
- package/src/components/resources/status-overclaim.test.ts +158 -0
- package/src/components/timeline/DiffViewer.test.tsx +26 -0
- package/src/components/timeline/DiffViewer.tsx +16 -12
- package/src/components/topology/TopologyGraph.tsx +7 -2
- package/src/components/ui/Collapse.tsx +9 -2
- package/src/components/ui/ConfirmDialog.test.tsx +37 -0
- package/src/components/ui/ConfirmDialog.tsx +3 -1
- package/src/components/ui/drawer-components.tsx +1 -1
- package/src/components/workload/WorkloadView.tsx +2 -1
package/package.json
CHANGED
|
@@ -63,7 +63,7 @@ export interface IssuesViewProps {
|
|
|
63
63
|
/** Empty-state CTA shown when there's no data. */
|
|
64
64
|
emptyAction?: ReactNode;
|
|
65
65
|
/** Per-row trailing action, rendered after the severity badge — e.g. the
|
|
66
|
-
* "
|
|
66
|
+
* "Investigate with AI" button in OSS. Omit to render no per-row action. */
|
|
67
67
|
renderActions?: (ctx: IssueRowSlotContext) => ReactNode;
|
|
68
68
|
}
|
|
69
69
|
|
|
@@ -121,6 +121,8 @@ export interface IssueRecentChangeField {
|
|
|
121
121
|
export interface IssueRecentChange {
|
|
122
122
|
source?: string;
|
|
123
123
|
kind: string;
|
|
124
|
+
/** Kubernetes API version for an exact, collision-safe resource identity. */
|
|
125
|
+
apiVersion?: string;
|
|
124
126
|
namespace?: string;
|
|
125
127
|
name: string;
|
|
126
128
|
changeType: string;
|
|
@@ -110,6 +110,7 @@ import {
|
|
|
110
110
|
getGatewayClassController,
|
|
111
111
|
getGatewayClassDescription,
|
|
112
112
|
getRouteStatus,
|
|
113
|
+
getRouteStatusReason,
|
|
113
114
|
getRouteParents,
|
|
114
115
|
getRouteHostnames,
|
|
115
116
|
getRouteBackends,
|
|
@@ -566,7 +567,7 @@ const KNOWN_COLUMNS: Record<string, Column[]> = {
|
|
|
566
567
|
{ key: 'name', label: 'Name' },
|
|
567
568
|
{ key: 'namespace', label: 'Namespace', width: 'w-48' },
|
|
568
569
|
{ key: 'status', label: 'Status', width: 'w-40' },
|
|
569
|
-
{ key: 'desired', label: 'Desired', width: 'w-20', tooltip: '
|
|
570
|
+
{ key: 'desired', label: 'Desired', width: 'w-20', tooltip: 'Nodes eligible for this daemon after node selectors, required node affinity and taints/tolerations are applied' },
|
|
570
571
|
{ key: 'ready', label: 'Ready', width: 'w-20', tooltip: 'Number of pods that are ready (passing readiness probes)' },
|
|
571
572
|
{ key: 'upToDate', label: 'Up-to-date', width: 'w-32', tooltip: 'Number of pods running the current pod template spec' },
|
|
572
573
|
{ key: 'available', label: 'Available', width: 'w-28', tooltip: 'Number of pods available (ready for minReadySeconds duration)' },
|
|
@@ -578,7 +579,7 @@ const KNOWN_COLUMNS: Record<string, Column[]> = {
|
|
|
578
579
|
{ key: 'namespace', label: 'Namespace', width: 'w-48' },
|
|
579
580
|
{ key: 'status', label: 'Status', width: 'w-40' },
|
|
580
581
|
{ key: 'ready', label: 'Ready', width: 'w-24', tooltip: 'Ready pods / Desired replicas' },
|
|
581
|
-
{ key: 'upToDate', label: 'Up-to-date', width: 'w-32', tooltip: '
|
|
582
|
+
{ key: 'upToDate', label: 'Up-to-date', width: 'w-32', tooltip: 'Pods running the current pod template; a rolling-update partition or OnDelete strategy can intentionally leave pods on an older template' },
|
|
582
583
|
{ key: 'images', label: 'Images', width: 'w-48' },
|
|
583
584
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
584
585
|
],
|
|
@@ -587,7 +588,7 @@ const KNOWN_COLUMNS: Record<string, Column[]> = {
|
|
|
587
588
|
{ key: 'namespace', label: 'Namespace', width: 'w-48' },
|
|
588
589
|
{ key: 'ready', label: 'Ready', width: 'w-24' },
|
|
589
590
|
{ key: 'owner', label: 'Owner', width: 'w-48' },
|
|
590
|
-
{ key: 'status', label: 'Status', width: 'w-24' },
|
|
591
|
+
{ key: 'status', label: 'Status', width: 'w-24', tooltip: 'Active means desired replicas are greater than zero; Old means desired replicas are zero, regardless of Deployment revision' },
|
|
591
592
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
592
593
|
],
|
|
593
594
|
services: [
|
|
@@ -623,7 +624,7 @@ const KNOWN_COLUMNS: Record<string, Column[]> = {
|
|
|
623
624
|
nodes: [
|
|
624
625
|
{ key: 'name', label: 'Name' },
|
|
625
626
|
{ key: 'status', label: 'Status', width: 'w-44' },
|
|
626
|
-
{ key: 'roles', label: 'Roles', width: 'w-28' },
|
|
627
|
+
{ key: 'roles', label: 'Roles', width: 'w-28', tooltip: 'Node roles from node-role.kubernetes.io/* labels. A node with no role label shows as worker.' },
|
|
627
628
|
{ key: 'cpu', label: 'CPU', width: 'w-40', tooltip: 'Current CPU usage / allocatable' },
|
|
628
629
|
{ key: 'memory', label: 'Memory', width: 'w-40', tooltip: 'Current memory usage / allocatable' },
|
|
629
630
|
{ key: 'pods', label: 'Pods', width: 'w-28', tooltip: 'Pods running / allocatable' },
|
|
@@ -637,7 +638,7 @@ const KNOWN_COLUMNS: Record<string, Column[]> = {
|
|
|
637
638
|
{ key: 'name', label: 'Name' },
|
|
638
639
|
{ key: 'namespace', label: 'Namespace', width: 'w-48' },
|
|
639
640
|
{ key: 'keys', label: 'Keys', width: 'w-48' },
|
|
640
|
-
{ key: 'size', label: 'Size', width: 'w-24' },
|
|
641
|
+
{ key: 'size', label: 'Size', width: 'w-24', tooltip: 'Estimated size of data values, including decoded binary data. Kubernetes rejects ConfigMaps whose combined data exceeds 1 MiB.' },
|
|
641
642
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
642
643
|
],
|
|
643
644
|
secrets: [
|
|
@@ -645,7 +646,7 @@ const KNOWN_COLUMNS: Record<string, Column[]> = {
|
|
|
645
646
|
{ key: 'namespace', label: 'Namespace', width: 'w-48' },
|
|
646
647
|
{ key: 'type', label: 'Type', width: 'w-28' },
|
|
647
648
|
{ key: 'keys', label: 'Keys', width: 'w-20' },
|
|
648
|
-
{ key: 'expires', label: 'Expires', width: 'w-24', tooltip:
|
|
649
|
+
{ key: 'expires', label: 'Expires', width: 'w-24', tooltip: "Days until the certificate in a TLS Secret expires. Blank for Secrets carrying no readable TLS certificate; ! means the expiry lookup itself failed." },
|
|
649
650
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
650
651
|
],
|
|
651
652
|
jobs: [
|
|
@@ -659,7 +660,7 @@ const KNOWN_COLUMNS: Record<string, Column[]> = {
|
|
|
659
660
|
cronjobs: [
|
|
660
661
|
{ key: 'name', label: 'Name' },
|
|
661
662
|
{ key: 'namespace', label: 'Namespace', width: 'w-48' },
|
|
662
|
-
{ key: 'schedule', label: 'Schedule', width: 'w-40' },
|
|
663
|
+
{ key: 'schedule', label: 'Schedule', width: 'w-40', tooltip: "Cron schedule; fires in spec.timeZone when set, otherwise the controller's zone" },
|
|
663
664
|
{ key: 'status', label: 'Status', width: 'w-28' },
|
|
664
665
|
{ key: 'lastRun', label: 'Last Run', width: 'w-28' },
|
|
665
666
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
@@ -685,10 +686,10 @@ const KNOWN_COLUMNS: Record<string, Column[]> = {
|
|
|
685
686
|
persistentvolumeclaims: [
|
|
686
687
|
{ key: 'name', label: 'Name' },
|
|
687
688
|
{ key: 'namespace', label: 'Namespace', width: 'w-48' },
|
|
688
|
-
{ key: 'status', label: 'Status', width: 'w-24' },
|
|
689
|
+
{ key: 'status', label: 'Status', width: 'w-24', tooltip: 'With WaitForFirstConsumer, Pending can mean no pod has triggered binding yet. If a pod is waiting, check PVC events.' },
|
|
689
690
|
{ key: 'capacity', label: 'Capacity', width: 'w-24' },
|
|
690
691
|
{ key: 'storageClass', label: 'Storage Class', width: 'w-40' },
|
|
691
|
-
{ key: 'accessModes', label: 'Access', width: 'w-20', tooltip: '
|
|
692
|
+
{ key: 'accessModes', label: 'Access', width: 'w-20', tooltip: 'RWO=ReadWriteOnce (one node, possibly several pods), ROX=ReadOnlyMany (read-only on many nodes), RWX=ReadWriteMany (read-write on many nodes), RWOP=ReadWriteOncePod (one pod only)' },
|
|
692
693
|
{ key: 'volume', label: 'Volume', width: 'w-48' },
|
|
693
694
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
694
695
|
],
|
|
@@ -733,16 +734,16 @@ const KNOWN_COLUMNS: Record<string, Column[]> = {
|
|
|
733
734
|
{ key: 'namespace', label: 'Namespace', width: 'w-48' },
|
|
734
735
|
{ key: 'status', label: 'Ready', width: 'w-24' },
|
|
735
736
|
{ key: 'domains', label: 'Domains', width: 'w-56' },
|
|
736
|
-
{ key: 'issuer', label: 'Issuer', width: 'w-36' },
|
|
737
|
+
{ key: 'issuer', label: 'Issuer', width: 'w-36', tooltip: "Issuer name alone may be ambiguous. Check the certificate's issuer reference for its kind and API group." },
|
|
737
738
|
{ key: 'expires', label: 'Expires', width: 'w-24', tooltip: 'Days until certificate expires' },
|
|
738
739
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
739
740
|
],
|
|
740
741
|
persistentvolumes: [
|
|
741
742
|
{ key: 'name', label: 'Name' },
|
|
742
|
-
{ key: 'status', label: 'Status', width: 'w-24' },
|
|
743
|
+
{ key: 'status', label: 'Status', width: 'w-24', tooltip: "Released means the claim is gone but the volume still holds the previous claimant's data, so it will not be re-bound — check Reclaim before reusing it." },
|
|
743
744
|
{ key: 'capacity', label: 'Capacity', width: 'w-24' },
|
|
744
|
-
{ key: 'accessModes', label: 'Access', width: 'w-20', tooltip: 'RWO=ReadWriteOnce, ROX=ReadOnlyMany, RWX=ReadWriteMany' },
|
|
745
|
-
{ key: 'reclaimPolicy', label: 'Reclaim', width: 'w-20' },
|
|
745
|
+
{ key: 'accessModes', label: 'Access', width: 'w-20', tooltip: 'RWO=ReadWriteOnce (one node, possibly several pods), ROX=ReadOnlyMany (read-only on many nodes), RWX=ReadWriteMany (read-write on many nodes), RWOP=ReadWriteOncePod (one pod only)' },
|
|
746
|
+
{ key: 'reclaimPolicy', label: 'Reclaim', width: 'w-20', tooltip: 'When the claim is deleted, Delete removes the backing storage and its data; Retain leaves it for manual reclamation.' },
|
|
746
747
|
{ key: 'storageClass', label: 'Storage Class', width: 'w-40' },
|
|
747
748
|
{ key: 'claim', label: 'Claim', width: 'w-48' },
|
|
748
749
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
@@ -750,8 +751,8 @@ const KNOWN_COLUMNS: Record<string, Column[]> = {
|
|
|
750
751
|
storageclasses: [
|
|
751
752
|
{ key: 'name', label: 'Name' },
|
|
752
753
|
{ key: 'provisioner', label: 'Provisioner', width: 'w-48' },
|
|
753
|
-
{ key: 'reclaimPolicy', label: 'Reclaim', width: 'w-20' },
|
|
754
|
-
{ key: 'bindingMode', label: 'Binding Mode', width: 'w-36' },
|
|
754
|
+
{ key: 'reclaimPolicy', label: 'Reclaim', width: 'w-20', tooltip: 'Default policy for volumes this class provisions — Delete removes the backing storage when the claim goes away; Retain leaves it for manual reclamation.' },
|
|
755
|
+
{ key: 'bindingMode', label: 'Binding Mode', width: 'w-36', tooltip: "When the volume is provisioned — WaitForFirstConsumer waits for a pod so the disk lands in the right zone; Immediate provisions before the pod's placement is known, which can leave the pod unschedulable." },
|
|
755
756
|
{ key: 'expansion', label: 'Expansion', width: 'w-24', tooltip: 'Whether volumes can be expanded after creation' },
|
|
756
757
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
757
758
|
],
|
|
@@ -779,7 +780,7 @@ const KNOWN_COLUMNS: Record<string, Column[]> = {
|
|
|
779
780
|
orders: [
|
|
780
781
|
{ key: 'name', label: 'Name' },
|
|
781
782
|
{ key: 'namespace', label: 'Namespace', width: 'w-48' },
|
|
782
|
-
{ key: 'state', label: 'State', width: 'w-24' },
|
|
783
|
+
{ key: 'state', label: 'State', width: 'w-24', tooltip: 'Valid means ACME issuance finished; it does not confirm the TLS Secret is ready. If Pending persists, inspect the challenges and the order reason.' },
|
|
783
784
|
{ key: 'domains', label: 'Domains', width: 'w-48' },
|
|
784
785
|
{ key: 'issuer', label: 'Issuer', width: 'w-36' },
|
|
785
786
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
@@ -788,25 +789,27 @@ const KNOWN_COLUMNS: Record<string, Column[]> = {
|
|
|
788
789
|
{ key: 'name', label: 'Name' },
|
|
789
790
|
{ key: 'namespace', label: 'Namespace', width: 'w-48' },
|
|
790
791
|
{ key: 'challengeType', label: 'Type', width: 'w-20' },
|
|
791
|
-
{ key: 'state', label: 'State', width: 'w-24' },
|
|
792
|
+
{ key: 'state', label: 'State', width: 'w-24', tooltip: 'Valid means domain authorization succeeded. If Pending persists, inspect the reason for scheduling, solver, or propagation problems.' },
|
|
792
793
|
{ key: 'domain', label: 'Domain', width: 'w-48' },
|
|
793
|
-
{ key: 'presented', label: 'Presented', width: 'w-24' },
|
|
794
|
+
{ key: 'presented', label: 'Presented', width: 'w-24', tooltip: 'While Pending, Yes means the solver presented the token, not that it is reachable. Check the reason for propagation failures; No can also mean queued.' },
|
|
794
795
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
795
796
|
],
|
|
796
797
|
gateways: [
|
|
797
798
|
{ key: 'name', label: 'Name' },
|
|
798
799
|
{ key: 'namespace', label: 'Namespace', width: 'w-48' },
|
|
799
|
-
{ key: 'status', label: 'Status', width: 'w-28' },
|
|
800
|
+
{ key: 'status', label: 'Status', width: 'w-28', tooltip: "Programmed = the controller reports configuration should take effect soon; traffic has not been tested. Accepted = the controller accepted this Gateway but has not reported Programmed. If it stays Accepted, check the Gateway's conditions and addresses." },
|
|
800
801
|
{ key: 'class', label: 'Class', width: 'w-36' },
|
|
801
802
|
{ key: 'listeners', label: 'Listeners', width: 'w-40', tooltip: 'Protocol:Port for each listener' },
|
|
802
|
-
|
|
803
|
+
// attachedRoutes is reported per listener, so a route attached to several
|
|
804
|
+
// listeners is counted once for each — the total is not a route count.
|
|
805
|
+
{ key: 'routes', label: 'Routes', width: 'w-20', tooltip: "Sum of route counts reported by listeners; one route can count more than once. If 0 is unexpected, check the routes' Gateway names, listener permissions, and controller status." },
|
|
803
806
|
{ key: 'addresses', label: 'Addresses', width: 'w-48' },
|
|
804
807
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
805
808
|
],
|
|
806
809
|
httproutes: [
|
|
807
810
|
{ key: 'name', label: 'Name' },
|
|
808
811
|
{ key: 'namespace', label: 'Namespace', width: 'w-48' },
|
|
809
|
-
{ key: 'status', label: 'Status', width: 'w-28' },
|
|
812
|
+
{ 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.' },
|
|
810
813
|
{ key: 'hostnames', label: 'Hostnames', width: 'w-48' },
|
|
811
814
|
{ key: 'parents', label: 'Gateways', width: 'w-36' },
|
|
812
815
|
{ key: 'backends', label: 'Backends', width: 'w-48', tooltip: 'Backend services receiving traffic' },
|
|
@@ -818,7 +821,7 @@ const KNOWN_COLUMNS: Record<string, Column[]> = {
|
|
|
818
821
|
{ key: 'namespace', label: 'Namespace', width: 'w-48' },
|
|
819
822
|
{ key: 'status', label: 'Status', width: 'w-28' },
|
|
820
823
|
{ key: 'servers', label: 'Servers', width: 'w-24' },
|
|
821
|
-
{ key: 'selector', label: 'Selector', width: 'w-56' },
|
|
824
|
+
{ key: 'selector', label: 'Selector', width: 'w-56', tooltip: "Selects gateway workloads that receive this configuration. An omitted selector applies to all workloads in Istio's selection scope; it does not mean none." },
|
|
822
825
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
823
826
|
],
|
|
824
827
|
gatewayclasses: [
|
|
@@ -838,7 +841,6 @@ const KNOWN_COLUMNS: Record<string, Column[]> = {
|
|
|
838
841
|
],
|
|
839
842
|
nodeclaims: [
|
|
840
843
|
{ key: 'name', label: 'Name' },
|
|
841
|
-
{ key: 'namespace', label: 'Namespace', width: 'w-36' },
|
|
842
844
|
{ key: 'status', label: 'Status', width: 'w-24' },
|
|
843
845
|
{ key: 'instanceType', label: 'Instance Type', width: 'w-32' },
|
|
844
846
|
{ key: 'capacityType', label: 'Capacity', width: 'w-24', tooltip: 'Spot or On-Demand' },
|
|
@@ -868,7 +870,7 @@ const KNOWN_COLUMNS: Record<string, Column[]> = {
|
|
|
868
870
|
{ key: 'name', label: 'Name' },
|
|
869
871
|
{ key: 'namespace', label: 'Namespace', width: 'w-36' },
|
|
870
872
|
{ key: 'status', label: 'Status', width: 'w-28', tooltip: 'Allocated + reserved, allocated-but-unreserved, or pending' },
|
|
871
|
-
{ key: 'deviceClass', label: 'Device Class', width: 'w-44' },
|
|
873
|
+
{ key: 'deviceClass', label: 'Device Class', width: 'w-44', tooltip: 'DeviceClasses whose selectors constrain eligible devices. A request may list alternative classes; this column does not show which alternative was allocated.' },
|
|
872
874
|
{ key: 'allocated', label: 'Allocated Driver', width: 'w-44' },
|
|
873
875
|
{ key: 'reservedFor', label: 'Reserved For', width: 'w-44' },
|
|
874
876
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
@@ -881,15 +883,17 @@ const KNOWN_COLUMNS: Record<string, Column[]> = {
|
|
|
881
883
|
],
|
|
882
884
|
deviceclasses: [
|
|
883
885
|
{ key: 'name', label: 'Name' },
|
|
884
|
-
|
|
886
|
+
// Selectors are ANDed, so an empty list restricts nothing rather than
|
|
887
|
+
// matching nothing — the reading most people reach for first.
|
|
888
|
+
{ key: 'selectors', label: 'Selectors', width: 'w-28', tooltip: 'Devices must satisfy every selector. Zero selectors means this class imposes no selector restriction; claim selectors and other allocation requirements still apply.' },
|
|
885
889
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
886
890
|
],
|
|
887
891
|
resourceslices: [
|
|
888
892
|
{ key: 'name', label: 'Name' },
|
|
889
893
|
{ key: 'driver', label: 'Driver', width: 'w-44' },
|
|
890
|
-
{ key: 'pool', label: 'Pool', width: 'w-36' },
|
|
891
|
-
{ key: 'node', label: 'Node', width: 'w-44' },
|
|
892
|
-
{ key: 'devices', label: 'Devices', width: 'w-24' },
|
|
894
|
+
{ key: 'pool', label: 'Pool', width: 'w-36', tooltip: "Pools are identified by driver and pool name. The scheduler needs a complete set of slices at the pool's highest generation before allocating from it." },
|
|
895
|
+
{ key: 'node', label: 'Node', width: 'w-44', tooltip: "Node providing this slice's devices. A dash means node access is described by a node selector, all-nodes access, or per-device node selection." },
|
|
896
|
+
{ key: 'devices', label: 'Devices', width: 'w-24', tooltip: 'Devices published in this slice, including devices already allocated. This is not a count of free devices or the whole pool.' },
|
|
893
897
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
894
898
|
],
|
|
895
899
|
nvidiaclusterpolicies: [
|
|
@@ -935,7 +939,6 @@ const KNOWN_COLUMNS: Record<string, Column[]> = {
|
|
|
935
939
|
],
|
|
936
940
|
resourceflavors: [
|
|
937
941
|
{ key: 'name', label: 'Name' },
|
|
938
|
-
{ key: 'status', label: 'Status', width: 'w-24' },
|
|
939
942
|
{ key: 'nodeLabels', label: 'Node Labels', width: 'w-28', tooltip: 'Node label selector count' },
|
|
940
943
|
{ key: 'taints', label: 'Taints', width: 'w-24' },
|
|
941
944
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
@@ -985,7 +988,7 @@ const KNOWN_COLUMNS: Record<string, Column[]> = {
|
|
|
985
988
|
{ key: 'namespace', label: 'Namespace', width: 'w-36' },
|
|
986
989
|
{ key: 'status', label: 'Status', width: 'w-24' },
|
|
987
990
|
{ key: 'schedule', label: 'Schedule', width: 'w-32' },
|
|
988
|
-
{ key: '
|
|
991
|
+
{ key: 'timeZone', label: 'Time Zone', width: 'w-36', tooltip: 'IANA zone the schedule is read in; unset follows the local zone of the KubeRay operator pod' },
|
|
989
992
|
{ key: 'lastSchedule', label: 'Last Schedule', width: 'w-28' },
|
|
990
993
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
991
994
|
],
|
|
@@ -1112,7 +1115,6 @@ const KNOWN_COLUMNS: Record<string, Column[]> = {
|
|
|
1112
1115
|
jobtemplates: [
|
|
1113
1116
|
{ key: 'name', label: 'Name' },
|
|
1114
1117
|
{ key: 'namespace', label: 'Namespace', width: 'w-36' },
|
|
1115
|
-
{ key: 'status', label: 'Status', width: 'w-24' },
|
|
1116
1118
|
{ key: 'tasks', label: 'Tasks', width: 'w-20' },
|
|
1117
1119
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
1118
1120
|
],
|
|
@@ -1219,7 +1221,7 @@ const KNOWN_COLUMNS: Record<string, Column[]> = {
|
|
|
1219
1221
|
{ key: 'namespace', label: 'Namespace', width: 'w-36' },
|
|
1220
1222
|
{ key: 'status', label: 'Status', width: 'w-24' },
|
|
1221
1223
|
{ key: 'target', label: 'Job Target', width: 'w-48' },
|
|
1222
|
-
{ key: 'strategy', label: 'Strategy', width: 'w-28' },
|
|
1224
|
+
{ key: 'strategy', label: 'Strategy', width: 'w-28', tooltip: 'Controls how queue length and running or pending Jobs determine how many new Jobs KEDA creates' },
|
|
1223
1225
|
{ key: 'triggerTypes', label: 'Trigger Types', width: 'w-40' },
|
|
1224
1226
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
1225
1227
|
],
|
|
@@ -1241,7 +1243,7 @@ const KNOWN_COLUMNS: Record<string, Column[]> = {
|
|
|
1241
1243
|
servicemonitors: [
|
|
1242
1244
|
{ key: 'name', label: 'Name' },
|
|
1243
1245
|
{ key: 'namespace', label: 'Namespace', width: 'w-36' },
|
|
1244
|
-
{ key: 'status', label: 'Status', width: 'w-
|
|
1246
|
+
{ key: 'status', label: 'Status', width: 'w-36' },
|
|
1245
1247
|
{ key: 'endpoints', label: 'Endpoints', width: 'w-20', tooltip: 'Number of scrape endpoints' },
|
|
1246
1248
|
{ key: 'jobLabel', label: 'Job Label', width: 'w-32' },
|
|
1247
1249
|
{ key: 'selector', label: 'Selector', width: 'w-48' },
|
|
@@ -1250,7 +1252,7 @@ const KNOWN_COLUMNS: Record<string, Column[]> = {
|
|
|
1250
1252
|
prometheusrules: [
|
|
1251
1253
|
{ key: 'name', label: 'Name' },
|
|
1252
1254
|
{ key: 'namespace', label: 'Namespace', width: 'w-36' },
|
|
1253
|
-
{ key: 'status', label: 'Status', width: 'w-
|
|
1255
|
+
{ key: 'status', label: 'Status', width: 'w-36' },
|
|
1254
1256
|
{ key: 'groups', label: 'Groups', width: 'w-20' },
|
|
1255
1257
|
{ key: 'rules', label: 'Rules', width: 'w-20', tooltip: 'Total alert + recording rules' },
|
|
1256
1258
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
@@ -1258,7 +1260,7 @@ const KNOWN_COLUMNS: Record<string, Column[]> = {
|
|
|
1258
1260
|
podmonitors: [
|
|
1259
1261
|
{ key: 'name', label: 'Name' },
|
|
1260
1262
|
{ key: 'namespace', label: 'Namespace', width: 'w-36' },
|
|
1261
|
-
{ key: 'status', label: 'Status', width: 'w-
|
|
1263
|
+
{ key: 'status', label: 'Status', width: 'w-36' },
|
|
1262
1264
|
{ key: 'endpoints', label: 'Endpoints', width: 'w-20', tooltip: 'Number of pod metrics endpoints' },
|
|
1263
1265
|
{ key: 'selector', label: 'Selector', width: 'w-48' },
|
|
1264
1266
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
@@ -1420,8 +1422,8 @@ const KNOWN_COLUMNS: Record<string, Column[]> = {
|
|
|
1420
1422
|
grpcroutes: [
|
|
1421
1423
|
{ key: 'name', label: 'Name' },
|
|
1422
1424
|
{ key: 'namespace', label: 'Namespace', width: 'w-48' },
|
|
1423
|
-
{ key: 'status', label: 'Status', width: 'w-28' },
|
|
1424
|
-
{ key: 'hostnames', label: 'Hostnames', width: 'w-48' },
|
|
1425
|
+
{ 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.' },
|
|
1426
|
+
{ key: 'hostnames', label: 'Hostnames', width: 'w-48', tooltip: "gRPC hostnames this route matches. Any means the route adds no hostname restriction; the Gateway listener and the route's other matching rules still apply." },
|
|
1425
1427
|
{ key: 'parents', label: 'Gateways', width: 'w-36' },
|
|
1426
1428
|
{ key: 'backends', label: 'Backends', width: 'w-48', tooltip: 'Backend services receiving traffic' },
|
|
1427
1429
|
{ key: 'rules', label: 'Rules', width: 'w-20' },
|
|
@@ -1430,7 +1432,7 @@ const KNOWN_COLUMNS: Record<string, Column[]> = {
|
|
|
1430
1432
|
tcproutes: [
|
|
1431
1433
|
{ key: 'name', label: 'Name' },
|
|
1432
1434
|
{ key: 'namespace', label: 'Namespace', width: 'w-48' },
|
|
1433
|
-
{ key: 'status', label: 'Status', width: 'w-28' },
|
|
1435
|
+
{ 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.' },
|
|
1434
1436
|
{ key: 'parents', label: 'Gateways', width: 'w-36' },
|
|
1435
1437
|
{ key: 'backends', label: 'Backends', width: 'w-48', tooltip: 'Backend services receiving traffic' },
|
|
1436
1438
|
{ key: 'rules', label: 'Rules', width: 'w-20' },
|
|
@@ -1439,8 +1441,8 @@ const KNOWN_COLUMNS: Record<string, Column[]> = {
|
|
|
1439
1441
|
tlsroutes: [
|
|
1440
1442
|
{ key: 'name', label: 'Name' },
|
|
1441
1443
|
{ key: 'namespace', label: 'Namespace', width: 'w-48' },
|
|
1442
|
-
{ key: 'status', label: 'Status', width: 'w-28' },
|
|
1443
|
-
{ key: 'hostnames', label: 'Hostnames', width: 'w-48', tooltip: 'SNI
|
|
1444
|
+
{ 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.' },
|
|
1445
|
+
{ key: 'hostnames', label: 'Hostnames', width: 'w-48', tooltip: 'TLS SNI names requested by this route. Any means no route-level hostname restriction; the Gateway listener still applies.' },
|
|
1444
1446
|
{ key: 'parents', label: 'Gateways', width: 'w-36' },
|
|
1445
1447
|
{ key: 'backends', label: 'Backends', width: 'w-48', tooltip: 'Backend services receiving traffic' },
|
|
1446
1448
|
{ key: 'rules', label: 'Rules', width: 'w-20' },
|
|
@@ -1472,14 +1474,13 @@ const KNOWN_COLUMNS: Record<string, Column[]> = {
|
|
|
1472
1474
|
{ key: 'namespace', label: 'Namespace', width: 'w-48' },
|
|
1473
1475
|
{ key: 'policyTypes', label: 'Types', width: 'w-28' },
|
|
1474
1476
|
{ key: 'selector', label: 'Pod Selector', width: 'w-48' },
|
|
1475
|
-
{ key: 'rules', label: 'Rules', width: 'w-24', tooltip: 'Ingress / Egress rule
|
|
1477
|
+
{ key: 'rules', label: 'Rules', width: 'w-24', tooltip: 'Ingress / Egress allow-rule counts. For a direction listed in Types, 0 means this policy allows none; other policies can still allow traffic.' },
|
|
1476
1478
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
1477
1479
|
],
|
|
1478
1480
|
caliconetworkpolicies: [
|
|
1479
1481
|
{ key: 'name', label: 'Name' },
|
|
1480
1482
|
{ key: 'namespace', label: 'Namespace', width: 'w-36' },
|
|
1481
1483
|
{ key: 'selector', label: 'Selector', width: 'w-48' },
|
|
1482
|
-
{ key: 'namespaceSelector', label: 'Namespace Selector', width: 'w-48', defaultVisible: false },
|
|
1483
1484
|
{ key: 'serviceAccountSelector', label: 'Service Account Selector', width: 'w-48', defaultVisible: false },
|
|
1484
1485
|
{ key: 'tier', label: 'Tier', width: 'w-28' },
|
|
1485
1486
|
{ key: 'order', label: 'Order', width: 'w-24' },
|
|
@@ -1502,7 +1503,6 @@ const KNOWN_COLUMNS: Record<string, Column[]> = {
|
|
|
1502
1503
|
{ key: 'name', label: 'Name' },
|
|
1503
1504
|
{ key: 'namespace', label: 'Namespace', width: 'w-36' },
|
|
1504
1505
|
{ key: 'selector', label: 'Selector', width: 'w-48' },
|
|
1505
|
-
{ key: 'namespaceSelector', label: 'Namespace Selector', width: 'w-48', defaultVisible: false },
|
|
1506
1506
|
{ key: 'serviceAccountSelector', label: 'Service Account Selector', width: 'w-48', defaultVisible: false },
|
|
1507
1507
|
{ key: 'tier', label: 'Tier', width: 'w-28' },
|
|
1508
1508
|
{ key: 'order', label: 'Order', width: 'w-24' },
|
|
@@ -1561,7 +1561,7 @@ const KNOWN_COLUMNS: Record<string, Column[]> = {
|
|
|
1561
1561
|
{ key: 'name', label: 'Name' },
|
|
1562
1562
|
{ key: 'namespace', label: 'Namespace', width: 'w-48' },
|
|
1563
1563
|
{ key: 'status', label: 'Status', width: 'w-24' },
|
|
1564
|
-
{ key: 'budget', label: 'Budget', width: 'w-36' },
|
|
1564
|
+
{ key: 'budget', label: 'Budget', width: 'w-36', tooltip: 'min keeps at least this many pods available; max unavail permits at most this many unavailable during voluntary evictions; percentages use the expected pod count' },
|
|
1565
1565
|
{ key: 'healthy', label: 'Healthy', width: 'w-24' },
|
|
1566
1566
|
{ key: 'allowed', label: 'Allowed', width: 'w-24', tooltip: 'Number of disruptions currently allowed' },
|
|
1567
1567
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
@@ -1569,7 +1569,7 @@ const KNOWN_COLUMNS: Record<string, Column[]> = {
|
|
|
1569
1569
|
serviceaccounts: [
|
|
1570
1570
|
{ key: 'name', label: 'Name' },
|
|
1571
1571
|
{ key: 'namespace', label: 'Namespace', width: 'w-48' },
|
|
1572
|
-
{ key: 'automount', label: 'Automount', width: 'w-32', tooltip: '
|
|
1572
|
+
{ key: 'automount', label: 'Automount', width: 'w-32', tooltip: 'Default automatic API-token mounting for pods using this ServiceAccount. A pod can override this setting. Disable it for workloads that do not need Kubernetes API credentials.' },
|
|
1573
1573
|
{ key: 'secrets', label: 'Secrets', width: 'w-24' },
|
|
1574
1574
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
1575
1575
|
],
|
|
@@ -1607,12 +1607,12 @@ const KNOWN_COLUMNS: Record<string, Column[]> = {
|
|
|
1607
1607
|
{ key: 'name', label: 'Name' },
|
|
1608
1608
|
{ key: 'namespace', label: 'Namespace', width: 'w-48' },
|
|
1609
1609
|
{ key: 'holder', label: 'Holder', width: 'w-48' },
|
|
1610
|
-
{ key: 'renewTime', label: 'Last Renewed', width: 'w-32' },
|
|
1610
|
+
{ key: 'renewTime', label: 'Last Renewed', width: 'w-32', tooltip: 'Time since the recorded renewal; red means it exceeds leaseDurationSeconds. Check holder health and API connectivity' },
|
|
1611
1611
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
1612
1612
|
],
|
|
1613
1613
|
priorityclasses: [
|
|
1614
1614
|
{ key: 'name', label: 'Name' },
|
|
1615
|
-
{ key: 'value', label: 'Value', width: 'w-24' },
|
|
1615
|
+
{ key: 'value', label: 'Value', width: 'w-24', tooltip: 'Higher values receive scheduling preference; Pods can still be preempted by Pods with higher priority' },
|
|
1616
1616
|
{ key: 'globalDefault', label: 'Global Default', width: 'w-36' },
|
|
1617
1617
|
{ key: 'preemptionPolicy', label: 'Preemption', width: 'w-32' },
|
|
1618
1618
|
{ key: 'description', label: 'Description', width: 'w-64' },
|
|
@@ -1626,7 +1626,7 @@ const KNOWN_COLUMNS: Record<string, Column[]> = {
|
|
|
1626
1626
|
mutatingwebhookconfigurations: [
|
|
1627
1627
|
{ key: 'name', label: 'Name' },
|
|
1628
1628
|
{ key: 'webhooks', label: 'Webhooks', width: 'w-28' },
|
|
1629
|
-
{ key: 'failurePolicy', label: 'Failure Policy', width: 'w-36' },
|
|
1629
|
+
{ key: 'failurePolicy', label: 'Failure Policy', width: 'w-36', tooltip: 'If a matching webhook call errors or times out, Fail rejects the API request; Ignore continues without that webhook. This describes failure handling, not current webhook health.' },
|
|
1630
1630
|
{ key: 'target', label: 'Target', width: 'w-48' },
|
|
1631
1631
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
1632
1632
|
],
|
|
@@ -1833,8 +1833,7 @@ const KNOWN_COLUMNS: Record<string, Column[]> = {
|
|
|
1833
1833
|
{ key: 'namespace', label: 'Namespace', width: 'w-36' },
|
|
1834
1834
|
{ key: 'status', label: 'Status', width: 'w-24' },
|
|
1835
1835
|
{ key: 'store', label: 'Store', width: 'w-36' },
|
|
1836
|
-
{ key: '
|
|
1837
|
-
{ key: 'refreshInterval', label: 'Refresh', width: 'w-24' },
|
|
1836
|
+
{ key: 'refreshInterval', label: 'Refresh', width: 'w-24', tooltip: 'How often the value is re-pulled from the provider. A zero interval means it is fetched once and not updated afterward.' },
|
|
1838
1837
|
{ key: 'lastSync', label: 'Last Sync', width: 'w-28' },
|
|
1839
1838
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
1840
1839
|
],
|
|
@@ -2090,16 +2089,16 @@ const KNOWN_COLUMNS: Record<string, Column[]> = {
|
|
|
2090
2089
|
destinationrules: [
|
|
2091
2090
|
{ key: 'name', label: 'Name' },
|
|
2092
2091
|
{ key: 'namespace', label: 'Namespace', width: 'w-36' },
|
|
2093
|
-
{ key: 'status', label: 'Status', width: 'w-24' },
|
|
2094
|
-
{ key: 'host', label: 'Host', width: 'w-48' },
|
|
2095
|
-
{ key: 'subsets', label: 'Subsets', width: 'w-20' },
|
|
2096
|
-
{ key: '
|
|
2092
|
+
{ key: 'status', label: 'Status', width: 'w-24', defaultVisible: false },
|
|
2093
|
+
{ key: 'host', label: 'Host', width: 'w-48', tooltip: "The service this rule's traffic policy applies to" },
|
|
2094
|
+
{ key: 'subsets', label: 'Subsets', width: 'w-20', tooltip: 'Named backend groups that routes can select, often for canary releases. Defining subsets does not send traffic to them.' },
|
|
2095
|
+
{ key: 'tlsMode', label: 'Client TLS', width: 'w-28', tooltip: 'Declared client-side TLS mode for this host at the rule level. Subset and port policies can override it; this does not establish the effective mTLS posture.' },
|
|
2096
|
+
{ key: 'loadBalancer', label: 'LB Policy', width: 'w-28', tooltip: "Load balancing algorithm declared by this rule. '-' means none is set, so Istio applies its mesh default (LEAST_REQUEST since 1.14, overridable per mesh). Subset and port policies can differ." },
|
|
2097
2097
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
2098
2098
|
],
|
|
2099
2099
|
serviceentries: [
|
|
2100
2100
|
{ key: 'name', label: 'Name' },
|
|
2101
2101
|
{ key: 'namespace', label: 'Namespace', width: 'w-36' },
|
|
2102
|
-
{ key: 'status', label: 'Status', width: 'w-24' },
|
|
2103
2102
|
{ key: 'hosts', label: 'Hosts', width: 'w-48' },
|
|
2104
2103
|
{ key: 'location', label: 'Location', width: 'w-28' },
|
|
2105
2104
|
{ key: 'ports', label: 'Ports', width: 'w-32' },
|
|
@@ -2108,7 +2107,6 @@ const KNOWN_COLUMNS: Record<string, Column[]> = {
|
|
|
2108
2107
|
peerauthentications: [
|
|
2109
2108
|
{ key: 'name', label: 'Name' },
|
|
2110
2109
|
{ key: 'namespace', label: 'Namespace', width: 'w-36' },
|
|
2111
|
-
{ key: 'status', label: 'Status', width: 'w-24' },
|
|
2112
2110
|
{ key: 'mode', label: 'mTLS Mode', width: 'w-28' },
|
|
2113
2111
|
{ key: 'selector', label: 'Selector', width: 'w-48' },
|
|
2114
2112
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
@@ -2116,10 +2114,10 @@ const KNOWN_COLUMNS: Record<string, Column[]> = {
|
|
|
2116
2114
|
authorizationpolicies: [
|
|
2117
2115
|
{ key: 'name', label: 'Name' },
|
|
2118
2116
|
{ key: 'namespace', label: 'Namespace', width: 'w-36' },
|
|
2119
|
-
{ key: 'status', label: 'Status', width: 'w-24' },
|
|
2120
|
-
{ key: 'action', label: 'Action', width: 'w-24' },
|
|
2121
|
-
{ key: 'rules', label: 'Rules', width: 'w-20' },
|
|
2122
|
-
{ key: 'selector', label: '
|
|
2117
|
+
{ key: 'status', label: 'Status', width: 'w-24', defaultVisible: false },
|
|
2118
|
+
{ key: 'action', label: 'Action', width: 'w-24', tooltip: 'ALLOW contributes permitted matches; DENY blocks matches; AUDIT marks matches for a configured audit plugin; CUSTOM adds an external check. Other policies also affect the decision.' },
|
|
2119
|
+
{ key: 'rules', label: 'Rules', width: 'w-20', tooltip: 'Rules are alternatives — zero rules match nothing, an empty rule matches everything. An ALLOW policy with zero rules permits nothing on its own.' },
|
|
2120
|
+
{ key: 'selector', label: 'Applies to', width: 'w-48', tooltip: 'Declared workload labels or resource attachments. With neither, scope is inherited — the namespace, or the whole mesh when the policy sits in the mesh root namespace.' },
|
|
2123
2121
|
{ key: 'age', label: 'Age', width: 'w-24' },
|
|
2124
2122
|
],
|
|
2125
2123
|
// Knative Serving
|
|
@@ -3138,6 +3136,8 @@ export function mergeSavedVisibleColumns(
|
|
|
3138
3136
|
savedVisible: string[],
|
|
3139
3137
|
extraKeys: string[],
|
|
3140
3138
|
printerColumns: ExtraColumn[],
|
|
3139
|
+
effective?: Column[],
|
|
3140
|
+
known?: string[],
|
|
3141
3141
|
): Set<string> {
|
|
3142
3142
|
const merged = new Set(savedVisible)
|
|
3143
3143
|
for (const k of extraKeys) merged.add(k)
|
|
@@ -3146,6 +3146,24 @@ export function mergeSavedVisibleColumns(
|
|
|
3146
3146
|
if (c.defaultVisible !== false) merged.add(c.key)
|
|
3147
3147
|
}
|
|
3148
3148
|
}
|
|
3149
|
+
// Curated columns the blob predates. Same principle as the two above, but it
|
|
3150
|
+
// needs a record of what the user was actually offered: the blob stores only
|
|
3151
|
+
// what is visible, so a curated key's absence is ambiguous between "hidden on
|
|
3152
|
+
// purpose" and "not offered yet". `known` is that record. Without one the two
|
|
3153
|
+
// cases are indistinguishable, so nothing is inferred and visibility is left
|
|
3154
|
+
// to the saved set alone.
|
|
3155
|
+
//
|
|
3156
|
+
// Printer columns are excluded even when `known` lists none of them. They are
|
|
3157
|
+
// fetched, so `known` can be written during the window before the printer
|
|
3158
|
+
// table arrives; treating their absence as "new" would re-show ones the user
|
|
3159
|
+
// hid. Their own rule above already covers them.
|
|
3160
|
+
if (effective && Array.isArray(known)) {
|
|
3161
|
+
const seen = new Set(known)
|
|
3162
|
+
for (const c of effective) {
|
|
3163
|
+
if (c.key.startsWith(PRINTER_COLUMN_PREFIX)) continue
|
|
3164
|
+
if (!seen.has(c.key) && c.defaultVisible !== false) merged.add(c.key)
|
|
3165
|
+
}
|
|
3166
|
+
}
|
|
3149
3167
|
return merged
|
|
3150
3168
|
}
|
|
3151
3169
|
|
|
@@ -3201,6 +3219,10 @@ interface ColumnSettings {
|
|
|
3201
3219
|
visible: string[]
|
|
3202
3220
|
widths: Record<string, number>
|
|
3203
3221
|
custom?: CustomColumnDef[]
|
|
3222
|
+
/** Every column key the user was offered when this was written. Lets a later
|
|
3223
|
+
* load tell a column they hid from one that did not exist yet. Absent on
|
|
3224
|
+
* blobs written before this field. */
|
|
3225
|
+
known?: string[]
|
|
3204
3226
|
}
|
|
3205
3227
|
|
|
3206
3228
|
function loadColumnSettings(kind: string, group?: string): ColumnSettings | null {
|
|
@@ -3778,6 +3800,12 @@ export function ResourcesView({
|
|
|
3778
3800
|
return [...filteredExtras, ...kindColumns, ...builtCustomColumns]
|
|
3779
3801
|
}, [selectedKind.name, selectedKind.group, extraLeadingColumns, builtCustomColumns, builtPrinterColumns])
|
|
3780
3802
|
|
|
3803
|
+
// The offered key set, and a stable signature for it. allColumns is a new
|
|
3804
|
+
// identity on every printer-column refetch; depending on the array itself
|
|
3805
|
+
// would rewrite the settings blob on each poll.
|
|
3806
|
+
const allColumnKeys = useMemo(() => allColumns.map(c => c.key), [allColumns])
|
|
3807
|
+
const allColumnKeysSig = allColumnKeys.join('\u0000')
|
|
3808
|
+
|
|
3781
3809
|
// Map of extra column keys for fast O(1) lookup on each render path
|
|
3782
3810
|
// (cell render, sort, column-filter unique-values).
|
|
3783
3811
|
// Recompute header-derived widths once the web font swaps in (see watchHeaderFont).
|
|
@@ -3794,6 +3822,13 @@ export function ResourcesView({
|
|
|
3794
3822
|
// Guards the save effect from persisting on the initial load of each kind
|
|
3795
3823
|
// (set false by the load effect, flipped true on its first skipped save).
|
|
3796
3824
|
const isColumnSettingsLoaded = useRef(false)
|
|
3825
|
+
// Every column key this kind has ever offered, accumulated. A single save
|
|
3826
|
+
// cannot stand in for it: the offered set shrinks as well as grows — an
|
|
3827
|
+
// uncurated kind's generic Status disappears once its printer columns arrive
|
|
3828
|
+
// — and a shrunk snapshot would forget that a column absent from `visible`
|
|
3829
|
+
// was one the user hid rather than one never shown.
|
|
3830
|
+
const knownColumnKeys = useRef<Set<string>>(new Set())
|
|
3831
|
+
const knownColumnKeysKind = useRef('')
|
|
3797
3832
|
// Whether the user has a persisted column blob for this kind — data-driven
|
|
3798
3833
|
// column defaults (GPU auto-show) must never override explicit user choices.
|
|
3799
3834
|
const hadSavedColumnSettings = useRef(false)
|
|
@@ -3819,6 +3854,15 @@ export function ResourcesView({
|
|
|
3819
3854
|
// (non-array, blank path, bad source) must not crash the later .map or add dead columns.
|
|
3820
3855
|
const savedCustom = sanitizeCustomColumnDefs(saved?.custom)
|
|
3821
3856
|
setCustomColumns(savedCustom)
|
|
3857
|
+
// Reset on a kind change only. This effect also re-runs when the printer
|
|
3858
|
+
// columns arrive, and that is the moment the offered set shrinks — clearing
|
|
3859
|
+
// here would drop the evidence for the column they replaced just before the
|
|
3860
|
+
// next save persists the record without it.
|
|
3861
|
+
const knownKeysIdentity = selectedKindIdentityOf(selectedKind)
|
|
3862
|
+
if (knownColumnKeysKind.current !== knownKeysIdentity) {
|
|
3863
|
+
knownColumnKeys.current = new Set()
|
|
3864
|
+
knownColumnKeysKind.current = knownKeysIdentity
|
|
3865
|
+
}
|
|
3822
3866
|
const kindColumns = columnsForKindWithPrinter(selectedKind.name, selectedKind.group, builtPrinterColumns)
|
|
3823
3867
|
const builtinKeys = new Set(kindColumns.map(c => c.key))
|
|
3824
3868
|
const extras = filterHostExtras(extraLeadingColumns, builtinKeys, selectedKind.name)
|
|
@@ -3835,18 +3879,32 @@ export function ResourcesView({
|
|
|
3835
3879
|
// discard the stale save and use the specialized columns instead. User-added
|
|
3836
3880
|
// custom columns mean the blob isn't a stale pure-defaults save — keep it, or
|
|
3837
3881
|
// the migration would clear them from storage (and un-hide hidden ones).
|
|
3882
|
+
// Persisted JSON: only an array of keys is usable, anything else reads as
|
|
3883
|
+
// no record at all.
|
|
3884
|
+
const savedKnown = Array.isArray(saved.known)
|
|
3885
|
+
? saved.known.filter((k): k is string => typeof k === 'string')
|
|
3886
|
+
: undefined
|
|
3887
|
+
for (const k of savedKnown ?? []) knownColumnKeys.current.add(k)
|
|
3888
|
+
|
|
3838
3889
|
const defaultKeys = DEFAULT_COLUMNS.map(c => c.key)
|
|
3890
|
+
// A blob carrying an offered-key record is not a pre-curation leftover:
|
|
3891
|
+
// its visible set matching the generic defaults means the user hid their
|
|
3892
|
+
// way down to them, and clearing it would undo exactly that.
|
|
3839
3893
|
const isStaleDefaults = kindColumns !== DEFAULT_COLUMNS &&
|
|
3894
|
+
!savedKnown &&
|
|
3840
3895
|
!savedCustom.length &&
|
|
3841
3896
|
saved.visible.length === defaultKeys.length &&
|
|
3842
3897
|
saved.visible.every(v => defaultKeys.includes(v))
|
|
3843
3898
|
if (isStaleDefaults) {
|
|
3844
3899
|
clearColumnSettings(selectedKind.name, selectedKind.group)
|
|
3900
|
+
knownColumnKeys.current = new Set()
|
|
3845
3901
|
hadSavedColumnSettings.current = false
|
|
3846
3902
|
setVisibleColumns(getDefaultVisibleColumns(effective))
|
|
3847
3903
|
setColumnWidths({})
|
|
3848
3904
|
} else {
|
|
3849
|
-
setVisibleColumns(mergeSavedVisibleColumns(
|
|
3905
|
+
setVisibleColumns(mergeSavedVisibleColumns(
|
|
3906
|
+
saved.visible, extraKeys, builtPrinterColumns, effective, savedKnown,
|
|
3907
|
+
))
|
|
3850
3908
|
setColumnWidths(saved.widths || {})
|
|
3851
3909
|
}
|
|
3852
3910
|
} else {
|
|
@@ -3862,6 +3920,7 @@ export function ResourcesView({
|
|
|
3862
3920
|
// Save column settings when they change (skip the initial load of each kind)
|
|
3863
3921
|
useEffect(() => {
|
|
3864
3922
|
if (visibleColumns.size === 0) return // not loaded yet
|
|
3923
|
+
for (const k of allColumnKeys) knownColumnKeys.current.add(k)
|
|
3865
3924
|
if (!isColumnSettingsLoaded.current) {
|
|
3866
3925
|
isColumnSettingsLoaded.current = true
|
|
3867
3926
|
return
|
|
@@ -3870,11 +3929,13 @@ export function ResourcesView({
|
|
|
3870
3929
|
visible: Array.from(visibleColumns),
|
|
3871
3930
|
widths: columnWidths,
|
|
3872
3931
|
custom: customColumns,
|
|
3932
|
+
known: Array.from(knownColumnKeys.current),
|
|
3873
3933
|
})
|
|
3874
3934
|
// A persisted blob blocks GPU auto-show from here on — same rule in-session
|
|
3875
3935
|
// as across sessions, so a later data refresh can't override user choices.
|
|
3876
3936
|
hadSavedColumnSettings.current = true
|
|
3877
|
-
|
|
3937
|
+
// allColumnKeysSig, not allColumnKeys: see the memo above.
|
|
3938
|
+
}, [visibleColumns, columnWidths, customColumns, selectedKind.name, selectedKind.group, allColumnKeysSig])
|
|
3878
3939
|
|
|
3879
3940
|
// Close column picker on outside click or Escape
|
|
3880
3941
|
useEffect(() => {
|
|
@@ -3977,6 +4038,7 @@ export function ResourcesView({
|
|
|
3977
4038
|
// Reset column settings to defaults (also drops user-added custom columns)
|
|
3978
4039
|
const resetColumnSettings = useCallback(() => {
|
|
3979
4040
|
clearColumnSettings(selectedKind.name, selectedKind.group)
|
|
4041
|
+
knownColumnKeys.current = new Set()
|
|
3980
4042
|
setCustomColumns([])
|
|
3981
4043
|
const customKeys = new Set(builtCustomColumns.map(c => c.key))
|
|
3982
4044
|
setVisibleColumns(getDefaultVisibleColumns(allColumns.filter(c => !customKeys.has(c.key))))
|
|
@@ -8899,11 +8961,11 @@ function RouteCell({ resource, column }: { resource: any; column: string }) {
|
|
|
8899
8961
|
switch (column) {
|
|
8900
8962
|
case 'status': {
|
|
8901
8963
|
const status = getRouteStatus(resource)
|
|
8902
|
-
|
|
8903
|
-
|
|
8904
|
-
|
|
8905
|
-
|
|
8906
|
-
|
|
8964
|
+
const reason = getRouteStatusReason(resource)
|
|
8965
|
+
const badge = <span className={clsx('badge', status.color)}>{status.text}</span>
|
|
8966
|
+
// "Degraded" on its own says something is wrong and not what. The
|
|
8967
|
+
// controller's own reason is usually "the backend you named is missing".
|
|
8968
|
+
return reason ? <Tooltip content={reason}>{badge}</Tooltip> : badge
|
|
8907
8969
|
}
|
|
8908
8970
|
case 'hostnames': {
|
|
8909
8971
|
const hostnames = getRouteHostnames(resource)
|