@rancher/shell 2.0.1 → 2.0.2-rc.1

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 (89) hide show
  1. package/assets/translations/en-us.yaml +51 -26
  2. package/assets/translations/zh-hans.yaml +1 -0
  3. package/components/AssignTo.vue +2 -0
  4. package/components/Questions/index.vue +2 -2
  5. package/components/auth/RoleDetailEdit.vue +5 -4
  6. package/components/form/Members/ClusterPermissionsEditor.vue +1 -1
  7. package/components/form/ProjectMemberEditor.vue +1 -1
  8. package/components/form/ResourceLabeledSelect.vue +11 -3
  9. package/components/form/labeled-select-utils/labeled-select.utils.ts +1 -1
  10. package/config/pagination-table-headers.js +5 -4
  11. package/config/roles.ts +34 -19
  12. package/config/router/navigation-guards/attempt-first-login.js +1 -1
  13. package/config/router/navigation-guards/authentication.js +1 -1
  14. package/config/router/navigation-guards/i18n.js +1 -1
  15. package/config/router/navigation-guards/index.js +2 -1
  16. package/config/router/navigation-guards/load-initial-settings.js +1 -1
  17. package/config/router/navigation-guards/runtime-extension-route.js +31 -0
  18. package/config/router/routes.js +10 -1
  19. package/config/uiplugins.js +130 -61
  20. package/core/plugin.ts +5 -0
  21. package/core/plugins.js +7 -1
  22. package/edit/provisioning.cattle.io.cluster/__tests__/Basics.test.ts +86 -13
  23. package/edit/provisioning.cattle.io.cluster/__tests__/DirectoryConfig.test.ts +3 -134
  24. package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +209 -0
  25. package/edit/provisioning.cattle.io.cluster/index.vue +8 -4
  26. package/edit/provisioning.cattle.io.cluster/rke2.vue +115 -17
  27. package/edit/provisioning.cattle.io.cluster/tabs/AddOnAdditionalManifest.vue +50 -0
  28. package/edit/provisioning.cattle.io.cluster/tabs/AddOnConfig.vue +29 -64
  29. package/edit/provisioning.cattle.io.cluster/tabs/Basics.vue +42 -3
  30. package/edit/provisioning.cattle.io.cluster/tabs/DirectoryConfig.vue +22 -86
  31. package/edit/provisioning.cattle.io.cluster/tabs/registries/RegistryConfigs.vue +8 -2
  32. package/edit/provisioning.cattle.io.cluster/tabs/registries/__tests__/RegistryConfigs.test.ts +61 -0
  33. package/initialize/entry-helpers.js +4 -21
  34. package/mixins/__tests__/chart.test.ts +4 -1
  35. package/mixins/chart.js +30 -14
  36. package/models/__tests__/apps.deployment.test.ts +93 -0
  37. package/models/apps.deployment.js +18 -4
  38. package/models/management.cattle.io.cluster.js +2 -2
  39. package/models/management.cattle.io.user.js +3 -3
  40. package/models/nodedriver.js +5 -0
  41. package/models/provisioning.cattle.io.cluster.js +4 -0
  42. package/package.json +1 -1
  43. package/pages/404.vue +15 -0
  44. package/pages/auth/login.vue +4 -1
  45. package/pages/auth/setup.vue +4 -1
  46. package/pages/c/_cluster/explorer/index.vue +5 -0
  47. package/pages/c/_cluster/manager/jwt.authentication/index.vue +10 -4
  48. package/pages/c/_cluster/settings/performance.vue +2 -2
  49. package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +7 -10
  50. package/pages/c/_cluster/uiplugins/index.vue +24 -16
  51. package/pages/home.vue +1 -13
  52. package/plugins/dashboard-store/actions.js +1 -1
  53. package/plugins/dashboard-store/getters.js +1 -1
  54. package/plugins/steve/__tests__/getters.test.ts +5 -5
  55. package/plugins/steve/getters.js +6 -4
  56. package/plugins/steve/hybrid-class.js +1 -5
  57. package/scripts/extension/helm/charts/ui-plugin-server/Chart.yaml +1 -1
  58. package/scripts/publish-shell.sh +53 -55
  59. package/scripts/test-plugins-build.sh +45 -39
  60. package/shell/types/shell/index.d.ts +2 -0
  61. package/store/type-map.js +4 -2
  62. package/types/store/pagination.types.ts +1 -1
  63. package/utils/cluster.js +9 -0
  64. package/utils/settings.ts +3 -1
  65. package/creators/app/app.package.json +0 -14
  66. package/creators/app/files/.eslintignore +0 -16
  67. package/creators/app/files/.eslintrc.js +0 -173
  68. package/creators/app/files/.gitignore +0 -70
  69. package/creators/app/files/.gitlab-ci.yml +0 -14
  70. package/creators/app/files/.vscode/settings.json +0 -21
  71. package/creators/app/files/babel.config.js +0 -1
  72. package/creators/app/files/tsconfig.json +0 -42
  73. package/creators/app/files/vue.config.js +0 -6
  74. package/creators/app/init +0 -120
  75. package/creators/app/package.json +0 -25
  76. package/creators/pkg/files/.github/workflows/build-extension-catalog.yml +0 -24
  77. package/creators/pkg/files/.github/workflows/build-extension-charts.yml +0 -22
  78. package/creators/pkg/files/babel.config.js +0 -1
  79. package/creators/pkg/files/index.ts +0 -14
  80. package/creators/pkg/files/tsconfig.json +0 -53
  81. package/creators/pkg/files/vue.config.js +0 -1
  82. package/creators/pkg/init +0 -286
  83. package/creators/pkg/package.json +0 -19
  84. package/creators/pkg/pkg.package.json +0 -21
  85. package/creators/pkg/vue-shim.ts +0 -4
  86. package/creators/update/init +0 -56
  87. package/creators/update/package.json +0 -20
  88. package/creators/update/upgrade +0 -56
  89. package/types/shell/index.d.ts +0 -4585
@@ -3,7 +3,7 @@ import { Banner } from '@components/Banner';
3
3
 
4
4
  import Questions from '@shell/components/Questions';
5
5
  import YamlEditor from '@shell/components/YamlEditor';
6
- import { camelToTitle } from '@shell/utils/string';
6
+ import { labelForAddon } from '@shell/utils/cluster';
7
7
  import { _EDIT } from '@shell/config/query-params';
8
8
 
9
9
  export default {
@@ -29,8 +29,8 @@ export default {
29
29
  required: true,
30
30
  },
31
31
 
32
- addonVersions: {
33
- type: Array,
32
+ addonVersion: {
33
+ type: Object,
34
34
  required: false,
35
35
  default: null
36
36
  },
@@ -50,28 +50,15 @@ export default {
50
50
 
51
51
  },
52
52
 
53
+ data() {
54
+ return { labelForAddon };
55
+ },
56
+
53
57
  computed: {
54
- additionalManifest: {
55
- get() {
56
- return this.value.spec.rkeConfig.additionalManifest;
57
- },
58
- set(neu) {
59
- this.$emit('additional-manifest-changed', neu);
60
- }
61
- },
62
58
  isEdit() {
63
59
  return this.mode === _EDIT;
64
60
  },
65
- },
66
-
67
- methods: {
68
-
69
- labelForAddon(name) {
70
- const fallback = `${ camelToTitle(name.replace(/^(rke|rke2|rancher)-/, '')) } Configuration`;
71
-
72
- return this.$store.getters['i18n/withFallback'](`cluster.addonChart."${ name }"`, null, fallback);
73
- },
74
- },
61
+ }
75
62
  };
76
63
  </script>
77
64
 
@@ -84,54 +71,32 @@ export default {
84
71
  {{ t('cluster.addOns.dependencyBanner') }}
85
72
  </Banner>
86
73
  <div
87
- v-if="versionInfo && addonVersions.length"
74
+ v-if="versionInfo && addonVersion"
88
75
  :key="addonsRev"
89
76
  >
90
- <div
91
- v-for="v in addonVersions"
92
- :key="v._key"
93
- >
94
- <h3>{{ labelForAddon(v.name) }}</h3>
95
- <Questions
96
- v-if="versionInfo[v.name] && versionInfo[v.name].questions && v.name && userChartValuesTemp[v.name]"
97
- v-model="userChartValuesTemp[v.name]"
98
- :emit="true"
99
- in-store="management"
100
- :mode="mode"
101
- :tabbed="false"
102
- :source="versionInfo[v.name]"
103
- :target-namespace="value.metadata.namespace"
104
- @updated="$emit('update-questions', v.name)"
105
- />
106
- <YamlEditor
107
- v-else
108
- ref="yaml-values"
109
- :value="initYamlEditor(v.name)"
110
- :scrolling="true"
111
- :as-object="true"
112
- :editor-mode="mode === 'view' ? 'VIEW_CODE' : 'EDIT_CODE'"
113
- :hide-preview-buttons="true"
114
- @input="data => $emit('update-values', v.name, data)"
115
- />
116
- <div class="spacer" />
117
- </div>
118
- </div>
119
-
120
- <div>
121
- <h3>
122
- {{ t('cluster.addOns.additionalManifest.title') }}
123
- <i
124
- v-clean-tooltip="t('cluster.addOns.additionalManifest.tooltip')"
125
- class="icon icon-info"
126
- />
127
- </h3>
77
+ <h3>{{ labelForAddon(addonVersion.name) }}</h3>
78
+ <Questions
79
+ v-if="versionInfo[addonVersion.name] && versionInfo[addonVersion.name].questions && addonVersion.name && userChartValuesTemp[addonVersion.name]"
80
+ v-model="userChartValuesTemp[addonVersion.name]"
81
+ :emit="true"
82
+ in-store="management"
83
+ :mode="mode"
84
+ :tabbed="false"
85
+ :source="versionInfo[addonVersion.name]"
86
+ :target-namespace="value.metadata.namespace"
87
+ @updated="$emit('update-questions', addonVersion.name)"
88
+ />
128
89
  <YamlEditor
129
- ref="yaml-additional"
130
- v-model="additionalManifest"
90
+ v-else
91
+ ref="yaml-values"
92
+ :value="initYamlEditor(addonVersion.name)"
93
+ :scrolling="true"
94
+ :as-object="true"
131
95
  :editor-mode="mode === 'view' ? 'VIEW_CODE' : 'EDIT_CODE'"
132
- initial-yaml-values="# Additional Manifest YAML"
133
- class="yaml-editor"
96
+ :hide-preview-buttons="true"
97
+ @input="data => $emit('update-values', addonVersion.name, data)"
134
98
  />
99
+ <div class="spacer" />
135
100
  </div>
136
101
  </div>
137
102
  </template>
@@ -11,7 +11,7 @@ import LabeledSelect from '@shell/components/form/LabeledSelect';
11
11
  import YamlEditor from '@shell/components/YamlEditor';
12
12
  import { LEGACY } from '@shell/store/features';
13
13
  import semver from 'semver';
14
- import { _EDIT } from '@shell/config/query-params';
14
+ import { _CREATE, _EDIT } from '@shell/config/query-params';
15
15
 
16
16
  const HARVESTER = 'harvester';
17
17
 
@@ -106,6 +106,14 @@ export default {
106
106
  cloudProviderOptions: {
107
107
  type: Array,
108
108
  required: true
109
+ },
110
+ isAzureProviderUnsupported: {
111
+ type: Boolean,
112
+ required: true
113
+ },
114
+ canAzureMigrateOnEdit: {
115
+ type: Boolean,
116
+ required: true
109
117
  }
110
118
  },
111
119
 
@@ -377,9 +385,11 @@ export default {
377
385
  },
378
386
 
379
387
  canNotEditCloudProvider() {
380
- const canNotEdit = this.isEdit;
388
+ if (!this.isEdit) {
389
+ return false;
390
+ }
381
391
 
382
- return canNotEdit;
392
+ return !this.canAzureMigrateOnEdit;
383
393
  },
384
394
 
385
395
  /**
@@ -387,6 +397,20 @@ export default {
387
397
  */
388
398
  showCloudProviderAmazonAdditionalConfigWarning() {
389
399
  return !!semver.gte(this.value.spec.kubernetesVersion, 'v1.27.0') && this.agentConfig?.['cloud-provider-name'] === 'aws';
400
+ },
401
+
402
+ /**
403
+ * Display warning about unsupported Azure provider if k8s >= 1.30
404
+ */
405
+ showCloudProviderUnsupportedAzureWarning() {
406
+ return this.showCloudProvider && this.mode === _CREATE && this.isAzureProviderUnsupported;
407
+ },
408
+
409
+ /**
410
+ * Display warning about Azure provider migration from k8s versions >= 1.27 to External provider
411
+ */
412
+ showCloudProviderMigrateAzureWarning() {
413
+ return this.showCloudProvider && this.mode === _EDIT && this.canAzureMigrateOnEdit;
390
414
  }
391
415
  },
392
416
 
@@ -423,6 +447,20 @@ export default {
423
447
  v-clean-html="t('cluster.harvester.warning.cloudProvider.incompatible', null, true)"
424
448
  />
425
449
  </Banner>
450
+ <Banner
451
+ v-if="showCloudProviderUnsupportedAzureWarning"
452
+ color="warning"
453
+ data-testid="clusterBasics__showCloudProviderUnsupportedAzureWarning"
454
+ >
455
+ <span v-clean-html="t('cluster.banner.cloudProviderUnsupportedAzure', {}, true)" />
456
+ </Banner>
457
+ <Banner
458
+ v-if="showCloudProviderMigrateAzureWarning"
459
+ color="warning"
460
+ data-testid="clusterBasics__showCloudProviderMigrateAzureWarning"
461
+ >
462
+ <span v-clean-html="t('cluster.banner.cloudProviderMigrateAzure', {}, true)" />
463
+ </Banner>
426
464
  <Banner
427
465
  v-if="showCloudProviderAmazonAdditionalConfigWarning"
428
466
  color="warning"
@@ -461,6 +499,7 @@ export default {
461
499
  <LabeledSelect
462
500
  v-if="agentConfig"
463
501
  v-model="agentConfig['cloud-provider-name']"
502
+ data-testid="clusterBasics__cloudProvider"
464
503
  :mode="mode"
465
504
  :disabled="canNotEditCloudProvider"
466
505
  :options="cloudProviderOptions"
@@ -1,16 +1,12 @@
1
1
 
2
2
  <script>
3
- import { Checkbox } from '@components/Form/Checkbox';
4
3
  import { LabeledInput } from '@components/Form/LabeledInput';
5
4
  import { _CREATE } from '@shell/config/query-params';
6
5
 
7
6
  export default {
8
7
  name: 'DirectoryConfig',
9
- components: {
10
- Checkbox,
11
- LabeledInput,
12
- },
13
- props: {
8
+ components: { LabeledInput },
9
+ props: {
14
10
  mode: {
15
11
  type: String,
16
12
  required: true,
@@ -21,50 +17,11 @@ export default {
21
17
  required: true,
22
18
  },
23
19
  },
24
- data() {
25
- let atLeastOneDirWithAnIdentifier = false;
26
- let allDirsWithSameIdentifier = false;
27
-
28
- if (this.value.systemAgent.length || this.value.provisioning.length || this.value.k8sDistro.length) {
29
- atLeastOneDirWithAnIdentifier = true;
30
- if (this.value.systemAgent === this.value.provisioning && this.value.provisioning === this.value.k8sDistro &&
31
- this.value.systemAgent === this.value.k8sDistro) {
32
- allDirsWithSameIdentifier = true;
33
- }
34
- }
35
-
36
- return {
37
- isSettingCommonConfig: !(atLeastOneDirWithAnIdentifier && !allDirsWithSameIdentifier),
38
- commonConfig: allDirsWithSameIdentifier ? this.value.systemAgent : '',
39
- };
40
- },
41
- watch: {
42
- commonConfig(neu) {
43
- if (neu && neu.length && this.isSettingCommonConfig) {
44
- this.value.systemAgent = neu;
45
- this.value.provisioning = neu;
46
- this.value.k8sDistro = neu;
47
- }
48
- }
49
- },
50
20
  computed: {
51
21
  disableEditInput() {
52
22
  return this.mode !== _CREATE;
53
23
  }
54
- },
55
- methods: {
56
- handleCommonConfig(val) {
57
- this.isSettingCommonConfig = val;
58
-
59
- if (val) {
60
- this.value.systemAgent = '';
61
- this.value.provisioning = '';
62
- this.value.k8sDistro = '';
63
- } else {
64
- this.commonConfig = '';
65
- }
66
- }
67
- },
24
+ }
68
25
  };
69
26
  </script>
70
27
 
@@ -74,54 +31,33 @@ export default {
74
31
  <h3 class="mb-20">
75
32
  {{ t('cluster.directoryConfig.title') }}
76
33
  </h3>
77
- <Checkbox
78
- class="mb-10"
79
- :value="isSettingCommonConfig"
34
+ <LabeledInput
35
+ v-model="value.systemAgent"
36
+ class="mb-20"
37
+ :mode="mode"
38
+ :label="t('cluster.directoryConfig.systemAgent.label')"
39
+ :tooltip="t('cluster.directoryConfig.systemAgent.tooltip')"
40
+ :disabled="disableEditInput"
41
+ data-testid="rke2-directory-config-systemAgent-data-dir"
42
+ />
43
+ <LabeledInput
44
+ v-model="value.provisioning"
45
+ class="mb-20"
80
46
  :mode="mode"
81
- :label="t('cluster.directoryConfig.checkboxText')"
47
+ :label="t('cluster.directoryConfig.provisioning.label')"
48
+ :tooltip="t('cluster.directoryConfig.provisioning.tooltip')"
82
49
  :disabled="disableEditInput"
83
- data-testid="rke2-directory-config-individual-config-checkbox"
84
- @input="handleCommonConfig"
50
+ data-testid="rke2-directory-config-provisioning-data-dir"
85
51
  />
86
52
  <LabeledInput
87
- v-if="isSettingCommonConfig"
88
- v-model="commonConfig"
53
+ v-model="value.k8sDistro"
89
54
  class="mb-20"
90
55
  :mode="mode"
91
- :label="t('cluster.directoryConfig.common.label')"
92
- :tooltip="t('cluster.directoryConfig.common.tooltip')"
56
+ :label="t('cluster.directoryConfig.k8sDistro.label')"
57
+ :tooltip="t('cluster.directoryConfig.k8sDistro.tooltip')"
93
58
  :disabled="disableEditInput"
94
- data-testid="rke2-directory-config-common-data-dir"
59
+ data-testid="rke2-directory-config-k8sDistro-data-dir"
95
60
  />
96
- <div v-if="!isSettingCommonConfig">
97
- <LabeledInput
98
- v-model="value.systemAgent"
99
- class="mb-20"
100
- :mode="mode"
101
- :label="t('cluster.directoryConfig.systemAgent.label')"
102
- :tooltip="t('cluster.directoryConfig.systemAgent.tooltip')"
103
- :disabled="disableEditInput"
104
- data-testid="rke2-directory-config-systemAgent-data-dir"
105
- />
106
- <LabeledInput
107
- v-model="value.provisioning"
108
- class="mb-20"
109
- :mode="mode"
110
- :label="t('cluster.directoryConfig.provisioning.label')"
111
- :tooltip="t('cluster.directoryConfig.provisioning.tooltip')"
112
- :disabled="disableEditInput"
113
- data-testid="rke2-directory-config-provisioning-data-dir"
114
- />
115
- <LabeledInput
116
- v-model="value.k8sDistro"
117
- class="mb-20"
118
- :mode="mode"
119
- :label="t('cluster.directoryConfig.k8sDistro.label')"
120
- :tooltip="t('cluster.directoryConfig.k8sDistro.tooltip')"
121
- :disabled="disableEditInput"
122
- data-testid="rke2-directory-config-k8sDistro-data-dir"
123
- />
124
- </div>
125
61
  <div class="mb-40" />
126
62
  </div>
127
63
  </div>
@@ -7,6 +7,7 @@ import SelectOrCreateAuthSecret from '@shell/components/form/SelectOrCreateAuthS
7
7
  import CreateEditView from '@shell/mixins/create-edit-view';
8
8
  import SecretSelector from '@shell/components/form/SecretSelector';
9
9
  import { SECRET_TYPES as TYPES } from '@shell/config/secret';
10
+ import { base64Decode, base64Encode } from '@shell/utils/crypto';
10
11
 
11
12
  export default {
12
13
  components: {
@@ -55,7 +56,7 @@ export default {
55
56
  if (configMap[hostname]) {
56
57
  configMap[hostname].insecureSkipVerify = configMap[hostname].insecureSkipVerify ?? defaultAddValue.insecureSkipVerify;
57
58
  configMap[hostname].authConfigSecretName = configMap[hostname].authConfigSecretName ?? defaultAddValue.authConfigSecretName;
58
- configMap[hostname].caBundle = configMap[hostname].caBundle ?? defaultAddValue.caBundle;
59
+ configMap[hostname].caBundle = base64Decode(configMap[hostname].caBundle ?? defaultAddValue.caBundle);
59
60
  configMap[hostname].tlsSecretName = configMap[hostname].tlsSecretName ?? defaultAddValue.tlsSecretName;
60
61
  }
61
62
  entries.push({
@@ -94,7 +95,11 @@ export default {
94
95
  continue;
95
96
  }
96
97
 
97
- configs[h] = { ...entry };
98
+ configs[h] = {
99
+ ...entry,
100
+ caBundle: base64Encode(entry.caBundle)
101
+ };
102
+
98
103
  delete configs[h].hostname;
99
104
  }
100
105
 
@@ -177,6 +182,7 @@ export default {
177
182
 
178
183
  <LabeledInput
179
184
  v-model="row.value.caBundle"
185
+ :data-testid="`registry-caBundle-${i}`"
180
186
  class="mt-20"
181
187
  type="multiline"
182
188
  label="CA Cert Bundle"
@@ -0,0 +1,61 @@
1
+ import { mount, Wrapper } from '@vue/test-utils';
2
+ import { clone } from '@shell/utils/object';
3
+ import { _EDIT } from '@shell/config/query-params';
4
+ import { PROV_CLUSTER } from '@shell/edit/provisioning.cattle.io.cluster/__tests__/utils/cluster';
5
+ import RegistryConfigs from '@shell/edit/provisioning.cattle.io.cluster/tabs/registries/RegistryConfigs.vue';
6
+
7
+ describe('component: RegistryConfigs', () => {
8
+ let wrapper: Wrapper<InstanceType<typeof RegistryConfigs> & { [key: string]: any }>;
9
+
10
+ const mountOptions = {
11
+ propsData: {
12
+ value: {},
13
+ mode: _EDIT,
14
+ clusterRegisterBeforeHook: () => {}
15
+ },
16
+ stubs: {
17
+ SelectOrCreateAuthSecret: true,
18
+ SecretSelector: true,
19
+ },
20
+ mocks: { $store: { getters: { 'i18n/t': jest.fn() } } }
21
+ };
22
+
23
+ describe('key CA Cert Bundle', () => {
24
+ it('should display default key', () => {
25
+ const value = clone(PROV_CLUSTER);
26
+
27
+ value.spec.rkeConfig.registries.configs = { foo: { caBundle: 'Zm9vYmFy' } };
28
+
29
+ mountOptions.propsData.value = value;
30
+
31
+ wrapper = mount(
32
+ RegistryConfigs,
33
+ mountOptions
34
+ );
35
+
36
+ const registry = wrapper.find('[data-testid^="registry-caBundle"]').element as HTMLTextAreaElement;
37
+
38
+ expect(registry.value).toBe('foobar');
39
+ });
40
+
41
+ it('should update key in base64 format', async() => {
42
+ const value = clone(PROV_CLUSTER);
43
+
44
+ value.spec.rkeConfig.registries.configs = { foo: { caBundle: 'Zm9vYmFy' } };
45
+
46
+ mountOptions.propsData.value = value;
47
+
48
+ wrapper = mount(
49
+ RegistryConfigs,
50
+ mountOptions
51
+ );
52
+
53
+ const registry = wrapper.find('[data-testid^="registry-caBundle"]');
54
+
55
+ await registry.setValue('ssh key');
56
+ wrapper.vm.update();
57
+
58
+ expect(wrapper.emitted('updateConfigs')![0][0]['foo']['caBundle']).toBe('c3NoIGtleQ==');
59
+ });
60
+ });
61
+ });
@@ -3,7 +3,6 @@ import { updatePageTitle } from '@shell/utils/title';
3
3
  import { getVendor } from '@shell/config/private-label';
4
4
  import middleware from '@shell/config/middleware.js';
5
5
  import { withQuery } from 'ufo';
6
- import dynamicPluginLoader from '@shell/pkg/dynamic-plugin-loader';
7
6
 
8
7
  // Global variable used on mount, updated on route change and used in the render function
9
8
  let app;
@@ -191,30 +190,14 @@ async function render(to, from, next) {
191
190
  next: _next.bind(this)
192
191
  });
193
192
 
193
+ if (this.$loading.start && !this.$loading.manual) {
194
+ this.$loading.start();
195
+ }
196
+
194
197
  // Get route's matched components
195
198
  const matches = [];
196
199
  const Components = getMatchedComponents(to, matches);
197
200
 
198
- // If no Components matched, generate 404
199
- if (!Components.length) {
200
- // Handle the loading of dynamic plugins (Harvester) because we only want to attempt to load those plugins and routes if we first couldn't find a page.
201
- // We should probably get rid of this concept entirely and just load plugins at the start.
202
- await app.context.store.dispatch('loadManagement');
203
- const newLocation = await dynamicPluginLoader.check({ route: { path: window.location.pathname }, store: app.context.store });
204
-
205
- // If we have a new location, double check that it's actually valid
206
- const resolvedRoute = newLocation?.path ? app.context.store.app.router.resolve({ path: newLocation.path.replace(/^\/{0,1}dashboard/, '') }) : null;
207
-
208
- if (resolvedRoute?.route.matched.length) {
209
- // Note - don't use `redirect` or `store.app.route` (breaks feature by failing to run middleware in default layout)
210
- return next(resolvedRoute.resolved.path);
211
- }
212
-
213
- errorRedirect(this, new Error('404: This page could not be found'));
214
-
215
- return next();
216
- }
217
-
218
201
  try {
219
202
  // Call middleware
220
203
  await callMiddleware.call(this, Components, app.context);
@@ -24,8 +24,9 @@ describe('chartMixin', () => {
24
24
  localVue.mixin(ChartMixin);
25
25
 
26
26
  it.each(testCases.opa)(
27
- 'should add OPA deprecation warning properly', (chartId, expected) => {
27
+ 'should add OPA deprecation warning properly', async(chartId, expected) => {
28
28
  const store = new Vuex.Store({
29
+ actions: { 'catalog/load': () => {} },
29
30
  getters: {
30
31
  currentCluster: () => {},
31
32
  isRancher: () => true,
@@ -44,6 +45,8 @@ describe('chartMixin', () => {
44
45
 
45
46
  instance.$route = { query: { chart: 'chart_name' } };
46
47
 
48
+ await instance.fetchChart();
49
+
47
50
  const warnings = instance.warnings;
48
51
 
49
52
  expect(warnings).toHaveLength(expected);
package/mixins/chart.js CHANGED
@@ -27,6 +27,8 @@ export default {
27
27
  existing: null,
28
28
 
29
29
  ignoreWarning: false,
30
+
31
+ chart: null,
30
32
  };
31
33
  },
32
34
 
@@ -35,20 +37,6 @@ export default {
35
37
 
36
38
  showPreRelease: mapPref(SHOW_PRE_RELEASE),
37
39
 
38
- chart() {
39
- if ( this.repo && this.query.chartName ) {
40
- return this.$store.getters['catalog/chart']({
41
- repoType: this.query.repoType,
42
- repoName: this.query.repoName,
43
- chartName: this.query.chartName,
44
- includeHidden: true,
45
- showDeprecated: this.showDeprecated
46
- });
47
- }
48
-
49
- return null;
50
- },
51
-
52
40
  repo() {
53
41
  return this.$store.getters['catalog/repo']({
54
42
  repoType: this.query.repoType,
@@ -258,11 +246,39 @@ export default {
258
246
  },
259
247
 
260
248
  methods: {
249
+ /**
250
+ * Populate `this.chart`
251
+ *
252
+ * `chart` used to be a computed property pointing at getter catalog/chart
253
+ *
254
+ * this however stopped recalculating given changes to the store
255
+ *
256
+ * (the store would populate a charts collection, which the getter uses to find the chart,
257
+ * however this did not kick off the computed property, so this.charts was not populated)
258
+ *
259
+ * Now we find and cache the chart
260
+ */
261
+ fetchStoreChart() {
262
+ if (!this.chart && this.repo && this.query.chartName) {
263
+ this.chart = this.$store.getters['catalog/chart']({
264
+ repoType: this.query.repoType,
265
+ repoName: this.query.repoName,
266
+ chartName: this.query.chartName,
267
+ includeHidden: true,
268
+ showDeprecated: this.showDeprecated
269
+ });
270
+ }
271
+
272
+ return this.chart;
273
+ },
274
+
261
275
  async fetchChart() {
262
276
  this.versionInfoError = null;
263
277
 
264
278
  await this.$store.dispatch('catalog/load'); // not the problem
265
279
 
280
+ this.fetchStoreChart();
281
+
266
282
  if ( this.query.appNamespace && this.query.appName ) {
267
283
  // First check the URL query for an app name and namespace.
268
284
  // Use those values to check for a catalog app resource.
@@ -0,0 +1,93 @@
1
+ import Deployment from '@shell/models/apps.deployment';
2
+ import { WORKLOAD_TYPES } from '@shell/config/types';
3
+
4
+ describe('class Deployment', () => {
5
+ describe('replicaSetId', () => {
6
+ it.each([{
7
+ relationships: [],
8
+ expected: undefined,
9
+ }, {
10
+ relationships: [{
11
+ rel: 'owner',
12
+ toType: WORKLOAD_TYPES.REPLICA_SET,
13
+ toId: 'rel-id'
14
+ }],
15
+ expected: 'rel-id',
16
+ }, {
17
+ relationships: [{
18
+ rel: 'owner',
19
+ toType: WORKLOAD_TYPES.REPLICA_SET,
20
+ toId: 'rel-id-1',
21
+ message: 'ReplicaSet is available. Replicas: 1'
22
+ }],
23
+ expected: 'rel-id-1',
24
+ }, {
25
+ relationships: [{
26
+ rel: 'owner',
27
+ toType: WORKLOAD_TYPES.REPLICA_SET,
28
+ toId: 'rel-id-1',
29
+ message: 'ReplicaSet is available. Replicas: 0'
30
+ }, {
31
+ rel: 'owner',
32
+ toType: WORKLOAD_TYPES.REPLICA_SET,
33
+ toId: 'rel-id-2',
34
+ message: 'ReplicaSet is available. Replicas: 1'
35
+ }],
36
+ expected: 'rel-id-2',
37
+ }, {
38
+ relationships: [{
39
+ rel: 'owner',
40
+ toType: WORKLOAD_TYPES.REPLICA_SET,
41
+ toId: 'rel-id-1',
42
+ message: 'Message without replicas count'
43
+ }, {
44
+ rel: 'owner',
45
+ toType: WORKLOAD_TYPES.REPLICA_SET,
46
+ toId: 'rel-id-2',
47
+ message: 'Another message without replicas count'
48
+ }],
49
+ expected: 'rel-id-1',
50
+ }, {
51
+ relationships: [{
52
+ rel: 'owner',
53
+ toType: WORKLOAD_TYPES.REPLICA_SET,
54
+ toId: 'rel-id-1',
55
+ message: 'ReplicaSet is available. Replicas: 0'
56
+ }, {
57
+ rel: 'owner',
58
+ toType: WORKLOAD_TYPES.REPLICA_SET,
59
+ toId: 'rel-id-2',
60
+ message: 'ReplicaSet is available. Replicas: 0'
61
+ }],
62
+ expected: 'rel-id-1',
63
+ }, {
64
+ relationships: [{
65
+ rel: 'owner',
66
+ toType: WORKLOAD_TYPES.REPLICA_SET,
67
+ toId: 'rel-id-1',
68
+ message: 'Message without replicas count'
69
+ }, {
70
+ rel: 'owner',
71
+ toType: WORKLOAD_TYPES.REPLICA_SET,
72
+ toId: 'rel-id-2',
73
+ message: 'ReplicaSet is available. Replicas: 0'
74
+ }],
75
+ expected: 'rel-id-1',
76
+ }])('replicaSetId', ({ relationships, expected }) => {
77
+ const deploymentData = {
78
+ id: 'any-id',
79
+ type: WORKLOAD_TYPES.DEPLOYMENT,
80
+ metadata: {
81
+ name: 'any-name',
82
+ namespace: 'any-namespace',
83
+ uid: 'any-uid',
84
+ relationships,
85
+ },
86
+ };
87
+
88
+ const deployment = new Deployment(deploymentData);
89
+
90
+ expect(deployment.replicaSetId).toStrictEqual(expected);
91
+ });
92
+ });
93
+ });