@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,128 @@
1
+ import MgmtCluster from '@shell/models/management.cattle.io.cluster';
2
+ import { filterHiddenLocalCluster, filterOnlyKubernetesClusters, paginationFilterClusters } from '@shell/utils/cluster';
3
+ import { VuexStore } from '@shell/types/store/vuex';
4
+ import { FilterArgs, PaginationArgs, PaginationFilterEquality, PaginationParamFilter } from '@shell/types/store/pagination.types';
5
+ import { sameContents } from '@shell/utils/array';
6
+ import { PagTableFetchPageSecondaryResourcesOpts, PagTableFetchSecondaryResourcesOpts } from '@shell/types/components/paginatedResourceTable';
7
+ import { CAPI } from '@shell/config/types';
8
+ import { ActionFindPageArgs } from '@shell/types/store/dashboard-store.types';
9
+
10
+ interface CommonConfig {
11
+ $store: VuexStore
12
+ }
13
+
14
+ /**
15
+ * Utils to support listing management.cattle.io.clusters
16
+ */
17
+ class ManagementClusterUtils {
18
+ /**
19
+ * Filter out hidden clusters from list of all clusters
20
+ */
21
+ filterRowsLocal(rows: MgmtCluster[], { $store }: CommonConfig): MgmtCluster[] {
22
+ return filterHiddenLocalCluster(filterOnlyKubernetesClusters(rows || [], $store), $store);
23
+ }
24
+
25
+ /**
26
+ * Filter out hidden clusters via api
27
+ */
28
+ filterRowsApi(pagination: PaginationArgs, { $store }: CommonConfig): PaginationArgs {
29
+ if (!pagination.filters) {
30
+ pagination.filters = [];
31
+ }
32
+
33
+ const existingFilters = pagination.filters;
34
+ const requiredFilters = paginationFilterClusters($store, false);
35
+
36
+ for (let i = 0; i < requiredFilters.length; i++) {
37
+ let found = false;
38
+ const required = requiredFilters[i];
39
+
40
+ for (let j = 0; j < existingFilters.length; j++) {
41
+ const existing = existingFilters[j];
42
+
43
+ if (
44
+ required.fields.length === existing.fields.length &&
45
+ sameContents(required.fields.map((e) => e.field), existing.fields.map((e) => e.field))
46
+ ) {
47
+ Object.assign(existing, required);
48
+ found = true;
49
+ break;
50
+ }
51
+ }
52
+
53
+ if (!found) {
54
+ pagination.filters.push(required);
55
+ }
56
+ }
57
+
58
+ return pagination;
59
+ }
60
+
61
+ /**
62
+ * Fetch resources used to support vai off world
63
+ *
64
+ * Of type PagTableFetchSecondaryResources
65
+ */
66
+ fetchSecondaryResources(opts: PagTableFetchSecondaryResourcesOpts, { $store }: CommonConfig): Promise<any>[] {
67
+ if (opts.canPaginate) {
68
+ return [];
69
+ }
70
+
71
+ const promises = [];
72
+
73
+ if ( $store.getters['management/canList'](CAPI.RANCHER_CLUSTER) ) {
74
+ promises.push($store.dispatch('management/findAll', { type: CAPI.RANCHER_CLUSTER }));
75
+ }
76
+
77
+ return promises;
78
+ }
79
+
80
+ /**
81
+ * Fetch resources used to support vai on and the current page
82
+ */
83
+ async fetchPageSecondaryResources({
84
+ canPaginate, force, page, pagResult
85
+ }: PagTableFetchPageSecondaryResourcesOpts, { $store }: CommonConfig): Promise<Promise<any>[]> {
86
+ if (!canPaginate || !page?.length) {
87
+ return [];
88
+ }
89
+
90
+ const promises = [];
91
+
92
+ if ( $store.getters['management/canList'](CAPI.RANCHER_CLUSTER) ) {
93
+ const opt: ActionFindPageArgs = {
94
+ force,
95
+ pagination: new FilterArgs({
96
+ filters: new PaginationParamFilter({
97
+ fields: [{
98
+ value: page.map((r: any) => r.provClusterId).join(','),
99
+ equality: PaginationFilterEquality.IN,
100
+ field: 'id',
101
+ }],
102
+ })
103
+ })
104
+ };
105
+
106
+ // Prov clusters
107
+ promises.push($store.dispatch(`management/findPage`, { type: CAPI.RANCHER_CLUSTER, opt }));
108
+ }
109
+
110
+ return promises;
111
+ }
112
+
113
+ forgetSecondaryResources({ context }: { context?: string } = {}, { $store }: CommonConfig) {
114
+ const canList = $store.getters['management/canList'](CAPI.RANCHER_CLUSTER);
115
+ const canPaginate = $store.getters['management/paginationEnabled']({
116
+ id: CAPI.RANCHER_CLUSTER,
117
+ context
118
+ });
119
+
120
+ if (canList && canPaginate) {
121
+ $store.dispatch('management/forgetType', CAPI.RANCHER_CLUSTER);
122
+ }
123
+ }
124
+ }
125
+
126
+ const instance = new ManagementClusterUtils();
127
+
128
+ export default instance;
@@ -214,6 +214,72 @@ describe('chartMixin', () => {
214
214
  id: 'custom-ns/custom-name'
215
215
  });
216
216
  });
217
+
218
+ it('should fall back to matchingInstalledApps when version namespace lookup fails', async() => {
219
+ const installedApp = {
220
+ id: 'other-ns/custom-name',
221
+ spec: { chart: { metadata: { version: '0.9.0' } } }
222
+ };
223
+
224
+ const mockStore = {
225
+ dispatch: jest.fn((action, payload) => {
226
+ if (action === 'cluster/find') {
227
+ return Promise.reject(new Error('not found'));
228
+ }
229
+
230
+ return Promise.resolve();
231
+ }),
232
+ getters: {
233
+ currentCluster: () => {},
234
+ isRancher: () => true,
235
+ 'catalog/repo': () => {
236
+ return () => 'repo';
237
+ },
238
+ 'catalog/chart': () => {
239
+ return {
240
+ id: 'chart-id',
241
+ versions: [{ version: '1.0.0' }],
242
+ matchingInstalledApps: [installedApp]
243
+ };
244
+ },
245
+ 'catalog/version': () => ({
246
+ version: '1.0.0',
247
+ annotations: {
248
+ [CATALOG_ANNOTATIONS.NAMESPACE]: 'custom-ns',
249
+ [CATALOG_ANNOTATIONS.RELEASE_NAME]: 'custom-name',
250
+ }
251
+ }),
252
+ 'prefs/get': () => {},
253
+ 'i18n/t': () => jest.fn()
254
+ }
255
+ };
256
+
257
+ const DummyComponent = {
258
+ mixins: [ChartMixin],
259
+ template: '<div></div>',
260
+ };
261
+
262
+ const wrapper = mount(
263
+ DummyComponent,
264
+ {
265
+ global: {
266
+ mocks: {
267
+ $store: mockStore,
268
+ $route: {
269
+ query: {
270
+ chart: 'chart_name',
271
+ versionName: '1.0.0'
272
+ }
273
+ }
274
+ }
275
+ }
276
+ });
277
+
278
+ await wrapper.vm.fetchChart();
279
+
280
+ expect(wrapper.vm.existing).toStrictEqual(installedApp);
281
+ expect(wrapper.vm.mode).toStrictEqual('edit');
282
+ });
217
283
  });
218
284
 
219
285
  describe('action', () => {
@@ -366,6 +432,52 @@ describe('chartMixin', () => {
366
432
  });
367
433
  });
368
434
 
435
+ describe('isChartTargeted', () => {
436
+ const DummyComponent = {
437
+ mixins: [ChartMixin],
438
+ template: '<div></div>',
439
+ };
440
+
441
+ const mockStore = {
442
+ dispatch: jest.fn(() => Promise.resolve()),
443
+ getters: { 'i18n/t': (key: string) => key }
444
+ };
445
+
446
+ it('should return truthy when version annotations have both namespace and release-name', () => {
447
+ const wrapper = mount(DummyComponent, {
448
+ data: () => ({
449
+ version: {
450
+ annotations: {
451
+ [CATALOG_ANNOTATIONS.NAMESPACE]: 'custom-ns',
452
+ [CATALOG_ANNOTATIONS.RELEASE_NAME]: 'custom-name',
453
+ }
454
+ }
455
+ }),
456
+ global: { mocks: { $store: mockStore } }
457
+ });
458
+
459
+ expect(wrapper.vm.isChartTargeted).toBeTruthy();
460
+ });
461
+
462
+ it('should return falsy when version annotations are missing', () => {
463
+ const wrapper = mount(DummyComponent, {
464
+ data: () => ({ version: { annotations: {} } }),
465
+ global: { mocks: { $store: mockStore } }
466
+ });
467
+
468
+ expect(wrapper.vm.isChartTargeted).toBeFalsy();
469
+ });
470
+
471
+ it('should return falsy when version is null', () => {
472
+ const wrapper = mount(DummyComponent, {
473
+ data: () => ({ version: null }),
474
+ global: { mocks: { $store: mockStore } }
475
+ });
476
+
477
+ expect(wrapper.vm.isChartTargeted).toBeFalsy();
478
+ });
479
+ });
480
+
369
481
  describe('mappedVersions', () => {
370
482
  it('should return versions sorted by semver (descending)', () => {
371
483
  const versions = [
package/mixins/brand.js CHANGED
@@ -183,7 +183,8 @@ export default {
183
183
  },
184
184
  setBodyClass() {
185
185
  const body = document.getElementsByTagName('body')[0];
186
- const cssClass = `overflow-hidden dashboard-body`;
186
+ const isStandalone = this.$route?.meta?.standalone;
187
+ const cssClass = isStandalone ? 'dashboard-body' : 'overflow-hidden dashboard-body';
187
188
  let bodyClass = `theme-${ this.theme } ${ cssClass }`;
188
189
 
189
190
  if ( this.brand ) {
package/mixins/chart.js CHANGED
@@ -13,7 +13,7 @@ import { CAPI, CATALOG } from '@shell/config/types';
13
13
  import { isPrerelease } from '@shell/utils/version';
14
14
  import { compareChartVersions } from '@shell/utils/chart';
15
15
  import difference from 'lodash/difference';
16
- import { LINUX, APP_UPGRADE_STATUS } from '@shell/store/catalog';
16
+ import { APP_UPGRADE_STATUS, isRancherRepo, getPermittedOSs } from '@shell/store/catalog';
17
17
  import { clone } from '@shell/utils/object';
18
18
  import { merge } from 'lodash';
19
19
 
@@ -84,7 +84,8 @@ export default {
84
84
  keywords: version.keywords
85
85
  };
86
86
 
87
- const permittedSystems = (version?.annotations?.[CATALOG_ANNOTATIONS.PERMITTED_OS] || LINUX).split(',');
87
+ const isRancher = isRancherRepo(this.repo, this.chart);
88
+ const permittedSystems = getPermittedOSs(version?.annotations, isRancher);
88
89
 
89
90
  if (permittedSystems.length > 0 && difference(OSs, permittedSystems).length > 0) {
90
91
  nue.disabled = true;
@@ -271,7 +272,7 @@ export default {
271
272
  },
272
273
 
273
274
  isChartTargeted() {
274
- return this.chart?.targetNamespace && this.chart?.targetName;
275
+ return this.version?.annotations?.[CATALOG_ANNOTATIONS.NAMESPACE] && this.version?.annotations?.[CATALOG_ANNOTATIONS.RELEASE_NAME];
275
276
  },
276
277
 
277
278
  hasQuestions() {
@@ -386,8 +387,8 @@ export default {
386
387
  // Use those values to check for a catalog app resource.
387
388
  // If found, set the form to edit mode. If not, set the
388
389
  // form to create mode.
389
- // This is a hard blocker - installing a new instance is NOT allowed.
390
390
 
391
+ // This is a hard blocker - installing a new instance is NOT allowed.
391
392
  this.canInstallNew = false;
392
393
 
393
394
  try {
@@ -410,11 +411,10 @@ export default {
410
411
  if ( targetNamespace && targetName ) {
411
412
  // If the app name and namespace values are not provided in the
412
413
  // query, fall back on target values defined in the Helm chart itself.
413
-
414
414
  // Ask to install a special chart with fixed namespace/name
415
415
  // or edit it if there's an existing install.
416
- // This is a hard blocker - installing a new instance is NOT allowed.
417
416
 
417
+ // This is a hard blocker - installing a new instance is NOT allowed.
418
418
  this.canInstallNew = false;
419
419
 
420
420
  try {
@@ -424,8 +424,12 @@ export default {
424
424
  });
425
425
  this.mode = _EDIT;
426
426
  } catch (e) {
427
- this.mode = _CREATE;
428
- this.existing = null;
427
+ // Version targets a different namespace than where the app is installed.
428
+ // Fall back to matching installed apps (e.g. chart detail page).
429
+ const matching = this.chart?.matchingInstalledApps || [];
430
+
431
+ this.existing = matching[0] || null;
432
+ this.mode = this.existing ? _EDIT : _CREATE;
429
433
  }
430
434
  } else {
431
435
  // Regular chart (not targeted) - check if there are installed instances.
@@ -1,5 +1,5 @@
1
1
  import { NAMESPACE_FILTER_NAMESPACED_YES, NAMESPACE_FILTER_NAMESPACED_NO, NAMESPACE_FILTER_ALL } from '@shell/utils/namespace-filter';
2
- import { NAMESPACE } from '@shell/config/types';
2
+ import { MANAGEMENT, NAMESPACE } from '@shell/config/types';
3
3
  import { ALL_NAMESPACES } from '@shell/store/prefs';
4
4
  import { mapGetters } from 'vuex';
5
5
  import { ResourceListComponentName } from '../components/ResourceList/resource-list.config';
@@ -248,7 +248,7 @@ export default {
248
248
  namespaceFilters: {
249
249
  immediate: true,
250
250
  async handler(neu, old) {
251
- if (!this.canPaginate || !this.isNamespaced) {
251
+ if (!this.canPaginate || !this.isNamespaced || !this.currentProduct?.showNamespaceFilter) {
252
252
  return;
253
253
  }
254
254
 
@@ -283,7 +283,7 @@ export default {
283
283
  allNamespaces: this.$store.getters[`${ this.currentProduct?.inStore }/all`](NAMESPACE),
284
284
  selection: neu,
285
285
  isAllNamespaces: this.isAllNamespaces,
286
- isLocalCluster: this.$store.getters['currentCluster'].isLocal,
286
+ isLocalCluster: this.$store.getters['currentCluster']?.isLocal,
287
287
  showReservedRancherNamespaces: this.showDynamicRancherNamespaces,
288
288
  productHidesSystemNamespaces: this.productHidesSystemNamespaces,
289
289
  });
@@ -354,15 +354,51 @@ export default {
354
354
 
355
355
  async beforeUnmount() {
356
356
  if (this.havePaginated) {
357
+ const store = this.overrideInStore || this.inStore;
357
358
  // of type @STEVE_WATCH_PARAMS
358
359
  const watchArgs = {
359
360
  type: this.resource,
360
361
  mode: STEVE_WATCH_MODE.RESOURCE_CHANGES,
361
362
  };
362
363
 
363
- await this.$store.dispatch(`${ this.overrideInStore || this.inStore }/forgetType`, this.resource, (watchParams) => {
364
- return watchParams.type === watchArgs.type && watchParams.mode === watchArgs.type.mode;
364
+ // Ensure that a resource remains in the store... and it's the same reference as before
365
+ // We'll do this for the mgmt cluster if there's a currentCluster set, otherwise we become unstuck
366
+ // (on nav we ensure currentCluster exists, but beforeUnmount fires afterwards, removing the cluster from pages that are using it)
367
+ const retainResource = this.resource === MANAGEMENT.CLUSTER && this.currentCluster ? this.currentCluster : null;
368
+
369
+ // Forget (unwatch, clear from store) the list's resource
370
+ await this.$store.dispatch(`${ store }/forgetType`, {
371
+ type: this.resource,
372
+ compareWatches: (watchParams) => {
373
+ return watchParams.type === watchArgs.type && watchParams.mode === watchArgs.mode;
374
+ },
375
+ unwatch: true,
376
+ forget: !retainResource
365
377
  });
378
+
379
+ if (!!retainResource) {
380
+ // The end state should be just like we've dispatched `${store}/find`
381
+
382
+ try {
383
+ // Clone to ensure we can load each property of A into B without worrying about them being the same reference
384
+ const clone = await this.$store.dispatch(`${ store }/clone`, { resource: retainResource });
385
+
386
+ // Load the resource. It's already in the store but this makes sure any legacy state (like havePage) is reset
387
+ await this.$store.dispatch(`${ store }/load`, {
388
+ data: clone,
389
+ existing: retainResource,
390
+ invalidatePageCache: true,
391
+ });
392
+
393
+ // Watch the resource.
394
+ await this.$store.dispatch(`${ store }/watch`, {
395
+ type: this.resource,
396
+ id: retainResource.id,
397
+ });
398
+ } catch (error) {
399
+ console.error('Error occurred whilst trying to retain the management cluster in cache and correctly watched', error); // eslint-disable-line no-console
400
+ }
401
+ }
366
402
  }
367
403
  }
368
404
  };
@@ -1,5 +1,5 @@
1
1
  import Kubeconfig from '@shell/models/ext.cattle.io.kubeconfig';
2
- import { CAPI, MANAGEMENT } from '@shell/config/types';
2
+ import { MANAGEMENT } from '@shell/config/types';
3
3
 
4
4
  // SteveModel is JS, so we need to type the constructor
5
5
  const KubeconfigModel = Kubeconfig as unknown as new (data: object) => Kubeconfig;
@@ -19,8 +19,9 @@ describe('class Kubeconfig', () => {
19
19
  // Mock $rootGetters before any getters are accessed
20
20
  // Cast to any since $rootGetters is inherited from JS SteveModel
21
21
  jest.spyOn(kubeconfig as any, '$rootGetters', 'get').mockReturnValue({
22
- 'i18n/t': mockT,
23
- 'management/all': () => [],
22
+ 'i18n/t': mockT,
23
+ 'management/all': () => [],
24
+ 'management/byId': () => null,
24
25
  ...rootGetters
25
26
  });
26
27
 
@@ -70,11 +71,12 @@ describe('class Kubeconfig', () => {
70
71
  });
71
72
 
72
73
  describe('referencedClusters', () => {
73
- const mockProvCluster = {
74
- mgmt: { id: 'c-m-abc123' },
75
- status: { clusterName: 'c-m-abc123' },
76
- nameDisplay: 'my-cluster',
77
- detailLocation: { name: 'c-cluster-product-resource-id', params: { cluster: 'my-cluster' } }
74
+ const mockProvCluster = { detailLocation: { name: 'c-cluster-product-resource-id', params: { cluster: 'my-cluster' } } };
75
+
76
+ const mockMgmtClusterWithProv = {
77
+ id: 'c-m-abc123',
78
+ nameDisplay: 'my-cluster',
79
+ provCluster: mockProvCluster
78
80
  };
79
81
 
80
82
  const mockMgmtCluster = {
@@ -99,12 +101,12 @@ describe('class Kubeconfig', () => {
99
101
  spec: { clusters: ['c-m-abc123'] }
100
102
  },
101
103
  {
102
- 'management/all': (type: string) => {
103
- if (type === CAPI.RANCHER_CLUSTER) {
104
- return [mockProvCluster];
104
+ 'management/byId': (type: string, id: string) => {
105
+ if (type === MANAGEMENT.CLUSTER && id === 'c-m-abc123') {
106
+ return mockMgmtClusterWithProv;
105
107
  }
106
108
 
107
- return [];
109
+ return null;
108
110
  }
109
111
  }
110
112
  );
@@ -124,12 +126,12 @@ describe('class Kubeconfig', () => {
124
126
  spec: { clusters: ['c-m-def456'] }
125
127
  },
126
128
  {
127
- 'management/all': (type: string) => {
128
- if (type === MANAGEMENT.CLUSTER) {
129
- return [mockMgmtCluster];
129
+ 'management/byId': (type: string, id: string) => {
130
+ if (type === MANAGEMENT.CLUSTER && id === 'c-m-def456') {
131
+ return mockMgmtCluster;
130
132
  }
131
133
 
132
- return [];
134
+ return null;
133
135
  }
134
136
  }
135
137
  );
@@ -157,11 +159,12 @@ describe('class Kubeconfig', () => {
157
159
  expect(mockT).toHaveBeenCalledWith('"ext.cattle.io.kubeconfig".deleted', { name: 'c-m-deleted' });
158
160
  });
159
161
 
160
- it('should prefer provisioning cluster over management cluster', () => {
161
- const mgmtClusterSameId = {
162
+ it('should prefer provisioning cluster over management cluster for location', () => {
163
+ const mgmtClusterBothLocs = {
162
164
  id: 'c-m-abc123',
163
- nameDisplay: 'mgmt-version',
164
- detailLocation: { name: 'mgmt-location' }
165
+ nameDisplay: 'my-cluster',
166
+ detailLocation: { name: 'mgmt-location' },
167
+ provCluster: { detailLocation: { name: 'prov-location' } }
165
168
  };
166
169
 
167
170
  const kubeconfig = createKubeconfig(
@@ -170,15 +173,12 @@ describe('class Kubeconfig', () => {
170
173
  spec: { clusters: ['c-m-abc123'] }
171
174
  },
172
175
  {
173
- 'management/all': (type: string) => {
174
- if (type === CAPI.RANCHER_CLUSTER) {
175
- return [mockProvCluster];
176
- }
177
- if (type === MANAGEMENT.CLUSTER) {
178
- return [mgmtClusterSameId];
176
+ 'management/byId': (type: string, id: string) => {
177
+ if (type === MANAGEMENT.CLUSTER && id === 'c-m-abc123') {
178
+ return mgmtClusterBothLocs;
179
179
  }
180
180
 
181
- return [];
181
+ return null;
182
182
  }
183
183
  }
184
184
  );
@@ -186,7 +186,7 @@ describe('class Kubeconfig', () => {
186
186
  expect(kubeconfig.referencedClusters).toStrictEqual([
187
187
  {
188
188
  label: 'my-cluster',
189
- location: mockProvCluster.detailLocation
189
+ location: { name: 'prov-location' }
190
190
  }
191
191
  ]);
192
192
  });
@@ -195,7 +195,7 @@ describe('class Kubeconfig', () => {
195
195
  describe('sortedReferencedClusters', () => {
196
196
  it('should sort existing clusters before deleted clusters', () => {
197
197
  const existingCluster = {
198
- mgmt: { id: 'c-m-exists' },
198
+ id: 'c-m-exists',
199
199
  nameDisplay: 'existing-cluster',
200
200
  detailLocation: { name: 'location' }
201
201
  };
@@ -206,12 +206,12 @@ describe('class Kubeconfig', () => {
206
206
  spec: { clusters: ['deleted-1', 'c-m-exists', 'deleted-2'] }
207
207
  },
208
208
  {
209
- 'management/all': (type: string) => {
210
- if (type === CAPI.RANCHER_CLUSTER) {
211
- return [existingCluster];
209
+ 'management/byId': (type: string, id: string) => {
210
+ if (type === MANAGEMENT.CLUSTER && id === 'c-m-exists') {
211
+ return existingCluster;
212
212
  }
213
213
 
214
- return [];
214
+ return null;
215
215
  }
216
216
  }
217
217
  );
@@ -225,17 +225,17 @@ describe('class Kubeconfig', () => {
225
225
  });
226
226
 
227
227
  it('should sort existing clusters alphabetically', () => {
228
- const clusters = [
229
- {
230
- mgmt: { id: 'c-m-zebra' }, nameDisplay: 'zebra', detailLocation: { name: 'z' }
228
+ const clusters: Record<string, any> = {
229
+ 'c-m-zebra': {
230
+ id: 'c-m-zebra', nameDisplay: 'zebra', detailLocation: { name: 'z' }
231
231
  },
232
- {
233
- mgmt: { id: 'c-m-alpha' }, nameDisplay: 'alpha', detailLocation: { name: 'a' }
232
+ 'c-m-alpha': {
233
+ id: 'c-m-alpha', nameDisplay: 'alpha', detailLocation: { name: 'a' }
234
234
  },
235
- {
236
- mgmt: { id: 'c-m-beta' }, nameDisplay: 'beta', detailLocation: { name: 'b' }
235
+ 'c-m-beta': {
236
+ id: 'c-m-beta', nameDisplay: 'beta', detailLocation: { name: 'b' }
237
237
  }
238
- ];
238
+ };
239
239
 
240
240
  const kubeconfig = createKubeconfig(
241
241
  {
@@ -243,12 +243,12 @@ describe('class Kubeconfig', () => {
243
243
  spec: { clusters: ['c-m-zebra', 'c-m-alpha', 'c-m-beta'] }
244
244
  },
245
245
  {
246
- 'management/all': (type: string) => {
247
- if (type === CAPI.RANCHER_CLUSTER) {
248
- return clusters;
246
+ 'management/byId': (type: string, id: string) => {
247
+ if (type === MANAGEMENT.CLUSTER) {
248
+ return clusters[id] || null;
249
249
  }
250
250
 
251
- return [];
251
+ return null;
252
252
  }
253
253
  }
254
254
  );
@@ -259,17 +259,17 @@ describe('class Kubeconfig', () => {
259
259
  });
260
260
 
261
261
  it('should sort numerically when names contain numbers', () => {
262
- const clusters = [
263
- {
264
- mgmt: { id: 'c-m-2' }, nameDisplay: 'cluster2', detailLocation: { name: 'c2' }
262
+ const clusters: Record<string, any> = {
263
+ 'c-m-2': {
264
+ id: 'c-m-2', nameDisplay: 'cluster2', detailLocation: { name: 'c2' }
265
265
  },
266
- {
267
- mgmt: { id: 'c-m-10' }, nameDisplay: 'cluster10', detailLocation: { name: 'c10' }
266
+ 'c-m-10': {
267
+ id: 'c-m-10', nameDisplay: 'cluster10', detailLocation: { name: 'c10' }
268
268
  },
269
- {
270
- mgmt: { id: 'c-m-1' }, nameDisplay: 'cluster1', detailLocation: { name: 'c1' }
269
+ 'c-m-1': {
270
+ id: 'c-m-1', nameDisplay: 'cluster1', detailLocation: { name: 'c1' }
271
271
  }
272
- ];
272
+ };
273
273
 
274
274
  const kubeconfig = createKubeconfig(
275
275
  {
@@ -277,12 +277,12 @@ describe('class Kubeconfig', () => {
277
277
  spec: { clusters: ['c-m-2', 'c-m-10', 'c-m-1'] }
278
278
  },
279
279
  {
280
- 'management/all': (type: string) => {
281
- if (type === CAPI.RANCHER_CLUSTER) {
282
- return clusters;
280
+ 'management/byId': (type: string, id: string) => {
281
+ if (type === MANAGEMENT.CLUSTER) {
282
+ return clusters[id] || null;
283
283
  }
284
284
 
285
- return [];
285
+ return null;
286
286
  }
287
287
  }
288
288
  );
@@ -295,14 +295,14 @@ describe('class Kubeconfig', () => {
295
295
 
296
296
  describe('referencedClustersSortable', () => {
297
297
  it('should return comma-separated lowercase labels', () => {
298
- const clusters = [
299
- {
300
- mgmt: { id: 'c-m-1' }, nameDisplay: 'Alpha', detailLocation: { name: 'a' }
298
+ const clusters: Record<string, any> = {
299
+ 'c-m-1': {
300
+ id: 'c-m-1', nameDisplay: 'Alpha', detailLocation: { name: 'a' }
301
301
  },
302
- {
303
- mgmt: { id: 'c-m-2' }, nameDisplay: 'Beta', detailLocation: { name: 'b' }
302
+ 'c-m-2': {
303
+ id: 'c-m-2', nameDisplay: 'Beta', detailLocation: { name: 'b' }
304
304
  }
305
- ];
305
+ };
306
306
 
307
307
  const kubeconfig = createKubeconfig(
308
308
  {
@@ -310,12 +310,12 @@ describe('class Kubeconfig', () => {
310
310
  spec: { clusters: ['c-m-1', 'c-m-2'] }
311
311
  },
312
312
  {
313
- 'management/all': (type: string) => {
314
- if (type === CAPI.RANCHER_CLUSTER) {
315
- return clusters;
313
+ 'management/byId': (type: string, id: string) => {
314
+ if (type === MANAGEMENT.CLUSTER) {
315
+ return clusters[id] || null;
316
316
  }
317
317
 
318
- return [];
318
+ return null;
319
319
  }
320
320
  }
321
321
  );
@@ -7,7 +7,7 @@ jest.mock('@shell/utils/clipboard', () => {
7
7
  describe('class MgmtCluster', () => {
8
8
  describe('provisioner', () => {
9
9
  const testCases = [
10
- [{ provider: 'rke', driver: 'imported' }, 'imported'],
10
+ [{ provider: 'rke2', driver: 'imported' }, 'rke2'],
11
11
  [{ provider: 'k3s', driver: 'K3S' }, 'K3S'],
12
12
  [{ provider: 'aks', driver: 'AKS' }, 'AKS'],
13
13
  [{}, 'imported'],