@rancher/shell 3.0.9-rc.1 → 3.0.9-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 (208) hide show
  1. package/assets/styles/base/_color.scss +1 -0
  2. package/assets/styles/base/_typography.scss +14 -5
  3. package/assets/styles/themes/_light.scss +1 -1
  4. package/assets/styles/themes/_modern.scss +1 -1
  5. package/assets/translations/en-us.yaml +104 -33
  6. package/assets/translations/zh-hans.yaml +13 -2
  7. package/components/ActionMenu.vue +7 -8
  8. package/components/ActionMenuShell.vue +23 -24
  9. package/components/CodeMirror.vue +4 -3
  10. package/components/DetailText.vue +54 -7
  11. package/components/Drawer/Chrome.vue +11 -4
  12. package/components/Drawer/DrawerCard.vue +19 -0
  13. package/components/Drawer/ResourceDetailDrawer/ConfigTab.vue +3 -11
  14. package/components/Drawer/ResourceDetailDrawer/__tests__/ConfigTab.test.ts +2 -2
  15. package/components/Drawer/ResourceDetailDrawer/index.vue +3 -20
  16. package/components/Drawer/types.ts +1 -0
  17. package/components/DynamicContent/DynamicContentCloseButton.vue +2 -2
  18. package/components/LocaleSelector.vue +1 -1
  19. package/components/Markdown.vue +1 -1
  20. package/components/PopoverCard.vue +3 -3
  21. package/components/Resource/Detail/Card/ExtrasCard.vue +39 -0
  22. package/components/Resource/Detail/Card/Scaler.vue +10 -2
  23. package/components/Resource/Detail/Card/StateCard/__tests__/composables.test.ts +142 -0
  24. package/components/Resource/Detail/Card/StateCard/composables.ts +41 -11
  25. package/components/Resource/Detail/Card/StateCard/index.vue +3 -9
  26. package/components/Resource/Detail/Card/StateCard/types.ts +6 -0
  27. package/components/Resource/Detail/Card/{PodsCard → StatusCard}/index.vue +14 -10
  28. package/components/Resource/Detail/Card/__tests__/PodsCard.test.ts +24 -25
  29. package/components/Resource/Detail/Cards.vue +27 -0
  30. package/components/Resource/Detail/Masthead/__tests__/index.test.ts +70 -0
  31. package/components/Resource/Detail/Masthead/index.vue +5 -0
  32. package/components/Resource/Detail/Metadata/KeyValueRow.vue +4 -2
  33. package/components/Resource/Detail/ResourcePopover/ResourcePopoverCard.vue +2 -2
  34. package/components/Resource/Detail/ResourceRow.types.ts +14 -0
  35. package/components/Resource/Detail/ResourceRow.vue +23 -35
  36. package/components/Resource/Detail/StatusRow.vue +5 -2
  37. package/components/Resource/Detail/TitleBar/__tests__/composables.test.ts +38 -7
  38. package/components/Resource/Detail/TitleBar/__tests__/index.test.ts +106 -2
  39. package/components/Resource/Detail/TitleBar/composables.ts +2 -1
  40. package/components/Resource/Detail/TitleBar/index.vue +41 -6
  41. package/components/ResourceDetail/Masthead/__tests__/index.test.ts +49 -1
  42. package/components/ResourceDetail/Masthead/__tests__/latest.test.ts +85 -0
  43. package/components/ResourceDetail/Masthead/index.vue +1 -0
  44. package/components/ResourceDetail/Masthead/latest.vue +8 -1
  45. package/components/ResourceDetail/Masthead/legacy.vue +1 -1
  46. package/components/ResourceTable.vue +1 -1
  47. package/components/Setting.vue +1 -1
  48. package/components/SortableTable/index.vue +25 -0
  49. package/components/SortableTable/selection.js +25 -12
  50. package/components/SortableTable/sorting.js +1 -1
  51. package/components/Tabbed/Tab.vue +5 -0
  52. package/components/Tabbed/index.vue +40 -9
  53. package/components/Window/ContainerShell.vue +10 -13
  54. package/components/__tests__/ProjectRow.test.ts +102 -15
  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/ResourceQuota/Project.vue +59 -8
  70. package/components/form/ResourceQuota/ProjectRow.vue +116 -21
  71. package/components/form/ResourceQuota/shared.js +42 -18
  72. package/components/form/SeccompProfile.vue +113 -0
  73. package/components/form/Security.vue +244 -133
  74. package/components/form/__tests__/LabeledSelect.test.ts +1 -1
  75. package/components/form/__tests__/SeccompProfile.test.js +124 -0
  76. package/components/form/__tests__/Security.test.ts +125 -37
  77. package/components/formatter/Autoscaler.vue +2 -2
  78. package/components/formatter/FleetSummaryGraph.vue +4 -1
  79. package/components/formatter/LinkName.vue +3 -2
  80. package/components/nav/Group.vue +5 -0
  81. package/components/nav/Header.vue +3 -3
  82. package/components/nav/HeaderPageActionMenu.vue +1 -1
  83. package/components/nav/NamespaceFilter.vue +6 -6
  84. package/components/nav/NotificationCenter/index.vue +1 -1
  85. package/components/nav/TopLevelMenu.helper.ts +41 -16
  86. package/components/nav/TopLevelMenu.vue +45 -25
  87. package/components/nav/WorkspaceSwitcher.vue +1 -1
  88. package/components/nav/__tests__/TopLevelMenu.helper.test.ts +277 -0
  89. package/components/nav/__tests__/TopLevelMenu.test.ts +160 -4
  90. package/components/templates/default.vue +0 -3
  91. package/components/templates/home.vue +0 -3
  92. package/components/templates/plain.vue +0 -3
  93. package/composables/useClickOutside.ts +1 -1
  94. package/config/product/explorer.js +2 -3
  95. package/config/table-headers.js +9 -7
  96. package/config/types.js +45 -9
  97. package/detail/__tests__/workload.test.ts +8 -16
  98. package/detail/catalog.cattle.io.app.vue +5 -0
  99. package/detail/fleet.cattle.io.cluster.vue +6 -0
  100. package/detail/management.cattle.io.oidcclient.vue +15 -4
  101. package/detail/workload/index.vue +7 -109
  102. package/edit/__tests__/management.cattle.io.project.test.js +137 -0
  103. package/edit/__tests__/projectsecret.test.ts +42 -0
  104. package/edit/auth/__tests__/oidc.test.ts +50 -0
  105. package/edit/auth/oidc.vue +68 -44
  106. package/edit/autoscaling.horizontalpodautoscaler/index.vue +140 -59
  107. package/edit/autoscaling.horizontalpodautoscaler/metrics-row.vue +41 -5
  108. package/edit/management.cattle.io.project.vue +36 -6
  109. package/edit/monitoring.coreos.com.alertmanagerconfig/index.vue +16 -3
  110. package/edit/projectsecret.vue +29 -0
  111. package/edit/provisioning.cattle.io.cluster/__tests__/Basics.test.ts +89 -200
  112. package/edit/provisioning.cattle.io.cluster/__tests__/Networking.test.ts +58 -17
  113. package/edit/provisioning.cattle.io.cluster/rke2.vue +11 -0
  114. package/edit/provisioning.cattle.io.cluster/tabs/Basics.vue +3 -63
  115. package/edit/provisioning.cattle.io.cluster/tabs/networking/index.vue +82 -14
  116. package/edit/workload/__tests__/index.test.ts +3 -4
  117. package/edit/workload/index.vue +47 -28
  118. package/edit/workload/mixins/workload.js +66 -31
  119. package/initialize/install-plugins.js +0 -2
  120. package/list/catalog.cattle.io.clusterrepo.vue +1 -1
  121. package/list/projectsecret.vue +2 -2
  122. package/machine-config/__tests__/vmwarevsphere.test.ts +64 -0
  123. package/machine-config/amazonec2.vue +2 -2
  124. package/machine-config/vmwarevsphere.vue +58 -4
  125. package/mixins/__tests__/chart.test.ts +63 -0
  126. package/mixins/chart.js +56 -51
  127. package/models/__tests__/catalog.cattle.io.app.test.ts +33 -0
  128. package/models/__tests__/workload.test.ts +333 -0
  129. package/models/catalog.cattle.io.app.js +8 -0
  130. package/models/management.cattle.io.cluster.js +22 -30
  131. package/models/pod.js +14 -0
  132. package/models/provisioning.cattle.io.cluster.js +2 -2
  133. package/models/secret.js +1 -1
  134. package/models/workload.js +93 -27
  135. package/package.json +4 -4
  136. package/pages/__tests__/diagnostic.test.ts +71 -0
  137. package/pages/c/_cluster/apps/charts/__tests__/install.test.ts +91 -0
  138. package/pages/c/_cluster/apps/charts/install.vue +4 -4
  139. package/pages/c/_cluster/explorer/EventsTable.vue +2 -2
  140. package/pages/c/_cluster/explorer/tools/index.vue +23 -5
  141. package/pages/c/_cluster/fleet/index.vue +14 -8
  142. package/pages/c/_cluster/manager/hostedprovider/index.vue +1 -19
  143. package/pages/c/_cluster/monitoring/alertmanagerconfig/_alertmanagerconfigid/receiver.vue +18 -5
  144. package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +1 -1
  145. package/pages/c/_cluster/uiplugins/index.vue +41 -9
  146. package/pages/diagnostic.vue +17 -3
  147. package/plugins/dashboard-store/__tests__/resource-class.test.ts +234 -0
  148. package/plugins/dashboard-store/actions.js +9 -8
  149. package/plugins/dashboard-store/resource-class.js +97 -1
  150. package/plugins/steve/__tests__/revision.test.ts +84 -0
  151. package/plugins/steve/__tests__/steve-pagination-utils.test.ts +30 -0
  152. package/plugins/steve/__tests__/subscribe.spec.ts +134 -0
  153. package/plugins/steve/revision.ts +26 -0
  154. package/plugins/steve/steve-pagination-utils.ts +6 -5
  155. package/plugins/steve/subscribe.js +188 -49
  156. package/plugins/subscribe-events.ts +2 -2
  157. package/rancher-components/Form/Checkbox/Checkbox.vue +13 -0
  158. package/rancher-components/LabeledTooltip/LabeledTooltip.vue +1 -1
  159. package/rancher-components/Pill/RcCounterBadge/RcCounterBadge.vue +2 -1
  160. package/rancher-components/Pill/RcStatusBadge/RcStatusBadge.vue +3 -1
  161. package/rancher-components/Pill/RcStatusIndicator/RcStatusIndicator.vue +3 -1
  162. package/rancher-components/Pill/RcTag/RcTag.vue +1 -1
  163. package/rancher-components/Pill/index.ts +4 -0
  164. package/rancher-components/RcButton/RcButton.test.ts +53 -9
  165. package/rancher-components/RcButton/RcButton.vue +217 -25
  166. package/rancher-components/RcButton/types.ts +27 -1
  167. package/rancher-components/RcDropdown/RcDropdownMenu.vue +4 -4
  168. package/rancher-components/RcDropdown/types.ts +3 -3
  169. package/rancher-components/RcIcon/RcIcon.test.ts +42 -0
  170. package/rancher-components/RcIcon/RcIcon.vue +9 -6
  171. package/rancher-components/RcIcon/types.ts +13 -9
  172. package/rancher-components/RcItemCard/RcItemCard.test.ts +16 -6
  173. package/rancher-components/RcItemCard/RcItemCard.vue +13 -23
  174. package/rancher-components/utils/status.test.ts +10 -15
  175. package/rancher-components/utils/status.ts +5 -6
  176. package/store/__tests__/auth.test.ts +21 -5
  177. package/store/auth.js +6 -3
  178. package/store/aws.js +18 -12
  179. package/store/index.js +4 -8
  180. package/store/type-map.utils.ts +1 -1
  181. package/types/kube/kube-api.ts +29 -3
  182. package/types/rancher/steve.api.ts +40 -0
  183. package/types/shell/index.d.ts +262 -156
  184. package/types/store/pagination.types.ts +1 -0
  185. package/types/store/subscribe-events.types.ts +1 -0
  186. package/utils/__tests__/azure.test.ts +56 -0
  187. package/utils/__tests__/back-off.test.ts +364 -245
  188. package/utils/__tests__/error.test.ts +44 -0
  189. package/utils/__tests__/fleet.test.ts +8 -1
  190. package/utils/__tests__/pagination-wrapper.test.ts +167 -0
  191. package/utils/__tests__/version.test.ts +55 -1
  192. package/utils/azure.js +12 -0
  193. package/utils/back-off.ts +302 -69
  194. package/utils/dynamic-content/__tests__/index.test.ts +1 -1
  195. package/utils/dynamic-content/__tests__/new-release.test.ts +48 -7
  196. package/utils/dynamic-content/__tests__/support-notice.test.ts +1 -4
  197. package/utils/dynamic-content/index.ts +1 -6
  198. package/utils/dynamic-content/new-release.ts +5 -3
  199. package/utils/dynamic-content/types.d.ts +0 -1
  200. package/utils/error.js +9 -0
  201. package/utils/fleet.ts +2 -2
  202. package/utils/inactivity.ts +2 -3
  203. package/utils/pagination-wrapper.ts +99 -15
  204. package/utils/validators/formRules/index.ts +3 -0
  205. package/utils/version.js +38 -0
  206. package/components/auth/AzureWarning.vue +0 -77
  207. /package/components/Resource/Detail/{Card/PodsCard/Bubble.vue → Bubble.vue} +0 -0
  208. /package/components/Resource/Detail/Card/{PodsCard → StatusCard}/composable.ts +0 -0
@@ -0,0 +1,85 @@
1
+ import { mount } from '@vue/test-utils';
2
+ import Latest from '@shell/components/ResourceDetail/Masthead/latest.vue';
3
+
4
+ jest.mock('@shell/components/Resource/Detail/TitleBar/index.vue', () => ({
5
+ name: 'TitleBar',
6
+ template: `<div data-testid="title-bar">TitleBar</div>`
7
+ }));
8
+ jest.mock('@shell/components/Resource/Detail/TitleBar/composables', () => ({ useDefaultTitleBarProps: jest.fn(() => ({})) }));
9
+ jest.mock('@shell/components/Resource/Detail/Metadata/index.vue', () => ({
10
+ name: 'Metadata',
11
+ template: `<div data-testid="metadata">Metadata</div>`
12
+ }));
13
+ jest.mock('@shell/components/Resource/Detail/Metadata/composables', () => ({ useDefaultMetadataForLegacyPagesProps: jest.fn(() => ({})) }));
14
+ jest.mock('@shell/components/Resource/Detail/composables', () => ({ useResourceDetailBannerProps: jest.fn(() => null) }));
15
+ jest.mock('@shell/components/Resource/Detail/Cards.vue', () => ({
16
+ name: 'Cards',
17
+ template: `<div data-testid="cards">Cards</div>`,
18
+ props: ['resource']
19
+ }));
20
+ jest.mock('@components/Banner', () => ({
21
+ Banner: {
22
+ name: 'Banner',
23
+ template: `<div data-testid="banner">Banner</div>`
24
+ }
25
+ }));
26
+
27
+ const defaultMocks = {
28
+ directives: { 'ui-context': () => {} },
29
+ global: {
30
+ mocks: {
31
+ $store: {
32
+ getters: { 'i18n/t': jest.fn() },
33
+ dispatch: jest.fn()
34
+ }
35
+ }
36
+ }
37
+ };
38
+
39
+ describe('component: Masthead/latest', () => {
40
+ beforeEach(() => {
41
+ jest.clearAllMocks();
42
+ });
43
+
44
+ it('should render Cards when isCustomDetailOrEdit is true', () => {
45
+ const props = {
46
+ value: { name: 'test-resource' },
47
+ isCustomDetailOrEdit: true
48
+ };
49
+
50
+ const wrapper = mount(Latest, { props, ...defaultMocks });
51
+
52
+ expect(wrapper.find('[data-testid="cards"]').exists()).toBe(true);
53
+ });
54
+
55
+ it('should not render Cards when isCustomDetailOrEdit is false', () => {
56
+ const props = {
57
+ value: { name: 'test-resource' },
58
+ isCustomDetailOrEdit: false
59
+ };
60
+
61
+ const wrapper = mount(Latest, { props, ...defaultMocks });
62
+
63
+ expect(wrapper.find('[data-testid="cards"]').exists()).toBe(false);
64
+ });
65
+
66
+ it('should not render Cards when isCustomDetailOrEdit is not provided (defaults to false)', () => {
67
+ const props = { value: { name: 'test-resource' } };
68
+
69
+ const wrapper = mount(Latest, { props, ...defaultMocks });
70
+
71
+ expect(wrapper.find('[data-testid="cards"]').exists()).toBe(false);
72
+ });
73
+
74
+ it('should always render TitleBar and Metadata', () => {
75
+ const props = {
76
+ value: { name: 'test-resource' },
77
+ isCustomDetailOrEdit: false
78
+ };
79
+
80
+ const wrapper = mount(Latest, { props, ...defaultMocks });
81
+
82
+ expect(wrapper.find('[data-testid="title-bar"]').exists()).toBe(true);
83
+ expect(wrapper.find('[data-testid="metadata"]').exists()).toBe(true);
84
+ });
85
+ });
@@ -48,6 +48,7 @@ const showLatestMasthead = computed(() => isNewDetailPageEnabled.value && isView
48
48
  v-if="showLatestMasthead"
49
49
  :value="props.value"
50
50
  :resourceSubtype="props.resourceSubtype"
51
+ :isCustomDetailOrEdit="props.hasDetail || props.hasEdit"
51
52
  />
52
53
  <Legacy
53
54
  v-else
@@ -7,11 +7,13 @@ import Metadata from '@shell/components/Resource/Detail/Metadata/index.vue';
7
7
  import { useDefaultMetadataForLegacyPagesProps } from '@shell/components/Resource/Detail/Metadata/composables';
8
8
  import { useResourceDetailBannerProps } from '@shell/components/Resource/Detail/composables';
9
9
  import { computed } from 'vue';
10
+ import Cards from '@shell/components/Resource/Detail/Cards.vue';
10
11
 
11
12
  // We are disabling eslint for this script to allow the use of the Props interface
12
13
  export interface Props {
13
14
  value?: Object;
14
15
  resourceSubtype?: string;
16
+ isCustomDetailOrEdit?: boolean;
15
17
  }
16
18
 
17
19
  </script>
@@ -19,7 +21,7 @@ export interface Props {
19
21
  <script lang="ts" setup>
20
22
  import { useStore } from 'vuex';
21
23
 
22
- const props = withDefaults(defineProps<Props>(), { value: () => ({}), resourceSubtype: undefined });
24
+ const props = withDefaults(defineProps<Props>(), { value: () => ({}), resourceSubtype: undefined, isCustomDetailOrEdit: false });
23
25
 
24
26
  const uiCtxResource = computed(() => {
25
27
  const {
@@ -64,6 +66,11 @@ const store = useStore();
64
66
  v-bind="metadataProps"
65
67
  class="mmt-4"
66
68
  />
69
+ <Cards
70
+ v-if="props.isCustomDetailOrEdit"
71
+ class="mb-20"
72
+ :resource="props.value"
73
+ />
67
74
  </div>
68
75
  </template>
69
76
 
@@ -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"
@@ -427,7 +427,7 @@ export default {
427
427
  },
428
428
 
429
429
  _applicableExtensionTableHooks() {
430
- if (this.$store.$plugin?.getUIConfig) {
430
+ if (this.$store.$extension?.getUIConfig) {
431
431
  const extensionTableHooks = getApplicableExtensionEnhancements(this, ExtensionPoint.TABLE, TableLocation.RESOURCE, this.$route);
432
432
 
433
433
  return extensionTableHooks;
@@ -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;
@@ -15,6 +15,10 @@ export default {
15
15
  default: null,
16
16
  type: String
17
17
  },
18
+ labelIcon: {
19
+ type: String,
20
+ default: null
21
+ },
18
22
  name: {
19
23
  required: true,
20
24
  type: String
@@ -137,6 +141,7 @@ export default {
137
141
  :id="name"
138
142
  :aria-hidden="!active"
139
143
  role="tabpanel"
144
+ :aria-labelledby="`tab-${name}`"
140
145
  >
141
146
  <div
142
147
  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
 
@@ -201,7 +207,7 @@ export default {
201
207
  return TabLocation.OTHER;
202
208
  }
203
209
  },
204
- hasIcon(tab) {
210
+ hasErrorIcon(tab) {
205
211
  return tab.displayAlertIcon || (tab.error && !tab.active);
206
212
  },
207
213
  hashChange() {
@@ -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,17 +333,23 @@ 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
  >
349
+ <i
350
+ v-if="tab.labelIcon"
351
+ :class="`tab-label-icon icon ${tab.labelIcon}`"
352
+ />
337
353
  <span>
338
354
  {{ tab.labelDisplay }}
339
355
  </span>
@@ -342,7 +358,7 @@ export default {
342
358
  class="tab-badge"
343
359
  >{{ tab.badge }}</span>
344
360
  <i
345
- v-if="hasIcon(tab)"
361
+ v-if="hasErrorIcon(tab)"
346
362
  v-clean-tooltip="t('validation.tab')"
347
363
  class="conditions-alert-icon icon-error"
348
364
  />
@@ -442,6 +458,17 @@ export default {
442
458
  display: flex;
443
459
  flex-direction: row;
444
460
 
461
+ &.remove-borders {
462
+ border: none;
463
+
464
+ + .tab-container {
465
+ border: none;
466
+ border-top: 1px solid var(--border);
467
+ padding: 0;
468
+ padding-top: 24px;
469
+ }
470
+ }
471
+
445
472
  + .tab-container {
446
473
  border: solid thin var(--border);
447
474
  }
@@ -458,7 +485,7 @@ export default {
458
485
  .tab {
459
486
  position: relative;
460
487
  float: left;
461
- padding: 0 8px 0 0;
488
+ padding: 0 4px 0 4px;
462
489
  cursor: pointer;
463
490
 
464
491
  A {
@@ -491,11 +518,15 @@ export default {
491
518
  }
492
519
 
493
520
  &.error {
494
- & A > i {
521
+ & A > .icon-error {
495
522
  color: var(--error);
496
523
  }
497
524
  }
498
525
 
526
+ .tab-label-icon {
527
+ margin-right: 8px;
528
+ }
529
+
499
530
  .tab-badge {
500
531
  margin-left: 5px;
501
532
  background-color: var(--link);
@@ -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
 
@@ -1,31 +1,39 @@
1
1
  import ProjectRow from '@shell/components/form/ResourceQuota/ProjectRow.vue';
2
- import { RANCHER_TYPES } from '@shell/components/form/ResourceQuota/shared';
2
+ import { RANCHER_TYPES, TYPES } from '@shell/components/form/ResourceQuota/shared';
3
3
  import { shallowMount } from '@vue/test-utils';
4
4
 
5
- const CONFIGMAP_STRING = RANCHER_TYPES[0].value;
5
+ const CONFIGMAP_STRING = TYPES.CONFIG_MAPS;
6
6
 
7
7
  describe('component: ProjectRow.vue', () => {
8
- const wrapper = shallowMount(ProjectRow,
9
- {
10
- props: {
11
- mode: 'edit',
12
- types: RANCHER_TYPES,
13
- type: CONFIGMAP_STRING,
14
- value: {
15
- spec: {
16
- namespaceDefaultResourceQuota: { limit: {} },
17
- resourceQuota: { limit: {} }
18
- }
8
+ const defaultMountOptions = {
9
+ props: {
10
+ mode: 'edit',
11
+ types: RANCHER_TYPES,
12
+ type: CONFIGMAP_STRING,
13
+ index: 0,
14
+ value: {
15
+ spec: {
16
+ namespaceDefaultResourceQuota: { limit: {} },
17
+ resourceQuota: { limit: {} }
19
18
  }
20
19
  }
21
- });
20
+ }
21
+ };
22
22
 
23
23
  it('should render the correct input fields and set the correct computed values, based on the provided data', () => {
24
+ const wrapper = shallowMount(
25
+ ProjectRow,
26
+ { ...defaultMountOptions }
27
+ );
28
+
24
29
  const typeInput = wrapper.find(`[data-testid="projectrow-type-input"]`);
30
+ const customTypeInput = wrapper.find(`[data-testid="projectrow-custom-type-input"]`);
25
31
  const projectQuotaInput = wrapper.find(`[data-testid="projectrow-project-quota-input"]`);
26
32
  const namespaceQuotaInput = wrapper.find(`[data-testid="projectrow-namespace-quota-input"]`);
27
33
 
28
34
  expect(typeInput.exists()).toBe(true);
35
+ expect(customTypeInput.exists()).toBe(true);
36
+ expect(customTypeInput.attributes().disabled).toBe('true');
29
37
  expect(projectQuotaInput.exists()).toBe(true);
30
38
  expect(namespaceQuotaInput.exists()).toBe(true);
31
39
  expect(wrapper.vm.resourceQuotaLimit).toStrictEqual({});
@@ -33,6 +41,11 @@ describe('component: ProjectRow.vue', () => {
33
41
  });
34
42
 
35
43
  it('triggering "updateQuotaLimit" should trigger Vue.set with the correct data', () => {
44
+ const wrapper = shallowMount(
45
+ ProjectRow,
46
+ { ...defaultMountOptions }
47
+ );
48
+
36
49
  wrapper.vm.updateQuotaLimit('resourceQuota', CONFIGMAP_STRING, 10);
37
50
 
38
51
  expect(wrapper.vm.value).toStrictEqual({
@@ -44,6 +57,11 @@ describe('component: ProjectRow.vue', () => {
44
57
  });
45
58
 
46
59
  it('triggering "updateType" with the same type that existed should clear limits and trigger emit', () => {
60
+ const wrapper = shallowMount(
61
+ ProjectRow,
62
+ { ...defaultMountOptions }
63
+ );
64
+
47
65
  wrapper.vm.updateType(CONFIGMAP_STRING);
48
66
 
49
67
  expect(wrapper.vm.value).toStrictEqual({
@@ -54,6 +72,75 @@ describe('component: ProjectRow.vue', () => {
54
72
  });
55
73
 
56
74
  expect(wrapper.emitted('type-change')).toBeTruthy();
57
- expect(wrapper.emitted('type-change')[0]).toStrictEqual([CONFIGMAP_STRING]);
75
+ expect(wrapper.emitted('type-change')[0]).toStrictEqual([{ index: 0, type: CONFIGMAP_STRING }]);
76
+ });
77
+
78
+ it('should update standard resource types', async() => {
79
+ const wrapper = shallowMount(
80
+ ProjectRow,
81
+ { ...defaultMountOptions }
82
+ );
83
+
84
+ expect(wrapper.vm.isCustom).toBe(false);
85
+
86
+ await wrapper.vm.updateQuotaLimit('resourceQuota', 'limitsCpu', '100m');
87
+ await wrapper.vm.updateQuotaLimit('namespaceDefaultResourceQuota', 'limitsCpu', '50m');
88
+
89
+ expect(wrapper.vm.value.spec.resourceQuota.limit.limitsCpu).toBe('100m');
90
+ expect(wrapper.vm.value.spec.namespaceDefaultResourceQuota.limit.limitsCpu).toBe('50m');
91
+ expect(wrapper.vm.value.spec.resourceQuota.limit.extended).toBeUndefined();
92
+ });
93
+
94
+ it('should switch to a custom resource type', async() => {
95
+ const wrapper = shallowMount(
96
+ ProjectRow,
97
+ { ...defaultMountOptions }
98
+ );
99
+
100
+ await wrapper.vm.updateType(TYPES.EXTENDED);
101
+
102
+ expect(wrapper.emitted('type-change')).toHaveLength(1);
103
+ expect(wrapper.emitted('type-change')[0][0]).toStrictEqual({ index: 0, type: TYPES.EXTENDED });
104
+ });
105
+
106
+ it('should update custom resource types', async() => {
107
+ const wrapper = shallowMount(
108
+ ProjectRow,
109
+ {
110
+ ...defaultMountOptions,
111
+ props: {
112
+ ...defaultMountOptions.props,
113
+ type: TYPES.EXTENDED
114
+ }
115
+ }
116
+ );
117
+
118
+ expect(wrapper.vm.isCustom).toBe(true);
119
+
120
+ const customTypeInput = wrapper.find(`[data-testid="projectrow-custom-type-input"]`);
121
+
122
+ expect(customTypeInput.attributes().disabled).toBe('false');
123
+ await wrapper.vm.updateCustomType('custom.resource/foo');
124
+
125
+ expect(wrapper.vm.customType).toBe('custom.resource/foo');
126
+
127
+ await wrapper.vm.updateQuotaLimit('resourceQuota', 'custom.resource/foo', 1);
128
+ await wrapper.vm.updateQuotaLimit('namespaceDefaultResourceQuota', 'custom.resource/foo', 2);
129
+
130
+ expect(wrapper.vm.value.spec.resourceQuota.limit.extended['custom.resource/foo']).toBe(1);
131
+ expect(wrapper.vm.value.spec.namespaceDefaultResourceQuota.limit.extended['custom.resource/foo']).toBe(2);
132
+ });
133
+
134
+ it('should handle custom resource types with periods', () => {
135
+ const wrapper = shallowMount(ProjectRow, {
136
+ ...defaultMountOptions,
137
+ props: {
138
+ ...defaultMountOptions.props,
139
+ type: 'extended.requests.nvidia.com/gpu'
140
+ }
141
+ });
142
+
143
+ expect(wrapper.vm.isCustom).toBe(true);
144
+ expect(wrapper.vm.customType).toBe('requests.nvidia.com/gpu');
58
145
  });
59
146
  });