@rancher/shell 0.3.7 → 0.3.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/assets/translations/en-us.yaml +55 -14
  2. package/babel.config.js +17 -4
  3. package/components/CodeMirror.vue +146 -14
  4. package/components/ContainerResourceLimit.vue +14 -1
  5. package/components/CruResource.vue +21 -5
  6. package/components/ExplorerProjectsNamespaces.vue +5 -1
  7. package/components/GroupPanel.vue +57 -0
  8. package/components/Inactivity.vue +229 -0
  9. package/components/YamlEditor.vue +2 -2
  10. package/components/form/ArrayList.vue +1 -1
  11. package/components/form/KeyValue.vue +34 -1
  12. package/components/form/MatchExpressions.vue +120 -21
  13. package/components/form/NodeAffinity.vue +54 -4
  14. package/components/form/PodAffinity.vue +160 -47
  15. package/components/form/Tolerations.vue +40 -4
  16. package/components/form/__tests__/ArrayList.test.ts +3 -3
  17. package/components/form/__tests__/MatchExpressions.test.ts +1 -1
  18. package/components/nav/Header.vue +2 -0
  19. package/config/settings.ts +10 -1
  20. package/core/plugins-loader.js +0 -2
  21. package/creators/app/files/.gitignore +73 -0
  22. package/creators/app/init +1 -0
  23. package/edit/configmap.vue +33 -6
  24. package/edit/provisioning.cattle.io.cluster/AgentConfiguration.vue +326 -0
  25. package/edit/provisioning.cattle.io.cluster/index.vue +1 -0
  26. package/edit/provisioning.cattle.io.cluster/rke2.vue +63 -15
  27. package/edit/workload/mixins/workload.js +12 -4
  28. package/layouts/blank.vue +4 -0
  29. package/layouts/default.vue +3 -0
  30. package/layouts/home.vue +4 -1
  31. package/layouts/plain.vue +4 -1
  32. package/mixins/chart.js +1 -1
  33. package/models/batch.cronjob.js +18 -3
  34. package/models/provisioning.cattle.io.cluster.js +24 -0
  35. package/models/workload.js +1 -1
  36. package/package.json +2 -3
  37. package/pages/auth/login.vue +1 -0
  38. package/pages/c/_cluster/explorer/index.vue +1 -4
  39. package/pages/c/_cluster/settings/performance.vue +61 -7
  40. package/pages/prefs.vue +18 -2
  41. package/pkg/vue.config.js +0 -1
  42. package/plugins/codemirror.js +158 -0
  43. package/public/index.html +1 -1
  44. package/store/index.js +36 -21
  45. package/types/shell/index.d.ts +20 -1
  46. package/utils/create-yaml.js +105 -8
  47. package/utils/settings.ts +12 -0
  48. package/vue.config.js +2 -2
@@ -69,6 +69,7 @@ import S3Config from './S3Config';
69
69
  import SelectCredential from './SelectCredential';
70
70
  import AdvancedSection from '@shell/components/AdvancedSection.vue';
71
71
  import { ELEMENTAL_SCHEMA_IDS, KIND, ELEMENTAL_CLUSTER_PROVIDER } from '../../config/elemental-types';
72
+ import AgentConfiguration, { cleanAgentConfiguration } from './AgentConfiguration';
72
73
 
73
74
  const PUBLIC = 'public';
74
75
  const PRIVATE = 'private';
@@ -96,6 +97,8 @@ const NODE_TOTAL = {
96
97
  icon: 'icon-checkmark'
97
98
  }
98
99
  };
100
+ const CLUSTER_AGENT_CUSTOMIZATION = 'clusterAgentDeploymentCustomization';
101
+ const FLEET_AGENT_CUSTOMIZATION = 'fleetAgentDeploymentCustomization';
99
102
 
100
103
  export default {
101
104
  components: {
@@ -107,6 +110,7 @@ export default {
107
110
  BadgeState,
108
111
  Banner,
109
112
  Checkbox,
113
+ AgentConfiguration,
110
114
  ClusterMembershipEditor,
111
115
  CruResource,
112
116
  DrainOptions,
@@ -274,6 +278,18 @@ export default {
274
278
 
275
279
  this.userChartValues[key] = value;
276
280
  });
281
+
282
+ // Ensure we have empty models for the two agent configurations
283
+
284
+ // Cluster Agent Configuration
285
+ if ( !this.value.spec[CLUSTER_AGENT_CUSTOMIZATION]) {
286
+ set(this.value.spec, CLUSTER_AGENT_CUSTOMIZATION, {});
287
+ }
288
+
289
+ // Fleet Agent Configuration
290
+ if ( !this.value.spec[FLEET_AGENT_CUSTOMIZATION] ) {
291
+ set(this.value.spec, FLEET_AGENT_CUSTOMIZATION, {});
292
+ }
277
293
  },
278
294
 
279
295
  data() {
@@ -306,12 +322,7 @@ export default {
306
322
  const lastDefaultPodSecurityPolicyTemplateName = this.value.spec.defaultPodSecurityPolicyTemplateName;
307
323
  const previousKubernetesVersion = this.value.spec.kubernetesVersion;
308
324
 
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) : {};
325
+ const truncateLimit = this.value.defaultHostnameLengthLimit;
315
326
 
316
327
  return {
317
328
  loadedOnce: false,
@@ -350,7 +361,6 @@ export default {
350
361
  psps: null, // List of policies if any
351
362
  truncateHostnames: truncateLimit === NETBIOS_TRUNCATION_LENGTH,
352
363
  truncateLimit,
353
- supportsTruncation: !!specSchema?.resourceFields?.machinePoolDefaults,
354
364
  };
355
365
  },
356
366
 
@@ -1087,6 +1097,7 @@ export default {
1087
1097
  created() {
1088
1098
  this.registerBeforeHook(this.saveMachinePools, 'save-machine-pools');
1089
1099
  this.registerBeforeHook(this.setRegistryConfig, 'set-registry-config');
1100
+ this.registerBeforeHook(this.agentConfigurationCleanup, 'cleanup-agent-config');
1090
1101
  this.registerAfterHook(this.cleanupMachinePools, 'cleanup-machine-pools');
1091
1102
  this.registerAfterHook(this.saveRoleBindings, 'save-role-bindings');
1092
1103
  },
@@ -1095,19 +1106,20 @@ export default {
1095
1106
  nlToBr,
1096
1107
  set,
1097
1108
 
1109
+ agentConfigurationCleanup() {
1110
+ // Clean agent configuration objects, so we only send values when the user has configured something
1111
+ cleanAgentConfiguration(this.value.spec, CLUSTER_AGENT_CUSTOMIZATION);
1112
+ cleanAgentConfiguration(this.value.spec, FLEET_AGENT_CUSTOMIZATION);
1113
+ },
1114
+
1098
1115
  /**
1099
1116
  * set instanceNameLimit to 15 to all pool machine if truncateHostnames checkbox is clicked
1100
1117
  */
1101
1118
  truncateName() {
1102
1119
  if (this.truncateHostnames) {
1103
- this.value.machinePoolDefaults = this.value.machinePoolDefaults || {};
1104
- this.value.machinePoolDefaults.hostnameLengthLimit = 15;
1120
+ this.value.defaultHostnameLengthLimit = NETBIOS_TRUNCATION_LENGTH;
1105
1121
  } else {
1106
- delete this.value.machinePoolDefaults.hostnameLengthLimit;
1107
-
1108
- if (Object.keys(this.value.machinePoolDefaults).length === 0) {
1109
- delete this.value.machinePoolDefaults;
1110
- }
1122
+ this.value.removeDefaultHostnameLengthLimit();
1111
1123
  }
1112
1124
  },
1113
1125
  /**
@@ -1478,7 +1490,20 @@ export default {
1478
1490
  delete this.value.spec.rkeConfig.machineGlobalConfig.profile;
1479
1491
  }
1480
1492
 
1493
+ // store the current data for fleet and cluster agent so that we can re-apply it later if the save fails
1494
+ // we also have a before hook (check created() hooks) where the cleanup of the data occurs
1495
+ const clusterAgentDeploymentCustomization = JSON.parse(JSON.stringify(this.value.spec[CLUSTER_AGENT_CUSTOMIZATION]));
1496
+ const fleetAgentDeploymentCustomization = JSON.parse(JSON.stringify(this.value.spec[FLEET_AGENT_CUSTOMIZATION]));
1497
+
1481
1498
  await this.save(btnCb);
1499
+
1500
+ // comes from createEditView mixin
1501
+ // if there are any errors saving, restore the agent config data
1502
+ if (this.errors?.length) {
1503
+ // Ensure the agent configuration is set back to the values before we changed (cleaned) it
1504
+ set(this.value.spec, CLUSTER_AGENT_CUSTOMIZATION, clusterAgentDeploymentCustomization);
1505
+ set(this.value.spec, FLEET_AGENT_CUSTOMIZATION, fleetAgentDeploymentCustomization);
1506
+ }
1482
1507
  },
1483
1508
  // create a secret to reference the harvester cluster kubeconfig in rkeConfig
1484
1509
  async createKubeconfigSecret(kubeconfig = '') {
@@ -2598,7 +2623,6 @@ export default {
2598
2623
  />
2599
2624
  </div>
2600
2625
  <div
2601
- v-if="supportsTruncation"
2602
2626
  class="col span-6"
2603
2627
  >
2604
2628
  <Checkbox
@@ -2833,6 +2857,30 @@ export default {
2833
2857
  </div>
2834
2858
  </Tab>
2835
2859
 
2860
+ <!-- Cluster Agent Configuration -->
2861
+ <Tab
2862
+ name="clusteragentconfig"
2863
+ label-key="cluster.agentConfig.tabs.cluster"
2864
+ >
2865
+ <AgentConfiguration
2866
+ v-model="value.spec.clusterAgentDeploymentCustomization"
2867
+ type="cluster"
2868
+ :mode="mode"
2869
+ />
2870
+ </Tab>
2871
+
2872
+ <!-- Fleet Agent Configuration -->
2873
+ <Tab
2874
+ name="fleetagentconfig"
2875
+ label-key="cluster.agentConfig.tabs.fleet"
2876
+ >
2877
+ <AgentConfiguration
2878
+ v-model="value.spec.fleetAgentDeploymentCustomization"
2879
+ type="fleet"
2880
+ :mode="mode"
2881
+ />
2882
+ </Tab>
2883
+
2836
2884
  <!-- Advanced -->
2837
2885
  <Tab
2838
2886
  v-if="haveArgInfo || agentArgs['protect-kernel-defaults']"
@@ -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
 
package/mixins/chart.js CHANGED
@@ -14,7 +14,7 @@ import { CAPI, CATALOG } from '@shell/config/types';
14
14
  import { isPrerelease } from '@shell/utils/version';
15
15
  import difference from 'lodash/difference';
16
16
  import { LINUX } from '@shell/store/catalog';
17
- import { clone } from 'utils/object';
17
+ import { clone } from '@shell/utils/object';
18
18
  import { merge } from 'lodash';
19
19
 
20
20
  export default {
@@ -2,6 +2,7 @@ import { insertAt } from '@shell/utils/array';
2
2
  import { clone } from '@shell/utils/object';
3
3
  import { WORKLOAD_TYPES } from '@shell/config/types';
4
4
  import Workload from './workload';
5
+ import { WORKLOAD_TYPE_TO_KIND_MAPPING } from '@shell/detail/workload/index';
5
6
 
6
7
  export default class CronJob extends Workload {
7
8
  get state() {
@@ -47,12 +48,26 @@ export default class CronJob extends Workload {
47
48
  }
48
49
 
49
50
  async runNow() {
50
- const job = await this.$dispatch('create', clone(this.spec.jobTemplate));
51
+ const ownerRef = {
52
+ apiVersion: this.apiVersion,
53
+ controller: true,
54
+ kind: this.kind,
55
+ name: this.metadata.name,
56
+ uid: this.metadata.uid
57
+ };
58
+
59
+ // Set type and kind to ensure the correct model is returned (via classify). This object will be persisted to the store
60
+ const job = await this.$dispatch('create', {
61
+ type: WORKLOAD_TYPES.JOB,
62
+ kind: WORKLOAD_TYPE_TO_KIND_MAPPING[WORKLOAD_TYPES.JOB],
63
+ ...clone(this.spec.jobTemplate)
64
+ });
51
65
 
52
- job.type = WORKLOAD_TYPES.JOB;
53
66
  job.metadata = job.metadata || {};
54
67
  job.metadata.namespace = this.metadata.namespace;
55
- job.metadata.generateName = `${ this.metadata.name }-`;
68
+ // Can't use `generatedName` and no `name`... as this fails schema validation
69
+ job.metadata.name = `${ this.metadata.name }-${ Date.now() }`;
70
+ job.metadata.ownerReferences = [ownerRef];
56
71
 
57
72
  await job.save();
58
73
 
@@ -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
  }
@@ -296,7 +296,7 @@ export default class Workload extends WorkloadService {
296
296
  }
297
297
 
298
298
  get endpoint() {
299
- return this?.metadata?.annotations[CATTLE_PUBLIC_ENDPOINTS];
299
+ return this?.metadata?.annotations?.[CATTLE_PUBLIC_ENDPOINTS];
300
300
  }
301
301
 
302
302
  get desired() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rancher/shell",
3
- "version": "0.3.7",
3
+ "version": "0.3.9",
4
4
  "description": "Rancher Dashboard Shell",
5
5
  "repository": "https://github.com/rancherlabs/dashboard",
6
6
  "license": "Apache-2.0",
@@ -69,7 +69,7 @@
69
69
  "dagre-d3": "0.6.4",
70
70
  "dayjs": "1.8.29",
71
71
  "diff2html": "2.11.2",
72
- "dompurify": "2.0.12",
72
+ "dompurify": "2.4.5",
73
73
  "eslint": "7.32.0",
74
74
  "eslint-config-standard": "16.0.3",
75
75
  "eslint-import-resolver-node": "0.3.4",
@@ -89,7 +89,6 @@
89
89
  "jest": "27.5.1",
90
90
  "jest-serializer-vue": "2.0.2",
91
91
  "jexl": "2.2.2",
92
- "jquery": "3.5.1",
93
92
  "js-cookie": "2.2.1",
94
93
  "js-yaml": "4.1.0",
95
94
  "js-yaml-loader": "1.2.2",
@@ -303,6 +303,7 @@ export default {
303
303
  </h1>
304
304
  <div
305
305
  class="login-messages"
306
+ data-testid="login__messages"
306
307
  :class="{'login-messages--hasContent': hasLoginMessage}"
307
308
  >
308
309
  <Banner
@@ -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>
package/pages/prefs.vue CHANGED
@@ -163,7 +163,9 @@ export default {
163
163
  <h4 v-t="'prefs.language'" />
164
164
  <div class="row">
165
165
  <div class="col span-4">
166
- <LocaleSelector />
166
+ <LocaleSelector
167
+ data-testid="prefs__languageSelector"
168
+ />
167
169
  </div>
168
170
  </div>
169
171
  </div>
@@ -173,6 +175,7 @@ export default {
173
175
  <h4 v-t="'prefs.theme.label'" />
174
176
  <ButtonGroup
175
177
  v-model="theme"
178
+ data-testid="prefs__themeOptions"
176
179
  :options="themeOptions"
177
180
  />
178
181
  <div class="mt-10">
@@ -190,7 +193,9 @@ export default {
190
193
  >
191
194
  <hr>
192
195
  <h4 v-t="'prefs.landing.label'" />
193
- <LandingPagePreference />
196
+ <LandingPagePreference
197
+ data-testid="prefs__landingPagePreference"
198
+ />
194
199
  </div>
195
200
  <!-- Display Settings -->
196
201
  <div class="mt-10 mb-10">
@@ -203,6 +208,7 @@ export default {
203
208
  <div class="col span-4">
204
209
  <LabeledSelect
205
210
  v-model="dateFormat"
211
+ data-testid="prefs__displaySetting__dateFormat"
206
212
  :label="t('prefs.dateFormat.label')"
207
213
  :options="dateOptions"
208
214
  />
@@ -210,6 +216,7 @@ export default {
210
216
  <div class="col span-4">
211
217
  <LabeledSelect
212
218
  v-model="timeFormat"
219
+ data-testid="prefs__displaySetting__timeFormat"
213
220
  :label="t('prefs.timeFormat.label')"
214
221
  :options="timeOptions"
215
222
  />
@@ -220,6 +227,7 @@ export default {
220
227
  <div class="col span-4">
221
228
  <LabeledSelect
222
229
  v-model.number="perPage"
230
+ data-testid="prefs__displaySetting__perPage"
223
231
  :label="t('prefs.perPage.label')"
224
232
  :options="perPageOptions"
225
233
  option-key="value"
@@ -230,6 +238,7 @@ export default {
230
238
  <div class="col span-4">
231
239
  <LabeledSelect
232
240
  v-model.number="menuMaxClusters"
241
+ data-testid="prefs__displaySetting__menuMaxClusters"
233
242
  :label="t('prefs.clusterToShow.label')"
234
243
  :options="menuClusterOptions"
235
244
  option-key="value"
@@ -245,6 +254,7 @@ export default {
245
254
  <h4 v-t="'prefs.confirmationSetting.title'" />
246
255
  <Checkbox
247
256
  v-model="scalingDownPrompt"
257
+ data-testid="prefs__scalingDownPrompt"
248
258
  :label="t('prefs.confirmationSetting.scalingDownPrompt')"
249
259
  class="mt-10"
250
260
  />
@@ -255,18 +265,21 @@ export default {
255
265
  <h4 v-t="'prefs.advFeatures.title'" />
256
266
  <Checkbox
257
267
  v-model="viewInApi"
268
+ data-testid="prefs__viewInApi"
258
269
  :label="t('prefs.advFeatures.viewInApi', {}, true)"
259
270
  class="mt-10"
260
271
  />
261
272
  <br>
262
273
  <Checkbox
263
274
  v-model="allNamespaces"
275
+ data-testid="prefs__allNamespaces"
264
276
  :label="t('prefs.advFeatures.allNamespaces', {}, true)"
265
277
  class="mt-20"
266
278
  />
267
279
  <br>
268
280
  <Checkbox
269
281
  v-model="themeShortcut"
282
+ data-testid="prefs__themeShortcut"
270
283
  :label="t('prefs.advFeatures.themeShortcut', {}, true)"
271
284
  class="mt-20"
272
285
  />
@@ -274,6 +287,7 @@ export default {
274
287
  <Checkbox
275
288
  v-if="!isSingleProduct"
276
289
  v-model="hideDescriptions"
290
+ data-testid="prefs__hideDescriptions"
277
291
  :label="t('prefs.hideDesc.label')"
278
292
  class="mt-20"
279
293
  />
@@ -292,6 +306,7 @@ export default {
292
306
  <h4 v-t="'prefs.keymap.label'" />
293
307
  <ButtonGroup
294
308
  v-model="keymap"
309
+ data-testid="prefs__keymapOptions"
295
310
  :options="keymapOptions"
296
311
  />
297
312
  </div>
@@ -304,6 +319,7 @@ export default {
304
319
  <h4 v-t="'prefs.helm.label'" />
305
320
  <ButtonGroup
306
321
  v-model="showPreRelease"
322
+ data-testid="prefs__helmOptions"
307
323
  :options="helmOptions"
308
324
  />
309
325
  </div>