@rancher/shell 3.0.5-rc.2 → 3.0.5-rc.5

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 (289) hide show
  1. package/assets/data/aws-regions.json +2 -0
  2. package/assets/images/icons/document.svg +3 -0
  3. package/assets/images/vendor/cognito.svg +1 -0
  4. package/assets/styles/app.scss +1 -0
  5. package/assets/styles/base/_basic.scss +10 -0
  6. package/assets/styles/base/_spacing.scss +29 -0
  7. package/assets/styles/global/_layout.scss +1 -2
  8. package/assets/styles/themes/_dark.scss +25 -0
  9. package/assets/styles/themes/_light.scss +65 -0
  10. package/assets/translations/en-us.yaml +377 -37
  11. package/assets/translations/zh-hans.yaml +8 -15
  12. package/chart/monitoring/index.vue +1 -1
  13. package/components/AsyncButton.vue +2 -0
  14. package/components/Certificates.vue +5 -0
  15. package/components/CodeMirror.vue +3 -3
  16. package/components/CruResource.vue +103 -15
  17. package/components/ExplorerProjectsNamespaces.vue +7 -2
  18. package/components/FilterPanel.vue +156 -0
  19. package/components/FixedBanner.vue +19 -5
  20. package/components/{fleet/ForceDirectedTreeChart/index.vue → ForceDirectedTreeChart.vue} +47 -41
  21. package/components/IconOrSvg.vue +14 -35
  22. package/components/PaginatedResourceTable.vue +7 -0
  23. package/components/PromptRemove.vue +5 -1
  24. package/components/Resource/Detail/Card/PodsCard/Bubble.vue +13 -0
  25. package/components/Resource/Detail/Card/PodsCard/composable.ts +30 -0
  26. package/components/Resource/Detail/Card/PodsCard/index.vue +118 -0
  27. package/components/Resource/Detail/Card/ResourceUsageCard/composable.ts +51 -0
  28. package/components/Resource/Detail/Card/ResourceUsageCard/index.vue +79 -0
  29. package/components/Resource/Detail/Card/Scaler.vue +89 -0
  30. package/components/Resource/Detail/Card/StateCard/composables.ts +112 -0
  31. package/components/Resource/Detail/Card/StateCard/index.vue +39 -0
  32. package/components/Resource/Detail/Card/VerticalGap.vue +11 -0
  33. package/components/Resource/Detail/Card/__tests__/Card.test.ts +36 -0
  34. package/components/Resource/Detail/Card/__tests__/PodsCard.test.ts +84 -0
  35. package/components/Resource/Detail/Card/__tests__/ResourceUsageCard.test.ts +72 -0
  36. package/components/Resource/Detail/Card/__tests__/Scaler.test.ts +87 -0
  37. package/components/Resource/Detail/Card/__tests__/StateCard.test.ts +53 -0
  38. package/components/Resource/Detail/Card/__tests__/VerticalGap.test.ts +14 -0
  39. package/components/Resource/Detail/Card/__tests__/index.test.ts +36 -0
  40. package/components/Resource/Detail/Card/index.vue +56 -0
  41. package/components/Resource/Detail/Metadata/Annotations/__tests__/index.test.ts +19 -0
  42. package/components/Resource/Detail/Metadata/Annotations/composable.ts +12 -0
  43. package/components/Resource/Detail/Metadata/Annotations/index.vue +26 -0
  44. package/components/Resource/Detail/Metadata/IdentifyingInformation/__tests__/index.test.ts +103 -0
  45. package/components/Resource/Detail/Metadata/IdentifyingInformation/composable.ts +281 -0
  46. package/components/Resource/Detail/Metadata/IdentifyingInformation/index.vue +111 -0
  47. package/components/Resource/Detail/Metadata/KeyValue.vue +130 -0
  48. package/components/Resource/Detail/Metadata/Labels/__tests__/index.test.ts +18 -0
  49. package/components/Resource/Detail/Metadata/Labels/composable.ts +12 -0
  50. package/components/Resource/Detail/Metadata/Labels/index.vue +27 -0
  51. package/components/Resource/Detail/Metadata/Rectangle.vue +32 -0
  52. package/components/Resource/Detail/Metadata/__tests__/KeyValue.test.ts +107 -0
  53. package/components/Resource/Detail/Metadata/__tests__/Rectangle.test.ts +24 -0
  54. package/components/Resource/Detail/Metadata/__tests__/index.test.ts +91 -0
  55. package/components/Resource/Detail/Metadata/composables.ts +29 -0
  56. package/components/Resource/Detail/Metadata/index.vue +66 -0
  57. package/components/Resource/Detail/Page.vue +22 -0
  58. package/components/Resource/Detail/PercentageBar.vue +40 -0
  59. package/components/Resource/Detail/ResourceRow.vue +119 -0
  60. package/components/Resource/Detail/SpacedRow.vue +14 -0
  61. package/components/Resource/Detail/StatusBar.vue +59 -0
  62. package/components/Resource/Detail/StatusRow.vue +61 -0
  63. package/components/Resource/Detail/TitleBar/Title.vue +13 -0
  64. package/components/Resource/Detail/TitleBar/Top.vue +14 -0
  65. package/components/Resource/Detail/TitleBar/__tests__/Title.test.ts +17 -0
  66. package/components/Resource/Detail/TitleBar/__tests__/Top.test.ts +17 -0
  67. package/components/Resource/Detail/TitleBar/__tests__/index.test.ts +142 -0
  68. package/components/Resource/Detail/TitleBar/composable.ts +31 -0
  69. package/components/Resource/Detail/TitleBar/index.vue +124 -0
  70. package/components/Resource/Detail/Top/index.vue +34 -0
  71. package/components/Resource/Detail/__tests__/Page.test.ts +32 -0
  72. package/components/ResourceDetail/Masthead.vue +0 -1
  73. package/components/ResourceDetail/__tests__/index.test.ts +114 -0
  74. package/components/ResourceDetail/index.vue +64 -562
  75. package/components/ResourceDetail/legacy.vue +545 -0
  76. package/components/ResourceList/index.vue +2 -1
  77. package/components/ResourceTable.vue +41 -7
  78. package/components/SlideInPanelManager.vue +77 -10
  79. package/components/SortableTable/index.vue +13 -2
  80. package/components/SortableTable/selection.js +22 -9
  81. package/components/StatusBadge.vue +6 -4
  82. package/components/SubtleLink.vue +25 -0
  83. package/components/Tabbed/index.vue +6 -0
  84. package/components/Wizard.vue +12 -1
  85. package/components/YamlEditor.vue +1 -1
  86. package/components/__tests__/AsyncButton.test.ts +39 -0
  87. package/components/__tests__/CruResource.test.ts +63 -0
  88. package/components/__tests__/FilterPanel.test.ts +81 -0
  89. package/components/__tests__/PromptModal.test.ts +0 -2
  90. package/components/auth/AuthBanner.vue +2 -3
  91. package/components/auth/RoleDetailEdit.vue +45 -3
  92. package/components/auth/login/oidc.vue +6 -1
  93. package/components/fleet/FleetApplications.vue +181 -0
  94. package/components/fleet/FleetHelmOps.vue +115 -0
  95. package/components/fleet/FleetIntro.vue +58 -28
  96. package/components/fleet/FleetNoWorkspaces.vue +5 -1
  97. package/components/fleet/FleetOCIStorageSecret.vue +171 -0
  98. package/components/fleet/FleetRepos.vue +38 -76
  99. package/components/fleet/FleetResources.vue +50 -22
  100. package/components/fleet/FleetSummary.vue +26 -51
  101. package/components/fleet/__tests__/FleetOCIStorageSecret.test.ts +213 -0
  102. package/components/fleet/__tests__/FleetSummary.test.ts +39 -39
  103. package/components/fleet/dashboard/Empty.vue +73 -0
  104. package/components/fleet/dashboard/ResourceCard.vue +183 -0
  105. package/components/fleet/dashboard/ResourceCardSummary.vue +199 -0
  106. package/components/fleet/dashboard/ResourceDetails.vue +196 -0
  107. package/components/fleet/dashboard/ResourcePanel.vue +376 -0
  108. package/components/form/ArrayList.vue +139 -117
  109. package/components/form/BannerSettings.vue +145 -96
  110. package/components/form/KeyValue.vue +10 -7
  111. package/components/form/LabeledSelect.vue +9 -2
  112. package/components/form/MatchExpressions.vue +5 -1
  113. package/components/form/NameNsDescription.vue +1 -1
  114. package/components/form/ResourceSelector.vue +26 -23
  115. package/components/form/ResourceTabs/index.vue +2 -1
  116. package/components/form/Select.vue +9 -2
  117. package/components/form/SimpleSecretSelector.vue +8 -2
  118. package/components/form/UnitInput.vue +13 -0
  119. package/components/form/ValueFromResource.vue +31 -19
  120. package/components/form/__tests__/ArrayList.test.ts +32 -0
  121. package/components/form/__tests__/KeyValue.test.ts +36 -0
  122. package/components/form/__tests__/LabeledSelect.test.ts +33 -0
  123. package/components/form/__tests__/Select.test.ts +34 -1
  124. package/components/form/__tests__/UnitInput.test.ts +23 -1
  125. package/components/formatter/ClusterLink.vue +5 -8
  126. package/components/formatter/Description.vue +30 -0
  127. package/components/formatter/FleetApplicationClustersReady.vue +77 -0
  128. package/components/formatter/FleetApplicationSource.vue +71 -0
  129. package/components/formatter/FleetSummaryGraph.vue +7 -0
  130. package/components/formatter/__tests__/ClusterLink.test.ts +2 -32
  131. package/components/nav/Header.vue +8 -7
  132. package/components/nav/NamespaceFilter.vue +1 -1
  133. package/components/nav/TopLevelMenu.helper.ts +55 -34
  134. package/components/nav/TopLevelMenu.vue +11 -0
  135. package/components/nav/Type.vue +4 -1
  136. package/components/nav/WindowManager/index.vue +1 -0
  137. package/composables/useI18n.ts +12 -11
  138. package/config/labels-annotations.js +14 -11
  139. package/config/product/auth.js +1 -0
  140. package/config/product/explorer.js +16 -13
  141. package/config/product/fleet.js +70 -17
  142. package/config/product/manager.js +1 -28
  143. package/config/query-params.js +3 -1
  144. package/config/roles.ts +1 -0
  145. package/config/router/routes.js +20 -2
  146. package/config/secret.ts +15 -0
  147. package/config/settings.ts +14 -15
  148. package/config/table-headers.js +59 -27
  149. package/config/types.js +2 -0
  150. package/core/plugin-helpers.ts +3 -2
  151. package/detail/catalog.cattle.io.app.vue +0 -1
  152. package/detail/fleet.cattle.io.cluster.vue +28 -15
  153. package/detail/fleet.cattle.io.gitrepo.vue +10 -1
  154. package/detail/fleet.cattle.io.helmop.vue +157 -0
  155. package/detail/provisioning.cattle.io.cluster.vue +13 -3
  156. package/detail/service.vue +0 -1
  157. package/detail/workload/index.vue +21 -34
  158. package/dialog/ExtensionCatalogUninstallDialog.vue +14 -8
  159. package/dialog/HelmOpForceUpdateDialog.vue +132 -0
  160. package/dialog/RedeployWorkloadDialog.vue +164 -0
  161. package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +56 -67
  162. package/edit/__tests__/service.test.ts +2 -1
  163. package/edit/auth/oidc.vue +159 -93
  164. package/edit/fleet.cattle.io.gitrepo.vue +26 -33
  165. package/edit/fleet.cattle.io.helmop.vue +997 -0
  166. package/edit/management.cattle.io.fleetworkspace.vue +43 -10
  167. package/edit/networking.k8s.io.networkpolicy/PolicyRule.vue +3 -14
  168. package/edit/networking.k8s.io.networkpolicy/PolicyRuleTarget.vue +57 -62
  169. package/edit/networking.k8s.io.networkpolicy/PolicyRules.vue +3 -14
  170. package/edit/networking.k8s.io.networkpolicy/__tests__/PolicyRuleTarget.test.ts +72 -41
  171. package/edit/networking.k8s.io.networkpolicy/__tests__/utils/mock.json +17 -1
  172. package/edit/networking.k8s.io.networkpolicy/index.vue +18 -30
  173. package/edit/provisioning.cattle.io.cluster/index.vue +21 -73
  174. package/edit/service.vue +13 -28
  175. package/list/fleet.cattle.io.gitrepo.vue +1 -1
  176. package/list/fleet.cattle.io.helmop.vue +108 -0
  177. package/list/namespace.vue +5 -2
  178. package/list/workload.vue +6 -1
  179. package/mixins/auth-config.js +8 -1
  180. package/mixins/preset.js +100 -0
  181. package/mixins/resource-fetch-api-pagination.js +57 -43
  182. package/mixins/resource-fetch.js +15 -6
  183. package/mixins/resource-table-watch.js +45 -0
  184. package/models/__tests__/chart.test.ts +273 -0
  185. package/models/__tests__/fleet.cattle.io.gitrepo.test.ts +1 -1
  186. package/models/__tests__/workload.test.ts +1 -0
  187. package/models/chart.js +144 -2
  188. package/models/cluster/node.js +1 -0
  189. package/models/cluster.js +32 -2
  190. package/models/fleet-application.js +385 -0
  191. package/models/fleet.cattle.io.bundle.js +9 -8
  192. package/models/fleet.cattle.io.gitrepo.js +41 -365
  193. package/models/fleet.cattle.io.helmop.js +228 -0
  194. package/models/management.cattle.io.authconfig.js +1 -0
  195. package/models/management.cattle.io.cluster.js +0 -20
  196. package/models/management.cattle.io.fleetworkspace.js +12 -0
  197. package/models/management.cattle.io.node.js +7 -22
  198. package/models/management.cattle.io.nodepool.js +12 -0
  199. package/models/namespace.js +5 -0
  200. package/models/provisioning.cattle.io.cluster.js +18 -64
  201. package/models/service.js +24 -9
  202. package/models/workload.js +84 -49
  203. package/package.json +2 -1
  204. package/pages/auth/verify.vue +13 -1
  205. package/pages/c/_cluster/apps/charts/AddRepoLink.vue +37 -0
  206. package/pages/c/_cluster/apps/charts/AppChartCardFooter.vue +80 -0
  207. package/pages/c/_cluster/apps/charts/AppChartCardSubHeader.vue +54 -0
  208. package/pages/c/_cluster/apps/charts/StatusLabel.vue +33 -0
  209. package/pages/c/_cluster/apps/charts/index.vue +302 -484
  210. package/pages/c/_cluster/apps/charts/install.vue +0 -1
  211. package/pages/c/_cluster/explorer/EventsTable.vue +1 -1
  212. package/pages/c/_cluster/explorer/index.vue +11 -0
  213. package/pages/c/_cluster/fleet/__tests__/index.test.ts +426 -0
  214. package/pages/c/_cluster/fleet/application/_resource/_id.vue +14 -0
  215. package/pages/c/_cluster/fleet/application/_resource/create.vue +14 -0
  216. package/pages/c/_cluster/fleet/application/create.vue +340 -0
  217. package/pages/c/_cluster/fleet/application/index.vue +139 -0
  218. package/pages/c/_cluster/fleet/graph/config.js +277 -0
  219. package/pages/c/_cluster/fleet/index.vue +772 -330
  220. package/pages/c/_cluster/longhorn/index.vue +2 -2
  221. package/pages/c/_cluster/settings/banners.vue +56 -2
  222. package/pages/c/_cluster/settings/performance.vue +7 -26
  223. package/pages/explorer/resource/detail/configmap.vue +19 -0
  224. package/pages/home.vue +11 -52
  225. package/plugins/clean-html.js +2 -0
  226. package/plugins/dashboard-store/__tests__/actions.test.ts +4 -1
  227. package/plugins/dashboard-store/actions.js +153 -30
  228. package/plugins/dashboard-store/getters.js +108 -24
  229. package/plugins/dashboard-store/mutations.js +61 -12
  230. package/plugins/dashboard-store/resource-class.js +36 -4
  231. package/plugins/steve/__tests__/getters.test.ts +18 -11
  232. package/plugins/steve/__tests__/steve-class.test.ts +1 -0
  233. package/plugins/steve/__tests__/subscribe.spec.ts +66 -1
  234. package/plugins/steve/actions.js +37 -12
  235. package/plugins/steve/getters.js +39 -10
  236. package/plugins/steve/steve-class.js +5 -0
  237. package/plugins/steve/steve-pagination-utils.ts +213 -50
  238. package/plugins/steve/subscribe.js +229 -42
  239. package/plugins/steve/worker/web-worker.advanced.js +3 -1
  240. package/rancher-components/BadgeState/BadgeState.vue +3 -1
  241. package/rancher-components/Banner/Banner.test.ts +51 -3
  242. package/rancher-components/Banner/Banner.vue +28 -6
  243. package/rancher-components/Form/Checkbox/Checkbox.vue +2 -2
  244. package/rancher-components/Form/LabeledInput/LabeledInput.test.ts +5 -1
  245. package/rancher-components/Form/ToggleSwitch/ToggleSwitch.vue +21 -1
  246. package/rancher-components/RcItemCard/RcItemCard.test.ts +189 -0
  247. package/rancher-components/RcItemCard/RcItemCard.vue +425 -0
  248. package/rancher-components/RcItemCard/RcItemCardAction.vue +24 -0
  249. package/rancher-components/RcItemCard/index.ts +2 -0
  250. package/store/auth.js +1 -0
  251. package/store/catalog.js +62 -24
  252. package/store/features.js +0 -1
  253. package/store/index.js +33 -14
  254. package/store/slideInPanel.ts +6 -0
  255. package/store/type-map.js +1 -0
  256. package/store/type-map.utils.ts +45 -2
  257. package/types/fleet.d.ts +36 -1
  258. package/types/kube/kube-api.ts +22 -0
  259. package/types/resources/settings.d.ts +19 -5
  260. package/types/shell/index.d.ts +595 -471
  261. package/types/store/dashboard-store.types.ts +41 -4
  262. package/types/store/pagination.types.ts +25 -3
  263. package/types/store/subscribe.types.ts +50 -0
  264. package/utils/auth.js +32 -3
  265. package/utils/cluster.js +24 -20
  266. package/utils/fleet-types.ts +0 -0
  267. package/utils/fleet.ts +200 -1
  268. package/utils/grafana.js +1 -0
  269. package/utils/object.js +0 -12
  270. package/utils/pagination-utils.ts +32 -3
  271. package/utils/pagination-wrapper.ts +132 -50
  272. package/utils/perf-setting.utils.ts +28 -0
  273. package/utils/selector-typed.ts +205 -0
  274. package/utils/selector.js +29 -6
  275. package/utils/settings.ts +4 -1
  276. package/utils/style.ts +39 -0
  277. package/utils/uiplugins.ts +10 -6
  278. package/utils/v-sphere.ts +5 -1
  279. package/utils/validators/formRules/__tests__/index.test.ts +36 -3
  280. package/utils/validators/formRules/index.ts +10 -3
  281. package/utils/window.js +11 -7
  282. package/components/__tests__/ApplicationCard.test.ts +0 -27
  283. package/components/cards/ApplicationCard.vue +0 -145
  284. package/components/fleet/ForceDirectedTreeChart/chartIcons.js +0 -17
  285. package/components/formatter/RKETemplateName.vue +0 -37
  286. package/config/secret.js +0 -14
  287. package/dialog/SaveAsRKETemplateDialog.vue +0 -139
  288. package/pages/c/_cluster/fleet/GitRepoGraphConfig.js +0 -249
  289. /package/{components/form/SSHKnownHosts → dialog}/__tests__/KnownHostsEditDialog.test.ts +0 -0
@@ -29,6 +29,11 @@ export default defineComponent({
29
29
  type: String,
30
30
  default: '',
31
31
  },
32
+
33
+ disabled: {
34
+ type: Boolean,
35
+ default: false,
36
+ },
32
37
  },
33
38
 
34
39
  emits: ['update:value'],
@@ -84,7 +89,10 @@ export default defineComponent({
84
89
  </script>
85
90
 
86
91
  <template>
87
- <span class="toggle-container">
92
+ <span
93
+ class="toggle-container"
94
+ :class="{'toggle-disabled': disabled}"
95
+ >
88
96
  <span
89
97
  class="label no-select hand"
90
98
  :class="{ active: !state}"
@@ -126,6 +134,18 @@ $toggle-height: 16px;
126
134
  span:last-child {
127
135
  padding-left: 6px;
128
136
  }
137
+
138
+ &.toggle-disabled {
139
+ pointer-events: none;
140
+
141
+ .slider {
142
+ background-color: var(--checkbox-disabled-bg);
143
+
144
+ &:before {
145
+ opacity: 0.6;
146
+ }
147
+ }
148
+ }
129
149
  }
130
150
  /* The switch - the box around the slider */
131
151
  .switch {
@@ -0,0 +1,189 @@
1
+ import { mount } from '@vue/test-utils';
2
+ import RcItemCard from './RcItemCard.vue';
3
+ import RcItemCardAction from './RcItemCardAction.vue';
4
+
5
+ class ResizeObserverMock {
6
+ observe = jest.fn();
7
+ unobserve = jest.fn();
8
+ disconnect = jest.fn();
9
+ }
10
+
11
+ global.ResizeObserver = ResizeObserverMock;
12
+
13
+ const id = 'test';
14
+
15
+ const baseProps = {
16
+ id,
17
+ value: { someProperty: 'some-value' },
18
+ image: { src: 'logo.png', alt: { text: 'Logo' } },
19
+ header: {
20
+ title: { text: 'Card Title' },
21
+ statuses: [
22
+ { icon: 'icon-one', tooltip: { text: 'Status One' } },
23
+ { icon: 'icon-two' }
24
+ ]
25
+ },
26
+ content: { text: 'Card description here' }
27
+ };
28
+
29
+ describe('rcItemCard', () => {
30
+ it('renders title, image, and content', () => {
31
+ const wrapper = mount(RcItemCard, { props: baseProps });
32
+
33
+ expect(wrapper.get('[data-testid="item-card-header-title"]').text()).toBe('Card Title');
34
+ expect(wrapper.get('[data-testid="item-card-content"]').text()).toContain('Card description here');
35
+ expect(wrapper.get('[data-testid="item-card-image"]')).toBeTruthy();
36
+ expect(wrapper.findAll(`[data-testid="item-card-header-statuses-status"]`)).toHaveLength(2);
37
+ });
38
+
39
+ it('renders pill only in medium variant', () => {
40
+ const wrapper = mount(RcItemCard, {
41
+ props: {
42
+ ...baseProps,
43
+ variant: 'medium',
44
+ pill: { label: { text: 'Installed' } }
45
+ }
46
+ });
47
+
48
+ expect(wrapper.get('[data-testid="item-card-pill"]').text()).toBe('Installed');
49
+
50
+ // now test that it's not rendered when variant is small
51
+ const wrapperSmall = mount(RcItemCard, {
52
+ props: {
53
+ ...baseProps,
54
+ variant: 'small',
55
+ pill: { label: { text: 'Installed' } }
56
+ }
57
+ });
58
+
59
+ expect(wrapperSmall.find('[data-testid="item-card-pill"]').exists()).toBe(false);
60
+ });
61
+
62
+ it('renders action-menu if slot content is provided for it', () => {
63
+ const wrapper = mount(RcItemCard, {
64
+ props: { ...baseProps },
65
+ slots: { 'item-card-actions': '<div class="test-slot-for-actions">test</div>' }
66
+ });
67
+
68
+ expect(wrapper.find('.test-slot-for-actions').exists()).toBe(true);
69
+ });
70
+
71
+ it('renders action-menu when actions are passed as a prop', () => {
72
+ const wrapper = mount(RcItemCard, {
73
+ props: {
74
+ ...baseProps,
75
+ actions: [{ action: 'test', label: 'test' }]
76
+ }
77
+ });
78
+
79
+ expect(wrapper.findComponent('[data-testid="item-card-header-action-menu"]').exists()).toBe(true);
80
+ });
81
+
82
+ it('does not render action-menu if no slot and no actions', () => {
83
+ const wrapper = mount(RcItemCard, { props: { ...baseProps } });
84
+
85
+ expect(wrapper.findComponent('[data-testid="item-card-header-action-menu"]').exists()).toBe(false);
86
+ });
87
+
88
+ it('emits card-click when clicked and clickable', async() => {
89
+ const wrapper = mount(RcItemCard, {
90
+ props: {
91
+ ...baseProps,
92
+ clickable: true
93
+ }
94
+ });
95
+
96
+ await wrapper.trigger('click');
97
+
98
+ const emitted = wrapper.emitted('card-click');
99
+
100
+ expect(emitted).toBeTruthy();
101
+ expect(emitted?.[0]).toStrictEqual([{ someProperty: 'some-value' }]);
102
+ });
103
+
104
+ it('does not emit card-click when clicking on rc-item-card-action content', async() => {
105
+ const wrapper = mount(RcItemCard, {
106
+ props: {
107
+ ...baseProps,
108
+ clickable: true
109
+ },
110
+ global: { components: { RcItemCardAction } },
111
+ slots: { 'item-card-actions': '<rc-item-card-action>Click me</rc-item-card-action>' }
112
+ });
113
+
114
+ await wrapper.get('[data-testid="rc-item-card-action"]').trigger('click');
115
+
116
+ expect(wrapper.emitted('card-click')).toBeFalsy();
117
+ });
118
+
119
+ it('sets role and tabindex when clickable', () => {
120
+ const wrapper = mount(RcItemCard, {
121
+ props: {
122
+ ...baseProps,
123
+ clickable: true
124
+ }
125
+ });
126
+
127
+ const root = wrapper.get(`[data-testid="item-card-${ id }"]`);
128
+
129
+ expect(root.attributes('role')).toBe('button');
130
+ expect(root.attributes('tabindex')).toBe('0');
131
+ });
132
+
133
+ it('does not set role or tabindex when not clickable', () => {
134
+ const wrapper = mount(RcItemCard, {
135
+ props: {
136
+ ...baseProps,
137
+ clickable: false
138
+ }
139
+ });
140
+
141
+ const root = wrapper.get(`[data-testid="item-card-${ id }"]`);
142
+
143
+ expect(root.attributes('role')).toBeUndefined();
144
+ expect(root.attributes('tabindex')).toBeUndefined();
145
+ });
146
+
147
+ it('supports keyboard enter to trigger click', async() => {
148
+ const wrapper = mount(RcItemCard, {
149
+ props: {
150
+ ...baseProps,
151
+ clickable: true
152
+ }
153
+ });
154
+
155
+ await wrapper.trigger('keydown.enter');
156
+ expect(wrapper.emitted('card-click')).toBeTruthy();
157
+ });
158
+
159
+ it('supports slot for footer and sub-header', () => {
160
+ const wrapper = mount(RcItemCard, {
161
+ props: baseProps,
162
+ slots: {
163
+ 'item-card-footer': '<div>FooterContent</div>',
164
+ 'item-card-sub-header': '<div>SubHeaderContent</div>'
165
+ }
166
+ });
167
+
168
+ expect(wrapper.text()).toContain('FooterContent');
169
+ expect(wrapper.text()).toContain('SubHeaderContent');
170
+ });
171
+
172
+ it('renders icon with custom color', () => {
173
+ const wrapper = mount(RcItemCard, {
174
+ props: {
175
+ ...baseProps,
176
+ header: {
177
+ ...baseProps.header,
178
+ statuses: [
179
+ { icon: 'icon-custom', customColor: 'red' }
180
+ ]
181
+ }
182
+ }
183
+ });
184
+
185
+ const icon = wrapper.get('[data-testid="item-card-header-status-0"]');
186
+
187
+ expect(icon.attributes('style')).toContain('color: red');
188
+ });
189
+ });
@@ -0,0 +1,425 @@
1
+ <script setup lang="ts">
2
+ import { ref, computed } from 'vue';
3
+ import { useStore } from 'vuex';
4
+ import { useI18n } from '@shell/composables/useI18n';
5
+ import LazyImage from '@shell/components/LazyImage.vue';
6
+ import { DropdownOption } from '@components/RcDropdown/types';
7
+ import ActionMenu from '@shell/components/ActionMenuShell.vue';
8
+
9
+ const store = useStore();
10
+ const { t } = useI18n(store);
11
+
12
+ /**
13
+ * Variants available for ItemCard layout
14
+ */
15
+ type RcItemCardVariant = 'small' | 'medium';
16
+
17
+ /**
18
+ * A label that can be either plain text or a translatable key.
19
+ */
20
+ type Label = {
21
+ key?: string;
22
+ text?: string;
23
+ };
24
+
25
+ /**
26
+ * Represents an image used in the card.
27
+ */
28
+ type Image = {
29
+ src: string;
30
+ alt?: Label;
31
+ };
32
+
33
+ /**
34
+ * Optional pill badge, typically used to highlight a tag or state.
35
+ */
36
+ type Pill = {
37
+ label: Label;
38
+ tooltip?: Label;
39
+ };
40
+
41
+ /**
42
+ * Represents an icon-based status indicator shown in the card header.
43
+ */
44
+ type Status = {
45
+ icon: string;
46
+ color?: string;
47
+ customColor?: string;
48
+ tooltip?: Label;
49
+ handleClick?: () => void;
50
+ };
51
+
52
+ /**
53
+ * Header metadata for the card.
54
+ */
55
+ type Header = {
56
+ title?: Label;
57
+ statuses?: Status[];
58
+ };
59
+
60
+ /**
61
+ * The generic data value passed to the card.
62
+ */
63
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
64
+ type ItemValue = Record<string, any>;
65
+
66
+ /**
67
+ * Props accepted by the ItemCard component.
68
+ */
69
+ interface RcItemCardProps {
70
+ /** Unique identifier for the card (used in test IDs) */
71
+ id: string;
72
+
73
+ /** Any object value associated with this card */
74
+ value: ItemValue;
75
+
76
+ /** Card title, status icons and action menu. Image will be included in the header in small variant too */
77
+ header: Header;
78
+
79
+ /** Optional image to show in card (position depends on variant). A slot is available for it too #item-card-image */
80
+ image?: Image;
81
+
82
+ /** Optional actions that will be displayed inside an action-menu */
83
+ actions?: DropdownOption;
84
+
85
+ /** Text content inside the card body. A slot is available for it too #item-card-content */
86
+ content?: Label;
87
+
88
+ /** Layout variant: 'small' or 'medium' */
89
+ variant?: RcItemCardVariant;
90
+
91
+ /** Optional pill shown (only if variant is not 'small'). A slot is available for it too #item-card-pill */
92
+ pill?: Pill;
93
+
94
+ /** Makes the card clickable and emits 'card-click' on click/enter/space */
95
+ clickable?: boolean;
96
+ }
97
+
98
+ const props = defineProps<RcItemCardProps>();
99
+
100
+ /**
101
+ * Emits 'card-click' when card is clicked or activated via keyboard.
102
+ */
103
+ const emit = defineEmits<{( e: 'card-click', value: ItemValue): void; }>();
104
+
105
+ /**
106
+ * Handles the card click while avoiding nested interactive elements
107
+ * By using RcItemCardAction.vue the 'item-card-action' attribute automatically gets added
108
+ * which then gets used to ignore 'card-click'
109
+ */
110
+ function _handleCardClick(e: MouseEvent | KeyboardEvent) {
111
+ const interactiveSelector = '[item-card-action]';
112
+
113
+ // Prevent card click if the user clicks on an inner actionable element like repo, category, or tag
114
+ if ((e.target as HTMLElement).closest(interactiveSelector)) {
115
+ return;
116
+ }
117
+
118
+ emit('card-click', props.value);
119
+ }
120
+
121
+ /**
122
+ * Utility to resolve localized or plain text labels.
123
+ */
124
+ function labelText(label?: Label): string {
125
+ return label?.key ? t(label.key) : label?.text ?? '';
126
+ }
127
+
128
+ /** ---------------- data ------------------ */
129
+ const cardEl = ref<HTMLElement | null>(null);
130
+
131
+ /** ---------------- Computed ------------------ */
132
+
133
+ const headerTitle = computed(() => labelText(props.header.title));
134
+ const imageAlt = computed(() => labelText(props.image?.alt));
135
+ const pillLabel = computed(() => labelText(props.pill?.label));
136
+ const pillTooltip = computed(() => labelText(props.pill?.tooltip));
137
+ const contentText = computed(() => labelText(props.content));
138
+ const statusTooltips = computed(() => props.header.statuses?.map((status) => labelText(status.tooltip)) || []);
139
+
140
+ const cardMeta = computed(() => ({
141
+ ariaLabel: props.clickable ? t('itemCard.ariaLabel.clickable', { cardTitle: labelText(props.header.title) }) : undefined,
142
+ tabIndex: props.clickable ? '0' : undefined,
143
+ role: props.clickable ? 'button' : undefined
144
+ }));
145
+
146
+ </script>
147
+
148
+ <template>
149
+ <div
150
+ ref="cardEl"
151
+ class="item-card"
152
+ :role="cardMeta.role"
153
+ :tabindex="cardMeta.tabIndex"
154
+ :aria-label="cardMeta.ariaLabel"
155
+ :data-testid="`item-card-${id}`"
156
+ @click="_handleCardClick"
157
+ @keydown.enter="_handleCardClick"
158
+ @keydown.space.prevent="_handleCardClick"
159
+ >
160
+ <div :class="['item-card-body', variant]">
161
+ <template v-if="variant !== 'small'">
162
+ <div>
163
+ <slot name="item-card-image">
164
+ <div
165
+ v-if="image"
166
+ :class="['item-card-image', variant]"
167
+ data-testid="item-card-image"
168
+ >
169
+ <LazyImage
170
+ :src="image.src"
171
+ :alt="imageAlt"
172
+ :style="{'width': '40px', 'height': '40px', 'object-fit': 'contain'}"
173
+ />
174
+ </div>
175
+ </slot>
176
+ <slot name="item-card-pill">
177
+ <div
178
+ v-if="pill"
179
+ v-clean-tooltip="pillTooltip"
180
+ class="item-card-pill"
181
+ data-testid="item-card-pill"
182
+ >
183
+ {{ pillLabel }}
184
+ </div>
185
+ </slot>
186
+ </div>
187
+ </template>
188
+
189
+ <div :class="['item-card-body-details', variant]">
190
+ <div :class="['item-card-header', variant]">
191
+ <div class="item-card-header-left">
192
+ <template v-if="variant === 'small'">
193
+ <slot name="item-card-image">
194
+ <div
195
+ v-if="image"
196
+ :class="['item-card-image', variant]"
197
+ data-testid="item-card-image"
198
+ >
199
+ <LazyImage
200
+ :src="image.src"
201
+ :alt="imageAlt"
202
+ :style="{'width': '24px', 'height': '24px', 'object-fit': 'contain'}"
203
+ />
204
+ </div>
205
+ </slot>
206
+ </template>
207
+ <slot name="item-card-header-title">
208
+ <h3
209
+ v-if="header.title"
210
+ v-clean-tooltip="headerTitle"
211
+ :class="['item-card-header-title', variant]"
212
+ data-testid="item-card-header-title"
213
+ >
214
+ {{ headerTitle }}
215
+ </h3>
216
+ </slot>
217
+ </div>
218
+ <div class="item-card-header-right">
219
+ <div
220
+ v-if="header.statuses?.length"
221
+ class="item-card-header-statuses"
222
+ >
223
+ <div
224
+ v-for="(status, i) in header.statuses"
225
+ :key="i"
226
+ class="item-card-header-statuses-status"
227
+ data-testid="item-card-header-statuses-status"
228
+ >
229
+ <i
230
+ v-clean-tooltip="statusTooltips[i]"
231
+ :class="['icon', status.icon, status.color]"
232
+ :style="{color: status.customColor}"
233
+ :data-testid="`item-card-header-status-${i}`"
234
+ />
235
+ </div>
236
+ </div>
237
+
238
+ <template v-if="$slots['item-card-actions']">
239
+ <div class="item-card-header-action-menu">
240
+ <slot name="item-card-actions" />
241
+ </div>
242
+ </template>
243
+ <template v-else-if="actions">
244
+ <div class="item-card-header-action-menu">
245
+ <ActionMenu
246
+ data-testid="item-card-header-action-menu"
247
+ :custom-actions="actions"
248
+ />
249
+ </div>
250
+ </template>
251
+ </div>
252
+ </div>
253
+
254
+ <slot name="item-card-sub-header" />
255
+
256
+ <template v-if="$slots['item-card-content']">
257
+ <slot name="item-card-content">
258
+ <div
259
+ class="item-card-content"
260
+ data-testid="item-card-content"
261
+ />
262
+ </slot>
263
+ </template>
264
+ <template v-else-if="content">
265
+ <div
266
+ class="item-card-content"
267
+ data-testid="item-card-content"
268
+ >
269
+ <p>{{ contentText }}</p>
270
+ </div>
271
+ </template>
272
+
273
+ <slot name="item-card-footer" />
274
+ </div>
275
+ </div>
276
+ </div>
277
+ </template>
278
+
279
+ <style scoped lang="scss">
280
+ $image-medium-box-width: 48px;
281
+
282
+ .item-card {
283
+ display: flex;
284
+ padding: 16px;
285
+ align-items: flex-start;
286
+ gap: var(--gap-lg);
287
+ border-radius: var(--border-radius-md);
288
+ border: 1px solid var(--border);
289
+ background: var(--body-bg);
290
+
291
+ &:hover {
292
+ border-color: var(--primary);
293
+ }
294
+
295
+ &:focus-visible {
296
+ @include focus-outline;
297
+ outline-offset: -2px;
298
+ }
299
+
300
+ &-image {
301
+ width: $image-medium-box-width;
302
+ height: $image-medium-box-width;
303
+ display: flex;
304
+ align-items: center;
305
+ justify-content: center;
306
+ background: #fff;
307
+ border-radius: var(--border-radius);
308
+
309
+ &.small {
310
+ width: 32px;
311
+ height: 32px;
312
+ margin-right: 12px;
313
+ }
314
+ }
315
+
316
+ &-header {
317
+ display: flex;
318
+ align-items: center;
319
+ justify-content: space-between;
320
+ width: 100%;
321
+ height: 24px;
322
+ color: var(--body-text);
323
+
324
+ &-left,
325
+ &-right {
326
+ display: flex;
327
+ align-items: center;
328
+ }
329
+
330
+ &-left {
331
+ flex-grow: 1;
332
+ }
333
+
334
+ &-title {
335
+ max-width: 60%;
336
+ font-size: 18px;
337
+ font-weight: 600;
338
+ margin-bottom: 0px;
339
+ line-height: 24px;
340
+ text-overflow: ellipsis;
341
+ white-space: nowrap;
342
+ overflow: hidden;
343
+ }
344
+
345
+ &-statuses {
346
+ display: flex;
347
+ align-items: flex-start;
348
+ gap: 12px;
349
+
350
+ &-status {
351
+ width: 24px;
352
+ height: 24px;
353
+ display: flex;
354
+ align-items: center;
355
+ justify-content: center;
356
+
357
+ .icon {
358
+ font-size: 23px;
359
+
360
+ &.error { color: var(--error); }
361
+ &.info { color: var(--info); }
362
+ &.success { color: var(--success); }
363
+ }
364
+ }
365
+ }
366
+
367
+ &-action-menu {
368
+ margin-left: 12px;
369
+ }
370
+ }
371
+
372
+ &-content {
373
+ display: -webkit-box;
374
+ -webkit-line-clamp: 3;
375
+ -webkit-box-orient: vertical;
376
+ overflow: hidden;
377
+ text-overflow: ellipsis;
378
+ line-height: 21px;
379
+ word-break: break-word;
380
+ }
381
+
382
+ &-pill {
383
+ display: flex;
384
+ width: $image-medium-box-width;
385
+ padding: 4px 8px;
386
+ margin-top: 16px;
387
+ justify-content: center;
388
+ align-items: center;
389
+ border-radius: var(--border-radius);
390
+ background: var(--default);
391
+ text-transform: uppercase;
392
+ color: var(--disabled-text);
393
+ font-size: 10px;
394
+ font-weight: 600;
395
+ }
396
+
397
+ &-body {
398
+ display: flex;
399
+ flex-direction: row;
400
+ width: 100%;
401
+ gap: var(--gap-lg);
402
+
403
+ &.small {
404
+ flex-direction: column;
405
+ align-items: flex-start;
406
+ gap: var(--gap);
407
+ flex: 1;
408
+ }
409
+
410
+ &-details {
411
+ display: flex;
412
+ flex-direction: column;
413
+ align-items: flex-start;
414
+ width: calc(100% - var(--gap-lg) - $image-medium-box-width);
415
+ gap: var(--gap);
416
+ flex: 1;
417
+
418
+ &.small {
419
+ width: 100%;
420
+ }
421
+ }
422
+ }
423
+ }
424
+
425
+ </style>
@@ -0,0 +1,24 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * RcItemCardAction
4
+ *
5
+ * This component is used to wrap any interactive elements (like buttons, links)
6
+ * inside an RcItemCard so they don't trigger the card-click event.
7
+ *
8
+ * Usage:
9
+ *
10
+ * <rc-item-card-action @click="doSomething">
11
+ * <a href="#">Click me</a>
12
+ * </rc-item-card-action>
13
+ *
14
+ */
15
+ </script>
16
+
17
+ <template>
18
+ <div
19
+ item-card-action
20
+ data-testid="rc-item-card-action"
21
+ >
22
+ <slot />
23
+ </div>
24
+ </template>
@@ -0,0 +1,2 @@
1
+ export { default as RcItemCard } from './RcItemCard.vue';
2
+ export { default as RcItemCardAction } from './RcItemCardAction.vue';
package/store/auth.js CHANGED
@@ -14,6 +14,7 @@ export const BASE_SCOPES = {
14
14
  azuread: [],
15
15
  keycloakoidc: ['openid profile email'],
16
16
  genericoidc: ['openid profile email'],
17
+ cognito: ['openid email'],
17
18
  };
18
19
 
19
20
  const KEY = 'rc_nonce';