@rancher/shell 3.0.5-rc.9 → 3.0.6

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 (55) hide show
  1. package/assets/translations/en-us.yaml +11 -5
  2. package/components/FilterPanel.vue +8 -1
  3. package/components/PaginatedResourceTable.vue +7 -2
  4. package/components/PodSecurityAdmission.vue +2 -0
  5. package/components/PromptRemove.vue +5 -0
  6. package/components/ResourceTable.vue +30 -20
  7. package/components/form/ProjectMemberEditor.vue +2 -0
  8. package/components/nav/Header.vue +6 -5
  9. package/config/product/explorer.js +5 -5
  10. package/config/store.js +2 -0
  11. package/config/uiplugins.js +1 -1
  12. package/core/plugin.ts +15 -3
  13. package/core/types.ts +30 -7
  14. package/dialog/SloDialog.vue +1 -1
  15. package/edit/auth/oidc.vue +106 -6
  16. package/edit/auth/saml.vue +5 -5
  17. package/edit/provisioning.cattle.io.cluster/tabs/AddOnAdditionalManifest.vue +1 -0
  18. package/edit/provisioning.cattle.io.cluster/tabs/AddOnConfig.vue +1 -0
  19. package/edit/provisioning.cattle.io.cluster/tabs/Basics.vue +1 -0
  20. package/edit/provisioning.cattle.io.cluster/tabs/etcd/S3Config.vue +1 -0
  21. package/edit/provisioning.cattle.io.cluster/tabs/registries/index.vue +2 -0
  22. package/edit/provisioning.cattle.io.cluster/tabs/upgrade/DrainOptions.vue +6 -0
  23. package/initialize/install-plugins.js +1 -3
  24. package/list/catalog.cattle.io.clusterrepo.vue +2 -2
  25. package/mixins/__tests__/auth-config.test.ts +4 -6
  26. package/mixins/__tests__/chart.test.ts +1 -1
  27. package/mixins/auth-config.js +33 -10
  28. package/mixins/chart.js +1 -1
  29. package/models/management.cattle.io.cluster.js +1 -1
  30. package/models/workload.js +1 -1
  31. package/package.json +2 -2
  32. package/pages/auth/login.vue +8 -3
  33. package/pages/auth/logout.vue +6 -5
  34. package/pages/c/_cluster/apps/charts/AppChartCardFooter.vue +6 -0
  35. package/pages/c/_cluster/apps/charts/StatusLabel.vue +4 -3
  36. package/pages/c/_cluster/apps/charts/index.vue +12 -11
  37. package/pages/c/_cluster/explorer/tools/__tests__/index.test.ts +1 -1
  38. package/pages/c/_cluster/explorer/tools/index.vue +1 -1
  39. package/plugins/axios.js +3 -2
  40. package/plugins/dashboard-store/resource-class.js +12 -1
  41. package/plugins/ember-cookie.js +7 -3
  42. package/plugins/steve/steve-pagination-utils.ts +14 -22
  43. package/plugins/steve/subscribe.js +4 -2
  44. package/scripts/test-plugins-build.sh +4 -6
  45. package/scripts/typegen.sh +2 -0
  46. package/store/__tests__/cookies.test.ts +72 -0
  47. package/store/auth.js +34 -11
  48. package/store/cookies.ts +30 -0
  49. package/store/prefs.js +10 -5
  50. package/types/shell/index.d.ts +183 -11
  51. package/types/store/vuex.d.ts +2 -1
  52. package/types/vue-shim.d.ts +2 -5
  53. package/utils/auth.js +1 -1
  54. package/utils/pagination-utils.ts +11 -2
  55. package/utils/cookie-universal.js +0 -10
@@ -478,6 +478,12 @@ addProjectMemberDialog:
478
478
  title: Add Project Member
479
479
 
480
480
  authConfig:
481
+ slo:
482
+ sloTitle: Log Out behavior
483
+ sloOptions:
484
+ onlyRancher: Log out of Rancher and not {name}
485
+ logoutAll: Log out of Rancher and {name} (includes all other applications registered with {name})
486
+ choose: Allow the user to choose one of the above in an additional log out step
481
487
  accessMode:
482
488
  label: 'Configure who should be able to login and use {vendor}'
483
489
  required: Restrict access to only the authorized users & groups
@@ -627,11 +633,6 @@ authConfig:
627
633
  UID: UID Field
628
634
  adfs: Configure an AD FS account
629
635
  api: '{vendor} API Host'
630
- sloTitle: Log Out behavior
631
- sloOptions:
632
- onlyRancher: Log out of Rancher and not {name}
633
- logoutAll: Log out of Rancher and {name} (includes all other applications registered with {name})
634
- choose: Allow the user to choose one of the above in an additional log out step
635
636
  cert:
636
637
  label: Certificate
637
638
  placeholder: Paste in the certificate, starting with -----BEGIN CERTIFICATE-----
@@ -698,6 +699,9 @@ authConfig:
698
699
  title: Are you sure? This update is irreversible.
699
700
  body: '<p><b>You may need to make some additional changes</b>. Please ensure the Azure AD app has the Directory.Read.All <b>Application</b> permission added to Microsoft Graph.<br> If any endpoints were customized while configuring Azure AD authentication in Rancher, they will not be automatically updated. </p>'
700
701
  oidc:
702
+ endSessionEndpoint:
703
+ title: End Session Endpoint
704
+ tooltip: Provider specific URL used for logging a user out of their session
701
705
  genericoidc: Configure an OIDC account
702
706
  keycloakoidc: Configure a Keycloak OIDC account
703
707
  cognito: Configure an Amazon Cognito account
@@ -7566,6 +7570,8 @@ model:
7566
7570
  saml: SAML
7567
7571
  oauth: OAuth
7568
7572
  oidc: OIDC
7573
+ keycloakoidc: Keycloak
7574
+ genericoidc: OIDC provider
7569
7575
  cognito: Amazon Cognito
7570
7576
  name:
7571
7577
  keycloak: Keycloak (SAML)
@@ -20,6 +20,8 @@ type FilterOption = {
20
20
  componentProps?: Record<string, unknown>;
21
21
  /** Label to show next to the checkbox, or a custom component next to the checkbox */
22
22
  label?: string | { component: ComponentType; componentProps: Record<string, unknown>; };
23
+ /** Tooltip to be displayed above the checkbox on hover */
24
+ labelTooltip?: string;
23
25
  };
24
26
 
25
27
  /**
@@ -97,7 +99,12 @@ const updateFilter = (key: string, value: string[]) => {
97
99
  @update:value="updateFilter(filter.key, $event)"
98
100
  >
99
101
  <template #label>
100
- <span v-if="typeof option.label === 'string'">{{ option.label }}</span>
102
+ <span
103
+ v-if="typeof option.label === 'string'"
104
+ v-clean-tooltip="{content: option.labelTooltip, delay: { show: 1000 }}"
105
+ >
106
+ {{ option.label }}
107
+ </span>
101
108
  <component
102
109
  :is="option.label.component"
103
110
  v-else
@@ -2,6 +2,7 @@
2
2
  import { defineComponent } from 'vue';
3
3
  import ResourceFetch from '@shell/mixins/resource-fetch';
4
4
  import ResourceTable from '@shell/components/ResourceTable.vue';
5
+ import { VuexStore } from '@shell/types/store/vuex';
5
6
 
6
7
  /**
7
8
  * This is meant to enable ResourceList like capabilities outside of List pages / components
@@ -130,13 +131,17 @@ export default defineComponent({
130
131
  safeHeaders(): any[] {
131
132
  const customHeaders: any[] = this.canPaginate ? this.paginationHeaders : this.headers;
132
133
 
133
- return customHeaders || this.$store.getters['type-map/headersFor'](this.schema, this.canPaginate);
134
+ const $store = this.$store as VuexStore;
135
+
136
+ return customHeaders || $store.getters['type-map/headersFor'](this.schema, this.canPaginate);
134
137
  }
135
138
  },
136
139
 
137
140
  methods: {
138
141
  clearSelection() {
139
- this.$refs.table.clearSelection();
142
+ const table = this.$refs.table as { clearSelection: () => void };
143
+
144
+ table.clearSelection();
140
145
  },
141
146
  }
142
147
  });
@@ -222,6 +222,7 @@ export default defineComponent({
222
222
  <Checkbox
223
223
  v-if="!labelsAlwaysActive"
224
224
  v-model:value="psaControl.active"
225
+ :mode="mode"
225
226
  :data-testid="componentTestid + '--psaControl-' + i + '-active'"
226
227
  :label="level"
227
228
  :label-key="`podSecurityAdmission.labels.${ level }`"
@@ -281,6 +282,7 @@ export default defineComponent({
281
282
  <span class="col span-2">
282
283
  <Checkbox
283
284
  v-model:value="psaExemptionsControl.active"
285
+ :mode="mode"
284
286
  :data-testid="componentTestid + '--psaExemptionsControl-' + i + '-active'"
285
287
  :label="dimension"
286
288
  :label-key="`podSecurityAdmission.labels.${ dimension }`"
@@ -384,6 +384,7 @@ export default {
384
384
  >
385
385
  <span
386
386
  v-clean-html="t('promptRemove.confirmName', { nameToMatch: escapeHtml(nameToMatch) }, true)"
387
+ class="confirm-text"
387
388
  />
388
389
  </div>
389
390
  </div>
@@ -472,5 +473,9 @@ export default {
472
473
  flex: 1;
473
474
  }
474
475
  }
476
+
477
+ .confirm-text b {
478
+ user-select: all;
479
+ }
475
480
  }
476
481
  </style>
@@ -317,6 +317,7 @@ export default {
317
317
  // add custom table columns provided by the extensions ExtensionPoint.TABLE_COL hook
318
318
  // gate it so that we prevent errors on older versions of dashboard
319
319
  if (this.$store.$plugin?.getUIConfig) {
320
+ // { column: TableColumn, paginationColumn: PaginationTableColumn }[]
320
321
  const extensionCols = getApplicableExtensionEnhancements(this, ExtensionPoint.TABLE_COL, TableColumnLocation.RESOURCE, this.$route);
321
322
 
322
323
  // Try and insert the columns before the Age column
@@ -339,27 +340,36 @@ export default {
339
340
  }
340
341
 
341
342
  // adding extension defined cols to the correct header config
342
- extensionCols.forEach((col) => {
343
- if (this.externalPaginationEnabled) {
344
- // validate that the required settings are supplied to enable search and sort server-side
345
- // these do not check other invalid scenarios like a path is a string but to a model property, or that the field supports sort/search via api (some basic non-breaking checks are done further on)
346
- if (
347
- col.search !== false && // search is explicitly disabled
348
- (typeof col.search !== 'string' && !Array.isArray(col.search)) && // primary property path to search on
349
- typeof col.value !== 'string' // secondary property path to search on
350
- ) {
351
- console.warn(`Unable to support server-side search for extension provided column "${ col.name || col.label || col.labelKey }" (column must provide \`search\` or \`value\` property containing a path to a property in the resource. search can be an array).`); // eslint-disable-line no-console
352
-
353
- col.search = false;
354
- }
343
+ extensionCols.forEach((config) => {
344
+ let { column: col, paginationColumn } = config;
355
345
 
356
- if (
357
- col.sort !== false && // sort is explicitly disabled
358
- (typeof col.sort !== 'string' && !Array.isArray(col.sort)) // primary property path to sort on
359
- ) {
360
- console.warn(`Unable to support server-side sort for extension provided column "${ col.name || col.label || col.labelKey }" (column must provide \`sort\` property containing a path to a property, or array of paths, in the resource)`); // eslint-disable-line no-console
361
-
362
- col.sort = false;
346
+ if (this.externalPaginationEnabled) {
347
+ if (paginationColumn) {
348
+ // Use the pagination column, no need to
349
+ col = paginationColumn;
350
+ } else {
351
+ // Attempt to fall back on the single column
352
+
353
+ // validate that the required settings are supplied to enable search and sort server-side
354
+ // these do not check other invalid scenarios like a path is a string but to a model property, or that the field supports sort/search via api (some basic non-breaking checks are done further on)
355
+ if (
356
+ col.search !== false && // search is explicitly disabled
357
+ (typeof col.search !== 'string' && !Array.isArray(col.search)) && // primary property path to search on
358
+ typeof col.value !== 'string' // secondary property path to search on
359
+ ) {
360
+ console.warn(`Unable to support server-side search for extension provided column "${ col.name || col.label || col.labelKey }" (column must provide \`search\` or \`value\` property containing a path to a property in the resource. search can be an array).`); // eslint-disable-line no-console
361
+
362
+ col.search = false;
363
+ }
364
+
365
+ if (
366
+ col.sort !== false && // sort is explicitly disabled
367
+ (typeof col.sort !== 'string' && !Array.isArray(col.sort)) // primary property path to sort on
368
+ ) {
369
+ console.warn(`Unable to support server-side sort for extension provided column "${ col.name || col.label || col.labelKey }" (column must provide \`sort\` property containing a path to a property, or array of paths, in the resource)`); // eslint-disable-line no-console
370
+
371
+ col.sort = false;
372
+ }
363
373
  }
364
374
  }
365
375
 
@@ -286,6 +286,7 @@ export default {
286
286
  <template v-slot:body>
287
287
  <RadioGroup
288
288
  v-model:value="value.permissionGroup"
289
+ :mode="mode"
289
290
  data-testid="permission-options"
290
291
  :options="options"
291
292
  name="permission-group"
@@ -301,6 +302,7 @@ export default {
301
302
  >
302
303
  <Checkbox
303
304
  v-model:value="permission.value"
305
+ :mode="mode"
304
306
  :data-testid="`custom-permission-${i}`"
305
307
  :disabled="permission.locked"
306
308
  class="mb-5"
@@ -29,6 +29,7 @@ import {
29
29
  RcDropdownSeparator,
30
30
  RcDropdownTrigger
31
31
  } from '@components/RcDropdown';
32
+ import { SLO_AUTH_PROVIDERS } from '@shell/store/auth';
32
33
 
33
34
  export default {
34
35
 
@@ -99,10 +100,10 @@ export default {
99
100
  'showWorkspaceSwitcher'
100
101
  ]),
101
102
 
102
- samlAuthProviderEnabled() {
103
+ sloAuthProviderEnabled() {
103
104
  const publicAuthProviders = this.$store.getters['rancher/all']('authProvider');
104
105
 
105
- return publicAuthProviders.find((authProvider) => configType[authProvider.id] === 'saml') || {};
106
+ return publicAuthProviders.find((authProvider) => SLO_AUTH_PROVIDERS.includes(configType[authProvider?.id])) || {};
106
107
  },
107
108
 
108
109
  shouldShowSloLogoutModal() {
@@ -111,7 +112,7 @@ export default {
111
112
  return false;
112
113
  }
113
114
 
114
- const { logoutAllSupported, logoutAllEnabled, logoutAllForced } = this.samlAuthProviderEnabled;
115
+ const { logoutAllSupported, logoutAllEnabled, logoutAllForced } = this.sloAuthProviderEnabled;
115
116
 
116
117
  return logoutAllSupported && logoutAllEnabled && !logoutAllForced;
117
118
  },
@@ -276,8 +277,8 @@ export default {
276
277
  showSloModal() {
277
278
  this.$store.dispatch('management/promptModal', {
278
279
  component: 'SloDialog',
279
- componentProps: { authProvider: this.samlAuthProviderEnabled },
280
- modalWidth: '500px'
280
+ componentProps: { authProvider: this.sloAuthProviderEnabled },
281
+ modalWidth: '600px'
281
282
  });
282
283
  },
283
284
  // Sizes the product area of the header such that it shrinks to ensure the whole header bar can be shown
@@ -294,7 +294,7 @@ export function init(store) {
294
294
  STEVE_NAMESPACE_COL,
295
295
  {
296
296
  ...INGRESS_TARGET,
297
- sort: 'spec.rules[0].host', // Pending API Support - BUG - https://github.com/rancher/rancher/issues/50526
297
+ sort: 'spec.rules[0].host',
298
298
  search: false, // This is broken in normal world, so disable here
299
299
  },
300
300
  {
@@ -359,10 +359,10 @@ export function init(store) {
359
359
  STEVE_STATE_COL,
360
360
  STEVE_NAME_COL,
361
361
  STEVE_NAMESPACE_COL,
362
- HPA_REFERENCE, // Pending API Support - BUG - https://github.com/rancher/rancher/issues/50527
363
- MIN_REPLICA, // Pending API Support - BUG - https://github.com/rancher/rancher/issues/50527
364
- MAX_REPLICA, // Pending API Support - BUG - https://github.com/rancher/rancher/issues/50527
365
- CURRENT_REPLICA, // Pending API Support - BUG - https://github.com/rancher/rancher/issues/50527
362
+ HPA_REFERENCE,
363
+ MIN_REPLICA,
364
+ MAX_REPLICA,
365
+ CURRENT_REPLICA,
366
366
  STEVE_AGE_COL
367
367
  ]
368
368
  );
package/config/store.js CHANGED
@@ -39,6 +39,7 @@ let store = {};
39
39
  resolveStoreModules(require('../store/customisation.js'), 'customisation.js');
40
40
  resolveStoreModules(require('../store/cru-resource.ts'), 'cru-resource.ts');
41
41
  resolveStoreModules(require('../store/notifications.ts'), 'notifications.ts');
42
+ resolveStoreModules(require('../store/cookies.ts'), 'cookies.ts');
42
43
 
43
44
  // If the environment supports hot reloading...
44
45
 
@@ -69,6 +70,7 @@ let store = {};
69
70
  '../store/customisation.js',
70
71
  '../store/cru-resource.ts',
71
72
  '../store/notifications.ts',
73
+ '../store/cookies.ts',
72
74
  ], () => {
73
75
  // Update `root.modules` with the latest definitions.
74
76
  updateModules();
@@ -107,7 +107,7 @@ export function uiPluginAnnotation(chart, name) {
107
107
  /**
108
108
  * Parse the Rancher version string
109
109
  */
110
- function parseRancherVersion(v) {
110
+ export function parseRancherVersion(v) {
111
111
  let parsedVersion = semver.coerce(v)?.version;
112
112
  const splitArr = parsedVersion?.split('.');
113
113
 
package/core/plugin.ts CHANGED
@@ -16,6 +16,7 @@ import {
16
16
  ModelExtensionConstructor,
17
17
  PluginRouteRecordRaw, RegisterStore, UnregisterStore, CoreStoreSpecifics, CoreStoreConfig,
18
18
  NavHooks, OnNavToPackage, OnNavAwayFromPackage, OnLogIn, OnLogOut,
19
+ PaginationTableColumn,
19
20
  ExtensionEnvironment
20
21
  } from './types';
21
22
  import coreStore, { coreStoreModule, coreStoreState } from '@shell/plugins/dashboard-store';
@@ -247,10 +248,21 @@ export class Plugin implements IPlugin {
247
248
  }
248
249
 
249
250
  /**
250
- * Adds a new column to a table on the UI
251
+ * Adds a new column to a ResourceTable
252
+ *
253
+ * @param where
254
+ * @param when
255
+ * @param action
256
+ * @param column
257
+ * The information required to show a header and values for a column in a table
258
+ * @param paginationColumn
259
+ * As per `column`, but is used where server-side pagination is enabled
251
260
  */
252
- addTableColumn(where: string, when: LocationConfig | string, column: TableColumn): void {
253
- this._addUIConfig(ExtensionPoint.TABLE_COL, where, when, column);
261
+ addTableColumn(where: string, when: LocationConfig | string, column: TableColumn, paginationColumn?: PaginationTableColumn): void {
262
+ this._addUIConfig(ExtensionPoint.TABLE_COL, where, when, {
263
+ column,
264
+ paginationColumn
265
+ });
254
266
  }
255
267
 
256
268
  setHomePage(component: any) {
package/core/types.ts CHANGED
@@ -130,9 +130,6 @@ export type Card = {
130
130
  component: Function;
131
131
  };
132
132
 
133
- // Duplication of HeaderOptions?
134
- export type TableColumn = any;
135
-
136
133
  /** Definition of a tab (options that can be passed when defining an extension tab enhancement) */
137
134
  export type Tab = {
138
135
  name: string;
@@ -281,6 +278,9 @@ export interface ProductOptions {
281
278
  // typeStoreMap: string;
282
279
  }
283
280
 
281
+ /**
282
+ * Configuration required to show a header in a ResourceTable
283
+ */
284
284
  export interface HeaderOptions {
285
285
  /**
286
286
  * Name of the header. This should be unique.
@@ -305,7 +305,7 @@ export interface HeaderOptions {
305
305
  /**
306
306
  * A string which represents the path to access the value from the row object which we'll use to sort i.e. `row.meta.value`
307
307
  */
308
- sort?: string | string[];
308
+ sort?: string | string[] | boolean;
309
309
 
310
310
  /**
311
311
  * A string which represents the path to access the value from the row object which we'll use to search i.e. `row.meta.value`.
@@ -336,6 +336,21 @@ export interface HeaderOptions {
336
336
  getValue?: (row: any) => string | number | null | undefined;
337
337
  }
338
338
 
339
+ /**
340
+ * Configuration required to show a header in a ResourceTable when server-side pagination is enable
341
+ */
342
+ export type PaginationHeaderOptions = Omit<HeaderOptions, 'getValue'>
343
+
344
+ /**
345
+ * External extension configuration for @HeaderOptions
346
+ */
347
+ export type TableColumn = HeaderOptions;
348
+
349
+ /**
350
+ * External extension configuration for @PaginationHeaderOptions
351
+ */
352
+ export type PaginationTableColumn = PaginationHeaderOptions;
353
+
339
354
  export interface ConfigureTypeOptions {
340
355
  /**
341
356
  * Override for the create button string on a list view
@@ -621,9 +636,17 @@ export interface IPlugin {
621
636
  addCard(where: CardLocation | string, when: LocationConfig | string, action: Card): void;
622
637
 
623
638
  /**
624
- * Adds a new column to the SortableTable component
625
- */
626
- addTableColumn(where: TableColumnLocation | string, when: LocationConfig | string, action: TableColumn): void;
639
+ * Adds a new column to a ResourceTable
640
+ *
641
+ * @param where
642
+ * @param when
643
+ * @param action
644
+ * @param column
645
+ * The information required to show a header and values for a column in a table
646
+ * @param paginationColumn
647
+ * As per `column`, but is used where server-side pagination is enabled
648
+ */
649
+ addTableColumn(where: TableColumnLocation | string, when: LocationConfig | string, column: TableColumn, paginationColumn?: TableColumn): void;
627
650
 
628
651
  /**
629
652
  * Set the component to use for the landing home page
@@ -17,7 +17,7 @@ export default {
17
17
 
18
18
  computed: {
19
19
  name() {
20
- return this.authProvider?.nameDisplay;
20
+ return this.t(`model.authConfig.provider."${ this.authProvider?.id }"`);
21
21
  }
22
22
  },
23
23
 
@@ -1,7 +1,7 @@
1
1
  <script>
2
2
  import Loading from '@shell/components/Loading';
3
3
  import CreateEditView from '@shell/mixins/create-edit-view';
4
- import AuthConfig from '@shell/mixins/auth-config';
4
+ import AuthConfig, { SLO_OPTION_VALUES } from '@shell/mixins/auth-config';
5
5
  import CruResource from '@shell/components/CruResource';
6
6
  import AllowedPrincipals from '@shell/components/auth/AllowedPrincipals';
7
7
  import FileSelector from '@shell/components/form/FileSelector';
@@ -15,6 +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
19
 
19
20
  export default {
20
21
  components: {
@@ -33,6 +34,8 @@ export default {
33
34
  CopyToClipboardText,
34
35
  },
35
36
 
37
+ emits: ['validationChanged'],
38
+
36
39
  mixins: [CreateEditView, AuthConfig],
37
40
 
38
41
  data() {
@@ -56,7 +59,8 @@ export default {
56
59
  userInfoEndpoint: null,
57
60
  },
58
61
  // TODO #13457: this is duplicated due wrong format
59
- oidcScope: []
62
+ oidcScope: [],
63
+ SLO_OPTION_VALUES
60
64
  };
61
65
  },
62
66
 
@@ -89,6 +93,11 @@ export default {
89
93
  return false;
90
94
  }
91
95
 
96
+ // make sure that if SLO options are enabled on radio group, field "endSessionEndpoint" is required
97
+ if (this.isLogoutAllSupported && this.sloEndSessionEndpointUiEnabled && (!this.model.endSessionEndpoint || !isUrl(this.model.endSessionEndpoint))) {
98
+ return false;
99
+ }
100
+
92
101
  if (this.isAmazonCognito) {
93
102
  const { issuer } = this.model;
94
103
 
@@ -129,10 +138,36 @@ export default {
129
138
 
130
139
  isAmazonCognito() {
131
140
  return this.model?.id === 'cognito';
141
+ },
142
+
143
+ isLogoutAllSupported() {
144
+ return this.model?.logoutAllSupported;
145
+ },
146
+
147
+ sloOptions() {
148
+ return [
149
+ { value: SLO_OPTION_VALUES.rancher, label: this.t('authConfig.slo.sloOptions.onlyRancher', { name: this.model?.nameDisplay }) },
150
+ { value: SLO_OPTION_VALUES.all, label: this.t('authConfig.slo.sloOptions.logoutAll', { name: this.model?.nameDisplay }) },
151
+ { value: SLO_OPTION_VALUES.both, label: this.t('authConfig.slo.sloOptions.choose') },
152
+ ];
153
+ },
154
+
155
+ sloTypeText() {
156
+ const sloOptionSelected = this.sloOptions.find((item) => item.value === this.sloType);
157
+
158
+ return sloOptionSelected?.label || '';
159
+ },
160
+
161
+ sloEndSessionEndpointUiEnabled() {
162
+ return this.sloType === SLO_OPTION_VALUES.all || this.sloType === SLO_OPTION_VALUES.both;
132
163
  }
133
164
  },
134
165
 
135
166
  watch: {
167
+ fvFormIsValid(newValue) {
168
+ this.$emit('validationChanged', !!newValue);
169
+ },
170
+
136
171
  'oidcUrls.url'() {
137
172
  this.updateEndpoints();
138
173
  },
@@ -166,6 +201,25 @@ export default {
166
201
  if (!old && neu) {
167
202
  this.customEndpoint.value = !this.oidcUrls.url && !!this.model.issuer;
168
203
  }
204
+ },
205
+
206
+ // sloType is defined on shell/mixins/auth-config.js
207
+ sloType(neu) {
208
+ switch (neu) {
209
+ case SLO_OPTION_VALUES.rancher:
210
+ this.model.logoutAllEnabled = false;
211
+ this.model.logoutAllForced = false;
212
+ this.model.endSessionEndpoint = '';
213
+ break;
214
+ case SLO_OPTION_VALUES.all:
215
+ this.model.logoutAllEnabled = true;
216
+ this.model.logoutAllForced = true;
217
+ break;
218
+ case SLO_OPTION_VALUES.both:
219
+ this.model.logoutAllEnabled = true;
220
+ this.model.logoutAllForced = false;
221
+ break;
222
+ }
169
223
  }
170
224
  },
171
225
 
@@ -224,11 +278,19 @@ export default {
224
278
  :edit="goToEdit"
225
279
  >
226
280
  <template #rows>
227
- <tr><td>{{ t(`authConfig.oidc.rancherUrl`) }}: </td><td>{{ model.rancherUrl }}</td></tr>
228
- <tr><td>{{ t(`authConfig.oidc.clientId`) }}: </td><td>{{ model.clientId }}</td></tr>
229
- <tr><td>{{ t(`authConfig.oidc.issuer`) }}: </td><td>{{ model.issuer }}</td></tr>
281
+ <tr><td>{{ t('authConfig.oidc.rancherUrl') }}: </td><td>{{ model.rancherUrl }}</td></tr>
282
+ <tr><td>{{ t('authConfig.oidc.clientId') }}: </td><td>{{ model.clientId }}</td></tr>
283
+ <tr><td>{{ t('authConfig.oidc.issuer') }}: </td><td>{{ model.issuer }}</td></tr>
230
284
  <tr v-if="model.authEndpoint">
231
- <td>{{ t(`authConfig.oidc.authEndpoint`) }}: </td><td>{{ model.authEndpoint }}</td>
285
+ <td>{{ t('authConfig.oidc.authEndpoint') }}: </td><td>{{ model.authEndpoint }}</td>
286
+ </tr>
287
+ <tr v-if="isLogoutAllSupported">
288
+ <td>{{ t('authConfig.slo.sloTitle') }}: </td><td>{{ sloTypeText }}</td>
289
+ </tr>
290
+ <tr v-if="isLogoutAllSupported && sloEndSessionEndpointUiEnabled">
291
+ <td>
292
+ {{ t('authConfig.oidc.endSessionEndpoint.title') }}:
293
+ </td><td>{{ model.endSessionEndpoint }}</td>
232
294
  </tr>
233
295
  </template>
234
296
  </AuthBanner>
@@ -494,6 +556,44 @@ export default {
494
556
  </div>
495
557
  </div>
496
558
  </template>
559
+
560
+ <!-- SLO logout -->
561
+ <div
562
+ v-if="isLogoutAllSupported"
563
+ class="mt-40 mb-20"
564
+ >
565
+ <div class="row">
566
+ <div class="col span-12">
567
+ <h3>{{ t('authConfig.slo.sloTitle') }}</h3>
568
+ </div>
569
+ </div>
570
+ <div class="row">
571
+ <div class="col span-4">
572
+ <RadioGroup
573
+ v-model:value="sloType"
574
+ :mode="mode"
575
+ :options="sloOptions"
576
+ :disabled="!model.logoutAllSupported"
577
+ name="sloTypeRadio"
578
+ />
579
+ </div>
580
+ </div>
581
+ <div
582
+ v-if="sloEndSessionEndpointUiEnabled"
583
+ class="row mt-20"
584
+ >
585
+ <div class="col span-6">
586
+ <LabeledInput
587
+ v-model:value="model.endSessionEndpoint"
588
+ :tooltip="t('authConfig.oidc.endSessionEndpoint.tooltip')"
589
+ :label="t('authConfig.oidc.endSessionEndpoint.title')"
590
+ :mode="mode"
591
+ required
592
+ data-testid="oidc-endSessionEndpoint"
593
+ />
594
+ </div>
595
+ </div>
596
+ </div>
497
597
  </template>
498
598
  </CruResource>
499
599
  </div>
@@ -73,9 +73,9 @@ export default {
73
73
 
74
74
  sloOptions() {
75
75
  return [
76
- { value: SLO_OPTION_VALUES.rancher, label: this.t('authConfig.saml.sloOptions.onlyRancher', { name: this.model?.nameDisplay }) },
77
- { value: SLO_OPTION_VALUES.all, label: this.t('authConfig.saml.sloOptions.logoutAll', { name: this.model?.nameDisplay }) },
78
- { value: SLO_OPTION_VALUES.both, label: this.t('authConfig.saml.sloOptions.choose') },
76
+ { value: SLO_OPTION_VALUES.rancher, label: this.t('authConfig.slo.sloOptions.onlyRancher', { name: this.model?.nameDisplay }) },
77
+ { value: SLO_OPTION_VALUES.all, label: this.t('authConfig.slo.sloOptions.logoutAll', { name: this.model?.nameDisplay }) },
78
+ { value: SLO_OPTION_VALUES.both, label: this.t('authConfig.slo.sloOptions.choose') },
79
79
  ];
80
80
  },
81
81
 
@@ -175,7 +175,7 @@ export default {
175
175
  <tr><td>{{ t(`authConfig.saml.api`) }}: </td><td>{{ model.rancherApiHost }}</td></tr>
176
176
  <tr><td>{{ t(`authConfig.saml.groups`) }}: </td><td>{{ model.groupsField }}</td></tr>
177
177
  <tr v-if="isLogoutAllSupported">
178
- <td>{{ t(`authConfig.saml.sloTitle`) }}: </td><td>{{ sloTypeText }}</td>
178
+ <td>{{ t(`authConfig.slo.sloTitle`) }}: </td><td>{{ sloTypeText }}</td>
179
179
  </tr>
180
180
  </template>
181
181
 
@@ -357,7 +357,7 @@ export default {
357
357
  >
358
358
  <div class="row">
359
359
  <div class="col span-12">
360
- <h3>{{ t('authConfig.saml.sloTitle') }}</h3>
360
+ <h3>{{ t('authConfig.slo.sloTitle') }}</h3>
361
361
  </div>
362
362
  </div>
363
363
  <div class="row">
@@ -41,6 +41,7 @@ export default {
41
41
  <YamlEditor
42
42
  ref="yaml-additional"
43
43
  v-model:value="additionalManifest"
44
+ :mode="mode"
44
45
  :editor-mode="mode === 'view' ? 'VIEW_CODE' : 'EDIT_CODE'"
45
46
  initial-yaml-values="# Additional Manifest YAML"
46
47
  class="yaml-editor"
@@ -99,6 +99,7 @@ export default {
99
99
  ref="yaml-values"
100
100
  data-testid="addon-yaml-editor"
101
101
  :value="initYamlEditor(addonVersion.name)"
102
+ :mode="mode"
102
103
  :scrolling="true"
103
104
  :as-object="true"
104
105
  :editor-mode="mode === 'view' ? 'VIEW_CODE' : 'EDIT_CODE'"
@@ -493,6 +493,7 @@ export default {
493
493
  />
494
494
  <Checkbox
495
495
  :value="showDeprecatedPatchVersions"
496
+ :mode="mode"
496
497
  :label="t('cluster.kubernetesVersion.deprecatedPatches')"
497
498
  :tooltip="t('cluster.kubernetesVersion.deprecatedPatchWarning')"
498
499
  class="patch-version"