@rancher/shell 3.0.3 → 3.0.5-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.
- package/assets/styles/base/_basic.scss +6 -0
- package/assets/styles/global/_button.scss +1 -0
- package/assets/translations/en-us.yaml +38 -3
- package/cloud-credential/aws.vue +2 -0
- package/components/AssignTo.vue +25 -11
- package/components/AsyncButton.vue +24 -7
- package/components/BannerGraphic.vue +1 -0
- package/components/CommunityLinks.vue +3 -3
- package/components/CopyToClipboardText.vue +2 -1
- package/components/DetailText.vue +5 -0
- package/components/DisableAuthProviderModal.vue +1 -0
- package/components/ExplorerMembers.vue +1 -1
- package/components/ExplorerProjectsNamespaces.vue +56 -14
- package/components/LandingPagePreference.vue +5 -3
- package/components/LocaleSelector.vue +38 -94
- package/components/ModalWithCard.vue +1 -0
- package/components/MoveModal.vue +1 -0
- package/components/PromptRemove.vue +2 -1
- package/components/PromptRestore.vue +1 -0
- package/components/ResourceCancelModal.vue +1 -0
- package/components/SortableTable/index.vue +35 -10
- package/components/StatusBadge.vue +10 -4
- package/components/__tests__/AsyncButton.test.ts +2 -2
- package/components/auth/Principal.vue +9 -3
- package/components/auth/__tests__/RoleDetailEdit.test.ts +3 -2
- package/components/form/ArrayList.vue +75 -54
- package/components/form/Command.vue +6 -15
- package/components/form/EnvVars.vue +15 -8
- package/components/form/HealthCheck.vue +3 -3
- package/components/form/HookOption.vue +11 -16
- package/components/form/KeyValue.vue +1 -1
- package/components/form/LabeledSelect.vue +2 -1
- package/components/form/LifecycleHooks.vue +3 -3
- package/components/form/MatchExpressions.vue +10 -7
- package/components/form/NameNsDescription.vue +123 -103
- package/components/form/Networking.vue +20 -12
- package/components/form/NodeAffinity.vue +31 -23
- package/components/form/NodeScheduling.vue +13 -3
- package/components/form/PodAffinity.vue +43 -43
- package/components/form/Probe.vue +67 -66
- package/components/form/ResourceQuota/Project.vue +5 -1
- package/components/form/ResourceSelector.vue +7 -9
- package/components/form/SSHKnownHosts/KnownHostsEditDialog.vue +6 -3
- package/components/form/SSHKnownHosts/__tests__/KnownHostsEditDialog.test.ts +12 -1
- package/components/form/SSHKnownHosts/index.vue +16 -2
- package/components/form/Security.vue +54 -56
- package/components/form/Select.vue +31 -6
- package/components/form/ShellInput.vue +5 -1
- package/components/form/Tolerations.vue +5 -1
- package/components/form/ValueFromResource.vue +134 -121
- package/components/form/WorkloadPorts.vue +18 -18
- package/components/form/__tests__/ArrayList.test.ts +3 -0
- package/components/form/__tests__/MatchExpressions.test.ts +12 -12
- package/components/form/__tests__/NameNsDescription.test.ts +115 -14
- package/components/form/__tests__/Probe.test.ts +12 -8
- package/components/form/__tests__/SSHKnownHosts.test.ts +11 -0
- package/components/form/__tests__/Select.test.ts +37 -0
- package/components/formatter/InternalExternalIP.vue +2 -0
- package/components/formatter/SecretData.vue +20 -7
- package/components/nav/Group.vue +15 -1
- package/components/nav/Header.vue +1 -0
- package/components/nav/Type.vue +12 -1
- package/components/templates/blank.vue +4 -1
- package/components/templates/default.vue +2 -0
- package/components/templates/home.vue +4 -1
- package/components/templates/plain.vue +4 -1
- package/composables/useRuntimeFlag.ts +29 -0
- package/config/router/routes.js +20 -13
- package/core/types.ts +5 -0
- package/dialog/AddCustomBadgeDialog.vue +1 -0
- package/dialog/DeactivateDriverDialog.vue +5 -4
- package/dialog/ForceMachineRemoveDialog.vue +4 -1
- package/dialog/GitRepoForceUpdateDialog.vue +1 -1
- package/edit/__tests__/monitoring.coreos.com.prometheusrule.test.ts +16 -3
- package/edit/auth/__tests__/oidc.test.ts +152 -109
- package/edit/auth/azuread.vue +1 -0
- package/edit/auth/googleoauth.vue +4 -0
- package/edit/auth/oidc.vue +37 -4
- package/edit/cloudcredential.vue +1 -0
- package/edit/fleet.cattle.io.gitrepo.vue +1 -0
- package/edit/logging.banzaicloud.io.output/__tests__/logging.banzaicloud.io.output.test.ts +40 -9
- package/edit/networking.k8s.io.ingress/IngressClass.vue +7 -3
- package/edit/networking.k8s.io.ingress/__tests__/IngressClass.test.ts +58 -0
- package/edit/persistentvolume/__tests__/persistentvolume.test.ts +14 -2
- package/edit/provisioning.cattle.io.cluster/SelectCredential.vue +1 -0
- package/edit/provisioning.cattle.io.cluster/rke2.vue +25 -34
- package/edit/provisioning.cattle.io.cluster/tabs/AgentConfiguration.vue +6 -1
- package/edit/provisioning.cattle.io.cluster/tabs/MachinePool.vue +29 -1
- package/edit/provisioning.cattle.io.cluster/tabs/etcd/index.vue +2 -2
- package/edit/token.vue +2 -0
- package/edit/workload/index.vue +1 -0
- package/edit/workload/mixins/workload.js +0 -2
- package/list/management.cattle.io.feature.vue +1 -0
- package/list/provisioning.cattle.io.cluster.vue +20 -12
- package/machine-config/__tests__/vmwarevsphere.test.ts +48 -3
- package/machine-config/vmwarevsphere.vue +16 -0
- package/models/__tests__/namespace.test.ts +25 -1
- package/models/cloudcredential.js +5 -0
- package/models/kontainerdriver.js +6 -3
- package/models/management.cattle.io.node.js +3 -3
- package/models/namespace.js +4 -5
- package/models/nodedriver.js +6 -3
- package/models/workload.js +4 -1
- package/package.json +4 -4
- package/pages/about.vue +16 -8
- package/pages/account/index.vue +4 -1
- package/pages/auth/login.vue +11 -3
- package/pages/auth/logout.vue +4 -1
- package/pages/auth/setup.vue +1 -0
- package/pages/auth/verify.vue +4 -1
- package/pages/c/_cluster/apps/charts/chart.vue +1 -1
- package/pages/diagnostic.vue +47 -2
- package/pages/fail-whale.vue +6 -3
- package/pages/home.vue +24 -18
- package/pages/support/index.vue +4 -1
- package/promptRemove/management.cattle.io.fleetworkspace.vue +1 -1
- package/promptRemove/management.cattle.io.globalrole.vue +1 -1
- package/promptRemove/management.cattle.io.project.vue +2 -2
- package/promptRemove/management.cattle.io.roletemplate.vue +1 -1
- package/promptRemove/pod.vue +1 -1
- package/rancher-components/Form/Radio/RadioGroup.vue +25 -23
- package/rancher-components/Form/ToggleSwitch/ToggleSwitch.vue +3 -3
- package/rancher-components/RcDropdown/RcDropdown.vue +6 -5
- package/rancher-components/RcDropdown/RcDropdownMenu.vue +4 -2
- package/rancher-components/RcDropdown/RcDropdownTrigger.vue +12 -2
- package/rancher-components/RcDropdown/useDropdownCollection.ts +8 -0
- package/rancher-components/RcDropdown/useDropdownContext.ts +9 -3
- package/scripts/extension/publish +1 -0
- package/server/har-file.js +25 -3
- package/store/features.js +2 -1
- package/store/type-map.js +4 -0
- package/types/shell/index.d.ts +9 -2
- package/utils/__tests__/string.test.ts +2 -2
- package/utils/cluster.js +35 -0
- package/utils/string.js +1 -3
- package/utils/validators/machine-pool.ts +20 -0
- package/components/formatter/ExtensionCache.vue +0 -74
- package/components/formatter/Port.vue +0 -24
- package/components/formatter/SecretType.vue +0 -41
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { shallowMount } from '@vue/test-utils';
|
|
2
|
+
import IngressClass from '@shell/edit/networking.k8s.io.ingress/IngressClass.vue';
|
|
3
|
+
import LabeledSelect from '@shell/components/form/LabeledSelect';
|
|
4
|
+
import { _EDIT } from '@shell/config/query-params';
|
|
5
|
+
|
|
6
|
+
jest.mock('@shell/components/form/LabeledSelect', () => ({
|
|
7
|
+
name: 'LabeledSelect',
|
|
8
|
+
template: '<div></div>',
|
|
9
|
+
props: ['value', 'taggable', 'searchable', 'mode', 'label', 'options', 'optionLabel'],
|
|
10
|
+
emits: ['update:value'],
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
describe('ingressClass.vue', () => {
|
|
14
|
+
it('renders correctly', () => {
|
|
15
|
+
const wrapper = shallowMount(IngressClass, {
|
|
16
|
+
props: {
|
|
17
|
+
value: {},
|
|
18
|
+
ingressClasses: [],
|
|
19
|
+
mode: _EDIT,
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
expect(wrapper.exists()).toBe(true);
|
|
24
|
+
expect(wrapper.findComponent(LabeledSelect).exists()).toBe(true);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('updates ingressClassName correctly', async() => {
|
|
28
|
+
const wrapper = shallowMount(IngressClass, {
|
|
29
|
+
props: {
|
|
30
|
+
value: {},
|
|
31
|
+
ingressClasses: [{ label: 'nginx', value: 'nginx' }],
|
|
32
|
+
mode: _EDIT,
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
await wrapper.findComponent(LabeledSelect).vm.$emit('update:value', 'nginx');
|
|
37
|
+
|
|
38
|
+
expect(wrapper.vm.ingressClassName).toBe('nginx');
|
|
39
|
+
expect(wrapper.props('value').spec.ingressClassName).toBe('nginx');
|
|
40
|
+
expect(wrapper.emitted()['update:value']).toBeTruthy();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('removes ingressClassName when none is selected', async() => {
|
|
44
|
+
const wrapper = shallowMount(IngressClass, {
|
|
45
|
+
props: {
|
|
46
|
+
value: { spec: { ingressClassName: 'nginx' } },
|
|
47
|
+
ingressClasses: [{ label: 'nginx', value: 'nginx' }],
|
|
48
|
+
mode: _EDIT,
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
await wrapper.findComponent(LabeledSelect).vm.$emit('update:value', '');
|
|
53
|
+
|
|
54
|
+
expect(wrapper.vm.ingressClassName).toBe('');
|
|
55
|
+
expect(wrapper.props('value').spec.ingressClassName).toBeUndefined();
|
|
56
|
+
expect(wrapper.emitted()['update:value']).toBeTruthy();
|
|
57
|
+
});
|
|
58
|
+
});
|
|
@@ -9,7 +9,13 @@ describe('view: PersistentVolume', () => {
|
|
|
9
9
|
};
|
|
10
10
|
const resource = 'PersistentVolume';
|
|
11
11
|
const wrapper = mount(PersistentVolume as ExtendedVue<Vue, {}, {}, {}, PersistentVolume>, {
|
|
12
|
-
props: {
|
|
12
|
+
props: {
|
|
13
|
+
value: {
|
|
14
|
+
setAnnotation: jest.fn(),
|
|
15
|
+
spec: {},
|
|
16
|
+
metadata: {},
|
|
17
|
+
}
|
|
18
|
+
},
|
|
13
19
|
|
|
14
20
|
global: {
|
|
15
21
|
mocks: {
|
|
@@ -54,7 +60,13 @@ describe('view: PersistentVolume', () => {
|
|
|
54
60
|
const plugin = 'csi';
|
|
55
61
|
const resource = 'PersistentVolume';
|
|
56
62
|
const wrapper = mount(PersistentVolume as ExtendedVue<Vue, {}, {}, {}, PersistentVolume>, {
|
|
57
|
-
props:
|
|
63
|
+
props: {
|
|
64
|
+
value: {
|
|
65
|
+
setAnnotation: jest.fn(),
|
|
66
|
+
spec: { [plugin]: { value: plugin } },
|
|
67
|
+
metadata: {},
|
|
68
|
+
}
|
|
69
|
+
},
|
|
58
70
|
global: {
|
|
59
71
|
mocks: {
|
|
60
72
|
$store: {
|
|
@@ -236,6 +236,7 @@ export default {
|
|
|
236
236
|
<Loading v-if="$fetchState.pending" />
|
|
237
237
|
<CruResource
|
|
238
238
|
v-else
|
|
239
|
+
:done-params="$attrs['done-params'] /* Without this, changes to the validationPassed prop end up propagating all the way to the root of the app and force a re-render when the input becomes valid. I haven't found a reasonable explanation for why this happens. */"
|
|
239
240
|
:mode="mode"
|
|
240
241
|
:validation-passed="validationPassed"
|
|
241
242
|
:resource="newCredential"
|
|
@@ -25,7 +25,9 @@ import {
|
|
|
25
25
|
clone, diff, set, get, isEmpty, mergeWithReplaceArrays
|
|
26
26
|
} from '@shell/utils/object';
|
|
27
27
|
import { allHash } from '@shell/utils/promise';
|
|
28
|
-
import {
|
|
28
|
+
import {
|
|
29
|
+
getAllOptionsAfterCurrentVersion, filterOutDeprecatedPatchVersions, isHarvesterSatisfiesVersion, labelForAddon, initSchedulingCustomization
|
|
30
|
+
} from '@shell/utils/cluster';
|
|
29
31
|
|
|
30
32
|
import { BadgeState } from '@components/BadgeState';
|
|
31
33
|
import { Banner } from '@components/Banner';
|
|
@@ -63,7 +65,6 @@ import ClusterAppearance from '@shell/components/form/ClusterAppearance';
|
|
|
63
65
|
import AddOnAdditionalManifest from '@shell/edit/provisioning.cattle.io.cluster/tabs/AddOnAdditionalManifest';
|
|
64
66
|
import VsphereUtils, { VMWARE_VSPHERE } from '@shell/utils/v-sphere';
|
|
65
67
|
import { mapGetters } from 'vuex';
|
|
66
|
-
import { SCHEDULING_CUSTOMIZATION } from '@shell/store/features';
|
|
67
68
|
const HARVESTER = 'harvester';
|
|
68
69
|
const HARVESTER_CLOUD_PROVIDER = 'harvester-cloud-provider';
|
|
69
70
|
const NETBIOS_TRUNCATION_LENGTH = 15;
|
|
@@ -149,7 +150,13 @@ export default {
|
|
|
149
150
|
await this.initSpecs();
|
|
150
151
|
await this.initAddons();
|
|
151
152
|
await this.initRegistry();
|
|
152
|
-
await this.
|
|
153
|
+
const sc = await initSchedulingCustomization(this.value.spec, this.features, this.$store, this.mode);
|
|
154
|
+
|
|
155
|
+
this.clusterAgentDefaultPC = sc.clusterAgentDefaultPC;
|
|
156
|
+
this.clusterAgentDefaultPDB = sc.clusterAgentDefaultPDB;
|
|
157
|
+
this.schedulingCustomizationFeatureEnabled = sc.schedulingCustomizationFeatureEnabled;
|
|
158
|
+
this.schedulingCustomizationOriginallyEnabled = sc.schedulingCustomizationOriginallyEnabled;
|
|
159
|
+
this.errors = this.errors.concat(sc.errors);
|
|
153
160
|
|
|
154
161
|
Object.entries(this.chartValues).forEach(([name, value]) => {
|
|
155
162
|
const key = this.chartVersionKey(name);
|
|
@@ -243,20 +250,21 @@ export default {
|
|
|
243
250
|
fvFormRuleSets: [{
|
|
244
251
|
path: 'metadata.name', rules: ['subDomain'], translationKey: 'nameNsDescription.name.label'
|
|
245
252
|
}],
|
|
246
|
-
harvesterVersionRange:
|
|
247
|
-
cisOverride:
|
|
253
|
+
harvesterVersionRange: {},
|
|
254
|
+
cisOverride: false,
|
|
248
255
|
truncateLimit,
|
|
249
|
-
busy:
|
|
250
|
-
machinePoolValidation:
|
|
251
|
-
machinePoolErrors:
|
|
252
|
-
addonConfigValidation:
|
|
253
|
-
allNamespaces:
|
|
254
|
-
extensionTabs:
|
|
255
|
-
clusterAgentDeploymentCustomization:
|
|
256
|
-
schedulingCustomizationFeatureEnabled:
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
256
|
+
busy: false,
|
|
257
|
+
machinePoolValidation: {}, // map of validation states for each machine pool
|
|
258
|
+
machinePoolErrors: {},
|
|
259
|
+
addonConfigValidation: {}, // validation state of each addon config (boolean of whether codemirror's yaml lint passed)
|
|
260
|
+
allNamespaces: [],
|
|
261
|
+
extensionTabs: getApplicableExtensionEnhancements(this, ExtensionPoint.TAB, TabLocation.CLUSTER_CREATE_RKE2, this.$route, this),
|
|
262
|
+
clusterAgentDeploymentCustomization: null,
|
|
263
|
+
schedulingCustomizationFeatureEnabled: false,
|
|
264
|
+
schedulingCustomizationOriginallyEnabled: false,
|
|
265
|
+
clusterAgentDefaultPC: null,
|
|
266
|
+
clusterAgentDefaultPDB: null,
|
|
267
|
+
activeTab: null,
|
|
260
268
|
REGISTRIES_TAB_NAME,
|
|
261
269
|
labelForAddon
|
|
262
270
|
|
|
@@ -1062,24 +1070,6 @@ export default {
|
|
|
1062
1070
|
}
|
|
1063
1071
|
},
|
|
1064
1072
|
|
|
1065
|
-
async initSchedulingCustomization() {
|
|
1066
|
-
this.schedulingCustomizationFeatureEnabled = this.features(SCHEDULING_CUSTOMIZATION);
|
|
1067
|
-
try {
|
|
1068
|
-
this.clusterAgentDefaultPC = JSON.parse((await this.$store.dispatch('management/find', { type: MANAGEMENT.SETTING, id: SETTING.CLUSTER_AGENT_DEFAULT_PRIORITY_CLASS })).value) || null;
|
|
1069
|
-
} catch (e) {
|
|
1070
|
-
this.errors.push(e);
|
|
1071
|
-
}
|
|
1072
|
-
try {
|
|
1073
|
-
this.clusterAgentDefaultPDB = JSON.parse((await this.$store.dispatch('management/find', { type: MANAGEMENT.SETTING, id: SETTING.CLUSTER_AGENT_DEFAULT_POD_DISTRIBUTION_BUDGET })).value) || null;
|
|
1074
|
-
} catch (e) {
|
|
1075
|
-
this.errors.push(e);
|
|
1076
|
-
}
|
|
1077
|
-
|
|
1078
|
-
if (this.schedulingCustomizationFeatureEnabled && this.mode === _CREATE && isEmpty(this.value?.spec?.clusterAgentDeploymentCustomization?.schedulingCustomization)) {
|
|
1079
|
-
set(this.value, 'spec.clusterAgentDeploymentCustomization.schedulingCustomization', { priorityClass: this.clusterAgentDefaultPC, podDisruptionBudget: this.clusterAgentDefaultPDB });
|
|
1080
|
-
}
|
|
1081
|
-
},
|
|
1082
|
-
|
|
1083
1073
|
setSchedulingCustomization(val) {
|
|
1084
1074
|
if (val) {
|
|
1085
1075
|
set(this.value, 'spec.clusterAgentDeploymentCustomization.schedulingCustomization', { priorityClass: this.clusterAgentDefaultPC, podDisruptionBudget: this.clusterAgentDefaultPDB });
|
|
@@ -2450,6 +2440,7 @@ export default {
|
|
|
2450
2440
|
:scheduling-customization-feature-enabled="schedulingCustomizationFeatureEnabled"
|
|
2451
2441
|
:default-p-c="clusterAgentDefaultPC"
|
|
2452
2442
|
:default-p-d-b="clusterAgentDefaultPDB"
|
|
2443
|
+
:scheduling-customization-originally-enabled="schedulingCustomizationOriginallyEnabled"
|
|
2453
2444
|
@scheduling-customization-changed="setSchedulingCustomization"
|
|
2454
2445
|
/>
|
|
2455
2446
|
</Tab>
|
|
@@ -48,6 +48,11 @@ export default {
|
|
|
48
48
|
type: Boolean,
|
|
49
49
|
required: false
|
|
50
50
|
},
|
|
51
|
+
schedulingCustomizationOriginallyEnabled: {
|
|
52
|
+
type: Boolean,
|
|
53
|
+
default: false
|
|
54
|
+
},
|
|
55
|
+
|
|
51
56
|
defaultPC: {
|
|
52
57
|
type: Object,
|
|
53
58
|
default: () => {},
|
|
@@ -149,7 +154,7 @@ export default {
|
|
|
149
154
|
},
|
|
150
155
|
|
|
151
156
|
schedulingCustomizationVisible() {
|
|
152
|
-
return this.schedulingCustomizationFeatureEnabled ||
|
|
157
|
+
return this.schedulingCustomizationFeatureEnabled || this.schedulingCustomizationOriginallyEnabled;
|
|
153
158
|
},
|
|
154
159
|
|
|
155
160
|
affinityOptions() {
|
|
@@ -8,6 +8,8 @@ import AdvancedSection from '@shell/components/AdvancedSection.vue';
|
|
|
8
8
|
import { Banner } from '@components/Banner';
|
|
9
9
|
import UnitInput from '@shell/components/form/UnitInput.vue';
|
|
10
10
|
import { randomStr } from '@shell/utils/string';
|
|
11
|
+
import FormValidation from '@shell/mixins/form-validation';
|
|
12
|
+
import { MACHINE_POOL_VALIDATION } from '@shell/utils/validators/machine-pool';
|
|
11
13
|
|
|
12
14
|
export default {
|
|
13
15
|
|
|
@@ -25,6 +27,8 @@ export default {
|
|
|
25
27
|
UnitInput
|
|
26
28
|
},
|
|
27
29
|
|
|
30
|
+
mixins: [FormValidation],
|
|
31
|
+
|
|
28
32
|
props: {
|
|
29
33
|
value: {
|
|
30
34
|
type: Object,
|
|
@@ -122,6 +126,12 @@ export default {
|
|
|
122
126
|
uuid: randomStr(),
|
|
123
127
|
|
|
124
128
|
unhealthyNodeTimeoutInteger: this.value.pool.unhealthyNodeTimeout ? parseDuration(this.value.pool.unhealthyNodeTimeout) : 0,
|
|
129
|
+
|
|
130
|
+
validationErrors: [],
|
|
131
|
+
|
|
132
|
+
MACHINE_POOL_VALIDATION,
|
|
133
|
+
|
|
134
|
+
fvFormRuleSets: MACHINE_POOL_VALIDATION.RULESETS,
|
|
125
135
|
};
|
|
126
136
|
},
|
|
127
137
|
|
|
@@ -136,7 +146,7 @@ export default {
|
|
|
136
146
|
|
|
137
147
|
isWindows() {
|
|
138
148
|
return this.value?.config?.os === 'windows';
|
|
139
|
-
}
|
|
149
|
+
}
|
|
140
150
|
},
|
|
141
151
|
|
|
142
152
|
watch: {
|
|
@@ -148,6 +158,20 @@ export default {
|
|
|
148
158
|
} else {
|
|
149
159
|
this.value.pool.machineOS = 'linux';
|
|
150
160
|
}
|
|
161
|
+
},
|
|
162
|
+
|
|
163
|
+
validationErrors: {
|
|
164
|
+
handler(newValue) {
|
|
165
|
+
this.$emit('validationChanged', newValue.length === 0);
|
|
166
|
+
},
|
|
167
|
+
deep: true
|
|
168
|
+
},
|
|
169
|
+
|
|
170
|
+
fvFormIsValid: {
|
|
171
|
+
handler(newValue) {
|
|
172
|
+
this.$emit('validationChanged', newValue);
|
|
173
|
+
},
|
|
174
|
+
deep: true
|
|
151
175
|
}
|
|
152
176
|
},
|
|
153
177
|
|
|
@@ -227,6 +251,8 @@ export default {
|
|
|
227
251
|
:label="t('cluster.machinePool.name.label')"
|
|
228
252
|
:required="true"
|
|
229
253
|
:disabled="!value.config || !!value.config.id || busy"
|
|
254
|
+
:rules="fvGetAndReportPathRules(MACHINE_POOL_VALIDATION.FIELDS.NAME)"
|
|
255
|
+
data-testid="machine-pool-name-input"
|
|
230
256
|
/>
|
|
231
257
|
</div>
|
|
232
258
|
<div class="col span-4">
|
|
@@ -238,6 +264,8 @@ export default {
|
|
|
238
264
|
type="number"
|
|
239
265
|
min="0"
|
|
240
266
|
:required="true"
|
|
267
|
+
:rules="fvGetAndReportPathRules(MACHINE_POOL_VALIDATION.FIELDS.QUANTITY)"
|
|
268
|
+
data-testid="machine-pool-quantity-input"
|
|
241
269
|
/>
|
|
242
270
|
</div>
|
|
243
271
|
<div class="col span-4 pt-5">
|
|
@@ -90,8 +90,8 @@ export default {
|
|
|
90
90
|
v-model:value="etcd.snapshotRetention"
|
|
91
91
|
:mode="mode"
|
|
92
92
|
:label="t('cluster.rke2.etcd.snapshotRetention.label')"
|
|
93
|
-
:suffix="t('cluster.rke2.snapshots.suffix')"
|
|
94
|
-
:tooltip="t('cluster.rke2.etcd.snapshotRetention.tooltip')"
|
|
93
|
+
:suffix="s3Backup ? t('cluster.rke2.snapshots.s3Suffix') : t('cluster.rke2.snapshots.suffix')"
|
|
94
|
+
:tooltip="s3Backup ? t('cluster.rke2.etcd.snapshotRetention.tooltip') : undefined"
|
|
95
95
|
/>
|
|
96
96
|
</div>
|
|
97
97
|
</div>
|
package/edit/token.vue
CHANGED
|
@@ -210,6 +210,7 @@ export default {
|
|
|
210
210
|
:disabled="form.expiryType !== 'custom'"
|
|
211
211
|
type="number"
|
|
212
212
|
:mode="mode"
|
|
213
|
+
:aria-label="t('accountAndKeys.apiKeys.add.ariaLabel.expiration')"
|
|
213
214
|
>
|
|
214
215
|
<Select
|
|
215
216
|
v-model:value="form.customExpiryUnits"
|
|
@@ -217,6 +218,7 @@ export default {
|
|
|
217
218
|
:options="expiryUnitsOptions"
|
|
218
219
|
:clearable="false"
|
|
219
220
|
:reduce="opt=>opt.value"
|
|
221
|
+
:aria-label="t('accountAndKeys.apiKeys.add.ariaLabel.expirationUnits')"
|
|
220
222
|
/>
|
|
221
223
|
</div>
|
|
222
224
|
</div>
|
package/edit/workload/index.vue
CHANGED
|
@@ -113,24 +113,32 @@ export default {
|
|
|
113
113
|
},
|
|
114
114
|
|
|
115
115
|
createLocation() {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
116
|
+
const options = this.$store.getters[`type-map/optionsFor`](this.resource)?.custom || {};
|
|
117
|
+
const params = {
|
|
118
|
+
product: this.$store.getters['currentProduct'].name,
|
|
119
|
+
resource: this.resource
|
|
120
|
+
};
|
|
121
|
+
const defaultLocation = {
|
|
122
|
+
name: 'c-cluster-product-resource-create',
|
|
123
|
+
params
|
|
122
124
|
};
|
|
125
|
+
|
|
126
|
+
return options.createLocation ? options.createLocation(params) : defaultLocation;
|
|
123
127
|
},
|
|
124
128
|
|
|
125
129
|
importLocation() {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
130
|
+
const options = this.$store.getters[`type-map/optionsFor`](this.resource)?.custom || {};
|
|
131
|
+
const params = {
|
|
132
|
+
product: this.$store.getters['currentProduct'].name,
|
|
133
|
+
resource: this.resource
|
|
134
|
+
};
|
|
135
|
+
const defaultLocation = {
|
|
136
|
+
name: 'c-cluster-product-resource-create',
|
|
137
|
+
params,
|
|
132
138
|
query: { [MODE]: _IMPORT }
|
|
133
139
|
};
|
|
140
|
+
|
|
141
|
+
return options.importLocation ? options.importLocation(params) : defaultLocation;
|
|
134
142
|
},
|
|
135
143
|
|
|
136
144
|
canImport() {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { mount } from '@vue/test-utils';
|
|
1
|
+
import { mount, shallowMount } from '@vue/test-utils';
|
|
2
2
|
import vmwarevsphere from '@shell/machine-config/vmwarevsphere.vue';
|
|
3
3
|
import { DEFAULT_VALUES, SENTINEL } from '@shell/machine-config/vmwarevsphere-config';
|
|
4
4
|
|
|
@@ -172,12 +172,12 @@ describe('component: vmwarevsphere', () => {
|
|
|
172
172
|
name: 'tag_name',
|
|
173
173
|
category: 'tag_category',
|
|
174
174
|
};
|
|
175
|
-
const
|
|
175
|
+
const expectedResult = [{
|
|
176
176
|
...tag, label: `${ tag.category } / ${ tag.name }`, value: tag.id
|
|
177
177
|
}];
|
|
178
178
|
const wrapper = mount(vmwarevsphere, defaultCreateSetup);
|
|
179
179
|
|
|
180
|
-
expect(wrapper.vm.mapTagsToContent([tag])).toStrictEqual(
|
|
180
|
+
expect(wrapper.vm.mapTagsToContent([tag])).toStrictEqual(expectedResult);
|
|
181
181
|
});
|
|
182
182
|
});
|
|
183
183
|
|
|
@@ -254,4 +254,49 @@ describe('component: vmwarevsphere', () => {
|
|
|
254
254
|
});
|
|
255
255
|
});
|
|
256
256
|
});
|
|
257
|
+
|
|
258
|
+
describe('syncNetworkValueForLegacyLabels', () => {
|
|
259
|
+
it('should update the current network value properly', () => {
|
|
260
|
+
const legacyName = 'legacy_name';
|
|
261
|
+
const legacyValue = 'legacy_value';
|
|
262
|
+
const networkLabel = 'network_label';
|
|
263
|
+
|
|
264
|
+
const wrapper = shallowMount(vmwarevsphere, {
|
|
265
|
+
...defaultEditSetup,
|
|
266
|
+
propsData: {
|
|
267
|
+
...defaultEditSetup.propsData,
|
|
268
|
+
value: {
|
|
269
|
+
...defaultEditSetup.propsData.value,
|
|
270
|
+
network: [legacyName]
|
|
271
|
+
}
|
|
272
|
+
},
|
|
273
|
+
computed: {
|
|
274
|
+
networks: () => [
|
|
275
|
+
{
|
|
276
|
+
name: legacyName, label: networkLabel, value: legacyValue
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
name: 'name1', label: 'label1', value: 'value1'
|
|
280
|
+
},
|
|
281
|
+
{
|
|
282
|
+
name: 'name2', label: 'label2', value: 'value2'
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
name: 'name3', label: 'label3', value: 'value3'
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
name: 'name4', label: 'label4', value: 'value4'
|
|
289
|
+
},
|
|
290
|
+
]
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
// check the current network before updating
|
|
295
|
+
expect(wrapper.vm.value.network).toStrictEqual([legacyName]);
|
|
296
|
+
|
|
297
|
+
wrapper.vm.syncNetworkValueForLegacyLabels();
|
|
298
|
+
|
|
299
|
+
expect(wrapper.vm.value.network).toStrictEqual([legacyValue]);
|
|
300
|
+
});
|
|
301
|
+
});
|
|
257
302
|
});
|
|
@@ -558,6 +558,7 @@ export default {
|
|
|
558
558
|
this.resetValueIfNecessary('network', content, options, true);
|
|
559
559
|
|
|
560
560
|
set(this, 'networksResults', content);
|
|
561
|
+
this.syncNetworkValueForLegacyLabels();
|
|
561
562
|
this.vappMode = this.getInitialVappMode(this.value);
|
|
562
563
|
},
|
|
563
564
|
|
|
@@ -669,6 +670,21 @@ export default {
|
|
|
669
670
|
}
|
|
670
671
|
},
|
|
671
672
|
|
|
673
|
+
// Network labels have been updated to include the MOID.
|
|
674
|
+
// To ensure previously selected networks remain consistent with this change,
|
|
675
|
+
// we update the current network value to allow correct selection from the network list.
|
|
676
|
+
syncNetworkValueForLegacyLabels() {
|
|
677
|
+
const currentNetwork = this.value.network[0];
|
|
678
|
+
|
|
679
|
+
if (this.mode !== _CREATE && currentNetwork) {
|
|
680
|
+
const networkMatch = this.networks.find((network) => currentNetwork === network.name && currentNetwork !== network.label);
|
|
681
|
+
|
|
682
|
+
if (networkMatch) {
|
|
683
|
+
this.value.network = [networkMatch.value];
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
},
|
|
687
|
+
|
|
672
688
|
mapPathOptionsToContent(pathOptions) {
|
|
673
689
|
return (pathOptions || []).map((pathOption) => {
|
|
674
690
|
return {
|
|
@@ -84,7 +84,31 @@ describe('class Namespace', () => {
|
|
|
84
84
|
});
|
|
85
85
|
|
|
86
86
|
it.todo('should return the project');
|
|
87
|
-
|
|
87
|
+
|
|
88
|
+
describe('handling groupById', () => {
|
|
89
|
+
it('should return the groupById if have project id', () => {
|
|
90
|
+
const namespace = new Namespace({});
|
|
91
|
+
|
|
92
|
+
jest.spyOn(namespace, 'project', 'get').mockReturnValue({
|
|
93
|
+
id: 'mock-project-id',
|
|
94
|
+
type: 'project',
|
|
95
|
+
name: 'mock-project',
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
expect(namespace.groupById).toStrictEqual('mock-project-id');
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('should return the groupById if project id undefined', () => {
|
|
102
|
+
const t = jest.fn(() => 'Not in a Project');
|
|
103
|
+
const ctx = { rootGetters: { 'i18n/t': t } };
|
|
104
|
+
const namespace = new Namespace({}, ctx);
|
|
105
|
+
|
|
106
|
+
jest.spyOn(namespace, 'project', 'get').mockReturnValue({});
|
|
107
|
+
|
|
108
|
+
expect(namespace.groupById).toStrictEqual('Not in a Project');
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
88
112
|
it.todo('should return the project name with i18n');
|
|
89
113
|
it.todo('should return the projectNameSort');
|
|
90
114
|
it.todo('should check if istioInstalled');
|
|
@@ -72,14 +72,17 @@ export default class KontainerDriver extends Driver {
|
|
|
72
72
|
return this.$dispatch('rancher/request', {
|
|
73
73
|
url: `v3/kontainerDrivers/${ escape(this.id) }?action=activate`,
|
|
74
74
|
method: 'post',
|
|
75
|
-
}, { root: true })
|
|
75
|
+
}, { root: true }).catch((err) => {
|
|
76
|
+
this.$dispatch('growl/fromError', { title: this.t('drivers.error.activate', { name: this.nameDisplay }), err }, { root: true });
|
|
77
|
+
});
|
|
76
78
|
}
|
|
77
79
|
|
|
78
80
|
async activateBulk(resources) {
|
|
79
81
|
await Promise.all(resources.map((resource) => this.$dispatch('rancher/request', {
|
|
80
82
|
url: `v3/kontainerDrivers/${ escape(resource.id) }?action=activate`,
|
|
81
83
|
method: 'post',
|
|
82
|
-
}, { root: true }
|
|
83
|
-
|
|
84
|
+
}, { root: true }).catch((err) => {
|
|
85
|
+
this.$dispatch('growl/fromError', { title: this.t('drivers.error.activate', { name: resource.nameDisplay }), err }, { root: true });
|
|
86
|
+
})));
|
|
84
87
|
}
|
|
85
88
|
}
|
|
@@ -168,12 +168,12 @@ export default class MgmtNode extends HybridModel {
|
|
|
168
168
|
}
|
|
169
169
|
|
|
170
170
|
get canScaleDown() {
|
|
171
|
-
|
|
171
|
+
const hasAction = this.norman?.actions?.scaledown;
|
|
172
|
+
|
|
173
|
+
if (!this.isEtcd && !this.isControlPlane && hasAction) {
|
|
172
174
|
return true;
|
|
173
175
|
}
|
|
174
176
|
|
|
175
|
-
const hasAction = this.norman?.actions?.scaledown;
|
|
176
|
-
|
|
177
177
|
return hasAction && notOnlyOfRole(this, this.provisioningCluster?.nodes);
|
|
178
178
|
}
|
|
179
179
|
}
|
package/models/namespace.js
CHANGED
|
@@ -5,7 +5,6 @@ import {
|
|
|
5
5
|
import { ISTIO, MANAGEMENT } from '@shell/config/types';
|
|
6
6
|
|
|
7
7
|
import { get, set } from '@shell/utils/object';
|
|
8
|
-
import { escapeHtml } from '@shell/utils/string';
|
|
9
8
|
import { insertAt, isArray } from '@shell/utils/array';
|
|
10
9
|
import SteveModel from '@shell/plugins/steve/steve-class';
|
|
11
10
|
import { HARVESTER_NAME as HARVESTER } from '@shell/config/features';
|
|
@@ -124,11 +123,11 @@ export default class Namespace extends SteveModel {
|
|
|
124
123
|
return project;
|
|
125
124
|
}
|
|
126
125
|
|
|
127
|
-
get
|
|
128
|
-
const
|
|
126
|
+
get groupById() {
|
|
127
|
+
const projectId = this.project?.id;
|
|
129
128
|
|
|
130
|
-
if (
|
|
131
|
-
return
|
|
129
|
+
if ( projectId ) {
|
|
130
|
+
return projectId;
|
|
132
131
|
} else {
|
|
133
132
|
return this.$rootGetters['i18n/t']('resourceTable.groupLabel.notInAProject');
|
|
134
133
|
}
|
package/models/nodedriver.js
CHANGED
|
@@ -80,14 +80,17 @@ export default class NodeDriver extends Driver {
|
|
|
80
80
|
return this.$dispatch('rancher/request', {
|
|
81
81
|
url: `v3/nodeDrivers/${ escape(this.id) }?action=activate`,
|
|
82
82
|
method: 'post',
|
|
83
|
-
}, { root: true })
|
|
83
|
+
}, { root: true }).catch((err) => {
|
|
84
|
+
this.$dispatch('growl/fromError', { title: this.t('drivers.error.activate', { name: this.nameDisplay }), err }, { root: true });
|
|
85
|
+
});
|
|
84
86
|
}
|
|
85
87
|
|
|
86
88
|
async activateBulk(resources) {
|
|
87
89
|
await Promise.all(resources.map((resource) => this.$dispatch('rancher/request', {
|
|
88
90
|
url: `v3/nodeDrivers/${ escape(resource.id) }?action=activate`,
|
|
89
91
|
method: 'post',
|
|
90
|
-
}, { root: true }
|
|
91
|
-
|
|
92
|
+
}, { root: true }).catch((err) => {
|
|
93
|
+
this.$dispatch('growl/fromError', { title: this.t('drivers.error.activate', { name: resource.nameDisplay }), err }, { root: true });
|
|
94
|
+
})));
|
|
92
95
|
}
|
|
93
96
|
}
|
package/models/workload.js
CHANGED
|
@@ -34,7 +34,10 @@ export default class Workload extends WorkloadService {
|
|
|
34
34
|
enabled: !!this.links.update,
|
|
35
35
|
});
|
|
36
36
|
|
|
37
|
-
if (type !== WORKLOAD_TYPES.JOB &&
|
|
37
|
+
if (type !== WORKLOAD_TYPES.JOB &&
|
|
38
|
+
type !== WORKLOAD_TYPES.CRON_JOB &&
|
|
39
|
+
type !== WORKLOAD_TYPES.REPLICA_SET
|
|
40
|
+
) {
|
|
38
41
|
insertAt(out, 0, {
|
|
39
42
|
action: 'toggleRollbackModal',
|
|
40
43
|
label: this.t('action.rollback'),
|