@rancher/shell 3.0.1-rc.3 → 3.0.1

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 (94) hide show
  1. package/assets/data/aws-regions.json +1 -0
  2. package/assets/styles/base/_basic.scss +5 -0
  3. package/assets/styles/base/_mixins.scss +8 -0
  4. package/assets/styles/global/_button.scss +5 -0
  5. package/assets/styles/themes/_dark.scss +2 -0
  6. package/assets/styles/themes/_light.scss +2 -0
  7. package/assets/translations/en-us.yaml +40 -22
  8. package/assets/translations/zh-hans.yaml +1 -7
  9. package/chart/monitoring/StorageClassSelector.vue +1 -1
  10. package/components/AssignTo.vue +1 -0
  11. package/components/AsyncButton.vue +1 -0
  12. package/components/BackLink.vue +8 -2
  13. package/components/PaginatedResourceTable.vue +135 -0
  14. package/components/ResourceDetail/Masthead.vue +1 -1
  15. package/components/ResourceDetail/index.vue +66 -11
  16. package/components/ResourceList/index.vue +0 -1
  17. package/components/ResourceTable.vue +6 -1
  18. package/components/ResourceYaml.vue +0 -53
  19. package/components/SortableTable/index.vue +8 -6
  20. package/components/Tabbed/index.vue +35 -2
  21. package/components/form/ResourceLabeledSelect.vue +2 -2
  22. package/components/form/ResourceTabs/index.vue +0 -23
  23. package/components/form/Taints.vue +1 -1
  24. package/components/form/UnitInput.vue +1 -1
  25. package/components/form/__tests__/UnitInput.test.ts +1 -1
  26. package/components/nav/TopLevelMenu.helper.ts +546 -0
  27. package/components/nav/TopLevelMenu.vue +125 -160
  28. package/components/nav/WindowManager/ContainerShell.vue +13 -4
  29. package/components/nav/WindowManager/__tests__/ContainerShell.test.ts +20 -18
  30. package/components/nav/__tests__/TopLevelMenu.test.ts +338 -326
  31. package/composables/useLabeledFormElement.ts +6 -2
  32. package/config/pagination-table-headers.js +4 -4
  33. package/config/product/explorer.js +2 -0
  34. package/config/router/navigation-guards/index.js +1 -2
  35. package/config/router/routes.js +1 -1
  36. package/config/settings.ts +15 -8
  37. package/core/plugin.ts +8 -1
  38. package/core/types-provisioning.ts +5 -0
  39. package/core/types.ts +26 -1
  40. package/dialog/DrainNode.vue +6 -6
  41. package/edit/catalog.cattle.io.clusterrepo.vue +95 -52
  42. package/edit/provisioning.cattle.io.cluster/index.vue +8 -3
  43. package/edit/workload/index.vue +1 -1
  44. package/edit/workload/storage/csi/index.vue +29 -1
  45. package/edit/workload/storage/index.vue +1 -0
  46. package/initialize/App.vue +3 -10
  47. package/initialize/install-plugins.js +1 -2
  48. package/list/management.cattle.io.podsecurityadmissionconfigurationtemplate.vue +6 -2
  49. package/list/node.vue +8 -5
  50. package/mixins/resource-fetch-api-pagination.js +40 -5
  51. package/mixins/resource-fetch.js +48 -5
  52. package/models/management.cattle.io.nodepool.js +5 -4
  53. package/models/nodedriver.js +2 -2
  54. package/models/provisioning.cattle.io.cluster.js +3 -11
  55. package/package.json +7 -8
  56. package/pages/about.vue +22 -0
  57. package/pages/auth/setup.vue +7 -28
  58. package/pages/c/_cluster/explorer/__tests__/index.test.ts +36 -24
  59. package/pages/c/_cluster/explorer/index.vue +100 -59
  60. package/pages/home.vue +308 -123
  61. package/plugins/dashboard-store/__tests__/mutations.test.ts +2 -0
  62. package/plugins/dashboard-store/actions.js +29 -19
  63. package/plugins/dashboard-store/getters.js +5 -2
  64. package/plugins/dashboard-store/mutations.js +4 -2
  65. package/plugins/steve/__tests__/mutations.test.ts +2 -1
  66. package/plugins/steve/steve-pagination-utils.ts +25 -2
  67. package/plugins/steve/subscribe.js +22 -8
  68. package/rancher-components/Banner/Banner.vue +1 -0
  69. package/rancher-components/Form/Checkbox/Checkbox.vue +2 -0
  70. package/rancher-components/Form/LabeledInput/LabeledInput.vue +2 -0
  71. package/rancher-components/Form/Radio/RadioButton.vue +2 -0
  72. package/rancher-components/Form/Radio/RadioGroup.vue +2 -0
  73. package/rancher-components/Form/TextArea/TextAreaAutoGrow.vue +2 -0
  74. package/rancher-components/Form/ToggleSwitch/ToggleSwitch.vue +3 -0
  75. package/rancher-components/StringList/StringList.test.ts +15 -15
  76. package/rancher-components/StringList/StringList.vue +3 -0
  77. package/scripts/extension/helm/charts/ui-plugin-server/Chart.yaml +0 -2
  78. package/scripts/extension/parse-tag-name +2 -0
  79. package/scripts/test-plugins-build.sh +4 -2
  80. package/store/index.js +31 -9
  81. package/tsconfig.json +7 -1
  82. package/types/resources/settings.d.ts +1 -1
  83. package/types/shell/index.d.ts +1107 -1276
  84. package/types/store/dashboard-store.types.ts +4 -0
  85. package/types/store/pagination.types.ts +13 -0
  86. package/types/store/vuex.d.ts +8 -0
  87. package/types/vue-shim.d.ts +6 -31
  88. package/utils/cluster.js +92 -1
  89. package/utils/pagination-utils.ts +17 -8
  90. package/utils/pagination-wrapper.ts +70 -0
  91. package/utils/uiplugins.ts +307 -0
  92. package/components/templates/error.vue +0 -131
  93. package/config/router/navigation-guards/history.js +0 -13
  94. package/plugins/back-button.js +0 -3
@@ -19,13 +19,17 @@ export default {
19
19
  required: true,
20
20
  },
21
21
  },
22
+ emits: ['error'],
22
23
 
23
24
  async fetch() {
24
25
  const inStore = this.$store.getters['currentStore']();
25
26
 
27
+ try {
26
28
  // Fetch storage classes so we can determine if a PVC can be expanded
27
- this.$store.dispatch(`${ inStore }/findAll`, { type: STORAGE_CLASS });
28
-
29
+ await this.$store.dispatch(`${ inStore }/findAll`, { type: STORAGE_CLASS });
30
+ } catch (e) {
31
+ this.$emit('error', e?.data || e);
32
+ }
29
33
  await this.$fetchType(this.resource);
30
34
  }
31
35
  };
package/list/node.vue CHANGED
@@ -21,6 +21,7 @@ import { COLUMN_BREAKPOINTS } from '@shell/types/store/type-map';
21
21
 
22
22
  import ResourceFetch from '@shell/mixins/resource-fetch';
23
23
  import { mapGetters } from 'vuex';
24
+ import { FetchPageSecondaryResourcesOpts } from 'components/PaginatedResourceTable.vue';
24
25
 
25
26
  export default defineComponent({
26
27
  name: 'ListNode',
@@ -36,10 +37,12 @@ export default defineComponent({
36
37
  type: String,
37
38
  required: true,
38
39
  },
40
+
39
41
  schema: {
40
42
  type: Object,
41
43
  required: true,
42
44
  },
45
+
43
46
  useQueryParamsForSimpleFiltering: {
44
47
  type: Boolean,
45
48
  default: false
@@ -209,8 +212,8 @@ export default defineComponent({
209
212
  *
210
213
  * So when we have a page.... use those entries as filters when fetching the other resources
211
214
  */
212
- async fetchPageSecondaryResources(force = false) {
213
- if (!this.rows?.length) {
215
+ async fetchPageSecondaryResources({ canPaginate, force, page }: FetchPageSecondaryResourcesOpts) {
216
+ if (!page?.length) {
214
217
  return;
215
218
  }
216
219
 
@@ -220,7 +223,7 @@ export default defineComponent({
220
223
  const opt: ActionFindPageArgs = {
221
224
  force,
222
225
  pagination: new FilterArgs({
223
- filters: PaginationParamFilter.createMultipleFields(this.rows.map((r: any) => new PaginationFilterField({
226
+ filters: PaginationParamFilter.createMultipleFields(page.map((r: any) => new PaginationFilterField({
224
227
  field: 'status.nodeName',
225
228
  value: r.id
226
229
  }))),
@@ -242,7 +245,7 @@ export default defineComponent({
242
245
  namespaced: namespace,
243
246
  pagination: new FilterArgs({
244
247
  filters: PaginationParamFilter.createMultipleFields(
245
- this.rows.reduce((res: PaginationFilterField[], r: any ) => {
248
+ page.reduce((res: PaginationFilterField[], r: any ) => {
246
249
  const name = r.metadata?.annotations?.[CAPI_ANNOTATIONS.MACHINE_NAME];
247
250
 
248
251
  if (name) {
@@ -268,7 +271,7 @@ export default defineComponent({
268
271
  force,
269
272
  pagination: new FilterArgs({
270
273
  filters: PaginationParamFilter.createMultipleFields(
271
- this.rows.map((r: any) => new PaginationFilterField({
274
+ page.map((r: any) => new PaginationFilterField({
272
275
  field: 'spec.nodeName',
273
276
  value: r.id,
274
277
  }))
@@ -13,6 +13,21 @@ import stevePaginationUtils from '@shell/plugins/steve/steve-pagination-utils';
13
13
  */
14
14
  export default {
15
15
 
16
+ props: {
17
+ namespaced: {
18
+ type: Boolean,
19
+ default: null, // Automatic from schema
20
+ },
21
+
22
+ /**
23
+ * Where in the ui this mixin is used. For instance the home page cluster list would be `home`
24
+ */
25
+ context: {
26
+ type: String,
27
+ default: null,
28
+ },
29
+ },
30
+
16
31
  data() {
17
32
  return {
18
33
  forceUpdateLiveAndDelayed: 0,
@@ -68,7 +83,7 @@ export default {
68
83
  },
69
84
 
70
85
  namespaceFilterChanged(neu) {
71
- if (!this.canPaginate || !this.schema?.attributes?.namespaced) {
86
+ if (!this.canPaginate || !this.isNamespaced) {
72
87
  return;
73
88
  }
74
89
 
@@ -166,7 +181,16 @@ export default {
166
181
  return;
167
182
  }
168
183
 
169
- return this.resource && this.$store.getters[`${ this.currentProduct?.inStore }/paginationEnabled`]?.(this.resource.id || this.resource);
184
+ if (!this.resource) {
185
+ return false;
186
+ }
187
+
188
+ const args = {
189
+ id: this.resource.id || this.resource,
190
+ context: this.context,
191
+ };
192
+
193
+ return this.resource && this.$store.getters[`${ this.inStore }/paginationEnabled`]?.(args);
170
194
  },
171
195
 
172
196
  paginationResult() {
@@ -182,7 +206,7 @@ export default {
182
206
  return;
183
207
  }
184
208
 
185
- return this.$store.getters[`${ this.currentProduct?.inStore }/havePage`](this.resource);
209
+ return this.$store.getters[`${ this.inStore }/havePage`](this.resource);
186
210
  },
187
211
 
188
212
  /**
@@ -197,6 +221,15 @@ export default {
197
221
  */
198
222
  showDynamicRancherNamespaces() {
199
223
  return this.$store.getters['prefs/get'](ALL_NAMESPACES);
224
+ },
225
+
226
+ isNamespaced() {
227
+ if (this.namespaced !== null) { // null is the default value
228
+ // This is an override, but only if it's set
229
+ return !!this.namespaced;
230
+ }
231
+
232
+ return this.schema?.attributes?.namespaced;
200
233
  }
201
234
  },
202
235
 
@@ -221,7 +254,7 @@ export default {
221
254
  namespaceFilters: {
222
255
  immediate: true,
223
256
  async handler(neu, old) {
224
- if (!this.canPaginate || !this.schema?.attributes?.namespaced) {
257
+ if (!this.canPaginate || !this.isNamespaced) {
225
258
  return;
226
259
  }
227
260
 
@@ -298,7 +331,9 @@ export default {
298
331
  return;
299
332
  }
300
333
 
301
- await this.fetchPageSecondaryResources();
334
+ await this.fetchPageSecondaryResources({
335
+ canPaginate: this.canPaginate, force: false, page: this.rows, pagResult: this.paginationResult
336
+ });
302
337
  }
303
338
  },
304
339
  };
@@ -31,11 +31,18 @@ export default {
31
31
  perfConfig = DEFAULT_PERF_SETTING;
32
32
  }
33
33
 
34
+ // Normally owner components supply `resource` and `inStore` as part of their data, however these are needed here before parent data runs
35
+ // So set up both here
36
+ const params = { ...this.$route.params };
37
+ const resource = params.resource || this.schema?.id; // Resource can either be on a page showing single list, or a page of a resource showing a list of another resource
38
+ const inStore = this.$store.getters['currentStore'](resource);
39
+
34
40
  return {
41
+ inStore,
35
42
  perfConfig,
36
43
  init: false,
37
44
  multipleResources: [],
38
- loadResources: [this.resource],
45
+ loadResources: [resource],
39
46
  // manual refresh vars
40
47
  hasManualRefresh: false,
41
48
  watch: true,
@@ -60,17 +67,47 @@ export default {
60
67
  }
61
68
  },
62
69
 
70
+ props: {
71
+ /**
72
+ * Add additional filtering to the rows
73
+ *
74
+ * Should only be used when we have all results, otherwise we're filtering a page which already has been filtered...
75
+ */
76
+ localFilter: {
77
+ type: Function,
78
+ default: null,
79
+ },
80
+
81
+ /**
82
+ * Add additional filtering to the pagination api request
83
+ */
84
+ apiFilter: {
85
+ type: Function,
86
+ default: null,
87
+ },
88
+ },
89
+
63
90
  computed: {
64
91
  ...mapGetters({ refreshFlag: 'resource-fetch/refreshFlag' }),
92
+
65
93
  rows() {
66
94
  const currResource = this.fetchedResourceType.find((item) => item.type === this.resource);
67
95
 
68
96
  if (currResource) {
69
- return this.$store.getters[`${ currResource.currStore }/all`](this.resource);
70
- } else {
71
- return [];
97
+ const rows = this.$store.getters[`${ currResource.currStore }/all`](this.resource);
98
+
99
+ if (this.canPaginate) {
100
+ if (this.havePaginated) {
101
+ return rows;
102
+ }
103
+ } else {
104
+ return this.localFilter ? this.localFilter(rows) : rows;
105
+ }
72
106
  }
107
+
108
+ return [];
73
109
  },
110
+
74
111
  loading() {
75
112
  if (this.canPaginate) {
76
113
  return this.paginating;
@@ -86,7 +123,9 @@ export default {
86
123
  if (this.init && neu) {
87
124
  await this.$fetch();
88
125
  if (this.canPaginate && this.fetchPageSecondaryResources) {
89
- this.fetchPageSecondaryResources(true);
126
+ this.fetchPageSecondaryResources({
127
+ canPaginate: this.canPaginate, force: true, page: this.rows, pagResult: this.paginationResult
128
+ });
90
129
  }
91
130
  }
92
131
  }
@@ -140,6 +179,10 @@ export default {
140
179
  force: this.paginating !== null // Fix for manual refresh (before ripped out).
141
180
  };
142
181
 
182
+ if (this.apiFilter) {
183
+ opt.paginating = this.apiFilter(opt.pagination);
184
+ }
185
+
143
186
  this['paginating'] = true;
144
187
 
145
188
  const that = this;
@@ -4,11 +4,12 @@ import HybridModel from '@shell/plugins/steve/hybrid-class';
4
4
  import { notOnlyOfRole } from '@shell/models/cluster.x-k8s.io.machine';
5
5
 
6
6
  export default class MgmtNodePool extends HybridModel {
7
- get nodeTemplate() {
8
- const id = (this.spec?.nodeTemplateName || '').replace(/:/, '/');
9
- const template = this.$getters['byId'](MANAGEMENT.NODE_TEMPLATE, id);
7
+ get nodeTemplateId() {
8
+ return (this.spec?.nodeTemplateName || '').replace(/:/, '/');
9
+ }
10
10
 
11
- return template;
11
+ get nodeTemplate() {
12
+ return this.$getters['byId'](MANAGEMENT.NODE_TEMPLATE, this.nodeTemplateId);
12
13
  }
13
14
 
14
15
  get provider() {
@@ -18,7 +18,7 @@ export default class NodeDriver extends Driver {
18
18
  icon: 'icon icon-play',
19
19
  bulkable: true,
20
20
  bulkAction: 'activateBulk',
21
- enabled: !!this.actions.activate && this.state === 'inactive',
21
+ enabled: !!this.actions?.activate && this.state === 'inactive',
22
22
  },
23
23
  {
24
24
  action: 'deactivate',
@@ -26,7 +26,7 @@ export default class NodeDriver extends Driver {
26
26
  icon: 'icon icon-pause',
27
27
  bulkable: true,
28
28
  bulkAction: 'deactivateBulk',
29
- enabled: !!this.actions.deactivate && this.state === 'active',
29
+ enabled: !!this.actions?.deactivate && this.state === 'active',
30
30
  weight: -1,
31
31
  },
32
32
  { divider: true },
@@ -245,7 +245,7 @@ export default class ProvCluster extends SteveModel {
245
245
  }
246
246
 
247
247
  get canDelete() {
248
- return super.canDelete && this.stateObj.name !== 'removing';
248
+ return super.canDelete && this.stateObj?.name !== 'removing';
249
249
  }
250
250
 
251
251
  get canEditYaml() {
@@ -349,19 +349,11 @@ export default class ProvCluster extends SteveModel {
349
349
  }
350
350
 
351
351
  get mgmtClusterId() {
352
- return this.mgmt?.id || this.id?.replace(`${ this.metadata.namespace }/`, '');
352
+ return this.status?.clusterName;
353
353
  }
354
354
 
355
355
  get mgmt() {
356
- const name = this.status?.clusterName;
357
-
358
- if ( !name ) {
359
- return null;
360
- }
361
-
362
- const out = this.$rootGetters['management/byId'](MANAGEMENT.CLUSTER, name);
363
-
364
- return out;
356
+ return this.$rootGetters['management/byId'](MANAGEMENT.CLUSTER, this.mgmtClusterId);
365
357
  }
366
358
 
367
359
  get isReady() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rancher/shell",
3
- "version": "3.0.1-rc.3",
3
+ "version": "3.0.1",
4
4
  "description": "Rancher Dashboard Shell",
5
5
  "repository": "https://github.com/rancherlabs/dashboard",
6
6
  "license": "Apache-2.0",
@@ -39,7 +39,7 @@
39
39
  "@popperjs/core": "2.4.4",
40
40
  "@rancher/icons": "2.0.29",
41
41
  "@types/is-url": "1.2.30",
42
- "@types/node": "16.4.3",
42
+ "@types/node": "20.10.8",
43
43
  "@types/semver": "^7.5.8",
44
44
  "@typescript-eslint/eslint-plugin": "~5.4.0",
45
45
  "@typescript-eslint/parser": "~5.4.0",
@@ -60,7 +60,7 @@
60
60
  "color": "4.2.3",
61
61
  "codemirror": ">=5.64.0 <6",
62
62
  "codemirror-editor-vue3": "2.7.1",
63
- "cookie": "0.5.0",
63
+ "cookie": "0.7.0",
64
64
  "cookie-universal": "2.2.2",
65
65
  "core-js": "3.25.3",
66
66
  "cron-validator": "1.3.1",
@@ -104,7 +104,7 @@
104
104
  "js-yaml": "4.1.0",
105
105
  "js-yaml-loader": "1.2.2",
106
106
  "jsdiff": "1.1.1",
107
- "jsonpath-plus": "10.0.0",
107
+ "jsonpath-plus": "10.0.7",
108
108
  "jsrsasign": "10.5.25",
109
109
  "jszip": "3.8.0",
110
110
  "lodash": "4.17.21",
@@ -123,7 +123,7 @@
123
123
  "start-server-and-test": "1.13.1",
124
124
  "style-loader": "1.2.1",
125
125
  "ts-node": "8.10.2",
126
- "typescript": "4.5.5",
126
+ "typescript": "5.6.3",
127
127
  "ufo": "0.7.11",
128
128
  "unfetch": "4.2.0",
129
129
  "url-parse": "1.5.10",
@@ -131,7 +131,6 @@
131
131
  "vue-router": "4.4.3",
132
132
  "vue-select": "4.0.0-beta.6",
133
133
  "vue-server-renderer": "2.7.16",
134
- "vue-template-compiler": "2.7.16",
135
134
  "vue3-resize": "0.2.0",
136
135
  "vue3-virtual-scroll-list": "0.2.1",
137
136
  "vuedraggable": "4.1.0",
@@ -161,7 +160,7 @@
161
160
  "roarr": "7.0.4",
162
161
  "semver": "7.5.4",
163
162
  "@types/lodash": "4.17.5",
164
- "@types/node": "~20.10.0",
163
+ "@types/node": "20.10.8",
165
164
  "@vue/cli-service/html-webpack-plugin": "^5.0.0"
166
165
  },
167
166
  "nyc": {
@@ -170,4 +169,4 @@
170
169
  ".vue"
171
170
  ]
172
171
  }
173
- }
172
+ }
package/pages/about.vue CHANGED
@@ -108,6 +108,9 @@ export default {
108
108
  :to="{ name: 'diagnostic' }"
109
109
  class="btn role-primary"
110
110
  data-testid="about__diagnostics_button"
111
+ role="button"
112
+ :aria-label="t('about.diagnostic.title')"
113
+ @keyup.space="$router.push({ name: 'diagnostic' })"
111
114
  >
112
115
  {{ t('about.diagnostic.title') }}
113
116
  </router-link>
@@ -126,6 +129,8 @@ export default {
126
129
  href="https://github.com/rancher/rancher"
127
130
  target="_blank"
128
131
  rel="nofollow noopener noreferrer"
132
+ role="link"
133
+ :aria-label="t('about.versions.githubRepo', {name: t(`about.versions.rancher`) })"
129
134
  >
130
135
  {{ t("about.versions.rancher") }}
131
136
  </a>
@@ -137,6 +142,8 @@ export default {
137
142
  href="https://github.com/rancher/dashboard"
138
143
  target="_blank"
139
144
  rel="nofollow noopener noreferrer"
145
+ role="link"
146
+ :aria-label="t('about.versions.githubRepo', {name: t(`generic.dashboard`)})"
140
147
  >
141
148
  {{ t("generic.dashboard") }}
142
149
  </a>
@@ -148,6 +155,8 @@ export default {
148
155
  href="https://github.com/rancher/cli"
149
156
  target="_blank"
150
157
  rel="nofollow noopener noreferrer"
158
+ role="link"
159
+ :aria-label="t('about.versions.githubRepo', {name: t(`about.versions.cli`) })"
151
160
  >
152
161
  {{ appName }} {{ t("about.versions.cli") }}
153
162
  </a>
@@ -159,6 +168,8 @@ export default {
159
168
  href="https://github.com/rancher/helm"
160
169
  target="_blank"
161
170
  rel="nofollow noopener noreferrer"
171
+ role="link"
172
+ :aria-label="t('about.versions.githubRepo', {name: t(`about.versions.helm`) })"
162
173
  >
163
174
  {{ t("about.versions.helm") }}
164
175
  </a>
@@ -170,6 +181,8 @@ export default {
170
181
  href="https://github.com/rancher/machine"
171
182
  target="_blank"
172
183
  rel="nofollow noopener noreferrer"
184
+ role="link"
185
+ :aria-label="t('about.versions.githubRepo', {name: t(`about.versions.machine`) })"
173
186
  >
174
187
  {{ t("about.versions.machine") }}
175
188
  </a>
@@ -178,9 +191,12 @@ export default {
178
191
  </table>
179
192
  <p class="pt-20">
180
193
  <a
194
+ class="release-notes-link"
181
195
  :href="releaseNotesUrl"
182
196
  target="_blank"
183
197
  rel="nofollow noopener noreferrer"
198
+ role="link"
199
+ :aria-label="t('about.versions.releaseNotes')"
184
200
  >
185
201
  {{ t('about.versions.releaseNotes') }}
186
202
  </a>
@@ -202,8 +218,12 @@ export default {
202
218
  <td>
203
219
  <a
204
220
  v-if="d.imageList"
221
+ tabindex="0"
205
222
  :data-testid="`image_list_download_link__${d.label}`"
223
+ role="link"
224
+ :aria-label="t('about.versions.downloadImages', { listName: t(d.label) })"
206
225
  @click="d.imageList"
226
+ @keyup.enter="d.imageList"
207
227
  >
208
228
  {{ t('asyncButton.download.action') }}
209
229
  </a>
@@ -230,6 +250,8 @@ export default {
230
250
  <a
231
251
  v-if="d.cliLink"
232
252
  :href="d.cliLink"
253
+ role="link"
254
+ :aria-label="t('about.versions.downloadCli', { os: t(d.label) })"
233
255
  >{{ d.cliFile }}</a>
234
256
  </td>
235
257
  </tr>
@@ -11,7 +11,6 @@ import { getVendor, getProduct, setVendor } from '@shell/config/private-label';
11
11
  import { RadioGroup } from '@components/Form/Radio';
12
12
  import { setSetting } from '@shell/utils/settings';
13
13
  import { SETTING } from '@shell/config/settings';
14
- import { isDevBuild } from '@shell/utils/version';
15
14
  import { exceptionToErrorsArray } from '@shell/utils/error';
16
15
  import Password from '@shell/components/form/Password';
17
16
  import { applyProducts } from '@shell/store/type-map';
@@ -66,7 +65,6 @@ export default {
66
65
  v3User: null,
67
66
  serverUrl: null,
68
67
  mcmEnabled: null,
69
- telemetry: null,
70
68
  eula: false,
71
69
  principals: null,
72
70
  errors: []
@@ -101,16 +99,7 @@ export default {
101
99
  },
102
100
 
103
101
  async fetch() {
104
- const telemetrySetting = this.$store.getters['management/byId'](MANAGEMENT.SETTING, SETTING.TELEMETRY);
105
102
  const serverUrlSetting = this.$store.getters['management/byId'](MANAGEMENT.SETTING, SETTING.SERVER_URL);
106
- const rancherVersionSetting = this.$store.getters['management/byId'](MANAGEMENT.SETTING, SETTING.VERSION_RANCHER);
107
- let telemetry = true;
108
-
109
- if (telemetrySetting?.value && telemetrySetting.value !== 'prompt') {
110
- telemetry = telemetrySetting.value !== 'out';
111
- } else if (!rancherVersionSetting?.value || isDevBuild(rancherVersionSetting?.value)) {
112
- telemetry = false;
113
- }
114
103
 
115
104
  let plSetting;
116
105
 
@@ -163,7 +152,6 @@ export default {
163
152
  this['v3User'] = v3User;
164
153
  this['serverUrl'] = serverUrl;
165
154
  this['mcmEnabled'] = mcmEnabled;
166
- this['telemetry'] = telemetry;
167
155
  this['principals'] = principals;
168
156
  },
169
157
 
@@ -244,7 +232,6 @@ export default {
244
232
 
245
233
  if (this.isFirstLogin) {
246
234
  promises.push( setSetting(this.$store, SETTING.EULA_AGREED, (new Date()).toISOString()) );
247
- promises.push( setSetting(this.$store, SETTING.TELEMETRY, this.telemetry ? 'in' : 'out') );
248
235
 
249
236
  if ( this.mcmEnabled && this.serverUrl ) {
250
237
  promises.push( setSetting(this.$store, SETTING.SERVER_URL, this.serverUrl) );
@@ -406,20 +393,6 @@ export default {
406
393
  </div>
407
394
  </template>
408
395
 
409
- <div class="checkbox mt-40">
410
- <Checkbox
411
- id="checkbox-telemetry"
412
- v-model:value="telemetry"
413
- >
414
- <template #label>
415
- <t
416
- k="setup.telemetry"
417
- :raw="true"
418
- :name="productName"
419
- />
420
- </template>
421
- </Checkbox>
422
- </div>
423
396
  <div class="checkbox pt-10 eula">
424
397
  <Checkbox
425
398
  id="checkbox-eula"
@@ -461,6 +434,9 @@ export default {
461
434
  </h4>
462
435
  </div>
463
436
  </div>
437
+ <div>
438
+ &nbsp;
439
+ </div>
464
440
  </div>
465
441
  <BrandImage
466
442
  class="col span-6 landscape"
@@ -517,11 +493,14 @@ export default {
517
493
  width: 51%;
518
494
 
519
495
  & > div:first-of-type {
520
- flex:3;
496
+ flex: 3;
521
497
  }
522
498
  & > div:nth-of-type(2) {
523
499
  flex: 9;
524
500
  }
501
+ & > div:nth-of-type(3) {
502
+ flex: 2;
503
+ }
525
504
  }
526
505
 
527
506
  .setup-title {
@@ -99,7 +99,7 @@ describe('page: cluster dashboard', () => {
99
99
  [STATES_ENUM.HEALTHY, 'icon-checkmark', true, false, false, [{ status: 'True' }], 1, 0],
100
100
  ]]
101
101
  ])('%p cluster - %p agent health box :', (_, agentId, isLocal, agentResources, statuses) => {
102
- it.each(statuses)('should NOT show %p status due to missing canList permissions', (status, iconClass, isLoaded, disconnected, error, conditions, readyReplicas, unavailableReplicas) => {
102
+ it.each(statuses)('should NOT show %p status due to missing canList permissions', (status, iconClass, isLoaded, disconnected, error, conditions, readyReplicas, unavailableReplicas) => {
103
103
  const options = clone(mountOptions);
104
104
 
105
105
  options.global.mocks.$store.getters.currentCluster.isLocal = isLocal;
@@ -138,41 +138,48 @@ describe('page: cluster dashboard', () => {
138
138
 
139
139
  describe.each([
140
140
  ['local', 'fleet', true, ['fleetDeployment', 'fleetStatefulSet'], [
141
- [STATES_ENUM.IN_PROGRESS, 'icon-spinner', false, false, false, '', 0, 0],
142
- [STATES_ENUM.UNHEALTHY, 'icon-warning', true, false, false, [{ status: 'False' }], 0, 0],
143
- [STATES_ENUM.UNHEALTHY, 'icon-warning', true, false, true, [{ status: 'True' }], 0, 0],
144
- [STATES_ENUM.WARNING, 'icon-warning', true, true, false, [{ status: 'True' }], 0, 0],
145
- [STATES_ENUM.WARNING, 'icon-warning', true, false, false, [{ status: 'True' }], 0, 0],
146
- [STATES_ENUM.WARNING, 'icon-warning', true, false, false, [{ status: 'True' }], 0, 1],
147
- [STATES_ENUM.HEALTHY, 'icon-checkmark', true, false, false, [{ status: 'True' }], 1, 0],
141
+ [STATES_ENUM.IN_PROGRESS, 'icon-spinner', false, false, false, false, '', 0, 0],
142
+ [STATES_ENUM.UNHEALTHY, 'icon-warning', true, true, false, false, [{ status: 'False' }], 0, 0],
143
+ [STATES_ENUM.UNHEALTHY, 'icon-warning', true, true, false, true, [{ status: 'True' }], 0, 0],
144
+ [STATES_ENUM.WARNING, 'icon-warning', true, true, true, false, [{ status: 'True' }], 0, 0],
145
+ [STATES_ENUM.WARNING, 'icon-warning', true, true, false, false, [{ status: 'True' }], 0, 0],
146
+ [STATES_ENUM.WARNING, 'icon-warning', true, true, false, false, [{ status: 'True' }], 0, 1],
147
+ [STATES_ENUM.HEALTHY, 'icon-checkmark', false, true, false, false, [{ status: 'True' }], 1, 0],
148
148
  ]],
149
149
  ['downstream RKE2', 'fleet', false, ['fleetStatefulSet'], [
150
- [STATES_ENUM.IN_PROGRESS, 'icon-spinner', false, false, false, '', 0, 0],
151
- [STATES_ENUM.UNHEALTHY, 'icon-warning', true, false, false, [{ status: 'False' }], 0, 0],
152
- [STATES_ENUM.UNHEALTHY, 'icon-warning', true, false, true, [{ status: 'True' }], 0, 0],
153
- [STATES_ENUM.WARNING, 'icon-warning', true, true, false, [{ status: 'True' }], 0, 0],
154
- [STATES_ENUM.WARNING, 'icon-warning', true, false, false, [{ status: 'True' }], 0, 0],
155
- [STATES_ENUM.WARNING, 'icon-warning', true, false, false, [{ status: 'True' }], 0, 1],
156
- [STATES_ENUM.HEALTHY, 'icon-checkmark', true, false, false, [{ status: 'True' }], 1, 0],
150
+ [STATES_ENUM.IN_PROGRESS, 'icon-spinner', false, false, false, false, '', 0, 0],
151
+ [STATES_ENUM.UNHEALTHY, 'icon-warning', true, true, false, false, [{ status: 'False' }], 0, 0],
152
+ [STATES_ENUM.UNHEALTHY, 'icon-warning', true, true, false, true, [{ status: 'True' }], 0, 0],
153
+ [STATES_ENUM.WARNING, 'icon-warning', true, true, true, false, [{ status: 'True' }], 0, 0],
154
+ [STATES_ENUM.WARNING, 'icon-warning', true, true, false, false, [{ status: 'True' }], 0, 0],
155
+ [STATES_ENUM.WARNING, 'icon-warning', true, true, false, false, [{ status: 'True' }], 0, 1],
156
+ [STATES_ENUM.HEALTHY, 'icon-checkmark', false, true, false, false, [{ status: 'True' }], 1, 0],
157
157
  ]],
158
158
  ['downstream RKE2', 'cattle', false, ['cattleDeployment'], [
159
- [STATES_ENUM.IN_PROGRESS, 'icon-spinner', false, false, false, '', 0, 0],
160
- [STATES_ENUM.UNHEALTHY, 'icon-warning', true, false, false, [{ status: 'False' }], 0, 0],
161
- [STATES_ENUM.UNHEALTHY, 'icon-warning', true, true, false, [{ status: 'True' }], 0, 0],
162
- [STATES_ENUM.UNHEALTHY, 'icon-warning', true, false, true, [{ status: 'True' }], 0, 0],
163
- [STATES_ENUM.WARNING, 'icon-warning', true, false, false, [{ status: 'True' }], 0, 0],
164
- [STATES_ENUM.WARNING, 'icon-warning', true, false, false, [{ status: 'True' }], 0, 1],
165
- [STATES_ENUM.HEALTHY, 'icon-checkmark', true, false, false, [{ status: 'True' }], 1, 0],
159
+ [STATES_ENUM.IN_PROGRESS, 'icon-spinner', false, false, false, false, '', 0, 0],
160
+ [STATES_ENUM.UNHEALTHY, 'icon-warning', true, true, false, false, [{ status: 'False' }], 0, 0],
161
+ [STATES_ENUM.UNHEALTHY, 'icon-warning', true, true, true, false, [{ status: 'True' }], 0, 0],
162
+ [STATES_ENUM.UNHEALTHY, 'icon-warning', true, true, false, true, [{ status: 'True' }], 0, 0],
163
+ [STATES_ENUM.WARNING, 'icon-warning', true, true, false, false, [{ status: 'True' }], 0, 0],
164
+ [STATES_ENUM.WARNING, 'icon-warning', true, true, false, false, [{ status: 'True' }], 0, 1],
165
+ [STATES_ENUM.HEALTHY, 'icon-checkmark', false, true, false, false, [{ status: 'True' }], 1, 0],
166
166
  ]]
167
167
  ])('%p cluster - %p agent health box ::', (_, agentId, isLocal, agentResources, statuses) => {
168
- it.each(statuses)('should show %p status', (status, iconClass, isLoaded, disconnected, error, conditions, readyReplicas, unavailableReplicas) => {
168
+ it.each(statuses)('should show %p status', async(status, iconClass, clickable, isLoaded, disconnected, error, conditions, readyReplicas, unavailableReplicas) => {
169
+ let agentRoute = null;
170
+
169
171
  const options = clone(mountOptions);
170
172
 
171
173
  options.global.mocks.$store.getters.currentCluster.isLocal = isLocal;
172
174
 
173
- // let's pass the canList now
174
175
  options.global.mocks.$store.getters['cluster/canList'] = (type: string) => !!(type === WORKLOAD_TYPES.DEPLOYMENT) || !!(type === WORKLOAD_TYPES.STATEFUL_SET);
175
176
 
177
+ options.global.mocks.$router = {
178
+ push: (route: any) => {
179
+ agentRoute = route;
180
+ }
181
+ };
182
+
176
183
  const resources = agentResources.reduce((acc, r) => {
177
184
  const agent = {
178
185
  metadata: { state: { error } },
@@ -204,7 +211,12 @@ describe('page: cluster dashboard', () => {
204
211
 
205
212
  expect(box.element).toBeDefined();
206
213
  expect(box.element.classList).toContain(status);
214
+ expect(!!(box.element as any).$_popper).toBe(clickable);
207
215
  expect(icon.element.classList).toContain(iconClass);
216
+
217
+ await box.trigger('click');
218
+
219
+ expect(!!agentRoute).toBe(clickable);
208
220
  });
209
221
  });
210
222