@rancher/shell 3.0.8 → 3.0.9-rc.2

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 (192) hide show
  1. package/apis/intf/modal.ts +38 -0
  2. package/apis/intf/slide-in.ts +3 -1
  3. package/apis/shell/__tests__/slide-in.test.ts +36 -0
  4. package/apis/shell/slide-in.ts +5 -1
  5. package/assets/styles/base/_color.scss +1 -0
  6. package/assets/styles/base/_typography.scss +14 -5
  7. package/assets/styles/themes/_light.scss +1 -1
  8. package/assets/styles/themes/_modern.scss +1 -1
  9. package/assets/translations/en-us.yaml +94 -33
  10. package/assets/translations/zh-hans.yaml +0 -2
  11. package/components/ActionMenuShell.vue +4 -4
  12. package/components/CodeMirror.vue +4 -3
  13. package/components/DetailText.vue +54 -7
  14. package/components/Drawer/Chrome.vue +11 -4
  15. package/components/Drawer/DrawerCard.vue +19 -0
  16. package/components/Drawer/ResourceDetailDrawer/ConfigTab.vue +3 -11
  17. package/components/Drawer/ResourceDetailDrawer/__tests__/ConfigTab.test.ts +2 -2
  18. package/components/Drawer/ResourceDetailDrawer/index.vue +3 -20
  19. package/components/Drawer/types.ts +1 -0
  20. package/components/DynamicContent/DynamicContentCloseButton.vue +2 -2
  21. package/components/LocaleSelector.vue +1 -1
  22. package/components/Markdown.vue +1 -1
  23. package/components/PopoverCard.vue +3 -3
  24. package/components/Resource/Detail/Card/ExtrasCard.vue +39 -0
  25. package/components/Resource/Detail/Card/StateCard/__tests__/composables.test.ts +142 -0
  26. package/components/Resource/Detail/Card/StateCard/composables.ts +41 -11
  27. package/components/Resource/Detail/Card/StateCard/index.vue +3 -9
  28. package/components/Resource/Detail/Card/StateCard/types.ts +6 -0
  29. package/components/Resource/Detail/Card/{PodsCard → StatusCard}/index.vue +11 -10
  30. package/components/Resource/Detail/Card/__tests__/PodsCard.test.ts +24 -25
  31. package/components/Resource/Detail/Cards.vue +27 -0
  32. package/components/Resource/Detail/Masthead/__tests__/index.test.ts +70 -0
  33. package/components/Resource/Detail/Masthead/index.vue +5 -0
  34. package/components/Resource/Detail/Metadata/KeyValueRow.vue +4 -2
  35. package/components/Resource/Detail/ResourcePopover/ResourcePopoverCard.vue +2 -2
  36. package/components/Resource/Detail/ResourceRow.types.ts +14 -0
  37. package/components/Resource/Detail/ResourceRow.vue +23 -35
  38. package/components/Resource/Detail/StatusRow.vue +5 -2
  39. package/components/Resource/Detail/TitleBar/__tests__/composables.test.ts +38 -7
  40. package/components/Resource/Detail/TitleBar/__tests__/index.test.ts +106 -2
  41. package/components/Resource/Detail/TitleBar/composables.ts +2 -1
  42. package/components/Resource/Detail/TitleBar/index.vue +41 -6
  43. package/components/ResourceDetail/Masthead/__tests__/index.test.ts +49 -1
  44. package/components/ResourceDetail/Masthead/__tests__/latest.test.ts +85 -0
  45. package/components/ResourceDetail/Masthead/index.vue +1 -0
  46. package/components/ResourceDetail/Masthead/latest.vue +8 -1
  47. package/components/ResourceDetail/Masthead/legacy.vue +1 -1
  48. package/components/Setting.vue +1 -1
  49. package/components/SortableTable/index.vue +25 -0
  50. package/components/SortableTable/selection.js +25 -12
  51. package/components/SortableTable/sorting.js +1 -1
  52. package/components/Tabbed/Tab.vue +1 -0
  53. package/components/Tabbed/index.vue +29 -6
  54. package/components/Window/ContainerShell.vue +10 -13
  55. package/components/fleet/FleetClusterTargets/TargetsList.vue +47 -29
  56. package/components/fleet/FleetClusterTargets/index.vue +82 -29
  57. package/components/fleet/FleetClusters.vue +26 -12
  58. package/components/fleet/FleetGitRepoPaths.vue +2 -2
  59. package/components/fleet/FleetResources.vue +14 -0
  60. package/components/fleet/FleetValuesFrom.vue +2 -2
  61. package/components/fleet/__tests__/FleetClusterTargets.test.ts +531 -0
  62. package/components/fleet/__tests__/FleetClusters.test.ts +576 -0
  63. package/components/fleet/dashboard/ResourceDetails.vue +96 -123
  64. package/components/form/Conditions.vue +1 -15
  65. package/components/form/HookOption.vue +5 -0
  66. package/components/form/LabeledSelect.vue +1 -1
  67. package/components/form/LifecycleHooks.vue +2 -6
  68. package/components/form/ResourceLabeledSelect.vue +12 -1
  69. package/components/form/SeccompProfile.vue +113 -0
  70. package/components/form/Security.vue +244 -133
  71. package/components/form/__tests__/LabeledSelect.test.ts +1 -1
  72. package/components/form/__tests__/SeccompProfile.test.js +124 -0
  73. package/components/form/__tests__/Security.test.ts +125 -37
  74. package/components/formatter/Autoscaler.vue +2 -2
  75. package/components/formatter/FleetSummaryGraph.vue +4 -1
  76. package/components/nav/Group.vue +5 -0
  77. package/components/nav/Header.vue +3 -3
  78. package/components/nav/HeaderPageActionMenu.vue +1 -1
  79. package/components/nav/NamespaceFilter.vue +6 -6
  80. package/components/nav/NotificationCenter/index.vue +1 -1
  81. package/components/nav/TopLevelMenu.helper.ts +41 -16
  82. package/components/nav/TopLevelMenu.vue +45 -25
  83. package/components/nav/WorkspaceSwitcher.vue +1 -1
  84. package/components/nav/__tests__/TopLevelMenu.helper.test.ts +277 -0
  85. package/components/nav/__tests__/TopLevelMenu.test.ts +160 -4
  86. package/components/templates/default.vue +0 -3
  87. package/components/templates/home.vue +0 -3
  88. package/components/templates/plain.vue +0 -3
  89. package/composables/useClickOutside.ts +1 -1
  90. package/config/product/explorer.js +1 -2
  91. package/config/types.js +41 -8
  92. package/detail/__tests__/workload.test.ts +8 -16
  93. package/detail/catalog.cattle.io.app.vue +6 -0
  94. package/detail/fleet.cattle.io.cluster.vue +6 -0
  95. package/detail/workload/index.vue +7 -109
  96. package/edit/__tests__/projectsecret.test.ts +42 -0
  97. package/edit/auth/__tests__/oidc.test.ts +50 -0
  98. package/edit/auth/oidc.vue +68 -44
  99. package/edit/autoscaling.horizontalpodautoscaler/index.vue +140 -59
  100. package/edit/autoscaling.horizontalpodautoscaler/metrics-row.vue +41 -5
  101. package/edit/projectsecret.vue +29 -0
  102. package/edit/provisioning.cattle.io.cluster/__tests__/Basics.test.ts +89 -200
  103. package/edit/provisioning.cattle.io.cluster/__tests__/Networking.test.ts +58 -17
  104. package/edit/provisioning.cattle.io.cluster/rke2.vue +11 -0
  105. package/edit/provisioning.cattle.io.cluster/tabs/Basics.vue +3 -63
  106. package/edit/provisioning.cattle.io.cluster/tabs/networking/index.vue +82 -14
  107. package/edit/workload/__tests__/index.test.ts +122 -85
  108. package/edit/workload/index.vue +48 -29
  109. package/edit/workload/mixins/workload.js +85 -32
  110. package/list/catalog.cattle.io.clusterrepo.vue +1 -1
  111. package/list/projectsecret.vue +2 -2
  112. package/machine-config/__tests__/vmwarevsphere.test.ts +64 -0
  113. package/machine-config/amazonec2.vue +2 -2
  114. package/machine-config/vmwarevsphere.vue +58 -4
  115. package/mixins/__tests__/brand.spec.ts +18 -13
  116. package/mixins/__tests__/chart.test.ts +63 -0
  117. package/mixins/chart.js +56 -51
  118. package/models/__tests__/catalog.cattle.io.app.test.ts +33 -0
  119. package/models/__tests__/workload.test.ts +333 -0
  120. package/models/catalog.cattle.io.app.js +8 -0
  121. package/models/pod.js +14 -0
  122. package/models/secret.js +1 -1
  123. package/models/workload.js +93 -27
  124. package/package.json +4 -4
  125. package/pages/c/_cluster/apps/charts/__tests__/install.test.ts +91 -0
  126. package/pages/c/_cluster/apps/charts/install.vue +4 -4
  127. package/pages/c/_cluster/explorer/EventsTable.vue +2 -2
  128. package/pages/c/_cluster/fleet/index.vue +18 -12
  129. package/pages/c/_cluster/manager/hostedprovider/index.vue +1 -19
  130. package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +1 -1
  131. package/pages/c/_cluster/uiplugins/index.vue +1 -1
  132. package/plugins/dashboard-store/__tests__/resource-class.test.ts +234 -0
  133. package/plugins/dashboard-store/actions.js +9 -8
  134. package/plugins/dashboard-store/resource-class.js +97 -1
  135. package/plugins/steve/__tests__/revision.test.ts +84 -0
  136. package/plugins/steve/__tests__/steve-pagination-utils.test.ts +30 -0
  137. package/plugins/steve/__tests__/subscribe.spec.ts +134 -0
  138. package/plugins/steve/mutations.js +9 -0
  139. package/plugins/steve/revision.ts +26 -0
  140. package/plugins/steve/steve-pagination-utils.ts +6 -5
  141. package/plugins/steve/subscribe.js +211 -51
  142. package/plugins/subscribe-events.ts +2 -2
  143. package/rancher-components/Form/Checkbox/Checkbox.vue +13 -0
  144. package/rancher-components/LabeledTooltip/LabeledTooltip.vue +1 -1
  145. package/rancher-components/Pill/RcCounterBadge/RcCounterBadge.vue +1 -1
  146. package/rancher-components/Pill/RcStatusBadge/RcStatusBadge.vue +3 -1
  147. package/rancher-components/Pill/RcStatusIndicator/RcStatusIndicator.vue +3 -1
  148. package/rancher-components/Pill/RcTag/RcTag.vue +1 -1
  149. package/rancher-components/Pill/index.ts +4 -0
  150. package/rancher-components/RcButton/RcButton.test.ts +53 -9
  151. package/rancher-components/RcButton/RcButton.vue +217 -25
  152. package/rancher-components/RcButton/types.ts +27 -1
  153. package/rancher-components/RcDropdown/RcDropdownMenu.vue +4 -4
  154. package/rancher-components/RcDropdown/types.ts +3 -3
  155. package/rancher-components/RcIcon/RcIcon.test.ts +42 -0
  156. package/rancher-components/RcIcon/RcIcon.vue +9 -6
  157. package/rancher-components/RcIcon/types.ts +13 -9
  158. package/rancher-components/utils/status.test.ts +10 -15
  159. package/rancher-components/utils/status.ts +5 -6
  160. package/store/aws.js +18 -12
  161. package/store/index.js +4 -8
  162. package/store/type-map.utils.ts +1 -1
  163. package/types/kube/kube-api.ts +29 -3
  164. package/types/rancher/steve.api.ts +40 -0
  165. package/types/shell/index.d.ts +99 -0
  166. package/types/store/dashboard-store.types.ts +29 -7
  167. package/types/store/pagination.types.ts +1 -0
  168. package/types/store/subscribe-events.types.ts +1 -0
  169. package/utils/__tests__/azure.test.ts +56 -0
  170. package/utils/__tests__/back-off.test.ts +364 -245
  171. package/utils/__tests__/error.test.ts +44 -0
  172. package/utils/__tests__/fleet.test.ts +8 -1
  173. package/utils/__tests__/pagination-wrapper.test.ts +167 -0
  174. package/utils/__tests__/version.test.ts +55 -1
  175. package/utils/azure.js +12 -0
  176. package/utils/back-off.ts +302 -69
  177. package/utils/cspAdaptor.ts +32 -14
  178. package/utils/dynamic-content/__tests__/index.test.ts +1 -1
  179. package/utils/dynamic-content/__tests__/new-release.test.ts +48 -7
  180. package/utils/dynamic-content/__tests__/support-notice.test.ts +1 -4
  181. package/utils/dynamic-content/index.ts +1 -6
  182. package/utils/dynamic-content/new-release.ts +5 -3
  183. package/utils/dynamic-content/types.d.ts +0 -1
  184. package/utils/error.js +9 -0
  185. package/utils/fleet.ts +2 -2
  186. package/utils/inactivity.ts +2 -3
  187. package/utils/pagination-wrapper.ts +101 -17
  188. package/utils/validators/formRules/index.ts +3 -0
  189. package/utils/version.js +38 -0
  190. package/components/auth/AzureWarning.vue +0 -77
  191. /package/components/Resource/Detail/{Card/PodsCard/Bubble.vue → Bubble.vue} +0 -0
  192. /package/components/Resource/Detail/Card/{PodsCard → StatusCard}/composable.ts +0 -0
@@ -559,7 +559,7 @@ export default {
559
559
  <template v-if="featureDropdownMenu">
560
560
  <ActionMenu
561
561
  v-if="isView"
562
- button-role="multiAction"
562
+ button-variant="multiAction"
563
563
  button-size="compact"
564
564
  :resource="value"
565
565
  data-testid="masthead-action-menu"
@@ -45,7 +45,7 @@ export default {
45
45
  :resource="value.data"
46
46
  :button-aria-label="t('advancedSettings.edit.label')"
47
47
  data-testid="action-button"
48
- button-role="tertiary"
48
+ button-variant="tertiary"
49
49
  />
50
50
  </div>
51
51
  </div>
@@ -1590,6 +1590,18 @@ export default {
1590
1590
  :onRowMouseEnter="onRowMouseEnter"
1591
1591
  :onRowMouseLeave="onRowMouseLeave"
1592
1592
  >
1593
+ <slot
1594
+ :full-colspan="fullColspan"
1595
+ :row="row.row"
1596
+ :show-sub-row="row.row.stateDescription"
1597
+ :sub-matches="subMatches"
1598
+ :keyField="keyField"
1599
+ :componentTestid="componentTestid"
1600
+ :i="i"
1601
+ :onRowMouseEnter="onRowMouseEnter"
1602
+ :onRowMouseLeave="onRowMouseLeave"
1603
+ name="additional-sub-row"
1604
+ />
1593
1605
  <tr
1594
1606
  v-if="row.row.stateDescription"
1595
1607
  :key="row.row[keyField] + '-description'"
@@ -1924,12 +1936,25 @@ export default {
1924
1936
  &.main-row.has-sub-row {
1925
1937
  border-bottom: 0;
1926
1938
  }
1939
+ &.additional-sub-row.has-sub-row {
1940
+ border-bottom: 0;
1941
+ }
1927
1942
 
1928
1943
  // if a main-row is hovered also hover it's sibling sub row. note - the reverse is handled in selection.js
1929
1944
  &.main-row:not(.row-selected):hover + .sub-row {
1930
1945
  background-color: var(--sortable-table-hover-bg);
1931
1946
  }
1932
1947
 
1948
+ // Case with only additional-sub-row
1949
+ &.main-row:not(.row-selected):hover + .additional-sub-row {
1950
+ background-color: var(--sortable-table-hover-bg);
1951
+ }
1952
+
1953
+ // Case with both additional-sub-row and sub-row
1954
+ &.main-row:not(.row-selected):hover + .additional-sub-row + .sub-row{
1955
+ background-color: var(--sortable-table-hover-bg);
1956
+ }
1957
+
1933
1958
  &:last-of-type {
1934
1959
  border-bottom: 0;
1935
1960
  }
@@ -177,24 +177,32 @@ export default {
177
177
  }
178
178
  },
179
179
 
180
- onRowMouseEnter(e) {
180
+ removeOrAddHover(option, e) {
181
+ // Hardcoded logic to not overcomplicate just adding the conditions of next and previous
181
182
  const tr = e.target.closest('TR');
182
183
 
183
- if (tr.classList.contains('sub-row')) {
184
- const trMainRow = tr.previousElementSibling;
184
+ if (tr.classList.contains('sub-row') || tr.classList.contains('additional-sub-row')) {
185
+ const trPreviousRow = tr.previousElementSibling;
186
+ const trNextRow = tr.nextElementSibling;
187
+
188
+ trPreviousRow.classList[option]('sub-row-hovered');
185
189
 
186
- trMainRow.classList.add('sub-row-hovered');
190
+ if (!trPreviousRow.classList.contains('main-row')) {
191
+ const trMainRow = trPreviousRow.previousElementSibling;
192
+
193
+ trMainRow.classList[option]('sub-row-hovered');
194
+ }
195
+ if (trNextRow?.classList.contains('sub-row')) {
196
+ trNextRow.classList[option]('sub-row-hovered');
197
+ }
187
198
  }
188
199
  },
200
+ onRowMouseEnter(e) {
201
+ this.removeOrAddHover('add', e);
202
+ },
189
203
 
190
204
  onRowMouseLeave(e) {
191
- const tr = e.target.closest('TR');
192
-
193
- if (tr.classList.contains('sub-row')) {
194
- const trMainRow = tr.previousElementSibling;
195
-
196
- trMainRow.classList.remove('sub-row-hovered');
197
- }
205
+ this.removeOrAddHover('remove', e);
198
206
  },
199
207
 
200
208
  nodeForEvent(e) {
@@ -486,6 +494,11 @@ export default {
486
494
 
487
495
  this.$nextTick(() => {
488
496
  this.$emit('selection', this.selectedRows);
497
+ if (this.selectedRows && this.selectedRows.length) {
498
+ for ( let i = 0 ; i < this.selectedRows.length ; i++ ) {
499
+ this.updateInput(this.selectedRows[i], true, this.keyField);
500
+ }
501
+ }
489
502
  });
490
503
  },
491
504
 
@@ -505,7 +518,7 @@ export default {
505
518
  let tr = input.closest('tr');
506
519
  let first = true;
507
520
 
508
- while ( tr && (first || tr.classList.contains('sub-row') ) ) {
521
+ while ( tr && (first || tr.classList.contains('sub-row') || tr.classList.contains('additional-sub-row')) ) {
509
522
  if (on) {
510
523
  tr.classList.add('row-selected');
511
524
  } else {
@@ -79,7 +79,7 @@ export default {
79
79
 
80
80
  if ( markedColumn ) {
81
81
  this._defaultSortBy = markedColumn.name;
82
- descending = markedColumn.defaultSortDescending;
82
+ descending = markedColumn.defaultSortDescending || false;
83
83
  } else if ( nameColumn ) {
84
84
  // Use the name column if there is one
85
85
  this._defaultSortBy = nameColumn.name;
@@ -137,6 +137,7 @@ export default {
137
137
  :id="name"
138
138
  :aria-hidden="!active"
139
139
  role="tabpanel"
140
+ :aria-labelledby="`tab-${name}`"
140
141
  >
141
142
  <div
142
143
  v-if="shouldShowHeader"
@@ -83,6 +83,11 @@ export default {
83
83
  componentTestid: {
84
84
  type: String,
85
85
  default: 'tabbed'
86
+ },
87
+
88
+ removeBorders: {
89
+ type: Boolean,
90
+ default: false,
86
91
  }
87
92
  },
88
93
 
@@ -128,7 +133,8 @@ export default {
128
133
  return {
129
134
  tabs: [...parsedExtTabs],
130
135
  extensionTabs: parsedExtTabs,
131
- activeTabName: null
136
+ activeTabName: null,
137
+ tabRefs: {}
132
138
  };
133
139
  },
134
140
 
@@ -263,7 +269,10 @@ export default {
263
269
  this.select(nextName);
264
270
 
265
271
  this.$nextTick(() => {
266
- this.$refs.tablist.focus();
272
+ this.$refs.tablist.removeAttribute('tabindex');
273
+ if (this.tabRefs[nextName]) {
274
+ this.tabRefs[nextName].focus();
275
+ }
267
276
  });
268
277
 
269
278
  function getCyclicalIdx(currentIdx, direction, tabsLength) {
@@ -299,7 +308,8 @@ export default {
299
308
  class="tabbed-container"
300
309
  :class="{
301
310
  'side-tabs': !!sideTabs,
302
- 'tabs-only': tabsOnly
311
+ 'tabs-only': tabsOnly,
312
+ 'remove-borders': removeBorders
303
313
  }"
304
314
  :data-testid="componentTestid"
305
315
  >
@@ -308,7 +318,7 @@ export default {
308
318
  ref="tablist"
309
319
  role="tablist"
310
320
  class="tabs"
311
- :class="{'clearfix':!sideTabs, 'vertical': sideTabs, 'horizontal': !sideTabs}"
321
+ :class="{'clearfix':!sideTabs, 'vertical': sideTabs, 'horizontal': !sideTabs, 'remove-borders': removeBorders}"
312
322
  :data-testid="`${componentTestid}-block`"
313
323
  tabindex="0"
314
324
  @keydown.right.prevent="selectNext(1)"
@@ -323,14 +333,16 @@ export default {
323
333
  :key="tab.name"
324
334
  :data-testid="tab.name"
325
335
  :class="{tab: true, active: tab.active, disabled: tab.disabled, error: (tab.error)}"
326
- role="presentation"
327
336
  >
328
337
  <a
338
+ :id="`tab-${tab.name}`"
339
+ :ref="(el) => { if (el) tabRefs[tab.name] = el; }"
329
340
  :data-testid="`btn-${tab.name}`"
330
341
  :aria-controls="tab.name"
331
342
  :aria-selected="tab.active"
332
343
  :aria-label="tab.labelDisplay || ''"
333
344
  role="tab"
345
+ :tabindex="tab.active ? '0' : '-1'"
334
346
  @click.prevent="select(tab.name, $event)"
335
347
  @keyup.enter.space="select(tab.name, $event)"
336
348
  >
@@ -442,6 +454,17 @@ export default {
442
454
  display: flex;
443
455
  flex-direction: row;
444
456
 
457
+ &.remove-borders {
458
+ border: none;
459
+
460
+ + .tab-container {
461
+ border: none;
462
+ border-top: 1px solid var(--border);
463
+ padding: 0;
464
+ padding-top: 24px;
465
+ }
466
+ }
467
+
445
468
  + .tab-container {
446
469
  border: solid thin var(--border);
447
470
  }
@@ -458,7 +481,7 @@ export default {
458
481
  .tab {
459
482
  position: relative;
460
483
  float: left;
461
- padding: 0 8px 0 0;
484
+ padding: 0 4px 0 4px;
462
485
  cursor: pointer;
463
486
 
464
487
  A {
@@ -172,8 +172,11 @@ export default {
172
172
  await this.$store.dispatch('cluster/find', { type: NODE, id: nodeId });
173
173
  }
174
174
  } catch {}
175
-
176
- await this.setupTerminal();
175
+ try {
176
+ await this.setupTerminal();
177
+ } catch (e) {
178
+ this.errorMsg = e;
179
+ }
177
180
  await this.connect();
178
181
 
179
182
  clearInterval(this.keepAliveTimer);
@@ -234,26 +237,20 @@ export default {
234
237
 
235
238
  this.fitAddon = new addons.fit.FitAddon();
236
239
  this.searchAddon = new addons.search.SearchAddon();
240
+ terminal.loadAddon(this.fitAddon);
241
+ terminal.loadAddon(this.searchAddon);
242
+ terminal.loadAddon(new addons.weblinks.WebLinksAddon());
243
+ terminal.open(this.$refs.xterm);
237
244
 
238
245
  try {
239
246
  this.webglAddon = new addons.webgl.WebglAddon();
247
+ terminal.loadAddon(this.webglAddon);
240
248
  } catch (e) {
241
249
  // Some browsers (Safari) don't support the webgl renderer, so don't use it.
242
250
  this.webglAddon = null;
243
251
  this.canvasAddon = new addons.canvas.CanvasAddon();
244
- }
245
-
246
- terminal.loadAddon(this.fitAddon);
247
- terminal.loadAddon(this.searchAddon);
248
- terminal.loadAddon(new addons.weblinks.WebLinksAddon());
249
- terminal.open(this.$refs.xterm);
250
-
251
- if (this.webglAddon) {
252
- terminal.loadAddon(this.webglAddon);
253
- } else {
254
252
  terminal.loadAddon(this.canvasAddon);
255
253
  }
256
-
257
254
  this.fit();
258
255
  this.flush();
259
256
 
@@ -18,49 +18,67 @@ export default {
18
18
  },
19
19
 
20
20
  computed: {
21
- names() {
22
- const names = this.clusters.map(({ nameDisplay, name }) => nameDisplay || name);
23
- const max = 10;
21
+ clustersRenderList() {
22
+ const clustersRenderList = this.clusters.map(({ nameDisplay, name, detailLocation }) => ({
23
+ name: nameDisplay || name,
24
+ detailLocation,
25
+ }));
24
26
 
25
- const remaining = names.length - max;
26
-
27
- if (remaining > 0) {
28
- return [
29
- ...names.filter((_, i) => i < max),
30
- this.t('fleet.clusterTargets.rules.matching.plusMore', { n: remaining }, true),
31
- ];
32
- }
33
-
34
- return names;
27
+ return clustersRenderList;
35
28
  }
36
29
  }
37
30
  };
38
31
  </script>
39
32
 
40
33
  <template>
41
- <div class="targets-list">
42
- <h3>{{ t('fleet.clusterTargets.rules.matching.title') }}</h3>
43
- <span
44
- v-for="(name, i) in names"
45
- :key="i"
46
- class="row mt-5"
47
- >
48
- {{ name }}
49
- </span>
50
- <span
51
- v-if="!names.length"
52
- class="text-label"
53
- >
54
- {{ emptyLabel || t('fleet.clusterTargets.rules.matching.empty') }}
55
- </span>
34
+ <div class="targets-list-main">
35
+ <h3>{{ t('fleet.clusterTargets.rules.matching.title', { n: clustersRenderList.length }) }}</h3>
36
+ <div class="targets-list-list">
37
+ <span
38
+ v-for="(cluster, i) in clustersRenderList"
39
+ :key="i"
40
+ class="row mt-5"
41
+ >
42
+ <router-link
43
+ :to="cluster.detailLocation"
44
+ target="_blank"
45
+ class="link-main"
46
+ >
47
+ {{ cluster.name }}&nbsp;<i class="link-icon icon icon-external-link" />
48
+ </router-link>
49
+ </span>
50
+ <span
51
+ v-if="!clustersRenderList.length"
52
+ class="text-label"
53
+ >
54
+ {{ emptyLabel || t('fleet.clusterTargets.rules.matching.empty') }}
55
+ </span>
56
+ </div>
56
57
  </div>
57
58
  </template>
58
59
 
59
60
  <style lang="scss" scoped>
60
- .targets-list {
61
+ .targets-list-main {
61
62
  height: 100%;
62
63
  border-radius: 4px;
63
64
  padding: 16px;
64
65
  background-color: var(--tabbed-sidebar-bg);
66
+ display: flex;
67
+ flex-direction: column;
68
+ }
69
+ .targets-list-list {
70
+ overflow-y: scroll;
71
+ }
72
+ .link-main{
73
+ word-spacing: 22px;
74
+ }
75
+ .link-icon {
76
+ position: absolute;
77
+ margin-left: -14px; // Remove the space of the icon to make it float to accomodate the underline
78
+ display: none; // Make the icon disappear by default
79
+ }
80
+
81
+ .link-main:hover .link-icon {
82
+ display: inline; // Only appear when hovered
65
83
  }
66
84
  </style>
@@ -16,13 +16,16 @@ import TargetsList from '@shell/components/fleet/FleetClusterTargets/TargetsList
16
16
 
17
17
  export interface Cluster {
18
18
  name: string,
19
- nameDisplay: string
19
+ nameDisplay: string,
20
+ detailLocation: object,
20
21
  }
21
22
 
22
23
  interface DataType {
23
24
  targetMode: TargetMode,
24
25
  allClusters: any[],
26
+ allClusterGroups: any[],
25
27
  selectedClusters: string[],
28
+ selectedClusterGroups: string[],
26
29
  clusterSelectors: Selector[],
27
30
  key: number,
28
31
  }
@@ -76,19 +79,26 @@ export default {
76
79
  allClusters: {
77
80
  inStoreType: 'management',
78
81
  type: FLEET.CLUSTER
79
- }
80
- }, this.$store) as { allClusters: any[] };
82
+ },
83
+ allClusterGroups: {
84
+ inStoreType: 'management',
85
+ type: FLEET.CLUSTER_GROUP
86
+ },
87
+ }, this.$store) as { allClusters: any[], allClusterGroups: any[] };
81
88
 
82
89
  this.allClusters = hash.allClusters || [];
90
+ this.allClusterGroups = hash.allClusterGroups || [];
83
91
  },
84
92
 
85
93
  data(): DataType {
86
94
  return {
87
- targetMode: 'all',
88
- allClusters: [],
89
- selectedClusters: [],
90
- clusterSelectors: [],
91
- key: 0 // Generates a unique key to handle Targets
95
+ targetMode: 'all',
96
+ allClusters: [],
97
+ allClusterGroups: [],
98
+ selectedClusters: [],
99
+ selectedClusterGroups: [],
100
+ clusterSelectors: [],
101
+ key: 0 // Generates a unique key to handle Targets
92
102
  };
93
103
  },
94
104
 
@@ -96,10 +106,9 @@ export default {
96
106
  this.fromTargets();
97
107
 
98
108
  if (this.mode === _CREATE) {
99
- this.update();
100
-
101
109
  // Restore the targetMode from parent component; this is the case of edit targets in CREATE mode, go to YAML editor and come back to the form
102
110
  this.targetMode = this.created || 'all';
111
+ this.update();
103
112
  } else {
104
113
  this.targetMode = FleetUtils.Application.getTargetMode(this.targets || [], this.namespace);
105
114
  }
@@ -153,6 +162,14 @@ export default {
153
162
  .map((x) => ({ label: x.nameDisplay, value: x.metadata.name }));
154
163
  },
155
164
 
165
+ clusterGroupsOptions() {
166
+ return this.allClusterGroups
167
+ .filter((x) => x.metadata.namespace === this.namespace)
168
+ .map((x) => {
169
+ return { label: x.nameDisplay, value: x.metadata.name };
170
+ });
171
+ },
172
+
156
173
  isLocal() {
157
174
  return this.namespace === 'fleet-local';
158
175
  },
@@ -178,6 +195,12 @@ export default {
178
195
  this.update();
179
196
  },
180
197
 
198
+ selectClusterGroups(list: string[]) {
199
+ this.selectedClusterGroups = list;
200
+
201
+ this.update();
202
+ },
203
+
181
204
  addMatchExpressions() {
182
205
  const neu = { key: this.key++ };
183
206
 
@@ -224,11 +247,15 @@ export default {
224
247
  clusterGroupSelector,
225
248
  } = target;
226
249
 
227
- // If clusterGroup or clusterGroupSelector are defined, targets are marked as complex and won't handle by the UI
228
- if (clusterGroup || clusterGroupSelector) {
250
+ // If clusterGroupSelector are defined, targets are marked as complex and won't handle by the UI
251
+ if (clusterGroupSelector) {
229
252
  return;
230
253
  }
231
254
 
255
+ if (clusterGroup) {
256
+ this.selectedClusterGroups.push(clusterGroup);
257
+ }
258
+
232
259
  if (clusterName) {
233
260
  this.selectedClusters.push(clusterName);
234
261
  }
@@ -254,20 +281,22 @@ export default {
254
281
  case 'all':
255
282
  return [excludeHarvesterRule];
256
283
  case 'clusters':
257
- return this.normalizeTargets(this.selectedClusters, this.clusterSelectors);
284
+ return this.normalizeTargets(this.selectedClusters, this.clusterSelectors, this.selectedClusterGroups);
258
285
  case 'advanced':
259
286
  case 'local':
260
287
  return this.targets;
261
288
  }
262
289
  },
263
290
 
264
- normalizeTargets(selected: string[], clusterMatchExpressions: Selector[]) {
291
+ normalizeTargets(selected: string[], clusterMatchExpressions: Selector[], selectedClusterGroups: string[]): Target[] | undefined {
265
292
  const targets: Target[] = [];
266
293
 
294
+ // Select by name
267
295
  selected.forEach((clusterName) => {
268
296
  targets.push({ clusterName });
269
297
  });
270
298
 
299
+ // Select by labels
271
300
  clusterMatchExpressions.forEach((elem) => {
272
301
  const { matchLabels: labels, matchExpressions: expressions } = elem || {};
273
302
 
@@ -311,6 +340,11 @@ export default {
311
340
  }
312
341
  });
313
342
 
343
+ // Select by cluster group
344
+ selectedClusterGroups.forEach((clusterGroup) => {
345
+ targets.push({ clusterGroup });
346
+ });
347
+
314
348
  if (targets.length) {
315
349
  return targets;
316
350
  }
@@ -321,6 +355,7 @@ export default {
321
355
  reset() {
322
356
  this.targetMode = 'all';
323
357
  this.selectedClusters = [];
358
+ this.selectedClusterGroups = [];
324
359
  this.clusterSelectors = [];
325
360
  }
326
361
  },
@@ -356,25 +391,25 @@ export default {
356
391
  >
357
392
  <div class="col span-9">
358
393
  <h3 class="m-0">
359
- {{ t('fleet.clusterTargets.title') }}
394
+ {{ t('fleet.clusterTargets.clusters.title') }}
360
395
  </h3>
361
396
  <LabeledSelect
362
397
  data-testid="fleet-target-cluster-name-selector"
363
398
  class="mmt-4"
364
399
  :value="selectedClusters"
365
- :label="t('fleet.clusterTargets.label')"
400
+ :label="t('fleet.clusterTargets.clusters.byName.label')"
366
401
  :options="clustersOptions"
367
402
  :taggable="true"
368
403
  :close-on-select="false"
369
404
  :mode="mode"
370
405
  :multiple="true"
371
- :placeholder="t('fleet.clusterTargets.placeholders.selectMultiple')"
406
+ :placeholder="t('fleet.clusterTargets.clusters.byName.placeholder')"
372
407
  @update:value="selectClusters"
373
408
  />
374
- <div class="mmt-8">
375
- <h3 class="m-0">
376
- {{ t('fleet.clusterTargets.rules.title') }}
377
- </h3>
409
+ <div class="mmt-6">
410
+ <h4 class="m-0">
411
+ {{ t('fleet.clusterTargets.clusters.byLabel.title') }}
412
+ </h4>
378
413
  <div
379
414
  v-for="(selector, i) in clusterSelectors"
380
415
  :key="selector.key"
@@ -386,29 +421,47 @@ export default {
386
421
  :value="selector"
387
422
  :mode="mode"
388
423
  :initial-empty-row="true"
389
- :label-key="t('fleet.clusterTargets.rules.labelKey')"
424
+ :label-key="t('fleet.clusterTargets.clusters.byLabel.labelKey')"
390
425
  :add-icon="'icon-plus'"
391
426
  :add-class="'btn-sm'"
392
427
  @update:value="updateMatchExpressions(i, $event, selector.key)"
393
428
  />
394
429
  <RcButton
395
- small
396
- link
430
+ size="small"
431
+ variant="link"
397
432
  @click="removeMatchExpressions(selector.key)"
398
433
  >
399
434
  <i class="icon icon-x" />
400
435
  </RcButton>
401
436
  </div>
402
437
  <RcButton
403
- small
404
- secondary
405
- class="mmt-6"
438
+ size="small"
439
+ variant="secondary"
440
+ class="mmt-4"
406
441
  @click="addMatchExpressions"
407
442
  >
408
443
  <i class="icon icon-plus" />
409
- <span>{{ t('fleet.clusterTargets.rules.addSelector') }}</span>
444
+ <span>{{ t('fleet.clusterTargets.clusters.byLabel.addSelector') }}</span>
410
445
  </RcButton>
411
446
  </div>
447
+ <div class="mmt-8">
448
+ <h3 class="m-0">
449
+ {{ t('fleet.clusterTargets.clusterGroups.title') }}
450
+ </h3>
451
+ <LabeledSelect
452
+ data-testid="fleet-target-cluster-group-selector"
453
+ class="mmt-4"
454
+ :value="selectedClusterGroups"
455
+ :label="t('fleet.clusterTargets.clusterGroups.byName.label')"
456
+ :options="clusterGroupsOptions"
457
+ :taggable="true"
458
+ :close-on-select="false"
459
+ :mode="mode"
460
+ :multiple="true"
461
+ :placeholder="t('fleet.clusterTargets.clusterGroups.byName.placeholder')"
462
+ @update:value="selectClusterGroups"
463
+ />
464
+ </div>
412
465
  </div>
413
466
  <div class="col span-3">
414
467
  <TargetsList
@@ -450,6 +503,6 @@ export default {
450
503
  }
451
504
 
452
505
  .target-list {
453
- max-height: 250px;
506
+ max-height: 320px;
454
507
  }
455
508
  </style>