@rancher/shell 3.0.5-rc.7 → 3.0.5-rc.9

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 (301) hide show
  1. package/assets/brand/classic/metadata.json +3 -0
  2. package/assets/styles/app.scss +1 -0
  3. package/assets/styles/base/_color.scss +19 -0
  4. package/assets/styles/base/_helpers.scss +10 -0
  5. package/assets/styles/base/_variables.scss +1 -1
  6. package/assets/styles/fonts/_icons.scss +1 -32
  7. package/assets/styles/global/_layout.scss +1 -1
  8. package/assets/styles/global/_tooltip.scss +7 -4
  9. package/assets/styles/themes/_dark.scss +272 -259
  10. package/assets/styles/themes/_light.scss +551 -516
  11. package/assets/styles/themes/_modern.scss +936 -0
  12. package/assets/translations/en-us.yaml +219 -38
  13. package/assets/translations/zh-hans.yaml +0 -1
  14. package/chart/__tests__/S3.test.ts +2 -1
  15. package/chart/monitoring/grafana/index.vue +8 -2
  16. package/cloud-credential/generic.vue +18 -10
  17. package/cloud-credential/harvester.vue +1 -9
  18. package/components/ActionMenuShell.vue +3 -1
  19. package/components/AdvancedSection.vue +8 -0
  20. package/components/ChartReadme.vue +17 -7
  21. package/components/Cron/CronExpressionEditor.vue +299 -0
  22. package/components/Cron/CronExpressionEditorModal.vue +247 -0
  23. package/components/Cron/CronTooltip.vue +87 -0
  24. package/components/Cron/types.ts +13 -0
  25. package/components/Drawer/ResourceDetailDrawer/__tests__/composables.test.ts +1 -26
  26. package/components/Drawer/ResourceDetailDrawer/composables.ts +0 -23
  27. package/components/Drawer/ResourceDetailDrawer/index.vue +17 -4
  28. package/components/ForceDirectedTreeChart/composable.ts +11 -0
  29. package/components/InstallHelmCharts.vue +656 -0
  30. package/components/LazyImage.vue +60 -4
  31. package/components/LocaleSelector.vue +7 -2
  32. package/components/Markdown.vue +4 -0
  33. package/components/PromptModal.vue +1 -1
  34. package/components/Resource/Detail/Card/__tests__/StateCard.test.ts +1 -0
  35. package/components/Resource/Detail/CopyToClipboard.vue +78 -0
  36. package/components/Resource/Detail/FetchLoader/__tests__/composables.test.ts +69 -0
  37. package/components/Resource/Detail/FetchLoader/composables.ts +27 -0
  38. package/components/Resource/Detail/Masthead/composable.ts +16 -0
  39. package/components/Resource/Detail/Masthead/index.vue +37 -0
  40. package/components/Resource/Detail/Metadata/Annotations/__tests__/index.test.ts +1 -1
  41. package/components/Resource/Detail/Metadata/Annotations/index.vue +1 -1
  42. package/components/Resource/Detail/Metadata/IdentifyingInformation/__tests__/identifying-fields.test.ts +13 -61
  43. package/components/Resource/Detail/Metadata/IdentifyingInformation/__tests__/index.test.ts +33 -6
  44. package/components/Resource/Detail/Metadata/IdentifyingInformation/identifying-fields.ts +29 -43
  45. package/components/Resource/Detail/Metadata/IdentifyingInformation/index.vue +25 -5
  46. package/components/Resource/Detail/Metadata/KeyValue.vue +12 -23
  47. package/components/Resource/Detail/Metadata/KeyValueRow.vue +144 -0
  48. package/components/Resource/Detail/Metadata/Labels/__tests__/index.test.ts +1 -0
  49. package/components/Resource/Detail/Metadata/Labels/index.vue +1 -0
  50. package/components/Resource/Detail/Metadata/__tests__/KeyValue.test.ts +30 -32
  51. package/components/Resource/Detail/Metadata/__tests__/KeyValueRow.test.ts +108 -0
  52. package/components/Resource/Detail/Metadata/__tests__/composables.test.ts +10 -20
  53. package/components/Resource/Detail/Metadata/__tests__/index.test.ts +12 -5
  54. package/components/Resource/Detail/Metadata/composables.ts +9 -10
  55. package/components/Resource/Detail/Metadata/index.vue +18 -2
  56. package/components/Resource/Detail/Page.vue +35 -21
  57. package/components/Resource/Detail/Preview/Content.vue +63 -0
  58. package/components/Resource/Detail/Preview/Preview.vue +128 -0
  59. package/components/Resource/Detail/Preview/__tests__/Content.spec.ts +71 -0
  60. package/components/Resource/Detail/Preview/__tests__/Preview.spec.ts +121 -0
  61. package/components/Resource/Detail/ResourcePopover/ResourcePopoverCard.vue +141 -0
  62. package/components/Resource/Detail/ResourcePopover/__tests__/ResourcePopoverCard.test.ts +136 -0
  63. package/components/Resource/Detail/ResourcePopover/__tests__/index.test.ts +245 -0
  64. package/components/Resource/Detail/ResourcePopover/index.vue +226 -0
  65. package/components/Resource/Detail/SpacedRow.vue +1 -0
  66. package/components/Resource/Detail/TitleBar/__tests__/composables.test.ts +8 -14
  67. package/components/Resource/Detail/TitleBar/__tests__/index.test.ts +1 -1
  68. package/components/Resource/Detail/TitleBar/composables.ts +3 -6
  69. package/components/Resource/Detail/TitleBar/index.vue +11 -29
  70. package/components/Resource/Detail/ViewOptions/composable.ts +9 -0
  71. package/components/Resource/Detail/ViewOptions/index.vue +41 -0
  72. package/components/Resource/Detail/__tests__/CopyToClipboard.spec.ts +82 -0
  73. package/components/ResourceDetail/Masthead/legacy.vue +0 -19
  74. package/components/ResourceDetail/index.vue +544 -74
  75. package/components/ResourceTable.vue +24 -0
  76. package/components/SlideInPanelManager.vue +10 -3
  77. package/components/SortableTable/index.vue +11 -5
  78. package/components/SortableTable/paging.js +3 -0
  79. package/components/Tabbed/Tab.vue +43 -1
  80. package/components/Tabbed/index.vue +32 -4
  81. package/components/__tests__/Cron/CronExpressionEditor.test.ts +151 -0
  82. package/components/__tests__/Cron/CronExpressionEditorModal.test.ts +81 -0
  83. package/components/__tests__/LazyImage.spec.ts +121 -0
  84. package/components/auth/login/saml.vue +86 -0
  85. package/components/fleet/FleetStatus.vue +4 -0
  86. package/components/form/ClusterAppearance.vue +5 -0
  87. package/components/form/LabeledSelect.vue +8 -8
  88. package/components/form/Members/ClusterPermissionsEditor.vue +1 -1
  89. package/components/form/ProjectMemberEditor.vue +1 -1
  90. package/components/form/ResourceLabeledSelect.vue +19 -6
  91. package/components/form/ResourceTabs/composable.ts +54 -0
  92. package/components/form/ResourceTabs/index.vue +30 -7
  93. package/components/form/SecretSelector.vue +9 -0
  94. package/components/form/Select.vue +13 -10
  95. package/components/form/__tests__/LabeledSelect.test.ts +133 -0
  96. package/components/form/__tests__/Select.test.ts +134 -0
  97. package/components/form/labeled-select-utils/labeled-select-pagination.ts +3 -38
  98. package/components/formatter/FleetApplicationSource.vue +25 -17
  99. package/components/nav/Favorite.vue +4 -0
  100. package/components/nav/NotificationCenter/Notification.vue +1 -27
  101. package/components/nav/WindowManager/index.vue +3 -3
  102. package/composables/useExtensionManager.ts +17 -0
  103. package/config/home-links.js +12 -0
  104. package/config/labels-annotations.js +1 -3
  105. package/config/page-actions.js +0 -1
  106. package/config/product/explorer.js +3 -1
  107. package/config/product/fleet.js +2 -7
  108. package/config/product/manager.js +0 -5
  109. package/config/query-params.js +1 -0
  110. package/config/router/navigation-guards/clusters.js +2 -1
  111. package/config/router/navigation-guards/products.js +1 -1
  112. package/core/extension-manager-impl.js +518 -0
  113. package/core/plugins.js +35 -468
  114. package/core/types.ts +8 -2
  115. package/detail/__tests__/autoscaling.horizontalpodautoscaler.test.ts +1 -0
  116. package/detail/__tests__/provisioning.cattle.io.cluster.test.ts +11 -0
  117. package/detail/__tests__/workload.test.ts +164 -0
  118. package/detail/catalog.cattle.io.app.vue +7 -4
  119. package/detail/configmap.vue +33 -75
  120. package/detail/fleet.cattle.io.bundle.vue +1 -5
  121. package/detail/fleet.cattle.io.cluster.vue +3 -2
  122. package/detail/fleet.cattle.io.gitrepo.vue +76 -49
  123. package/detail/fleet.cattle.io.helmop.vue +78 -49
  124. package/detail/projectsecret.vue +11 -0
  125. package/detail/provisioning.cattle.io.cluster.vue +350 -324
  126. package/detail/secret.vue +49 -308
  127. package/detail/workload/index.vue +38 -21
  128. package/dialog/AddonConfigConfirmationDialog.vue +1 -1
  129. package/dialog/GenericPrompt.vue +1 -1
  130. package/dialog/ImportDialog.vue +9 -2
  131. package/dialog/InstallExtensionDialog.vue +26 -15
  132. package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +2 -1
  133. package/edit/__tests__/fleet.cattle.io.helmop.test.ts +224 -0
  134. package/edit/__tests__/resources.cattle.io.restore.test.ts +106 -0
  135. package/edit/cloudcredential.vue +31 -17
  136. package/edit/constraints.gatekeeper.sh.constraint/index.vue +10 -2
  137. package/edit/fleet.cattle.io.cluster.vue +19 -0
  138. package/edit/fleet.cattle.io.gitrepo.vue +28 -22
  139. package/edit/fleet.cattle.io.helmop.vue +78 -56
  140. package/edit/logging.banzaicloud.io.output/index.vue +1 -1
  141. package/edit/logging.banzaicloud.io.output/providers/awsElasticsearch.vue +5 -6
  142. package/edit/monitoring.coreos.com.alertmanagerconfig/index.vue +12 -11
  143. package/edit/monitoring.coreos.com.alertmanagerconfig/receiverConfig.vue +11 -1
  144. package/edit/networking.k8s.io.ingress/Certificate.vue +9 -11
  145. package/edit/networking.k8s.io.ingress/DefaultBackend.vue +8 -3
  146. package/edit/networking.k8s.io.ingress/Rule.vue +2 -5
  147. package/edit/networking.k8s.io.ingress/RulePath.vue +17 -11
  148. package/edit/networking.k8s.io.networkpolicy/PolicyRuleTarget.vue +11 -10
  149. package/edit/networking.k8s.io.networkpolicy/PolicyRules.vue +1 -3
  150. package/edit/networking.k8s.io.networkpolicy/index.vue +17 -17
  151. package/edit/provisioning.cattle.io.cluster/index.vue +14 -19
  152. package/edit/provisioning.cattle.io.cluster/rke2.vue +31 -15
  153. package/edit/provisioning.cattle.io.cluster/tabs/AgentConfiguration.vue +9 -7
  154. package/edit/provisioning.cattle.io.cluster/tabs/DirectoryConfig.vue +10 -12
  155. package/edit/provisioning.cattle.io.cluster/tabs/MachinePool.vue +39 -38
  156. package/edit/provisioning.cattle.io.cluster/tabs/etcd/S3Config.vue +41 -19
  157. package/edit/provisioning.cattle.io.cluster/tabs/etcd/index.vue +16 -3
  158. package/edit/provisioning.cattle.io.cluster/tabs/registries/RegistryConfigs.vue +30 -31
  159. package/edit/provisioning.cattle.io.cluster/tabs/registries/RegistryMirrors.vue +9 -10
  160. package/edit/provisioning.cattle.io.cluster/tabs/registries/index.vue +1 -3
  161. package/edit/provisioning.cattle.io.cluster/tabs/upgrade/DrainOptions.vue +16 -9
  162. package/edit/resources.cattle.io.restore.vue +5 -8
  163. package/edit/workload/index.vue +5 -14
  164. package/list/__tests__/workload.test.ts +1 -0
  165. package/list/provisioning.cattle.io.cluster.vue +1 -69
  166. package/list/workload.vue +8 -1
  167. package/machine-config/__tests__/vmwarevsphere.test.ts +5 -7
  168. package/machine-config/components/GCEImage.vue +6 -5
  169. package/machine-config/google.vue +20 -7
  170. package/machine-config/vmwarevsphere.vue +7 -17
  171. package/mixins/__tests__/chart.test.ts +139 -1
  172. package/mixins/chart.js +58 -20
  173. package/mixins/resource-fetch-api-pagination.js +3 -4
  174. package/models/__tests__/chart.test.ts +111 -80
  175. package/models/__tests__/fleet.cattle.io.helmop.test.ts +224 -0
  176. package/models/__tests__/namespace.test.ts +69 -0
  177. package/models/__tests__/node.test.ts +7 -63
  178. package/models/apps.statefulset.js +8 -10
  179. package/models/catalog.cattle.io.app.js +1 -1
  180. package/models/catalog.cattle.io.operation.js +1 -1
  181. package/models/chart.js +41 -21
  182. package/models/cloudcredential.js +2 -163
  183. package/models/cluster/node.js +7 -7
  184. package/models/cluster.x-k8s.io.machine.js +3 -3
  185. package/models/compliance.cattle.io.clusterscan.js +2 -2
  186. package/models/configmap.js +4 -0
  187. package/models/constraints.gatekeeper.sh.constraint.js +1 -1
  188. package/models/fleet-application.js +16 -63
  189. package/models/fleet.cattle.io.bundle.js +1 -38
  190. package/models/fleet.cattle.io.gitrepo.js +19 -1
  191. package/models/fleet.cattle.io.helmop.js +30 -22
  192. package/models/management.cattle.io.project.js +12 -0
  193. package/models/management.cattle.io.setting.js +4 -0
  194. package/models/namespace.js +30 -0
  195. package/models/persistentvolumeclaim.js +1 -1
  196. package/models/pod.js +2 -2
  197. package/models/provisioning.cattle.io.cluster.js +16 -40
  198. package/models/rke.cattle.io.etcdsnapshot.js +1 -1
  199. package/models/secret.js +4 -0
  200. package/models/storage.k8s.io.storageclass.js +2 -2
  201. package/models/workload.js +6 -3
  202. package/package.json +19 -18
  203. package/pages/c/_cluster/apps/charts/AppChartCardFooter.vue +26 -10
  204. package/pages/c/_cluster/apps/charts/AppChartCardSubHeader.vue +4 -1
  205. package/pages/c/_cluster/apps/charts/__tests__/AppChartCardFooter.spec.js +41 -0
  206. package/pages/c/_cluster/apps/charts/chart.vue +440 -183
  207. package/pages/c/_cluster/apps/charts/index.vue +1 -0
  208. package/pages/c/_cluster/apps/charts/install.vue +7 -6
  209. package/pages/c/_cluster/explorer/projectsecret.vue +3 -13
  210. package/pages/c/_cluster/explorer/tools/__tests__/index.test.ts +102 -12
  211. package/pages/c/_cluster/explorer/tools/index.vue +145 -254
  212. package/pages/c/_cluster/fleet/__tests__/index.test.ts +608 -314
  213. package/pages/c/_cluster/fleet/index.vue +103 -44
  214. package/pages/c/_cluster/manager/cloudCredential/index.vue +20 -60
  215. package/pages/c/_cluster/manager/drivers/kontainerDriver/index.vue +12 -2
  216. package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +11 -4
  217. package/pages/c/_cluster/uiplugins/__tests__/index.spec.ts +318 -0
  218. package/pages/c/_cluster/uiplugins/index.vue +256 -387
  219. package/pages/home.vue +1 -9
  220. package/plugins/dashboard-store/actions.js +42 -22
  221. package/plugins/dashboard-store/resource-class.js +80 -0
  222. package/plugins/steve/__tests__/getters.test.ts +1 -1
  223. package/plugins/steve/__tests__/subscribe.spec.ts +259 -1
  224. package/plugins/steve/getters.js +8 -2
  225. package/plugins/steve/resourceWatcher.js +10 -3
  226. package/plugins/steve/subscribe.js +192 -19
  227. package/plugins/steve/worker/web-worker.advanced.js +2 -0
  228. package/public/index.html +2 -1
  229. package/rancher-components/Card/Card.vue +1 -19
  230. package/rancher-components/Form/Checkbox/Checkbox.vue +1 -1
  231. package/rancher-components/Form/Radio/RadioButton.vue +1 -1
  232. package/rancher-components/Form/Radio/RadioGroup.vue +1 -1
  233. package/rancher-components/LabeledTooltip/LabeledTooltip.vue +1 -11
  234. package/rancher-components/Pill/RcCounterBadge/RcCounterBadge.test.ts +53 -0
  235. package/rancher-components/Pill/RcCounterBadge/RcCounterBadge.vue +65 -0
  236. package/rancher-components/Pill/RcCounterBadge/index.ts +1 -0
  237. package/rancher-components/Pill/RcCounterBadge/types.ts +7 -0
  238. package/rancher-components/Pill/RcStatusBadge/RcStatusBadge.test.ts +15 -0
  239. package/rancher-components/Pill/RcStatusBadge/RcStatusBadge.vue +65 -0
  240. package/rancher-components/Pill/RcStatusBadge/index.ts +2 -0
  241. package/rancher-components/Pill/RcStatusBadge/types.ts +5 -0
  242. package/rancher-components/Pill/RcStatusIndicator/RcStatusIndicator.test.ts +33 -0
  243. package/rancher-components/Pill/RcStatusIndicator/RcStatusIndicator.vue +75 -0
  244. package/rancher-components/Pill/RcStatusIndicator/index.ts +2 -0
  245. package/rancher-components/Pill/RcStatusIndicator/types.ts +7 -0
  246. package/rancher-components/Pill/RcTag/RcTag.test.ts +64 -0
  247. package/rancher-components/Pill/RcTag/RcTag.vue +94 -0
  248. package/rancher-components/Pill/RcTag/index.ts +1 -0
  249. package/rancher-components/Pill/RcTag/types.ts +9 -0
  250. package/rancher-components/Pill/types.ts +3 -0
  251. package/rancher-components/RcButton/RcButton.vue +1 -1
  252. package/rancher-components/RcDropdown/RcDropdown.test.ts +98 -0
  253. package/rancher-components/RcDropdown/RcDropdown.vue +5 -0
  254. package/rancher-components/RcDropdown/RcDropdownItem.vue +7 -1
  255. package/rancher-components/RcDropdown/RcDropdownItemCheckbox.vue +2 -1
  256. package/rancher-components/RcDropdown/RcDropdownItemSelect.vue +2 -1
  257. package/rancher-components/RcDropdown/useDropdownContext.ts +21 -0
  258. package/rancher-components/RcDropdown/useDropdownItem.ts +30 -1
  259. package/rancher-components/RcItemCard/RcItemCard.test.ts +20 -0
  260. package/rancher-components/RcItemCard/RcItemCard.vue +41 -6
  261. package/rancher-components/RcItemCard/RcItemCardAction.vue +12 -0
  262. package/store/__tests__/catalog.test.ts +156 -1
  263. package/store/aws.js +19 -8
  264. package/store/catalog.js +10 -5
  265. package/store/type-map.js +3 -15
  266. package/types/extension-manager.ts +26 -0
  267. package/types/resources/settings.d.ts +1 -1
  268. package/types/shell/index.d.ts +149 -44
  269. package/types/uiplugins.ts +73 -0
  270. package/utils/__tests__/back-off.test.ts +354 -0
  271. package/utils/__tests__/kontainer.test.ts +19 -0
  272. package/utils/__tests__/product.test.ts +129 -0
  273. package/utils/__tests__/resource.test.ts +87 -0
  274. package/utils/__tests__/uiplugins.test.ts +84 -0
  275. package/utils/alertmanagerconfig.js +2 -2
  276. package/utils/auth.js +3 -76
  277. package/utils/back-off.ts +176 -0
  278. package/utils/dynamic-importer.js +8 -0
  279. package/utils/kontainer.ts +3 -5
  280. package/utils/product.ts +39 -0
  281. package/utils/resource.ts +35 -0
  282. package/utils/select.js +0 -24
  283. package/utils/style.ts +3 -0
  284. package/utils/uiplugins.ts +29 -2
  285. package/utils/validators/__tests__/setting.test.js +92 -0
  286. package/utils/validators/formRules/__tests__/index.test.ts +91 -7
  287. package/utils/validators/formRules/index.ts +84 -8
  288. package/utils/validators/setting.js +17 -0
  289. package/vue.config.js +1 -1
  290. package/cloud-credential/__tests__/harvester.test.ts +0 -18
  291. package/components/Resource/Detail/Metadata/Rectangle.vue +0 -34
  292. package/components/Resource/Detail/Metadata/__tests__/Rectangle.test.ts +0 -24
  293. package/components/ResourceDetail/Masthead/__tests__/legacy.test.ts +0 -65
  294. package/components/ResourceDetail/__tests__/index.test.ts +0 -135
  295. package/components/ResourceDetail/legacy.vue +0 -562
  296. package/components/formatter/CloudCredExpired.vue +0 -69
  297. package/pages/explorer/resource/detail/configmap.vue +0 -42
  298. package/pages/explorer/resource/detail/projectsecret.vue +0 -9
  299. package/pages/explorer/resource/detail/secret.vue +0 -63
  300. package/utils/aws.js +0 -0
  301. /package/components/{ForceDirectedTreeChart.vue → ForceDirectedTreeChart/index.vue} +0 -0
@@ -97,32 +97,57 @@ describe('formRules', () => {
97
97
  });
98
98
 
99
99
  describe('urlRepository', () => {
100
- const message = JSON.stringify({ message: 'validation.git.url' });
100
+ const message = JSON.stringify({ message: 'validation.repository.url' });
101
101
  const testCases = [
102
102
  // Valid HTTP(s)
103
103
  ['https://github.com/rancher/dashboard.git', undefined],
104
104
  ['http://github.com/rancher/dashboard.git', undefined],
105
105
  ['https://github.com/rancher/dashboard', undefined],
106
106
  ['https://github.com/rancher/dashboard/', undefined],
107
+ ['https://github.com/rancher/%20dashboard/', undefined],
108
+ ['https://github.com/rancher/dashboard/%20', undefined],
109
+ ['https://localhost:8005', undefined],
107
110
 
108
111
  // Valid SSH
109
112
  ['git@github.com:rancher/dashboard.git', undefined],
110
113
  ['git@github.com:rancher/dashboard', undefined],
111
114
  ['git@github.com:rancher/dashboard/', undefined],
115
+ ['git@github.com:rancher/%20dashboard/', undefined],
116
+ ['git@github.com:rancher/dashboard/%20', undefined],
117
+ ['git@git.apps.local:fleet/fleet-local.git', undefined],
118
+ ['git@git.apps.local:33333/fleet/fleet-local.git', undefined],
112
119
 
113
120
  // Not valid HTTP(s)
114
121
  ['https://github.com/rancher/ dashboard.git', message],
115
122
  ['http://github.com/rancher/ dashboard.git', message],
123
+ ['http://github.com/ rancher/dashboard.git', message],
124
+ ['http://github.com /rancher/dashboard.git', message],
116
125
  ['https://github.com/rancher/dashboard ', message],
126
+ ['https%20://github.com/rancher/dashboard ', message],
127
+ ['ht%20tps://github.com/rancher/dashboard ', message],
128
+ ['https://git%20hub.com/rancher/dashboard/%20', message],
129
+ ['https://https://', message],
130
+ ['http:/ww.abc.com', message],
131
+ ['http:ww.abc.com', message],
117
132
  ['foo://github.com/rancher/dashboard/', message],
118
133
  ['github.com/rancher/dashboard/', message],
119
134
 
120
135
  // Not valid SSH
121
136
  ['git@github.com:rancher/ dashboard.git', message],
122
137
  ['git@github.com:rancher/dashboard ', message],
138
+ ['git@github.com:rancher/ dashboard', message],
139
+ ['git @github.com:rancher/dashboard', message],
140
+ ['git@github.com: rancher/dashboard', message],
123
141
  ['git@github.comrancher/dashboard', message],
142
+ ['git@githubcomrancher/dashboard', message],
143
+ ['%20git@github.comrancher/dashboard', message],
144
+ ['git@git%20hub.comrancher/dashboard', message],
145
+ ['git@git.apps.local:/fleet/fleet-local.git', message],
146
+ ['git@.git', message],
147
+ ['git@', message],
124
148
 
125
- [undefined, undefined]
149
+ [undefined, message],
150
+ ['', message]
126
151
  ];
127
152
 
128
153
  it.each(testCases)(
@@ -139,20 +164,26 @@ describe('formRules', () => {
139
164
  const message = JSON.stringify({ message: 'validation.oci.url' });
140
165
  const testCases = [
141
166
  // Valid
142
- ['oci://bucket/object', undefined],
167
+ ['oci://registry.example.com', undefined],
168
+ ['oci://myregistry.dev:5000', undefined],
169
+ ['oci://192.168.1.100', undefined],
170
+ ['oci://my.domain.com/my/image:tag', undefined],
171
+ ['oci://localhost:5000', undefined],
143
172
  ['oci://region.objectstorage.example.com/n', undefined],
144
- ['oci://a', undefined],
145
- ['oci://UPPERCASE/path', undefined],
146
173
 
147
174
  // Invalid
148
175
  ['http://example.com/oci', message],
149
176
  ['https://oci.cloud.com', message],
150
177
  ['ftp://oci.server.net', message],
151
- ['/path/to/oci', message],
178
+ ['path/to/oci', message],
179
+ ['oci://a', message],
152
180
  ['oci:/missing/slash', message],
153
181
  ['oci:', message],
154
182
  ['oci://', message],
155
- ['oci://space between', message],
183
+ ['oci://oci://duplicate/protocol', message],
184
+ ['oci ://registry.example.com/foo/bar', message],
185
+ ['oci://registry.example. com/foo/bar', message],
186
+ ['oci://registry.example.com/ foo/bar', message],
156
187
  ['oci://resource multiple spaces', message],
157
188
  ['', message],
158
189
  [undefined, message],
@@ -168,6 +199,59 @@ describe('formRules', () => {
168
199
  );
169
200
  });
170
201
 
202
+ describe('version', () => {
203
+ const message = JSON.stringify({ message: 'validation.version' });
204
+ const testCases: (null | string | undefined)[][] = [
205
+ // Valid
206
+ ['1.2.3', undefined],
207
+ ['', undefined],
208
+ [null, undefined],
209
+
210
+ // Invalid
211
+ ['1.2.x', message],
212
+ ['foo', message],
213
+ ['1.2', message],
214
+ ['1.2.', message],
215
+ ['.', message],
216
+ ];
217
+
218
+ it.each(testCases)(
219
+ 'should return undefined or correct message based on the provided Version: %p',
220
+ (version, expected) => {
221
+ const formRuleResult = formRules.version(version);
222
+
223
+ expect(formRuleResult).toStrictEqual(expected);
224
+ }
225
+ );
226
+ });
227
+
228
+ describe('semanticVersion', () => {
229
+ const message = JSON.stringify({ message: 'validation.semanticVersion' });
230
+ const testCases: (null | string | undefined)[][] = [
231
+ // Valid
232
+ ['1.2.x', undefined],
233
+ ['1.2.3', undefined],
234
+ ['1.2', undefined],
235
+ ['> 1', undefined],
236
+ ['', undefined],
237
+ [null, undefined],
238
+
239
+ // Invalid
240
+ ['foo', message],
241
+ ['1.2.', message],
242
+ ['.', message],
243
+ ];
244
+
245
+ it.each(testCases)(
246
+ 'should return undefined or correct message based on the provided Semantic Version: %p',
247
+ (version, expected) => {
248
+ const formRuleResult = formRules.semanticVersion(version);
249
+
250
+ expect(formRuleResult).toStrictEqual(expected);
251
+ }
252
+ );
253
+ });
254
+
171
255
  describe('alphanumeric', () => {
172
256
  const message = JSON.stringify({ message: 'validation.alphanumeric', key: 'testDisplayKey' });
173
257
  const testCases = [
@@ -1,3 +1,5 @@
1
+ import semver from 'semver';
2
+ import { parse } from '@shell/utils/url';
1
3
  import { RBAC } from '@shell/config/types';
2
4
  import { HCI } from '@shell/config/labels-annotations';
3
5
  import isEmpty from 'lodash/isEmpty';
@@ -5,7 +7,7 @@ import has from 'lodash/has';
5
7
  import isUrl from 'is-url';
6
8
  // import uniq from 'lodash/uniq';
7
9
  import { Translation } from '@shell/types/t';
8
- import { isHttps, isLocalhost, hasTrailingForwardSlash } from '@shell/utils/validators/setting';
10
+ import { isHttps, isLocalhost, hasTrailingForwardSlash, isDomainWithoutProtocol } from '@shell/utils/validators/setting';
9
11
  import { cronScheduleRule } from '@shell/utils/validators/cron-schedule';
10
12
 
11
13
  // import uniq from 'lodash/uniq';
@@ -164,6 +166,8 @@ export default function(
164
166
 
165
167
  const https: Validator = (val: string) => val && !isHttps(val) ? t('validation.setting.serverUrl.https') : undefined;
166
168
 
169
+ const awsStyleEndpoint: Validator = (val: string) => val && !isDomainWithoutProtocol(val) ? t('validation.setting.serverUrl.awsStyleEndpoint') : undefined;
170
+
167
171
  const localhost: Validator = (val: string) => isLocalhost(val) ? t('validation.setting.serverUrl.localhost') : undefined;
168
172
 
169
173
  const trailingForwardSlash: Validator = (val: string) => hasTrailingForwardSlash(val) ? t('validation.setting.serverUrl.trailingForwardSlash') : undefined;
@@ -173,22 +177,91 @@ export default function(
173
177
  const genericUrl: Validator = (val: string) => val && !isUrl(val) ? t('validation.genericUrl') : undefined;
174
178
 
175
179
  const urlRepository: Validator = (url: string) => {
176
- const regexPart1 = /^((http|git|ssh|http(s)|file|\/?)|(git@[\w\.]+))(:(\/\/)?)/gm;
177
- const regexPart2 = /^([\w\.@\:\/\-]+)([\d\/\w.-]+?)(.git){0,1}(\/)?$/gm;
180
+ const message = t('validation.repository.url');
181
+
182
+ if (!url) {
183
+ return message;
184
+ }
185
+
186
+ if (url.includes(' ')) {
187
+ return message;
188
+ }
189
+
190
+ const {
191
+ protocol,
192
+ authority,
193
+ host,
194
+ port,
195
+ path
196
+ } = parse(url);
197
+
198
+ // Test duplicate protocol
199
+ if (!host || protocol === host) {
200
+ return message;
201
+ }
202
+
203
+ // Test http(s) protocol
204
+ if (protocol && (!/^(http|http(s))/gm.test(protocol) || (!url.startsWith('https://') && !url.startsWith('http://')))) {
205
+ return message;
206
+ }
207
+
208
+ // Test ssh, authority must be valid (SSH user + host)
209
+ if (!protocol && !port && (!authority.endsWith(':') || path.startsWith('/'))) {
210
+ return message;
211
+ }
178
212
 
179
- if (url) {
180
- const urlPart2 = url.replaceAll(regexPart1, '');
213
+ // Encoded space characters (%20) are allowed only in the path
214
+ const hostAndPath = `${ host }${ path.replaceAll('%20', '') }`;
181
215
 
182
- return !urlPart2 || url === urlPart2 || !regexPart2.test(urlPart2.replaceAll('%20', '')) ? t('validation.git.url') : undefined;
216
+ // Test host/path
217
+ if (!/^([\w\.@\:\/\-]+)([\d\/\w.-]+?)(.git){0,1}(\/)?$/gm.test(hostAndPath)) {
218
+ return message;
183
219
  }
184
220
 
185
221
  return undefined;
186
222
  };
187
223
 
188
224
  const ociRegistry: Validator = (url: string) => {
189
- const regex = /^oci:\/\/\S+$/gm;
225
+ const message = t('validation.oci.url');
226
+
227
+ if (!url) {
228
+ return message;
229
+ }
230
+
231
+ if (url.includes(' ')) {
232
+ return message;
233
+ }
234
+
235
+ const {
236
+ protocol,
237
+ host,
238
+ path
239
+ } = parse(url);
240
+
241
+ // Test duplicate protocol
242
+ if (!host || protocol === host) {
243
+ return message;
244
+ }
245
+
246
+ // Test oci protocol
247
+ if (!url.startsWith('oci://')) {
248
+ return message;
249
+ }
250
+
251
+ // Test host/path
252
+ if (!/^([\w\.@\:\/\-]+)([\d\/\w.-]+?)(\/)?$/gm.test(`${ host }${ path }`)) {
253
+ return message;
254
+ }
255
+
256
+ return undefined;
257
+ };
258
+
259
+ const version: Validator = (value: string) => {
260
+ return value && !semver.valid(value) ? t('validation.version') : undefined;
261
+ };
190
262
 
191
- return !regex.test(url) ? t('validation.oci.url') : undefined;
263
+ const semanticVersion: Validator = (value: string) => {
264
+ return value && !semver.validRange(value) ? t('validation.semanticVersion') : undefined;
192
265
  };
193
266
 
194
267
  const alphanumeric: Validator = (val: string) => val && !/^[a-zA-Z0-9]+$/.test(val) ? t('validation.alphanumeric', { key }) : undefined;
@@ -541,6 +614,7 @@ export default function(
541
614
  imageUrl,
542
615
  interval,
543
616
  https,
617
+ awsStyleEndpoint,
544
618
  localhost,
545
619
  trailingForwardSlash,
546
620
  url,
@@ -561,9 +635,11 @@ export default function(
561
635
  isOctal,
562
636
  roleTemplateRules,
563
637
  ruleGroups,
638
+ semanticVersion,
564
639
  servicePort,
565
640
  subDomain,
566
641
  testRule,
642
+ version,
567
643
  wildcardHostname
568
644
  };
569
645
  }
@@ -1,9 +1,26 @@
1
1
  import isUrl from 'is-url';
2
2
 
3
+ // Note that these function cover specific use cases and you need to make sure it works for your use case before using them.
4
+ // ie they would consider empty values as valid, not all endpoint formatting is enforced
5
+
3
6
  export const isServerUrl = (value) => value === 'server-url';
4
7
 
5
8
  export const isHttps = (value) => value.toLowerCase().startsWith('https://');
6
9
 
10
+ /**
11
+ * Checks that provided string is a domain without protocol (case insensitive):
12
+ * - Cannot start with any protocol, such as http://, https://, ftp://, ftps://, udp://
13
+ * - Must only use the letters a to z, the numbers 0 to 9, and the dot (.) and hyphen (-) characters.
14
+ * - if the hyphen character is used in a domain name, it cannot be the first or the last character in the name.
15
+ * - The length of each label can be 2-63 characters
16
+ * - TLD is at least 2 characters
17
+ * - The total length of a domain name, including the dot at the end, cannot exceed 254 characters.
18
+ * - Allows for optional port and path
19
+ * @param {*} value
20
+ * @returns boolean indicating if the value is a domain without protocol
21
+ */
22
+ export const isDomainWithoutProtocol = (value) => (/^(?=.{1,254}$)(?![a-z][a-z0-9+.-]*:\/\/)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,}(?::\d{1,5})?(?:\/\S*)?$/i).test(value);
23
+
7
24
  export const isLocalhost = (value) => (/^(?:https?:\/\/)?(?:localhost|127\.0\.0\.1)/i).test(value);
8
25
 
9
26
  export const hasTrailingForwardSlash = (value) => isUrl(value) && value?.toLowerCase().endsWith('/');
package/vue.config.js CHANGED
@@ -546,7 +546,7 @@ module.exports = function(dir, appConfig = {}) {
546
546
  config.plugins.push(getVirtualModulesAutoImport(dir));
547
547
  config.plugins.push(getPackageImport(dir));
548
548
  config.plugins.push(createEnvVariablesPlugin(routerBasePath, rancherEnv));
549
- config.plugins.push(new NodePolyfillPlugin()); // required from Webpack 5 to polyfill node modules
549
+ config.plugins.push(new NodePolyfillPlugin({ additionalAliases: ['process'] })); // required from Webpack 5 to polyfill node modules
550
550
 
551
551
  // The static assets need to be in the built assets directory in order to get served (primarily the favicon)
552
552
  config.plugins.push(new CopyWebpackPlugin({ patterns: [{ from: path.join(SHELL_ABS, 'static'), to: '.' }] }));
@@ -1,18 +0,0 @@
1
- import { mount } from '@vue/test-utils';
2
- import HarvesterCloudCreds from '@shell/cloud-credential/harvester.vue';
3
-
4
- const mockStore = { getters: { 'i18n/t': jest.fn() } };
5
-
6
- describe('cloud credentials: Harvester', () => {
7
- const wrapper = mount(HarvesterCloudCreds, {
8
- props: { value: {} },
9
- global: { mocks: { $store: mockStore } }
10
- });
11
-
12
- it('should display the warning banner for token expiration', async() => {
13
- const warningBanner = wrapper.find('[data-testid="harvester-token-expiration-warning-banner"]');
14
-
15
- expect(warningBanner.exists()).toBe(true);
16
- expect(warningBanner.isVisible()).toBe(true);
17
- });
18
- });
@@ -1,34 +0,0 @@
1
- <script setup lang="ts">
2
- export interface RectangleProps {
3
- outline?: boolean;
4
- }
5
-
6
- const props = withDefaults(
7
- defineProps<RectangleProps>(),
8
- { outline: false }
9
- );
10
-
11
- </script>
12
-
13
- <template>
14
- <div
15
- class="rectangle"
16
- :class="{outline: props.outline}"
17
- >
18
- <slot />
19
- </div>
20
- </template>
21
-
22
- <style lang="scss" scoped>
23
- .rectangle {
24
- border: 1px solid var(--tag-bg);
25
- border-radius: 4px;
26
- padding: 0 8px;
27
- height: 23px;
28
- line-height: 23px;
29
-
30
- &:not(.outline) {
31
- background-color: var(--tag-bg);
32
- }
33
- }
34
- </style>
@@ -1,24 +0,0 @@
1
- import { mount } from '@vue/test-utils';
2
- import Rectangle from '@shell/components/Resource/Detail/Metadata/Rectangle.vue';
3
-
4
- describe('component: Rectangle', () => {
5
- it('should render container with class title and missing outline when passed outline:false', async() => {
6
- const wrapper = mount(Rectangle, { props: { outline: false } });
7
-
8
- expect(wrapper.find('.rectangle').exists()).toBeTruthy();
9
- expect(wrapper.find('.rectangle.outline').exists()).toBeFalsy();
10
- });
11
-
12
- it('should render outline class when passed outline:true', async() => {
13
- const wrapper = mount(Rectangle, { props: { outline: true } });
14
-
15
- expect(wrapper.find('.rectangle.outline').exists()).toBeTruthy();
16
- });
17
-
18
- it('should render default slot', async() => {
19
- const content = 'CONTENT';
20
- const wrapper = mount(Rectangle, { slots: { default: content } });
21
-
22
- expect(wrapper.find('.rectangle').element.innerHTML).toStrictEqual(content);
23
- });
24
- });
@@ -1,65 +0,0 @@
1
- import { mount, RouterLinkStub } from '@vue/test-utils';
2
- import { _VIEW } from '@shell/config/query-params';
3
- import Legacy from '@shell/components/ResourceDetail/Masthead/legacy.vue';
4
- import { createStore } from 'vuex';
5
-
6
- const mockedStore = () => {
7
- return {
8
- getters: {
9
- currentStore: () => 'current_store',
10
- currentProduct: { inStore: 'cluster' },
11
- isExplorer: false,
12
- currentCluster: {},
13
- 'type-map/labelFor': jest.fn(),
14
- 'type-map/optionsFor': jest.fn(),
15
- 'current_store/schemaFor': jest.fn(),
16
- },
17
- };
18
- };
19
-
20
- const requiredSetup = () => {
21
- const store = createStore({ getters: { 'management/byId': () => jest.fn() } });
22
-
23
- return {
24
- stubs: {
25
- 'router-link': RouterLinkStub,
26
- LiveDate: true
27
- },
28
- provide: { store },
29
- mocks: { $store: mockedStore() }
30
- };
31
- };
32
-
33
- describe('component: Masthead/legacy', () => {
34
- it.each([
35
- ['hidden', '', false, { displayName: 'admin', location: { id: 'resource-id' } }, false, false],
36
- ['plain-text', 'admin', true, { displayName: 'admin', location: null }, false, true],
37
- ['link', 'foo', true, { displayName: 'foo', location: { id: 'resource-id' } }, true, false],
38
- ])('"Created By" should be %p, with text: %p', (
39
- _,
40
- text,
41
- showCreatedBy,
42
- createdBy,
43
- showLink,
44
- showPlainText,
45
- ) => {
46
- const wrapper = mount(Legacy, {
47
- props: {
48
- mode: _VIEW,
49
- value: {
50
- showCreatedBy,
51
- createdBy,
52
- },
53
- },
54
- global: { ...requiredSetup() }
55
- });
56
-
57
- const container = wrapper.find('[data-testid="masthead-subheader-createdBy"]');
58
- const link = wrapper.find('[data-testid="masthead-subheader-createdBy-link"]');
59
- const plainText = wrapper.find('[data-testid="masthead-subheader-createdBy_plain-text"]');
60
-
61
- expect(link.exists()).toBe(showLink);
62
- expect(plainText.exists()).toBe(showPlainText);
63
- expect(showLink || showPlainText ? container.element.textContent : '').toContain(text);
64
- });
65
- });
@@ -1,135 +0,0 @@
1
- import { mount } from '@vue/test-utils';
2
- import ResourceDetail from '@shell/components/ResourceDetail/index.vue';
3
- import { _EDIT, _VIEW, LEGACY, MODE } from '@shell/config/query-params';
4
- import * as pageEnabled from '@shell/composables/useIsNewDetailPageEnabled';
5
- import flushPromises from 'flush-promises';
6
- import { computed } from 'vue';
7
-
8
- const mockQuery: any = {};
9
- const mockParams: any = {};
10
-
11
- jest.mock('@shell/components/ResourceDetail/legacy.vue', () => ({
12
- template: '<div class="legacy">Legacy</div>',
13
- name: 'Legacy',
14
- props: ['flexContent', 'componentTestId', 'storeOverride', 'resourceOverride', 'parentRouteOverride', 'errorsMap']
15
- }));
16
-
17
- jest.mock('@shell/components/Loading.vue', () => ({
18
- template: '<div class="loading">Loading</div>',
19
- name: 'Loading'
20
- }));
21
-
22
- jest.mock('@shell/pages/explorer/resource/detail/configmap.vue', () => ({
23
- template: '<div class="configmap">configmap</div>',
24
- name: 'configmap',
25
- }));
26
-
27
- jest.mock('vue-router', () => ({
28
- useRoute: () => ({
29
- query: mockQuery,
30
- params: mockParams
31
- })
32
- }));
33
-
34
- jest.mock('@shell/composables/useIsNewDetailPageEnabled');
35
-
36
- describe('component: ResourceDetail/index', () => {
37
- const resourceName = 'configmap';
38
- const useIsNewDetailPageEnabledSpy = jest.spyOn(pageEnabled, 'useIsNewDetailPageEnabled');
39
-
40
- beforeEach(() => {
41
- jest.clearAllMocks();
42
- });
43
-
44
- it('should render legacy component with default props if useIsNewDetailPageEnabledSpy is false', async() => {
45
- mockParams.resource = resourceName;
46
- mockQuery[MODE] = _VIEW;
47
-
48
- useIsNewDetailPageEnabledSpy.mockReturnValue(computed(() => false));
49
-
50
- const wrapper = mount(ResourceDetail, { });
51
- const legacyComponent = wrapper.findComponent<any>({ name: 'Legacy' });
52
-
53
- expect(legacyComponent.props('flexContent')).toStrictEqual(false);
54
- expect(legacyComponent.props('componentTestId')).toStrictEqual('resource-details');
55
- expect(legacyComponent.props('storeOverride')).toBeUndefined();
56
- expect(legacyComponent.props('resourceOverride')).toBeUndefined();
57
- expect(legacyComponent.props('parentRouteOverride')).toBeUndefined();
58
- expect(legacyComponent.props('errorsMap')).toBeUndefined();
59
- expect(useIsNewDetailPageEnabledSpy).toHaveBeenCalledTimes(1);
60
- });
61
-
62
- it('should render legacy component with default props if useIsNewDetailPageEnabledSpy is false but resourceName has not been added to our mapping', async() => {
63
- mockParams.resource = 'notMapped';
64
- mockQuery[MODE] = _VIEW;
65
-
66
- useIsNewDetailPageEnabledSpy.mockReturnValue(computed(() => false));
67
-
68
- const wrapper = mount(ResourceDetail, {});
69
- const legacyComponent = wrapper.findComponent<any>({ name: 'Legacy' });
70
-
71
- expect(legacyComponent.exists()).toBeTruthy();
72
- expect(useIsNewDetailPageEnabledSpy).toHaveBeenCalledTimes(1);
73
- });
74
-
75
- it('should render legacy component with default props if useIsNewDetailPageEnabledSpy is false but mode is not VIEW', async() => {
76
- mockParams.resource = resourceName;
77
- mockQuery[MODE] = _EDIT;
78
-
79
- useIsNewDetailPageEnabledSpy.mockReturnValue(computed(() => false));
80
-
81
- const wrapper = mount(ResourceDetail, {});
82
- const legacyComponent = wrapper.findComponent<any>({ name: 'Legacy' });
83
-
84
- expect(legacyComponent.exists()).toBeTruthy();
85
- expect(useIsNewDetailPageEnabledSpy).toHaveBeenCalledTimes(1);
86
- });
87
-
88
- it('should render legacy component while forwarding props', async() => {
89
- mockParams.resource = resourceName;
90
- mockQuery[MODE] = _VIEW;
91
-
92
- const flexContent = true;
93
- const componentTestId = 'componentTestId';
94
- const storeOverride = 'storeOverride';
95
- const resourceOverride = 'resourceOverride';
96
- const parentRouteOverride = 'parentRouteOverride';
97
- const errorsMap = { error: 'test' };
98
-
99
- const wrapper = mount(ResourceDetail, {
100
- props: {
101
- flexContent,
102
- componentTestId,
103
- storeOverride,
104
- resourceOverride,
105
- parentRouteOverride,
106
- errorsMap,
107
- }
108
- });
109
- const legacyComponent = wrapper.findComponent<any>({ name: 'Legacy' });
110
-
111
- expect(legacyComponent.props('flexContent')).toStrictEqual(flexContent);
112
- expect(legacyComponent.props('componentTestId')).toStrictEqual(componentTestId);
113
- expect(legacyComponent.props('storeOverride')).toStrictEqual(storeOverride);
114
- expect(legacyComponent.props('resourceOverride')).toStrictEqual(resourceOverride);
115
- expect(legacyComponent.props('parentRouteOverride')).toStrictEqual(parentRouteOverride);
116
- expect(legacyComponent.props('errorsMap')).toStrictEqual(errorsMap);
117
- });
118
-
119
- it('should render new component if useIsNewDetailPageEnabledSpy is true', async() => {
120
- mockParams.resource = resourceName;
121
- mockParams.id = 'ID';
122
- mockQuery[MODE] = _VIEW;
123
- mockQuery[LEGACY] = 'false';
124
-
125
- useIsNewDetailPageEnabledSpy.mockReturnValue(computed(() => true));
126
-
127
- const wrapper = mount(ResourceDetail);
128
-
129
- await flushPromises();
130
- const configmapComponent = wrapper.findComponent<any>({ name: 'configmap' });
131
-
132
- expect(configmapComponent.exists()).toBeTruthy();
133
- expect(useIsNewDetailPageEnabledSpy).toHaveBeenCalledTimes(1);
134
- });
135
- });