@rancher/shell 3.0.12-rc.2 → 3.0.12-rc.3

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.
Files changed (272) hide show
  1. package/apis/impl/apis.ts +6 -0
  2. package/apis/index.ts +26 -0
  3. package/apis/intf/resources-api/cluster-api.ts +18 -0
  4. package/apis/intf/resources-api/mgmt-api.ts +15 -0
  5. package/apis/intf/resources-api/resource-base.ts +107 -0
  6. package/apis/intf/resources-api/resource-constants.ts +147 -0
  7. package/apis/intf/resources-api/resources-api.ts +143 -0
  8. package/apis/intf/resources.ts +49 -0
  9. package/apis/intf/{modal.ts → shell-api/modal.ts} +21 -26
  10. package/apis/intf/shell-api/proxy.ts +216 -0
  11. package/apis/intf/{slide-in.ts → shell-api/slide-in.ts} +4 -3
  12. package/apis/intf/{system.ts → shell-api/system.ts} +4 -1
  13. package/apis/intf/shell.ts +12 -6
  14. package/apis/resources/__tests__/resources-api-class.test.ts +550 -0
  15. package/apis/resources/index.ts +22 -0
  16. package/apis/resources/resources-api-class.ts +187 -0
  17. package/apis/shell/__tests__/proxy.test.ts +369 -0
  18. package/apis/shell/index.ts +8 -1
  19. package/apis/shell/modal.ts +4 -1
  20. package/apis/shell/notifications.ts +9 -6
  21. package/apis/shell/proxy.ts +256 -0
  22. package/apis/shell/slide-in.ts +4 -1
  23. package/apis/vue-shim.d.ts +2 -1
  24. package/assets/data/aws-regions.json +4 -0
  25. package/assets/fonts/lato/LatoLatin-Black.woff +0 -0
  26. package/assets/fonts/lato/LatoLatin-Black.woff2 +0 -0
  27. package/assets/fonts/lato/LatoLatin-BlackItalic.woff +0 -0
  28. package/assets/fonts/lato/LatoLatin-BlackItalic.woff2 +0 -0
  29. package/assets/fonts/lato/LatoLatin-Bold.woff +0 -0
  30. package/assets/fonts/lato/LatoLatin-Bold.woff2 +0 -0
  31. package/assets/fonts/lato/LatoLatin-BoldItalic.woff +0 -0
  32. package/assets/fonts/lato/LatoLatin-BoldItalic.woff2 +0 -0
  33. package/assets/fonts/lato/LatoLatin-Heavy.woff +0 -0
  34. package/assets/fonts/lato/LatoLatin-Heavy.woff2 +0 -0
  35. package/assets/fonts/lato/LatoLatin-HeavyItalic.woff +0 -0
  36. package/assets/fonts/lato/LatoLatin-HeavyItalic.woff2 +0 -0
  37. package/assets/fonts/lato/LatoLatin-Italic.woff +0 -0
  38. package/assets/fonts/lato/LatoLatin-Italic.woff2 +0 -0
  39. package/assets/fonts/lato/LatoLatin-Light.woff +0 -0
  40. package/assets/fonts/lato/LatoLatin-Light.woff2 +0 -0
  41. package/assets/fonts/lato/LatoLatin-LightItalic.woff +0 -0
  42. package/assets/fonts/lato/LatoLatin-LightItalic.woff2 +0 -0
  43. package/assets/fonts/lato/LatoLatin-Medium.woff +0 -0
  44. package/assets/fonts/lato/LatoLatin-Medium.woff2 +0 -0
  45. package/assets/fonts/lato/LatoLatin-MediumItalic.woff +0 -0
  46. package/assets/fonts/lato/LatoLatin-MediumItalic.woff2 +0 -0
  47. package/assets/fonts/lato/LatoLatin-Regular.woff +0 -0
  48. package/assets/fonts/lato/LatoLatin-Regular.woff2 +0 -0
  49. package/assets/fonts/lato/LatoLatin-Semibold.woff +0 -0
  50. package/assets/fonts/lato/LatoLatin-Semibold.woff2 +0 -0
  51. package/assets/fonts/lato/LatoLatin-SemiboldItalic.woff +0 -0
  52. package/assets/fonts/lato/LatoLatin-SemiboldItalic.woff2 +0 -0
  53. package/assets/styles/base/_variables.scss +2 -0
  54. package/assets/styles/fonts/_fontstack.scss +132 -8
  55. package/assets/translations/en-us.yaml +22 -5
  56. package/chart/monitoring/index.vue +10 -1
  57. package/components/ActionDropdownShell.vue +2 -1
  58. package/components/CruResourceFooter.vue +9 -5
  59. package/components/ExplorerProjectsNamespaces.vue +1 -1
  60. package/components/InstallHelmCharts.vue +2 -2
  61. package/components/LandingPagePreference.vue +14 -5
  62. package/components/Resource/Detail/Metadata/IdentifyingInformation/index.vue +15 -1
  63. package/components/Resource/Detail/Metadata/index.vue +6 -0
  64. package/components/Resource/Detail/ResourcePopover/index.vue +12 -1
  65. package/components/Resource/Detail/SpacedRow.vue +3 -1
  66. package/components/Resource/Detail/TitleBar/index.vue +10 -11
  67. package/components/ResourceList/Masthead.vue +12 -8
  68. package/components/SelectIconGrid.vue +0 -10
  69. package/components/SingleClusterInfo.vue +1 -0
  70. package/components/SortableTable/__tests__/sorting.test.ts +126 -0
  71. package/components/SortableTable/index.vue +6 -9
  72. package/components/SortableTable/selection.js +23 -5
  73. package/components/SortableTable/sorting.js +6 -3
  74. package/components/Wizard.vue +14 -13
  75. package/components/fleet/FleetBundles.vue +100 -12
  76. package/components/fleet/FleetClusterTargets/index.vue +37 -15
  77. package/components/fleet/__tests__/FleetClusterTargets.test.ts +149 -115
  78. package/components/fleet/__tests__/FleetClusters.test.ts +12 -12
  79. package/components/form/LabeledSelect.vue +20 -3
  80. package/components/form/NameNsDescription.vue +11 -0
  81. package/components/form/Security.vue +6 -2
  82. package/components/form/WorkloadPorts.vue +2 -7
  83. package/components/form/__tests__/Security.test.ts +76 -0
  84. package/components/formatter/Autoscaler.vue +4 -4
  85. package/components/formatter/ClusterKubeVersion.vue +27 -0
  86. package/components/formatter/ClusterLink.vue +1 -7
  87. package/components/formatter/ClusterProvider.vue +6 -10
  88. package/components/formatter/FleetSummaryGraph.vue +0 -3
  89. package/components/formatter/MachineSummaryGraph.vue +1 -1
  90. package/components/formatter/PodsUsage.vue +2 -2
  91. package/components/formatter/__tests__/Autoscaler.test.ts +19 -22
  92. package/components/formatter/__tests__/FleetSummaryGraph.test.ts +216 -0
  93. package/components/formatter/__tests__/PodsUsage.test.ts +6 -10
  94. package/components/nav/NamespaceFilter.vue +2 -2
  95. package/components/nav/TopLevelMenu.helper.ts +15 -3
  96. package/components/nav/TopLevelMenu.vue +16 -5
  97. package/components/nav/__tests__/TopLevelMenu.test.ts +145 -21
  98. package/components/templates/home.vue +18 -0
  99. package/components/templates/plain.vue +18 -0
  100. package/components/templates/standalone.vue +17 -0
  101. package/composables/useFormValidation.ts +93 -0
  102. package/composables/useVeeValidateField.test.ts +159 -0
  103. package/composables/useVeeValidateField.ts +67 -0
  104. package/config/pagination-table-headers.js +18 -1
  105. package/config/product/manager.js +82 -21
  106. package/config/router/routes.js +6 -0
  107. package/config/table-headers.js +20 -1
  108. package/config/types.js +2 -1
  109. package/core/__tests__/plugin-products.test.ts +904 -20
  110. package/core/plugin-products-base.ts +107 -7
  111. package/core/plugin-products.ts +4 -0
  112. package/core/plugin-types.ts +111 -1
  113. package/core/plugin.ts +15 -7
  114. package/core/productDebugger.js +9 -4
  115. package/core/types-provisioning.ts +43 -30
  116. package/core/types.ts +57 -20
  117. package/detail/__tests__/pod.test.ts +41 -0
  118. package/detail/harvesterhci.io.management.cluster.vue +6 -2
  119. package/detail/pod.vue +1 -1
  120. package/detail/provisioning.cattle.io.cluster.vue +4 -10
  121. package/edit/auth/__tests__/azuread.test.ts +217 -34
  122. package/edit/auth/azuread.vue +122 -14
  123. package/edit/auth/oidc.vue +2 -2
  124. package/edit/networking.k8s.io.ingress/DefaultBackend.vue +13 -4
  125. package/edit/networking.k8s.io.ingress/RulePath.vue +8 -4
  126. package/edit/networking.k8s.io.ingress/index.vue +75 -20
  127. package/edit/provisioning.cattle.io.cluster/__tests__/MachinePool.test.ts +104 -0
  128. package/edit/provisioning.cattle.io.cluster/index.vue +11 -7
  129. package/edit/provisioning.cattle.io.cluster/rke2.vue +8 -4
  130. package/edit/provisioning.cattle.io.cluster/tabs/MachinePool.vue +11 -0
  131. package/edit/provisioning.cattle.io.cluster/tabs/registries/RegistryConfigs.vue +37 -4
  132. package/edit/provisioning.cattle.io.cluster/tabs/registries/__tests__/RegistryConfigs.test.ts +132 -7
  133. package/edit/provisioning.cattle.io.cluster/tabs/registries/index.vue +2 -1
  134. package/edit/secret/__tests__/ssh.test.ts +5 -6
  135. package/edit/secret/basic.vue +31 -0
  136. package/edit/secret/index.vue +68 -17
  137. package/edit/secret/registry.vue +38 -0
  138. package/edit/secret/ssh.vue +29 -0
  139. package/edit/secret/tls.vue +30 -0
  140. package/edit/service.vue +4 -4
  141. package/edit/workload/Upgrading.vue +3 -3
  142. package/edit/workload/__tests__/Upgrading.test.ts +6 -9
  143. package/edit/workload/mixins/workload.js +2 -1
  144. package/list/fleet.cattle.io.bundle.vue +7 -104
  145. package/list/fleet.cattle.io.clusterregistrationtoken.vue +20 -0
  146. package/list/provisioning.cattle.io.cluster.vue +262 -180
  147. package/list/utils/management.cattle.io.cluster.utils.ts +128 -0
  148. package/mixins/__tests__/chart.test.ts +112 -0
  149. package/mixins/brand.js +2 -1
  150. package/mixins/chart.js +12 -8
  151. package/mixins/resource-fetch-api-pagination.js +41 -5
  152. package/models/__tests__/ext.cattle.io.kubeconfig.test.ts +67 -67
  153. package/models/__tests__/management.cattle.io.cluster.test.ts +1 -1
  154. package/models/__tests__/management.cattle.io.node.ts +6 -5
  155. package/models/__tests__/management.cattle.io.nodepool.ts +5 -4
  156. package/models/__tests__/provisioning.cattle.io.cluster.test.ts +32 -11
  157. package/models/base-cluster.x-k8s.io.js +26 -0
  158. package/models/cluster.js +1 -1
  159. package/models/cluster.x-k8s.io.machine.js +4 -22
  160. package/models/cluster.x-k8s.io.machinedeployment.js +2 -20
  161. package/models/cluster.x-k8s.io.machineset.js +2 -20
  162. package/models/compliance.cattle.io.clusterscan.js +130 -2
  163. package/models/ext.cattle.io.kubeconfig.ts +4 -7
  164. package/models/fleet-application.js +3 -1
  165. package/models/management.cattle.io.cluster.js +417 -40
  166. package/models/management.cattle.io.node.js +6 -4
  167. package/models/management.cattle.io.nodepool.js +1 -1
  168. package/models/networking.k8s.io.ingress.js +12 -4
  169. package/models/provisioning.cattle.io.cluster.js +47 -330
  170. package/models/rke.cattle.io.etcdsnapshot.js +1 -2
  171. package/package.json +11 -29
  172. package/pages/__tests__/readme.test.ts +49 -0
  173. package/pages/auth/setup.vue +2 -3
  174. package/pages/c/_cluster/apps/charts/__tests__/chart.test.ts +76 -0
  175. package/pages/c/_cluster/apps/charts/chart.vue +60 -8
  176. package/pages/c/_cluster/apps/charts/install.vue +10 -7
  177. package/pages/c/_cluster/explorer/__tests__/index.test.ts +23 -25
  178. package/pages/c/_cluster/explorer/index.vue +5 -49
  179. package/pages/c/_cluster/istio/__tests__/istio.index.test.ts +194 -0
  180. package/pages/c/_cluster/istio/index.vue +21 -6
  181. package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +1 -0
  182. package/pages/c/_cluster/uiplugins/__tests__/index.test.ts +719 -2
  183. package/pages/c/_cluster/uiplugins/index.vue +203 -197
  184. package/pages/diagnostic.vue +13 -17
  185. package/pages/fail-whale.vue +18 -0
  186. package/pages/home.vue +77 -260
  187. package/pages/readme.vue +88 -0
  188. package/plugins/dashboard-store/__tests__/resource-class.test.ts +88 -0
  189. package/plugins/dashboard-store/actions.js +40 -18
  190. package/plugins/dashboard-store/resource-class.js +5 -2
  191. package/plugins/steve/__tests__/subscribe.spec.ts +6 -3
  192. package/plugins/steve/steve-pagination-utils.ts +11 -3
  193. package/plugins/steve/subscribe.js +35 -5
  194. package/rancher-components/Form/LabeledInput/LabeledInput.test.ts +10 -4
  195. package/rancher-components/Form/LabeledInput/LabeledInput.vue +7 -52
  196. package/rancher-components/RcButton/RcButton.test.ts +37 -1
  197. package/rancher-components/RcButton/RcButton.vue +38 -8
  198. package/rancher-components/RcDropdown/RcDropdownTrigger.vue +10 -8
  199. package/store/__tests__/catalog.test.ts +115 -1
  200. package/store/__tests__/type-map.test.ts +556 -1
  201. package/store/action-menu.js +8 -3
  202. package/store/auth.js +1 -1
  203. package/store/aws.js +27 -16
  204. package/store/catalog.js +27 -3
  205. package/store/digitalocean.js +20 -38
  206. package/store/index.js +2 -0
  207. package/store/linode.js +25 -40
  208. package/store/pnap.js +1 -0
  209. package/store/type-map.js +111 -29
  210. package/tsconfig.paths.json +8 -8
  211. package/types/kube/kube-api.ts +14 -1
  212. package/types/rancher/steve.api.ts +12 -12
  213. package/types/resources/settings.d.ts +2 -1
  214. package/types/shell/index.d.ts +102 -2
  215. package/types/store/dashboard-store.types.ts +108 -11
  216. package/types/store/pagination.types.ts +6 -3
  217. package/utils/__tests__/alertmanagerconfig.test.ts +117 -0
  218. package/utils/__tests__/async.test.ts +87 -0
  219. package/utils/__tests__/aws.test.ts +140 -0
  220. package/utils/__tests__/banners.test.ts +176 -0
  221. package/utils/__tests__/chart.test.ts +64 -1
  222. package/utils/__tests__/color.test.ts +226 -0
  223. package/utils/__tests__/duration.test.ts +140 -0
  224. package/utils/__tests__/fleet.test.ts +340 -0
  225. package/utils/__tests__/ingress.test.ts +553 -0
  226. package/utils/__tests__/kube.test.ts +68 -0
  227. package/utils/__tests__/namespace-filter.test.ts +109 -0
  228. package/utils/__tests__/pagination-utils.test.ts +361 -0
  229. package/utils/__tests__/parse-externalid.test.ts +137 -0
  230. package/utils/__tests__/perf-setting.utils.test.ts +98 -0
  231. package/utils/__tests__/poller-sequential.test.ts +177 -0
  232. package/utils/__tests__/poller.test.ts +170 -0
  233. package/utils/__tests__/promise.test.ts +346 -0
  234. package/utils/__tests__/settings.test.ts +140 -0
  235. package/utils/__tests__/sort-utils.test.ts +301 -0
  236. package/utils/__tests__/string-utils.test.ts +798 -0
  237. package/utils/__tests__/string.test.ts +23 -1
  238. package/utils/__tests__/style.test.ts +154 -0
  239. package/utils/__tests__/svg-filter.test.ts +184 -0
  240. package/utils/__tests__/units.test.ts +417 -0
  241. package/utils/__tests__/versions.test.ts +128 -0
  242. package/utils/__tests__/xccdf.test.ts +391 -0
  243. package/utils/chart.js +36 -0
  244. package/utils/fleet.ts +13 -3
  245. package/utils/gatekeeper/__tests__/util.test.ts +174 -0
  246. package/utils/gc/__tests__/gc-interval.test.ts +119 -0
  247. package/utils/gc/__tests__/gc-root-store.test.ts +225 -0
  248. package/utils/gc/__tests__/gc-route-changed.test.ts +96 -0
  249. package/utils/gc/__tests__/gc.test.ts +487 -0
  250. package/utils/ingress.ts +9 -1
  251. package/utils/pagination-utils.ts +2 -1
  252. package/utils/string.js +25 -2
  253. package/utils/uiplugins.ts +5 -5
  254. package/utils/validators/__tests__/cluster-name.test.ts +110 -0
  255. package/utils/validators/__tests__/cron-schedule.test.ts +79 -0
  256. package/utils/validators/__tests__/index.test.ts +481 -0
  257. package/utils/validators/__tests__/kubernetes-name.test.ts +163 -0
  258. package/utils/validators/__tests__/misc-validators.test.ts +246 -0
  259. package/utils/validators/__tests__/pod-affinity.test.ts +382 -0
  260. package/utils/validators/__tests__/prometheusrule.test.ts +211 -0
  261. package/utils/validators/__tests__/role-template.test.ts +149 -0
  262. package/utils/validators/__tests__/service.test.ts +283 -0
  263. package/utils/validators/__tests__/setting.test.js +32 -0
  264. package/utils/validators/formRules/__tests__/index.test.ts +50 -0
  265. package/utils/validators/formRules/index.ts +5 -5
  266. package/utils/validators/machine-pool.ts +1 -1
  267. package/utils/validators/setting.js +18 -3
  268. package/utils/xccdf.ts +418 -0
  269. package/assets/fonts/lato/lato-v17-latin-700.woff +0 -0
  270. package/assets/fonts/lato/lato-v17-latin-700.woff2 +0 -0
  271. package/assets/fonts/lato/lato-v17-latin-regular.woff +0 -0
  272. package/assets/fonts/lato/lato-v17-latin-regular.woff2 +0 -0
@@ -0,0 +1,216 @@
1
+ import { shallowMount } from '@vue/test-utils';
2
+ import FleetSummaryGraph from '@shell/components/formatter/FleetSummaryGraph.vue';
3
+ import { FLEET } from '@shell/config/types';
4
+ import { ExtendedVue, Vue } from 'vue/types/vue';
5
+ import { DefaultProps } from 'vue/types/options';
6
+
7
+ const FleetSummaryGraphComponent = FleetSummaryGraph as unknown as ExtendedVue<Vue, {}, {}, {}, DefaultProps>;
8
+
9
+ function makeRow({
10
+ type = FLEET.GIT_REPO,
11
+ resourceCounts = {},
12
+ targetClusters = [],
13
+ statusResourceCountsForCluster = undefined,
14
+ }: {
15
+ type?: string;
16
+ resourceCounts?: Record<string, number>;
17
+ targetClusters?: any[];
18
+ statusResourceCountsForCluster?: any;
19
+ } = {}) {
20
+ const row: Record<string, any> = {
21
+ id: 'test-ns/test-row',
22
+ type,
23
+ status: { resourceCounts },
24
+ targetClusters,
25
+ };
26
+
27
+ if (statusResourceCountsForCluster !== undefined) {
28
+ row.statusResourceCountsForCluster = statusResourceCountsForCluster;
29
+ }
30
+
31
+ return row;
32
+ }
33
+
34
+ describe('component: FleetSummaryGraph', () => {
35
+ describe('summary', () => {
36
+ it('returns status.resourceCounts for GitRepo rows', () => {
37
+ const resourceCounts = {
38
+ desiredReady: 7,
39
+ ready: 7,
40
+ };
41
+ const wrapper = shallowMount(FleetSummaryGraphComponent, {
42
+ propsData: {
43
+ row: makeRow({
44
+ resourceCounts,
45
+ targetClusters: [{ id: 'cluster-1' }],
46
+ }),
47
+ },
48
+ });
49
+
50
+ expect((wrapper.vm as any).summary).toStrictEqual(resourceCounts);
51
+ });
52
+
53
+ it('returns status.resourceCounts for HelmOp rows', () => {
54
+ const resourceCounts = {
55
+ desiredReady: 3,
56
+ ready: 2,
57
+ modified: 1,
58
+ };
59
+ const wrapper = shallowMount(FleetSummaryGraphComponent, {
60
+ propsData: {
61
+ row: makeRow({
62
+ type: FLEET.HELM_OP,
63
+ resourceCounts,
64
+ targetClusters: [{ id: 'cluster-1' }],
65
+ }),
66
+ },
67
+ });
68
+
69
+ expect((wrapper.vm as any).summary).toStrictEqual(resourceCounts);
70
+ });
71
+
72
+ it('does not return function when row has statusResourceCountsForCluster as a method', () => {
73
+ const resourceCounts = {
74
+ desiredReady: 5,
75
+ ready: 5,
76
+ };
77
+ const wrapper = shallowMount(FleetSummaryGraphComponent, {
78
+ propsData: {
79
+ row: makeRow({
80
+ resourceCounts,
81
+ targetClusters: [{ id: 'cluster-1' }],
82
+ // Simulate a class method — the bug was that a truthy function
83
+ // caused summary to return the function itself instead of resourceCounts
84
+ statusResourceCountsForCluster: (clusterId: string) => ({ desiredReady: 0 }),
85
+ }),
86
+ },
87
+ });
88
+
89
+ const summary = (wrapper.vm as any).summary;
90
+
91
+ expect(typeof summary).not.toBe('function');
92
+ expect(summary).toStrictEqual(resourceCounts);
93
+ });
94
+
95
+ it('calls statusResourceCountsForCluster with clusterId when clusterId prop is set', () => {
96
+ const perClusterData = {
97
+ desiredReady: 2,
98
+ ready: 1,
99
+ };
100
+ const mockFn = jest.fn().mockReturnValue(perClusterData);
101
+
102
+ const wrapper = shallowMount(FleetSummaryGraphComponent, {
103
+ propsData: {
104
+ row: makeRow({ statusResourceCountsForCluster: mockFn }),
105
+ clusterId: 'cluster-1',
106
+ },
107
+ });
108
+
109
+ expect((wrapper.vm as any).summary).toStrictEqual(perClusterData);
110
+ expect(mockFn).toHaveBeenCalledWith('cluster-1');
111
+ });
112
+
113
+ it('returns empty object when status.resourceCounts is undefined', () => {
114
+ const wrapper = shallowMount(FleetSummaryGraphComponent, { propsData: { row: makeRow() } });
115
+
116
+ expect((wrapper.vm as any).summary).toStrictEqual({});
117
+ });
118
+ });
119
+
120
+ describe('show', () => {
121
+ it('returns true when stateParts exist and row has targetClusters', () => {
122
+ const wrapper = shallowMount(FleetSummaryGraphComponent, {
123
+ propsData: {
124
+ row: makeRow({
125
+ resourceCounts: {
126
+ desiredReady: 5,
127
+ ready: 5,
128
+ },
129
+ targetClusters: [{ id: 'cluster-1' }],
130
+ }),
131
+ },
132
+ });
133
+
134
+ expect((wrapper.vm as any).show).toBeTruthy();
135
+ });
136
+
137
+ it('returns false when stateParts exist but targetClusters is empty', () => {
138
+ const wrapper = shallowMount(FleetSummaryGraphComponent, {
139
+ propsData: {
140
+ row: makeRow({
141
+ resourceCounts: {
142
+ desiredReady: 5,
143
+ ready: 5,
144
+ },
145
+ targetClusters: [],
146
+ }),
147
+ },
148
+ });
149
+
150
+ expect((wrapper.vm as any).show).toBeFalsy();
151
+ });
152
+
153
+ it('returns true for FLEET.CLUSTER type even without targetClusters', () => {
154
+ const wrapper = shallowMount(FleetSummaryGraphComponent, {
155
+ propsData: {
156
+ row: makeRow({
157
+ type: FLEET.CLUSTER,
158
+ resourceCounts: {
159
+ desiredReady: 3,
160
+ ready: 3,
161
+ },
162
+ }),
163
+ },
164
+ });
165
+
166
+ expect((wrapper.vm as any).show).toBeTruthy();
167
+ });
168
+
169
+ it('returns false when resourceCounts is empty', () => {
170
+ const wrapper = shallowMount(FleetSummaryGraphComponent, { propsData: { row: makeRow({ targetClusters: [{ id: 'cluster-1' }] }) } });
171
+
172
+ expect((wrapper.vm as any).show).toBeFalsy();
173
+ });
174
+ });
175
+
176
+ describe('stateParts', () => {
177
+ it('filters out keys starting with "desired"', () => {
178
+ const wrapper = shallowMount(FleetSummaryGraphComponent, {
179
+ propsData: {
180
+ row: makeRow({
181
+ resourceCounts: {
182
+ desiredReady: 5,
183
+ ready: 5,
184
+ },
185
+ targetClusters: [{ id: 'cluster-1' }],
186
+ }),
187
+ },
188
+ });
189
+
190
+ const parts = (wrapper.vm as any).stateParts;
191
+
192
+ expect(parts.every((p: any) => !p.label.startsWith('Desired'))).toBe(true);
193
+ });
194
+
195
+ it('filters out entries with value 0', () => {
196
+ const wrapper = shallowMount(FleetSummaryGraphComponent, {
197
+ propsData: {
198
+ row: makeRow({
199
+ resourceCounts: {
200
+ desiredReady: 5,
201
+ ready: 5,
202
+ notReady: 0,
203
+ },
204
+ targetClusters: [{ id: 'cluster-1' }],
205
+ }),
206
+ },
207
+ });
208
+
209
+ const parts = (wrapper.vm as any).stateParts;
210
+ const labels = parts.map((p: any) => p.label);
211
+
212
+ expect(labels).toContain('Ready');
213
+ expect(labels).not.toContain('NotReady');
214
+ });
215
+ });
216
+ });
@@ -7,11 +7,9 @@ describe('component: PodsUsage', () => {
7
7
  props: {
8
8
  row: {
9
9
  isReady: true,
10
- mgmt: {
11
- status: {
12
- requested: { pods: 10 },
13
- allocatable: { pods: 20 }
14
- }
10
+ status: {
11
+ requested: { pods: 10 },
12
+ allocatable: { pods: 20 }
15
13
  }
16
14
  }
17
15
  },
@@ -28,11 +26,9 @@ describe('component: PodsUsage', () => {
28
26
  props: {
29
27
  row: {
30
28
  isReady: true,
31
- mgmt: {
32
- status: {
33
- requested: { pods: 10 },
34
- allocatable: { pods: 0 }
35
- }
29
+ status: {
30
+ requested: { pods: 10 },
31
+ allocatable: { pods: 0 }
36
32
  }
37
33
  }
38
34
  },
@@ -1005,8 +1005,8 @@ export default {
1005
1005
  .ns-filter-clear {
1006
1006
  cursor: pointer;
1007
1007
  position: absolute;
1008
- right: 10px;
1009
- top: 10px;
1008
+ right: 12px;
1009
+ top: 5px;
1010
1010
  line-height: 24px;
1011
1011
  text-align: center;
1012
1012
  width: 14px;
@@ -35,7 +35,18 @@ interface UpdateArgs {
35
35
  }
36
36
 
37
37
  type MgmtCluster = {
38
- [key: string]: any
38
+ [key: string]: any,
39
+ id: string,
40
+ nameDisplay: string,
41
+ canExplore: boolean,
42
+ providerMenuLogo: string,
43
+ badge: string,
44
+ iconColor: string,
45
+ isLocal: boolean,
46
+ pinned: boolean,
47
+ description: string,
48
+ pin: () => void
49
+ unpin: () => void
39
50
  }
40
51
 
41
52
  type ProvCluster = {
@@ -148,7 +159,8 @@ export abstract class BaseTopLevelMenuHelper {
148
159
  return {
149
160
  id: mgmtCluster.id,
150
161
  label: mgmtCluster.nameDisplay,
151
- ready: mgmtCluster.isReady,
162
+ // Align side nav cluster, home page name link and cluster management cluster explore buttons on canExplore
163
+ ready: mgmtCluster.canExplore,
152
164
  providerNavLogo: mgmtCluster.providerMenuLogo,
153
165
  badge: mgmtCluster.badge,
154
166
  iconColor: mgmtCluster.iconColor,
@@ -488,7 +500,7 @@ export class TopLevelMenuHelperLegacy extends BaseTopLevelMenuHelper implements
488
500
  const maxClustersToShow = args.unPinnedMax || 10;
489
501
 
490
502
  const search = (clusterFilter || '').toLowerCase();
491
- let localCluster: MgmtCluster | null = null;
503
+ let localCluster: TopLevelMenuCluster | null = null;
492
504
 
493
505
  const filtered = clusters.filter((c) => {
494
506
  // If we're searching we don't care if pinned or not
@@ -98,6 +98,17 @@ export default {
98
98
  return count?.summary.count;
99
99
  },
100
100
 
101
+ routeComboActive() {
102
+ if (!this.routeCombo) {
103
+ return false;
104
+ }
105
+
106
+ const ready = [...this.appBar.pinFiltered, ...this.appBar.clustersFiltered].filter((c) => c.ready);
107
+ const readyCount = ready.length;
108
+
109
+ return readyCount > 1 || (readyCount === 1 && this.clusterId !== ready[0].id);
110
+ },
111
+
101
112
  // New
102
113
  search() {
103
114
  return (this.clusterFilter || '').toLowerCase();
@@ -386,7 +397,7 @@ export default {
386
397
  },
387
398
 
388
399
  clusterMenuClick(ev, cluster) {
389
- if (this.routeCombo) {
400
+ if (this.routeComboActive) {
390
401
  ev.preventDefault();
391
402
 
392
403
  if (this.isCurrRouteClusterExplorer && this.productFromRoute === this.currentProduct?.name) {
@@ -423,7 +434,7 @@ export default {
423
434
  },
424
435
 
425
436
  async goToHarvesterCluster() {
426
- const localCluster = this.$store.getters['management/all'](CAPI.RANCHER_CLUSTER).find((C) => C.id === 'fleet-local/local');
437
+ const localCluster = this.$store.getters['management/byId'](CAPI.RANCHER_CLUSTER, 'fleet-local/local');
427
438
 
428
439
  try {
429
440
  await localCluster.goToHarvesterCluster();
@@ -446,7 +457,7 @@ export default {
446
457
  content = this.shown ? null : contentText;
447
458
 
448
459
  // if key combo is pressed, then we update the tooltip as well
449
- } else if (this.routeCombo &&
460
+ } else if (this.routeComboActive &&
450
461
  typeof item === 'object' &&
451
462
  !Array.isArray(item) &&
452
463
  item !== null &&
@@ -706,7 +717,7 @@ export default {
706
717
  <ClusterIconMenu
707
718
  v-clean-tooltip="getTooltipConfig(c, true)"
708
719
  :cluster="c"
709
- :route-combo="routeCombo"
720
+ :route-combo="routeComboActive"
710
721
  class="rancher-provider-icon"
711
722
  />
712
723
  <div
@@ -785,7 +796,7 @@ export default {
785
796
  <ClusterIconMenu
786
797
  v-clean-tooltip="getTooltipConfig(c, true)"
787
798
  :cluster="c"
788
- :route-combo="routeCombo"
799
+ :route-combo="routeComboActive"
789
800
  class="rancher-provider-icon"
790
801
  />
791
802
  <div
@@ -110,28 +110,28 @@ describe('topLevelMenu', () => {
110
110
  id: 'an-id1',
111
111
  mgmt: { id: 'an-id1' },
112
112
  nameDisplay: 'c-cluster',
113
- isReady: true
113
+ canExplore: true
114
114
  },
115
115
  {
116
116
  name: 'x33-cwf5-name',
117
117
  id: 'an-id2',
118
118
  mgmt: { id: 'an-id2' },
119
119
  nameDisplay: 'a-cluster',
120
- isReady: true
120
+ canExplore: true
121
121
  },
122
122
  {
123
123
  name: 'x34-cwf5-name',
124
124
  id: 'an-id3',
125
125
  mgmt: { id: 'an-id3' },
126
126
  nameDisplay: 'b-cluster',
127
- isReady: true
127
+ canExplore: true
128
128
  },
129
129
  {
130
130
  name: 'local-name',
131
131
  id: 'local',
132
132
  mgmt: { id: 'local' },
133
133
  nameDisplay: 'local',
134
- isReady: true
134
+ canExplore: true
135
135
  },
136
136
  ])
137
137
  }
@@ -156,28 +156,28 @@ describe('topLevelMenu', () => {
156
156
  id: 'an-id1',
157
157
  mgmt: { id: 'an-id1' },
158
158
  nameDisplay: 'c-cluster',
159
- isReady: true
159
+ canExplore: true
160
160
  },
161
161
  {
162
162
  name: 'x33-cwf5-name',
163
163
  id: 'an-id2',
164
164
  mgmt: { id: 'an-id2' },
165
165
  nameDisplay: 'a-cluster',
166
- isReady: false
166
+ canExplore: false
167
167
  },
168
168
  {
169
169
  name: 'x34-cwf5-name',
170
170
  id: 'an-id3',
171
171
  mgmt: { id: 'an-id3' },
172
172
  nameDisplay: 'b-cluster',
173
- isReady: true
173
+ canExplore: true
174
174
  },
175
175
  {
176
176
  name: 'local-name',
177
177
  id: 'local',
178
178
  mgmt: { id: 'local' },
179
179
  nameDisplay: 'local',
180
- isReady: true,
180
+ canExplore: true,
181
181
  isLocal: true,
182
182
  },
183
183
  ];
@@ -195,9 +195,9 @@ describe('topLevelMenu', () => {
195
195
  await waitForIt();
196
196
 
197
197
  expect(wrapper.find('[data-testid="top-level-menu-cluster-0"] .cluster-name p').text()).toStrictEqual('local');
198
+ expect(wrapper.find('[data-testid="top-level-menu-cluster-3"] .cluster-name p').text()).toStrictEqual('a-cluster');
198
199
  expect(wrapper.find('[data-testid="top-level-menu-cluster-1"] .cluster-name p').text()).toStrictEqual('b-cluster');
199
200
  expect(wrapper.find('[data-testid="top-level-menu-cluster-2"] .cluster-name p').text()).toStrictEqual('c-cluster');
200
- expect(wrapper.find('[data-testid="top-level-menu-cluster-3"] .cluster-name p').text()).toStrictEqual('a-cluster');
201
201
  });
202
202
 
203
203
  it('should show local cluster always on top of the list of clusters (pinned and ready clusters)', async() => {
@@ -212,7 +212,7 @@ describe('topLevelMenu', () => {
212
212
  id: 'an-id1',
213
213
  mgmt: { id: 'an-id1' },
214
214
  nameDisplay: 'c-cluster',
215
- isReady: true,
215
+ canExplore: true,
216
216
  pinned: true
217
217
  },
218
218
  {
@@ -220,7 +220,7 @@ describe('topLevelMenu', () => {
220
220
  id: 'an-id2',
221
221
  mgmt: { id: 'an-id2' },
222
222
  nameDisplay: 'a-cluster',
223
- isReady: true,
223
+ canExplore: true,
224
224
  pinned: true
225
225
  },
226
226
  {
@@ -228,7 +228,7 @@ describe('topLevelMenu', () => {
228
228
  id: 'an-id3',
229
229
  mgmt: { id: 'an-id3' },
230
230
  nameDisplay: 'b-cluster',
231
- isReady: true,
231
+ canExplore: true,
232
232
  pinned: true
233
233
  },
234
234
  {
@@ -236,7 +236,7 @@ describe('topLevelMenu', () => {
236
236
  id: 'local',
237
237
  mgmt: { id: 'local' },
238
238
  nameDisplay: 'local',
239
- isReady: true,
239
+ canExplore: true,
240
240
  pinned: true
241
241
  },
242
242
  ])
@@ -271,7 +271,7 @@ describe('topLevelMenu', () => {
271
271
  id: 'an-id1',
272
272
  mgmt: { id: 'an-id1' },
273
273
  nameDisplay: 'c-cluster',
274
- isReady: true,
274
+ canExplore: true,
275
275
  pinned: true
276
276
  },
277
277
  {
@@ -279,7 +279,7 @@ describe('topLevelMenu', () => {
279
279
  id: 'an-id2',
280
280
  mgmt: { id: 'an-id2' },
281
281
  nameDisplay: 'a-cluster',
282
- isReady: true,
282
+ canExplore: true,
283
283
  pinned: true
284
284
  },
285
285
  {
@@ -287,7 +287,7 @@ describe('topLevelMenu', () => {
287
287
  id: 'an-id3',
288
288
  mgmt: { id: 'an-id3' },
289
289
  nameDisplay: 'b-cluster',
290
- isReady: false,
290
+ canExplore: false,
291
291
  pinned: true
292
292
  },
293
293
  {
@@ -295,7 +295,7 @@ describe('topLevelMenu', () => {
295
295
  id: 'local',
296
296
  mgmt: { id: 'local' },
297
297
  nameDisplay: 'local',
298
- isReady: true,
298
+ canExplore: true,
299
299
  pinned: true
300
300
  },
301
301
  ])
@@ -328,7 +328,7 @@ describe('topLevelMenu', () => {
328
328
  mgmt: { id: 'an-id1' },
329
329
  description: 'some-description1',
330
330
  nameDisplay: 'some-label',
331
- isReady: true,
331
+ canExplore: true,
332
332
  pinned: true
333
333
  },
334
334
  // pinned NOT ready cluster
@@ -347,7 +347,7 @@ describe('topLevelMenu', () => {
347
347
  mgmt: { id: 'an-id3' },
348
348
  description: 'some-description3',
349
349
  nameDisplay: 'some-label',
350
- isReady: true
350
+ canExplore: true
351
351
  },
352
352
  // unpinned NOT ready cluster
353
353
  {
@@ -392,7 +392,7 @@ describe('topLevelMenu', () => {
392
392
  mgmt: { id: 'an-id1' },
393
393
  description: 'some-description1',
394
394
  nameDisplay: 'some-label',
395
- isReady: true,
395
+ canExplore: true,
396
396
  pinned: true
397
397
  },
398
398
  // pinned NOT ready cluster
@@ -411,7 +411,7 @@ describe('topLevelMenu', () => {
411
411
  mgmt: { id: 'an-id3' },
412
412
  description: 'some-description3',
413
413
  nameDisplay: 'some-label',
414
- isReady: true
414
+ canExplore: true
415
415
  },
416
416
  // unpinned NOT ready cluster
417
417
  {
@@ -713,4 +713,128 @@ describe('topLevelMenu', () => {
713
713
  expect(wrapper.vm.mgmtClusters).toStrictEqual([]);
714
714
  });
715
715
  });
716
+
717
+ describe('computed properties', () => {
718
+ describe('routeComboActive', () => {
719
+ it('should be true when routeCombo is true and there are multiple ready clusters', async() => {
720
+ const wrapper: Wrapper<InstanceType<typeof TopLevelMenu>> = mount(TopLevelMenu, {
721
+ global: {
722
+ mocks: {
723
+ $route: {},
724
+ $store: {
725
+ ...generateStore([
726
+ {
727
+ nameDisplay: 'cluster1',
728
+ id: 'an-id1',
729
+ mgmt: { id: 'an-id1' },
730
+ canExplore: true
731
+ },
732
+ {
733
+ nameDisplay: 'cluster2',
734
+ id: 'an-id2',
735
+ mgmt: { id: 'an-id2' },
736
+ canExplore: true
737
+ }
738
+ ])
739
+ }
740
+ },
741
+ stubs: ['BrandImage', 'router-link'],
742
+ }
743
+ });
744
+
745
+ await waitForIt();
746
+ await wrapper.setData({ routeCombo: true });
747
+
748
+ expect(wrapper.vm.routeComboActive).toBe(true);
749
+ });
750
+
751
+ it('should be false when routeCombo is false', async() => {
752
+ const wrapper: Wrapper<InstanceType<typeof TopLevelMenu>> = mount(TopLevelMenu, {
753
+ global: {
754
+ mocks: {
755
+ $route: {},
756
+ $store: {
757
+ ...generateStore([
758
+ {
759
+ nameDisplay: 'cluster1',
760
+ id: 'an-id1',
761
+ mgmt: { id: 'an-id1' },
762
+ canExplore: true
763
+ },
764
+ {
765
+ nameDisplay: 'cluster2',
766
+ id: 'an-id2',
767
+ mgmt: { id: 'an-id2' },
768
+ canExplore: true
769
+ }
770
+ ])
771
+ }
772
+ },
773
+ stubs: ['BrandImage', 'router-link'],
774
+ }
775
+ });
776
+
777
+ await waitForIt();
778
+ await wrapper.setData({ routeCombo: false });
779
+
780
+ expect(wrapper.vm.routeComboActive).toBe(false);
781
+ });
782
+
783
+ it('should be false when there is only one ready cluster and it is the current cluster', async() => {
784
+ const store = generateStore([
785
+ {
786
+ nameDisplay: 'cluster1',
787
+ id: 'an-id1',
788
+ mgmt: { id: 'an-id1' },
789
+ canExplore: true
790
+ }
791
+ ]);
792
+
793
+ store.getters.clusterId = 'an-id1' as any;
794
+
795
+ const wrapper: Wrapper<InstanceType<typeof TopLevelMenu>> = mount(TopLevelMenu, {
796
+ global: {
797
+ mocks: {
798
+ $route: {},
799
+ $store: store
800
+ },
801
+ stubs: ['BrandImage', 'router-link'],
802
+ }
803
+ });
804
+
805
+ await waitForIt();
806
+ await wrapper.setData({ routeCombo: true });
807
+
808
+ expect(wrapper.vm.routeComboActive).toBe(false);
809
+ });
810
+
811
+ it('should be true when there is only one ready cluster but it is not the current cluster', async() => {
812
+ const store = generateStore([
813
+ {
814
+ nameDisplay: 'cluster1',
815
+ id: 'an-id1',
816
+ mgmt: { id: 'an-id1' },
817
+ canExplore: true
818
+ }
819
+ ]);
820
+
821
+ store.getters.clusterId = 'some-other-cluster-id' as any;
822
+
823
+ const wrapper: Wrapper<InstanceType<typeof TopLevelMenu>> = mount(TopLevelMenu, {
824
+ global: {
825
+ mocks: {
826
+ $route: {},
827
+ $store: store
828
+ },
829
+ stubs: ['BrandImage', 'router-link'],
830
+ }
831
+ });
832
+
833
+ await waitForIt();
834
+ await wrapper.setData({ routeCombo: true });
835
+
836
+ expect(wrapper.vm.routeComboActive).toBe(true);
837
+ });
838
+ });
839
+ });
716
840
  });