@rancher/shell 3.0.1-rc.1 → 3.0.1-rc.3

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 (35) hide show
  1. package/assets/styles/app.scss +0 -1
  2. package/assets/translations/en-us.yaml +2 -5
  3. package/assets/translations/zh-hans.yaml +0 -3
  4. package/components/GlobalRoleBindings.vue +0 -7
  5. package/components/SideNav.vue +5 -3
  6. package/components/SortableTable/THead.vue +1 -1
  7. package/components/auth/RoleDetailEdit.vue +0 -16
  8. package/components/nav/TopLevelMenu.vue +5 -3
  9. package/edit/catalog.cattle.io.clusterrepo.vue +0 -9
  10. package/edit/constraints.gatekeeper.sh.constraint/index.vue +5 -2
  11. package/edit/provisioning.cattle.io.cluster/__tests__/DirectoryConfig.test.ts +17 -7
  12. package/edit/provisioning.cattle.io.cluster/rke2.vue +3 -4
  13. package/edit/provisioning.cattle.io.cluster/tabs/DirectoryConfig.vue +20 -6
  14. package/models/__tests__/management.cattle.io.cluster.test.ts +0 -42
  15. package/models/fleet.cattle.io.gitrepo.js +5 -0
  16. package/models/management.cattle.io.cluster.js +0 -24
  17. package/models/management.cattle.io.globalrole.js +1 -0
  18. package/models/provisioning.cattle.io.cluster.js +23 -1
  19. package/package.json +6 -4
  20. package/pages/auth/login.vue +7 -2
  21. package/pages/c/_cluster/auth/roles/index.vue +1 -11
  22. package/pages/c/_cluster/explorer/__tests__/index.test.ts +71 -1
  23. package/pages/c/_cluster/explorer/index.vue +6 -2
  24. package/rancher-components/Form/TextArea/TextAreaAutoGrow.vue +1 -1
  25. package/scripts/extension/parse-tag-name +19 -12
  26. package/scripts/publish-shell.sh +10 -2
  27. package/scripts/test-plugins-build.sh +8 -9
  28. package/scripts/typegen.sh +2 -0
  29. package/types/shell/index.d.ts +116 -1
  30. package/utils/__tests__/object.test.ts +30 -1
  31. package/utils/object.js +28 -0
  32. package/utils/version.js +1 -1
  33. package/vue.config.js +4 -3
  34. package/machine-config/__tests__/vmwarevsphere-pool-config-merge.test.ts +0 -30
  35. package/machine-config/vmwarevsphere-pool-config-merge.ts +0 -25
@@ -32,4 +32,3 @@
32
32
 
33
33
  @import "./vendor/vue-select";
34
34
  @import "./vendor/code-mirror";
35
- @import 'node_modules/xterm/css/xterm.css';
@@ -1065,7 +1065,7 @@ catalog:
1065
1065
  target:
1066
1066
  git: Git repository containing Helm chart or cluster template definitions
1067
1067
  http: http(s) URL to an index generated by Helm
1068
- oci: OCI Repository <span class="oci-experimental-badge text-bold ml-5">Experimental</span>
1068
+ oci: OCI Repository
1069
1069
  label: Target
1070
1070
  url:
1071
1071
  label: Index URL
@@ -1475,6 +1475,7 @@ cluster:
1475
1475
  placeholder: A unique name for the cluster
1476
1476
  directoryConfig:
1477
1477
  title: Data directory configuration
1478
+ banner: Data directory configuration can not be changed once the cluster has been created
1478
1479
  radioInput:
1479
1480
  defaultLabel: Use default data directory configuration
1480
1481
  commonLabel: Use a common base directory for data directory configuration (sub-directories will be used for the system-agent, provisioning and distro paths)
@@ -4877,10 +4878,6 @@ rbac:
4877
4878
  admin:
4878
4879
  label: Administrator
4879
4880
  description: Administrators have full control over the entire installation and all resources in all clusters.
4880
- restricted-admin:
4881
- label: Restricted Administrator
4882
- description: Restricted Admins have full control over all resources in all downstream clusters but no access to the local cluster.
4883
- deprecation: 'Warning: The Restricted Administrator role has been deprecated as of Rancher 2.8.0 and will be removed in a future release - Check out the <a href="{releaseNotesUrl}" target="_blank" rel="noopener noreferrer nofollow">Release Notes</a>'
4884
4881
  user:
4885
4882
  label: Standard User
4886
4883
  description: Standard Users can create new clusters and manage clusters and projects they have been granted access to.
@@ -4556,9 +4556,6 @@ rbac:
4556
4556
  admin:
4557
4557
  label: 管理员
4558
4558
  description: 管理员可以完全控制整个安装以及所有集群中的所有资源。
4559
- restricted-admin:
4560
- label: 受限管理员
4561
- description: 受限管理员可以完全控制所有下游集群的所有资源,但不能访问本地集群。
4562
4559
  user:
4563
4560
  label: 普通用户
4564
4561
  description: 普通用户可以创建集群,并管理其授权访问的集群和项目。
@@ -108,7 +108,6 @@ export default {
108
108
  // This not only identifies global roles but the order here is the order we want to display them in the UI
109
109
  globalPermissions: [
110
110
  'admin',
111
- 'restricted-admin',
112
111
  'user',
113
112
  'user-base',
114
113
  ],
@@ -121,7 +120,6 @@ export default {
121
120
  };
122
121
  },
123
122
  computed: {
124
- ...mapGetters(['releaseNotesUrl']),
125
123
  ...mapGetters({ t: 'i18n/t' }),
126
124
 
127
125
  isCreate() {
@@ -375,11 +373,6 @@ export default {
375
373
  </div>
376
374
  </template>
377
375
  </Checkbox>
378
- <p
379
- v-if="role.id === 'restricted-admin'"
380
- v-clean-html="t('rbac.globalRoles.role.restricted-admin.deprecation', { releaseNotesUrl }, true)"
381
- class="deprecation-notice"
382
- />
383
376
  </div>
384
377
  </div>
385
378
  </template>
@@ -111,8 +111,10 @@ export default {
111
111
 
112
112
  computed: {
113
113
  ...mapState(['managementReady', 'clusterReady']),
114
- ...mapGetters(['productId', 'clusterId', 'currentProduct', 'rootProduct', 'isSingleProduct', 'namespaceMode', 'isExplorer', 'isVirtualCluster']),
115
- ...mapGetters({ locale: 'i18n/selectedLocaleLabel', availableLocales: 'i18n/availableLocales' }),
114
+ ...mapGetters(['isStandaloneHarvester', 'productId', 'clusterId', 'currentProduct', 'rootProduct', 'isSingleProduct', 'namespaceMode', 'isExplorer', 'isVirtualCluster']),
115
+ ...mapGetters({
116
+ locale: 'i18n/selectedLocaleLabel', availableLocales: 'i18n/availableLocales', hasMultipleLocales: 'i18n/hasMultipleLocales'
117
+ }),
116
118
  ...mapGetters('type-map', ['activeProducts']),
117
119
 
118
120
  favoriteTypes: mapPref(FAVORITE_TYPES),
@@ -437,7 +439,7 @@ export default {
437
439
  </span>
438
440
 
439
441
  <!-- locale selector -->
440
- <span v-if="isSingleProduct">
442
+ <span v-if="isSingleProduct && hasMultipleLocales && !isStandaloneHarvester">
441
443
  <v-dropdown
442
444
  popperClass="localeSelector"
443
445
  placement="top"
@@ -429,7 +429,7 @@ export default {
429
429
  color: var(--body-text);
430
430
 
431
431
  .table-header-container {
432
- display: flex;
432
+ display: inline-flex;
433
433
 
434
434
  .content {
435
435
  display: flex;
@@ -1,5 +1,4 @@
1
1
  <script>
2
- import { mapGetters } from 'vuex';
3
2
  import { MANAGEMENT, RBAC } from '@shell/config/types';
4
3
  import CruResource from '@shell/components/CruResource';
5
4
  import CreateEditView from '@shell/mixins/create-edit-view';
@@ -15,7 +14,6 @@ import { ucFirst } from '@shell/utils/string';
15
14
  import SortableTable from '@shell/components/SortableTable';
16
15
  import { _CLONE, _DETAIL } from '@shell/config/query-params';
17
16
  import { SCOPED_RESOURCES, SCOPED_RESOURCE_GROUPS } from '@shell/config/roles';
18
- import { Banner } from '@components/Banner';
19
17
  import LabeledInput from '@components/Form/LabeledInput/LabeledInput.vue';
20
18
 
21
19
  import { SUBTYPE_MAPPING, VERBS } from '@shell/models/management.cattle.io.roletemplate';
@@ -66,7 +64,6 @@ export default {
66
64
  SortableTable,
67
65
  Loading,
68
66
  Error,
69
- Banner,
70
67
  LabeledInput
71
68
  },
72
69
 
@@ -163,12 +160,6 @@ export default {
163
160
  },
164
161
 
165
162
  computed: {
166
- ...mapGetters(['releaseNotesUrl']),
167
-
168
- showRestrictedAdminDeprecationBanner() {
169
- return this.value.subtype === GLOBAL && this.value.id === 'restricted-admin';
170
- },
171
-
172
163
  label() {
173
164
  return this.t(`rbac.roletemplate.subtypes.${ this.value.subtype }.label`);
174
165
  },
@@ -559,13 +550,6 @@ export default {
559
550
  @finish="save"
560
551
  @cancel="cancel"
561
552
  >
562
- <Banner
563
- v-if="showRestrictedAdminDeprecationBanner"
564
- color="warning"
565
- class="mb-20"
566
- >
567
- <span v-clean-html="t('rbac.globalRoles.role.restricted-admin.deprecation', { releaseNotesUrl }, true)" />
568
- </Banner>
569
553
  <template v-if="isDetail">
570
554
  <SortableTable
571
555
  key-field="index"
@@ -920,7 +920,7 @@ export default {
920
920
  // needs !important so that we can
921
921
  // offset the tooltip a bit so it doesn't
922
922
  // overlap the pin icon and cause bad UX
923
- left: 35px !important;
923
+ left: 48px !important;
924
924
  }
925
925
 
926
926
  .localeSelector, .footer-tooltip {
@@ -1069,7 +1069,7 @@ export default {
1069
1069
  line-height: normal;
1070
1070
 
1071
1071
  & > p {
1072
- width: 195px;
1072
+ width: 182px;
1073
1073
  white-space: nowrap;
1074
1074
  overflow: hidden;
1075
1075
  text-overflow: ellipsis;
@@ -1117,9 +1117,11 @@ export default {
1117
1117
 
1118
1118
  > i, > img {
1119
1119
  display: block;
1120
- width: 42px;
1121
1120
  font-size: $icon-size;
1122
1121
  margin-right: 14px;
1122
+ &.group-icon {
1123
+ width: 42px;
1124
+ }
1123
1125
  }
1124
1126
 
1125
1127
  .rancher-provider-icon,
@@ -287,12 +287,3 @@ export default {
287
287
  />
288
288
  </form>
289
289
  </template>
290
-
291
- <style lang="scss">
292
- span.oci-experimental-badge {
293
- background-color: var(--warning);
294
- color: var(--darker-active-bg);
295
- font-size: 12px;
296
- padding: 2px 6px;
297
- }
298
- </style>
@@ -1,7 +1,7 @@
1
1
  <script>
2
2
  import merge from 'lodash/merge';
3
3
  import { ucFirst } from '@shell/utils/string';
4
- import { isSimpleKeyValue } from '@shell/utils/object';
4
+ import { isSimpleKeyValue, set } from '@shell/utils/object';
5
5
  import { _CREATE, _VIEW } from '@shell/config/query-params';
6
6
  import { SCHEMA, NAMESPACE } from '@shell/config/types';
7
7
  import CreateEditView from '@shell/mixins/create-edit-view';
@@ -222,6 +222,9 @@ export default {
222
222
  this.value.spec.match.namespaces = [];
223
223
  this.value.spec.match.excludedNamespaces = [];
224
224
  }
225
+ },
226
+ setParameters(e) {
227
+ return set(this.value.spec, 'parameters', e);
225
228
  }
226
229
  }
227
230
  };
@@ -356,7 +359,7 @@ export default {
356
359
  v-model:value="parametersYaml"
357
360
  class="yaml-editor"
358
361
  :editor-mode="editorMode"
359
- @newObject="$set(value.spec, 'parameters', $event)"
362
+ @newObject="setParameters"
360
363
  />
361
364
  </Tab>
362
365
  </Tabbed>
@@ -113,7 +113,7 @@ describe('component: DirectoryConfig', () => {
113
113
  expect(wrapper.vm.value.k8sDistro).toStrictEqual(k8sDistroValue);
114
114
  });
115
115
 
116
- it('should render the component with configuration being an empty object, without errors and radio be of value DATA_DIR_RADIO_OPTIONS.DEFAULT (edit scenario)', () => {
116
+ it('should render the component with configuration being an empty object, without errors and radio be of value DATA_DIR_RADIO_OPTIONS.CUSTOM (edit scenario)', () => {
117
117
  const newMountOptions = clone(mountOptions);
118
118
 
119
119
  newMountOptions.propsData.value = {};
@@ -131,17 +131,21 @@ describe('component: DirectoryConfig', () => {
131
131
  const k8sDistroInput = wrapper.find('[data-testid="rke2-directory-config-k8sDistro-data-dir"]');
132
132
 
133
133
  expect(title.exists()).toBe(true);
134
- expect(radioInput.exists()).toBe(true);
134
+ expect(radioInput.isVisible()).toBe(false);
135
135
 
136
- expect(wrapper.vm.dataConfigRadioValue).toBe(DATA_DIR_RADIO_OPTIONS.DEFAULT);
136
+ expect(wrapper.vm.dataConfigRadioValue).toBe(DATA_DIR_RADIO_OPTIONS.CUSTOM);
137
137
 
138
138
  // since we have all of the vars empty, then the inputs should not be there
139
- expect(systemAgentInput.exists()).toBe(false);
140
- expect(provisioningInput.exists()).toBe(false);
141
- expect(k8sDistroInput.exists()).toBe(false);
139
+ expect(systemAgentInput.exists()).toBe(true);
140
+ expect(provisioningInput.exists()).toBe(true);
141
+ expect(k8sDistroInput.exists()).toBe(true);
142
+
143
+ expect(systemAgentInput.attributes().disabled).toBeDefined();
144
+ expect(provisioningInput.attributes().disabled).toBeDefined();
145
+ expect(k8sDistroInput.attributes().disabled).toBeDefined();
142
146
  });
143
147
 
144
- it('radio input should be set to DATA_DIR_RADIO_OPTIONS.CUSTOM with all data dir values existing and different (edit scenario)', async() => {
148
+ it('radio input should be set to DATA_DIR_RADIO_OPTIONS.CUSTOM with all data dir values existing and different (edit scenario)', () => {
145
149
  const newMountOptions = clone(mountOptions);
146
150
  const inputPath = 'some-data-dir';
147
151
 
@@ -157,14 +161,20 @@ describe('component: DirectoryConfig', () => {
157
161
 
158
162
  expect(wrapper.vm.dataConfigRadioValue).toBe(DATA_DIR_RADIO_OPTIONS.CUSTOM);
159
163
 
164
+ const radioInput = wrapper.find('[data-testid="rke2-directory-config-radio-input"]');
160
165
  const systemAgentInput = wrapper.find('[data-testid="rke2-directory-config-systemAgent-data-dir"]');
161
166
  const provisioningInput = wrapper.find('[data-testid="rke2-directory-config-provisioning-data-dir"]');
162
167
  const k8sDistroInput = wrapper.find('[data-testid="rke2-directory-config-k8sDistro-data-dir"]');
163
168
 
169
+ expect(radioInput.isVisible()).toBe(false);
164
170
  expect(systemAgentInput.isVisible()).toBe(true);
165
171
  expect(provisioningInput.isVisible()).toBe(true);
166
172
  expect(k8sDistroInput.isVisible()).toBe(true);
167
173
 
174
+ expect(systemAgentInput.attributes().disabled).toBeDefined();
175
+ expect(provisioningInput.attributes().disabled).toBeDefined();
176
+ expect(k8sDistroInput.attributes().disabled).toBeDefined();
177
+
168
178
  expect(wrapper.vm.value.systemAgent).toStrictEqual(`${ inputPath }/${ DEFAULT_SUBDIRS.AGENT }`);
169
179
  expect(wrapper.vm.value.provisioning).toStrictEqual(`${ inputPath }/${ DEFAULT_SUBDIRS.PROVISIONING }`);
170
180
  expect(wrapper.vm.value.k8sDistro).toStrictEqual(`${ inputPath }/${ DEFAULT_SUBDIRS.K8S_DISTRO_K3S }`);
@@ -22,11 +22,10 @@ import { _CREATE, _EDIT, _VIEW } from '@shell/config/query-params';
22
22
  import { findBy, removeObject, clear } from '@shell/utils/array';
23
23
  import { createYaml } from '@shell/utils/create-yaml';
24
24
  import {
25
- clone, diff, set, get, isEmpty
25
+ clone, diff, set, get, isEmpty, mergeWithReplaceArrays
26
26
  } from '@shell/utils/object';
27
27
  import { allHash } from '@shell/utils/promise';
28
28
  import { sortBy } from '@shell/utils/sort';
29
- import { vspherePoolConfigMerge } from '@shell/machine-config/vmwarevsphere-pool-config-merge';
30
29
 
31
30
  import { compare, sortable } from '@shell/utils/version';
32
31
  import { isHarvesterSatisfiesVersion, labelForAddon } from '@shell/utils/cluster';
@@ -1260,7 +1259,7 @@ export default {
1260
1259
  delete clonedCurrentConfig.metadata;
1261
1260
 
1262
1261
  if (this.provider === VMWARE_VSPHERE) {
1263
- machinePool.config = vspherePoolConfigMerge(clonedLatestConfig, clonedCurrentConfig);
1262
+ machinePool.config = mergeWithReplaceArrays(clonedLatestConfig, clonedCurrentConfig);
1264
1263
  } else {
1265
1264
  machinePool.config = merge(clonedLatestConfig, clonedCurrentConfig);
1266
1265
  }
@@ -1644,7 +1643,7 @@ export default {
1644
1643
  const defaultChartValue = this.versionInfo[name];
1645
1644
  const key = this.chartVersionKey(name);
1646
1645
 
1647
- return merge({}, defaultChartValue?.values || {}, this.userChartValues[key] || {});
1646
+ return mergeWithReplaceArrays(defaultChartValue?.values, this.userChartValues[key]);
1648
1647
  },
1649
1648
 
1650
1649
  initServerAgentArgs() {
@@ -1,8 +1,9 @@
1
1
 
2
2
  <script>
3
3
  import { LabeledInput } from '@components/Form/LabeledInput';
4
- import { _CREATE } from '@shell/config/query-params';
4
+ import { _CREATE, _EDIT } from '@shell/config/query-params';
5
5
  import RadioGroup from '@components/Form/Radio/RadioGroup.vue';
6
+ import { Banner } from '@components/Banner';
6
7
 
7
8
  export const DATA_DIR_RADIO_OPTIONS = {
8
9
  DEFAULT: 'defaultDataDir',
@@ -23,7 +24,8 @@ export default {
23
24
  name: 'DirectoryConfig',
24
25
  components: {
25
26
  LabeledInput,
26
- RadioGroup
27
+ RadioGroup,
28
+ Banner
27
29
  },
28
30
  props: {
29
31
  mode: {
@@ -50,9 +52,7 @@ export default {
50
52
  }
51
53
 
52
54
  if (this.mode !== _CREATE) {
53
- if (this.value?.systemAgent?.length || this.value?.provisioning?.length || this.value?.k8sDistro?.length) {
54
- dataConfigRadioValue = DATA_DIR_RADIO_OPTIONS.CUSTOM;
55
- }
55
+ dataConfigRadioValue = DATA_DIR_RADIO_OPTIONS.CUSTOM;
56
56
  }
57
57
 
58
58
  return {
@@ -85,6 +85,9 @@ export default {
85
85
  }
86
86
  },
87
87
  computed: {
88
+ isDisabled() {
89
+ return this.mode === _EDIT;
90
+ },
88
91
  dataConfigRadioOptions() {
89
92
  const defaultDataDirOption = {
90
93
  value: DATA_DIR_RADIO_OPTIONS.DEFAULT,
@@ -148,10 +151,17 @@ export default {
148
151
  <template>
149
152
  <div class="row">
150
153
  <div class="col span-8">
151
- <h3 class="mb-20">
154
+ <h3>
152
155
  {{ t('cluster.directoryConfig.title') }}
153
156
  </h3>
157
+ <Banner
158
+ class="mb-20"
159
+ :closable="false"
160
+ color="info"
161
+ label-key="cluster.directoryConfig.banner"
162
+ />
154
163
  <RadioGroup
164
+ v-show="!isDisabled"
155
165
  :value="dataConfigRadioValue"
156
166
  class="mb-10"
157
167
  :mode="mode"
@@ -167,6 +177,7 @@ export default {
167
177
  :mode="mode"
168
178
  :label="t('cluster.directoryConfig.common.label')"
169
179
  :tooltip="t('cluster.directoryConfig.common.tooltip')"
180
+ :disabled="isDisabled"
170
181
  data-testid="rke2-directory-config-common-data-dir"
171
182
  />
172
183
  <div v-if="dataConfigRadioValue === DATA_DIR_RADIO_OPTIONS.CUSTOM">
@@ -176,6 +187,7 @@ export default {
176
187
  :mode="mode"
177
188
  :label="t('cluster.directoryConfig.systemAgent.label')"
178
189
  :tooltip="t('cluster.directoryConfig.systemAgent.tooltip')"
190
+ :disabled="isDisabled"
179
191
  data-testid="rke2-directory-config-systemAgent-data-dir"
180
192
  />
181
193
  <LabeledInput
@@ -184,6 +196,7 @@ export default {
184
196
  :mode="mode"
185
197
  :label="t('cluster.directoryConfig.provisioning.label')"
186
198
  :tooltip="t('cluster.directoryConfig.provisioning.tooltip')"
199
+ :disabled="isDisabled"
187
200
  data-testid="rke2-directory-config-provisioning-data-dir"
188
201
  />
189
202
  <LabeledInput
@@ -192,6 +205,7 @@ export default {
192
205
  :mode="mode"
193
206
  :label="t('cluster.directoryConfig.k8sDistro.label')"
194
207
  :tooltip="t('cluster.directoryConfig.k8sDistro.tooltip')"
208
+ :disabled="isDisabled"
195
209
  data-testid="rke2-directory-config-k8sDistro-data-dir"
196
210
  />
197
211
  </div>
@@ -4,28 +4,6 @@ jest.mock('@shell/utils/clipboard', () => {
4
4
  return { copyTextToClipboard: jest.fn(() => Promise.resolve({})) };
5
5
  });
6
6
 
7
- const importedRKE2ClusterInfo = { status: { driver: 'rke2', provider: 'rke2' } };
8
-
9
- const provisionedRKE2ClusterInfo = { status: { driver: 'rke2', provider: 'imported' } };
10
-
11
- const importedK3sClusterInfo = { status: { driver: 'k3s', provider: 'k3s' } };
12
-
13
- const provisionedK3sClusterInfo = { status: { driver: 'k3s', provider: 'imported' } };
14
-
15
- const importedAksClusterInfo = { spec: { aksConfig: { imported: true } }, status: { provider: 'aks', driver: 'AKS' } };
16
-
17
- const provisionedAksClusterInfo = { spec: { aksConfig: { imported: false } }, status: { provider: 'aks', driver: 'AKS' } };
18
-
19
- const importedRKE1ClusterInfo = { status: { provider: 'rke', driver: 'imported' } };
20
-
21
- const provisionedRKE1ClusterInfo = { status: { provider: 'rke', driver: 'rancherKubernetesEngine' } };
22
-
23
- const localRKE1ClusterInfo = { status: { provider: 'rke', driver: 'imported' } };
24
-
25
- const localRKE2ClusterInfo = { status: { provider: 'rke2', driver: 'rke2' } };
26
-
27
- const localEKSClusterInfo = { status: { provider: 'eks', driver: 'imported' } };
28
-
29
7
  describe('class MgmtCluster', () => {
30
8
  describe('provisioner', () => {
31
9
  const testCases = [
@@ -42,24 +20,4 @@ describe('class MgmtCluster', () => {
42
20
  }
43
21
  );
44
22
  });
45
-
46
- describe('isImported', () => {
47
- it.each([
48
- [importedRKE2ClusterInfo, true],
49
- [provisionedRKE2ClusterInfo, false],
50
- [importedK3sClusterInfo, true],
51
- [provisionedK3sClusterInfo, false],
52
- [importedAksClusterInfo, true],
53
- [provisionedAksClusterInfo, false],
54
- [importedRKE1ClusterInfo, true],
55
- [provisionedRKE1ClusterInfo, false],
56
- [localRKE1ClusterInfo, true],
57
- [localRKE2ClusterInfo, true],
58
- [localEKSClusterInfo, true]
59
- ])('should return isImported based on props data', (clusterData, expected) => {
60
- const cluster = new MgmtCluster(clusterData);
61
-
62
- expect(cluster.isImported).toBe(expected);
63
- });
64
- });
65
23
  });
@@ -338,6 +338,11 @@ export default class GitRepo extends SteveModel {
338
338
  for (const bd of bundleDeployments) {
339
339
  const clusterId = FleetUtils.clusterIdFromBundleDeploymentLabels(bd.metadata?.labels);
340
340
  const c = clusters[clusterId];
341
+
342
+ if (!c) {
343
+ continue;
344
+ }
345
+
341
346
  const resources = FleetUtils.resourcesFromBundleDeploymentStatus(bd.status);
342
347
 
343
348
  resources.forEach((r) => {
@@ -89,30 +89,6 @@ export default class MgmtCluster extends SteveModel {
89
89
  return pools.filter((x) => x.spec?.clusterName === this.id);
90
90
  }
91
91
 
92
- get isImported() {
93
- if (this.isLocal) {
94
- return false;
95
- }
96
- // imported rke2 and k3s have status.driver === rke2 and k3s respectively
97
- // Provisioned rke2 and k3s have status.driver === imported
98
- if (this.status?.provider === 'k3s' || this.status?.provider === 'rke2') {
99
- return this.status?.driver === this.status?.provider;
100
- }
101
-
102
- // imported KEv2
103
- const kontainerConfigs = ['aksConfig', 'eksConfig', 'gkeConfig'];
104
-
105
- const isImportedKontainer = kontainerConfigs.filter((key) => {
106
- return this.spec?.[key]?.imported === true;
107
- }).length;
108
-
109
- if (isImportedKontainer) {
110
- return true;
111
- }
112
-
113
- return this.provisioner === 'imported';
114
- }
115
-
116
92
  get provisioner() {
117
93
  // For imported K3s clusters, this.status.driver is 'k3s.'
118
94
  return this.status?.driver ? this.status.driver : 'imported';
@@ -117,6 +117,7 @@ export default class GlobalRole extends SteveDescriptionModel {
117
117
  norman.id = this.id;
118
118
  norman.name = this.displayName;
119
119
  norman.description = this.description;
120
+ norman.inheritedClusterRoles = this.inheritedClusterRoles;
120
121
 
121
122
  return norman;
122
123
  })();
@@ -282,7 +282,29 @@ export default class ProvCluster extends SteveModel {
282
282
  }
283
283
 
284
284
  get isImported() {
285
- return this.mgmt?.isImported;
285
+ if (this.isLocal) {
286
+ return false;
287
+ }
288
+
289
+ // imported rke2 and k3s have status.driver === rke2 and k3s respectively
290
+ // Provisioned rke2 and k3s have status.driver === imported
291
+ if (this.mgmt?.status?.provider === 'k3s' || this.mgmt?.status?.provider === 'rke2') {
292
+ return this.mgmt?.status?.driver === this.mgmt?.status?.provider;
293
+ }
294
+
295
+ // imported KEv2
296
+ // we can't rely on this.provisioner to determine imported-ness for these clusters, as it will return 'aks' 'eks' 'gke' for both provisioned and imported clusters
297
+ const kontainerConfigs = ['aksConfig', 'eksConfig', 'gkeConfig'];
298
+
299
+ const isImportedKontainer = kontainerConfigs.filter((key) => {
300
+ return this.mgmt?.spec?.[key]?.imported === true;
301
+ }).length;
302
+
303
+ if (isImportedKontainer) {
304
+ return true;
305
+ }
306
+
307
+ return this.provisioner === 'imported';
286
308
  }
287
309
 
288
310
  get isCustom() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rancher/shell",
3
- "version": "3.0.1-rc.1",
3
+ "version": "3.0.1-rc.3",
4
4
  "description": "Rancher Dashboard Shell",
5
5
  "repository": "https://github.com/rancherlabs/dashboard",
6
6
  "license": "Apache-2.0",
@@ -36,7 +36,6 @@
36
36
  "@babel/plugin-proposal-private-property-in-object": "7.14.5",
37
37
  "@babel/preset-typescript": "7.16.7",
38
38
  "@novnc/novnc": "1.2.0",
39
- "@nuxtjs/axios": "5.13.6",
40
39
  "@popperjs/core": "2.4.4",
41
40
  "@rancher/icons": "2.0.29",
42
41
  "@types/is-url": "1.2.30",
@@ -51,6 +50,8 @@
51
50
  "@vue/vue3-jest": "^27.0.0-alpha.1",
52
51
  "add": "2.0.6",
53
52
  "ansi_up": "5.0.0",
53
+ "axios": "0.21.4",
54
+ "axios-retry": "3.1.9",
54
55
  "babel-eslint": "10.1.0",
55
56
  "babel-plugin-module-resolver": "4.0.0",
56
57
  "babel-preset-vue": "2.0.2",
@@ -73,6 +74,7 @@
73
74
  "d3-selection": "1.4.1",
74
75
  "dagre-d3": "0.6.4",
75
76
  "dayjs": "1.8.29",
77
+ "defu": "5.0.1",
76
78
  "diff2html": "3.4.24",
77
79
  "dompurify": "2.5.4",
78
80
  "element-matches": "^0.1.2",
@@ -160,7 +162,7 @@
160
162
  "semver": "7.5.4",
161
163
  "@types/lodash": "4.17.5",
162
164
  "@types/node": "~20.10.0",
163
- "@vue/cli-service/html-webpack-plugin": "^5.0.0"
165
+ "@vue/cli-service/html-webpack-plugin": "^5.0.0"
164
166
  },
165
167
  "nyc": {
166
168
  "extension": [
@@ -168,4 +170,4 @@
168
170
  ".vue"
169
171
  ]
170
172
  }
171
- }
173
+ }
@@ -31,6 +31,7 @@ import {
31
31
  import loadPlugins from '@shell/plugins/plugin';
32
32
  import Loading from '@shell/components/Loading';
33
33
  import { getGlobalBannerFontSizes } from '@shell/utils/banners';
34
+ import { HARVESTER_NAME as HARVESTER } from '@shell/config/features';
34
35
 
35
36
  export default {
36
37
  name: 'Login',
@@ -64,7 +65,7 @@ export default {
64
65
  },
65
66
 
66
67
  computed: {
67
- ...mapGetters(['isStandaloneHarvester']),
68
+ ...mapGetters(['isSingleProduct']),
68
69
  ...mapGetters({ t: 'i18n/t', hasMultipleLocales: 'i18n/hasMultipleLocales' }),
69
70
 
70
71
  loggedOutSuccessMsg() {
@@ -77,6 +78,10 @@ export default {
77
78
  return this.t('login.loggedOut');
78
79
  },
79
80
 
81
+ isHarvester() {
82
+ return this.isSingleProduct?.productName === HARVESTER;
83
+ },
84
+
80
85
  singleProvider() {
81
86
  return this.providers.length === 1 ? this.providers[0] : undefined;
82
87
  },
@@ -498,7 +503,7 @@ export default {
498
503
  </div>
499
504
  </template>
500
505
  <div
501
- v-if="showLocaleSelector && hasMultipleLocales && !isStandaloneHarvester"
506
+ v-if="showLocaleSelector && hasMultipleLocales && !isHarvester"
502
507
  class="locale-selector"
503
508
  >
504
509
  <LocaleSelector
@@ -1,5 +1,4 @@
1
1
  <script>
2
- import { mapGetters } from 'vuex';
3
2
  import Tab from '@shell/components/Tabbed/Tab';
4
3
  import Tabbed from '@shell/components/Tabbed';
5
4
  import { MANAGEMENT } from '@shell/config/types';
@@ -8,7 +7,6 @@ import Loading from '@shell/components/Loading';
8
7
  import { SUBTYPE_MAPPING, CREATE_VERBS } from '@shell/models/management.cattle.io.roletemplate';
9
8
  import { NAME } from '@shell/config/product/auth';
10
9
  import { BLANK_CLUSTER } from '@shell/store/store-types.js';
11
- import { Banner } from '@components/Banner';
12
10
 
13
11
  const GLOBAL = SUBTYPE_MAPPING.GLOBAL.key;
14
12
  const CLUSTER = SUBTYPE_MAPPING.CLUSTER.key;
@@ -33,7 +31,7 @@ export default {
33
31
  name: 'Roles',
34
32
 
35
33
  components: {
36
- Tab, Tabbed, ResourceTable, Loading, Banner
34
+ Tab, Tabbed, ResourceTable, Loading
37
35
  },
38
36
 
39
37
  async fetch() {
@@ -100,8 +98,6 @@ export default {
100
98
  },
101
99
 
102
100
  computed: {
103
- ...mapGetters(['releaseNotesUrl']),
104
-
105
101
  globalResources() {
106
102
  return this.globalRoles;
107
103
  },
@@ -183,12 +179,6 @@ export default {
183
179
  :weight="tabs[GLOBAL].weight"
184
180
  :label-key="tabs[GLOBAL].labelKey"
185
181
  >
186
- <Banner
187
- color="warning"
188
- class="mb-20"
189
- >
190
- <span v-clean-html="t('rbac.globalRoles.role.restricted-admin.deprecation', { releaseNotesUrl }, true)" />
191
- </Banner>
192
182
  <ResourceTable
193
183
  :schema="tabs[GLOBAL].schema"
194
184
  :rows="globalResources"
@@ -3,6 +3,7 @@ import Dashboard from '@shell/pages/c/_cluster/explorer/index.vue';
3
3
  import { shallowMount } from '@vue/test-utils';
4
4
  import { STATES_ENUM } from '@shell/plugins/dashboard-store/resource-class';
5
5
  import { NODE_ARCHITECTURE } from '@shell/config/labels-annotations';
6
+ import { WORKLOAD_TYPES } from '@shell/config/types';
6
7
 
7
8
  describe('page: cluster dashboard', () => {
8
9
  const mountOptions = {
@@ -97,12 +98,81 @@ describe('page: cluster dashboard', () => {
97
98
  [STATES_ENUM.WARNING, 'icon-warning', true, false, false, [{ status: 'True' }], 0, 1],
98
99
  [STATES_ENUM.HEALTHY, 'icon-checkmark', true, false, false, [{ status: 'True' }], 1, 0],
99
100
  ]]
100
- ])('%p cluster - %p agent health box', (_, agentId, isLocal, agentResources, statuses) => {
101
+ ])('%p cluster - %p agent health box :', (_, agentId, isLocal, agentResources, statuses) => {
102
+ it.each(statuses)('should NOT show %p status due to missing canList permissions', (status, iconClass, isLoaded, disconnected, error, conditions, readyReplicas, unavailableReplicas) => {
103
+ const options = clone(mountOptions);
104
+
105
+ options.global.mocks.$store.getters.currentCluster.isLocal = isLocal;
106
+
107
+ const resources = agentResources.reduce((acc, r) => {
108
+ const agent = {
109
+ metadata: { state: { error } },
110
+ spec: { replicas: 1 },
111
+ status: {
112
+ readyReplicas,
113
+ unavailableReplicas,
114
+ conditions
115
+ }
116
+ };
117
+
118
+ return isLoaded ? {
119
+ ...acc,
120
+ [r]: agent
121
+ } : 'loading';
122
+ }, {});
123
+
124
+ const wrapper = shallowMount(Dashboard, {
125
+ ...options,
126
+ data: () => ({
127
+ ...resources,
128
+ disconnected,
129
+ canViewAgents: true
130
+ })
131
+ });
132
+
133
+ const box = wrapper.find(`[data-testid="k8s-service-${ agentId }"]`);
134
+
135
+ expect(box.exists()).toBe(false);
136
+ });
137
+ });
138
+
139
+ describe.each([
140
+ ['local', 'fleet', true, ['fleetDeployment', 'fleetStatefulSet'], [
141
+ [STATES_ENUM.IN_PROGRESS, 'icon-spinner', false, false, false, '', 0, 0],
142
+ [STATES_ENUM.UNHEALTHY, 'icon-warning', true, false, false, [{ status: 'False' }], 0, 0],
143
+ [STATES_ENUM.UNHEALTHY, 'icon-warning', true, false, true, [{ status: 'True' }], 0, 0],
144
+ [STATES_ENUM.WARNING, 'icon-warning', true, true, false, [{ status: 'True' }], 0, 0],
145
+ [STATES_ENUM.WARNING, 'icon-warning', true, false, false, [{ status: 'True' }], 0, 0],
146
+ [STATES_ENUM.WARNING, 'icon-warning', true, false, false, [{ status: 'True' }], 0, 1],
147
+ [STATES_ENUM.HEALTHY, 'icon-checkmark', true, false, false, [{ status: 'True' }], 1, 0],
148
+ ]],
149
+ ['downstream RKE2', 'fleet', false, ['fleetStatefulSet'], [
150
+ [STATES_ENUM.IN_PROGRESS, 'icon-spinner', false, false, false, '', 0, 0],
151
+ [STATES_ENUM.UNHEALTHY, 'icon-warning', true, false, false, [{ status: 'False' }], 0, 0],
152
+ [STATES_ENUM.UNHEALTHY, 'icon-warning', true, false, true, [{ status: 'True' }], 0, 0],
153
+ [STATES_ENUM.WARNING, 'icon-warning', true, true, false, [{ status: 'True' }], 0, 0],
154
+ [STATES_ENUM.WARNING, 'icon-warning', true, false, false, [{ status: 'True' }], 0, 0],
155
+ [STATES_ENUM.WARNING, 'icon-warning', true, false, false, [{ status: 'True' }], 0, 1],
156
+ [STATES_ENUM.HEALTHY, 'icon-checkmark', true, false, false, [{ status: 'True' }], 1, 0],
157
+ ]],
158
+ ['downstream RKE2', 'cattle', false, ['cattleDeployment'], [
159
+ [STATES_ENUM.IN_PROGRESS, 'icon-spinner', false, false, false, '', 0, 0],
160
+ [STATES_ENUM.UNHEALTHY, 'icon-warning', true, false, false, [{ status: 'False' }], 0, 0],
161
+ [STATES_ENUM.UNHEALTHY, 'icon-warning', true, true, false, [{ status: 'True' }], 0, 0],
162
+ [STATES_ENUM.UNHEALTHY, 'icon-warning', true, false, true, [{ status: 'True' }], 0, 0],
163
+ [STATES_ENUM.WARNING, 'icon-warning', true, false, false, [{ status: 'True' }], 0, 0],
164
+ [STATES_ENUM.WARNING, 'icon-warning', true, false, false, [{ status: 'True' }], 0, 1],
165
+ [STATES_ENUM.HEALTHY, 'icon-checkmark', true, false, false, [{ status: 'True' }], 1, 0],
166
+ ]]
167
+ ])('%p cluster - %p agent health box ::', (_, agentId, isLocal, agentResources, statuses) => {
101
168
  it.each(statuses)('should show %p status', (status, iconClass, isLoaded, disconnected, error, conditions, readyReplicas, unavailableReplicas) => {
102
169
  const options = clone(mountOptions);
103
170
 
104
171
  options.global.mocks.$store.getters.currentCluster.isLocal = isLocal;
105
172
 
173
+ // let's pass the canList now
174
+ options.global.mocks.$store.getters['cluster/canList'] = (type: string) => !!(type === WORKLOAD_TYPES.DEPLOYMENT) || !!(type === WORKLOAD_TYPES.STATEFUL_SET);
175
+
106
176
  const resources = agentResources.reduce((acc, r) => {
107
177
  const agent = {
108
178
  metadata: { state: { error } },
@@ -188,7 +188,11 @@ export default {
188
188
  },
189
189
 
190
190
  fleetAgentNamespace() {
191
- return this.$store.getters['cluster/byId'](NAMESPACE, 'cattle-fleet-system');
191
+ if (this.currentCluster.isLocal) {
192
+ return this.$store.getters['cluster/canList'](WORKLOAD_TYPES.DEPLOYMENT) && this.$store.getters['cluster/canList'](WORKLOAD_TYPES.STATEFUL_SET) && this.$store.getters['cluster/byId'](NAMESPACE, 'cattle-fleet-system');
193
+ }
194
+
195
+ return this.$store.getters['cluster/canList'](WORKLOAD_TYPES.STATEFUL_SET) && this.$store.getters['cluster/byId'](NAMESPACE, 'cattle-fleet-system');
192
196
  },
193
197
 
194
198
  cattleAgentNamespace() {
@@ -196,7 +200,7 @@ export default {
196
200
  return;
197
201
  }
198
202
 
199
- return this.$store.getters['cluster/byId'](NAMESPACE, 'cattle-system');
203
+ return this.$store.getters['cluster/canList'](WORKLOAD_TYPES.DEPLOYMENT) && this.$store.getters['cluster/byId'](NAMESPACE, 'cattle-system');
200
204
  },
201
205
 
202
206
  canViewAgents() {
@@ -1,6 +1,6 @@
1
1
  <script lang="ts">
2
2
  import { defineComponent, inject, PropType } from 'vue';
3
- import debounce from 'lodash/debounce';
3
+ import { debounce } from 'lodash';
4
4
  import { _EDIT, _VIEW } from '@shell/config/query-params';
5
5
 
6
6
  interface NonReactiveProps {
@@ -4,27 +4,34 @@ GITHUB_RELEASE_TAG=$1
4
4
  GITHUB_RUN_ID=$2
5
5
  GITHUB_WORKFLOW_TYPE=$3
6
6
 
7
- # Ensure "catalog" workflow release tag name does not match a pkg/<pkg-name>
7
+ echo "Parse tag name - evaluating release tag $GITHUB_RELEASE_TAG"
8
+
9
+ # Ensure "catalog" workflow release tag name matches the root <pkg-name>
8
10
  if [[ "${GITHUB_WORKFLOW_TYPE}" == "catalog" ]]; then
11
+ BASE_EXT=$(jq -r .name package.json)
12
+ EXT_VERSION=$(jq -r .version package.json)
13
+
14
+ if [[ "${GITHUB_RELEASE_TAG}" != "${BASE_EXT}-${EXT_VERSION}" ]]; then
15
+ echo -e "release tag doesn't match catalog tag: release tag -> ${GITHUB_RELEASE_TAG} ::: curr catalog tag -> ${BASE_EXT}-${EXT_VERSION}"
16
+ gh run cancel ${GITHUB_RUN_ID}
17
+ fi
18
+ # Ensure "chart" workflow release tag name matches some pkg/<pkg-name>
19
+ else
20
+ NO_MATCHES="true"
21
+
9
22
  for d in pkg/*/ ; do
10
23
  pkg=$(basename $d)
11
24
 
12
25
  PKG_VERSION=$(jq -r .version pkg/${pkg}/package.json)
13
- PKG_NAME="${pkg}-${PKG_VERSION}"
26
+ CURR_PKG_TAG="${pkg}-${PKG_VERSION}"
14
27
 
15
- if [[ "${GITHUB_RELEASE_TAG}" == "${PKG_NAME}" ]]; then
16
- gh run cancel ${GITHUB_RUN_ID}
17
- else
18
- continue
28
+ if [[ "${GITHUB_RELEASE_TAG}" == "${CURR_PKG_TAG}" ]]; then
29
+ NO_MATCHES="false"
19
30
  fi
20
31
  done
21
- else
22
- # Ensure "charts" workflow release tag name does not match the root <pkg-name>
23
- BASE_EXT=$(jq -r .name package.json)
24
- EXT_VERSION=$(jq -r .version package.json)
25
32
 
26
- if [[ "${GITHUB_RELEASE_TAG}" == "${BASE_EXT}-${EXT_VERSION}" ]]; then
27
- echo -e "tag: ${GITHUB_RELEASE_TAG}"
33
+ if [[ "${NO_MATCHES}" == "true" ]]; then
34
+ echo -e "release tag doesn't match any chart tag: ${GITHUB_RELEASE_TAG}. Check your pkg/<!-YOUR-EXT-> folders and corresponding versions to complete the match"
28
35
  gh run cancel ${GITHUB_RUN_ID}
29
36
  fi
30
37
  fi
@@ -6,7 +6,6 @@ SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd)"
6
6
  BASE_DIR="$(cd $SCRIPT_DIR && cd ../.. && pwd)"
7
7
  SHELL_DIR=$BASE_DIR/shell/
8
8
  CREATORS_DIR=$BASE_DIR/creators/extension
9
- PUBLISH_ARGS="--no-git-tag-version --access public --registry $NPM_REGISTRY $NPM_TAG"
10
9
  FORCE_PUBLISH_TO_NPM="false"
11
10
  DEFAULT_NPM_REGISTRY="https://registry.npmjs.org"
12
11
 
@@ -31,6 +30,8 @@ if [ "$FORCE_PUBLISH_TO_NPM" == "true" ]; then
31
30
  export NPM_REGISTRY=$DEFAULT_NPM_REGISTRY
32
31
  fi
33
32
 
33
+ PUBLISH_ARGS="--no-git-tag-version --access public --registry $NPM_REGISTRY"
34
+
34
35
  pushd ${SHELL_DIR} >/dev/null
35
36
 
36
37
  function publish() {
@@ -47,9 +48,16 @@ function publish() {
47
48
 
48
49
  # if the PKG_VERSION has a - it means it will be a pre-release
49
50
  if [[ $PKG_VERSION == *"-"* ]]; then
50
- PUBLISH_ARGS="--no-git-tag-version --access public --registry $NPM_REGISTRY --tag pre-release"
51
+ PUBLISH_ARGS="$PUBLISH_ARGS --tag pre-release"
52
+ fi
53
+
54
+ # when testing the workflow, we don't want to actually do an npm publish but only a dry run
55
+ if [ ${DRY_RUN} == "true" ]; then
56
+ PUBLISH_ARGS="$PUBLISH_ARGS --dry-run"
51
57
  fi
52
58
 
59
+ echo "Publish to NPM - arguments ::: ${PUBLISH_ARGS}"
60
+
53
61
  echo "Publishing ${NAME} from ${FOLDER}"
54
62
  pushd ${FOLDER} >/dev/null
55
63
 
@@ -169,8 +169,9 @@ fi
169
169
 
170
170
  # function to clone repos and install dependencies (including the newly published shell version)
171
171
  function clone_repo_test_extension_build() {
172
- REPO_NAME=$1
173
- PKG_NAME=$2
172
+ REPO_ORG=$1
173
+ REPO_NAME=$2
174
+ PKG_NAME=$3
174
175
 
175
176
  echo -e "\nSetting up $REPO_NAME repository locally\n"
176
177
 
@@ -183,7 +184,7 @@ function clone_repo_test_extension_build() {
183
184
  fi
184
185
 
185
186
  # cloning repo
186
- git clone https://github.com/rancher/$REPO_NAME.git
187
+ git clone https://github.com/$REPO_ORG/$REPO_NAME.git
187
188
  pushd ${BASE_DIR}/$REPO_NAME
188
189
 
189
190
  echo -e "\nInstalling dependencies for $REPO_NAME\n"
@@ -196,9 +197,6 @@ function clone_repo_test_extension_build() {
196
197
  sed -i.bak -e "s/\"\@rancher\/shell\": \"[0-9]*.[0-9]*.[0-9]*\",/\"\@rancher\/shell\": \"${SHELL_VERSION}\",/g" package.json
197
198
  rm package.json.bak
198
199
 
199
- # we need to remove yarn.lock, otherwise it would install a version that we don't want
200
- rm yarn.lock
201
-
202
200
  echo -e "\nInstalling newly built shell version\n"
203
201
 
204
202
  # installing new version of shell
@@ -223,8 +221,9 @@ function clone_repo_test_extension_build() {
223
221
 
224
222
  # Here we just add the extension that we want to include as a check (all our official extensions should be included here)
225
223
  # Don't forget to add the unit tests exception to clone_repo_test_extension_build function if a new extension has those
226
- # clone_repo_test_extension_build "kubewarden-ui" "kubewarden"
227
- # clone_repo_test_extension_build "elemental-ui" "elemental"
228
- # clone_repo_test_extension_build "capi-ui-extension" "capi"
224
+ clone_repo_test_extension_build "rancher" "kubewarden-ui" "kubewarden"
225
+ clone_repo_test_extension_build "rancher" "elemental-ui" "elemental"
226
+ clone_repo_test_extension_build "neuvector" "manager-ext" "neuvector-ui-ext"
227
+ # clone_repo_test_extension_build "rancher" "capi-ui-extension" "capi"
229
228
 
230
229
  echo "All done"
@@ -31,9 +31,11 @@ ${BASE_DIR}/node_modules/.bin/tsc ${SHELL_DIR}/plugins/dashboard-store/normalize
31
31
  ${BASE_DIR}/node_modules/.bin/tsc ${SHELL_DIR}/plugins/dashboard-store/resource-class.js --declaration --allowJs --emitDeclarationOnly --outDir ${SHELL_DIR}/tmp/plugins/dashboard-store/ > /dev/null
32
32
  ${BASE_DIR}/node_modules/.bin/tsc ${SHELL_DIR}/plugins/dashboard-store/classify.js --declaration --allowJs --emitDeclarationOnly --outDir ${SHELL_DIR}/tmp/plugins/dashboard-store/ > /dev/null
33
33
  ${BASE_DIR}/node_modules/.bin/tsc ${SHELL_DIR}/plugins/dashboard-store/actions.js --declaration --allowJs --emitDeclarationOnly --outDir ${SHELL_DIR}/tmp/plugins/dashboard-store/ > /dev/null
34
+ ${BASE_DIR}/node_modules/.bin/tsc ${SHELL_DIR}/plugins/steve/steve-class.js --declaration --allowJs --emitDeclarationOnly --outDir ${SHELL_DIR}/tmp/plugins/steve/ > /dev/null
34
35
 
35
36
  # # mixins
36
37
  ${BASE_DIR}/node_modules/.bin/tsc ${SHELL_DIR}/mixins/create-edit-view/index.js --declaration --allowJs --emitDeclarationOnly --outDir ${SHELL_DIR}/tmp/mixins/create-edit-view > /dev/null
38
+ ${BASE_DIR}/node_modules/.bin/tsc ${SHELL_DIR}/mixins/resource-fetch.js --declaration --allowJs --emitDeclarationOnly --outDir ${SHELL_DIR}/tmp/mixins > /dev/null
37
39
 
38
40
  # # models
39
41
  ${BASE_DIR}/node_modules/.bin/tsc ${SHELL_DIR}/models/namespace.js --declaration --allowJs --emitDeclarationOnly --outDir ${SHELL_DIR}/tmp/models/ > /dev/null
@@ -2352,6 +2352,67 @@ declare var _default: import("vue").DefineComponent<{
2352
2352
  export default _default;
2353
2353
  }
2354
2354
 
2355
+ // @shell/mixins/resource-fetch
2356
+
2357
+ declare module '@shell/mixins/resource-fetch' {
2358
+ declare namespace _default {
2359
+ const mixins: any[];
2360
+ const inheritAttrs: boolean;
2361
+ function data(): {
2362
+ perfConfig: {};
2363
+ init: boolean;
2364
+ multipleResources: any[];
2365
+ loadResources: any[];
2366
+ hasManualRefresh: boolean;
2367
+ watch: boolean;
2368
+ isTooManyItemsToAutoUpdate: boolean;
2369
+ force: boolean;
2370
+ incremental: boolean;
2371
+ fetchedResourceType: any[];
2372
+ paginating: any;
2373
+ };
2374
+ function data(): {
2375
+ perfConfig: {};
2376
+ init: boolean;
2377
+ multipleResources: any[];
2378
+ loadResources: any[];
2379
+ hasManualRefresh: boolean;
2380
+ watch: boolean;
2381
+ isTooManyItemsToAutoUpdate: boolean;
2382
+ force: boolean;
2383
+ incremental: boolean;
2384
+ fetchedResourceType: any[];
2385
+ paginating: any;
2386
+ };
2387
+ function beforeUnmount(): void;
2388
+ function beforeUnmount(): void;
2389
+ namespace computed {
2390
+ function rows(): any;
2391
+ function rows(): any;
2392
+ function loading(): any;
2393
+ function loading(): any;
2394
+ const refreshFlag: import("vuex").Computed;
2395
+ }
2396
+ namespace watch {
2397
+ function refreshFlag(neu: any): Promise<void>;
2398
+ function refreshFlag(neu: any): Promise<void>;
2399
+ }
2400
+ namespace methods {
2401
+ function $initializeFetchData(type: any, multipleResources: any[], storeType: any): void;
2402
+ function $initializeFetchData(type: any, multipleResources: any[], storeType: any): void;
2403
+ function $fetchType(type: any, multipleResources: any[], storeType: any): any;
2404
+ function $fetchType(type: any, multipleResources: any[], storeType: any): any;
2405
+ function __getCountForResources(resourceNames: any, namespace: any, storeType: any): any;
2406
+ function __getCountForResources(resourceNames: any, namespace: any, storeType: any): any;
2407
+ function __getCountForResource(resourceName: any, namespace: any, storeType: any): any;
2408
+ function __getCountForResource(resourceName: any, namespace: any, storeType: any): any;
2409
+ function __gatherResourceFetchData(resourceName: any, multipleResources: any, currStore: any): void;
2410
+ function __gatherResourceFetchData(resourceName: any, multipleResources: any, currStore: any): void;
2411
+ }
2412
+ }
2413
+ export default _default;
2414
+ }
2415
+
2355
2416
  // @shell/models/namespace
2356
2417
 
2357
2418
  declare module '@shell/models/namespace' {
@@ -3115,6 +3176,40 @@ export default class Resource {
3115
3176
  }
3116
3177
  }
3117
3178
 
3179
+ // @shell/plugins/steve/hybrid-class
3180
+
3181
+ declare module '@shell/plugins/steve/hybrid-class' {
3182
+ export function cleanHybridResources(data: any): any;
3183
+ export default class HybridModel {
3184
+ constructor(data: any, ctx: any, rehydrateNamespace?: any, setClone?: boolean);
3185
+ get labels(): any;
3186
+ setLabels(val: any): void;
3187
+ metadata: {};
3188
+ setLabel(key: any, val: any): void;
3189
+ get annotations(): any;
3190
+ setAnnotations(val: any): void;
3191
+ setAnnotation(key: any, val: any): void;
3192
+ get state(): any;
3193
+ }
3194
+ }
3195
+
3196
+ // @shell/plugins/steve/steve-class
3197
+
3198
+ declare module '@shell/plugins/steve/steve-class' {
3199
+ export default class SteveModel extends HybridModel {
3200
+ get name(): any;
3201
+ get namespace(): any;
3202
+ /**
3203
+ * Set description based on the type of model available with private fallback
3204
+ */
3205
+ set description(arg: any);
3206
+ get description(): any;
3207
+ _description: any;
3208
+ cleanForSave(data: any, forNew: any): any;
3209
+ }
3210
+ import HybridModel from "./hybrid-class";
3211
+ }
3212
+
3118
3213
  // @shell/store/features
3119
3214
 
3120
3215
  declare module '@shell/store/features' {
@@ -3940,6 +4035,26 @@ export function dropKeys(obj: any, keys: any): void;
3940
4035
  * @returns
3941
4036
  */
3942
4037
  export function deepToRaw(obj: any, cache?: any): any;
4038
+ /**
4039
+ * Helper function to alter Lodash merge function default behaviour on merging arrays while updating machine pool configuration.
4040
+ *
4041
+ * In rke2.vue, the syncMachineConfigWithLatest function updates machine pool configuration by
4042
+ * merging the latest configuration received from the backend with the current configuration updated by the user.
4043
+ * However, Lodash's merge function treats arrays like object so index values are merged and not appended to arrays
4044
+ * resulting in undesired outcomes for us, Example:
4045
+ *
4046
+ * const lastSavedConfigFromBE = { a: ["test"] };
4047
+ * const currentConfigByUser = { a: [] };
4048
+ * merge(lastSavedConfigFromBE, currentConfigByUser); // returns { a: ["test"] }; but we expect { a: [] };
4049
+ *
4050
+ * More info: https://github.com/lodash/lodash/issues/1313
4051
+ *
4052
+ * This helper function addresses the issue by always replacing the old array with the new array during the merge process.
4053
+ *
4054
+ * This helper is used for another case in rke2.vue to handle merging addon chart default values with the user's current values.
4055
+ * It fixed https://github.com/rancher/dashboard/issues/12418
4056
+ */
4057
+ export function mergeWithReplaceArrays(obj1?: {}, obj2?: {}): any;
3943
4058
  export { isEqualBasic as isEqual };
3944
4059
  export function toDictionary(array: any, callback: any): any;
3945
4060
  /**
@@ -4588,7 +4703,7 @@ declare module '@shell/utils/version' {
4588
4703
  export function parse(str: any): any;
4589
4704
  export function sortable(str: any): any;
4590
4705
  export function compare(in1: any, in2: any): any;
4591
- export function isPrerelease(version: any): boolean;
4706
+ export function isPrerelease(version?: string): boolean;
4592
4707
  export function isDevBuild(version: any): boolean;
4593
4708
  export function getVersionInfo(store: any): {
4594
4709
  displayVersion: any;
@@ -1,6 +1,6 @@
1
1
  import { reactive, isReactive } from 'vue';
2
2
  import {
3
- clone, get, getter, isEmpty, toDictionary, remove, diff, definedKeys, deepToRaw
3
+ clone, get, getter, isEmpty, toDictionary, remove, diff, definedKeys, deepToRaw, mergeWithReplaceArrays
4
4
  } from '@shell/utils/object';
5
5
 
6
6
  describe('fx: get', () => {
@@ -378,3 +378,32 @@ describe('fx: deepToRaw', () => {
378
378
  });
379
379
  });
380
380
  });
381
+
382
+ describe('fx: mergeWithReplaceArrays', () => {
383
+ const testCases: Array<[object?, object?, object?]> = [
384
+ // Some array test cases, an array from the first object should be replaced with the array from the second object
385
+ [{ a: ['one'] }, { a: [] }, { a: [] }],
386
+ [{ a: ['one', 'two'] }, { a: ['one', 'two', 'three'] }, { a: ['one', 'two', 'three'] }],
387
+ [{ a: ['one', 'two'], b: ['three', 'four'] }, { a: ['one'], b: [] }, { a: ['one'], b: [] }],
388
+ [{
389
+ a: ['one', 'two'], b: ['three', 'four'], c: 'five'
390
+ }, { a: ['one'], b: [] }, {
391
+ a: ['one'], b: [], c: 'five'
392
+ }],
393
+ // Some other test cases
394
+ [{ a: 'one' }, { b: 'two' }, { a: 'one', b: 'two' }],
395
+ [{ a: 'one' }, { a: '', b: 'two' }, { a: '', b: 'two' }],
396
+ [{ a: 'one', b: 'two' }, { a: 1, c: { d: null } }, {
397
+ a: 1, b: 'two', c: { d: null }
398
+ }],
399
+ [undefined, undefined, {}],
400
+ [{}, undefined, {}],
401
+ [undefined, {}, {}],
402
+ ];
403
+
404
+ it.each(testCases)('should merge properly', (obj1, obj2, expected) => {
405
+ const result = mergeWithReplaceArrays(obj1, obj2);
406
+
407
+ expect(result).toStrictEqual(expected);
408
+ });
409
+ });
package/utils/object.js CHANGED
@@ -8,6 +8,7 @@ import isObject from 'lodash/isObject';
8
8
  import isArray from 'lodash/isArray';
9
9
  import isEqual from 'lodash/isEqual';
10
10
  import difference from 'lodash/difference';
11
+ import mergeWith from 'lodash/mergeWith';
11
12
  import { splitObjectPath, joinObjectPath } from '@shell/utils/string';
12
13
  import { addObject } from '@shell/utils/array';
13
14
 
@@ -471,3 +472,30 @@ export function deepToRaw(obj, cache = new WeakSet()) {
471
472
  return result;
472
473
  }
473
474
  }
475
+
476
+ /**
477
+ * Helper function to alter Lodash merge function default behaviour on merging arrays while updating machine pool configuration.
478
+ *
479
+ * In rke2.vue, the syncMachineConfigWithLatest function updates machine pool configuration by
480
+ * merging the latest configuration received from the backend with the current configuration updated by the user.
481
+ * However, Lodash's merge function treats arrays like object so index values are merged and not appended to arrays
482
+ * resulting in undesired outcomes for us, Example:
483
+ *
484
+ * const lastSavedConfigFromBE = { a: ["test"] };
485
+ * const currentConfigByUser = { a: [] };
486
+ * merge(lastSavedConfigFromBE, currentConfigByUser); // returns { a: ["test"] }; but we expect { a: [] };
487
+ *
488
+ * More info: https://github.com/lodash/lodash/issues/1313
489
+ *
490
+ * This helper function addresses the issue by always replacing the old array with the new array during the merge process.
491
+ *
492
+ * This helper is used for another case in rke2.vue to handle merging addon chart default values with the user's current values.
493
+ * It fixed https://github.com/rancher/dashboard/issues/12418
494
+ */
495
+ export function mergeWithReplaceArrays(obj1 = {}, obj2 = {}) {
496
+ return mergeWith(obj1, obj2, (obj1Value, obj2Value) => {
497
+ if (Array.isArray(obj1Value) && Array.isArray(obj2Value)) {
498
+ return obj2Value;
499
+ }
500
+ });
501
+ }
package/utils/version.js CHANGED
@@ -66,7 +66,7 @@ function comparePart(in1, in2) {
66
66
  return in1.localeCompare(in2);
67
67
  }
68
68
 
69
- export function isPrerelease(version) {
69
+ export function isPrerelease(version = '') {
70
70
  if (!semver.valid(version)) {
71
71
  version = semver.clean(version, { loose: true });
72
72
  }
package/vue.config.js CHANGED
@@ -131,11 +131,11 @@ const instrumentCode = () => {
131
131
  };
132
132
 
133
133
  const getLoaders = (SHELL_ABS) => [
134
- // Ensure there is a fallback for browsers that don't support web workers
134
+ // no fallback for pre-2013 browsers https://caniuse.com/webworkers
135
135
  {
136
136
  test: /web-worker.[a-z-]+.js/i,
137
137
  loader: 'worker-loader',
138
- options: { inline: 'fallback' },
138
+ options: { inline: 'no-fallback' },
139
139
  },
140
140
  // Handler for csv files (e.g. ec2 instance data)
141
141
  {
@@ -462,7 +462,7 @@ const getWatcherIgnored = (excludes) => {
462
462
  /dist-pkg/,
463
463
  /scripts\/standalone/,
464
464
  ];
465
- const pathExcludedPkg = excludes.map((excluded) => new RegExp(`/pkg.${ excluded }`));
465
+ const pathExcludedPkg = excludes.map((excluded) => new RegExp(`/pkg.${ excluded }/`));
466
466
  const pathsCombined = [...paths, ...pathExcludedPkg];
467
467
  const regexCombined = new RegExp(pathsCombined.map(({ source }) => source).join('|'));
468
468
 
@@ -516,6 +516,7 @@ module.exports = function(dir, _appConfig) {
516
516
  @import "~shell/assets/styles/base/_variables.scss";
517
517
  @import "~shell/assets/styles/base/_functions.scss";
518
518
  @import "~shell/assets/styles/base/_mixins.scss";
519
+ @import 'node_modules/xterm/css/xterm.css';
519
520
  `
520
521
  }
521
522
  }
@@ -1,30 +0,0 @@
1
- import { vspherePoolConfigMerge } from '@shell/machine-config/vmwarevsphere-pool-config-merge';
2
-
3
- describe('vspherePoolConfigMerge', () => {
4
- const testCases: Array<[object?, object?, object?]> = [
5
- // Some array test cases, an array from the first object should be replaced with the array from the second object
6
- [{ a: ['one'] }, { a: [] }, { a: [] }],
7
- [{ a: ['one', 'two'] }, { a: ['one', 'two', 'three'] }, { a: ['one', 'two', 'three'] }],
8
- [{ a: ['one', 'two'], b: ['three', 'four'] }, { a: ['one'], b: [] }, { a: ['one'], b: [] }],
9
- [{
10
- a: ['one', 'two'], b: ['three', 'four'], c: 'five'
11
- }, { a: ['one'], b: [] }, {
12
- a: ['one'], b: [], c: 'five'
13
- }],
14
- // Some other test cases
15
- [{ a: 'one' }, { b: 'two' }, { a: 'one', b: 'two' }],
16
- [{ a: 'one' }, { a: '', b: 'two' }, { a: '', b: 'two' }],
17
- [{ a: 'one', b: 'two' }, { a: 1, c: { d: null } }, {
18
- a: 1, b: 'two', c: { d: null }
19
- }],
20
- [undefined, undefined, {}],
21
- [{}, undefined, {}],
22
- [undefined, {}, {}],
23
- ];
24
-
25
- it.each(testCases)('should merge properly', (obj1, obj2, expected) => {
26
- const result = vspherePoolConfigMerge(obj1, obj2);
27
-
28
- expect(result).toStrictEqual(expected);
29
- });
30
- });
@@ -1,25 +0,0 @@
1
- import mergeWith from 'lodash/mergeWith';
2
-
3
- /**
4
- * Helper function to alter Lodash merge function default behaviour on merging arrays while updating machine pool configuration.
5
- *
6
- * In rke2.vue, the syncMachineConfigWithLatest function updates machine pool configuration by
7
- * merging the latest configuration received from the backend with the current configuration updated by the user.
8
- * However, Lodash's merge function treats arrays like object so index values are merged and not appended to arrays
9
- * resulting in undesired outcomes for us, Example:
10
- *
11
- * const lastSavedConfigFromBE = { a: ["test"] };
12
- * const currentConfigByUser = { a: [] };
13
- * merge(lastSavedConfigFromBE, currentConfigByUser); // returns { a: ["test"] }; but we expect { a: [] };
14
- *
15
- * More info: https://github.com/lodash/lodash/issues/1313
16
- *
17
- * This helper function addresses the issue by always replacing the old array with the new array during the merge process.
18
- */
19
- export function vspherePoolConfigMerge(obj1 = {}, obj2 = {}): Object {
20
- return mergeWith(obj1, obj2, (obj1Value, obj2Value) => {
21
- if (Array.isArray(obj1Value) && Array.isArray(obj2Value)) {
22
- return obj2Value;
23
- }
24
- });
25
- }