@rancher/shell 0.3.23 → 0.3.25

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 (149) hide show
  1. package/assets/styles/base/_variables.scss +1 -0
  2. package/assets/styles/themes/_dark.scss +1 -0
  3. package/assets/styles/themes/_light.scss +6 -5
  4. package/assets/translations/en-us.yaml +44 -17
  5. package/assets/translations/zh-hans.yaml +2 -2
  6. package/components/ClusterIconMenu.vue +143 -0
  7. package/components/CruResource.vue +7 -1
  8. package/components/ExplorerProjectsNamespaces.vue +11 -1
  9. package/components/FixedBanner.vue +17 -1
  10. package/components/Loading.vue +1 -1
  11. package/components/Markdown.vue +1 -1
  12. package/components/Questions/__tests__/Yaml.test.ts +3 -2
  13. package/components/SideNav.vue +1 -1
  14. package/components/SortableTable/index.vue +3 -2
  15. package/components/auth/RoleDetailEdit.vue +15 -2
  16. package/components/auth/login/saml.vue +12 -1
  17. package/components/form/LabeledSelect.vue +12 -5
  18. package/components/form/Members/ClusterPermissionsEditor.vue +1 -1
  19. package/components/form/Members/MembershipEditor.vue +6 -1
  20. package/components/form/SelectOrCreateAuthSecret.vue +7 -0
  21. package/components/form/__tests__/KeyValue.test.ts +6 -3
  22. package/components/form/__tests__/LabeledSelect.test.ts +18 -0
  23. package/components/formatter/PodsUsage.vue +11 -36
  24. package/components/formatter/PrincipalGroupBindings.vue +8 -5
  25. package/components/formatter/__tests__/PodsUsage.test.ts +36 -19
  26. package/components/nav/Group.vue +62 -34
  27. package/components/nav/Header.vue +13 -6
  28. package/components/nav/Pinned.vue +47 -0
  29. package/components/nav/TopLevelMenu.vue +673 -325
  30. package/components/nav/Type.vue +88 -8
  31. package/config/home-links.js +1 -1
  32. package/config/product/istio.js +15 -5
  33. package/config/router.js +3 -9
  34. package/config/table-headers.js +5 -6
  35. package/config/uiplugins.js +1 -0
  36. package/core/plugin-helpers.js +3 -0
  37. package/core/types.ts +6 -1
  38. package/creators/app/files/.vscode/settings.json +0 -1
  39. package/creators/pkg/init +2 -2
  40. package/detail/__tests__/autoscaling.horizontalpodautoscaler.test.ts +118 -0
  41. package/detail/autoscaling.horizontalpodautoscaler/index.vue +4 -4
  42. package/detail/provisioning.cattle.io.cluster.vue +7 -5
  43. package/edit/__tests__/management.cattle.io.clusterroletemplatebinding.test.ts +58 -0
  44. package/edit/__tests__/namespace.test.ts +5 -3
  45. package/edit/fleet.cattle.io.gitrepo.vue +43 -15
  46. package/edit/logging.banzaicloud.io.output/index.vue +7 -0
  47. package/edit/management.cattle.io.clusterroletemplatebinding.vue +3 -11
  48. package/edit/namespace.vue +8 -4
  49. package/edit/provisioning.cattle.io.cluster/Basics.vue +662 -0
  50. package/edit/provisioning.cattle.io.cluster/CustomCommand.vue +9 -8
  51. package/edit/provisioning.cattle.io.cluster/DrainOptions.vue +13 -8
  52. package/edit/provisioning.cattle.io.cluster/MachinePool.vue +11 -2
  53. package/edit/provisioning.cattle.io.cluster/MemberRoles.vue +40 -0
  54. package/edit/provisioning.cattle.io.cluster/__tests__/Basics.tests.ts +237 -0
  55. package/edit/provisioning.cattle.io.cluster/__tests__/CustomCommand.tests.ts +71 -23
  56. package/edit/provisioning.cattle.io.cluster/__tests__/DrainOptions.test.ts +52 -0
  57. package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +65 -142
  58. package/edit/provisioning.cattle.io.cluster/rke2.vue +253 -582
  59. package/edit/workload/storage/ContainerMountPaths.vue +7 -5
  60. package/edit/workload/storage/__tests__/Storage.test.ts +2 -2
  61. package/edit/workload/storage/persistentVolumeClaim/__tests__/persistentvolumeclaim.test.ts +36 -0
  62. package/edit/workload/storage/persistentVolumeClaim/persistentvolumeclaim.vue +15 -7
  63. package/initialize/App.js +2 -0
  64. package/initialize/client.js +63 -51
  65. package/initialize/index.js +7 -5
  66. package/layouts/default.vue +10 -2
  67. package/layouts/home.vue +6 -2
  68. package/layouts/plain.vue +9 -2
  69. package/list/fleet.cattle.io.cluster.vue +2 -2
  70. package/list/management.cattle.io.feature.vue +1 -1
  71. package/machine-config/amazonec2.vue +1 -0
  72. package/machine-config/vmwarevsphere.vue +48 -7
  73. package/mixins/brand.js +0 -8
  74. package/mixins/child-hook.js +2 -2
  75. package/mixins/create-edit-view/impl.js +3 -3
  76. package/mixins/fetch.client.js +3 -3
  77. package/models/__tests__/management.cattle.io.node.ts +96 -0
  78. package/models/__tests__/node.ts +74 -0
  79. package/models/cluster/node.js +6 -5
  80. package/models/cluster.x-k8s.io.machinedeployment.js +2 -2
  81. package/models/management.cattle.io.cluster.js +22 -1
  82. package/models/management.cattle.io.clusterroletemplatebinding.js +3 -3
  83. package/models/management.cattle.io.globalrole.js +17 -2
  84. package/models/management.cattle.io.node.js +6 -4
  85. package/models/management.cattle.io.projectroletemplatebinding.js +3 -3
  86. package/models/management.cattle.io.roletemplate.js +17 -2
  87. package/package.json +2 -6
  88. package/pages/__tests__/prefs.test.ts +1 -1
  89. package/pages/about.vue +2 -0
  90. package/pages/auth/setup.vue +5 -4
  91. package/pages/c/_cluster/explorer/ConfigBadge.vue +1 -0
  92. package/pages/c/_cluster/monitoring/index.vue +8 -3
  93. package/pages/c/_cluster/uiplugins/CatalogList/CatalogLoadDialog.vue +9 -66
  94. package/pages/c/_cluster/uiplugins/CatalogList/CatalogUninstallDialog.vue +182 -0
  95. package/pages/c/_cluster/uiplugins/CatalogList/index.vue +15 -32
  96. package/pages/c/_cluster/uiplugins/UninstallDialog.vue +8 -46
  97. package/pages/c/_cluster/uiplugins/index.vue +64 -64
  98. package/pages/diagnostic.vue +0 -39
  99. package/pages/home.vue +1 -1
  100. package/pages/prefs.vue +3 -13
  101. package/plugins/dashboard-store/normalize.js +4 -4
  102. package/plugins/dashboard-store/resource-class.js +1 -1
  103. package/plugins/int-number.js +5 -2
  104. package/plugins/positive-int-number.js +19 -0
  105. package/plugins/steve/__tests__/getters.spec.ts +15 -0
  106. package/plugins/steve/getters.js +22 -10
  107. package/public/index.html +4 -2
  108. package/rancher-components/BadgeState/BadgeState.vue +5 -1
  109. package/rancher-components/Banner/Banner.test.ts +51 -1
  110. package/rancher-components/Banner/Banner.vue +134 -53
  111. package/rancher-components/Card/Card.test.ts +37 -0
  112. package/rancher-components/Card/Card.vue +24 -7
  113. package/rancher-components/Form/Checkbox/Checkbox.test.ts +20 -29
  114. package/rancher-components/Form/Checkbox/Checkbox.vue +45 -20
  115. package/rancher-components/Form/LabeledInput/LabeledInput.test.ts +2 -8
  116. package/rancher-components/Form/LabeledInput/LabeledInput.vue +22 -10
  117. package/rancher-components/Form/Radio/RadioButton.test.ts +31 -0
  118. package/rancher-components/Form/Radio/RadioButton.vue +30 -13
  119. package/rancher-components/Form/Radio/RadioGroup.vue +26 -7
  120. package/rancher-components/Form/TextArea/TextAreaAutoGrow.vue +7 -6
  121. package/rancher-components/Form/ToggleSwitch/ToggleSwitch.test.ts +25 -38
  122. package/rancher-components/Form/ToggleSwitch/ToggleSwitch.vue +23 -11
  123. package/rancher-components/LabeledTooltip/LabeledTooltip.vue +19 -5
  124. package/rancher-components/StringList/StringList.test.ts +453 -49
  125. package/rancher-components/StringList/StringList.vue +92 -58
  126. package/scripts/extension/parse-tag-name +0 -0
  127. package/store/index.js +4 -0
  128. package/store/prefs.js +4 -4
  129. package/store/type-map.js +2 -16
  130. package/types/shell/index.d.ts +26 -14
  131. package/utils/__tests__/cluster.test.ts +55 -0
  132. package/utils/__tests__/object.test.ts +21 -2
  133. package/utils/__tests__/sort.test.ts +61 -0
  134. package/utils/cluster.js +47 -1
  135. package/utils/object.js +12 -5
  136. package/utils/string.js +12 -0
  137. package/utils/validators/formRules/__tests__/index.test.ts +13 -1
  138. package/utils/validators/formRules/index.ts +4 -0
  139. package/utils/validators/role-template.js +9 -1
  140. package/utils/version.js +1 -1
  141. package/vue.config.js +1 -4
  142. package/yarn-error.log +200 -0
  143. package/content/docs/en-us/getting-started.md +0 -224
  144. package/content/docs/en-us/whats-new.md +0 -29
  145. package/content/docs/zh-hans/getting-started.md +0 -224
  146. package/content/docs/zh-hans/whats-new.md +0 -28
  147. package/pages/docs/_doc.vue +0 -345
  148. package/pages/docs/toc.js +0 -27
  149. package/plugins/console.js +0 -34
@@ -1,6 +1,7 @@
1
1
  <script>
2
2
  import Favorite from '@shell/components/nav/Favorite';
3
3
  import { FAVORITE, USED } from '@shell/store/type-map';
4
+ import { linkActiveClass } from '@shell/config/router';
4
5
 
5
6
  const showFavoritesFor = [FAVORITE, USED];
6
7
 
@@ -27,12 +28,65 @@ export default {
27
28
 
28
29
  data() {
29
30
  return {
30
- near: false,
31
- over: false,
31
+ near: false,
32
+ over: false,
33
+ menuPath: this.type.route ? this.$router.resolve(this.type.route)?.route?.path : undefined,
34
+ linkActiveClass
32
35
  };
33
36
  },
34
37
 
35
38
  computed: {
39
+ isCurrent() {
40
+ // This is required to avoid scenarios where fragments break vue routers location matching
41
+ // For example, the following fails
42
+ // Curruent Path /c/c-m-hzqf4tqt/explorer/members#project-membership
43
+ // Menu Path /c/c-m-hzqf4tqt/explorer/members
44
+ // vue-router exact-path="true" fixes this (https://v3.router.vuejs.org/api/#exact-path),
45
+ // but fails when the the current path is a child (for instance a resource detail page)
46
+
47
+ // Scenarios to consider
48
+ // - Fragement world
49
+ // Curruent Path /c/c-m-hzqf4tqt/explorer/members#project-membership
50
+ // Menu Path /c/c-m-hzqf4tqt/explorer/members
51
+ // - Similar current paths
52
+ // /c/c-m-hzqf4tqt/fleet/fleet.cattle.io.bundlenamespacemapping
53
+ // /c/c-m-hzqf4tqt/fleet/fleet.cattle.io.bundle
54
+ // - Other menu items that appear in current menu item
55
+ // /c/c-m-hzqf4tqt/fleet
56
+ // /c/c-m-hzqf4tqt/fleet/management.cattle.io.fleetworkspace
57
+
58
+ // If there's no hash the n-link will determine it's linkActiveClass correctly, so avoid this faff
59
+ const invalidHash = !this.$route.hash;
60
+ // Lets be super safe
61
+ const invalidProps = !this.menuPath || !this.$route.path;
62
+
63
+ if (invalidHash || invalidProps) {
64
+ return false;
65
+ }
66
+
67
+ // We're kind of, but in a fixing way, copying n-link --> vue-router link see vue-router/src/components/link.js & vue-router/src/util/route.js
68
+ // We're only going to compare the path and ignore query and fragment
69
+
70
+ if (this.type.exact) {
71
+ return this.$route.path === this.menuPath;
72
+ }
73
+
74
+ const currentPath = this.$route.path.split('/');
75
+ const menuPath = this.menuPath.split('/');
76
+
77
+ if (menuPath.length > currentPath.length) {
78
+ return false;
79
+ }
80
+
81
+ for (let i = 0; i < menuPath.length; i++) {
82
+ if (menuPath[i] !== currentPath[i]) {
83
+ return false;
84
+ }
85
+ }
86
+
87
+ return true;
88
+ },
89
+
36
90
  showFavorite() {
37
91
  return ( this.type.mode && this.near && showFavoritesFor.includes(this.type.mode) );
38
92
  },
@@ -40,6 +94,10 @@ export default {
40
94
  showCount() {
41
95
  return typeof this.type.count !== 'undefined';
42
96
  },
97
+
98
+ namespaceIcon() {
99
+ return this.type.namespaced;
100
+ },
43
101
  },
44
102
 
45
103
  methods: {
@@ -76,7 +134,7 @@ export default {
76
134
  :to="type.route"
77
135
  tag="li"
78
136
  class="child nav-type"
79
- :class="{'root': isRoot, [`depth-${depth}`]: true}"
137
+ :class="{'root': isRoot, [`depth-${depth}`]: true, [linkActiveClass]: isCurrent}"
80
138
  :exact="type.exact"
81
139
  >
82
140
  <a
@@ -102,6 +160,10 @@ export default {
102
160
  v-if="showFavorite"
103
161
  :resource="type.name"
104
162
  />
163
+ <i
164
+ v-if="namespaceIcon"
165
+ class="icon icon-namespace namespaced"
166
+ />
105
167
  {{ type.count }}
106
168
  </span>
107
169
  </a>
@@ -127,13 +189,16 @@ export default {
127
189
  </template>
128
190
 
129
191
  <style lang="scss" scoped>
192
+ .namespaced {
193
+ margin-right: 4px;
194
+ }
195
+
130
196
  .child {
131
197
  margin: 0 var(--outline) 0 0;
132
198
 
133
199
  .label {
134
200
  align-items: center;
135
201
  grid-area: label;
136
- display: flex;
137
202
  overflow: hidden;
138
203
  text-overflow: ellipsis;
139
204
 
@@ -166,6 +231,7 @@ export default {
166
231
  text-overflow: ellipsis;
167
232
  white-space: nowrap;
168
233
  color: var(--body-text);
234
+ height: 33px;
169
235
 
170
236
  &:hover {
171
237
  background: var(--nav-hover);
@@ -181,26 +247,40 @@ export default {
181
247
  grid-area: favorite;
182
248
  font-size: 12px;
183
249
  position: relative;
250
+ vertical-align: middle;
251
+ margin-right: 4px;
184
252
  }
185
253
 
186
254
  .count {
187
- grid-area: count;
188
255
  font-size: 12px;
189
- text-align: right;
190
256
  justify-items: center;
191
257
  padding-right: 4px;
258
+ display: flex;
259
+ align-items: center;
192
260
  }
193
261
 
194
262
  &.nav-type:not(.depth-0) {
195
263
  A {
196
- font-size: 13px;
197
- padding: 5.5px 7px 5.5px 10px;
264
+ padding-left: 16px;
198
265
  }
199
266
 
200
267
  ::v-deep .label I {
201
268
  padding-right: 2px;
202
269
  }
203
270
  }
271
+
272
+ &.nav-type:is(.depth-1) {
273
+ A {
274
+ font-size: 13px;
275
+ padding-left: 23px;
276
+ }
277
+ }
278
+
279
+ &.nav-type:not(.depth-0):not(.depth-1) {
280
+ A {
281
+ padding-left: 14px;
282
+ }
283
+ }
204
284
  }
205
285
 
206
286
  </style>
@@ -26,7 +26,7 @@ const DEFAULT_LINKS = [
26
26
  },
27
27
  {
28
28
  key: 'getStarted',
29
- value: '/docs/getting-started',
29
+ value: 'https://ranchermanager.docs.rancher.com/getting-started/overview',
30
30
  enabled: true,
31
31
  },
32
32
  ];
@@ -1,4 +1,4 @@
1
- import { AGE, NAME as NAME_HEADER, STATE } from '@shell/config/table-headers';
1
+ import { AGE, NAME as NAME_HEADER, NAMESPACE as NAMESPACE_HEADER, STATE } from '@shell/config/table-headers';
2
2
  import { ISTIO } from '@shell/config/types';
3
3
  import { DSL, IF_HAVE } from '@shell/store/type-map';
4
4
 
@@ -14,9 +14,10 @@ export function init(store) {
14
14
  } = DSL(store, NAME);
15
15
 
16
16
  product({
17
- ifHaveGroup: /^(.*\.)*istio\.io$/,
18
- ifHave: IF_HAVE.NOT_V1_ISTIO,
19
- icon: 'istio',
17
+ ifHaveGroup: /^(.*\.)*istio\.io$/,
18
+ ifHave: IF_HAVE.NOT_V1_ISTIO,
19
+ icon: 'istio',
20
+ showNamespaceFilter: true,
20
21
  });
21
22
 
22
23
  virtualType({
@@ -42,7 +43,9 @@ export function init(store) {
42
43
  'networking.istio.io.envoyfilter',
43
44
  'networking.istio.io.serviceentry',
44
45
  'networking.istio.io.sidecar',
45
- 'networking.istio.io.workloadentrie',
46
+ 'networking.istio.io.proxyconfig',
47
+ 'networking.istio.io.workloadentry',
48
+ 'networking.istio.io.workloadgroup',
46
49
  ], 'Networking');
47
50
 
48
51
  basicType([
@@ -58,9 +61,16 @@ export function init(store) {
58
61
  'security.istio.io.requestauthentication',
59
62
  ], 'Security');
60
63
 
64
+ basicType([
65
+ 'install.istio.io.istiooperator',
66
+ 'telemetry.istio.io.telemetry',
67
+ 'extensions.istio.io.wasmplugin',
68
+ ], 'Advanced');
69
+
61
70
  headers(ISTIO.VIRTUAL_SERVICE, [
62
71
  STATE,
63
72
  NAME_HEADER,
73
+ NAMESPACE_HEADER,
64
74
  {
65
75
  name: 'gateways',
66
76
  label: 'Gateways',
package/config/router.js CHANGED
@@ -6,13 +6,15 @@ import scrollBehavior from '../utils/router.scrollBehavior.js';
6
6
 
7
7
  const emptyFn = () => {};
8
8
 
9
+ export const linkActiveClass = 'nuxt-link-active';
10
+
9
11
  Vue.use(Router);
10
12
 
11
13
  export const routerOptions = {
12
14
  mode: 'history',
13
15
  // Note: router base comes from the ROUTER_BASE env var
14
16
  base: process.env.routerBase || '/',
15
- linkActiveClass: 'nuxt-link-active',
17
+ linkActiveClass,
16
18
  linkExactActiveClass: 'nuxt-link-exact-active',
17
19
  scrollBehavior,
18
20
 
@@ -72,10 +74,6 @@ export const routerOptions = {
72
74
  path: '/auth/verify',
73
75
  component: () => interopDefault(import('../pages/auth/verify.vue' /* webpackChunkName: "pages/auth/verify" */)),
74
76
  name: 'auth-verify'
75
- }, {
76
- path: '/docs/toc',
77
- component: () => interopDefault(import('../pages/docs/toc.js' /* webpackChunkName: "pages/docs/toc" */)),
78
- name: 'docs-toc'
79
77
  }, {
80
78
  path: '/rio/mesh',
81
79
  component: () => interopDefault(import('../pages/rio/mesh.vue' /* webpackChunkName: "pages/rio/mesh" */)),
@@ -84,10 +82,6 @@ export const routerOptions = {
84
82
  path: '/c/:cluster',
85
83
  component: () => interopDefault(import('../pages/c/_cluster/index.vue' /* webpackChunkName: "pages/c/_cluster/index" */)),
86
84
  name: 'c-cluster'
87
- }, {
88
- path: '/docs/:doc?',
89
- component: () => interopDefault(import('../pages/docs/_doc.vue' /* webpackChunkName: "pages/docs/_doc" */)),
90
- name: 'docs-doc'
91
85
  }, {
92
86
  path: '/c/:cluster/apps',
93
87
  component: () => interopDefault(import('../pages/c/_cluster/apps/index.vue' /* webpackChunkName: "pages/c/_cluster/apps/index" */)),
@@ -993,13 +993,12 @@ export const UI_PLUGIN_CATALOG = [
993
993
  name: 'image',
994
994
  sort: ['image'],
995
995
  labelKey: 'plugins.manageCatalog.headers.image.label',
996
- value: 'deploymentImage'
996
+ value: 'image'
997
997
  },
998
998
  {
999
- name: 'cacheState',
1000
- sort: ['cacheState'],
1001
- labelKey: 'plugins.manageCatalog.headers.cacheState.label',
1002
- value: 'cacheState',
1003
- formatter: 'ExtensionCache'
999
+ name: 'repository',
1000
+ sort: ['repository'],
1001
+ labelKey: 'plugins.manageCatalog.headers.repository.label',
1002
+ value: 'repo.metadata.name'
1004
1003
  }
1005
1004
  ];
@@ -49,6 +49,7 @@ export const UI_PLUGIN_CHART_ANNOTATIONS = {
49
49
  UI_VERSION: 'catalog.cattle.io/ui-version',
50
50
  EXTENSIONS_HOST: 'catalog.cattle.io/ui-extensions-host',
51
51
  DISPLAY_NAME: 'catalog.cattle.io/display-name',
52
+ HIDDEN_BUILTIN: 'catalog.cattle.io/ui-hidden-builtin',
52
53
  };
53
54
 
54
55
  // Extension catalog labels
@@ -85,6 +85,9 @@ function checkExtensionRouteBinding($route, locationConfig, context) {
85
85
  // also handle "mode" in a separate way because it mainly depends on query params
86
86
  } else if (param === 'mode') {
87
87
  res = checkRouteMode($route, locationConfigParam);
88
+ } else if (param === 'resource') {
89
+ // Match exact resource but also allow resource of '*' to match any resource
90
+ res = (params[param] && locationConfigParam === '*') || locationConfigParam === params[param];
88
91
  } else if (param === 'context') {
89
92
  // Need all keys and values to match
90
93
  res = isEqual(locationConfigParam, context);
package/core/types.ts CHANGED
@@ -102,7 +102,7 @@ export type Action = {
102
102
  icon?: string;
103
103
  multiple?: boolean;
104
104
  enabled?: Function | boolean;
105
- invoke: (opts: ActionOpts, resources: any[]) => void | boolean | Promise<boolean>;
105
+ invoke: (opts: ActionOpts, resources: any[], globals?: any) => void | boolean | Promise<boolean>;
106
106
  };
107
107
 
108
108
  /** Definition of a panel (options that can be passed when defining an extension panel enhancement) */
@@ -567,3 +567,8 @@ export interface IPlugin {
567
567
  */
568
568
  DSL(store: any, productName: string): DSLReturnType;
569
569
  }
570
+
571
+ // Internal interface
572
+ // Built-in extensions may use this, but external extensions should not, as this is subject to change
573
+ // Defined as any for now
574
+ export type IInternal = any;
@@ -10,7 +10,6 @@
10
10
  ".gitignore": true,
11
11
  ".nuxt*": true,
12
12
  ".nyc_output": true,
13
- "Dockerfile": true,
14
13
  ".vscode": true,
15
14
  "LICENSE": true,
16
15
  "node_modules": true,
package/creators/pkg/init CHANGED
@@ -174,8 +174,8 @@ if (addWorkflowFolder) {
174
174
  }
175
175
 
176
176
  const files = [
177
- 'build-extension.yml',
178
- 'build-container.yml'
177
+ 'build-extension-catalog.yml',
178
+ 'build-extension-charts.yml'
179
179
  ];
180
180
 
181
181
  files.forEach((fileName) => {
@@ -0,0 +1,118 @@
1
+ import { shallowMount } from '@vue/test-utils';
2
+ import HorizontalPodAutoScaler from '@shell/detail/autoscaling.horizontalpodautoscaler/index.vue';
3
+
4
+ describe('view: autoscaling.horizontalpodautoscaler', () => {
5
+ const mockStore = {
6
+ getters: {
7
+ 'i18n/t': (text: string) => text,
8
+ t: (text: string) => text,
9
+ currentStore: () => 'current_store',
10
+ 'current_store/schemaFor': jest.fn(),
11
+ 'current_store/all': jest.fn(),
12
+ workspace: jest.fn(),
13
+ },
14
+ };
15
+
16
+ const mocks = {
17
+ $store: mockStore,
18
+ $fetchState: { pending: false },
19
+ $route: {
20
+ query: { AS: '' },
21
+ name: {
22
+ endsWith: () => {
23
+ return false;
24
+ },
25
+ },
26
+ },
27
+ };
28
+
29
+ const value = {
30
+ status: {
31
+ currentMetrics: [
32
+ {
33
+ resource: {
34
+ current: {
35
+ averageUtilization: 8,
36
+ averageValue: '11481088'
37
+ },
38
+ name: 'memory'
39
+ },
40
+ type: 'Resource'
41
+ },
42
+ {
43
+ resource: {
44
+ current: {
45
+ averageUtilization: 0,
46
+ averageValue: '1m'
47
+ },
48
+ name: 'cpu'
49
+ },
50
+ type: 'Resource'
51
+ }
52
+ ],
53
+ },
54
+ spec: {
55
+ maxReplicas: 10,
56
+ metrics: [
57
+ {
58
+ resource: {
59
+ name: 'memory',
60
+ target: {
61
+ averageUtilization: 80,
62
+ type: 'Utilization'
63
+ }
64
+ },
65
+ type: 'Resource'
66
+ },
67
+ {
68
+ resource: {
69
+ name: 'cpu',
70
+ target: {
71
+ averageUtilization: 50,
72
+ type: 'Utilization'
73
+ }
74
+ },
75
+ type: 'Resource'
76
+ }
77
+ ],
78
+ minReplicas: 1,
79
+ scaleTargetRef: {
80
+ apiVersion: 'apps/v1',
81
+ kind: 'Deployment',
82
+ name: 'php-apache'
83
+ }
84
+ }
85
+ };
86
+
87
+ const metricsValue = Object.values(value.spec.metrics);
88
+ const currentMetrics = Object.values(value.status.currentMetrics);
89
+
90
+ const wrapper = shallowMount(HorizontalPodAutoScaler, {
91
+ mocks,
92
+ propsData: { value },
93
+ });
94
+
95
+ describe.each(value.spec.metrics)('should display metrics for each resource:', (metric) => {
96
+ const name = metric.resource.name;
97
+
98
+ it(`${ name }:`, () => {
99
+ // Resource metrics
100
+ const resourceValue = wrapper.find(`[data-testid="resource-metrics-value-${ name }"]`);
101
+ const resourceName = wrapper.find(`[data-testid="resource-metrics-name-${ name }"]`);
102
+ const metricValue = metricsValue.find((f) => f.resource.name === name)?.resource;
103
+
104
+ // Current Metrics
105
+ const averageUtilization = wrapper.find(`[data-testid="current-metrics-Average Utilization-${ name }"]`);
106
+ const averageValue = wrapper.find(`[data-testid="current-metrics-Average Value-${ name }"]`);
107
+ const currentResource = currentMetrics.find((f) => f.resource.name === name)?.resource.current;
108
+
109
+ // Resource metrics
110
+ expect(resourceValue.element.textContent).toBe(`${ metricValue?.target?.averageUtilization }`);
111
+ expect(resourceName.element.textContent).toBe(`${ metricValue?.name }`);
112
+
113
+ // Current Metrics
114
+ expect(averageUtilization.element.textContent).toBe(`${ currentResource?.averageUtilization }`);
115
+ expect(averageValue.element.textContent).toBe(`${ currentResource?.averageValue }`);
116
+ });
117
+ });
118
+ });
@@ -43,7 +43,7 @@ export default {
43
43
  return metrics.map((metric) => {
44
44
  const metricValue = get(metric, camelCase(metric.type));
45
45
  const targetType = metricValue?.target?.type;
46
- const currentMatch = findBy(currentMetrics, 'type', metric.type);
46
+ const currentMatch = findBy(currentMetrics, 'resource.name', metric.resource.name);
47
47
  const current = currentMatch ? get(currentMatch, `${ camelCase(metric.type) }.current`) : null;
48
48
  const currentMetricsKVs = [];
49
49
 
@@ -128,7 +128,7 @@ export default {
128
128
  <label class="text-label">
129
129
  <t k="hpa.metrics.headers.value" />:
130
130
  </label>
131
- <span>{{ metric.targetValue }}</span>
131
+ <span :data-testid="`resource-metrics-value-${metric.subRowContent.resourceName}`">{{ metric.targetValue }}</span>
132
132
  </div>
133
133
  <div v-if="metric.metricSource === 'Object'">
134
134
  <div class="mb-5">
@@ -155,7 +155,7 @@ export default {
155
155
  <label class="text-label">
156
156
  <t k="hpa.metrics.headers.resource" />:
157
157
  </label>
158
- <span>{{ metric.subRowContent.resourceName }}</span>
158
+ <span :data-testid="`resource-metrics-name-${metric.subRowContent.resourceName}`">{{ metric.subRowContent.resourceName }}</span>
159
159
  </div>
160
160
  </div>
161
161
  </div>
@@ -173,7 +173,7 @@ export default {
173
173
  <label class="text-label">
174
174
  {{ current.targetName }}:
175
175
  </label>
176
- <span>{{ current.targetValue }}</span>
176
+ <span :data-testid="`current-metrics-${current.targetName}-${metric.subRowContent.resourceName}`">{{ current.targetValue }}</span>
177
177
  </div>
178
178
  </div>
179
179
  </div>
@@ -339,9 +339,10 @@ export default {
339
339
  pool._clusterSpec = mp;
340
340
 
341
341
  return {
342
- poolId: pool.id,
343
- mainRowKey: 'isFake',
342
+ poolId: pool.id,
343
+ mainRowKey: 'isFake',
344
344
  pool,
345
+ availableActions: []
345
346
  };
346
347
  });
347
348
  },
@@ -361,9 +362,10 @@ export default {
361
362
  const emptyNodePools = this.allNodePools.filter((x) => x.spec.clusterName === this.value.mgmtClusterId && x.spec.quantity === 0);
362
363
 
363
364
  return emptyNodePools.map((np) => ({
364
- spec: { nodePoolName: np.id.replace('/', ':') },
365
- mainRowKey: 'isFake',
366
- pool: np,
365
+ spec: { nodePoolName: np.id.replace('/', ':') },
366
+ mainRowKey: 'isFake',
367
+ pool: np,
368
+ availableActions: []
367
369
  }));
368
370
  },
369
371
 
@@ -0,0 +1,58 @@
1
+ /* eslint-disable jest/no-hooks */
2
+ import { mount } from '@vue/test-utils';
3
+ import ClusterRoleTemplateBinding from '@shell/edit/management.cattle.io.clusterroletemplatebinding.vue';
4
+ import Banner from '@components/Banner/Banner.vue';
5
+ import CruResource from '@shell/components/CruResource';
6
+
7
+ describe('view: management.cattle.io.clusterroletemplatebinding should', () => {
8
+ let wrapper: any;
9
+
10
+ const stubs = {
11
+ ClusterPermissionsEditor: true,
12
+ Loading: true
13
+ };
14
+
15
+ const requiredSetup = () => ({
16
+ // Remove all these mocks after migration to Vue 2.7/3 due mixin logic
17
+ mocks: {
18
+ $store: {
19
+ getters: {
20
+ currentStore: () => 'current_store',
21
+ 'current_store/schemaFor': jest.fn(),
22
+ 'current_store/all': jest.fn(),
23
+ currentCluster: { id: 'my-cluster' },
24
+ currentProduct: { inStore: 'whatever' },
25
+ 'i18n/t': (val) => val,
26
+ 'i18n/exists': jest.fn(),
27
+ },
28
+ dispatch: { 'management/findAll': () => ([]) }
29
+ },
30
+ $fetchState: { pending: false },
31
+ $route: { query: { AS: '' } },
32
+ $router: {
33
+ applyQuery: jest.fn(),
34
+ replace: jest.fn()
35
+ },
36
+ },
37
+ propsData: { value: {} },
38
+ stubs,
39
+ });
40
+
41
+ afterEach(() => {
42
+ wrapper.destroy();
43
+ });
44
+
45
+ it('should only show one error banner', async() => {
46
+ const errors = ['mistake!'];
47
+
48
+ wrapper = mount(ClusterRoleTemplateBinding, { ...requiredSetup() });
49
+
50
+ const cruResourceElem = wrapper.findComponent(CruResource);
51
+
52
+ await cruResourceElem.vm.$emit('error', errors);
53
+
54
+ const bannerElems = wrapper.findAllComponents(Banner);
55
+
56
+ expect(bannerElems).toHaveLength(1);
57
+ });
58
+ });
@@ -25,9 +25,10 @@ describe('view Namespace should', () => {
25
25
  $router: { applyQuery: {} },
26
26
  $store: {
27
27
  getters: {
28
- 'i18n/t': jest.fn(),
29
- 'management/all': () => ([project]),
30
- currentProduct: jest.fn(),
28
+ 'i18n/t': jest.fn(),
29
+ 'management/all': () => ([project]),
30
+ currentProduct: jest.fn(),
31
+ isStandaloneHarvester: false
31
32
  }
32
33
  }
33
34
  },
@@ -36,6 +37,7 @@ describe('view Namespace should', () => {
36
37
  ContainerResourceLimit: { template: '<div data-testid="limits"></div>' }, // Ensure value to be added to component
37
38
  NameNsDescription: true,
38
39
  Tab: true,
40
+ ResourceTabs: true
39
41
  }
40
42
  });
41
43