@rancher/shell 3.0.2-rc.2 → 3.0.2-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 (141) hide show
  1. package/assets/styles/base/_basic.scss +5 -7
  2. package/assets/styles/global/_button.scss +10 -0
  3. package/assets/styles/global/_tooltip.scss +2 -2
  4. package/assets/styles/themes/_dark.scss +14 -2
  5. package/assets/styles/themes/_light.scss +7 -2
  6. package/assets/styles/vendor/vue-select.scss +4 -0
  7. package/assets/translations/en-us.yaml +44 -5
  8. package/components/BannerGraphic.vue +0 -42
  9. package/components/ButtonMultiAction.vue +1 -1
  10. package/components/Carousel.vue +36 -29
  11. package/components/CommunityLinks.vue +6 -1
  12. package/components/GrowlManager.vue +9 -2
  13. package/components/LocaleSelector.vue +8 -1
  14. package/components/PaginatedResourceTable.vue +4 -7
  15. package/components/ProgressBarMulti.vue +14 -0
  16. package/components/Questions/Reference.vue +57 -28
  17. package/components/SelectIconGrid.vue +12 -1
  18. package/components/SideNav.vue +12 -38
  19. package/components/SortableTable/index.vue +1 -0
  20. package/components/Tabbed/index.vue +12 -1
  21. package/components/YamlEditor.vue +1 -0
  22. package/components/auth/Principal.vue +5 -3
  23. package/components/fleet/FleetClusters.vue +82 -1
  24. package/components/fleet/FleetRepos.vue +13 -30
  25. package/components/fleet/ForceDirectedTreeChart/index.vue +2 -2
  26. package/components/form/ChangePassword.vue +2 -0
  27. package/components/form/ColorInput.vue +24 -1
  28. package/components/form/FileSelector.vue +2 -0
  29. package/components/form/KeyValue.vue +230 -160
  30. package/components/form/LabeledSelect.vue +1 -1
  31. package/components/form/PlusMinus.vue +14 -2
  32. package/components/form/ResourceLabeledSelect.vue +13 -53
  33. package/components/form/ResourceSelector.vue +1 -0
  34. package/components/form/ResourceTabs/index.vue +79 -36
  35. package/components/form/SecretSelector.vue +2 -2
  36. package/components/form/__tests__/KeyValue.test.ts +1 -1
  37. package/components/formatter/FleetClusterSummaryGraph.vue +2 -2
  38. package/components/formatter/FleetSummaryGraph.vue +6 -7
  39. package/components/formatter/WorkloadHealthScale.vue +7 -0
  40. package/components/nav/Group.vue +30 -4
  41. package/components/nav/Header.vue +82 -114
  42. package/components/nav/HeaderPageActionMenu.vue +27 -131
  43. package/components/nav/NamespaceFilter.vue +1 -1
  44. package/components/nav/Type.vue +15 -0
  45. package/config/home-links.js +21 -13
  46. package/config/labels-annotations.js +2 -0
  47. package/config/page-actions.js +1 -0
  48. package/config/pagination-table-headers.js +15 -1
  49. package/config/product/explorer.js +7 -17
  50. package/config/table-headers.js +6 -0
  51. package/config/version.js +5 -1
  52. package/core/plugin.ts +41 -1
  53. package/core/plugins.js +125 -72
  54. package/core/types-provisioning.ts +91 -2
  55. package/core/types.ts +55 -0
  56. package/detail/__tests__/autoscaling.horizontalpodautoscaler.test.ts +12 -3
  57. package/detail/catalog.cattle.io.app.vue +1 -1
  58. package/detail/fleet.cattle.io.cluster.vue +3 -3
  59. package/detail/namespace.vue +13 -19
  60. package/detail/networking.k8s.io.ingress.vue +13 -53
  61. package/detail/provisioning.cattle.io.cluster.vue +12 -1
  62. package/detail/workload/index.vue +3 -3
  63. package/dialog/AddCustomBadgeDialog.vue +5 -1
  64. package/edit/auth/ldap/__tests__/config.test.ts +18 -0
  65. package/edit/auth/ldap/config.vue +24 -0
  66. package/edit/auth/saml.vue +8 -6
  67. package/edit/fleet.cattle.io.gitrepo.vue +7 -1
  68. package/edit/logging-flow/index.vue +4 -19
  69. package/edit/networking.k8s.io.ingress/index.vue +18 -65
  70. package/edit/networking.k8s.io.networkpolicy/index.vue +4 -5
  71. package/edit/provisioning.cattle.io.cluster/index.vue +13 -1
  72. package/edit/provisioning.cattle.io.cluster/rke2.vue +31 -115
  73. package/edit/provisioning.cattle.io.cluster/tabs/Basics.vue +2 -2
  74. package/edit/provisioning.cattle.io.cluster/tabs/networking/ACE.vue +14 -28
  75. package/edit/provisioning.cattle.io.cluster/tabs/networking/index.vue +25 -12
  76. package/edit/service.vue +1 -2
  77. package/list/networking.k8s.io.ingress.vue +1 -1
  78. package/list/node.vue +15 -8
  79. package/list/persistentvolume.vue +12 -4
  80. package/list/service.vue +1 -1
  81. package/list/workload.vue +4 -0
  82. package/mixins/chart.js +4 -1
  83. package/models/catalog.cattle.io.app.js +3 -1
  84. package/models/catalog.cattle.io.clusterrepo.js +56 -7
  85. package/models/fleet.cattle.io.bundle.js +0 -11
  86. package/models/fleet.cattle.io.cluster.js +17 -1
  87. package/models/fleet.cattle.io.gitrepo.js +86 -50
  88. package/models/provisioning.cattle.io.cluster.js +47 -2
  89. package/models/service.js +1 -0
  90. package/models/workload.js +19 -1
  91. package/package.json +5 -4
  92. package/pages/c/_cluster/apps/charts/index.vue +4 -0
  93. package/pages/c/_cluster/explorer/ConfigBadge.vue +8 -7
  94. package/pages/c/_cluster/explorer/index.vue +13 -6
  95. package/pages/c/_cluster/fleet/GitRepoGraphConfig.js +3 -3
  96. package/pages/c/_cluster/fleet/index.vue +75 -89
  97. package/pages/c/_cluster/settings/links.vue +2 -2
  98. package/pages/diagnostic.vue +17 -15
  99. package/pages/home.vue +32 -6
  100. package/plugins/clean-html.js +50 -0
  101. package/plugins/dashboard-store/resource-class.js +4 -0
  102. package/plugins/plugin.js +54 -49
  103. package/plugins/steve/mutations.js +1 -1
  104. package/plugins/steve/steve-class.js +8 -0
  105. package/plugins/steve/steve-pagination-utils.ts +3 -1
  106. package/rancher-components/Accordion/Accordion.vue +4 -4
  107. package/rancher-components/BadgeState/BadgeState.vue +7 -0
  108. package/rancher-components/Card/Card.vue +27 -1
  109. package/rancher-components/Form/Checkbox/Checkbox.vue +9 -2
  110. package/rancher-components/Form/LabeledInput/LabeledInput.test.ts +18 -1
  111. package/rancher-components/Form/LabeledInput/LabeledInput.vue +18 -1
  112. package/rancher-components/Form/ToggleSwitch/ToggleSwitch.vue +39 -2
  113. package/rancher-components/RcButton/RcButton.vue +90 -0
  114. package/rancher-components/RcButton/index.ts +2 -0
  115. package/rancher-components/RcButton/types.ts +17 -0
  116. package/rancher-components/RcDropdown/RcDropdown.vue +111 -0
  117. package/rancher-components/RcDropdown/RcDropdownItem.vue +127 -0
  118. package/rancher-components/RcDropdown/RcDropdownSeparator.vue +6 -0
  119. package/rancher-components/RcDropdown/RcDropdownTrigger.vue +43 -0
  120. package/rancher-components/RcDropdown/index.ts +4 -0
  121. package/rancher-components/RcDropdown/types.ts +22 -0
  122. package/rancher-components/RcDropdown/useDropdownCollection.ts +45 -0
  123. package/rancher-components/RcDropdown/useDropdownContext.ts +83 -0
  124. package/scripts/test-plugins-build.sh +2 -0
  125. package/scripts/typegen.sh +2 -0
  126. package/store/catalog.js +1 -1
  127. package/tsconfig.json +2 -1
  128. package/types/components/paginatedResourceTable.ts +25 -0
  129. package/types/components/resourceLabeledSelect.ts +48 -0
  130. package/types/resources/fleet.d.ts +17 -0
  131. package/types/shell/index.d.ts +61 -0
  132. package/utils/auth.js +5 -1
  133. package/utils/cluster.js +106 -0
  134. package/utils/fleet.ts +35 -3
  135. package/utils/ingress.ts +64 -0
  136. package/utils/uiplugins.ts +56 -44
  137. package/utils/validators/cron-schedule.js +7 -2
  138. package/utils/validators/formRules/__tests__/index.test.ts +53 -17
  139. package/utils/validators/formRules/index.ts +20 -5
  140. package/vue.config.js +1 -1
  141. package/components/RelatedWorkloadsTable.vue +0 -50
@@ -2,6 +2,7 @@ import { parse } from '@shell/utils/url';
2
2
  import { CATALOG } from '@shell/config/labels-annotations';
3
3
  import { insertAt } from '@shell/utils/array';
4
4
  import { CATALOG as CATALOG_TYPE } from '@shell/config/types';
5
+ import { colorForState, stateDisplay } from '@shell/plugins/dashboard-store/resource-class';
5
6
 
6
7
  import SteveModel from '@shell/plugins/steve/steve-class';
7
8
 
@@ -12,18 +13,40 @@ export default class ClusterRepo extends SteveModel {
12
13
  }
13
14
  }
14
15
 
16
+ get _isClusterRepoDisabled() {
17
+ return this.spec?.enabled === false;
18
+ }
19
+
15
20
  get _availableActions() {
16
21
  const out = super._availableActions;
17
22
 
18
23
  insertAt(out, 0, { divider: true });
19
24
 
20
- insertAt(out, 0, {
21
- action: 'refresh',
22
- label: this.t('action.refresh'),
23
- icon: 'icon icon-refresh',
24
- enabled: !!this.links.update,
25
- bulkable: true,
26
- });
25
+ if (this._isClusterRepoDisabled) {
26
+ insertAt(out, 1, {
27
+ action: 'enableClusterRepo',
28
+ label: this.t('action.enable'),
29
+ icon: 'icon icon-play',
30
+ enabled: true,
31
+ bulkable: true,
32
+ });
33
+ } else {
34
+ insertAt(out, 1, {
35
+ action: 'disableClusterRepo',
36
+ label: this.t('action.disable'),
37
+ icon: 'icon icon-pause',
38
+ enabled: true,
39
+ bulkable: true,
40
+ });
41
+
42
+ insertAt(out, 0, {
43
+ action: 'refresh',
44
+ label: this.t('action.refresh'),
45
+ icon: 'icon icon-refresh',
46
+ enabled: !!this.links.update,
47
+ bulkable: true,
48
+ });
49
+ }
27
50
 
28
51
  return out;
29
52
  }
@@ -39,6 +62,16 @@ export default class ClusterRepo extends SteveModel {
39
62
  this.$dispatch('catalog/load', { force: true, reset: true }, { root: true });
40
63
  }
41
64
 
65
+ async disableClusterRepo() {
66
+ this.spec.enabled = false;
67
+ await this.save();
68
+ }
69
+
70
+ async enableClusterRepo() {
71
+ this.spec.enabled = true;
72
+ await this.save();
73
+ }
74
+
42
75
  get isGit() {
43
76
  return !!this.spec?.gitRepo;
44
77
  }
@@ -157,6 +190,22 @@ export default class ClusterRepo extends SteveModel {
157
190
  } : undefined;
158
191
  }
159
192
 
193
+ get stateDisplay() {
194
+ if (this._isClusterRepoDisabled) {
195
+ return this.t('generic.disabled');
196
+ } else {
197
+ return stateDisplay(this.state);
198
+ }
199
+ }
200
+
201
+ get stateBackground() {
202
+ if (this._isClusterRepoDisabled) {
203
+ return 'badge-disabled';
204
+ } else {
205
+ return colorForState(this.state, this.stateObj?.error, this.stateObj?.transitioning).replace('text-', 'bg-');
206
+ }
207
+ }
208
+
160
209
  waitForOperation(operationId, timeout, interval = 2000) {
161
210
  return this.waitForTestFn(() => {
162
211
  if (!this.$getters['schemaFor'](CATALOG_TYPE.OPERATION)) {
@@ -6,17 +6,6 @@ import { FLEET } from '@shell/config/types';
6
6
  import { convertSelectorObj, matching } from '@shell/utils/selector';
7
7
 
8
8
  export default class FleetBundle extends SteveModel {
9
- get deploymentInfo() {
10
- const ready = this.status?.summary?.ready || 0;
11
- const total = this.status?.summary?.desiredReady || 0;
12
-
13
- return {
14
- ready,
15
- unready: total - ready,
16
- total
17
- };
18
- }
19
-
20
9
  get lastUpdateTime() {
21
10
  return this.status?.conditions?.[0].lastUpdateTime;
22
11
  }
@@ -1,5 +1,5 @@
1
1
  import { LOCAL_CLUSTER, MANAGEMENT, NORMAN } from '@shell/config/types';
2
- import { CAPI, FLEET as FLEET_LABELS } from '@shell/config/labels-annotations';
2
+ import { CAPI, FLEET as FLEET_LABELS, SYSTEM_LABELS } from '@shell/config/labels-annotations';
3
3
  import { _RKE2 } from '@shell/store/prefs';
4
4
  import SteveModel from '@shell/plugins/steve/steve-class';
5
5
  import { escapeHtml } from '@shell/utils/string';
@@ -190,6 +190,22 @@ export default class FleetCluster extends SteveModel {
190
190
  }
191
191
  }
192
192
 
193
+ get customLabels() {
194
+ const parsedLabels = [];
195
+
196
+ if (this.labels) {
197
+ for (const k in this.labels) {
198
+ const [prefix] = k.split('/');
199
+
200
+ if (!SYSTEM_LABELS.includes(prefix) && k !== CAPI.PROVIDER) {
201
+ parsedLabels.push(`${ k }=${ this.labels[k] }`);
202
+ }
203
+ }
204
+ }
205
+
206
+ return parsedLabels;
207
+ }
208
+
193
209
  async saveYaml(yaml) {
194
210
  await this._saveYaml(yaml);
195
211
 
@@ -1,5 +1,6 @@
1
1
  import { convert, matching, convertSelectorObj } from '@shell/utils/selector';
2
2
  import jsyaml from 'js-yaml';
3
+ import isEmpty from 'lodash/isEmpty';
3
4
  import { escapeHtml } from '@shell/utils/string';
4
5
  import { FLEET } from '@shell/config/types';
5
6
  import { FLEET as FLEET_ANNOTATIONS } from '@shell/config/labels-annotations';
@@ -7,7 +8,7 @@ import { addObject, addObjects, findBy, insertAt } from '@shell/utils/array';
7
8
  import { set } from '@shell/utils/object';
8
9
  import SteveModel from '@shell/plugins/steve/steve-class';
9
10
  import {
10
- colorForState, mapStateToEnum, primaryDisplayStatusFromCount, stateDisplay, stateSort
11
+ colorForState, mapStateToEnum, primaryDisplayStatusFromCount, stateDisplay, STATES_ENUM, stateSort,
11
12
  } from '@shell/plugins/dashboard-store/resource-class';
12
13
  import { NAME } from '@shell/config/product/explorer';
13
14
  import FleetUtils from '@shell/utils/fleet';
@@ -20,6 +21,26 @@ function quacksLikeAHash(str) {
20
21
  return false;
21
22
  }
22
23
 
24
+ function normalizeStateCounts(data) {
25
+ if (isEmpty(data)) {
26
+ return {
27
+ total: 0,
28
+ states: {},
29
+ };
30
+ }
31
+ const { desiredReady, ...rest } = data ;
32
+ const states = Object.entries(rest).reduce((res, [key, value]) => {
33
+ res[mapStateToEnum(key)] = value;
34
+
35
+ return res;
36
+ }, {});
37
+
38
+ return {
39
+ total: desiredReady,
40
+ states,
41
+ };
42
+ }
43
+
23
44
  export default class GitRepo extends SteveModel {
24
45
  applyDefaults() {
25
46
  const spec = this.spec || {};
@@ -305,18 +326,7 @@ export default class GitRepo extends SteveModel {
305
326
  }
306
327
 
307
328
  get bundles() {
308
- const all = this.$getters['all'](FLEET.BUNDLE);
309
-
310
- return all.filter((bundle) => bundle.repoName === this.name &&
311
- bundle.namespace === this.namespace &&
312
- bundle.namespacedName.startsWith(`${ this.namespace }:${ this.name }`));
313
- }
314
-
315
- /**
316
- * Bundles with state of active
317
- */
318
- get bundlesReady() {
319
- return this.bundles?.filter((bundle) => bundle.state === 'active');
329
+ return this.$getters['matching'](FLEET.BUNDLE, { 'fleet.cattle.io/repo-name': this.name }, this.namespace);
320
330
  }
321
331
 
322
332
  get bundleDeployments() {
@@ -325,6 +335,65 @@ export default class GitRepo extends SteveModel {
325
335
  return bds.filter((bd) => bd.metadata?.labels?.['fleet.cattle.io/repo-name'] === this.name);
326
336
  }
327
337
 
338
+ get allBundlesStatuses() {
339
+ const bundleDeploymentCountsPerBundle = this.bundleDeployments.reduce((acc, bd) => {
340
+ const bundleId = FleetUtils.bundleIdFromBundleDeploymentLabels(bd.metadata?.labels);
341
+ const state = mapStateToEnum(FleetUtils.bundleDeploymentState(bd));
342
+
343
+ if (!acc[bundleId]) {
344
+ acc[bundleId] = {
345
+ total: 0,
346
+ states: { [STATES_ENUM.READY]: 0 },
347
+ };
348
+ }
349
+ acc[bundleId].total++;
350
+
351
+ if (!acc[bundleId].states[state]) {
352
+ acc[bundleId].states[state] = 0;
353
+ }
354
+ acc[bundleId].states[state]++;
355
+
356
+ return acc;
357
+ }, {});
358
+ const bundleIds = Object.keys(bundleDeploymentCountsPerBundle);
359
+
360
+ return bundleIds.reduce((acc, bundleId) => {
361
+ const state = primaryDisplayStatusFromCount(bundleDeploymentCountsPerBundle[bundleId].states);
362
+
363
+ if (!acc.states[state]) {
364
+ acc.states[state] = 0;
365
+ }
366
+ acc.states[state]++;
367
+
368
+ return acc;
369
+ }, { total: bundleIds.length, states: { [STATES_ENUM.READY]: 0 } } );
370
+ }
371
+
372
+ get allResourceStatuses() {
373
+ return normalizeStateCounts(this.status?.resourceCounts || {});
374
+ }
375
+
376
+ statusResourceCountsForCluster(clusterId) {
377
+ if (!this.targetClusters.some((c) => c.id === clusterId)) {
378
+ return {};
379
+ }
380
+
381
+ return this.bundleDeployments
382
+ .filter((bd) => FleetUtils.clusterIdFromBundleDeploymentLabels(bd.metadata?.labels) === clusterId)
383
+ .map((bd) => FleetUtils.resourcesFromBundleDeploymentStatus(bd.status))
384
+ .flat()
385
+ .map((r) => r.state)
386
+ .reduce((prev, state) => {
387
+ if (!prev[state]) {
388
+ prev[state] = 0;
389
+ }
390
+ prev[state]++;
391
+ prev.desiredReady++;
392
+
393
+ return prev;
394
+ }, { desiredReady: 0 });
395
+ }
396
+
328
397
  get resourcesStatuses() {
329
398
  const bundleDeployments = this.bundleDeployments || [];
330
399
  const clusters = (this.targetClusters || []).reduce((res, c) => {
@@ -357,7 +426,7 @@ export default class GitRepo extends SteveModel {
357
426
  name: `c-cluster-product-resource${ r.namespace ? '-namespace' : '' }-id`,
358
427
  params: {
359
428
  product: NAME,
360
- cluster: c.metadata.labels[FLEET_ANNOTATIONS.CLUSTER_NAME],
429
+ cluster: c.metadata.labels[FLEET_ANNOTATIONS.CLUSTER_NAME], // explorer uses the "management" Cluster name, which differs from the Fleet Cluster name
361
430
  resource: type,
362
431
  namespace: r.namespace,
363
432
  id: r.name,
@@ -385,7 +454,6 @@ export default class GitRepo extends SteveModel {
385
454
  creationTimestamp: r.createdAt,
386
455
 
387
456
  // other properties
388
- clusterLabel: c.metadata.labels[FLEET_ANNOTATIONS.CLUSTER_NAME],
389
457
  stateBackground: color,
390
458
  stateDisplay: display,
391
459
  stateSort: stateSort(color, display),
@@ -408,42 +476,10 @@ export default class GitRepo extends SteveModel {
408
476
  };
409
477
  }
410
478
 
411
- get clusterResourceStatus() {
412
- const clusterStatuses = this.resourcesStatuses.reduce((prev, curr) => {
413
- const { clusterId, clusterLabel, state } = curr;
414
-
415
- if (!prev[clusterId]) {
416
- prev[clusterId] = {
417
- clusterLabel,
418
- resourceCounts: { [state]: 0, desiredReady: 0 }
419
-
420
- };
421
- }
422
-
423
- if (!prev[clusterId].resourceCounts[state]) {
424
- prev[clusterId].resourceCounts[state] = 0;
425
- }
426
-
427
- prev[clusterId].resourceCounts[state] += 1;
428
- prev[clusterId].resourceCounts.desiredReady += 1;
429
-
430
- return prev;
431
- }, {});
432
-
433
- const values = Object.keys(clusterStatuses).map((key) => {
434
- const { clusterLabel, resourceCounts } = clusterStatuses[key];
435
-
436
- return {
437
- clusterId: key,
438
- clusterLabel, // FLEET LABEL
439
- status: {
440
- displayStatus: primaryDisplayStatusFromCount(resourceCounts),
441
- resourceCounts: { ...resourceCounts }
442
- }
443
- };
444
- });
479
+ clusterState(clusterId) {
480
+ const resourceCounts = this.statusResourceCountsForCluster(clusterId);
445
481
 
446
- return values;
482
+ return primaryDisplayStatusFromCount(resourceCounts) || STATES_ENUM.ACTIVE;
447
483
  }
448
484
 
449
485
  get clustersList() {
@@ -5,7 +5,7 @@ import SteveModel from '@shell/plugins/steve/steve-class';
5
5
  import { findBy } from '@shell/utils/array';
6
6
  import { get, set } from '@shell/utils/object';
7
7
  import { sortBy } from '@shell/utils/sort';
8
- import { ucFirst } from '@shell/utils/string';
8
+ import { escapeHtml, ucFirst } from '@shell/utils/string';
9
9
  import { compare } from '@shell/utils/version';
10
10
  import { AS, MODE, _VIEW, _YAML } from '@shell/config/query-params';
11
11
  import { HARVESTER_NAME as HARVESTER } from '@shell/config/features';
@@ -178,7 +178,15 @@ export default class ProvCluster extends SteveModel {
178
178
  });
179
179
  }
180
180
 
181
- return actions.concat(out);
181
+ const all = actions.concat(out);
182
+
183
+ // If we have a helper that wants to modify the available actions, let it do it
184
+ if (this.customProvisionerHelper?.availableActions) {
185
+ // Provider can either modify the provided list or return one of its own
186
+ return this.customProvisionerHelper?.availableActions(this, all) || all;
187
+ }
188
+
189
+ return all;
182
190
  }
183
191
 
184
192
  get normanCluster() {
@@ -401,6 +409,11 @@ export default class ProvCluster extends SteveModel {
401
409
  }
402
410
 
403
411
  get provisionerDisplay() {
412
+ // Allow a model extension to override the display of the provisioner
413
+ if (this.customProvisionerHelper?.provisionerDisplay) {
414
+ return this.customProvisionerHelper?.provisionerDisplay(this);
415
+ }
416
+
404
417
  let provisioner = (this.provisioner || '').toLowerCase();
405
418
 
406
419
  // RKE provisioner can actually do K3s too...
@@ -496,6 +509,10 @@ export default class ProvCluster extends SteveModel {
496
509
  }
497
510
 
498
511
  get machineProviderDisplay() {
512
+ if (this.customProvisionerHelper?.machineProviderDisplay) {
513
+ return this.customProvisionerHelper?.machineProviderDisplay(this);
514
+ }
515
+
499
516
  if ( this.isImported ) {
500
517
  return null;
501
518
  }
@@ -954,6 +971,34 @@ export default class ProvCluster extends SteveModel {
954
971
  if ( res?._status === 204 ) {
955
972
  await this.$dispatch('ws.resource.remove', { data: this });
956
973
  }
974
+
975
+ // If this cluster has a custom provisioner, allow it to do custom deletion
976
+ if (this.customProvisionerHelper?.postDelete) {
977
+ return this.customProvisionerHelper?.postDelete(this);
978
+ }
979
+ }
980
+
981
+ /**
982
+ * Get the custom provisioner helper for this model
983
+ */
984
+ get customProvisionerHelper() {
985
+ // Find the first model extension that says it can be used for this model
986
+ return this.modelExtensions.find((modelExt) => modelExt.useFor ? modelExt.useFor(this) : false);
987
+ }
988
+
989
+ get groupByParent() {
990
+ // Customer helper can report if the cluster has a parent cluster
991
+ return this.customProvisionerHelper?.parentCluster?.(this);
992
+ }
993
+
994
+ get groupByLabel() {
995
+ const name = this.groupByParent;
996
+
997
+ if (name) {
998
+ return this.$rootGetters['i18n/t']('resourceTable.groupLabel.cluster', { name: escapeHtml(name) });
999
+ } else {
1000
+ return this.$rootGetters['i18n/t']('resourceTable.groupLabel.notInACluster');
1001
+ }
957
1002
  }
958
1003
 
959
1004
  get hasError() {
package/models/service.js CHANGED
@@ -138,6 +138,7 @@ export default class extends SteveModel {
138
138
 
139
139
  async fetchPods() {
140
140
  if (this.podRelationship) {
141
+ // Used in conjunction with `matches/match/label selectors`. Requires https://github.com/rancher/dashboard/issues/10417 to fix
141
142
  await this.$dispatch('cluster/findMatching', {
142
143
  type: POD,
143
144
  selector: this.podRelationship.selector,
@@ -554,6 +554,7 @@ export default class Workload extends WorkloadService {
554
554
  if (podRelationship) {
555
555
  const pods = this.$getters['podsByNamespace'](this.metadata.namespace);
556
556
 
557
+ // Used in conjunction with `matches/match/label selectors`. Requires https://github.com/rancher/dashboard/issues/10417 to fix
557
558
  return pods.filter((obj) => {
558
559
  return matches(obj, podRelationship.selector);
559
560
  });
@@ -594,6 +595,23 @@ export default class Workload extends WorkloadService {
594
595
  return (get(this, 'metadata.relationships') || []).filter((relationship) => relationship.toType === WORKLOAD_TYPES.JOB);
595
596
  }
596
597
 
598
+ /**
599
+ * Ensure the store has all matching jobs
600
+ */
601
+ async matchingJobs() {
602
+ if (this.type !== WORKLOAD_TYPES.CRON_JOB) {
603
+ return undefined;
604
+ }
605
+
606
+ // This will be 1 request per relationship, though there's not likely to be many per cron job
607
+ return Promise.all(this.jobRelationships.map((obj) => {
608
+ return this.$dispatch('find', { type: WORKLOAD_TYPES.JOB, id: obj.toId });
609
+ }));
610
+ }
611
+
612
+ /**
613
+ * Expects all required pods are fetched upfront
614
+ */
597
615
  get jobs() {
598
616
  if (this.type !== WORKLOAD_TYPES.CRON_JOB) {
599
617
  return undefined;
@@ -643,12 +661,12 @@ export default class Workload extends WorkloadService {
643
661
  }
644
662
 
645
663
  async matchingPods() {
664
+ // Used in conjunction with `matches/match/label selectors`. Requires https://github.com/rancher/dashboard/issues/10417 to fix
646
665
  const all = await this.$dispatch('findAll', { type: POD });
647
666
  const allInNamespace = all.filter((pod) => pod.metadata.namespace === this.metadata.namespace);
648
667
 
649
668
  const selector = convertSelectorObj(this.spec.selector);
650
669
 
651
- // See https://github.com/rancher/dashboard/issues/10417, all pods bad, need to replace local selector somehow
652
670
  return matching(allInNamespace, selector);
653
671
  }
654
672
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rancher/shell",
3
- "version": "3.0.2-rc.2",
3
+ "version": "3.0.2-rc.3",
4
4
  "description": "Rancher Dashboard Shell",
5
5
  "repository": "https://github.com/rancherlabs/dashboard",
6
6
  "license": "Apache-2.0",
@@ -31,7 +31,7 @@
31
31
  "@aws-sdk/client-eks": "3.1.0",
32
32
  "@aws-sdk/client-iam": "3.658.1",
33
33
  "@aws-sdk/client-kms": "3.8.1",
34
- "@babel/plugin-proposal-optional-chaining": "7.14.5",
34
+ "@babel/plugin-proposal-optional-chaining": "7.21.0",
35
35
  "@babel/plugin-proposal-private-methods": "7.18.6",
36
36
  "@babel/plugin-proposal-private-property-in-object": "7.14.5",
37
37
  "@babel/preset-typescript": "7.16.7",
@@ -64,7 +64,7 @@
64
64
  "cookie": "0.7.0",
65
65
  "core-js": "3.40.0",
66
66
  "cron-validator": "1.3.1",
67
- "cronstrue": "2.50.0",
67
+ "cronstrue": "2.53.0",
68
68
  "cross-env": "7.0.3",
69
69
  "css-loader": "6.7.3",
70
70
  "csv-loader": "3.0.3",
@@ -91,6 +91,7 @@
91
91
  "express": "4.17.1",
92
92
  "file-saver": "2.0.2",
93
93
  "floating-vue": "5.2.2",
94
+ "focus-trap": "7.6.2",
94
95
  "frontmatter-markdown-loader": "3.7.0",
95
96
  "identicon.js": "2.3.3",
96
97
  "intl-messageformat": "7.8.4",
@@ -120,7 +121,7 @@
120
121
  "set-cookie-parser": "2.4.6",
121
122
  "shell-quote": "1.7.3",
122
123
  "sinon": "8.1.1",
123
- "start-server-and-test": "1.13.1",
124
+ "start-server-and-test": "2.0.10",
124
125
  "style-loader": "1.2.1",
125
126
  "ts-node": "8.10.2",
126
127
  "typescript": "5.6.3",
@@ -455,6 +455,8 @@ export default {
455
455
  class="input-sm"
456
456
  :placeholder="t('catalog.charts.search')"
457
457
  data-testid="charts-filter-input"
458
+ :aria-label="t('catalog.charts.search')"
459
+ role="textbox"
458
460
  >
459
461
 
460
462
  <button
@@ -463,6 +465,8 @@ export default {
463
465
  @shortkey="focusSearch()"
464
466
  />
465
467
  <AsyncButton
468
+ role="button"
469
+ :aria-label="t('catalog.charts.refresh')"
466
470
  class="refresh-btn"
467
471
  mode="refresh"
468
472
  size="sm"
@@ -24,18 +24,23 @@ export default {
24
24
  </script>
25
25
 
26
26
  <template>
27
- <div class="config-badge">
27
+ <div
28
+ class="config-badge"
29
+ >
28
30
  <div>
29
- <a
31
+ <button
30
32
  class="badge-install btn btn-sm role-secondary"
31
33
  data-testid="add-custom-cluster-badge"
34
+ role="button"
35
+ tabindex="0"
32
36
  @click="customBadgeDialog"
37
+ @keyup.space="customBadgeDialog"
33
38
  >
34
39
  <i
35
40
  v-clean-tooltip="tooltip"
36
41
  class="icon icon-brush-icon"
37
42
  />
38
- </a>
43
+ </button>
39
44
  </div>
40
45
  </div>
41
46
  </template>
@@ -55,10 +60,6 @@ export default {
55
60
  > I {
56
61
  line-height: inherit;
57
62
  }
58
-
59
- &:focus {
60
- outline: 0;
61
- }
62
63
  }
63
64
 
64
65
  </style>
@@ -641,12 +641,14 @@ export default {
641
641
  <div data-testid="clusterProvider__label">
642
642
  <label>{{ t('glance.provider') }}: </label>
643
643
  <span v-if="isHarvesterCluster">
644
- <a
644
+ <button
645
+ class="btn role-link harvester-cluster-link"
645
646
  role="button"
647
+ :aria-label="displayProvider"
646
648
  @click="goToHarvesterCluster"
647
649
  >
648
650
  {{ displayProvider }}
649
- </a>
651
+ </button>
650
652
  </span>
651
653
  <span v-else>
652
654
  {{ displayProvider }}
@@ -682,6 +684,8 @@ export default {
682
684
  <router-link
683
685
  :to="{name: 'c-cluster-explorer-tools'}"
684
686
  class="cluster-tools-link"
687
+ role="link"
688
+ :aria-label="t('nav.clusterTools')"
685
689
  >
686
690
  <span>{{ t('nav.clusterTools') }}</span>
687
691
  </router-link>
@@ -871,6 +875,13 @@ export default {
871
875
  grid-row-gap: 20px;
872
876
  }
873
877
 
878
+ .harvester-cluster-link {
879
+ line-height: inherit;
880
+ min-height: inherit;
881
+ padding: 0;
882
+ vertical-align: bottom;
883
+ }
884
+
874
885
  @media only screen and (max-width: map-get($breakpoints, "--viewport-9")) {
875
886
  .extension-card-container {
876
887
  grid-template-columns: 1fr !important;
@@ -935,10 +946,6 @@ export default {
935
946
  line-height: inherit;
936
947
  margin-right: 4px;
937
948
  }
938
-
939
- &:focus {
940
- outline: 0;
941
- }
942
949
  }
943
950
 
944
951
  .cert-table-link {
@@ -77,7 +77,7 @@ export const gitRepoGraphConfig = {
77
77
  id: bd.id,
78
78
  matchingId: bd.id,
79
79
  type: bd.type,
80
- clusterId: cluster ? cluster.id : undefined,
80
+ clusterLabel: cluster ? cluster.namespacedName : undefined,
81
81
  clusterDetailLocation: cluster ? cluster.detailLocation : undefined,
82
82
  state: bd.state,
83
83
  stateLabel: bd.stateDisplay,
@@ -210,7 +210,7 @@ export const gitRepoGraphConfig = {
210
210
  type: 'title-link',
211
211
  labelKey: 'fleet.fdc.id',
212
212
  valueObj: {
213
- id: data.id,
213
+ label: data.id,
214
214
  detailLocation: data.detailLocation
215
215
  }
216
216
  }
@@ -221,7 +221,7 @@ export const gitRepoGraphConfig = {
221
221
  type: 'title-link',
222
222
  labelKey: 'fleet.fdc.cluster',
223
223
  valueObj: {
224
- id: data.clusterId,
224
+ label: data.clusterLabel,
225
225
  detailLocation: data.clusterDetailLocation
226
226
  }
227
227
  });