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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (272) hide show
  1. package/apis/impl/apis.ts +6 -0
  2. package/apis/index.ts +26 -0
  3. package/apis/intf/resources-api/cluster-api.ts +18 -0
  4. package/apis/intf/resources-api/mgmt-api.ts +15 -0
  5. package/apis/intf/resources-api/resource-base.ts +107 -0
  6. package/apis/intf/resources-api/resource-constants.ts +147 -0
  7. package/apis/intf/resources-api/resources-api.ts +143 -0
  8. package/apis/intf/resources.ts +49 -0
  9. package/apis/intf/{modal.ts → shell-api/modal.ts} +21 -26
  10. package/apis/intf/shell-api/proxy.ts +216 -0
  11. package/apis/intf/{slide-in.ts → shell-api/slide-in.ts} +4 -3
  12. package/apis/intf/{system.ts → shell-api/system.ts} +4 -1
  13. package/apis/intf/shell.ts +12 -6
  14. package/apis/resources/__tests__/resources-api-class.test.ts +550 -0
  15. package/apis/resources/index.ts +22 -0
  16. package/apis/resources/resources-api-class.ts +187 -0
  17. package/apis/shell/__tests__/proxy.test.ts +369 -0
  18. package/apis/shell/index.ts +8 -1
  19. package/apis/shell/modal.ts +4 -1
  20. package/apis/shell/notifications.ts +9 -6
  21. package/apis/shell/proxy.ts +256 -0
  22. package/apis/shell/slide-in.ts +4 -1
  23. package/apis/vue-shim.d.ts +2 -1
  24. package/assets/data/aws-regions.json +4 -0
  25. package/assets/fonts/lato/LatoLatin-Black.woff +0 -0
  26. package/assets/fonts/lato/LatoLatin-Black.woff2 +0 -0
  27. package/assets/fonts/lato/LatoLatin-BlackItalic.woff +0 -0
  28. package/assets/fonts/lato/LatoLatin-BlackItalic.woff2 +0 -0
  29. package/assets/fonts/lato/LatoLatin-Bold.woff +0 -0
  30. package/assets/fonts/lato/LatoLatin-Bold.woff2 +0 -0
  31. package/assets/fonts/lato/LatoLatin-BoldItalic.woff +0 -0
  32. package/assets/fonts/lato/LatoLatin-BoldItalic.woff2 +0 -0
  33. package/assets/fonts/lato/LatoLatin-Heavy.woff +0 -0
  34. package/assets/fonts/lato/LatoLatin-Heavy.woff2 +0 -0
  35. package/assets/fonts/lato/LatoLatin-HeavyItalic.woff +0 -0
  36. package/assets/fonts/lato/LatoLatin-HeavyItalic.woff2 +0 -0
  37. package/assets/fonts/lato/LatoLatin-Italic.woff +0 -0
  38. package/assets/fonts/lato/LatoLatin-Italic.woff2 +0 -0
  39. package/assets/fonts/lato/LatoLatin-Light.woff +0 -0
  40. package/assets/fonts/lato/LatoLatin-Light.woff2 +0 -0
  41. package/assets/fonts/lato/LatoLatin-LightItalic.woff +0 -0
  42. package/assets/fonts/lato/LatoLatin-LightItalic.woff2 +0 -0
  43. package/assets/fonts/lato/LatoLatin-Medium.woff +0 -0
  44. package/assets/fonts/lato/LatoLatin-Medium.woff2 +0 -0
  45. package/assets/fonts/lato/LatoLatin-MediumItalic.woff +0 -0
  46. package/assets/fonts/lato/LatoLatin-MediumItalic.woff2 +0 -0
  47. package/assets/fonts/lato/LatoLatin-Regular.woff +0 -0
  48. package/assets/fonts/lato/LatoLatin-Regular.woff2 +0 -0
  49. package/assets/fonts/lato/LatoLatin-Semibold.woff +0 -0
  50. package/assets/fonts/lato/LatoLatin-Semibold.woff2 +0 -0
  51. package/assets/fonts/lato/LatoLatin-SemiboldItalic.woff +0 -0
  52. package/assets/fonts/lato/LatoLatin-SemiboldItalic.woff2 +0 -0
  53. package/assets/styles/base/_variables.scss +2 -0
  54. package/assets/styles/fonts/_fontstack.scss +132 -8
  55. package/assets/translations/en-us.yaml +22 -5
  56. package/chart/monitoring/index.vue +10 -1
  57. package/components/ActionDropdownShell.vue +2 -1
  58. package/components/CruResourceFooter.vue +9 -5
  59. package/components/ExplorerProjectsNamespaces.vue +1 -1
  60. package/components/InstallHelmCharts.vue +2 -2
  61. package/components/LandingPagePreference.vue +14 -5
  62. package/components/Resource/Detail/Metadata/IdentifyingInformation/index.vue +15 -1
  63. package/components/Resource/Detail/Metadata/index.vue +6 -0
  64. package/components/Resource/Detail/ResourcePopover/index.vue +12 -1
  65. package/components/Resource/Detail/SpacedRow.vue +3 -1
  66. package/components/Resource/Detail/TitleBar/index.vue +10 -11
  67. package/components/ResourceList/Masthead.vue +12 -8
  68. package/components/SelectIconGrid.vue +0 -10
  69. package/components/SingleClusterInfo.vue +1 -0
  70. package/components/SortableTable/__tests__/sorting.test.ts +126 -0
  71. package/components/SortableTable/index.vue +6 -9
  72. package/components/SortableTable/selection.js +23 -5
  73. package/components/SortableTable/sorting.js +6 -3
  74. package/components/Wizard.vue +14 -13
  75. package/components/fleet/FleetBundles.vue +100 -12
  76. package/components/fleet/FleetClusterTargets/index.vue +37 -15
  77. package/components/fleet/__tests__/FleetClusterTargets.test.ts +149 -115
  78. package/components/fleet/__tests__/FleetClusters.test.ts +12 -12
  79. package/components/form/LabeledSelect.vue +20 -3
  80. package/components/form/NameNsDescription.vue +11 -0
  81. package/components/form/Security.vue +6 -2
  82. package/components/form/WorkloadPorts.vue +2 -7
  83. package/components/form/__tests__/Security.test.ts +76 -0
  84. package/components/formatter/Autoscaler.vue +4 -4
  85. package/components/formatter/ClusterKubeVersion.vue +27 -0
  86. package/components/formatter/ClusterLink.vue +1 -7
  87. package/components/formatter/ClusterProvider.vue +6 -10
  88. package/components/formatter/FleetSummaryGraph.vue +0 -3
  89. package/components/formatter/MachineSummaryGraph.vue +1 -1
  90. package/components/formatter/PodsUsage.vue +2 -2
  91. package/components/formatter/__tests__/Autoscaler.test.ts +19 -22
  92. package/components/formatter/__tests__/FleetSummaryGraph.test.ts +216 -0
  93. package/components/formatter/__tests__/PodsUsage.test.ts +6 -10
  94. package/components/nav/NamespaceFilter.vue +2 -2
  95. package/components/nav/TopLevelMenu.helper.ts +15 -3
  96. package/components/nav/TopLevelMenu.vue +16 -5
  97. package/components/nav/__tests__/TopLevelMenu.test.ts +145 -21
  98. package/components/templates/home.vue +18 -0
  99. package/components/templates/plain.vue +18 -0
  100. package/components/templates/standalone.vue +17 -0
  101. package/composables/useFormValidation.ts +93 -0
  102. package/composables/useVeeValidateField.test.ts +159 -0
  103. package/composables/useVeeValidateField.ts +67 -0
  104. package/config/pagination-table-headers.js +18 -1
  105. package/config/product/manager.js +82 -21
  106. package/config/router/routes.js +6 -0
  107. package/config/table-headers.js +20 -1
  108. package/config/types.js +2 -1
  109. package/core/__tests__/plugin-products.test.ts +904 -20
  110. package/core/plugin-products-base.ts +107 -7
  111. package/core/plugin-products.ts +4 -0
  112. package/core/plugin-types.ts +111 -1
  113. package/core/plugin.ts +15 -7
  114. package/core/productDebugger.js +9 -4
  115. package/core/types-provisioning.ts +43 -30
  116. package/core/types.ts +57 -20
  117. package/detail/__tests__/pod.test.ts +41 -0
  118. package/detail/harvesterhci.io.management.cluster.vue +6 -2
  119. package/detail/pod.vue +1 -1
  120. package/detail/provisioning.cattle.io.cluster.vue +4 -10
  121. package/edit/auth/__tests__/azuread.test.ts +217 -34
  122. package/edit/auth/azuread.vue +122 -14
  123. package/edit/auth/oidc.vue +2 -2
  124. package/edit/networking.k8s.io.ingress/DefaultBackend.vue +13 -4
  125. package/edit/networking.k8s.io.ingress/RulePath.vue +8 -4
  126. package/edit/networking.k8s.io.ingress/index.vue +75 -20
  127. package/edit/provisioning.cattle.io.cluster/__tests__/MachinePool.test.ts +104 -0
  128. package/edit/provisioning.cattle.io.cluster/index.vue +11 -7
  129. package/edit/provisioning.cattle.io.cluster/rke2.vue +8 -4
  130. package/edit/provisioning.cattle.io.cluster/tabs/MachinePool.vue +11 -0
  131. package/edit/provisioning.cattle.io.cluster/tabs/registries/RegistryConfigs.vue +37 -4
  132. package/edit/provisioning.cattle.io.cluster/tabs/registries/__tests__/RegistryConfigs.test.ts +132 -7
  133. package/edit/provisioning.cattle.io.cluster/tabs/registries/index.vue +2 -1
  134. package/edit/secret/__tests__/ssh.test.ts +5 -6
  135. package/edit/secret/basic.vue +31 -0
  136. package/edit/secret/index.vue +68 -17
  137. package/edit/secret/registry.vue +38 -0
  138. package/edit/secret/ssh.vue +29 -0
  139. package/edit/secret/tls.vue +30 -0
  140. package/edit/service.vue +4 -4
  141. package/edit/workload/Upgrading.vue +3 -3
  142. package/edit/workload/__tests__/Upgrading.test.ts +6 -9
  143. package/edit/workload/mixins/workload.js +2 -1
  144. package/list/fleet.cattle.io.bundle.vue +7 -104
  145. package/list/fleet.cattle.io.clusterregistrationtoken.vue +20 -0
  146. package/list/provisioning.cattle.io.cluster.vue +262 -180
  147. package/list/utils/management.cattle.io.cluster.utils.ts +128 -0
  148. package/mixins/__tests__/chart.test.ts +112 -0
  149. package/mixins/brand.js +2 -1
  150. package/mixins/chart.js +12 -8
  151. package/mixins/resource-fetch-api-pagination.js +41 -5
  152. package/models/__tests__/ext.cattle.io.kubeconfig.test.ts +67 -67
  153. package/models/__tests__/management.cattle.io.cluster.test.ts +1 -1
  154. package/models/__tests__/management.cattle.io.node.ts +6 -5
  155. package/models/__tests__/management.cattle.io.nodepool.ts +5 -4
  156. package/models/__tests__/provisioning.cattle.io.cluster.test.ts +32 -11
  157. package/models/base-cluster.x-k8s.io.js +26 -0
  158. package/models/cluster.js +1 -1
  159. package/models/cluster.x-k8s.io.machine.js +4 -22
  160. package/models/cluster.x-k8s.io.machinedeployment.js +2 -20
  161. package/models/cluster.x-k8s.io.machineset.js +2 -20
  162. package/models/compliance.cattle.io.clusterscan.js +130 -2
  163. package/models/ext.cattle.io.kubeconfig.ts +4 -7
  164. package/models/fleet-application.js +3 -1
  165. package/models/management.cattle.io.cluster.js +417 -40
  166. package/models/management.cattle.io.node.js +6 -4
  167. package/models/management.cattle.io.nodepool.js +1 -1
  168. package/models/networking.k8s.io.ingress.js +12 -4
  169. package/models/provisioning.cattle.io.cluster.js +47 -330
  170. package/models/rke.cattle.io.etcdsnapshot.js +1 -2
  171. package/package.json +11 -29
  172. package/pages/__tests__/readme.test.ts +49 -0
  173. package/pages/auth/setup.vue +2 -3
  174. package/pages/c/_cluster/apps/charts/__tests__/chart.test.ts +76 -0
  175. package/pages/c/_cluster/apps/charts/chart.vue +60 -8
  176. package/pages/c/_cluster/apps/charts/install.vue +10 -7
  177. package/pages/c/_cluster/explorer/__tests__/index.test.ts +23 -25
  178. package/pages/c/_cluster/explorer/index.vue +5 -49
  179. package/pages/c/_cluster/istio/__tests__/istio.index.test.ts +194 -0
  180. package/pages/c/_cluster/istio/index.vue +21 -6
  181. package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +1 -0
  182. package/pages/c/_cluster/uiplugins/__tests__/index.test.ts +719 -2
  183. package/pages/c/_cluster/uiplugins/index.vue +203 -197
  184. package/pages/diagnostic.vue +13 -17
  185. package/pages/fail-whale.vue +18 -0
  186. package/pages/home.vue +77 -260
  187. package/pages/readme.vue +88 -0
  188. package/plugins/dashboard-store/__tests__/resource-class.test.ts +88 -0
  189. package/plugins/dashboard-store/actions.js +40 -18
  190. package/plugins/dashboard-store/resource-class.js +5 -2
  191. package/plugins/steve/__tests__/subscribe.spec.ts +6 -3
  192. package/plugins/steve/steve-pagination-utils.ts +11 -3
  193. package/plugins/steve/subscribe.js +35 -5
  194. package/rancher-components/Form/LabeledInput/LabeledInput.test.ts +10 -4
  195. package/rancher-components/Form/LabeledInput/LabeledInput.vue +7 -52
  196. package/rancher-components/RcButton/RcButton.test.ts +37 -1
  197. package/rancher-components/RcButton/RcButton.vue +38 -8
  198. package/rancher-components/RcDropdown/RcDropdownTrigger.vue +10 -8
  199. package/store/__tests__/catalog.test.ts +115 -1
  200. package/store/__tests__/type-map.test.ts +556 -1
  201. package/store/action-menu.js +8 -3
  202. package/store/auth.js +1 -1
  203. package/store/aws.js +27 -16
  204. package/store/catalog.js +27 -3
  205. package/store/digitalocean.js +20 -38
  206. package/store/index.js +2 -0
  207. package/store/linode.js +25 -40
  208. package/store/pnap.js +1 -0
  209. package/store/type-map.js +111 -29
  210. package/tsconfig.paths.json +8 -8
  211. package/types/kube/kube-api.ts +14 -1
  212. package/types/rancher/steve.api.ts +12 -12
  213. package/types/resources/settings.d.ts +2 -1
  214. package/types/shell/index.d.ts +102 -2
  215. package/types/store/dashboard-store.types.ts +108 -11
  216. package/types/store/pagination.types.ts +6 -3
  217. package/utils/__tests__/alertmanagerconfig.test.ts +117 -0
  218. package/utils/__tests__/async.test.ts +87 -0
  219. package/utils/__tests__/aws.test.ts +140 -0
  220. package/utils/__tests__/banners.test.ts +176 -0
  221. package/utils/__tests__/chart.test.ts +64 -1
  222. package/utils/__tests__/color.test.ts +226 -0
  223. package/utils/__tests__/duration.test.ts +140 -0
  224. package/utils/__tests__/fleet.test.ts +340 -0
  225. package/utils/__tests__/ingress.test.ts +553 -0
  226. package/utils/__tests__/kube.test.ts +68 -0
  227. package/utils/__tests__/namespace-filter.test.ts +109 -0
  228. package/utils/__tests__/pagination-utils.test.ts +361 -0
  229. package/utils/__tests__/parse-externalid.test.ts +137 -0
  230. package/utils/__tests__/perf-setting.utils.test.ts +98 -0
  231. package/utils/__tests__/poller-sequential.test.ts +177 -0
  232. package/utils/__tests__/poller.test.ts +170 -0
  233. package/utils/__tests__/promise.test.ts +346 -0
  234. package/utils/__tests__/settings.test.ts +140 -0
  235. package/utils/__tests__/sort-utils.test.ts +301 -0
  236. package/utils/__tests__/string-utils.test.ts +798 -0
  237. package/utils/__tests__/string.test.ts +23 -1
  238. package/utils/__tests__/style.test.ts +154 -0
  239. package/utils/__tests__/svg-filter.test.ts +184 -0
  240. package/utils/__tests__/units.test.ts +417 -0
  241. package/utils/__tests__/versions.test.ts +128 -0
  242. package/utils/__tests__/xccdf.test.ts +391 -0
  243. package/utils/chart.js +36 -0
  244. package/utils/fleet.ts +13 -3
  245. package/utils/gatekeeper/__tests__/util.test.ts +174 -0
  246. package/utils/gc/__tests__/gc-interval.test.ts +119 -0
  247. package/utils/gc/__tests__/gc-root-store.test.ts +225 -0
  248. package/utils/gc/__tests__/gc-route-changed.test.ts +96 -0
  249. package/utils/gc/__tests__/gc.test.ts +487 -0
  250. package/utils/ingress.ts +9 -1
  251. package/utils/pagination-utils.ts +2 -1
  252. package/utils/string.js +25 -2
  253. package/utils/uiplugins.ts +5 -5
  254. package/utils/validators/__tests__/cluster-name.test.ts +110 -0
  255. package/utils/validators/__tests__/cron-schedule.test.ts +79 -0
  256. package/utils/validators/__tests__/index.test.ts +481 -0
  257. package/utils/validators/__tests__/kubernetes-name.test.ts +163 -0
  258. package/utils/validators/__tests__/misc-validators.test.ts +246 -0
  259. package/utils/validators/__tests__/pod-affinity.test.ts +382 -0
  260. package/utils/validators/__tests__/prometheusrule.test.ts +211 -0
  261. package/utils/validators/__tests__/role-template.test.ts +149 -0
  262. package/utils/validators/__tests__/service.test.ts +283 -0
  263. package/utils/validators/__tests__/setting.test.js +32 -0
  264. package/utils/validators/formRules/__tests__/index.test.ts +50 -0
  265. package/utils/validators/formRules/index.ts +5 -5
  266. package/utils/validators/machine-pool.ts +1 -1
  267. package/utils/validators/setting.js +18 -3
  268. package/utils/xccdf.ts +418 -0
  269. package/assets/fonts/lato/lato-v17-latin-700.woff +0 -0
  270. package/assets/fonts/lato/lato-v17-latin-700.woff2 +0 -0
  271. package/assets/fonts/lato/lato-v17-latin-regular.woff +0 -0
  272. package/assets/fonts/lato/lato-v17-latin-regular.woff2 +0 -0
@@ -5,6 +5,12 @@ import CopyCode from '@shell/components/CopyCode';
5
5
  import Tab from '@shell/components/Tabbed/Tab';
6
6
  import { allHash } from '@shell/utils/promise';
7
7
 
8
+ // Couple of things wrong here
9
+ // 1. It should be in pkg/harvester-manager extension
10
+ // 2. It's not used when the harvester-ui extension is loaded
11
+ // - extension has it's own detail/harvesterhci.io.management.cluster.vue which supersedes this
12
+ // - unless harvester standalone uses it, it should not exist in the harvester-ui extension
13
+
8
14
  export default {
9
15
  emits: ['input'],
10
16
 
@@ -31,8 +37,6 @@ export default {
31
37
 
32
38
  const res = await allHash(hash);
33
39
 
34
- this.allNodes = res.allNodes || [];
35
- this.allNodePools = res.allNodePools || [];
36
40
  this.clusterToken = res.clusterToken;
37
41
  },
38
42
 
package/detail/pod.vue CHANGED
@@ -106,7 +106,7 @@ export default {
106
106
  availableActions: this.value.containerActions,
107
107
  stateObj: status, // Required if there's a description
108
108
  stateDescription: descriptions.join(' | '), // Required to display the description
109
- initIcon: this.value.containerIsInit(container) ? 'icon-checkmark icon-2x text-success ml-5' : 'icon-minus icon-2x text-muted ml-5',
109
+ initIcon: this.value.containerIsInit(container) ? 'icon-checkmark text-success ml-5' : 'icon-minus text-muted ml-5',
110
110
 
111
111
  // Call openShell here so that opening the shell
112
112
  // at the container level still has 'this' in scope.
@@ -147,10 +147,6 @@ export default {
147
147
  fetchOne.normanClusters = this.$store.dispatch('rancher/findAll', { type: NORMAN.CLUSTER });
148
148
  }
149
149
 
150
- if ( this.value.isRke1 && this.$store.getters['isRancher'] ) {
151
- fetchOne.normanNodePools = this.$store.dispatch('rancher/findAll', { type: NORMAN.NODE_POOL });
152
- }
153
-
154
150
  const fetchOneRes = await allHash(fetchOne);
155
151
 
156
152
  this.allMachines = fetchOneRes.machines || [];
@@ -203,10 +199,6 @@ export default {
203
199
  if ( this.$store.getters['management/canList'](MANAGEMENT.RKE_TEMPLATE) ) {
204
200
  this.$store.dispatch('management/findAll', { type: MANAGEMENT.RKE_TEMPLATE });
205
201
  }
206
-
207
- if ( this.$store.getters['management/canList'](MANAGEMENT.RKE_TEMPLATE_REVISION) ) {
208
- this.$store.dispatch('management/findAll', { type: MANAGEMENT.RKE_TEMPLATE_REVISION });
209
- }
210
202
  },
211
203
 
212
204
  created() {
@@ -284,9 +276,11 @@ export default {
284
276
  extCustomParams: null,
285
277
  extDetailTabs: {
286
278
  machines: true, // in this component
279
+ nodes: true, // in this component
287
280
  logs: true, // in this component
288
281
  registration: true, // in this component
289
282
  snapshots: true, // in this component
283
+ autoscaler: true, // in this component
290
284
  related: true, // in ResourceTabs
291
285
  events: true, // in ResourceTabs
292
286
  conditions: true, // in ResourceTabs
@@ -451,7 +445,7 @@ export default {
451
445
  },
452
446
 
453
447
  showNodes() {
454
- return !this.showMachines && this.haveNodes && !!this.nodes.length && this.extDetailTabs.machines;
448
+ return !this.showMachines && this.haveNodes && !!this.nodes.length && this.extDetailTabs.nodes;
455
449
  },
456
450
 
457
451
  showSnapshots() {
@@ -628,7 +622,7 @@ export default {
628
622
  },
629
623
 
630
624
  showAutoScalerTab() {
631
- return isAutoscalerFeatureFlagEnabled(this.$store) && this.value.hasAccessToAutoscalerConfigMap;
625
+ return isAutoscalerFeatureFlagEnabled(this.$store) && this.value.hasAccessToAutoscalerConfigMap && this.extDetailTabs.autoscaler;
632
626
  }
633
627
  },
634
628
 
@@ -3,6 +3,7 @@ import { nextTick } from 'vue';
3
3
  import { mount } from '@vue/test-utils';
4
4
  import AzureAD from '@shell/edit/auth/azuread.vue';
5
5
  import { _EDIT } from '@shell/config/query-params';
6
+ import { SLO_OPTION_VALUES } from '@shell/mixins/auth-config';
6
7
 
7
8
  jest.mock('@shell/utils/clipboard', () => {
8
9
  return { copyTextToClipboard: jest.fn(() => Promise.resolve({})) };
@@ -28,43 +29,44 @@ const mockModel = {
28
29
  type: 'azureADConfig',
29
30
  };
30
31
 
31
- describe('edit: azureAD should', () => {
32
- let wrapper: any;
33
- const requiredSetup = () => ({
34
- data() {
35
- return {
36
- isEnabling: true,
37
- editConfig: false,
38
- model: { ...mockModel },
39
- serverSetting: null,
40
- errors: [],
41
- originalModel: null,
42
- principals: [],
43
- authConfigName: 'azuread',
44
- };
45
- },
46
- global: {
47
- mocks: {
48
- $fetchState: { pending: false },
49
- $store: {
50
- getters: {
51
- currentStore: () => 'current_store',
52
- 'current_store/schemaFor': jest.fn(),
53
- 'current_store/all': jest.fn(),
54
- 'i18n/t': (val: string) => val,
55
- 'i18n/exists': jest.fn(),
56
- },
57
- dispatch: jest.fn()
32
+ const requiredSetup = (modelOverrides = {}) => ({
33
+ data() {
34
+ return {
35
+ isEnabling: true,
36
+ editConfig: false,
37
+ model: { ...mockModel, ...modelOverrides },
38
+ serverSetting: null,
39
+ errors: [],
40
+ originalModel: null,
41
+ principals: [],
42
+ authConfigName: 'azuread',
43
+ };
44
+ },
45
+ global: {
46
+ mocks: {
47
+ $fetchState: { pending: false },
48
+ $store: {
49
+ getters: {
50
+ currentStore: () => 'current_store',
51
+ 'current_store/schemaFor': jest.fn(),
52
+ 'current_store/all': jest.fn(),
53
+ 'i18n/t': (val: string) => val,
54
+ 'i18n/exists': jest.fn(),
58
55
  },
59
- $route: { query: { AS: '' }, params: { id: 'azure' } },
60
- $router: { applyQuery: jest.fn() },
56
+ dispatch: jest.fn()
61
57
  },
58
+ $route: { query: { AS: '' }, params: { id: 'azure' } },
59
+ $router: { applyQuery: jest.fn() },
62
60
  },
63
- propsData: {
64
- value: { applicationSecret: '' },
65
- mode: _EDIT,
66
- },
67
- });
61
+ },
62
+ propsData: {
63
+ value: { applicationSecret: '' },
64
+ mode: _EDIT,
65
+ },
66
+ });
67
+
68
+ describe('edit: azureAD should', () => {
69
+ let wrapper: any;
68
70
 
69
71
  beforeEach(() => {
70
72
  wrapper = mount(AzureAD, { ...requiredSetup() });
@@ -236,3 +238,184 @@ describe('edit: azureAD should', () => {
236
238
  expect(saveButton.disabled).toBe(testCase.result);
237
239
  });
238
240
  });
241
+
242
+ describe('edit: azureAD SSO logout should', () => {
243
+ let wrapper: any;
244
+
245
+ afterEach(() => {
246
+ wrapper.unmount();
247
+ });
248
+
249
+ it('not render SLO section when logoutAllSupported is false', () => {
250
+ wrapper = mount(AzureAD, { ...requiredSetup({ logoutAllSupported: false }) });
251
+ const sloSection = wrapper.find('[data-testid="azuread-sloType"]');
252
+
253
+ expect(sloSection.exists()).toBe(false);
254
+ });
255
+
256
+ it('render SLO section when logoutAllSupported is true', async() => {
257
+ wrapper = mount(AzureAD, { ...requiredSetup({ logoutAllSupported: true }) });
258
+ await nextTick();
259
+ const sloSection = wrapper.find('[data-testid="azuread-sloType"]');
260
+
261
+ expect(sloSection.exists()).toBe(true);
262
+ });
263
+
264
+ it('not render endSessionEndpoint field when sloType is rancher', async() => {
265
+ wrapper = mount(AzureAD, {
266
+ ...requiredSetup({ logoutAllSupported: true }),
267
+ data() {
268
+ return {
269
+ ...requiredSetup({ logoutAllSupported: true }).data(),
270
+ sloType: SLO_OPTION_VALUES.rancher,
271
+ };
272
+ },
273
+ });
274
+ await nextTick();
275
+ const endSessionEndpointField = wrapper.find('[data-testid="azuread-endSessionEndpoint"]');
276
+
277
+ expect(endSessionEndpointField.exists()).toBe(false);
278
+ });
279
+
280
+ it('render endSessionEndpoint field when sloType is all', async() => {
281
+ wrapper = mount(AzureAD, {
282
+ ...requiredSetup({ logoutAllSupported: true }),
283
+ data() {
284
+ return {
285
+ ...requiredSetup({ logoutAllSupported: true }).data(),
286
+ sloType: SLO_OPTION_VALUES.all,
287
+ };
288
+ },
289
+ });
290
+ await nextTick();
291
+ const endSessionEndpointField = wrapper.find('[data-testid="azuread-endSessionEndpoint"]');
292
+
293
+ expect(endSessionEndpointField.exists()).toBe(true);
294
+ });
295
+
296
+ it('render endSessionEndpoint field when sloType is both', async() => {
297
+ wrapper = mount(AzureAD, {
298
+ ...requiredSetup({ logoutAllSupported: true }),
299
+ data() {
300
+ return {
301
+ ...requiredSetup({ logoutAllSupported: true }).data(),
302
+ sloType: SLO_OPTION_VALUES.both,
303
+ };
304
+ },
305
+ });
306
+ await nextTick();
307
+ const endSessionEndpointField = wrapper.find('[data-testid="azuread-endSessionEndpoint"]');
308
+
309
+ expect(endSessionEndpointField.exists()).toBe(true);
310
+ });
311
+
312
+ it.each([
313
+ {
314
+ sloType: SLO_OPTION_VALUES.all, endSessionEndpoint: '', disabled: true
315
+ },
316
+ {
317
+ sloType: SLO_OPTION_VALUES.all, endSessionEndpoint: 'not-a-url', disabled: true
318
+ },
319
+ {
320
+ sloType: SLO_OPTION_VALUES.all, endSessionEndpoint: 'https://login.microsoftonline.com/tenant/oauth2/v2.0/logout', disabled: false
321
+ },
322
+ {
323
+ sloType: SLO_OPTION_VALUES.both, endSessionEndpoint: '', disabled: true
324
+ },
325
+ {
326
+ sloType: SLO_OPTION_VALUES.both, endSessionEndpoint: 'https://login.microsoftonline.com/tenant/oauth2/v2.0/logout', disabled: false
327
+ },
328
+ ])('has save button disabled=$disabled when sloType=$sloType and endSessionEndpoint=$endSessionEndpoint', async(testCase) => {
329
+ wrapper = mount(AzureAD, {
330
+ ...requiredSetup({
331
+ logoutAllSupported: true,
332
+ tenantId: validTenantId,
333
+ applicationId: validApplicationId,
334
+ applicationSecret: validAppSecret,
335
+ endSessionEndpoint: testCase.endSessionEndpoint,
336
+ }),
337
+ data() {
338
+ return {
339
+ isEnabling: true,
340
+ editConfig: false,
341
+ model: {
342
+ ...mockModel,
343
+ logoutAllSupported: true,
344
+ tenantId: validTenantId,
345
+ applicationId: validApplicationId,
346
+ applicationSecret: validAppSecret,
347
+ graphEndpoint: validGraphEndpoint,
348
+ tokenEndpoint: validTokenEndpoint,
349
+ authEndpoint: validAuthEndpoint,
350
+ endSessionEndpoint: testCase.endSessionEndpoint,
351
+ },
352
+ sloType: testCase.sloType,
353
+ serverSetting: null,
354
+ errors: [],
355
+ originalModel: null,
356
+ principals: [],
357
+ authConfigName: 'azuread',
358
+ };
359
+ },
360
+ });
361
+ await nextTick();
362
+ const saveButton = wrapper.find('[data-testid="form-save"]').element as HTMLInputElement;
363
+
364
+ expect(saveButton.disabled).toBe(testCase.disabled);
365
+ });
366
+
367
+ it('sets logoutAllEnabled=false and logoutAllForced=false when sloType changes to rancher', async() => {
368
+ wrapper = mount(AzureAD, {
369
+ ...requiredSetup({ logoutAllSupported: true }),
370
+ data() {
371
+ return {
372
+ ...requiredSetup({ logoutAllSupported: true }).data(),
373
+ sloType: SLO_OPTION_VALUES.all,
374
+ };
375
+ },
376
+ });
377
+ await nextTick();
378
+ wrapper.vm.sloType = SLO_OPTION_VALUES.rancher;
379
+ await nextTick();
380
+
381
+ expect(wrapper.vm.model.logoutAllEnabled).toBe(false);
382
+ expect(wrapper.vm.model.logoutAllForced).toBe(false);
383
+ expect(wrapper.vm.model.endSessionEndpoint).toBe('');
384
+ });
385
+
386
+ it('sets logoutAllEnabled=true and logoutAllForced=true when sloType changes to all', async() => {
387
+ wrapper = mount(AzureAD, {
388
+ ...requiredSetup({ logoutAllSupported: true }),
389
+ data() {
390
+ return {
391
+ ...requiredSetup({ logoutAllSupported: true }).data(),
392
+ sloType: SLO_OPTION_VALUES.rancher,
393
+ };
394
+ },
395
+ });
396
+ await nextTick();
397
+ wrapper.vm.sloType = SLO_OPTION_VALUES.all;
398
+ await nextTick();
399
+
400
+ expect(wrapper.vm.model.logoutAllEnabled).toBe(true);
401
+ expect(wrapper.vm.model.logoutAllForced).toBe(true);
402
+ });
403
+
404
+ it('sets logoutAllEnabled=true and logoutAllForced=false when sloType changes to both', async() => {
405
+ wrapper = mount(AzureAD, {
406
+ ...requiredSetup({ logoutAllSupported: true }),
407
+ data() {
408
+ return {
409
+ ...requiredSetup({ logoutAllSupported: true }).data(),
410
+ sloType: SLO_OPTION_VALUES.rancher,
411
+ };
412
+ },
413
+ });
414
+ await nextTick();
415
+ wrapper.vm.sloType = SLO_OPTION_VALUES.both;
416
+ await nextTick();
417
+
418
+ expect(wrapper.vm.model.logoutAllEnabled).toBe(true);
419
+ expect(wrapper.vm.model.logoutAllForced).toBe(false);
420
+ });
421
+ });
@@ -11,7 +11,7 @@ import { Checkbox } from '@components/Form/Checkbox';
11
11
  import AuthBanner from '@shell/components/auth/AuthBanner';
12
12
  import CopyToClipboardText from '@shell/components/CopyToClipboardText.vue';
13
13
  import AllowedPrincipals from '@shell/components/auth/AllowedPrincipals';
14
- import AuthConfig from '@shell/mixins/auth-config';
14
+ import AuthConfig, { SLO_OPTION_VALUES } from '@shell/mixins/auth-config';
15
15
  import { AZURE_MIGRATED } from '@shell/config/labels-annotations';
16
16
  import { get } from '@shell/utils/object';
17
17
  import AuthProviderWarningBanners from '@shell/edit/auth/AuthProviderWarningBanners';
@@ -76,6 +76,8 @@ export default {
76
76
  if ( this.value?.graphEndpoint ) {
77
77
  this.setInitialEndpoint(this.value.graphEndpoint);
78
78
  }
79
+
80
+ await this.mixinFetch();
79
81
  },
80
82
 
81
83
  data() {
@@ -83,6 +85,7 @@ export default {
83
85
  isGroupMembershipFilterEnabled: !!this.value.groupMembershipFilter,
84
86
  endpoint: 'standard',
85
87
  oldEndpoint: false,
88
+ SLO_OPTION_VALUES,
86
89
 
87
90
  // Storing the applicationSecret is necessary because norman doesn't support returning secrets and when we
88
91
  // override the steve authconfig with a norman config the applicationSecret is lost
@@ -95,6 +98,7 @@ export default {
95
98
  { path: 'graphEndpoint', rules: ['graphEndpointRequired', 'graphEndpointMustBeURL'] },
96
99
  { path: 'tokenEndpoint', rules: ['tokenEndpointRequired', 'tokenEndpointMustBeURL'] },
97
100
  { path: 'authEndpoint', rules: ['authEndpointRequired', 'authEndpointMustBeURL'] },
101
+ { path: 'endSessionEndpoint', rules: ['endSessionEndpointRequiredAndValid'] },
98
102
  ]
99
103
  };
100
104
  },
@@ -103,17 +107,18 @@ export default {
103
107
  // Cannot pass this.model as a rootObject because it is undefined at that point, so had to use a workaround
104
108
  fvExtraRules() {
105
109
  return {
106
- tenantIdRequired: this.modelFieldRequired('tenantId', 'authConfig.azuread.tenantId.label'),
107
- applicationIdRequired: this.modelFieldRequired('applicationId', 'authConfig.azuread.applicationId.label'),
108
- applicationSecretRequired: this.applicationSecretRequired(),
109
- endpointRequired: this.modelFieldRequired('endpoint', 'authConfig.azuread.endpoint.label'),
110
- endpointMustBeURL: this.modelFieldURL('endpoint'),
111
- graphEndpointRequired: this.modelFieldRequired('graphEndpoint', 'authConfig.azuread.graphEndpoint.label'),
112
- graphEndpointMustBeURL: this.modelFieldURL('graphEndpoint'),
113
- tokenEndpointRequired: this.modelFieldRequired('tokenEndpoint', 'authConfig.azuread.tokenEndpoint.label'),
114
- tokenEndpointMustBeURL: this.modelFieldURL('tokenEndpoint'),
115
- authEndpointRequired: this.modelFieldRequired('authEndpoint', 'authConfig.azuread.authEndpoint.label'),
116
- authEndpointMustBeURL: this.modelFieldURL('authEndpoint')
110
+ tenantIdRequired: this.modelFieldRequired('tenantId', 'authConfig.azuread.tenantId.label'),
111
+ applicationIdRequired: this.modelFieldRequired('applicationId', 'authConfig.azuread.applicationId.label'),
112
+ applicationSecretRequired: this.applicationSecretRequired(),
113
+ endpointRequired: this.modelFieldRequired('endpoint', 'authConfig.azuread.endpoint.label'),
114
+ endpointMustBeURL: this.modelFieldURL('endpoint'),
115
+ graphEndpointRequired: this.modelFieldRequired('graphEndpoint', 'authConfig.azuread.graphEndpoint.label'),
116
+ graphEndpointMustBeURL: this.modelFieldURL('graphEndpoint'),
117
+ tokenEndpointRequired: this.modelFieldRequired('tokenEndpoint', 'authConfig.azuread.tokenEndpoint.label'),
118
+ tokenEndpointMustBeURL: this.modelFieldURL('tokenEndpoint'),
119
+ authEndpointRequired: this.modelFieldRequired('authEndpoint', 'authConfig.azuread.authEndpoint.label'),
120
+ authEndpointMustBeURL: this.modelFieldURL('authEndpoint'),
121
+ endSessionEndpointRequiredAndValid: this.endSessionEndpointRule(),
117
122
  };
118
123
  },
119
124
 
@@ -165,7 +170,29 @@ export default {
165
170
  },
166
171
  editMemberConfig() {
167
172
  return this.model.enabled && !this.isEnabling && !this.editConfig;
168
- }
173
+ },
174
+
175
+ isLogoutAllSupported() {
176
+ return this.model?.logoutAllSupported;
177
+ },
178
+
179
+ sloOptions() {
180
+ return [
181
+ { value: SLO_OPTION_VALUES.rancher, label: this.t('authConfig.slo.sloOptions.onlyRancher', { name: this.model?.nameDisplay }) },
182
+ { value: SLO_OPTION_VALUES.all, label: this.t('authConfig.slo.sloOptions.logoutAll', { name: this.model?.nameDisplay }) },
183
+ { value: SLO_OPTION_VALUES.both, label: this.t('authConfig.slo.sloOptions.choose') },
184
+ ];
185
+ },
186
+
187
+ sloTypeText() {
188
+ const sloOptionSelected = this.sloOptions.find((item) => item.value === this.sloType);
189
+
190
+ return sloOptionSelected?.label || '';
191
+ },
192
+
193
+ sloEndSessionEndpointUiEnabled() {
194
+ return this.sloType === SLO_OPTION_VALUES.all || this.sloType === SLO_OPTION_VALUES.both;
195
+ },
169
196
  },
170
197
 
171
198
  watch: {
@@ -179,6 +206,25 @@ export default {
179
206
  }
180
207
  },
181
208
 
209
+ // sloType is defined on shell/mixins/auth-config.js
210
+ sloType(neu) {
211
+ switch (neu) {
212
+ case SLO_OPTION_VALUES.rancher:
213
+ this.model.logoutAllEnabled = false;
214
+ this.model.logoutAllForced = false;
215
+ this.model.endSessionEndpoint = '';
216
+ break;
217
+ case SLO_OPTION_VALUES.all:
218
+ this.model.logoutAllEnabled = true;
219
+ this.model.logoutAllForced = true;
220
+ break;
221
+ case SLO_OPTION_VALUES.both:
222
+ this.model.logoutAllEnabled = true;
223
+ this.model.logoutAllForced = false;
224
+ break;
225
+ }
226
+ },
227
+
182
228
  model: {
183
229
  deep: true,
184
230
  handler() {
@@ -294,7 +340,21 @@ export default {
294
340
 
295
341
  return rule(this.model[path]);
296
342
  };
297
- }
343
+ },
344
+
345
+ endSessionEndpointRule() {
346
+ return () => {
347
+ if (!this.isLogoutAllSupported || !this.sloEndSessionEndpointUiEnabled) {
348
+ return undefined;
349
+ }
350
+ if (!this.model.endSessionEndpoint) {
351
+ return this.t('validation.required', { key: this.t('authConfig.azuread.endSessionEndpoint.title') });
352
+ }
353
+ const rule = formRulesGenerator(this.$store.getters['i18n/t'], {}).url;
354
+
355
+ return rule(this.model.endSessionEndpoint);
356
+ };
357
+ },
298
358
  }
299
359
  };
300
360
  </script>
@@ -348,6 +408,14 @@ export default {
348
408
  <td>{{ t(`authConfig.azuread.authEndpoint.label`) }}:</td>
349
409
  <td>{{ model.authEndpoint }}</td>
350
410
  </tr>
411
+ <tr v-if="isLogoutAllSupported">
412
+ <td>{{ t('authConfig.slo.sloTitle') }}:</td>
413
+ <td>{{ sloTypeText }}</td>
414
+ </tr>
415
+ <tr v-if="isLogoutAllSupported && sloEndSessionEndpointUiEnabled">
416
+ <td>{{ t('authConfig.azuread.endSessionEndpoint.title') }}:</td>
417
+ <td>{{ model.endSessionEndpoint }}</td>
418
+ </tr>
351
419
  </template>
352
420
  <template
353
421
  v-if="needsUpdate"
@@ -521,6 +589,46 @@ export default {
521
589
  </div>
522
590
  </div>
523
591
  </div>
592
+
593
+ <!-- SLO logout -->
594
+ <div
595
+ v-if="isLogoutAllSupported"
596
+ class="mb-20"
597
+ >
598
+ <div class="row">
599
+ <div class="col span-12">
600
+ <h3>{{ t('authConfig.slo.sloTitle') }}</h3>
601
+ </div>
602
+ </div>
603
+ <div class="row">
604
+ <div class="col span-4">
605
+ <RadioGroup
606
+ v-model:value="sloType"
607
+ :mode="mode"
608
+ :options="sloOptions"
609
+ :disabled="!model.logoutAllSupported"
610
+ name="sloTypeRadio"
611
+ data-testid="azuread-sloType"
612
+ />
613
+ </div>
614
+ </div>
615
+ <div
616
+ v-if="sloEndSessionEndpointUiEnabled"
617
+ class="row mt-20"
618
+ >
619
+ <div class="col span-6">
620
+ <LabeledInput
621
+ v-model:value="model.endSessionEndpoint"
622
+ :tooltip="t('authConfig.azuread.endSessionEndpoint.tooltip', { tenantId: `${ tenantId || 'tenant-id' }` }, true)"
623
+ :label="t('authConfig.azuread.endSessionEndpoint.title')"
624
+ :mode="mode"
625
+ :rules="fvGetAndReportPathRules('endSessionEndpoint')"
626
+ :required="true"
627
+ data-testid="azuread-endSessionEndpoint"
628
+ />
629
+ </div>
630
+ </div>
631
+ </div>
524
632
  </template>
525
633
  </CruResource>
526
634
  </div>
@@ -15,7 +15,7 @@ import { RadioGroup } from '@components/Form/Radio';
15
15
  import { Checkbox } from '@components/Form/Checkbox';
16
16
  import { BASE_SCOPES } from '@shell/store/auth';
17
17
  import CopyToClipboardText from '@shell/components/CopyToClipboardText.vue';
18
- import isUrl from 'is-url';
18
+ import { isValidUrl } from '@shell/utils/validators/setting';
19
19
 
20
20
  const PKCE_S256 = 'S256';
21
21
 
@@ -105,7 +105,7 @@ export default {
105
105
  }
106
106
 
107
107
  // make sure that if SLO options are enabled on radio group, field "endSessionEndpoint" is required
108
- if (this.isLogoutAllSupported && this.sloEndSessionEndpointUiEnabled && (!this.model.endSessionEndpoint || !isUrl(this.model.endSessionEndpoint))) {
108
+ if (this.isLogoutAllSupported && this.sloEndSessionEndpointUiEnabled && (!this.model.endSessionEndpoint || !isValidUrl(this.model.endSessionEndpoint))) {
109
109
  return false;
110
110
  }
111
111
 
@@ -43,7 +43,9 @@ export default {
43
43
  const backend = get(this.value.spec, this.value.defaultBackendPath);
44
44
 
45
45
  this.serviceName = get(backend, this.value.serviceNamePath) || '';
46
- this.servicePort = get(backend, this.value.servicePortPath) || '';
46
+ this.servicePort = get(backend, this.value.servicePortPath) ||
47
+ get(backend, this.value.servicePortNamePath) ||
48
+ '';
47
49
  },
48
50
  computed: {
49
51
  isView() {
@@ -75,10 +77,14 @@ export default {
75
77
  },
76
78
  methods: {
77
79
  update() {
78
- const backend = get(this.value.spec, this.value.defaultBackendPath) || {};
80
+ // Fresh object so the old port path (name vs number) doesn't linger.
81
+ const backend = {};
82
+ const parsed = Number.parseInt(this.servicePort);
83
+ const servicePort = Number.isNaN(parsed) ? this.servicePort : parsed;
84
+ const portPath = typeof servicePort === 'number' ? this.value.servicePortPath : this.value.servicePortNamePath;
79
85
 
80
86
  set(backend, this.value.serviceNamePath, this.serviceName);
81
- set(backend, this.value.servicePortPath, this.servicePort);
87
+ set(backend, portPath, servicePort);
82
88
  set(this.value.spec, this.value.defaultBackendPath, backend);
83
89
 
84
90
  this.$emit('update:value', this.value);
@@ -119,10 +125,12 @@ export default {
119
125
  class="col span-3"
120
126
  :style="{'margin-right': '0px'}"
121
127
  >
128
+ <!-- :required drives the asterisk; portRequired doesn't have .name === 'required' -->
122
129
  <LabeledInput
123
130
  v-if="portOptions.length === 0 || isView"
124
- v-model:value.number="servicePort"
131
+ v-model:value="servicePort"
125
132
  :mode="mode"
133
+ :required="true"
126
134
  :label="t('ingress.defaultBackend.port.label')"
127
135
  :placeholder="t('ingress.defaultBackend.port.placeholder')"
128
136
  :rules="rules.port"
@@ -132,6 +140,7 @@ export default {
132
140
  v-else
133
141
  v-model:value="servicePort"
134
142
  :mode="mode"
143
+ :required="true"
135
144
  :options="portOptions"
136
145
  :label="t('ingress.defaultBackend.port.label')"
137
146
  :placeholder="t('ingress.defaultBackend.port.placeholder')"
@@ -79,20 +79,24 @@ export default {
79
79
  set(this.value, 'path', this.value.path || '');
80
80
  set(this.value, 'pathType', this.value.pathType || this.pathTypes[0]);
81
81
  set(this.value.backend, this.ingress.serviceNamePath, get(this.value.backend, this.ingress.serviceNamePath) || '');
82
- set(this.value.backend, this.ingress.servicePortPath, get(this.value.backend, this.ingress.servicePortPath) || '');
83
82
 
84
83
  this.serviceName = get(this.value.backend, this.ingress.serviceNamePath);
85
- this.servicePort = get(this.value.backend, this.ingress.servicePortPath);
84
+ this.servicePort = get(this.value.backend, this.ingress.servicePortPath) ||
85
+ get(this.value.backend, this.ingress.servicePortNamePath) ||
86
+ '';
86
87
  },
87
88
  methods: {
88
89
  update() {
89
- const servicePort = Number.parseInt(this.servicePort) || this.servicePort;
90
+ const parsed = Number.parseInt(this.servicePort);
91
+ const servicePort = Number.isNaN(parsed) ? this.servicePort : parsed;
90
92
  const serviceName = this.serviceName.label || this.serviceName;
91
93
  const out = {
92
94
  id: this.value.id, backend: {}, path: this.path, pathType: this.pathType
93
95
  };
94
96
 
95
- set(out.backend, this.ingress.servicePortPath, servicePort);
97
+ const portPath = typeof servicePort === 'number' ? this.ingress.servicePortPath : this.ingress.servicePortNamePath;
98
+
99
+ set(out.backend, portPath, servicePort);
96
100
  set(out.backend, this.ingress.serviceNamePath, serviceName);
97
101
 
98
102
  this.$emit('update:value', out);