@rancher/shell 3.0.12-rc.3 → 3.0.12-rc.4

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 (258) hide show
  1. package/assets/styles/global/_layout.scss +4 -0
  2. package/assets/translations/en-us.yaml +144 -41
  3. package/assets/translations/zh-hans.yaml +1 -7
  4. package/chart/monitoring/ClusterSelector.vue +0 -21
  5. package/chart/monitoring/prometheus/index.vue +6 -3
  6. package/components/CruResource.vue +161 -14
  7. package/components/ExplorerMembers.vue +8 -4
  8. package/components/ExplorerProjectsNamespaces.vue +10 -6
  9. package/components/GrowlManager.vue +4 -0
  10. package/components/MgmtNodeList.vue +184 -0
  11. package/components/Resource/Detail/Card/StateCard/__tests__/composables.test.ts +90 -1
  12. package/components/Resource/Detail/Card/StateCard/composables.ts +57 -87
  13. package/components/Resource/Detail/Card/StatusCard/__tests__/StatusCard.test.ts +61 -0
  14. package/components/Resource/Detail/Card/StatusCard/index.vue +61 -15
  15. package/components/Resource/Detail/Metadata/IdentifyingInformation/index.vue +2 -0
  16. package/components/Resource/Detail/Metadata/KeyValue.vue +5 -2
  17. package/components/Resource/Detail/Metadata/KeyValueRow.vue +2 -6
  18. package/components/ResourceDetail/index.vue +1 -1
  19. package/components/ResourceList/Masthead.vue +7 -1
  20. package/components/ResourceList/index.vue +82 -1
  21. package/components/RichTranslation.vue +5 -2
  22. package/components/Setting.vue +1 -0
  23. package/components/SubtleLink.vue +31 -6
  24. package/components/Tabbed/Tab.vue +29 -3
  25. package/components/Tabbed/index.vue +25 -3
  26. package/components/TableOfContents/TableOfContents.vue +109 -0
  27. package/components/TableOfContents/composables.ts +258 -0
  28. package/components/Window/ContainerShell.vue +21 -11
  29. package/components/Window/__tests__/ContainerShell.test.ts +107 -37
  30. package/components/Wizard.vue +9 -4
  31. package/components/fleet/AppCoChartGrid.vue +401 -0
  32. package/components/fleet/AppCoEmptyState.vue +127 -0
  33. package/components/fleet/AppCoPageHeader.vue +119 -0
  34. package/components/fleet/AppCoVersionSelect.vue +70 -0
  35. package/components/fleet/FleetClusterTargets/ClusterSelectionFields.vue +217 -0
  36. package/components/fleet/FleetClusterTargets/TargetsList.vue +123 -35
  37. package/components/fleet/FleetClusterTargets/index.vue +189 -146
  38. package/components/fleet/FleetIntro.vue +7 -3
  39. package/components/fleet/FleetNoWorkspaces.vue +7 -3
  40. package/components/fleet/FleetSecretSelector.vue +5 -3
  41. package/components/fleet/FleetValuesFrom.vue +8 -2
  42. package/components/fleet/GitRepoTargetTab.vue +0 -2
  43. package/components/fleet/HelmOpAdvancedTab.vue +19 -53
  44. package/components/fleet/HelmOpAppCoConfigTab.vue +593 -0
  45. package/components/fleet/HelmOpAppCoResourcesSection.vue +162 -0
  46. package/components/fleet/HelmOpResourcesSection.vue +82 -0
  47. package/components/fleet/HelmOpTargetOptionsSection.vue +89 -0
  48. package/components/fleet/HelmOpTargetTab.vue +64 -60
  49. package/components/fleet/HelmOpValuesTab.vue +129 -105
  50. package/components/fleet/__tests__/AppCoEmptyState.test.ts +71 -0
  51. package/components/fleet/__tests__/AppCoVersionSelect.test.ts +36 -0
  52. package/components/fleet/__tests__/ClusterSelectionFields.test.ts +62 -0
  53. package/components/fleet/__tests__/FleetClusterTargets.test.ts +253 -0
  54. package/components/fleet/__tests__/FleetSecretSelector.test.ts +16 -0
  55. package/components/fleet/__tests__/FleetValuesFrom.test.ts +44 -0
  56. package/components/fleet/__tests__/HelmOpAppCoConfigTab.test.ts +59 -0
  57. package/components/fleet/__tests__/HelmOpAppCoResourcesSection.test.ts +62 -0
  58. package/components/fleet/__tests__/HelmOpResourcesSection.test.ts +43 -0
  59. package/components/fleet/__tests__/HelmOpTargetOptionsSection.test.ts +34 -0
  60. package/components/fleet/__tests__/HelmOpValuesTab.test.ts +39 -0
  61. package/components/fleet/__tests__/__snapshots__/AppCoEmptyState.test.ts.snap +97 -0
  62. package/components/fleet/__tests__/__snapshots__/AppCoVersionSelect.test.ts.snap +30 -0
  63. package/components/fleet/__tests__/__snapshots__/ClusterSelectionFields.test.ts.snap +209 -0
  64. package/components/fleet/__tests__/__snapshots__/HelmOpTargetOptionsSection.test.ts.snap +140 -0
  65. package/components/fleet/dashboard/Empty.vue +8 -4
  66. package/components/fleet/dashboard/ResourceCard.vue +28 -0
  67. package/components/fleet/dashboard/ResourceDetails.vue +28 -0
  68. package/components/fleet/dashboard/__tests__/ResourceCard.test.ts +87 -0
  69. package/components/form/ArrayList.vue +61 -4
  70. package/components/form/KeyValue.vue +23 -2
  71. package/components/form/LabeledSelect.vue +39 -1
  72. package/components/form/Labels.vue +22 -3
  73. package/components/form/NameNsDescription.vue +13 -5
  74. package/components/form/ResourceTabs/index.vue +1 -0
  75. package/components/form/__tests__/NameNsDescription.test.ts +75 -0
  76. package/components/formatter/InternalExternalIP.vue +10 -4
  77. package/components/formatter/ServiceTargets.vue +26 -7
  78. package/components/formatter/__tests__/InternalExternalIP.test.ts +132 -0
  79. package/components/formatter/__tests__/ServiceTargets.test.ts +412 -0
  80. package/components/nav/Header.vue +4 -0
  81. package/components/nav/TopLevelMenu.vue +7 -2
  82. package/components/nav/__tests__/Header.test.ts +15 -0
  83. package/components/nav/__tests__/TopLevelMenu.test.ts +120 -2
  84. package/components/templates/default.vue +9 -4
  85. package/components/templates/home.vue +9 -4
  86. package/components/templates/plain.vue +9 -4
  87. package/composables/useHelmOpResources.test.ts +56 -0
  88. package/composables/useHelmOpResources.ts +32 -0
  89. package/composables/useStateColor.test.ts +325 -0
  90. package/composables/useStateColor.ts +128 -0
  91. package/config/home-links.js +1 -1
  92. package/config/labels-annotations.js +1 -0
  93. package/config/product/explorer.js +17 -4
  94. package/config/product/manager.js +2 -0
  95. package/config/router/index.js +16 -0
  96. package/config/router/navigation-guards/__tests__/authentication.test.ts +130 -0
  97. package/config/router/navigation-guards/authentication.js +10 -4
  98. package/config/router/routes.js +20 -6
  99. package/config/settings.ts +0 -2
  100. package/config/table-headers.js +3 -4
  101. package/config/types.js +9 -0
  102. package/core/plugin-products-base.ts +3 -3
  103. package/core/plugin-types.ts +83 -30
  104. package/core/plugin.ts +3 -0
  105. package/core/types-provisioning.ts +34 -1
  106. package/core/types.ts +15 -2
  107. package/detail/__tests__/provisioning.cattle.io.cluster.test.ts +114 -0
  108. package/detail/__tests__/workload.test.ts +3 -152
  109. package/detail/catalog.cattle.io.clusterrepo.vue +1 -1
  110. package/detail/provisioning.cattle.io.cluster.vue +30 -4
  111. package/detail/workload/index.vue +12 -55
  112. package/edit/__tests__/catalog.cattle.io.clusterrepo.test.ts +248 -0
  113. package/edit/__tests__/fleet.cattle.io.helmop.test.ts +105 -0
  114. package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/General.test.ts.snap +6 -0
  115. package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/index.test.ts.snap +1 -0
  116. package/edit/auth/__tests__/azuread.test.ts +34 -9
  117. package/edit/auth/__tests__/github.test.ts +234 -0
  118. package/edit/auth/__tests__/oidc.test.ts +26 -6
  119. package/edit/auth/__tests__/saml.test.ts +196 -0
  120. package/edit/auth/azuread.vue +128 -95
  121. package/edit/auth/github.vue +72 -13
  122. package/edit/auth/ldap/__tests__/index.test.ts +206 -0
  123. package/edit/auth/ldap/config.vue +8 -0
  124. package/edit/auth/ldap/index.vue +75 -1
  125. package/edit/auth/oidc.vue +119 -73
  126. package/edit/auth/saml.vue +76 -12
  127. package/edit/catalog.cattle.io.clusterrepo.vue +140 -32
  128. package/edit/fleet.cattle.io.helmop.vue +491 -136
  129. package/edit/management.cattle.io.user.vue +5 -2
  130. package/edit/provisioning.cattle.io.cluster/rke2.vue +84 -10
  131. package/edit/provisioning.cattle.io.cluster/tabs/MachinePool.vue +11 -0
  132. package/list/group.principal.vue +5 -4
  133. package/list/harvesterhci.io.management.cluster.vue +8 -9
  134. package/list/management.cattle.io.user.vue +12 -9
  135. package/list/provisioning.cattle.io.cluster.vue +16 -10
  136. package/mixins/__tests__/auth-config.test.ts +90 -0
  137. package/mixins/__tests__/chart.test.ts +94 -0
  138. package/mixins/__tests__/resource-fetch-api-pagination.test.ts +48 -0
  139. package/mixins/auth-config.js +7 -0
  140. package/mixins/chart.js +11 -2
  141. package/mixins/child-hook.js +12 -6
  142. package/mixins/create-edit-view/impl.js +5 -3
  143. package/mixins/resource-fetch-api-pagination.js +21 -1
  144. package/models/__tests__/catalog.cattle.io.clusterrepo.test.ts +57 -0
  145. package/models/__tests__/compliance.cattle.io.clusterscan.test.ts +144 -0
  146. package/models/__tests__/fleet-application.test.ts +175 -0
  147. package/models/__tests__/fleet.cattle.io.bundle.test.ts +169 -0
  148. package/models/__tests__/fleet.cattle.io.helmop.test.ts +84 -0
  149. package/models/__tests__/management.cattle.io.node.ts +22 -0
  150. package/models/__tests__/namespace.test.ts +36 -0
  151. package/models/__tests__/provisioning.cattle.io.cluster.test.ts +49 -0
  152. package/models/__tests__/workload.test.ts +401 -26
  153. package/models/catalog.cattle.io.clusterrepo.js +28 -4
  154. package/models/compliance.cattle.io.clusterscan.js +39 -4
  155. package/models/fleet-application.js +4 -0
  156. package/models/fleet.cattle.io.helmop.js +20 -1
  157. package/models/management.cattle.io.cluster.js +18 -2
  158. package/models/management.cattle.io.node.js +44 -3
  159. package/models/namespace.js +1 -1
  160. package/models/pod.js +33 -1
  161. package/models/provisioning.cattle.io.cluster.js +5 -5
  162. package/models/workload.js +108 -13
  163. package/models/workload.service.js +5 -0
  164. package/package.json +14 -13
  165. package/pages/about.vue +5 -6
  166. package/pages/auth/login.vue +0 -35
  167. package/pages/auth/setup.vue +11 -0
  168. package/pages/c/_cluster/apps/charts/AppChartCardFooter.vue +2 -2
  169. package/pages/c/_cluster/apps/charts/AppChartCardSubHeader.vue +10 -1
  170. package/pages/c/_cluster/apps/charts/__tests__/index.test.ts +93 -0
  171. package/pages/c/_cluster/apps/charts/chart.vue +2 -1
  172. package/pages/c/_cluster/apps/charts/index.vue +48 -10
  173. package/pages/c/_cluster/apps/charts/install.vue +122 -116
  174. package/pages/c/_cluster/auth/roles/index.vue +5 -4
  175. package/pages/c/_cluster/explorer/workload-dashboard/ByNamespaceSection.vue +31 -0
  176. package/pages/c/_cluster/explorer/workload-dashboard/ByStateSection.vue +138 -0
  177. package/pages/c/_cluster/explorer/workload-dashboard/ByTypeSection.vue +30 -0
  178. package/pages/c/_cluster/explorer/workload-dashboard/WorkloadCard.vue +155 -0
  179. package/pages/c/_cluster/explorer/workload-dashboard/WorkloadNamespaceCard.vue +142 -0
  180. package/pages/c/_cluster/explorer/workload-dashboard/WorkloadTypeCard.vue +159 -0
  181. package/pages/c/_cluster/explorer/workload-dashboard/__tests__/composable.test.ts +561 -0
  182. package/pages/c/_cluster/explorer/workload-dashboard/composable.ts +440 -0
  183. package/pages/c/_cluster/explorer/workload-dashboard/index.vue +187 -0
  184. package/pages/c/_cluster/explorer/workload-dashboard/types.ts +80 -0
  185. package/pages/c/_cluster/fleet/application/create.vue +187 -136
  186. package/pages/c/_cluster/fleet/application/index.vue +5 -3
  187. package/pages/c/_cluster/fleet/application/suse-app-collection/ChartDetailBody.vue +338 -0
  188. package/pages/c/_cluster/fleet/application/suse-app-collection/ChartDetailHeader.vue +121 -0
  189. package/pages/c/_cluster/fleet/application/suse-app-collection/chart.vue +369 -0
  190. package/pages/c/_cluster/fleet/application/suse-app-collection/charts.vue +248 -0
  191. package/pages/c/_cluster/fleet/application/suse-app-collection/credentials.vue +310 -0
  192. package/pages/c/_cluster/fleet/index.vue +2 -2
  193. package/pages/c/_cluster/uiplugins/__tests__/index.test.ts +96 -0
  194. package/pages/c/_cluster/uiplugins/index.vue +15 -0
  195. package/pages/fail-whale.vue +16 -11
  196. package/pages/home.vue +16 -46
  197. package/plugins/clean-html.d.ts +9 -0
  198. package/plugins/dashboard-store/__tests__/resource-class.test.ts +93 -0
  199. package/plugins/dashboard-store/resource-class.js +62 -7
  200. package/plugins/steve/__tests__/actions.test.ts +212 -0
  201. package/plugins/steve/actions.js +96 -0
  202. package/plugins/steve/steve-pagination-utils.ts +1 -1
  203. package/rancher-components/Accordion/Accordion.vue +53 -9
  204. package/rancher-components/Form/Checkbox/Checkbox.vue +14 -0
  205. package/rancher-components/Form/Radio/RadioButton.vue +17 -1
  206. package/rancher-components/Form/Radio/RadioGroup.vue +10 -0
  207. package/rancher-components/Pill/RcTag/RcTag.vue +3 -2
  208. package/rancher-components/RcButton/RcButton.test.ts +103 -0
  209. package/rancher-components/RcButton/RcButton.vue +94 -15
  210. package/rancher-components/RcButton/types.ts +3 -0
  211. package/rancher-components/RcItemCard/RcItemCard.test.ts +18 -0
  212. package/rancher-components/RcItemCard/RcItemCard.vue +2 -2
  213. package/rancher-components/RcSection/RcSection.vue +28 -3
  214. package/scripts/extension/helm/package/Dockerfile +1 -1
  215. package/scripts/test-plugins-build.sh +2 -1
  216. package/store/__tests__/notifications.test.ts +434 -0
  217. package/store/catalog.js +57 -0
  218. package/store/plugins.js +7 -4
  219. package/types/components/buttonGroup.ts +5 -0
  220. package/types/shell/index.d.ts +104 -70
  221. package/utils/__tests__/auth.test.ts +273 -0
  222. package/utils/__tests__/computed.test.ts +193 -0
  223. package/utils/__tests__/cspAdaptor.test.ts +163 -0
  224. package/utils/__tests__/dom.test.ts +81 -0
  225. package/utils/__tests__/duration.test.ts +37 -1
  226. package/utils/__tests__/dynamic-importer.test.ts +102 -0
  227. package/utils/__tests__/fleet-appco.test.ts +312 -0
  228. package/utils/__tests__/monitoring.test.ts +130 -0
  229. package/utils/__tests__/object.test.ts +22 -0
  230. package/utils/__tests__/platform.test.ts +91 -0
  231. package/utils/__tests__/position.test.ts +237 -0
  232. package/utils/__tests__/provider.test.ts +51 -1
  233. package/utils/__tests__/queue.test.ts +232 -0
  234. package/utils/__tests__/release-notes.test.ts +221 -0
  235. package/utils/__tests__/router.test.js +254 -1
  236. package/utils/__tests__/select.test.ts +208 -0
  237. package/utils/__tests__/time.test.ts +265 -1
  238. package/utils/__tests__/title.test.ts +47 -0
  239. package/utils/__tests__/width.test.ts +53 -0
  240. package/utils/__tests__/window.test.ts +158 -0
  241. package/utils/__tests__/xccdf.test.ts +126 -6
  242. package/utils/crypto/__tests__/browserHashUtils.test.ts +98 -0
  243. package/utils/crypto/__tests__/index.test.ts +144 -0
  244. package/utils/duration.ts +104 -0
  245. package/utils/dynamic-content/__tests__/notification-handler.test.ts +196 -0
  246. package/utils/dynamic-content/info.ts +2 -1
  247. package/utils/error.js +13 -0
  248. package/utils/fleet-appco.ts +323 -0
  249. package/utils/object.js +22 -2
  250. package/utils/provider.ts +12 -0
  251. package/utils/validators/__tests__/container-images.test.ts +104 -0
  252. package/utils/validators/__tests__/flow-output.test.ts +91 -0
  253. package/utils/validators/__tests__/logging-outputs.test.ts +58 -0
  254. package/utils/validators/__tests__/monitoring-route.test.ts +119 -0
  255. package/utils/xccdf.ts +39 -42
  256. package/vue.config.js +1 -1
  257. package/pages/support/index.vue +0 -264
  258. package/utils/duration.js +0 -43
@@ -12,47 +12,72 @@ jest.mock('@shell/utils/crypto', () => {
12
12
  return {
13
13
  __esModule: true,
14
14
  ...originalModule,
15
- base64Decode: jest.fn().mockImplementation((str:String) => str)
15
+ base64Decode: jest.fn().mockImplementation((str:string) => str)
16
16
  };
17
17
  });
18
18
 
19
+ const mockOnData = jest.fn();
20
+ const mockLoadAddon = jest.fn();
21
+ const mockOpen = jest.fn();
22
+ const mockFocus = jest.fn();
23
+ const mockWrite = jest.fn();
24
+ const mockWriteln = jest.fn();
25
+ const mockReset = jest.fn();
26
+ const mockOnResize = jest.fn();
27
+ const mockPaste = jest.fn();
28
+ const mockDispose = jest.fn();
29
+ const mockClear = jest.fn();
30
+
31
+ jest.mock(/* webpackChunkName: "xterm" */ '@xterm/xterm', () => {
32
+ return {
33
+ Terminal: class {
34
+ onData = mockOnData;
35
+ loadAddon = mockLoadAddon;
36
+ open = mockOpen;
37
+ focus = mockFocus;
38
+ write = mockWrite;
39
+ writeln = mockWriteln;
40
+ reset = mockReset;
41
+ onResize = mockOnResize;
42
+ paste = mockPaste;
43
+ dispose = mockDispose;
44
+ clear = mockClear;
45
+ }
46
+ };
47
+ });
48
+
49
+ const mockFit = jest.fn();
50
+ const mockProposeDimensions = jest.fn().mockImplementation(() => ({ rows: 1, cols: 1 }));
51
+
52
+ jest.mock(/* webpackChunkName: "@xterm" */ '@xterm/addon-fit', () => {
53
+ return {
54
+ FitAddon: class {
55
+ fit = mockFit;
56
+ proposeDimensions = mockProposeDimensions;
57
+ }
58
+ };
59
+ });
60
+
61
+ jest.mock(/* webpackChunkName: "@xterm" */ '@xterm/addon-web-links', () => {
62
+ return { WebLinksAddon: class {} };
63
+ }, { virtual: true });
64
+
65
+ jest.mock(/* webpackChunkName: "@xterm" */ '@xterm/addon-search', () => {
66
+ return {
67
+ SearchAddon: class {
68
+ findNext = jest.fn(); findPrevious = jest.fn();
69
+ }
70
+ };
71
+ }, { virtual: true });
72
+
73
+ jest.mock(/* webpackChunkName: "@xterm" */ '@xterm/addon-webgl', () => {
74
+ return { WebglAddon: class {} };
75
+ }, { virtual: true });
76
+
19
77
  describe('component: ContainerShell', () => {
20
78
  const action = jest.fn();
21
79
  const translate = jest.fn();
22
80
  const schemaFor = jest.fn();
23
- const onData = jest.fn();
24
- const loadAddon = jest.fn();
25
- const open = jest.fn();
26
- const focus = jest.fn();
27
- const fit = jest.fn();
28
- const proposeDimensions = jest.fn().mockImplementation(() => {
29
- return { rows: 1 };
30
- });
31
- const write = jest.fn();
32
- const writeln = jest.fn();
33
- const reset = jest.fn();
34
-
35
- jest.mock(/* webpackChunkName: "xterm" */ 'xterm', () => {
36
- return {
37
- Terminal: class {
38
- onData = onData;
39
- loadAddon = loadAddon;
40
- open = open;
41
- focus = focus;
42
- write = write;
43
- writeln = writeln;
44
- reset = reset
45
- }
46
- };
47
- });
48
- jest.mock(/* webpackChunkName: "xterm" */ 'xterm-addon-fit', () => {
49
- return {
50
- FitAddon: class {
51
- fit = fit
52
- proposeDimensions = proposeDimensions
53
- }
54
- };
55
- });
56
81
 
57
82
  const defaultContainerShellParams = {
58
83
  propsData: {
@@ -77,7 +102,8 @@ describe('component: ContainerShell', () => {
77
102
  dispatch: action,
78
103
  getters: {
79
104
  'i18n/t': translate,
80
- 'cluster/schemaFor': schemaFor
105
+ 'cluster/schemaFor': schemaFor,
106
+ 'prefs/theme': jest.fn().mockReturnValue('dark')
81
107
  }
82
108
  }
83
109
  },
@@ -87,6 +113,7 @@ describe('component: ContainerShell', () => {
87
113
  const resetMocks = () => {
88
114
  // Clear all instances and calls to constructor and all methods:
89
115
  jest.clearAllMocks();
116
+ jest.restoreAllMocks();
90
117
  defaultContainerShellParams.propsData.pod.os = 'linux';
91
118
  };
92
119
 
@@ -99,7 +126,14 @@ describe('component: ContainerShell', () => {
99
126
  return wrapper;
100
127
  };
101
128
 
102
- it.todo('test that we are calling the xterm terminal and fitAddon class method mocks correctly');
129
+ it('test that we are calling the xterm terminal and fitAddon class method mocks correctly', async() => {
130
+ resetMocks();
131
+ await wrapperPostMounted(defaultContainerShellParams);
132
+
133
+ expect(mockLoadAddon).toHaveBeenCalledWith(expect.any(Object));
134
+ expect(mockOpen).toHaveBeenCalledWith(expect.any(HTMLElement));
135
+ expect(mockFit).toHaveBeenCalledWith();
136
+ });
103
137
 
104
138
  it('creates a window on the page', async() => {
105
139
  resetMocks();
@@ -110,6 +144,26 @@ describe('component: ContainerShell', () => {
110
144
  expect(windowComponent.isVisible()).toBe(true);
111
145
  });
112
146
 
147
+ it('does not load webgl addon on Safari browser', async() => {
148
+ resetMocks();
149
+
150
+ const originalUserAgent = window.navigator.userAgent;
151
+
152
+ Object.defineProperty(window.navigator, 'userAgent', {
153
+ value: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.1 Safari/605.1.15',
154
+ configurable: true
155
+ });
156
+
157
+ const wrapper = await wrapperPostMounted(defaultContainerShellParams);
158
+
159
+ expect(wrapper.vm.webglAddon).toBeNull();
160
+
161
+ Object.defineProperty(window.navigator, 'userAgent', {
162
+ value: originalUserAgent,
163
+ configurable: true
164
+ });
165
+ });
166
+
113
167
  it('the find action for the node is called if schemaFor finds a schema for NODE', async() => {
114
168
  resetMocks();
115
169
  const testSchemaFindsSchemaParams = {
@@ -242,7 +296,23 @@ describe('component: ContainerShell', () => {
242
296
  expect(wrapper.vm.os).toBe('linux');
243
297
  });
244
298
 
245
- it.todo('test that fit and flush are operating properly');
299
+ it('test that fit and flush are operating properly', async() => {
300
+ resetMocks();
301
+ const wrapper = await wrapperPostMounted(defaultContainerShellParams);
302
+
303
+ mockFit.mockClear();
304
+
305
+ if (typeof wrapper.vm.fit === 'function') {
306
+ wrapper.vm.fit();
307
+ }
308
+
309
+ expect(mockFit).toHaveBeenCalledWith();
310
+
311
+ if (typeof wrapper.vm.flush === 'function') {
312
+ wrapper.vm.flush();
313
+ }
314
+ });
315
+
246
316
  it.todo('test that we are properly feeding the terminal the commandOnFirstConnect prop correctly on connected');
247
317
 
248
318
  it('the socket message event sets data props correctly', async() => {
@@ -109,6 +109,11 @@ export default {
109
109
  default: null
110
110
  },
111
111
 
112
+ showStepHeader: {
113
+ type: Boolean,
114
+ default: true
115
+ },
116
+
112
117
  // The set of labels to display for the finish AsyncButton
113
118
  finishMode: {
114
119
  type: String,
@@ -289,7 +294,7 @@ export default {
289
294
  >
290
295
  <div>
291
296
  <div class="header">
292
- <div class="title">
297
+ <div :class="['title', !showStepHeader ? 'mmb-4' : '']">
293
298
  <div
294
299
  v-if="showBanner"
295
300
  class="top choice-banner"
@@ -328,7 +333,7 @@ export default {
328
333
  </slot>
329
334
  <!-- Step number with subtext -->
330
335
  <div
331
- v-if="activeStep && showSteps"
336
+ v-if="activeStep && showSteps && showStepHeader"
332
337
  class="subtitle"
333
338
  >
334
339
  <h2>{{ !!headerMode ? t(`wizard.${headerMode}`) : t(`asyncButton.${finishMode}.action`) }}: {{ t('wizard.step', {number:activeStepIndex+1}) }}</h2>
@@ -596,10 +601,10 @@ $spacer: 10px;
596
601
  flex-basis: 100%;
597
602
  border-top: 1px solid var(--border);
598
603
  position: relative;
599
- top: 17px;
604
+ top: 23px;
600
605
 
601
606
  .cru__content & {
602
- top: 13px;
607
+ top: 17px;
603
608
  }
604
609
  }
605
610
  }
@@ -0,0 +1,401 @@
1
+ <script setup lang="ts">
2
+ import { computed, ref } from 'vue';
3
+ import { useStore } from 'vuex';
4
+ import { useI18n } from '@shell/composables/useI18n';
5
+ import { ZERO_TIME, CATALOG_SORT_OPTIONS } from '@shell/config/types';
6
+ import { RcItemCard } from '@components/RcItemCard';
7
+ import AppChartCardSubHeader from '@shell/pages/c/_cluster/apps/charts/AppChartCardSubHeader';
8
+ import AppCoEmptyState from '@shell/components/fleet/AppCoEmptyState.vue';
9
+ import Loading from '@shell/components/Loading';
10
+ import Select from '@shell/components/form/Select';
11
+ import { RcIcon } from '@components/RcIcon';
12
+
13
+ interface ChartVersion {
14
+ version: string;
15
+ description?: string;
16
+ icon?: string;
17
+ appVersion?: string;
18
+ created?: string;
19
+ }
20
+
21
+ interface ChartEntry {
22
+ name: string;
23
+ description: string;
24
+ icon: string;
25
+ version: string;
26
+ created: string;
27
+ versions: ChartVersion[];
28
+ }
29
+
30
+ interface SubHeaderItem {
31
+ icon: string;
32
+ iconTooltip: { key: string };
33
+ label: string;
34
+ }
35
+
36
+ const props = withDefaults(defineProps<{
37
+ charts: Record<string, ChartVersion[]>;
38
+ loading?: boolean;
39
+ searchPlaceholder?: string;
40
+ }>(), {
41
+ charts: () => ({}),
42
+ loading: false,
43
+ searchPlaceholder: '',
44
+ });
45
+
46
+ const emit = defineEmits<{
47
+ 'select-chart': [value: unknown];
48
+ }>();
49
+
50
+ const store = useStore();
51
+ const { t } = useI18n(store);
52
+
53
+ const searchQuery = ref('');
54
+ const searchInput = ref<HTMLInputElement | null>(null);
55
+ const selectedSortOption = ref(CATALOG_SORT_OPTIONS.ALPHABETICAL_ASC);
56
+
57
+ const sortOptions = [
58
+ { kind: 'group', label: t('catalog.charts.sort.prefix') },
59
+ { value: CATALOG_SORT_OPTIONS.LAST_UPDATED_DESC, label: t('catalog.charts.sort.lastUpdatedDesc') },
60
+ { value: CATALOG_SORT_OPTIONS.ALPHABETICAL_ASC, label: t('catalog.charts.sort.alphaAscending') },
61
+ { value: CATALOG_SORT_OPTIONS.ALPHABETICAL_DESC, label: t('catalog.charts.sort.alphaDescending') },
62
+ ];
63
+
64
+ const placeholderText = computed(() => props.searchPlaceholder || t('catalog.charts.search'));
65
+
66
+ const allCharts = computed<ChartEntry[]>(() => {
67
+ const entries = props.charts as Record<string, any[]>;
68
+
69
+ if (!entries || !Object.keys(entries).length) {
70
+ return [];
71
+ }
72
+
73
+ return Object.keys(entries).sort().map((name) => {
74
+ const versions = entries[name] || [];
75
+ const latest = versions[0] || {};
76
+
77
+ return {
78
+ name,
79
+ description: latest.description || '',
80
+ icon: latest.icon || '',
81
+ version: latest.version || '',
82
+ created: latest.created || '',
83
+ versions,
84
+ };
85
+ });
86
+ });
87
+
88
+ const filteredCharts = computed(() => {
89
+ if (!searchQuery.value) {
90
+ return allCharts.value;
91
+ }
92
+
93
+ const raw = searchQuery.value;
94
+ const exactMatch = /^"(.+)"$/.exec(raw);
95
+ const q = exactMatch ? exactMatch[1].toLowerCase() : raw.toLowerCase();
96
+
97
+ return allCharts.value.filter((chart) => {
98
+ const name = chart.name.toLowerCase();
99
+ const desc = chart.description.toLowerCase();
100
+
101
+ if (exactMatch) {
102
+ return name === q || desc === q;
103
+ }
104
+
105
+ return name.includes(q) || desc.includes(q);
106
+ });
107
+ });
108
+
109
+ const sortedCharts = computed(() => {
110
+ const charts = [...filteredCharts.value];
111
+
112
+ switch (selectedSortOption.value) {
113
+ case CATALOG_SORT_OPTIONS.ALPHABETICAL_ASC:
114
+ return charts.sort((a, b) => a.name.localeCompare(b.name));
115
+ case CATALOG_SORT_OPTIONS.ALPHABETICAL_DESC:
116
+ return charts.sort((a, b) => b.name.localeCompare(a.name));
117
+ case CATALOG_SORT_OPTIONS.LAST_UPDATED_DESC:
118
+ return charts.sort((a, b) => {
119
+ const dateA = a.created ? new Date(a.created).getTime() : 0;
120
+ const dateB = b.created ? new Date(b.created).getTime() : 0;
121
+
122
+ return dateB - dateA;
123
+ });
124
+ default:
125
+ return charts;
126
+ }
127
+ });
128
+
129
+ const formatDate = (dateString: string): string => {
130
+ if (!dateString || dateString === ZERO_TIME) {
131
+ return '';
132
+ }
133
+
134
+ try {
135
+ const d = new Date(dateString);
136
+
137
+ return d.toLocaleDateString(undefined, {
138
+ year: 'numeric',
139
+ month: 'short',
140
+ day: 'numeric'
141
+ });
142
+ } catch (e) {
143
+ return dateString;
144
+ }
145
+ };
146
+
147
+ const chartCards = computed(() => {
148
+ return sortedCharts.value.map((chart) => {
149
+ const dateStr = chart.created ? formatDate(chart.created) : '';
150
+ const subHeaderItems: SubHeaderItem[] = [];
151
+
152
+ if (chart.version) {
153
+ subHeaderItems.push({
154
+ icon: 'icon-version-alt',
155
+ iconTooltip: { key: 'tableHeaders.version' },
156
+ label: chart.version,
157
+ });
158
+ }
159
+
160
+ if (dateStr) {
161
+ subHeaderItems.push({
162
+ icon: 'icon-refresh-alt',
163
+ iconTooltip: { key: 'tableHeaders.lastUpdated' },
164
+ label: dateStr,
165
+ });
166
+ }
167
+
168
+ return {
169
+ id: chart.name,
170
+ header: { title: { text: chart.name } },
171
+ image: chart.icon ? { src: chart.icon, alt: { text: chart.name } } : undefined,
172
+ content: { text: chart.description },
173
+ subHeaderItems,
174
+ rawChart: chart,
175
+ };
176
+ });
177
+ });
178
+
179
+ const totalMessage = computed(() => {
180
+ const count = sortedCharts.value.length;
181
+
182
+ if (searchQuery.value) {
183
+ return t('catalog.charts.totalMatchedChartsMessage', { count });
184
+ }
185
+
186
+ return t('catalog.charts.totalChartsMessage', { count });
187
+ });
188
+
189
+ const hasCharts = computed(() => allCharts.value.length > 0);
190
+
191
+ const clearSearch = () => {
192
+ searchQuery.value = '';
193
+ searchInput.value?.focus();
194
+ };
195
+
196
+ const onKeydown = (e: KeyboardEvent) => {
197
+ const input = searchInput.value;
198
+
199
+ if (!input || e.target === input) {
200
+ return;
201
+ }
202
+
203
+ if (e.key.length === 1 && !e.ctrlKey && !e.metaKey && !e.altKey) {
204
+ input.focus();
205
+ input.scrollIntoView({ behavior: 'smooth', block: 'start' });
206
+ }
207
+ };
208
+ </script>
209
+
210
+ <template>
211
+ <div
212
+ class="chart-card-grid"
213
+ tabindex="-1"
214
+ @keydown="onKeydown"
215
+ >
216
+ <Loading
217
+ v-if="props.loading"
218
+ mode="relative"
219
+ />
220
+
221
+ <slot
222
+ v-else-if="!hasCharts"
223
+ name="empty-state"
224
+ />
225
+
226
+ <template v-else>
227
+ <div class="search-input">
228
+ <input
229
+ ref="searchInput"
230
+ v-model="searchQuery"
231
+ type="search"
232
+ class="input"
233
+ :placeholder="placeholderText"
234
+ :aria-label="placeholderText"
235
+ data-testid="chart-card-grid-search"
236
+ >
237
+ <RcIcon
238
+ v-if="!searchQuery"
239
+ type="search"
240
+ size="medium"
241
+ class="icon-search"
242
+ aria-hidden="true"
243
+ />
244
+ </div>
245
+
246
+ <div class="chart-grid-content">
247
+ <div class="total-and-sort">
248
+ <p
249
+ class="total-message"
250
+ data-testid="chart-card-grid-total"
251
+ >
252
+ {{ totalMessage }}
253
+ </p>
254
+ <Select
255
+ v-model:value="selectedSortOption"
256
+ :clearable="false"
257
+ :searchable="false"
258
+ :options="sortOptions"
259
+ placement="bottom"
260
+ class="charts-sort-select"
261
+ >
262
+ <template #selected-option="{ label }">
263
+ <span class="mmr-1">{{ t('catalog.charts.sort.prefix') }}:</span>{{ label }}
264
+ </template>
265
+
266
+ <template #option="{ label, kind }">
267
+ <span
268
+ v-if="kind === 'group'"
269
+ class="mml-2 mmr-2"
270
+ >
271
+ {{ label }}:
272
+ </span>
273
+ <span
274
+ v-else
275
+ class="mml-6"
276
+ >
277
+ {{ label }}
278
+ </span>
279
+ </template>
280
+ </Select>
281
+ </div>
282
+
283
+ <div
284
+ v-if="chartCards.length"
285
+ class="chart-cards"
286
+ data-testid="chart-card-grid-cards"
287
+ >
288
+ <rc-item-card
289
+ v-for="card in chartCards"
290
+ :id="card.id"
291
+ :key="card.id"
292
+ :header="card.header"
293
+ :image="card.image"
294
+ :content="card.content"
295
+ :value="card.rawChart"
296
+ variant="medium"
297
+ :clickable="true"
298
+ :class="{ 'single-card': chartCards.length === 1 }"
299
+ data-testid="chart-card-grid-card"
300
+ @card-click="emit('select-chart', $event)"
301
+ >
302
+ <template #item-card-sub-header>
303
+ <AppChartCardSubHeader :items="card.subHeaderItems" />
304
+ </template>
305
+ <template
306
+ v-if="$slots['card-footer']"
307
+ #item-card-footer
308
+ >
309
+ <slot
310
+ name="card-footer"
311
+ :card="card"
312
+ />
313
+ </template>
314
+ </rc-item-card>
315
+ </div>
316
+
317
+ <AppCoEmptyState
318
+ v-else
319
+ :title="t('fleet.helmOp.add.steps.selection.emptyState.noMatch.title')"
320
+ data-testid="chart-card-grid-no-match"
321
+ >
322
+ {{ t('fleet.helmOp.add.steps.selection.emptyState.noMatch.descriptionPre') }}
323
+ <a
324
+ href="#"
325
+ @click.prevent="clearSearch"
326
+ >{{ t('fleet.helmOp.add.steps.selection.emptyState.noMatch.clearSearch') }}</a>
327
+ {{ t('fleet.helmOp.add.steps.selection.emptyState.noMatch.descriptionPost') }}
328
+ </AppCoEmptyState>
329
+
330
+ <slot name="after-cards" />
331
+ </div>
332
+ </template>
333
+ </div>
334
+ </template>
335
+
336
+ <style lang="scss" scoped>
337
+ .chart-card-grid {
338
+ display: flex;
339
+ flex-direction: column;
340
+ gap: var(--gap-lg);
341
+ outline: none;
342
+ }
343
+
344
+ .search-input {
345
+ position: relative;
346
+
347
+ input {
348
+ width: 100%;
349
+ height: 48px;
350
+ padding-left: 16px;
351
+ padding-right: 40px;
352
+ }
353
+
354
+ .icon-search {
355
+ position: absolute;
356
+ top: 16px;
357
+ right: 16px;
358
+ }
359
+ }
360
+
361
+ .chart-grid-content {
362
+ display: flex;
363
+ flex-direction: column;
364
+ gap: var(--gap-md);
365
+ }
366
+ .total-and-sort {
367
+ display: flex;
368
+ align-items: center;
369
+ justify-content: space-between;
370
+ flex-wrap: wrap;
371
+
372
+ .total-message {
373
+ font-size: 16px;
374
+ font-weight: 600;
375
+ color: var(--body-text);
376
+ }
377
+
378
+ .charts-sort-select {
379
+ width: 300px;
380
+
381
+ :deep(.v-select.inline.vs--single.vs--open .vs__selected) {
382
+ opacity: 1;
383
+ color: var(--dropdown-disabled-text);
384
+ }
385
+ }
386
+ }
387
+
388
+ .chart-cards {
389
+ display: grid;
390
+ grid-template-columns: repeat(auto-fill, minmax(400px, 1fr));
391
+ gap: var(--gap-md);
392
+ width: 100%;
393
+ height: max-content;
394
+ overflow: hidden;
395
+
396
+ .single-card {
397
+ max-width: 500px;
398
+ }
399
+ }
400
+
401
+ </style>