@rancher/shell 2.0.0 → 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 (154) hide show
  1. package/assets/translations/en-us.yaml +69 -29
  2. package/assets/translations/zh-hans.yaml +1 -0
  3. package/components/AlertTable.vue +17 -7
  4. package/components/AssignTo.vue +2 -0
  5. package/components/GrafanaDashboard.vue +6 -4
  6. package/components/PromptRemove.vue +1 -0
  7. package/components/Questions/index.vue +2 -2
  8. package/components/auth/RoleDetailEdit.vue +5 -4
  9. package/components/form/KeyValue.vue +1 -0
  10. package/components/form/Members/ClusterPermissionsEditor.vue +1 -1
  11. package/components/form/ProjectMemberEditor.vue +1 -1
  12. package/components/form/ResourceLabeledSelect.vue +11 -3
  13. package/components/form/Taints.vue +13 -7
  14. package/components/form/__tests__/Taints.test.ts +70 -0
  15. package/components/form/labeled-select-utils/labeled-select.utils.ts +1 -1
  16. package/components/nav/Header.vue +1 -1
  17. package/components/nav/TopLevelMenu.vue +1 -4
  18. package/config/pagination-table-headers.js +5 -4
  19. package/config/product/auth.js +1 -1
  20. package/config/roles.ts +34 -19
  21. package/config/router/navigation-guards/attempt-first-login.js +1 -1
  22. package/config/router/navigation-guards/authentication.js +1 -1
  23. package/config/router/navigation-guards/i18n.js +13 -0
  24. package/config/router/navigation-guards/index.js +3 -1
  25. package/config/router/navigation-guards/load-initial-settings.js +1 -1
  26. package/config/router/navigation-guards/runtime-extension-route.js +31 -0
  27. package/config/router/routes.js +10 -1
  28. package/config/uiplugins.js +130 -61
  29. package/core/plugin.ts +5 -0
  30. package/core/plugins.js +7 -1
  31. package/detail/__tests__/provisioning.cattle.io.cluster.test.ts +42 -0
  32. package/detail/provisioning.cattle.io.cluster.vue +4 -4
  33. package/dialog/DeactivateDriverDialog.vue +30 -11
  34. package/edit/auth/__tests__/oidc.test.ts +2 -2
  35. package/edit/provisioning.cattle.io.cluster/__tests__/Basics.test.ts +86 -13
  36. package/edit/provisioning.cattle.io.cluster/__tests__/DirectoryConfig.test.ts +3 -134
  37. package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +209 -0
  38. package/edit/provisioning.cattle.io.cluster/index.vue +8 -4
  39. package/edit/provisioning.cattle.io.cluster/rke2.vue +115 -17
  40. package/edit/provisioning.cattle.io.cluster/tabs/AddOnAdditionalManifest.vue +50 -0
  41. package/edit/provisioning.cattle.io.cluster/tabs/AddOnConfig.vue +29 -64
  42. package/edit/provisioning.cattle.io.cluster/tabs/Basics.vue +42 -3
  43. package/edit/provisioning.cattle.io.cluster/tabs/DirectoryConfig.vue +22 -86
  44. package/edit/provisioning.cattle.io.cluster/tabs/registries/RegistryConfigs.vue +8 -2
  45. package/edit/provisioning.cattle.io.cluster/tabs/registries/__tests__/RegistryConfigs.test.ts +61 -0
  46. package/edit/token.vue +2 -1
  47. package/initialize/entry-helpers.js +4 -24
  48. package/list/management.cattle.io.feature.vue +4 -2
  49. package/middleware/authenticated.js +0 -19
  50. package/mixins/__tests__/chart.test.ts +4 -1
  51. package/mixins/auth-config.js +1 -1
  52. package/mixins/chart.js +30 -14
  53. package/models/__tests__/apps.deployment.test.ts +93 -0
  54. package/models/apps.deployment.js +18 -4
  55. package/models/driver.js +3 -2
  56. package/models/kontainerdriver.js +30 -13
  57. package/models/management.cattle.io.authconfig.js +2 -2
  58. package/models/management.cattle.io.cluster.js +2 -2
  59. package/models/management.cattle.io.user.js +3 -3
  60. package/models/nodedriver.js +35 -13
  61. package/models/provisioning.cattle.io.cluster.js +4 -0
  62. package/package.json +3 -2
  63. package/pages/404.vue +15 -0
  64. package/pages/auth/login.vue +4 -1
  65. package/pages/auth/setup.vue +4 -1
  66. package/pages/c/_cluster/apps/charts/install.vue +3 -2
  67. package/pages/c/_cluster/explorer/index.vue +5 -0
  68. package/pages/c/_cluster/manager/drivers/kontainerDriver/index.vue +0 -3
  69. package/pages/c/_cluster/manager/drivers/nodeDriver/index.vue +1 -4
  70. package/pages/c/_cluster/manager/jwt.authentication/index.vue +10 -4
  71. package/pages/c/_cluster/settings/performance.vue +2 -2
  72. package/pages/c/_cluster/uiplugins/InstallDialog.vue +2 -1
  73. package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +7 -10
  74. package/pages/c/_cluster/uiplugins/index.vue +24 -16
  75. package/pages/home.vue +1 -13
  76. package/plugins/dashboard-store/actions.js +1 -1
  77. package/plugins/dashboard-store/getters.js +1 -1
  78. package/plugins/steve/__tests__/getters.test.ts +5 -5
  79. package/plugins/steve/getters.js +6 -4
  80. package/plugins/steve/hybrid-class.js +1 -5
  81. package/promptRemove/pod.vue +15 -7
  82. package/scripts/extension/helm/charts/ui-plugin-server/Chart.yaml +1 -1
  83. package/scripts/publish-shell.sh +54 -55
  84. package/scripts/test-plugins-build.sh +45 -39
  85. package/shell/types/shell/index.d.ts +2 -0
  86. package/store/auth.js +1 -1
  87. package/store/index.js +1 -1
  88. package/store/type-map.js +4 -2
  89. package/types/store/pagination.types.ts +1 -1
  90. package/utils/__tests__/kontainer.test.ts +89 -1
  91. package/utils/auth.js +1 -1
  92. package/utils/cluster.js +9 -0
  93. package/utils/kontainer.ts +5 -1
  94. package/utils/settings.ts +3 -1
  95. package/utils/version.js +2 -1
  96. package/creators/app/app.package.json +0 -13
  97. package/creators/app/files/.eslintignore +0 -16
  98. package/creators/app/files/.eslintrc.js +0 -173
  99. package/creators/app/files/.gitignore +0 -70
  100. package/creators/app/files/.gitlab-ci.yml +0 -14
  101. package/creators/app/files/.vscode/settings.json +0 -21
  102. package/creators/app/files/babel.config.js +0 -1
  103. package/creators/app/files/tsconfig.json +0 -42
  104. package/creators/app/files/vue.config.js +0 -6
  105. package/creators/app/init +0 -120
  106. package/creators/app/package.json +0 -25
  107. package/creators/pkg/files/.github/workflows/build-extension-catalog.yml +0 -24
  108. package/creators/pkg/files/.github/workflows/build-extension-charts.yml +0 -22
  109. package/creators/pkg/files/babel.config.js +0 -1
  110. package/creators/pkg/files/index.ts +0 -14
  111. package/creators/pkg/files/tsconfig.json +0 -53
  112. package/creators/pkg/files/vue.config.js +0 -1
  113. package/creators/pkg/init +0 -286
  114. package/creators/pkg/package.json +0 -19
  115. package/creators/pkg/pkg.package.json +0 -21
  116. package/creators/pkg/vue-shim.ts +0 -4
  117. package/creators/update/init +0 -56
  118. package/creators/update/package.json +0 -20
  119. package/creators/update/upgrade +0 -56
  120. package/rancher-components/components/Accordion/Accordion.test.ts +0 -45
  121. package/rancher-components/components/Accordion/Accordion.vue +0 -86
  122. package/rancher-components/components/Accordion/index.ts +0 -1
  123. package/rancher-components/components/BadgeState/BadgeState.test.ts +0 -12
  124. package/rancher-components/components/BadgeState/BadgeState.vue +0 -111
  125. package/rancher-components/components/BadgeState/index.ts +0 -1
  126. package/rancher-components/components/Banner/Banner.test.ts +0 -59
  127. package/rancher-components/components/Banner/Banner.vue +0 -244
  128. package/rancher-components/components/Banner/index.ts +0 -1
  129. package/rancher-components/components/Card/Card.test.ts +0 -37
  130. package/rancher-components/components/Card/Card.vue +0 -167
  131. package/rancher-components/components/Card/index.ts +0 -1
  132. package/rancher-components/components/Form/Checkbox/Checkbox.test.ts +0 -68
  133. package/rancher-components/components/Form/Checkbox/Checkbox.vue +0 -421
  134. package/rancher-components/components/Form/Checkbox/index.ts +0 -1
  135. package/rancher-components/components/Form/LabeledInput/LabeledInput.test.ts +0 -40
  136. package/rancher-components/components/Form/LabeledInput/LabeledInput.vue +0 -402
  137. package/rancher-components/components/Form/LabeledInput/index.ts +0 -1
  138. package/rancher-components/components/Form/Radio/RadioButton.test.ts +0 -33
  139. package/rancher-components/components/Form/Radio/RadioButton.vue +0 -293
  140. package/rancher-components/components/Form/Radio/RadioGroup.test.ts +0 -30
  141. package/rancher-components/components/Form/Radio/RadioGroup.vue +0 -259
  142. package/rancher-components/components/Form/Radio/index.ts +0 -2
  143. package/rancher-components/components/Form/TextArea/TextAreaAutoGrow.vue +0 -172
  144. package/rancher-components/components/Form/TextArea/index.ts +0 -1
  145. package/rancher-components/components/Form/ToggleSwitch/ToggleSwitch.test.ts +0 -94
  146. package/rancher-components/components/Form/ToggleSwitch/ToggleSwitch.vue +0 -152
  147. package/rancher-components/components/Form/ToggleSwitch/index.ts +0 -1
  148. package/rancher-components/components/Form/index.ts +0 -5
  149. package/rancher-components/components/LabeledTooltip/LabeledTooltip.vue +0 -156
  150. package/rancher-components/components/LabeledTooltip/index.ts +0 -1
  151. package/rancher-components/components/StringList/StringList.test.ts +0 -754
  152. package/rancher-components/components/StringList/StringList.vue +0 -650
  153. package/rancher-components/components/StringList/index.ts +0 -1
  154. package/types/shell/index.d.ts +0 -4585
@@ -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
+ });
package/edit/token.vue CHANGED
@@ -13,6 +13,7 @@ import Select from '@shell/components/form/Select';
13
13
  import CreateEditView from '@shell/mixins/create-edit-view';
14
14
  import { diffFrom } from '@shell/utils/time';
15
15
  import { filterHiddenLocalCluster, filterOnlyKubernetesClusters } from '@shell/utils/cluster';
16
+ import { SETTING } from '@shell/config/settings';
16
17
 
17
18
  export default {
18
19
  components: {
@@ -29,7 +30,7 @@ export default {
29
30
 
30
31
  data() {
31
32
  // Get the setting that defines the max token TTL allowed (in minutes)
32
- const maxTTLSetting = this.$store.getters['management/byId'](MANAGEMENT.SETTING, 'auth-token-max-ttl-minutes');
33
+ const maxTTLSetting = this.$store.getters['management/byId'](MANAGEMENT.SETTING, SETTING.AUTH_TOKEN_MAX_TTL_MINUTES);
33
34
  let maxTTL = 0;
34
35
 
35
36
  try {
@@ -190,34 +190,14 @@ async function render(to, from, next) {
190
190
  next: _next.bind(this)
191
191
  });
192
192
 
193
+ if (this.$loading.start && !this.$loading.manual) {
194
+ this.$loading.start();
195
+ }
196
+
193
197
  // Get route's matched components
194
198
  const matches = [];
195
199
  const Components = getMatchedComponents(to, matches);
196
200
 
197
- // If no Components matched, generate 404
198
- if (!Components.length) {
199
- // Call the authenticated middleware. This used to attempt to load the error layout but because it was missing it would:
200
- // 1. load the default layout instead
201
- // 2. then call the authenticated middleware
202
- // 3. Authenticated middleware would then load plugins and check to see if there was a valid route and navigate to that if it existed
203
- // 4. This would allow harvester cluster pages to load on page reload
204
- // We should really make authenticated middleware do less...
205
- await callMiddleware.call(this, [{ options: { middleware: ['authenticated'] } }], app.context);
206
-
207
- // We used to have i18n middleware which was called each time we called middleware. This is also needed to support harvester because of the way harvester loads as outlined in the comment above
208
- await this.$store.dispatch('i18n/init');
209
-
210
- if (nextCalled) {
211
- return;
212
- }
213
-
214
- // Show error page
215
- // this.error({ statusCode: 404, message: 'This page could not be found' });
216
- errorRedirect(this, new Error('404: This page could not be found'));
217
-
218
- return next();
219
- }
220
-
221
201
  try {
222
202
  // Call middleware
223
203
  await callMiddleware.call(this, Components, app.context);
@@ -163,14 +163,16 @@ export default {
163
163
  const response = await this.$axios.get(url, { timeout: 5000 });
164
164
 
165
165
  if (response?.status === 200) {
166
- this.rows = await this.$store.dispatch('management/findAll', { type: this.resource, opt: { force: true } });
166
+ await this.$store.dispatch('management/findAll', { type: this.resource, opt: { force: true } });
167
167
  btnCB(true);
168
168
  this.close();
169
169
  this.waiting = false;
170
170
  }
171
171
  } catch (e) {}
172
172
 
173
- this.waitForBackend(btnCB, id);
173
+ if (this.waiting) {
174
+ this.waitForBackend(btnCB, id);
175
+ }
174
176
  }, 5000);
175
177
  },
176
178
 
@@ -2,7 +2,6 @@ import { DEFAULT_WORKSPACE } from '@shell/config/types';
2
2
  import { applyProducts } from '@shell/store/type-map';
3
3
  import { ClusterNotFoundError, RedirectToError } from '@shell/utils/error';
4
4
  import { get } from '@shell/utils/object';
5
- import dynamicPluginLoader from '@shell/pkg/dynamic-plugin-loader';
6
5
  import { AFTER_LOGIN_ROUTE, WORKSPACE } from '@shell/store/prefs';
7
6
  import { BACK_TO } from '@shell/config/local-storage';
8
7
  import { NAME as FLEET_NAME } from '@shell/config/product/fleet.js';
@@ -107,24 +106,6 @@ export default async function({
107
106
  });
108
107
  }
109
108
 
110
- if (!route.matched?.length) {
111
- // If there are no matching routes we could be trying to nav to a page belonging to a dynamic plugin which needs loading
112
- await Promise.all([
113
- ...always,
114
- ]);
115
-
116
- // If a plugin claims the route and is loaded correctly we'll get a route back
117
- const newLocation = await dynamicPluginLoader.check({ route, store });
118
-
119
- // If we have a new location, double check that it's actually valid
120
- const resolvedRoute = newLocation ? store.app.router.resolve(newLocation) : null;
121
-
122
- if (resolvedRoute?.route.matched.length) {
123
- // Note - don't use `redirect` or `store.app.route` (breaks feature by failing to run middleware in default layout)
124
- return next(newLocation);
125
- }
126
- }
127
-
128
109
  // Ensure that the activeNamespaceCache is updated given the change of context either from or to a place where it uses workspaces
129
110
  // When fleet moves to it's own package this should be moved to pkg onEnter/onLeave
130
111
  if ((oldProduct === FLEET_NAME || product === FLEET_NAME) && oldProduct !== product) {
@@ -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);
@@ -273,7 +273,7 @@ export default {
273
273
 
274
274
  // KeyCloakOIDCConfig --> OIDCConfig
275
275
  set(this.model, 'rancherUrl', `${ serverUrl }/verify-auth`);
276
- set(this.model, 'scope', BASE_SCOPES.oidc[0]);
276
+ set(this.model, 'scope', this.model.id === 'keycloakoidc' ? BASE_SCOPES.keycloakoidc[0] : BASE_SCOPES.genericoidc[0]);
277
277
  break;
278
278
  }
279
279
 
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
+ });
@@ -10,14 +10,28 @@ const IGNORED_ANNOTATIONS = [
10
10
  'deprecated.deployment.rollback.to',
11
11
  ];
12
12
 
13
+ const replicasRegEx = /Replicas: (\d+)/;
14
+
13
15
  export default class Deployment extends Workload {
14
16
  get replicaSetId() {
15
- const set = this.metadata?.relationships?.find((relationship) => {
16
- return relationship.rel === 'owner' &&
17
- relationship.toType === WORKLOAD_TYPES.REPLICA_SET;
17
+ const relationships = this.metadata?.relationships || [];
18
+
19
+ // Find all relevant ReplicaSet relationships
20
+ const replicaSetRelationships = relationships.filter((relationship) => relationship.rel === 'owner' && relationship.toType === WORKLOAD_TYPES.REPLICA_SET
21
+ );
22
+
23
+ // Filter the ReplicaSets based on replicas > 0
24
+ const activeReplicaSet = replicaSetRelationships.find((relationship) => {
25
+ const replicasMatch = relationship.message?.match(replicasRegEx);
26
+ const replicas = replicasMatch ? parseInt(replicasMatch[1], 10) : 0;
27
+
28
+ return replicas > 0;
18
29
  });
19
30
 
20
- return set?.toId?.replace(`${ this.namespace }/`, '');
31
+ // If no active ReplicaSet is found, fall back to the first one from the list
32
+ const selectedReplicaSet = activeReplicaSet || replicaSetRelationships[0];
33
+
34
+ return selectedReplicaSet?.toId?.replace(`${ this.namespace }/`, '');
21
35
  }
22
36
 
23
37
  async rollBack(cluster, deployment, revision) {
package/models/driver.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { DESCRIPTION } from '@shell/config/labels-annotations';
2
2
  import NormanModel from '@shell/plugins/steve/norman-class';
3
3
  import { KONTAINER_TO_DRIVER } from './management.cattle.io.kontainerdriver';
4
+ import capitalize from 'lodash/capitalize';
4
5
 
5
6
  export default class Driver extends NormanModel {
6
7
  get canViewYaml() {
@@ -20,12 +21,12 @@ export default class Driver extends NormanModel {
20
21
  }
21
22
  }
22
23
 
23
- return KONTAINER_TO_DRIVER[this.id] || this.id;
24
+ return KONTAINER_TO_DRIVER[this.id] || this.name || this.id;
24
25
  }
25
26
 
26
27
  get nameDisplay() {
27
28
  const path = `cluster.provider.${ this.driverName }`;
28
- const label = this.driverName || this.name || this.id;
29
+ const label = capitalize(this.driverName);
29
30
 
30
31
  return this.$rootGetters['i18n/withFallback'](path, label);
31
32
  }
@@ -8,19 +8,21 @@ export default class KontainerDriver extends Driver {
8
8
  get _availableActions() {
9
9
  const out = [
10
10
  {
11
- action: 'activate',
12
- label: 'Activate',
13
- icon: 'icon icon-play',
14
- bulkable: true,
15
- enabled: !!this.links.update && !this.active
11
+ action: 'activate',
12
+ label: this.t('action.activate'),
13
+ icon: 'icon icon-play',
14
+ bulkable: true,
15
+ bulkAction: 'activateBulk',
16
+ enabled: !!this.links.update && !this.active
16
17
  },
17
18
  {
18
- action: 'deactivate',
19
- label: 'Deactivate',
20
- icon: 'icon icon-pause',
21
- bulkable: true,
22
- enabled: !!this.links.update && !!this.active,
23
- weight: -1
19
+ action: 'deactivate',
20
+ label: this.t('action.deactivate'),
21
+ icon: 'icon icon-pause',
22
+ bulkable: true,
23
+ bulkAction: 'deactivateBulk',
24
+ enabled: !!this.links.update && !!this.active,
25
+ weight: -1
24
26
  },
25
27
  { divider: true },
26
28
  {
@@ -52,9 +54,16 @@ export default class KontainerDriver extends Driver {
52
54
  return out;
53
55
  }
54
56
 
55
- deactivate() {
57
+ deactivate(resources = [this]) {
58
+ this.$dispatch('promptModal', {
59
+ componentProps: { drivers: resources, driverType: 'kontainerDrivers' },
60
+ component: 'DeactivateDriverDialog'
61
+ });
62
+ }
63
+
64
+ deactivateBulk(resources) {
56
65
  this.$dispatch('promptModal', {
57
- componentProps: { url: `v3/kontainerDrivers/${ escape(this.id) }?action=deactivate`, name: this.nameDisplay },
66
+ componentProps: { drivers: resources, driverType: 'kontainerDrivers' },
58
67
  component: 'DeactivateDriverDialog'
59
68
  });
60
69
  }
@@ -65,4 +74,12 @@ export default class KontainerDriver extends Driver {
65
74
  method: 'post',
66
75
  }, { root: true });
67
76
  }
77
+
78
+ async activateBulk(resources) {
79
+ await Promise.all(resources.map((resource) => this.$dispatch('rancher/request', {
80
+ url: `v3/kontainerDrivers/${ escape(resource.id) }?action=activate`,
81
+ method: 'post',
82
+ }, { root: true }
83
+ )));
84
+ }
68
85
  }