@rancher/shell 3.0.12-rc.3 → 3.0.12-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 (315) hide show
  1. package/assets/styles/global/_button.scss +1 -1
  2. package/assets/styles/global/_layout.scss +4 -0
  3. package/assets/translations/en-us.yaml +183 -51
  4. package/assets/translations/zh-hans.yaml +1 -7
  5. package/chart/monitoring/ClusterSelector.vue +0 -21
  6. package/chart/monitoring/prometheus/index.vue +6 -3
  7. package/components/ActionDropdownShell.vue +5 -3
  8. package/components/ButtonGroup.vue +26 -1
  9. package/components/CruResource.vue +212 -16
  10. package/components/ExplorerMembers.vue +8 -4
  11. package/components/ExplorerProjectsNamespaces.vue +10 -6
  12. package/components/GrowlManager.vue +4 -0
  13. package/components/MgmtNodeList.vue +184 -0
  14. package/components/PromptRestore.vue +93 -32
  15. package/components/Questions/index.vue +1 -0
  16. package/components/Resource/Detail/Card/StateCard/__tests__/composables.test.ts +90 -1
  17. package/components/Resource/Detail/Card/StateCard/composables.ts +57 -87
  18. package/components/Resource/Detail/Card/StatusCard/__tests__/StatusCard.test.ts +61 -0
  19. package/components/Resource/Detail/Card/StatusCard/index.vue +61 -15
  20. package/components/Resource/Detail/Metadata/IdentifyingInformation/index.vue +2 -0
  21. package/components/Resource/Detail/Metadata/KeyValue.vue +5 -2
  22. package/components/Resource/Detail/Metadata/KeyValueRow.vue +2 -6
  23. package/components/ResourceDetail/index.vue +1 -1
  24. package/components/ResourceList/Masthead.vue +7 -1
  25. package/components/ResourceList/index.vue +82 -1
  26. package/components/ResourceTable.vue +1 -0
  27. package/components/RichTranslation.vue +5 -2
  28. package/components/Setting.vue +1 -0
  29. package/components/SortableTable/index.vue +4 -3
  30. package/components/SubtleLink.vue +31 -6
  31. package/components/Tabbed/Tab.vue +29 -3
  32. package/components/Tabbed/index.vue +25 -3
  33. package/components/TableOfContents/TableOfContents.vue +109 -0
  34. package/components/TableOfContents/composables.ts +258 -0
  35. package/components/Window/ContainerShell.vue +21 -11
  36. package/components/Window/__tests__/ContainerShell.test.ts +107 -37
  37. package/components/Wizard.vue +23 -5
  38. package/components/__tests__/ButtonGroup.test.ts +56 -0
  39. package/components/__tests__/PromptRestore.test.ts +169 -19
  40. package/components/fleet/AppCoChartGrid.vue +401 -0
  41. package/components/fleet/AppCoEmptyState.vue +127 -0
  42. package/components/fleet/AppCoPageHeader.vue +119 -0
  43. package/components/fleet/AppCoVersionSelect.vue +70 -0
  44. package/components/fleet/FleetClusterTargets/ClusterSelectionFields.vue +217 -0
  45. package/components/fleet/FleetClusterTargets/TargetsList.vue +123 -35
  46. package/components/fleet/FleetClusterTargets/index.vue +189 -146
  47. package/components/fleet/FleetIntro.vue +7 -3
  48. package/components/fleet/FleetNoWorkspaces.vue +7 -3
  49. package/components/fleet/FleetSecretSelector.vue +5 -3
  50. package/components/fleet/FleetValuesFrom.vue +8 -2
  51. package/components/fleet/GitRepoAdvancedTab.vue +1 -0
  52. package/components/fleet/GitRepoMetadataTab.vue +5 -0
  53. package/components/fleet/GitRepoTargetTab.vue +0 -2
  54. package/components/fleet/HelmOpAdvancedTab.vue +19 -53
  55. package/components/fleet/HelmOpAppCoConfigTab.vue +597 -0
  56. package/components/fleet/HelmOpAppCoResourcesSection.vue +162 -0
  57. package/components/fleet/HelmOpMetadataTab.vue +5 -0
  58. package/components/fleet/HelmOpResourcesSection.vue +82 -0
  59. package/components/fleet/HelmOpTargetOptionsSection.vue +89 -0
  60. package/components/fleet/HelmOpTargetTab.vue +64 -60
  61. package/components/fleet/HelmOpValuesTab.vue +129 -105
  62. package/components/fleet/__tests__/AppCoEmptyState.test.ts +71 -0
  63. package/components/fleet/__tests__/AppCoVersionSelect.test.ts +36 -0
  64. package/components/fleet/__tests__/ClusterSelectionFields.test.ts +62 -0
  65. package/components/fleet/__tests__/FleetClusterTargets.test.ts +253 -0
  66. package/components/fleet/__tests__/FleetSecretSelector.test.ts +16 -0
  67. package/components/fleet/__tests__/FleetValuesFrom.test.ts +44 -0
  68. package/components/fleet/__tests__/HelmOpAppCoConfigTab.test.ts +59 -0
  69. package/components/fleet/__tests__/HelmOpAppCoResourcesSection.test.ts +62 -0
  70. package/components/fleet/__tests__/HelmOpResourcesSection.test.ts +43 -0
  71. package/components/fleet/__tests__/HelmOpTargetOptionsSection.test.ts +34 -0
  72. package/components/fleet/__tests__/HelmOpValuesTab.test.ts +39 -0
  73. package/components/fleet/__tests__/__snapshots__/AppCoEmptyState.test.ts.snap +97 -0
  74. package/components/fleet/__tests__/__snapshots__/AppCoVersionSelect.test.ts.snap +30 -0
  75. package/components/fleet/__tests__/__snapshots__/ClusterSelectionFields.test.ts.snap +209 -0
  76. package/components/fleet/__tests__/__snapshots__/HelmOpTargetOptionsSection.test.ts.snap +140 -0
  77. package/components/fleet/dashboard/Empty.vue +8 -4
  78. package/components/fleet/dashboard/ResourceCard.vue +28 -0
  79. package/components/fleet/dashboard/ResourceDetails.vue +28 -0
  80. package/components/fleet/dashboard/__tests__/ResourceCard.test.ts +87 -0
  81. package/components/form/ArrayList.vue +61 -4
  82. package/components/form/FileSelector.vue +39 -1
  83. package/components/form/KeyValue.vue +23 -2
  84. package/components/form/LabeledSelect.vue +39 -1
  85. package/components/form/Labels.vue +22 -3
  86. package/components/form/NameNsDescription.vue +13 -5
  87. package/components/form/PrivateRegistry.constants.ts +7 -0
  88. package/components/form/PrivateRegistry.vue +253 -18
  89. package/components/form/ResourceTabs/index.vue +1 -0
  90. package/components/form/SelectOrCreateAuthSecret.vue +140 -17
  91. package/components/form/__tests__/FileSelector.test.ts +23 -0
  92. package/components/form/__tests__/NameNsDescription.test.ts +75 -0
  93. package/components/form/__tests__/PrivateRegistry.test.ts +463 -73
  94. package/components/form/__tests__/SelectOrCreateAuthSecret.test.ts +122 -0
  95. package/components/formatter/EtcdSnapshotName.vue +73 -0
  96. package/components/formatter/InternalExternalIP.vue +10 -4
  97. package/components/formatter/ServiceTargets.vue +26 -7
  98. package/components/formatter/__tests__/InternalExternalIP.test.ts +132 -0
  99. package/components/formatter/__tests__/ServiceTargets.test.ts +412 -0
  100. package/components/nav/Header.vue +12 -1
  101. package/components/nav/TopLevelMenu.vue +7 -2
  102. package/components/nav/__tests__/Header.test.ts +15 -0
  103. package/components/nav/__tests__/TopLevelMenu.test.ts +120 -2
  104. package/components/templates/default.vue +16 -4
  105. package/components/templates/home.vue +9 -4
  106. package/components/templates/plain.vue +9 -4
  107. package/composables/useHelmOpResources.test.ts +56 -0
  108. package/composables/useHelmOpResources.ts +32 -0
  109. package/composables/useStateColor.test.ts +325 -0
  110. package/composables/useStateColor.ts +128 -0
  111. package/config/features.js +1 -0
  112. package/config/home-links.js +1 -1
  113. package/config/labels-annotations.js +3 -0
  114. package/config/product/explorer.js +17 -4
  115. package/config/product/manager.js +8 -0
  116. package/config/router/index.js +16 -0
  117. package/config/router/navigation-guards/__tests__/authentication.test.ts +130 -0
  118. package/config/router/navigation-guards/authentication.js +10 -4
  119. package/config/router/routes.js +20 -6
  120. package/config/secret.ts +10 -0
  121. package/config/settings.ts +6 -4
  122. package/config/table-headers.js +3 -4
  123. package/config/types.js +16 -0
  124. package/core/plugin-products-base.ts +3 -3
  125. package/core/plugin-types.ts +83 -30
  126. package/core/plugin.ts +3 -0
  127. package/core/types-provisioning.ts +34 -1
  128. package/core/types.ts +15 -2
  129. package/detail/__tests__/provisioning.cattle.io.cluster.test.ts +114 -0
  130. package/detail/__tests__/workload.test.ts +3 -152
  131. package/detail/catalog.cattle.io.clusterrepo.vue +1 -1
  132. package/detail/provisioning.cattle.io.cluster.vue +109 -7
  133. package/detail/workload/index.vue +12 -55
  134. package/dialog/RotateEncryptionKeyDialog.vue +33 -9
  135. package/dialog/__tests__/RotateEncryptionKeyDialog.test.ts +78 -0
  136. package/edit/__tests__/catalog.cattle.io.clusterrepo.test.ts +248 -0
  137. package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +92 -0
  138. package/edit/__tests__/fleet.cattle.io.helmop.test.ts +206 -0
  139. package/edit/__tests__/management.cattle.io.setting.test.ts +2 -1
  140. package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/General.test.ts.snap +6 -0
  141. package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/index.test.ts.snap +1 -0
  142. package/edit/auth/__tests__/azuread.test.ts +34 -9
  143. package/edit/auth/__tests__/github.test.ts +234 -0
  144. package/edit/auth/__tests__/oidc.test.ts +26 -6
  145. package/edit/auth/__tests__/saml.test.ts +196 -0
  146. package/edit/auth/azuread.vue +128 -95
  147. package/edit/auth/github.vue +72 -13
  148. package/edit/auth/ldap/__tests__/index.test.ts +206 -0
  149. package/edit/auth/ldap/config.vue +8 -0
  150. package/edit/auth/ldap/index.vue +75 -1
  151. package/edit/auth/oidc.vue +119 -73
  152. package/edit/auth/saml.vue +76 -12
  153. package/edit/catalog.cattle.io.clusterrepo.vue +140 -32
  154. package/edit/compliance.cattle.io.clusterscanprofile.vue +39 -41
  155. package/edit/fleet.cattle.io.gitrepo.vue +70 -16
  156. package/edit/fleet.cattle.io.helmop.vue +542 -141
  157. package/edit/helm.cattle.io.projecthelmchart.vue +1 -0
  158. package/edit/{management.cattle.io.setting.vue → management.cattle.io.setting/index.vue} +32 -9
  159. package/edit/management.cattle.io.setting/system-default-registry-pull-secrets.vue +81 -0
  160. package/edit/management.cattle.io.user.vue +5 -2
  161. package/edit/provisioning.cattle.io.cluster/SelectCredential.vue +3 -12
  162. package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +18 -0
  163. package/edit/provisioning.cattle.io.cluster/rke2.vue +89 -11
  164. package/edit/provisioning.cattle.io.cluster/tabs/MachinePool.vue +11 -0
  165. package/edit/provisioning.cattle.io.cluster/tabs/etcd/index.vue +0 -1
  166. package/edit/provisioning.cattle.io.cluster/tabs/registries/index.vue +14 -55
  167. package/list/group.principal.vue +5 -4
  168. package/list/harvesterhci.io.management.cluster.vue +8 -9
  169. package/list/management.cattle.io.user.vue +12 -9
  170. package/list/provisioning.cattle.io.cluster.vue +16 -10
  171. package/mixins/__tests__/auth-config.test.ts +90 -0
  172. package/mixins/__tests__/chart.test.ts +94 -0
  173. package/mixins/__tests__/resource-fetch-api-pagination.test.ts +48 -0
  174. package/mixins/auth-config.js +7 -0
  175. package/mixins/chart.js +11 -2
  176. package/mixins/child-hook.js +12 -6
  177. package/mixins/create-edit-view/impl.js +5 -3
  178. package/mixins/resource-fetch-api-pagination.js +21 -1
  179. package/models/__tests__/catalog.cattle.io.clusterrepo.test.ts +57 -0
  180. package/models/__tests__/compliance.cattle.io.clusterscan.test.ts +144 -0
  181. package/models/__tests__/fleet-application.test.ts +175 -0
  182. package/models/__tests__/fleet.cattle.io.bundle.test.ts +169 -0
  183. package/models/__tests__/fleet.cattle.io.helmop.test.ts +84 -0
  184. package/models/__tests__/management.cattle.io.node.ts +22 -0
  185. package/models/__tests__/namespace.test.ts +36 -0
  186. package/models/__tests__/provisioning.cattle.io.cluster.test.ts +205 -0
  187. package/models/__tests__/secret.test.ts +68 -1
  188. package/models/__tests__/workload.test.ts +401 -26
  189. package/models/catalog.cattle.io.clusterrepo.js +28 -4
  190. package/models/compliance.cattle.io.clusterscan.js +39 -4
  191. package/models/fleet-application.js +4 -0
  192. package/models/fleet.cattle.io.helmop.js +20 -1
  193. package/models/management.cattle.io.cluster.js +39 -5
  194. package/models/management.cattle.io.node.js +44 -3
  195. package/models/namespace.js +1 -1
  196. package/models/pod.js +46 -3
  197. package/models/provisioning.cattle.io.cluster.js +64 -14
  198. package/models/rke.cattle.io.etcdsnapshot.js +17 -9
  199. package/models/secret.js +19 -0
  200. package/models/workload.js +120 -20
  201. package/models/workload.service.js +5 -0
  202. package/package.json +14 -13
  203. package/pages/about.vue +5 -6
  204. package/pages/auth/login.vue +0 -35
  205. package/pages/auth/setup.vue +11 -0
  206. package/pages/c/_cluster/apps/charts/AppChartCardFooter.vue +2 -2
  207. package/pages/c/_cluster/apps/charts/AppChartCardSubHeader.vue +10 -1
  208. package/pages/c/_cluster/apps/charts/__tests__/index.test.ts +93 -0
  209. package/pages/c/_cluster/apps/charts/__tests__/install.test.ts +485 -107
  210. package/pages/c/_cluster/apps/charts/chart.vue +2 -1
  211. package/pages/c/_cluster/apps/charts/index.vue +48 -10
  212. package/pages/c/_cluster/apps/charts/install.vue +236 -144
  213. package/pages/c/_cluster/auth/roles/index.vue +5 -4
  214. package/pages/c/_cluster/explorer/workload-dashboard/ByNamespaceSection.vue +31 -0
  215. package/pages/c/_cluster/explorer/workload-dashboard/ByStateSection.vue +138 -0
  216. package/pages/c/_cluster/explorer/workload-dashboard/ByTypeSection.vue +30 -0
  217. package/pages/c/_cluster/explorer/workload-dashboard/WorkloadCard.vue +155 -0
  218. package/pages/c/_cluster/explorer/workload-dashboard/WorkloadNamespaceCard.vue +142 -0
  219. package/pages/c/_cluster/explorer/workload-dashboard/WorkloadTypeCard.vue +159 -0
  220. package/pages/c/_cluster/explorer/workload-dashboard/__tests__/composable.test.ts +561 -0
  221. package/pages/c/_cluster/explorer/workload-dashboard/composable.ts +440 -0
  222. package/pages/c/_cluster/explorer/workload-dashboard/index.vue +187 -0
  223. package/pages/c/_cluster/explorer/workload-dashboard/types.ts +80 -0
  224. package/pages/c/_cluster/fleet/application/create.vue +187 -136
  225. package/pages/c/_cluster/fleet/application/index.vue +5 -3
  226. package/pages/c/_cluster/fleet/application/suse-app-collection/ChartDetailBody.vue +338 -0
  227. package/pages/c/_cluster/fleet/application/suse-app-collection/ChartDetailHeader.vue +121 -0
  228. package/pages/c/_cluster/fleet/application/suse-app-collection/chart.vue +369 -0
  229. package/pages/c/_cluster/fleet/application/suse-app-collection/charts.vue +248 -0
  230. package/pages/c/_cluster/fleet/application/suse-app-collection/credentials.vue +310 -0
  231. package/pages/c/_cluster/fleet/index.vue +2 -2
  232. package/pages/c/_cluster/uiplugins/__tests__/index.test.ts +96 -0
  233. package/pages/c/_cluster/uiplugins/index.vue +15 -0
  234. package/pages/fail-whale.vue +16 -11
  235. package/pages/home.vue +16 -46
  236. package/pkg/require-asset.lib.js +25 -0
  237. package/pkg/vue.config.js +7 -0
  238. package/plugins/clean-html.d.ts +9 -0
  239. package/plugins/dashboard-store/__tests__/resource-class.test.ts +177 -0
  240. package/plugins/dashboard-store/getters.js +0 -1
  241. package/plugins/dashboard-store/resource-class.js +114 -19
  242. package/plugins/steve/__tests__/actions.test.ts +212 -0
  243. package/plugins/steve/actions.js +96 -0
  244. package/plugins/steve/steve-pagination-utils.ts +1 -1
  245. package/rancher-components/Accordion/Accordion.vue +53 -9
  246. package/rancher-components/Form/Checkbox/Checkbox.vue +14 -0
  247. package/rancher-components/Form/Radio/RadioButton.vue +17 -1
  248. package/rancher-components/Form/Radio/RadioGroup.vue +10 -0
  249. package/rancher-components/Form/TextArea/TextAreaAutoGrow.vue +30 -0
  250. package/rancher-components/Form/TextArea/__tests__/TextAreaAutoGrow.test.ts +95 -0
  251. package/rancher-components/Pill/RcTag/RcTag.vue +3 -2
  252. package/rancher-components/RcButton/RcButton.test.ts +103 -0
  253. package/rancher-components/RcButton/RcButton.vue +94 -15
  254. package/rancher-components/RcButton/index.ts +1 -1
  255. package/rancher-components/RcButton/types.ts +3 -0
  256. package/rancher-components/RcDropdown/RcDropdownTrigger.vue +6 -1
  257. package/rancher-components/RcItemCard/RcItemCard.test.ts +18 -0
  258. package/rancher-components/RcItemCard/RcItemCard.vue +2 -2
  259. package/rancher-components/RcSection/RcSection.vue +28 -3
  260. package/scripts/extension/helm/package/Dockerfile +1 -1
  261. package/scripts/test-plugins-build.sh +2 -1
  262. package/store/__tests__/features.test.ts +131 -0
  263. package/store/__tests__/growl.test.ts +374 -0
  264. package/store/__tests__/modal.test.ts +131 -0
  265. package/store/__tests__/notifications.test.ts +434 -0
  266. package/store/__tests__/slideInPanel.test.ts +88 -0
  267. package/store/__tests__/type-map.utils.test.ts +433 -0
  268. package/store/catalog.js +57 -0
  269. package/store/features.js +4 -0
  270. package/store/plugins.js +7 -4
  271. package/types/components/buttonGroup.ts +5 -0
  272. package/types/shell/index.d.ts +166 -70
  273. package/utils/__tests__/auth.test.ts +273 -0
  274. package/utils/__tests__/computed.test.ts +193 -0
  275. package/utils/__tests__/cspAdaptor.test.ts +163 -0
  276. package/utils/__tests__/dom.test.ts +81 -0
  277. package/utils/__tests__/duration.test.ts +37 -1
  278. package/utils/__tests__/dynamic-importer.test.ts +102 -0
  279. package/utils/__tests__/fleet-appco.test.ts +312 -0
  280. package/utils/__tests__/monitoring.test.ts +130 -0
  281. package/utils/__tests__/object.test.ts +22 -0
  282. package/utils/__tests__/operation-cr.test.ts +34 -0
  283. package/utils/__tests__/platform.test.ts +91 -0
  284. package/utils/__tests__/position.test.ts +237 -0
  285. package/utils/__tests__/provider.test.ts +51 -1
  286. package/utils/__tests__/queue.test.ts +232 -0
  287. package/utils/__tests__/release-notes.test.ts +221 -0
  288. package/utils/__tests__/router.test.js +254 -1
  289. package/utils/__tests__/select.test.ts +208 -0
  290. package/utils/__tests__/time.test.ts +265 -1
  291. package/utils/__tests__/title.test.ts +47 -0
  292. package/utils/__tests__/width.test.ts +53 -0
  293. package/utils/__tests__/window.test.ts +158 -0
  294. package/utils/__tests__/xccdf.test.ts +126 -6
  295. package/utils/crypto/__tests__/browserHashUtils.test.ts +98 -0
  296. package/utils/crypto/__tests__/index.test.ts +144 -0
  297. package/utils/duration.ts +104 -0
  298. package/utils/dynamic-content/__tests__/notification-handler.test.ts +196 -0
  299. package/utils/dynamic-content/info.ts +2 -1
  300. package/utils/error.js +13 -0
  301. package/utils/fleet-appco.ts +323 -0
  302. package/utils/object.js +22 -2
  303. package/utils/operation-cr.js +19 -0
  304. package/utils/provider.ts +12 -0
  305. package/utils/require-asset.ts +7 -0
  306. package/utils/validators/__tests__/container-images.test.ts +104 -0
  307. package/utils/validators/__tests__/flow-output.test.ts +91 -0
  308. package/utils/validators/__tests__/logging-outputs.test.ts +58 -0
  309. package/utils/validators/__tests__/monitoring-route.test.ts +119 -0
  310. package/utils/validators/__tests__/private-registry.test.ts +27 -15
  311. package/utils/validators/private-registry.ts +15 -4
  312. package/utils/xccdf.ts +39 -42
  313. package/vue.config.js +1 -1
  314. package/pages/support/index.vue +0 -264
  315. package/utils/duration.js +0 -43
@@ -0,0 +1,109 @@
1
+ <script setup>
2
+ const props = defineProps({
3
+ accordions: {
4
+ type: Array,
5
+ required: true,
6
+ }
7
+ });
8
+
9
+ const handleScrollTo = (entry, event) => {
10
+ entry?.scrollTo?.();
11
+ event?.currentTarget?.blur?.();
12
+ };
13
+ </script>
14
+
15
+ <template>
16
+ <div class="toc-root">
17
+ <div class="toc-container">
18
+ <h4>
19
+ {{ t('cruResource.tableOfContents.jumpTo') }}
20
+ </h4>
21
+ <ul>
22
+ <li
23
+ v-for="(acc, i) in props.accordions"
24
+ :key="i"
25
+ :data-testid="`toc-list-item-${i}`"
26
+ >
27
+ <button
28
+ v-if="acc.scrollTo"
29
+ type="button"
30
+ class="btn role-link accordion-link"
31
+ @click="handleScrollTo(acc, $event)"
32
+ >
33
+ {{ acc.label }}
34
+ </button>
35
+ <span v-else>{{ acc.label }}</span>
36
+ <template v-if="acc?.children?.length">
37
+ <ul data-testid="toc-list">
38
+ <li
39
+ v-for="(childAcc, j) in acc.children"
40
+ :key="j"
41
+ :data-testid="`toc-list-item-${i}-${j}`"
42
+ >
43
+ <button
44
+ v-if="childAcc.scrollTo"
45
+ type="button"
46
+ class="btn role-link accordion-link"
47
+ @click="handleScrollTo(childAcc, $event)"
48
+ >
49
+ {{ childAcc.label }}
50
+ </button>
51
+ <span v-else>{{ childAcc.label }}</span>
52
+ </li>
53
+ </ul>
54
+ </template>
55
+ </li>
56
+ </ul>
57
+ </div>
58
+ </div>
59
+ </template>
60
+
61
+ <style lang="scss" scoped>
62
+ ul {
63
+ list-style-type: none;
64
+ margin: 0;
65
+ padding: 0;
66
+ }
67
+
68
+ li:not(:last-child) {
69
+ margin-bottom: var(--gap);
70
+ }
71
+
72
+ h4 {
73
+ margin-bottom: 12px;
74
+ margin-top: 0px
75
+ }
76
+
77
+ li ul {
78
+ padding-left: var(--gap-md);
79
+ & li {
80
+ margin-top: var(--gap);
81
+ margin-bottom: 0px;
82
+ }
83
+ }
84
+
85
+ .toc-container {
86
+ padding: var(--gap-md);
87
+ border-radius: var(--border-radius);
88
+ background-color: var(--subtle-overlay-bg);
89
+ }
90
+
91
+ .accordion-link {
92
+ padding: 0px;
93
+ min-height: 0px;
94
+ line-height: 1.3em;
95
+ white-space: normal;
96
+ overflow-wrap: anywhere;
97
+ word-break: break-word;
98
+ text-align: left;
99
+ display: block;
100
+ width: 100%;
101
+ }
102
+
103
+ li > span {
104
+ white-space: normal;
105
+ overflow-wrap: anywhere;
106
+ word-break: break-word;
107
+ display: block;
108
+ }
109
+ </style>
@@ -0,0 +1,258 @@
1
+ import debounce from 'lodash/debounce';
2
+ import { randomStr } from '@shell/utils/string';
3
+ import {
4
+ computed, inject, onMounted, onUnmounted, provide, ref, watch
5
+ } from 'vue';
6
+ import type {
7
+ ComputedRef,
8
+ Ref,
9
+ VNode
10
+ } from 'vue';
11
+
12
+ type SummaryInfo = {
13
+ id: string;
14
+ label?: ComputedRef<string> | string;
15
+ scrollTo?: () => void;
16
+ };
17
+
18
+ type SummaryComponent = {
19
+ summary: SummaryInfo;
20
+ summaryID: string;
21
+ $options?: { name?: string };
22
+ $?: { type?: { name?: string } };
23
+ };
24
+
25
+ type SummaryEntry = {
26
+ node: VNode;
27
+ children: SummaryEntry[];
28
+ label?: string | ComputedRef<string>;
29
+ scrollTo?: () => void;
30
+ component?: SummaryComponent;
31
+ };
32
+
33
+ type RegisterComponent = (component?: SummaryComponent | null) => void;
34
+
35
+ type FormSummaryContext = {
36
+ registerComponent: RegisterComponent;
37
+ unRegisterComponent: RegisterComponent;
38
+ refreshComponents: () => void;
39
+ updateComponentLabel: (summaryID: string, label: string) => boolean;
40
+ };
41
+
42
+ type ElementWithVNodeChildren = {
43
+ children?: ArrayLike<Element>;
44
+ };
45
+
46
+ type ElementWithSummaryID = HTMLElement & {
47
+ summaryID?: string;
48
+ };
49
+
50
+ // Unique key used by provide/inject so each form subtree gets its own
51
+ // summary registration context
52
+ const FORM_SUMMARY_KEY = Symbol('formSummary');
53
+
54
+ /**
55
+ * useFormSummary will determine the relative position of all descendant components
56
+ * that are using the useInSummary composable. It is used to build summaries of elaborate form components
57
+ * that may have interactable elements deeply nested in child components. The list of located components
58
+ * returned by locateComponentsByNamePattern includes access to the component instance and a scrollTo method.
59
+ */
60
+ export function useFormSummary(rootComponentRef: Readonly<Ref<HTMLElement | null>>) {
61
+ const registeredComponents = ref<Record<string, SummaryComponent>>({});
62
+ const locatedComponents = ref<SummaryEntry[]>([]);
63
+ const buildTree = (
64
+ components: SummaryEntry[] = [],
65
+ node?: any,
66
+ found = new Set<string>()
67
+ ) => {
68
+ let nextInput = components;
69
+
70
+ const summaryID = node?.el ? (node?.el as ElementWithSummaryID | null | undefined)?.summaryID || '' : node?.summaryID || '';
71
+ const component = registeredComponents.value[summaryID];
72
+ const summary = component?.summary;
73
+
74
+ if (component && summary && registeredComponents.value[summary.id] && !found.has(summary.id)) {
75
+ found.add(summary.id);
76
+
77
+ const out: SummaryEntry = {
78
+ node: node as VNode,
79
+ children: [],
80
+ ...summary,
81
+ scrollTo: component ? () => scrollToComponent(component) : undefined
82
+ };
83
+
84
+ out.component = component;
85
+
86
+ components.push(out);
87
+ nextInput = out.children;
88
+ }
89
+
90
+ if (!node) {
91
+ return;
92
+ }
93
+
94
+ const children = node.el ? Array.from((node.el as ElementWithVNodeChildren | null | undefined)?.children ?? []) : Array.from(node.children ?? []);
95
+
96
+ children.forEach((child: any) => {
97
+ buildTree(nextInput, child, found);
98
+ });
99
+
100
+ return components;
101
+ };
102
+
103
+ const locateRegisteredComponents = () => {
104
+ if (rootComponentRef?.value) {
105
+ locatedComponents.value = buildTree([], rootComponentRef.value) || [];
106
+ } else {
107
+ locatedComponents.value = [];
108
+ }
109
+ };
110
+
111
+ // when forms initially this is called synchonously by every component using the summary composable
112
+ // debounce without a delay reduces that to one call on initial page load
113
+ const debouncedLocateRegisteredComponents = debounce(locateRegisteredComponents);
114
+
115
+ // onMounted fires on the calling component (CruResource) after its template refs are
116
+ // populated and after all children have run their own onMounted hooks. This guarantees
117
+ // rootComponentRef is available and all child registrations have arrived.
118
+ onMounted(() => {
119
+ debouncedLocateRegisteredComponents();
120
+ });
121
+
122
+ const findParent = (component: SummaryComponent) => {
123
+ const walk = (entries: SummaryEntry[] = [], parent: SummaryEntry | null = null): SummaryEntry | null => {
124
+ for (const entry of entries) {
125
+ if (entry?.component?.summary?.id === component?.summary?.id) {
126
+ return parent;
127
+ }
128
+
129
+ if (entry?.children?.length) {
130
+ const found = walk(entry.children, entry);
131
+
132
+ if (found) {
133
+ return found;
134
+ }
135
+ }
136
+ }
137
+
138
+ return null;
139
+ };
140
+
141
+ return walk(locatedComponents.value);
142
+ };
143
+
144
+ const updateComponentLabel = (summaryID: string, label: string) => {
145
+ const walk = (entries: SummaryEntry[] = []): boolean => {
146
+ for (const entry of entries) {
147
+ if (entry?.component?.summary?.id === summaryID) {
148
+ entry.label = label;
149
+
150
+ return true;
151
+ }
152
+
153
+ if (entry?.children?.length && walk(entry.children)) {
154
+ return true;
155
+ }
156
+ }
157
+
158
+ return false;
159
+ };
160
+
161
+ return walk(locatedComponents.value);
162
+ };
163
+
164
+ const scrollToComponent = (component: SummaryComponent) => {
165
+ const parent = findParent(component);
166
+
167
+ if (parent?.component) {
168
+ scrollToComponent(parent.component);
169
+ }
170
+ if (component?.summary?.scrollTo) {
171
+ component.summary.scrollTo();
172
+ }
173
+ };
174
+
175
+ const registerComponent: RegisterComponent = (component) => {
176
+ if (!component || !component.summary?.id) {
177
+ return;
178
+ }
179
+ registeredComponents.value[component.summary?.id] = component;
180
+ debouncedLocateRegisteredComponents();
181
+ };
182
+
183
+ const unRegisterComponent: RegisterComponent = (component) => {
184
+ if (!component || !component.summary?.id) {
185
+ return;
186
+ }
187
+
188
+ delete registeredComponents.value[component.summary?.id];
189
+ debouncedLocateRegisteredComponents();
190
+ };
191
+
192
+ // Provide the register/unregister functions to all descendants
193
+ provide<FormSummaryContext>(FORM_SUMMARY_KEY, {
194
+ registerComponent,
195
+ unRegisterComponent,
196
+ refreshComponents: debouncedLocateRegisteredComponents,
197
+ updateComponentLabel,
198
+ });
199
+
200
+ return { locatedComponents };
201
+ }
202
+
203
+ /**
204
+ * Hook to register a component in the summary system.
205
+ * Injects register/unregister from the nearest ancestor that called useFormSummary().
206
+ * When the component is mounted (including after v-if re-reveals it), it re-registers
207
+ * into the correct scoped context. Components using inFormSummary will register themselves
208
+ * with the nearest ancestor containing useFormSummary
209
+ *
210
+ * @param options.scrollTo - Scroll handler. The ToC system calls this function when the
211
+ * user navigates to this component via the Table of Contents. Use this to perform any
212
+ * additional work before scrolling (e.g. expanding an accordion, revealing a tab).
213
+ * @param options.label - Label for this component's ToC entry. Accepts a plain string or
214
+ * a `ComputedRef<string>`.
215
+ * @param options.elementRef - A template ref pointing to the component's root element.
216
+ * Used by the ToC system to locate this component during DOM tree traversal.
217
+ */
218
+ export function useInSummary(options: { scrollTo: () => void; label: ComputedRef<string> | string; elementRef: Readonly<Ref<HTMLElement | null>> }) {
219
+ const {
220
+ registerComponent = () => {},
221
+ unRegisterComponent = () => {},
222
+ refreshComponents = () => {},
223
+ updateComponentLabel = () => false
224
+ } = inject<FormSummaryContext>(FORM_SUMMARY_KEY) || {};
225
+
226
+ const { scrollTo, label, elementRef } = options;
227
+
228
+ const summaryID = randomStr();
229
+ const summary: SummaryInfo = { id: summaryID, scrollTo };
230
+
231
+ // Wrap a plain string in a computed so the type is always ComputedRef<string>.
232
+ summary.label = typeof label === 'string' ? computed(() => label) : label;
233
+
234
+ watch(summary.label, (label) => {
235
+ const updated = updateComponentLabel(summaryID, label);
236
+
237
+ if (!updated) {
238
+ refreshComponents();
239
+ }
240
+ });
241
+
242
+ onMounted(() => {
243
+ if (elementRef.value) {
244
+ (elementRef.value as ElementWithSummaryID).summaryID = summaryID;
245
+ }
246
+
247
+ registerComponent({ summary, summaryID } as SummaryComponent);
248
+ });
249
+
250
+ onUnmounted(() => {
251
+ // Unregister by summary ID — only the ID is needed for deletion
252
+ const stub = { summary } as SummaryComponent;
253
+
254
+ unRegisterComponent(stub);
255
+ });
256
+
257
+ return { summary };
258
+ }
@@ -80,7 +80,6 @@ export default {
80
80
  fitAddon: null,
81
81
  searchAddon: null,
82
82
  webglAddon: null,
83
- canvasAddon: null,
84
83
  isOpen: false,
85
84
  isOpening: false,
86
85
  backlog: [],
@@ -168,7 +167,7 @@ export default {
168
167
  try {
169
168
  const schema = this.$store.getters[`cluster/schemaFor`](NODE);
170
169
 
171
- if (schema) {
170
+ if (schema && nodeId) {
172
171
  await this.$store.dispatch('cluster/find', { type: NODE, id: nodeId });
173
172
  }
174
173
  } catch {}
@@ -215,14 +214,13 @@ export default {
215
214
 
216
215
  async setupTerminal() {
217
216
  const docStyle = getComputedStyle(document.querySelector('body'));
218
- const xterm = await import(/* webpackChunkName: "xterm" */ 'xterm');
217
+ const xterm = await import(/* webpackChunkName: "xterm" */ '@xterm/xterm');
219
218
 
220
219
  const addons = await allHash({
221
- fit: import(/* webpackChunkName: "xterm" */ 'xterm-addon-fit'),
222
- webgl: import(/* webpackChunkName: "xterm" */ 'xterm-addon-webgl'),
223
- weblinks: import(/* webpackChunkName: "xterm" */ 'xterm-addon-web-links'),
224
- search: import(/* webpackChunkName: "xterm" */ 'xterm-addon-search'),
225
- canvas: import(/* webpackChunkName: "xterm" */ 'xterm-addon-canvas')
220
+ fit: import(/* webpackChunkName: "xterm" */ '@xterm/addon-fit'),
221
+ webgl: import(/* webpackChunkName: "xterm" */ '@xterm/addon-webgl'),
222
+ weblinks: import(/* webpackChunkName: "xterm" */ '@xterm/addon-web-links'),
223
+ search: import(/* webpackChunkName: "xterm" */ '@xterm/addon-search'),
226
224
  });
227
225
 
228
226
  const terminal = new xterm.Terminal({
@@ -242,9 +240,21 @@ export default {
242
240
  terminal.loadAddon(new addons.weblinks.WebLinksAddon());
243
241
  terminal.open(this.$refs.xterm);
244
242
 
243
+ // if user is using Safari with webGPU disabled, webglAddon will silently fail
244
+ // and we do not have a way to detect that.
245
+ // To avoid it, default to DOM rendering for Safari browsers
245
246
  try {
246
- this.webglAddon = new addons.webgl.WebglAddon();
247
- terminal.loadAddon(this.webglAddon);
247
+ const ua = window.navigator.userAgent.toLowerCase();
248
+ const isSafari = ua.includes('safari') &&
249
+ !ua.includes('crios') && // Chrome iOS
250
+ !ua.includes('fxios') && // Firefox iOS
251
+ !ua.includes('edgios') && // Edge iOS
252
+ !ua.includes('opr'); // Opera
253
+
254
+ if (!isSafari) {
255
+ this.webglAddon = new addons.webgl.WebglAddon();
256
+ terminal.loadAddon(this.webglAddon);
257
+ }
248
258
  } catch (e) {
249
259
  // Some browsers (Safari) don't support the webgl renderer, so don't use it.
250
260
  this.webglAddon = null;
@@ -288,7 +298,7 @@ export default {
288
298
  }
289
299
 
290
300
  const url = addParams(
291
- `${ this.pod.links.view.replace(/^http/, 'ws') }/exec`,
301
+ `${ this.pod.links?.view.replace(/^http/, 'ws') }/exec`,
292
302
  {
293
303
  container: this.container,
294
304
  stdout: 1,
@@ -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,
@@ -119,6 +124,11 @@ export default {
119
124
  errors: {
120
125
  type: Array,
121
126
  default: null,
127
+ },
128
+
129
+ beforeGoToStep: {
130
+ type: Function,
131
+ default: null
122
132
  }
123
133
  },
124
134
 
@@ -212,7 +222,7 @@ export default {
212
222
  },
213
223
 
214
224
  methods: {
215
- goToStep(number, fromNav) {
225
+ async goToStep(number, fromNav) {
216
226
  if (number < 1) {
217
227
  return;
218
228
  }
@@ -228,6 +238,14 @@ export default {
228
238
  return;
229
239
  }
230
240
 
241
+ if (this.beforeGoToStep && fromNav) {
242
+ try {
243
+ await this.beforeGoToStep(this.activeStep, selected);
244
+ } catch {
245
+ return;
246
+ }
247
+ }
248
+
231
249
  this.activeStep = selected;
232
250
 
233
251
  this.$emit('next', { step: selected });
@@ -289,7 +307,7 @@ export default {
289
307
  >
290
308
  <div>
291
309
  <div class="header">
292
- <div class="title">
310
+ <div :class="['title', !showStepHeader ? 'mmb-4' : '']">
293
311
  <div
294
312
  v-if="showBanner"
295
313
  class="top choice-banner"
@@ -328,7 +346,7 @@ export default {
328
346
  </slot>
329
347
  <!-- Step number with subtext -->
330
348
  <div
331
- v-if="activeStep && showSteps"
349
+ v-if="activeStep && showSteps && showStepHeader"
332
350
  class="subtitle"
333
351
  >
334
352
  <h2>{{ !!headerMode ? t(`wizard.${headerMode}`) : t(`asyncButton.${finishMode}.action`) }}: {{ t('wizard.step', {number:activeStepIndex+1}) }}</h2>
@@ -596,10 +614,10 @@ $spacer: 10px;
596
614
  flex-basis: 100%;
597
615
  border-top: 1px solid var(--border);
598
616
  position: relative;
599
- top: 17px;
617
+ top: 23px;
600
618
 
601
619
  .cru__content & {
602
- top: 13px;
620
+ top: 17px;
603
621
  }
604
622
  }
605
623
  }