@rancher/shell 0.3.6 → 0.3.8

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.
@@ -455,7 +455,13 @@ authConfig:
455
455
  objectClass: Object Class
456
456
  password: Password
457
457
  port: Port
458
+ protocol: Protocol
459
+ protocols:
460
+ starttls: Start TLS
461
+ ldap: LDAP
462
+ tls: LDAPS (TLS)
458
463
  customizeSchema: Customize Schema
464
+ oktaSchema: 'The defaults below are for a generic OpenLDAP server. For more information on the values to use when using the Okta LDAP interface, see: <a target="_blank" rel="noopener noreferrer nofollow" href="https://help.okta.com/en-us/Content/Topics/Directory/LDAP-interface-connection-settings.htm">Okta LDAP Interface connection settings</a>'
459
465
  users: Users
460
466
  groups: Groups
461
467
  searchAttribute: Search Attribute
@@ -500,6 +506,13 @@ authConfig:
500
506
  shibboleth: Configure a Shibboleth account
501
507
  showLdap: Configure an OpenLDAP Server
502
508
  userName: User Name Field
509
+ search:
510
+ title: User and Group Search
511
+ message: The SAML Protocol does not support search or lookup for users or groups. In order to enabled search, an OpenLDAP server must be configured.
512
+ on: LDAP User and Group search has been configured
513
+ off: LDAP User and Group search is not configured
514
+ show: Show details
515
+ hide: Hide details
503
516
  azuread:
504
517
  tenantId: Tenant ID
505
518
  applicationId: Application ID
@@ -1236,7 +1249,7 @@ cluster:
1236
1249
  memory: Memory
1237
1250
  disk: Disk
1238
1251
  image: Image
1239
- network:
1252
+ network:
1240
1253
  title: Networks
1241
1254
  network: Network
1242
1255
  addNetwork: Add Network
@@ -1814,7 +1827,7 @@ cluster:
1814
1827
  option: Default - RKE2 Embedded
1815
1828
  defaultPodSecurityAdmissionConfigurationTemplateName:
1816
1829
  label: Pod Security Admission Configuration Template
1817
- option:
1830
+ option:
1818
1831
  none: (None)
1819
1832
  default: Default - RKE2 Embedded
1820
1833
  cisProfile:
@@ -2189,16 +2202,16 @@ fleet:
2189
2202
  error: Error
2190
2203
  ready: Ready
2191
2204
  errors: Errors
2192
- workspaces:
2205
+ workspaces:
2193
2206
  tabs:
2194
2207
  restrictions: Allowed Target Namespaces
2195
2208
  timeout: Workspace creation timeout. It's possible the workspace was created. We suggest checking the workspace page before trying to create another.
2196
- restrictions:
2209
+ restrictions:
2197
2210
  addTitle: 'allowedTargetNamespaces'
2198
2211
  addLabel: Add
2199
2212
  banner: "{count, plural,
2200
2213
  =0 { Adding namespaces here will create a GitRepoRestriction. }
2201
- other { Only the Git Repo Restriction's <code>allowedTargetNamespaces</code> is managed here. You can make additional changes to the Git Repo Restriction}
2214
+ other { Only the Git Repo Restriction's <code>allowedTargetNamespaces</code> is managed here. You can make additional changes to the Git Repo Restriction}
2202
2215
  }"
2203
2216
  footer:
2204
2217
  docs: Docs
@@ -2247,7 +2260,7 @@ gatekeeperIndex:
2247
2260
  unavailable: OPA + Gatekeeper is not available in the system-charts catalog.
2248
2261
  violations: Violations
2249
2262
 
2250
- gatekeeperInstall:
2263
+ gatekeeperInstall:
2251
2264
  auditInterval: Auto Interval
2252
2265
  constraintViolationsLimit: Constraint Violations Limit
2253
2266
  runtimeDefaultSeccompProfile: Enable Runtime Default Seccomp Profile
@@ -3939,6 +3952,15 @@ podDisruptionBudget:
3939
3952
  maxUnavailable:
3940
3953
  label: Max. unavailable Pods
3941
3954
 
3955
+ inactivity:
3956
+ title: Session expiring
3957
+ titleExpired: Session expired
3958
+ banner: Your session is about to expire due to inactivity. Any unsaved changes will be lost.
3959
+ bannerExpired: Your session has expired in this tab due to inactivity.
3960
+ content: Click “Resume Session” to keep the session in this tab active or refresh the browser after the session has expired.
3961
+ contentExpired: To return to this page click “Refresh” below or refresh the browser.
3962
+ cta: Resume Session
3963
+ ctaExpired: Refresh
3942
3964
 
3943
3965
  # Rancher Extensions
3944
3966
  plugins:
@@ -4038,7 +4060,7 @@ plugins:
4038
4060
  podSecurityAdmission:
4039
4061
  name: Pod Security Admission
4040
4062
  description: Define the admission control mode you want to use for the pod security
4041
- banner:
4063
+ banner:
4042
4064
  modifications: 'Changing any template that is currently in use will cause an update to those live clusters the next time the cluster is updated'
4043
4065
  labels:
4044
4066
  enforce: Enforce
@@ -4052,7 +4074,7 @@ podSecurityAdmission:
4052
4074
  restricted: restricted
4053
4075
  version:
4054
4076
  placeholder: 'Version (default: latest)'
4055
- exemptions:
4077
+ exemptions:
4056
4078
  title: Exemptions
4057
4079
  description: Allow the creation of pods for specific Usernames, RuntimeClassNames, and Namespaces that would otherwise be prohibited due to the policies set above.
4058
4080
  placeholder: Enter a comma separated list of {psaExemptionsControl}
@@ -5173,7 +5195,7 @@ storageClass:
5173
5195
  warning: 'The {provisioner} in-tree plugin is deprecated: Find a CSI driver <a target="_blank" rel="noopener noreferrer nofollow" href="https://kubernetes-csi.github.io/docs/drivers.html">here.</a>'
5174
5196
  harvesterhci:
5175
5197
  title: Harvester (CSI)
5176
- warning:
5198
+ warning:
5177
5199
  unSatisfiesVersion: Please upgrade your Harvester CSI driver version to use this feature. (csi-driver >= v0.1.15)
5178
5200
  hostStorageClass:
5179
5201
  label: Host Storage Class
@@ -6118,8 +6140,6 @@ workload:
6118
6140
  pod: Pod
6119
6141
  containers: Containers
6120
6142
 
6121
-
6122
-
6123
6143
  ##############################
6124
6144
  # Model Properties
6125
6145
  ##############################
@@ -6765,7 +6785,7 @@ typeLabel:
6765
6785
  {count, plural,
6766
6786
  one { Cluster Registration Token }
6767
6787
  other { Cluster Registration Tokens }
6768
- }
6788
+ }
6769
6789
 
6770
6790
  action:
6771
6791
  clone: Clone
@@ -6989,6 +7009,14 @@ performance:
6989
7009
  label: Websocket Web Worker
6990
7010
  description: Updates to resources pushed to the UI come via WebSocket and are handled in the UI thread. Enable this option to handle cluster WebSocket updates in a Web Worker in a separate thread. This should help the responsiveness of the UI in systems where resources change often.
6991
7011
  checkboxLabel: Enable Advanced Websocket Web Worker
7012
+ inactivity:
7013
+ title: Inactivity
7014
+ checkboxLabel: Enable inactivity session expiration
7015
+ inputLabel: Inactivity timeout (minutes)
7016
+ information: To change the automatic logout behaviour, edit the authorisation and/or session token timeout values (<code>auth-user-session-ttl-minutes</code> and <code>auth-token-max-ttl-minutes</code>) in the Settings page.
7017
+ description: When enabled and the user is inactive past the specified timeout, the UI will no longer fresh page content and the user must reload the page to continue.
7018
+ authUserTTL: This timeout cannot be higher than the user session timeout auth-user-session-ttl-minutes, which is currently {current} minutes.
7019
+
6992
7020
 
6993
7021
  banner:
6994
7022
  label: Fixed Banners
@@ -0,0 +1,229 @@
1
+ <script>
2
+ import ModalWithCard from '@shell/components/ModalWithCard';
3
+ import { Banner } from '@components/Banner';
4
+ import PercentageBar from '@shell/components/PercentageBar.vue';
5
+ import throttle from 'lodash/throttle';
6
+ import { MANAGEMENT } from '@shell/config/types';
7
+ import { DEFAULT_PERF_SETTING, SETTING } from '@shell/config/settings';
8
+
9
+ export default {
10
+ name: 'Inactivity',
11
+ components: {
12
+ ModalWithCard, Banner, PercentageBar
13
+ },
14
+ data() {
15
+ return {
16
+ enabled: null,
17
+ isOpen: false,
18
+ isInactive: false,
19
+ showModalAfter: null,
20
+ inactivityTimeoutId: null,
21
+ courtesyTimer: null,
22
+ courtesyTimerId: null,
23
+ courtesyCountdown: null,
24
+ trackInactivity: throttle(this._trackInactivity, 1000),
25
+ };
26
+ },
27
+ async mounted() {
28
+ // Info: normally, this is done in the fetch hook but for some reasons while awaiting for things that will take a while, it won't be ready by the time mounted() is called, pending for investigation.
29
+ let settings;
30
+
31
+ try {
32
+ const settingsString = await this.$store.dispatch('management/find', { type: MANAGEMENT.SETTING, id: SETTING.UI_PERFORMANCE });
33
+
34
+ settings = settingsString?.value ? JSON.parse(settingsString.value) : DEFAULT_PERF_SETTING;
35
+ } catch { }
36
+
37
+ if (!settings || !settings?.inactivity || !settings?.inactivity.enabled) {
38
+ return;
39
+ }
40
+
41
+ this.enabled = settings?.inactivity?.enabled || false;
42
+
43
+ // Total amount of time before the user's session is lost
44
+ const thresholdToSeconds = settings?.inactivity?.threshold * 60;
45
+
46
+ // Amount of time the user sees the inactivity warning
47
+ this.courtesyTimer = Math.floor(thresholdToSeconds * 0.1);
48
+ this.courtesyTimer = Math.min(this.courtesyTimer, 60 * 5); // Never show the modal more than 5 minutes
49
+ // Amount of time before the user sees the inactivity warning
50
+ // Note - time before warning is shown + time warning is shown = settings threshold (total amount of time)
51
+ this.showModalAfter = thresholdToSeconds - this.courtesyTimer;
52
+
53
+ console.debug(`Inactivity modal will show after ${ this.showModalAfter / 60 }(m) and be shown for ${ this.courtesyTimer / 60 }(m)`); // eslint-disable-line no-console
54
+
55
+ this.courtesyCountdown = this.courtesyTimer;
56
+
57
+ if (settings?.inactivity.enabled) {
58
+ this.trackInactivity();
59
+ this.addIdleListeners();
60
+ }
61
+ },
62
+ beforeDestroy() {
63
+ this.removeEventListener();
64
+ this.clearAllTimeouts();
65
+ },
66
+ methods: {
67
+ _trackInactivity() {
68
+ if (this.isInactive || this.isOpen || !this.showModalAfter) {
69
+ return;
70
+ }
71
+
72
+ this.clearAllTimeouts();
73
+ const endTime = Date.now() + this.showModalAfter * 1000;
74
+
75
+ const checkInactivityTimer = () => {
76
+ const now = Date.now();
77
+
78
+ if (now >= endTime) {
79
+ this.isOpen = true;
80
+ this.startCountdown();
81
+
82
+ this.$modal.show('inactivityModal');
83
+ } else {
84
+ this.inactivityTimeoutId = setTimeout(checkInactivityTimer, 1000);
85
+ }
86
+ };
87
+
88
+ checkInactivityTimer();
89
+ },
90
+ startCountdown() {
91
+ const endTime = Date.now() + (this.courtesyCountdown * 1000);
92
+
93
+ const checkCountdown = () => {
94
+ const now = Date.now();
95
+
96
+ if (now >= endTime) {
97
+ this.isInactive = true;
98
+ this.unsubscribe();
99
+ this.clearAllTimeouts();
100
+ } else {
101
+ this.courtesyCountdown = Math.floor((endTime - now) / 1000);
102
+ this.courtesyTimerId = setTimeout(checkCountdown, 1000);
103
+ }
104
+ };
105
+
106
+ checkCountdown();
107
+ },
108
+ addIdleListeners() {
109
+ document.addEventListener('mousemove', this.trackInactivity);
110
+ document.addEventListener('mousedown', this.trackInactivity);
111
+ document.addEventListener('keypress', this.trackInactivity);
112
+ document.addEventListener('touchmove', this.trackInactivity);
113
+ document.addEventListener('visibilitychange', this.trackInactivity);
114
+ },
115
+ removeEventListener() {
116
+ document.removeEventListener('mousemove', this.trackInactivity);
117
+ document.removeEventListener('mousedown', this.trackInactivity);
118
+ document.removeEventListener('keypress', this.trackInactivity);
119
+ document.removeEventListener('touchmove', this.trackInactivity);
120
+ document.removeEventListener('visibilitychange', this.trackInactivity);
121
+ },
122
+
123
+ resume() {
124
+ this.isInactive = false;
125
+ this.isOpen = false;
126
+ this.courtesyCountdown = this.courtesyTimer;
127
+ this.clearAllTimeouts();
128
+
129
+ this.$modal.hide('inactivityModal');
130
+ },
131
+
132
+ refresh() {
133
+ window.location.reload();
134
+ },
135
+
136
+ unsubscribe() {
137
+ this.$store.dispatch('unsubscribe');
138
+ },
139
+ clearAllTimeouts() {
140
+ clearTimeout(this.inactivityTimeoutId);
141
+ clearTimeout(this.courtesyTimerId);
142
+ }
143
+
144
+ },
145
+ computed: {
146
+ isInactiveTexts() {
147
+ return this.isInactive ? {
148
+ title: this.t('inactivity.titleExpired'),
149
+ banner: this.t('inactivity.bannerExpired'),
150
+ content: this.t('inactivity.contentExpired'),
151
+ } : {
152
+ title: this.t('inactivity.title'),
153
+ banner: this.t('inactivity.banner'),
154
+ content: this.t('inactivity.content'),
155
+ };
156
+ },
157
+ timerPercentageLeft() {
158
+ return Math.floor((this.courtesyCountdown / this.courtesyTimer ) * 100);
159
+ },
160
+ colorStops() {
161
+ return {
162
+ 0: '--info', 30: '--info', 70: '--info'
163
+ };
164
+ },
165
+ }
166
+ };
167
+ </script>
168
+
169
+ <template>
170
+ <ModalWithCard
171
+ ref="inactivityModal"
172
+ name="inactivityModal"
173
+ save-text="Continue"
174
+ :v-if="isOpen"
175
+ @finish="resume"
176
+ >
177
+ <template #title>
178
+ {{ isInactiveTexts.title }}
179
+ </template>
180
+ <span>{{ courtesyCountdown }}</span>
181
+
182
+ <template #content>
183
+ <Banner color="info">
184
+ {{ isInactiveTexts.banner }}
185
+ </Banner>
186
+
187
+ <p>
188
+ {{ isInactiveTexts.content }}
189
+ </p>
190
+
191
+ <PercentageBar
192
+ v-if="!isInactive"
193
+ class="mt-20"
194
+ :value="timerPercentageLeft"
195
+ :color-stops="colorStops"
196
+ />
197
+ </template>
198
+
199
+ <template
200
+ #footer
201
+ >
202
+ <div class="card-actions">
203
+ <button
204
+ v-if="!isInactive"
205
+ class="btn role-tertiary bg-primary"
206
+ @click.prevent="resume"
207
+ >
208
+ <t k="inactivity.cta" />
209
+ </button>
210
+
211
+ <button
212
+ v-if="isInactive"
213
+ class="btn role-tertiary bg-primary"
214
+ @click.prevent="refresh"
215
+ >
216
+ <t k="inactivity.ctaExpired" />
217
+ </button>
218
+ </div>
219
+ </template>
220
+ </ModalWithCard>
221
+ </template>
222
+
223
+ <style lang="scss" scoped>
224
+ .card-actions {
225
+ display: flex;
226
+ width: 100%;
227
+ justify-content: flex-end;
228
+ }
229
+ </style>
@@ -73,6 +73,12 @@ export default {
73
73
  >
74
74
  <slot name="rows" />
75
75
  </table>
76
+
77
+ <slot
78
+ v-if="$slots.footer"
79
+ name="footer"
80
+ />
81
+
76
82
  <DisableAuthProviderModal
77
83
  ref="disableAuthProviderModal"
78
84
  @disable="disable"
@@ -140,6 +140,10 @@ export const ALLOWED_SETTINGS: GlobalSetting = {
140
140
  };
141
141
 
142
142
  export const DEFAULT_PERF_SETTING = {
143
+ inactivity: {
144
+ enabled: false,
145
+ threshold: 900,
146
+ },
143
147
  incrementalLoading: {
144
148
  enabled: true,
145
149
  threshold: 1500,
@@ -0,0 +1,73 @@
1
+ # compiled output
2
+ /dist
3
+ /tmp
4
+ /out-tsc
5
+
6
+ # Runtime data
7
+ pids
8
+ *.pid
9
+ *.seed
10
+ *.pid.lock
11
+
12
+ # Directory for instrumented libs generated by jscoverage/JSCover
13
+ lib-cov
14
+
15
+ # Coverage directory used by tools like istanbul
16
+ coverage
17
+
18
+ # nyc test coverage
19
+ .nyc_output
20
+
21
+ # IDEs and editors
22
+ .idea
23
+ .project
24
+ .classpath
25
+ .c9/
26
+ *.launch
27
+ .settings/
28
+ *.sublime-workspace
29
+
30
+ # IDE - VSCode
31
+ .vscode/*
32
+ !.vscode/settings.json
33
+ !.vscode/tasks.json
34
+ !.vscode/launch.json
35
+ !.vscode/extensions.json
36
+
37
+ # misc
38
+ .sass-cache
39
+ connect.lock
40
+ typings
41
+
42
+ # Logs
43
+ logs
44
+ *.log
45
+ npm-debug.log*
46
+ yarn-debug.log*
47
+ yarn-error.log*
48
+
49
+ # Dependency directories
50
+ node_modules/
51
+ jspm_packages/
52
+
53
+ # Optional npm cache directory
54
+ .npm
55
+
56
+ # Optional eslint cache
57
+ .eslintcache
58
+
59
+ # Optional REPL history
60
+ .node_repl_history
61
+
62
+ # Output of 'npm pack'
63
+ *.tgz
64
+
65
+ # Yarn Integrity file
66
+ .yarn-integrity
67
+
68
+ # dotenv environment variables file
69
+ .env
70
+
71
+ # System Files
72
+ .DS_Store
73
+ Thumbs.db
package/creators/app/init CHANGED
@@ -12,6 +12,7 @@ const targets = {
12
12
  const files = [
13
13
  'tsconfig.json',
14
14
  'vue.config.js',
15
+ '.gitignore',
15
16
  '.eslintignore',
16
17
  '.eslintrc.js',
17
18
  'babel.config.js',
@@ -5,6 +5,7 @@ import { Checkbox } from '@components/Form/Checkbox';
5
5
  import UnitInput from '@shell/components/form/UnitInput';
6
6
  import { Banner } from '@components/Banner';
7
7
  import FileSelector from '@shell/components/form/FileSelector';
8
+ import { OKTA, SHIBBOLETH } from '../saml';
8
9
 
9
10
  const DEFAULT_NON_TLS_PORT = 389;
10
11
  const DEFAULT_TLS_PORT = 636;
@@ -33,6 +34,11 @@ export default {
33
34
  type: {
34
35
  type: String,
35
36
  required: true
37
+ },
38
+
39
+ isCreate: {
40
+ type: Boolean,
41
+ default: false
36
42
  }
37
43
 
38
44
  },
@@ -46,9 +52,17 @@ export default {
46
52
  model: this.value,
47
53
  hostname: this.value.servers.join(','),
48
54
  serverSetting: null,
55
+ OKTA
49
56
  };
50
57
  },
51
58
 
59
+ computed: {
60
+ // Does the auth provider support LDAP for search in addition to SAML?
61
+ isSamlProvider() {
62
+ return this.type === SHIBBOLETH || this.type === OKTA;
63
+ }
64
+ },
65
+
52
66
  watch: {
53
67
  hostname(neu, old) {
54
68
  this.value.servers = neu.split(',');
@@ -232,6 +246,12 @@ export default {
232
246
  <div class="row">
233
247
  <h3> {{ t('authConfig.ldap.customizeSchema') }}</h3>
234
248
  </div>
249
+ <Banner
250
+ v-if="type === OKTA && isCreate"
251
+ class="row"
252
+ color="info"
253
+ label-key="authConfig.ldap.oktaSchema"
254
+ />
235
255
  <div class="row">
236
256
  <div class="col span-6">
237
257
  <h4>{{ t('authConfig.ldap.users') }}</h4>
@@ -361,7 +381,7 @@ export default {
361
381
  />
362
382
  </div>
363
383
  <div
364
- v-if="type!=='shibboleth'"
384
+ v-if="!isSamlProvider"
365
385
  class=" col span-6"
366
386
  >
367
387
  <RadioGroup
@@ -11,6 +11,31 @@ import FileSelector from '@shell/components/form/FileSelector';
11
11
  import AuthBanner from '@shell/components/auth/AuthBanner';
12
12
  import config from '@shell/edit/auth/ldap/config';
13
13
 
14
+ export const SHIBBOLETH = 'shibboleth';
15
+ export const OKTA = 'okta';
16
+
17
+ // Standard LDAP defaults
18
+ const LDAP_DEFAULTS = {
19
+ connectionTimeout: 5000,
20
+ groupDNAttribute: 'entryDN',
21
+ groupMemberMappingAttribute: 'member',
22
+ groupMemberUserAttribute: 'entryDN',
23
+ groupNameAttribute: 'cn',
24
+ groupObjectClass: 'groupOfNames',
25
+ groupSearchAttribute: 'cn',
26
+ nestedGroupMembershipEnabled: false,
27
+ port: 389,
28
+ servers: [],
29
+ starttls: false,
30
+ tls: false,
31
+ disabledStatusBitmask: 0,
32
+ userLoginAttribute: 'uid',
33
+ userMemberAttribute: 'memberOf',
34
+ userNameAttribute: 'cn',
35
+ userObjectClass: 'inetOrgPerson',
36
+ userSearchAttribute: 'uid|sn|givenName'
37
+ };
38
+
14
39
  export default {
15
40
  components: {
16
41
  Loading,
@@ -26,7 +51,10 @@ export default {
26
51
 
27
52
  mixins: [CreateEditView, AuthConfig],
28
53
  data() {
29
- return { showLdap: false };
54
+ return {
55
+ showLdap: false,
56
+ showLdapDetails: false,
57
+ };
30
58
  },
31
59
 
32
60
  computed: {
@@ -42,32 +70,32 @@ export default {
42
70
  return { enabled: true, ...this.model };
43
71
  },
44
72
 
73
+ // Does the auth provider support LDAP for search in addition to SAML?
74
+ supportsLDAPSearch() {
75
+ return this.NAME === SHIBBOLETH || this.NAME === OKTA;
76
+ },
77
+
78
+ ldapHosts() {
79
+ const hosts = this.model?.openLdapConfig.servers || [];
80
+
81
+ return hosts.join(',');
82
+ },
83
+
84
+ ldapProtocol() {
85
+ if (this.model?.openLdapConfig?.starttls) {
86
+ return this.t('authConfig.ldap.protocols.starttls');
87
+ } else if (this.model?.openLdapConfig?.tls) {
88
+ return this.t('authConfig.ldap.protocols.tls');
89
+ }
90
+
91
+ return this.t('authConfig.ldap.protocols.ldap');
92
+ }
45
93
  },
46
94
  watch: {
47
95
  showLdap(neu, old) {
48
96
  if (neu && !this.model.openLdapConfig) {
49
- const config = {
50
- connectionTimeout: 5000,
51
- groupDNAttribute: 'entryDN',
52
- groupMemberMappingAttribute: 'member',
53
- groupMemberUserAttribute: 'entryDN',
54
- groupNameAttribute: 'cn',
55
- groupObjectClass: 'groupOfNames',
56
- groupSearchAttribute: 'cn',
57
- nestedGroupMembershipEnabled: false,
58
- port: 389,
59
- servers: [],
60
- starttls: false,
61
- tls: false,
62
- disabledStatusBitmask: 0,
63
- userLoginAttribute: 'uid',
64
- userMemberAttribute: 'memberOf',
65
- userNameAttribute: 'cn',
66
- userObjectClass: 'inetOrgPerson',
67
- userSearchAttribute: 'uid|sn|givenName'
68
- };
69
-
70
- this.$set(this.model, 'openLdapConfig', config);
97
+ // Use a spread of config, so that if don't make changes to the defaults if the user edits them
98
+ this.$set(this.model, 'openLdapConfig', { ...LDAP_DEFAULTS });
71
99
  }
72
100
  }
73
101
  }
@@ -98,7 +126,9 @@ export default {
98
126
  :disable="disable"
99
127
  :edit="goToEdit"
100
128
  >
101
- <template slot="rows">
129
+ <template
130
+ slot="rows"
131
+ >
102
132
  <tr><td>{{ t(`authConfig.saml.displayName`) }}: </td><td>{{ model.displayNameField }}</td></tr>
103
133
  <tr><td>{{ t(`authConfig.saml.userName`) }}: </td><td>{{ model.userNameField }}</td></tr>
104
134
  <tr><td>{{ t(`authConfig.saml.UID`) }}: </td><td>{{ model.uidField }}</td></tr>
@@ -106,6 +136,47 @@ export default {
106
136
  <tr><td>{{ t(`authConfig.saml.api`) }}: </td><td>{{ model.rancherApiHost }}</td></tr>
107
137
  <tr><td>{{ t(`authConfig.saml.groups`) }}: </td><td>{{ model.groupsField }}</td></tr>
108
138
  </template>
139
+
140
+ <template
141
+ v-if="supportsLDAPSearch"
142
+ slot="footer"
143
+ >
144
+ <Banner
145
+ v-if="showLdap"
146
+ color="success"
147
+ class="banner"
148
+ >
149
+ <div
150
+ class="advanced-ldap-banner"
151
+ >
152
+ <div>{{ t('authConfig.saml.search.on') }}</div>
153
+ <div>
154
+ <a
155
+ class="toggle-btn"
156
+ @click="showLdapDetails = !showLdapDetails"
157
+ >
158
+ <template v-if="showLdapDetails">{{ t('authConfig.saml.search.hide') }}</template>
159
+ <template v-else>{{ t('authConfig.saml.search.show') }}</template>
160
+ </a>
161
+ </div>
162
+ </div>
163
+ </Banner>
164
+ <Banner
165
+ v-else
166
+ color="info"
167
+ >
168
+ {{ t('authConfig.saml.search.off') }}
169
+ </Banner>
170
+
171
+ <table v-if="showLdapDetails && model.openLdapConfig">
172
+ <tr><td>{{ t('authConfig.ldap.hostname.label') }}:</td><td>{{ ldapHosts }}</td></tr>
173
+ <tr><td>{{ t('authConfig.ldap.port') }}:</td><td>{{ model.openLdapConfig.port }}</td></tr>
174
+ <tr><td>{{ t('authConfig.ldap.protocol') }}:</td><td>{{ ldapProtocol }}</td></tr>
175
+ <tr><td>{{ t('authConfig.ldap.serviceAccountDN') }}:</td><td>{{ model.openLdapConfig.serviceAccountDistinguishedName }}</td></tr>
176
+ <tr><td>{{ t('authConfig.ldap.userSearchBase.label') }}:</td><td>{{ model.openLdapConfig.userSearchBase }}</td></tr>
177
+ <tr><td>{{ t('authConfig.ldap.groupSearchBase.label') }}:</td><td>{{ model.openLdapConfig.groupSearchBase }}</td></tr>
178
+ </table>
179
+ </template>
109
180
  </AuthBanner>
110
181
 
111
182
  <hr>
@@ -233,7 +304,26 @@ export default {
233
304
  />
234
305
  </div>
235
306
  </div>
236
- <div v-if="NAME === 'shibboleth'">
307
+ <div
308
+ v-if="!model.enabled"
309
+ class="row"
310
+ >
311
+ <div class="col span-12">
312
+ <Banner color="info">
313
+ <div v-clean-html="t('authConfig.associatedWarning', tArgs, true)" />
314
+ </Banner>
315
+ </div>
316
+ </div>
317
+ <div v-if="supportsLDAPSearch">
318
+ <div class="row">
319
+ <h2>{{ t('authConfig.saml.search.title') }}</h2>
320
+ </div>
321
+ <div class="row">
322
+ <Banner
323
+ label-key="authConfig.saml.search.message"
324
+ color="info"
325
+ />
326
+ </div>
237
327
  <div class="row">
238
328
  <Checkbox
239
329
  v-model="showLdap"
@@ -243,25 +333,15 @@ export default {
243
333
  </div>
244
334
  <div class="row mt-20 mb-20">
245
335
  <config
246
- v-if="showLdap"
336
+ v-if="showLdap && model.openLdapConfig"
247
337
  v-model="model.openLdapConfig"
248
338
  :type="NAME"
249
339
  :mode="mode"
340
+ :is-create="!model.enabled"
250
341
  />
251
342
  </div>
252
343
  </div>
253
344
  </template>
254
- <div
255
- v-if="!model.enabled"
256
- class="row"
257
- >
258
- <div class="col span-12">
259
- <Banner
260
- v-clean-html="t('authConfig.associatedWarning', tArgs, true)"
261
- color="info"
262
- />
263
- </div>
264
- </div>
265
345
  </CruResource>
266
346
  </div>
267
347
  </template>
@@ -274,4 +354,19 @@ export default {
274
354
  margin: 0 3px;
275
355
  }
276
356
  }
357
+
358
+ // Banner shows message and link formatted right aligned
359
+ .advanced-ldap-banner {
360
+ display: flex;
361
+ flex: 1;
362
+
363
+ > :first-child {
364
+ flex: 1;
365
+ }
366
+
367
+ .toggle-btn {
368
+ cursor: pointer;
369
+ user-select: none;
370
+ }
371
+ }
277
372
  </style>
@@ -306,12 +306,7 @@ export default {
306
306
  const lastDefaultPodSecurityPolicyTemplateName = this.value.spec.defaultPodSecurityPolicyTemplateName;
307
307
  const previousKubernetesVersion = this.value.spec.kubernetesVersion;
308
308
 
309
- const truncateLimit = this.value.machinePoolDefaults?.hostnameLengthLimit;
310
-
311
- // Is hostname truncation supported by the backend?
312
- const provSchema = this.$store.getters['management/schemaFor'](CAPI.RANCHER_CLUSTER);
313
- const specSchemaName = provSchema?.resourceFields?.spec?.type;
314
- const specSchema = specSchemaName ? this.$store.getters['management/schemaFor'](specSchemaName) : {};
309
+ const truncateLimit = this.value.defaultHostnameLengthLimit;
315
310
 
316
311
  return {
317
312
  loadedOnce: false,
@@ -350,7 +345,6 @@ export default {
350
345
  psps: null, // List of policies if any
351
346
  truncateHostnames: truncateLimit === NETBIOS_TRUNCATION_LENGTH,
352
347
  truncateLimit,
353
- supportsTruncation: !!specSchema?.resourceFields?.machinePoolDefaults,
354
348
  };
355
349
  },
356
350
 
@@ -1100,14 +1094,9 @@ export default {
1100
1094
  */
1101
1095
  truncateName() {
1102
1096
  if (this.truncateHostnames) {
1103
- this.value.machinePoolDefaults = this.value.machinePoolDefaults || {};
1104
- this.value.machinePoolDefaults.hostnameLengthLimit = 15;
1097
+ this.value.defaultHostnameLengthLimit = NETBIOS_TRUNCATION_LENGTH;
1105
1098
  } else {
1106
- delete this.value.machinePoolDefaults.hostnameLengthLimit;
1107
-
1108
- if (Object.keys(this.value.machinePoolDefaults).length === 0) {
1109
- delete this.value.machinePoolDefaults;
1110
- }
1099
+ this.value.removeDefaultHostnameLengthLimit();
1111
1100
  }
1112
1101
  },
1113
1102
  /**
@@ -2598,7 +2587,6 @@ export default {
2598
2587
  />
2599
2588
  </div>
2600
2589
  <div
2601
- v-if="supportsTruncation"
2602
2590
  class="col span-6"
2603
2591
  >
2604
2592
  <Checkbox
@@ -26,7 +26,7 @@ import Loading from '@shell/components/Loading';
26
26
  import Networking from '@shell/components/form/Networking';
27
27
  import VolumeClaimTemplate from '@shell/edit/workload/VolumeClaimTemplate';
28
28
  import Job from '@shell/edit/workload/Job';
29
- import { _EDIT, _CREATE, _VIEW } from '@shell/config/query-params';
29
+ import { _EDIT, _CREATE, _VIEW, _CLONE } from '@shell/config/query-params';
30
30
  import WorkloadPorts from '@shell/components/form/WorkloadPorts';
31
31
  import ContainerResourceLimit from '@shell/components/ContainerResourceLimit';
32
32
  import KeyValue from '@shell/components/form/KeyValue';
@@ -178,7 +178,7 @@ export default {
178
178
 
179
179
  // EDIT view for POD
180
180
  // Transform it from POD world to workload
181
- if ((this.mode === _EDIT || this.mode === _VIEW ) && this.value.type === 'pod' ) {
181
+ if ((this.mode === _EDIT || this.mode === _VIEW || this.realMode === _CLONE ) && this.value.type === 'pod') {
182
182
  const podSpec = { ...this.value.spec };
183
183
  const metadata = { ...this.value.metadata };
184
184
 
@@ -198,6 +198,7 @@ export default {
198
198
  if (
199
199
  this.mode === _CREATE ||
200
200
  this.mode === _VIEW ||
201
+ this.realMode === _CLONE ||
201
202
  (!createSidecar && !this.value.hasSidecars) // hasSideCars = containers.length > 1 || initContainers.length;
202
203
  ) {
203
204
  container = containers[0];
@@ -703,7 +704,7 @@ export default {
703
704
  if (
704
705
  this.type !== WORKLOAD_TYPES.JOB &&
705
706
  this.type !== WORKLOAD_TYPES.CRON_JOB &&
706
- this.mode === _CREATE
707
+ (this.mode === _CREATE || this.realMode === _CLONE)
707
708
  ) {
708
709
  this.spec.selector = { matchLabels: this.value.workloadSelector };
709
710
  Object.assign(this.value.metadata.labels, this.value.workloadSelector);
@@ -721,7 +722,7 @@ export default {
721
722
  if (
722
723
  this.type !== WORKLOAD_TYPES.JOB &&
723
724
  this.type !== WORKLOAD_TYPES.CRON_JOB &&
724
- this.mode === _CREATE
725
+ (this.mode === _CREATE || this.realMode === _CLONE)
725
726
  ) {
726
727
  if (!template.metadata) {
727
728
  template.metadata = { labels: this.value.workloadSelector };
@@ -784,6 +785,13 @@ export default {
784
785
 
785
786
  template.metadata.namespace = this.value.metadata.namespace;
786
787
 
788
+ // Handle the case where the user has changed the name of the workload
789
+ // Only do this for clone. Not allowed for edit
790
+ if (this.realMode === _CLONE) {
791
+ template.metadata.name = this.value.metadata.name;
792
+ template.metadata.description = this.value.metadata.description;
793
+ }
794
+
787
795
  // delete this.value.kind;
788
796
  if (this.container && !this.container.name) {
789
797
  this.$set(this.container, 'name', this.value.metadata.name);
package/layouts/blank.vue CHANGED
@@ -1,7 +1,9 @@
1
1
  <script>
2
2
  import Brand from '@shell/mixins/brand';
3
+ import Inactivity from '@shell/components/Inactivity';
3
4
 
4
5
  export default {
6
+ components: { Inactivity },
5
7
  middleware: ['authenticated'],
6
8
  mixins: [Brand],
7
9
  };
@@ -10,6 +12,8 @@ export default {
10
12
  <template>
11
13
  <main class="main-layout">
12
14
  <nuxt />
15
+
16
+ <Inactivity />
13
17
  </main>
14
18
  </template>
15
19
 
@@ -16,6 +16,7 @@ import PromptModal from '@shell/components/PromptModal';
16
16
  import AssignTo from '@shell/components/AssignTo';
17
17
  import Group from '@shell/components/nav/Group';
18
18
  import Header from '@shell/components/nav/Header';
19
+ import Inactivity from '@shell/components/Inactivity';
19
20
  import Brand from '@shell/mixins/brand';
20
21
  import FixedBanner from '@shell/components/FixedBanner';
21
22
  import AwsComplianceBanner from '@shell/components/AwsComplianceBanner';
@@ -57,6 +58,7 @@ export default {
57
58
  AwsComplianceBanner,
58
59
  AzureWarning,
59
60
  DraggableZone,
61
+ Inactivity
60
62
  },
61
63
 
62
64
  mixins: [PageHeaderActions, Brand, BrowserTabVisibility],
@@ -765,6 +767,7 @@ export default {
765
767
  </div>
766
768
  <FixedBanner :footer="true" />
767
769
  <GrowlManager />
770
+ <Inactivity />
768
771
  <DraggableZone ref="draggableZone" />
769
772
  </div>
770
773
  </template>
package/layouts/home.vue CHANGED
@@ -7,6 +7,7 @@ import { mapPref, THEME_SHORTCUT } from '@shell/store/prefs';
7
7
  import AwsComplianceBanner from '@shell/components/AwsComplianceBanner';
8
8
  import AzureWarning from '@shell/components/auth/AzureWarning';
9
9
  import BrowserTabVisibility from '@shell/mixins/browser-tab-visibility';
10
+ import Inactivity from '@shell/components/Inactivity';
10
11
  import { mapState } from 'vuex';
11
12
 
12
13
  export default {
@@ -16,7 +17,8 @@ export default {
16
17
  FixedBanner,
17
18
  GrowlManager,
18
19
  AzureWarning,
19
- AwsComplianceBanner
20
+ AwsComplianceBanner,
21
+ Inactivity
20
22
  },
21
23
 
22
24
  mixins: [Brand, BrowserTabVisibility],
@@ -51,6 +53,7 @@ export default {
51
53
  <template>
52
54
  <div class="dashboard-root">
53
55
  <FixedBanner :header="true" />
56
+ <Inactivity />
54
57
  <AwsComplianceBanner />
55
58
  <AzureWarning />
56
59
 
package/layouts/plain.vue CHANGED
@@ -11,6 +11,7 @@ import GrowlManager from '@shell/components/GrowlManager';
11
11
  import AwsComplianceBanner from '@shell/components/AwsComplianceBanner';
12
12
  import AzureWarning from '@shell/components/auth/AzureWarning';
13
13
  import BrowserTabVisibility from '@shell/mixins/browser-tab-visibility';
14
+ import Inactivity from '@shell/components/Inactivity';
14
15
 
15
16
  export default {
16
17
 
@@ -23,7 +24,8 @@ export default {
23
24
  FixedBanner,
24
25
  GrowlManager,
25
26
  AwsComplianceBanner,
26
- AzureWarning
27
+ AzureWarning,
28
+ Inactivity
27
29
  },
28
30
 
29
31
  middleware: ['authenticated'],
@@ -83,6 +85,7 @@ export default {
83
85
 
84
86
  <FixedBanner :footer="true" />
85
87
  <GrowlManager />
88
+ <Inactivity />
86
89
  </div>
87
90
  </template>
88
91
 
@@ -393,6 +393,30 @@ export default class ProvCluster extends SteveModel {
393
393
  }
394
394
  }
395
395
 
396
+ get machinePoolDefaults() {
397
+ return this.spec.rkeConfig?.machinePoolDefaults;
398
+ }
399
+
400
+ set defaultHostnameLengthLimit(value) {
401
+ this.spec.rkeConfig = this.spec.rkeConfig || {};
402
+ this.spec.rkeConfig.machinePoolDefaults = this.spec.rkeConfig.machinePoolDefaults || {};
403
+ this.spec.rkeConfig.machinePoolDefaults.hostnameLengthLimit = value;
404
+ }
405
+
406
+ get defaultHostnameLengthLimit() {
407
+ return this.spec.rkeConfig?.machinePoolDefaults?.hostnameLengthLimit;
408
+ }
409
+
410
+ removeDefaultHostnameLengthLimit() {
411
+ if (this.machinePoolDefaults?.hostnameLengthLimit) {
412
+ delete this.spec.rkeConfig.machinePoolDefaults.hostnameLengthLimit;
413
+
414
+ if (Object.keys(this.spec?.rkeConfig?.machinePoolDefaults).length === 0) {
415
+ delete this.spec.rkeConfig.machinePoolDefaults;
416
+ }
417
+ }
418
+ }
419
+
396
420
  get nodes() {
397
421
  return this.$rootGetters['management/all'](MANAGEMENT.NODE).filter(node => node.id.startsWith(this.mgmtClusterId));
398
422
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rancher/shell",
3
- "version": "0.3.6",
3
+ "version": "0.3.8",
4
4
  "description": "Rancher Dashboard Shell",
5
5
  "repository": "https://github.com/rancherlabs/dashboard",
6
6
  "license": "Apache-2.0",
@@ -23,7 +23,6 @@ import {
23
23
  WORKLOAD_TYPES,
24
24
  COUNT,
25
25
  CATALOG,
26
- POD,
27
26
  PSP,
28
27
  } from '@shell/config/types';
29
28
  import { mapPref, CLUSTER_TOOLS_TIP, PSP_DEPRECATION_BANNER } from '@shell/store/prefs';
@@ -264,11 +263,9 @@ export default {
264
263
  },
265
264
 
266
265
  podsUsed() {
267
- const pods = resourceCounts(this.$store, POD);
268
-
269
266
  return {
270
267
  total: parseSi(this.currentCluster?.status?.allocatable?.pods || '0'),
271
- useful: pods.total
268
+ useful: parseSi(this.currentCluster?.status?.requested?.pods || '0'),
272
269
  };
273
270
  },
274
271
 
@@ -23,13 +23,17 @@ export default {
23
23
  async fetch() {
24
24
  try {
25
25
  this.uiPerfSetting = await this.$store.dispatch('management/find', { type: MANAGEMENT.SETTING, id: SETTING.UI_PERFORMANCE });
26
- } catch (e) {
26
+ } catch {
27
27
  this.uiPerfSetting = await this.$store.dispatch('management/create', { type: MANAGEMENT.SETTING }, { root: true });
28
28
  // Setting does not exist - create a new one
29
29
  this.uiPerfSetting.value = JSON.stringify(DEFAULT_PERF_SETTING);
30
30
  this.uiPerfSetting.metadata = { name: SETTING.UI_PERFORMANCE };
31
31
  }
32
32
 
33
+ try {
34
+ this.authUserTTL = await this.$store.dispatch(`management/find`, { type: MANAGEMENT.SETTING, id: SETTING.AUTH_USER_SESSION_TTL_MINUTES });
35
+ } catch {}
36
+
33
37
  const sValue = this.uiPerfSetting?.value || JSON.stringify(DEFAULT_PERF_SETTING);
34
38
 
35
39
  this.value = {
@@ -42,11 +46,13 @@ export default {
42
46
 
43
47
  data() {
44
48
  return {
45
- uiPerfSetting: DEFAULT_PERF_SETTING,
46
- bannerVal: {},
47
- value: {},
48
- errors: [],
49
- gcStartedEnabled: null
49
+ uiPerfSetting: DEFAULT_PERF_SETTING,
50
+ authUserTTL: null,
51
+ bannerVal: {},
52
+ value: {},
53
+ errors: [],
54
+ gcStartedEnabled: null,
55
+ isInactivityThresholdValid: false,
50
56
  };
51
57
  },
52
58
 
@@ -56,9 +62,28 @@ export default {
56
62
 
57
63
  return schema?.resourceMethods?.includes('PUT') ? _EDIT : _VIEW;
58
64
  },
65
+
66
+ canSave() {
67
+ return this.value.inactivity.enabled ? this.isInactivityThresholdValid : true;
68
+ }
59
69
  },
60
70
 
61
71
  methods: {
72
+ validateInactivityThreshold(value) {
73
+ if (!this.authUserTTL?.value) {
74
+ this.isInactivityThresholdValid = true;
75
+
76
+ return;
77
+ }
78
+
79
+ if (parseInt(value) > parseInt(this.authUserTTL?.value)) {
80
+ this.isInactivityThresholdValid = false;
81
+
82
+ return this.t('performance.inactivity.authUserTTL', { current: this.authUserTTL.value });
83
+ }
84
+ this.isInactivityThresholdValid = true;
85
+ },
86
+
62
87
  async save(btnCB) {
63
88
  this.uiPerfSetting.value = JSON.stringify(this.value);
64
89
  this.errors = [];
@@ -89,8 +114,36 @@ export default {
89
114
  </h1>
90
115
  <div>
91
116
  <div class="ui-perf-setting">
92
- <!-- Websocket Notifications -->
117
+ <!-- Inactivity -->
93
118
  <div class="mt-20">
119
+ <h2>{{ t('performance.inactivity.title') }}</h2>
120
+ <p>{{ t('performance.inactivity.description') }}</p>
121
+ <Checkbox
122
+ v-model="value.inactivity.enabled"
123
+ :mode="mode"
124
+ :label="t('performance.inactivity.checkboxLabel')"
125
+ class="mt-10 mb-20"
126
+ :primary="true"
127
+ />
128
+ <div class="ml-20">
129
+ <LabeledInput
130
+ v-model="value.inactivity.threshold"
131
+ :mode="mode"
132
+ :label="t('performance.inactivity.inputLabel')"
133
+ :disabled="!value.inactivity.enabled"
134
+ class="input mb-10"
135
+ type="number"
136
+ min="0"
137
+ :rules="[validateInactivityThreshold]"
138
+ />
139
+ <span
140
+ v-clean-html="t('performance.inactivity.information', {}, true)"
141
+ :class="{ 'text-muted': !value.incrementalLoading.enabled }"
142
+ />
143
+ </div>
144
+ </div>
145
+ <!-- Websocket Notifications -->
146
+ <div class="mt-40">
94
147
  <h2>{{ t('performance.websocketNotification.label') }}</h2>
95
148
  <p>{{ t('performance.websocketNotification.description') }}</p>
96
149
  <Checkbox
@@ -296,6 +349,7 @@ export default {
296
349
  <AsyncButton
297
350
  class="pull-right mt-20"
298
351
  mode="apply"
352
+ :disabled="!canSave"
299
353
  @click="save"
300
354
  />
301
355
  </div>
@@ -82,7 +82,7 @@ pushd ${BASE_DIR} > /dev/null
82
82
 
83
83
  if [ "${GITHUB_BUILD}" == "true" ]; then
84
84
  # Determine if gh-pages build is possible
85
- if [[ "${GITHUB_BRANCH}" == "gh-pages" ]] && ! git show-ref -q --heads gh-pages; then
85
+ if [[ "${GITHUB_BRANCH}" == "gh-pages" ]] && ! git show-ref -q gh-pages; then
86
86
  echo -e "${YELLOW}'gh-pages' branch not found, this branch must exist before running this script${RESET}"
87
87
  exit 1
88
88
  fi
package/store/index.js CHANGED
@@ -1,32 +1,37 @@
1
- import Steve from '@shell/plugins/steve';
2
- import {
3
- COUNT, NAMESPACE, NORMAN, MANAGEMENT, FLEET, UI, VIRTUAL_HARVESTER_PROVIDER, DEFAULT_WORKSPACE
4
- } from '@shell/config/types';
5
- import { CLUSTER as CLUSTER_PREF, NAMESPACE_FILTERS, LAST_NAMESPACE, WORKSPACE } from '@shell/store/prefs';
6
- import { allHash, allHashSettled } from '@shell/utils/promise';
7
- import { ClusterNotFoundError, ApiError } from '@shell/utils/error';
8
- import { sortBy } from '@shell/utils/sort';
9
- import { filterBy, findBy } from '@shell/utils/array';
10
- import { BOTH, CLUSTER_LEVEL, NAMESPACED } from '@shell/store/type-map';
11
- import { NAME as EXPLORER } from '@shell/config/product/explorer';
12
- import { TIMED_OUT, LOGGED_OUT, _FLAGGED, UPGRADED } from '@shell/config/query-params';
1
+ import { BACK_TO } from '@shell/config/local-storage';
13
2
  import { setBrand, setVendor } from '@shell/config/private-label';
14
- import { addParam } from '@shell/utils/url';
3
+ import { NAME as EXPLORER } from '@shell/config/product/explorer';
4
+ import { LOGGED_OUT, TIMED_OUT, UPGRADED, _FLAGGED } from '@shell/config/query-params';
15
5
  import { SETTING } from '@shell/config/settings';
16
- import semver from 'semver';
17
- import { BACK_TO } from '@shell/config/local-storage';
18
- import { STEVE_MODEL_TYPES } from '@shell/plugins/steve/getters';
6
+ import {
7
+ COUNT,
8
+ DEFAULT_WORKSPACE,
9
+ FLEET,
10
+ MANAGEMENT,
11
+ NAMESPACE, NORMAN,
12
+ UI, VIRTUAL_HARVESTER_PROVIDER
13
+ } from '@shell/config/types';
19
14
  import { BY_TYPE } from '@shell/plugins/dashboard-store/classify';
15
+ import Steve from '@shell/plugins/steve';
16
+ import { STEVE_MODEL_TYPES } from '@shell/plugins/steve/getters';
17
+ import { CLUSTER as CLUSTER_PREF, LAST_NAMESPACE, NAMESPACE_FILTERS, WORKSPACE } from '@shell/store/prefs';
18
+ import { BOTH, CLUSTER_LEVEL, NAMESPACED } from '@shell/store/type-map';
19
+ import { filterBy, findBy } from '@shell/utils/array';
20
+ import { ApiError, ClusterNotFoundError } from '@shell/utils/error';
21
+ import { gcActions, gcGetters } from '@shell/utils/gc/gc-root-store';
20
22
  import {
21
- NAMESPACE_FILTER_ALL_USER as ALL_USER,
22
- NAMESPACE_FILTER_ALL_SYSTEM as ALL_SYSTEM,
23
23
  NAMESPACE_FILTER_ALL_ORPHANS as ALL_ORPHANS,
24
- NAMESPACE_FILTER_NAMESPACED_YES as NAMESPACED_YES,
24
+ NAMESPACE_FILTER_ALL_SYSTEM as ALL_SYSTEM,
25
+ NAMESPACE_FILTER_ALL_USER as ALL_USER,
25
26
  NAMESPACE_FILTER_NAMESPACED_NO as NAMESPACED_NO,
26
27
  NAMESPACE_FILTER_NAMESPACED_PREFIX as NAMESPACED_PREFIX,
28
+ NAMESPACE_FILTER_NAMESPACED_YES as NAMESPACED_YES,
27
29
  splitNamespaceFilterKey,
28
30
  } from '@shell/utils/namespace-filter';
29
- import { gcActions, gcGetters } from '@shell/utils/gc/gc-root-store';
31
+ import { allHash, allHashSettled } from '@shell/utils/promise';
32
+ import { sortBy } from '@shell/utils/sort';
33
+ import { addParam } from '@shell/utils/url';
34
+ import semver from 'semver';
30
35
 
31
36
  // Disables strict mode for all store instances to prevent warning about changing state outside of mutations
32
37
  // because it's more efficient to do that sometimes.
@@ -591,7 +596,6 @@ export const mutations = {
591
596
  state.isMultiCluster = isMultiCluster;
592
597
  state.isRancher = isRancher;
593
598
  },
594
-
595
599
  clusterReady(state, ready) {
596
600
  state.clusterReady = ready;
597
601
  },
@@ -1122,5 +1126,16 @@ export const actions = {
1122
1126
  commit(`setIsSingleProduct`, isSingleProduct);
1123
1127
  },
1124
1128
 
1129
+ unsubscribe( { state, dispatch }) {
1130
+ // It would be nice to grab all vuex module stores that we've registered, apparently this is only possible via the
1131
+ // internal properties store._modules.root._children.
1132
+ // So instead loop through all state entries to find stores
1133
+ return Object.entries(state).filter(([storeName, storeState]) => {
1134
+ if (storeState?.allowStreaming) {
1135
+ dispatch(`${ storeName }/unsubscribe`);
1136
+ }
1137
+ });
1138
+ },
1139
+
1125
1140
  ...gcActions
1126
1141
  };
package/utils/gc/gc.ts CHANGED
@@ -29,7 +29,7 @@ class GarbageCollect {
29
29
  * To avoid JSON.parse on the `ui-performance` setting keep a local cache
30
30
  */
31
31
  private getUiPerfGarbageCollection = (rootState: any) => {
32
- const uiPerfSetting = rootState.management.types[MANAGEMENT.SETTING].list.find((s: any) => s.id === SETTING.UI_PERFORMANCE);
32
+ const uiPerfSetting = rootState.management.types[MANAGEMENT.SETTING]?.list.find((s: any) => s.id === SETTING.UI_PERFORMANCE);
33
33
 
34
34
  if (!uiPerfSetting || !uiPerfSetting.value) {
35
35
  // Could be in the process of logging out