@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
@@ -660,12 +660,8 @@ export default {
660
660
  return getters.all(type);
661
661
  },
662
662
 
663
- // opt:
664
- // filter: Filter by fields, e.g. {field: value, anotherField: anotherValue} (default: none)
665
- // limit: Number of records to return per page (default: 1000)
666
- // sortBy: Sort by field
667
- // sortOrder: asc or desc
668
- // url: Use this specific URL instead of looking up the URL for the type/id. This should only be used for bootstrapping schemas on startup.
663
+ // opt: @ActionFindArgs
664
+ // @returns @ActionFindResponse
669
665
  // @TODO depaginate: If the response is paginated, retrieve all the pages. (default: true)
670
666
  async find(ctx, { type, id, opt }) {
671
667
  if (!id) {
@@ -697,11 +693,23 @@ export default {
697
693
  }
698
694
  }
699
695
 
696
+ const havePage = getters.havePage(type);
697
+
700
698
  opt = opt || {};
701
699
  opt.url = getters.urlFor(type, id, opt);
702
700
 
703
701
  const res = await dispatch('request', { opt, type });
704
702
 
703
+ if (!havePage && getters.havePage(type)) {
704
+ // There may be a super edge case where list --> detail (whilst loading) --> list navigation causes the list's rows to disappear
705
+ // Somehow the `findPage` from the list page returns before the `find`. The `find` then clears the page state in the cache.
706
+ // If this has happened silently return (we don't care about result)
707
+ // https://github.com/rancher/dashboard/issues/17524
708
+ console.warn(`Prevented \`find\` action from polluting cache for type "${ type }" (currently represents a page).`); // eslint-disable-line no-console
709
+
710
+ return;
711
+ }
712
+
705
713
  if (!opt.transient) {
706
714
  await dispatch('load', { data: res, invalidatePageCache: opt.invalidatePageCache });
707
715
  }
@@ -830,19 +838,33 @@ export default {
830
838
  /**
831
839
  * Remove all cached entries for a resource and stop watches
832
840
  */
833
- forgetType({ commit, dispatch, state }, type, compareWatches) {
834
- // Stop all known watches
835
- state.started
836
- .filter((entry) => compareWatches ? compareWatches(entry) : entry.type === type)
837
- .forEach((entry) => dispatch('unwatch', entry));
838
-
839
- // Stop all known back-off watch processes for this type
840
- dispatch('resetWatchBackOff', {
841
- type, compareWatches, resetStarted: false
842
- });
841
+ forgetType({ commit, dispatch, state }, payload) {
842
+ let type = payload;
843
+ let config = {};
844
+
845
+ if ( typeof payload === 'object' && payload !== null && payload.type ) {
846
+ type = payload.type;
847
+ config = payload;
848
+ }
849
+
850
+ const { compareWatches, unwatch = true, forget = true } = config;
843
851
 
844
- // Remove entries from store
845
- commit('forgetType', type);
852
+ if (unwatch) {
853
+ // Stop all known watches
854
+ state.started
855
+ .filter((entry) => compareWatches ? compareWatches(entry) : entry.type === type)
856
+ .forEach((entry) => dispatch('unwatch', entry));
857
+
858
+ // Stop all known back-off watch processes for this type
859
+ dispatch('resetWatchBackOff', {
860
+ type, compareWatches, resetStarted: false
861
+ });
862
+ }
863
+
864
+ if (forget) {
865
+ // Remove entries from store
866
+ commit('forgetType', type);
867
+ }
846
868
  },
847
869
 
848
870
  promptRemove({ commit, state }, resources ) {
@@ -946,12 +946,15 @@ export default class Resource {
946
946
  // where mostly likely extension CRD model is extending from resource-class
947
947
  const isResourceDetailDrawerCompatibleWithRancherSystem = semver.satisfies(parsedRancherVersion, '>= 2.13.0');
948
948
 
949
+ // If the resource can't show an edit or a yaml we don't want to show the configuration drawer
950
+ const showConfigEnabled = isResourceDetailDrawerCompatibleWithRancherSystem && this.disableResourceDetailDrawer !== true && (this.canCustomEdit || this.canYaml);
951
+
949
952
  const all = [
950
953
  {
951
954
  action: 'showConfiguration',
952
955
  label: this.t('action.showConfiguration'),
953
956
  icon: 'icon icon-document',
954
- enabled: isResourceDetailDrawerCompatibleWithRancherSystem && this.disableResourceDetailDrawer !== true && (this.canCustomEdit || this.canYaml), // If the resource can't show an edit or a yaml we don't want to show the configuration drawer
957
+ enabled: showConfigEnabled,
955
958
  },
956
959
  { divider: true },
957
960
  {
@@ -964,7 +967,7 @@ export default class Resource {
964
967
  action: this.canEditYaml ? 'goToEditYaml' : 'goToViewYaml',
965
968
  label: this.t(this.canEditYaml ? 'action.editYaml' : 'action.viewYaml'),
966
969
  icon: 'icon icon-file',
967
- enabled: this.canYaml,
970
+ enabled: this.canYaml && (this.canEditYaml || !showConfigEnabled), // Hide "View YAML" when "Show Configuration" is available since it already includes YAML viewing
968
971
  },
969
972
  {
970
973
  action: (this.canCustomEdit ? 'goToClone' : 'cloneYaml'),
@@ -14,7 +14,8 @@ describe('steve: subscribe', () => {
14
14
  schemaFor: () => null,
15
15
  inError: () => false,
16
16
  watchStarted: () => false,
17
- listenerManager: state.listenerManager
17
+ listenerManager: state.listenerManager,
18
+ typeRegistered: () => true,
18
19
  };
19
20
  const rootGetters = {
20
21
  'type-map/isSpoofed': () => false,
@@ -343,7 +344,7 @@ describe('steve: subscribe', () => {
343
344
 
344
345
  // call watch
345
346
  actions.watch({
346
- state, dispatch, getters, rootGetters
347
+ state, dispatch, getters, rootGetters, commit
347
348
  }, {
348
349
  ...obj,
349
350
  revision,
@@ -488,6 +489,7 @@ describe('steve: subscribe', () => {
488
489
  const state = {
489
490
  started: [],
490
491
  inError: {},
492
+ queue: [],
491
493
  listenerManager: new SteveWatchEventListenerManager()
492
494
  };
493
495
  const _getters = {
@@ -498,7 +500,8 @@ describe('steve: subscribe', () => {
498
500
  watchStarted: (...args) => getters.watchStarted(state)(...args),
499
501
  backOffId: (...args) => getters.backOffId()(...args),
500
502
  canBackoff: () => true,
501
- listenerManager: state.listenerManager
503
+ listenerManager: state.listenerManager,
504
+ typeRegistered: () => true,
502
505
  };
503
506
  const commit = (type, ...args) => mutations[type](state, ...args);
504
507
 
@@ -258,6 +258,11 @@ class StevePaginationUtils extends NamespaceProjectFilters {
258
258
  { field: 'spec.displayName' },
259
259
  { field: `status.provider` },
260
260
  { field: `status.connected` },
261
+ { field: `status.info.machineProvider` },
262
+ { field: `status.driver` },
263
+ { field: `status.provider` },
264
+ { field: `status.info.kubernetesVersion` },
265
+ { field: `spec.fleetWorkspaceName` },
261
266
  ],
262
267
  [SECRET]: [
263
268
  { field: `metadata.annotations[${ UI_PROJECT_SECRET_COPY }]` },
@@ -265,7 +270,10 @@ class StevePaginationUtils extends NamespaceProjectFilters {
265
270
  [NAMESPACE]: [
266
271
  ],
267
272
  [CAPI.MACHINE]: [
268
- { field: 'spec.clusterName' }
273
+ { field: 'spec.clusterName' },
274
+ ],
275
+ [CAPI.MACHINE_DEPLOYMENT]: [
276
+ { field: 'spec.clusterName' },
269
277
  ],
270
278
  [EVENT]: [
271
279
  { field: '_type' },
@@ -770,8 +778,8 @@ export const PAGINATION_SETTINGS_STORE_DEFAULTS: PaginationSettingsStores = {
770
778
  enableAll: false,
771
779
  enableSome: {
772
780
  enabled: [
773
- { resource: CAPI.RANCHER_CLUSTER, context: ['side-bar'] },
774
- { resource: MANAGEMENT.CLUSTER, context: ['side-bar'] },
781
+ { resource: CAPI.RANCHER_CLUSTER, context: ['side-bar', 'home', 'cluster-management'] },
782
+ { resource: MANAGEMENT.CLUSTER, context: ['side-bar', 'home', 'cluster-management'] },
775
783
  { resource: CATALOG.APP, context: ['branding'] },
776
784
  SECRET
777
785
  ],
@@ -469,8 +469,10 @@ const sharedActions = {
469
469
  const worker = (this.$workers || {})[getters.storeName];
470
470
 
471
471
  if (worker) {
472
+ const storeName = getters.storeName;
473
+
472
474
  worker.postMessage({ destroyWorker: true }); // we're only passing the boolean here because the key needs to be something truthy to ensure it's passed on the object.
473
- cleanupTasks.push(waitFor(() => !this.$workers[getters.storeName], 'Worker is destroyed'));
475
+ cleanupTasks.push(waitFor(() => !this.$workers?.[storeName], 'Worker to be destroyed', 30000, 10, true));
474
476
  }
475
477
 
476
478
  if ( socket ) {
@@ -553,17 +555,32 @@ const sharedActions = {
553
555
  * @param {STEVE_WATCH_PARAMS} params
554
556
  */
555
557
  watch({
556
- state, dispatch, getters, rootGetters
558
+ state, dispatch, getters, rootGetters, commit
557
559
  }, params) {
558
560
  state.debugSocket && console.info(`Watch Request [${ getters.storeName }]`, JSON.stringify(params)); // eslint-disable-line no-console
559
561
  let {
560
562
  // eslint-disable-next-line prefer-const
561
- type, selector, id, revision, namespace, stop, force, mode, standardWatch = true
563
+ type, selector, id, revision, namespace, stop, force, mode, standardWatch = true, registerType = false
562
564
  } = params;
563
565
 
564
566
  namespace = acceptOrRejectSocketMessage.subscribeNamespace(namespace);
565
567
  type = getters.normalizeType(type);
566
568
 
569
+ if ( !getters.typeRegistered(type) ) {
570
+ if (registerType) {
571
+ commit('registerType', type);
572
+ } else if (mode !== STEVE_WATCH_MODE.RESOURCE_CHANGES) {
573
+ // - If we continue and open up a watch whenever we receive a `resource.` notification we go to queueChanges (bar resource.changes mode).
574
+ // - queueChanges ignores any change that's for a type that hasn't been registered
575
+ // - So here we're just exiting early, avoiding the watch --> queueChanges --> ignore loop
576
+ //
577
+ // Interestingly this is hit quite a few times (on cluster create screens there's token, cluster, project, projectRoleTemplateBinding, etc)
578
+ state.debugSocket && console.info('Will not Watch (type is not registered)', JSON.stringify(params)); // eslint-disable-line no-console
579
+
580
+ return;
581
+ }
582
+ }
583
+
567
584
  if (rootGetters['type-map/isSpoofed'](type)) {
568
585
  state.debugSocket && console.info('Will not Watch (type is spoofed)', JSON.stringify(params)); // eslint-disable-line no-console
569
586
 
@@ -620,6 +637,9 @@ const sharedActions = {
620
637
  if (debounceMs) {
621
638
  msg.debounceMs = debounceMs;
622
639
  }
640
+
641
+ // Anything in the queue will pollute the result set, so clear (and print to console so we know it's working)
642
+ commit('clearFromQueue', { type, log: true });
623
643
  }
624
644
  }
625
645
 
@@ -1556,10 +1576,20 @@ const defaultMutations = {
1556
1576
  state.socketListenerManager = new SteveWatchEventListenerManager(state.config.namespace);
1557
1577
  },
1558
1578
 
1559
- clearFromQueue(state, type) {
1579
+ clearFromQueue(state, args) {
1580
+ const safeArgs = typeof args === 'object' ? args : { type: args };
1581
+ const { type, log } = safeArgs;
1582
+
1560
1583
  // Remove anything in the queue that is a resource update for the given type
1561
1584
  state.queue = state.queue.filter((item) => {
1562
- return item.body?.type !== type;
1585
+ const keep = item.body?.type !== type;
1586
+
1587
+ if (!keep && log) {
1588
+ // eslint-disable-next-line no-console
1589
+ console.info(`Clearing queued item of type \`${ type }\` from queue`, item);
1590
+ }
1591
+
1592
+ return keep;
1563
1593
  });
1564
1594
  },
1565
1595
  };
@@ -1,4 +1,4 @@
1
- import { defineComponent, nextTick } from 'vue';
1
+ import { defineComponent, nextTick, provide, ref } from 'vue';
2
2
  import { mount, flushPromises } from '@vue/test-utils';
3
3
  import { useForm } from 'vee-validate';
4
4
  import { LabeledInput } from './index';
@@ -195,21 +195,27 @@ describe('component: LabeledInput', () => {
195
195
 
196
196
  it('with name prop: form-level validation schema error is shown when the form validates', async() => {
197
197
  const errorMessage = 'Username is required';
198
+ const showAllErrors = ref(false);
198
199
  let triggerFormValidation!: () => Promise<unknown>;
199
200
 
200
201
  const TestWrapper = defineComponent({
201
202
  components: { LabeledInput },
202
203
  setup() {
204
+ provide('vee-show-all-errors', showAllErrors);
205
+
203
206
  const { validate } = useForm({
204
207
  validationSchema: { username: (v: string) => (!v ? errorMessage : true) },
205
- initialValues: { username: '' }
208
+ initialValues: { username: '' },
206
209
  });
207
210
 
208
- triggerFormValidation = validate;
211
+ triggerFormValidation = async() => {
212
+ await validate();
213
+ showAllErrors.value = true;
214
+ };
209
215
 
210
216
  return {};
211
217
  },
212
- template: '<LabeledInput name="username" value="" />'
218
+ template: '<LabeledInput name="username" value="" />',
213
219
  });
214
220
 
215
221
  const wrapper = mount(TestWrapper, { global: { mocks: { $store: { getters: { 'i18n/t': jest.fn() } } } } });
@@ -1,6 +1,5 @@
1
1
  <script lang="ts">
2
- import { defineComponent, inject, computed, watch } from 'vue';
3
- import { useField } from 'vee-validate';
2
+ import { defineComponent, inject, computed, toRef } from 'vue';
4
3
  import TextAreaAutoGrow from '@components/Form/TextArea/TextAreaAutoGrow.vue';
5
4
  import LabeledTooltip from '@components/LabeledTooltip/LabeledTooltip.vue';
6
5
  import { escapeHtml, generateRandomAlphaString } from '@shell/utils/string';
@@ -9,6 +8,7 @@ import { isValidCron } from 'cron-validator';
9
8
  import { debounce } from 'lodash';
10
9
  import { useLabeledFormElement, labeledFormElementProps } from '@shell/composables/useLabeledFormElement';
11
10
  import { useCompactInput } from '@shell/composables/useCompactInput';
11
+ import { useVeeValidateField } from '@shell/composables/useVeeValidateField';
12
12
 
13
13
  interface NonReactiveProps {
14
14
  onInput: (event: Event) => void | ((event: Event) => void);
@@ -142,56 +142,11 @@ export default defineComponent({
142
142
 
143
143
  const onInput = inject('onInput', provideProps.onInput);
144
144
 
145
- // Stable fallback name so useField is always called unconditionally.
146
- // When no name prop is given the field won't match any form-schema path.
147
- const standaloneFieldId = `__field__${ generateRandomAlphaString(12) }`;
148
- const veeFieldName = computed(() => props.name || standaloneFieldId);
149
-
150
- // Pass existing rules to vee-validate so field-level validation still runs
151
- const veeValidator = (value: unknown): boolean | string => {
152
- // Return true when name is absent to avoid duplicating
153
- // useLabeledFormElement validation
154
- if (!props.name) {
155
- return true;
156
- }
157
- for (const rule of props.rules as Array<(v: unknown) => string | undefined>) {
158
- const msg = rule(value);
159
-
160
- if (msg) {
161
- return msg;
162
- }
163
- }
164
-
165
- return true;
166
- };
167
-
168
- const {
169
- errorMessage: veeError,
170
- handleBlur: veeHandleBlur,
171
- validate: veeValidate,
172
- value: veeValue,
173
- } = useField<string | number | Record<string, unknown>>(
174
- veeFieldName,
175
- veeValidator,
176
- {
177
- initialValue: props.value as string | number,
178
- validateOnValueUpdate: true,
179
- }
180
- );
181
-
182
- // Keep vee-validate's internal value in sync with the controlled prop value.
183
- watch(() => props.value, (v) => {
184
- if (veeValue.value !== v) {
185
- veeValue.value = v as string | number;
186
- }
187
- });
188
-
189
- const effectiveValidationMessage = computed(() => {
190
- if (props.name && veeError.value) {
191
- return veeError.value;
192
- }
193
-
194
- return validationMessage.value;
145
+ const { effectiveValidationMessage, veeHandleBlur, veeValidate } = useVeeValidateField({
146
+ name: toRef(props, 'name'),
147
+ rules: toRef(props, 'rules'),
148
+ value: toRef(props, 'value'),
149
+ validationMessage,
195
150
  });
196
151
 
197
152
  const effectiveStatus = computed(() => props.status);
@@ -1,4 +1,4 @@
1
- import { mount } from '@vue/test-utils';
1
+ import { mount, RouterLinkStub } from '@vue/test-utils';
2
2
  import RcButton from './RcButton.vue';
3
3
 
4
4
  describe('rcButton.vue', () => {
@@ -138,4 +138,40 @@ describe('rcButton.vue', () => {
138
138
  expect(button.classes()).toContain('variant-ghost');
139
139
  });
140
140
  });
141
+
142
+ describe('to prop', () => {
143
+ it('renders as a <button> when no "to" prop is provided', () => {
144
+ const wrapper = mount(RcButton);
145
+
146
+ expect(wrapper.find('button').exists()).toBe(true);
147
+ expect(wrapper.findComponent(RouterLinkStub).exists()).toBe(false);
148
+ });
149
+
150
+ it('renders as a RouterLink when "to" prop is provided', () => {
151
+ const to = { name: 'some-route' };
152
+ const wrapper = mount(RcButton, {
153
+ props: { to },
154
+ global: { stubs: { RouterLink: RouterLinkStub } },
155
+ });
156
+
157
+ const link = wrapper.findComponent(RouterLinkStub);
158
+
159
+ expect(link.exists()).toBe(true);
160
+ expect(wrapper.find('button').exists()).toBe(false);
161
+ expect(link.props('to')).toStrictEqual(to);
162
+ });
163
+
164
+ it('applies button classes when rendered as a RouterLink', () => {
165
+ const wrapper = mount(RcButton, {
166
+ props: { to: '/foo', variant: 'secondary' },
167
+ global: { stubs: { RouterLink: RouterLinkStub } },
168
+ });
169
+
170
+ const link = wrapper.findComponent(RouterLinkStub);
171
+
172
+ expect(link.classes()).toContain('rc-button');
173
+ expect(link.classes()).toContain('btn');
174
+ expect(link.classes()).toContain('variant-secondary');
175
+ });
176
+ });
141
177
  });
@@ -7,10 +7,15 @@
7
7
  *
8
8
  * <rc-button variant="primary" @click="doAction">Perform an Action</rc-button>
9
9
  */
10
- import { computed, ref } from 'vue';
10
+ import { computed, ref, resolveComponent } from 'vue';
11
+ import type { RouteLocationRaw } from 'vue-router';
11
12
  import {
12
- ButtonVariantProps, ButtonSizeProps, ButtonVariantNewProps, ButtonSizeNewProps, ButtonSize,
13
- IconProps
13
+ ButtonVariantProps,
14
+ ButtonSizeProps,
15
+ ButtonVariantNewProps,
16
+ ButtonSizeNewProps,
17
+ ButtonSize,
18
+ IconProps,
14
19
  } from './types';
15
20
  import RcIcon from '@components/RcIcon/RcIcon.vue';
16
21
 
@@ -33,7 +38,22 @@ const buttonSizesNew: { size: ButtonSize, className: string }[] = [
33
38
  { size: 'large', className: 'btn-large' },
34
39
  ];
35
40
 
36
- const props = withDefaults(defineProps<ButtonVariantProps & ButtonSizeProps & ButtonVariantNewProps & ButtonSizeNewProps & IconProps>(), { size: 'medium' });
41
+ const props = withDefaults(
42
+ defineProps<
43
+ ButtonVariantProps &
44
+ ButtonSizeProps &
45
+ ButtonVariantNewProps &
46
+ ButtonSizeNewProps &
47
+ IconProps & { to?: RouteLocationRaw }
48
+ >(),
49
+ {
50
+ size: 'medium',
51
+ to: undefined,
52
+ }
53
+ );
54
+
55
+ const tag = computed(() => (props.to ? resolveComponent('RouterLink') : 'button'));
56
+ const role = computed(() => (props.to ? 'link' : 'button'));
37
57
 
38
58
  const activeVariantClassName = computed(() => {
39
59
  if (props.variant === 'multiAction' || props.multiAction) {
@@ -94,9 +114,11 @@ defineExpose({ focus });
94
114
  </script>
95
115
 
96
116
  <template>
97
- <button
117
+ <component
118
+ :is="tag"
98
119
  ref="RcFocusTarget"
99
- role="button"
120
+ :role="role"
121
+ :to="to"
100
122
  :class="{ ...buttonClass }"
101
123
  >
102
124
  <slot
@@ -124,15 +146,23 @@ defineExpose({ focus });
124
146
  size="inherit"
125
147
  />
126
148
  </slot>
127
- </button>
149
+ </component>
128
150
  </template>
129
151
 
130
152
  <style lang="scss" scoped>
131
- button {
153
+ .rc-button {
132
154
  display: inline-flex;
133
155
  align-items: center;
134
156
  justify-content: center;
135
157
 
158
+ // Override global .btn > .icon:not(:only-child) { margin-right: 6px } from _button.scss.
159
+ // RcButton uses flex gap for spacing instead. The :not(:only-child) clause matches the global
160
+ // selector so we win on specificity regardless of stylesheet load order; :deep() is needed to
161
+ // target slotted content.
162
+ & > :deep(.icon:not(:only-child)) {
163
+ margin-right: 0;
164
+ }
165
+
136
166
  // Much of the styling in here came from _button.scss. Because we're making changes from role to variant and we don't want to impact existing use cases we're pulling in some of these styles. We should in the long run deprecate that file.
137
167
  // Variant styles
138
168
  &.variant-primary {
@@ -35,18 +35,20 @@ defineExpose({ focus });
35
35
  @keydown.enter.space="handleKeydown"
36
36
  @click="showMenu(true)"
37
37
  >
38
- <template #before>
39
- <slot name="before">
40
- <!-- Empty Content -->
41
- </slot>
38
+ <template
39
+ v-if="$slots.before"
40
+ #before
41
+ >
42
+ <slot name="before" />
42
43
  </template>
43
44
  <slot name="default">
44
45
  <!--Empty slot content-->
45
46
  </slot>
46
- <template #after>
47
- <slot name="after">
48
- <!-- Empty Content -->
49
- </slot>
47
+ <template
48
+ v-if="$slots.after"
49
+ #after
50
+ >
51
+ <slot name="after" />
50
52
  </template>
51
53
  </RcButton>
52
54
  </template>
@@ -1,7 +1,7 @@
1
1
  import { CATALOG } from '@shell/config/types';
2
2
  import { CATALOG as CATALOG_ANNOTATIONS } from '@shell/config/labels-annotations';
3
3
  import {
4
- state, getters, actions, mutations, filterAndArrangeCharts
4
+ state, getters, actions, mutations, filterAndArrangeCharts, isRancherRepo, compatibleVersionsFor, getPermittedOSs
5
5
  } from '../catalog';
6
6
  import { createStore } from 'vuex';
7
7
 
@@ -509,4 +509,118 @@ describe('catalog', () => {
509
509
  });
510
510
  });
511
511
  });
512
+
513
+ describe('isRancherRepo', () => {
514
+ it('should return true if chart.isRancherRepo is true', () => {
515
+ const chart = { isRancherRepo: true } as any;
516
+
517
+ expect(isRancherRepo(null, chart)).toBe(true);
518
+ });
519
+
520
+ it('should return true if repo.isRancherSource is true', () => {
521
+ const repo = { isRancherSource: true } as any;
522
+
523
+ expect(isRancherRepo(repo, null)).toBe(true);
524
+ });
525
+
526
+ it('should return false if repo is not a rancher source', () => {
527
+ const repo = { isRancherSource: false } as any;
528
+
529
+ expect(isRancherRepo(repo, null)).toBe(false);
530
+ });
531
+ });
532
+
533
+ describe('compatibleVersionsFor', () => {
534
+ it('should allow versions if no OS constraint is provided', () => {
535
+ const chart = { versions: [{ version: '1.0.0' }] } as any;
536
+ const versions = compatibleVersionsFor(chart, undefined);
537
+
538
+ expect(versions).toHaveLength(1);
539
+ });
540
+
541
+ it('should allow windows nodes if permitted-os includes windows', () => {
542
+ const chart = {
543
+ versions: [{
544
+ version: '1.0.0',
545
+ annotations: { 'catalog.cattle.io/permits-os': 'linux,windows' }
546
+ }]
547
+ } as any;
548
+ const versions = compatibleVersionsFor(chart, 'windows');
549
+
550
+ expect(versions).toHaveLength(1);
551
+ });
552
+
553
+ it('should block windows nodes if permitted-os does not include windows', () => {
554
+ const chart = {
555
+ versions: [{
556
+ version: '1.0.0',
557
+ annotations: { 'catalog.cattle.io/permits-os': 'linux' }
558
+ }]
559
+ } as any;
560
+ const versions = compatibleVersionsFor(chart, 'windows');
561
+
562
+ expect(versions).toHaveLength(0);
563
+ });
564
+
565
+ it('should fallback to LINUX for rancher repos and block windows nodes', () => {
566
+ const chart = { isRancherRepo: true, versions: [{ version: '1.0.0' }] } as any;
567
+ const versions = compatibleVersionsFor(chart, 'windows');
568
+
569
+ expect(versions).toHaveLength(0);
570
+ });
571
+
572
+ it('should not fallback to LINUX for non-rancher repos and allow windows nodes', () => {
573
+ const chart = { isRancherRepo: false, versions: [{ version: '1.0.0' }] } as any;
574
+ const versions = compatibleVersionsFor(chart, 'windows');
575
+
576
+ expect(versions).toHaveLength(1);
577
+ });
578
+ });
579
+
580
+ describe('getPermittedOSs', () => {
581
+ it('should return explicitly permitted OSs when the annotation is present on a Rancher repo', () => {
582
+ const annotations = { [CATALOG_ANNOTATIONS.PERMITTED_OS]: 'linux,windows' };
583
+ const result = getPermittedOSs(annotations, true);
584
+
585
+ expect(result).toHaveLength(2);
586
+ expect(result).toContain('linux');
587
+ expect(result).toContain('windows');
588
+ });
589
+
590
+ it('should return explicitly permitted OSs when the annotation is present on a non-Rancher repo', () => {
591
+ const annotations = { [CATALOG_ANNOTATIONS.PERMITTED_OS]: 'linux' };
592
+ const result = getPermittedOSs(annotations, false);
593
+
594
+ expect(result).toHaveLength(1);
595
+ expect(result).toContain('linux');
596
+ });
597
+
598
+ it('should fallback to linux if the annotation is missing on a Rancher repo', () => {
599
+ const annotations = {};
600
+ const result = getPermittedOSs(annotations, true);
601
+
602
+ expect(result).toHaveLength(1);
603
+ expect(result).toContain('linux');
604
+ });
605
+
606
+ it('should return an empty array (no restrictions) if the annotation is missing on a non-Rancher repo', () => {
607
+ const annotations = {};
608
+ const result = getPermittedOSs(annotations, false);
609
+
610
+ expect(result).toHaveLength(0);
611
+ });
612
+
613
+ it('should handle undefined annotations safely for Rancher repos', () => {
614
+ const result = getPermittedOSs(undefined, true);
615
+
616
+ expect(result).toHaveLength(1);
617
+ expect(result).toContain('linux');
618
+ });
619
+
620
+ it('should handle undefined annotations safely for non-Rancher repos', () => {
621
+ const result = getPermittedOSs(undefined, false);
622
+
623
+ expect(result).toHaveLength(0);
624
+ });
625
+ });
512
626
  });