@rancher/shell 3.0.4 → 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 +37 -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 +1 -0
- package/components/PromptRestore.vue +1 -0
- package/components/ResourceCancelModal.vue +1 -0
- package/components/SortableTable/index.vue +10 -11
- package/components/__tests__/AsyncButton.test.ts +2 -2
- package/components/auth/__tests__/RoleDetailEdit.test.ts +3 -2
- package/components/form/ArrayList.vue +66 -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/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 +1 -0
- package/dialog/ForceMachineRemoveDialog.vue +4 -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/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/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 +3 -3
- 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/rancher-components/Form/Radio/RadioGroup.vue +25 -23
- package/rancher-components/RcDropdown/RcDropdown.vue +3 -2
- package/rancher-components/RcDropdown/RcDropdownTrigger.vue +10 -0
- 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 +8 -1
- package/utils/cluster.js +35 -0
- 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() {
|
|
@@ -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'),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rancher/shell",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.5-rc.1",
|
|
4
4
|
"description": "Rancher Dashboard Shell",
|
|
5
5
|
"repository": "https://github.com/rancherlabs/dashboard",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -75,7 +75,7 @@
|
|
|
75
75
|
"dayjs": "1.8.29",
|
|
76
76
|
"defu": "5.0.1",
|
|
77
77
|
"diff2html": "3.4.24",
|
|
78
|
-
"dompurify": "2.5
|
|
78
|
+
"dompurify": "3.2.5",
|
|
79
79
|
"element-matches": "^0.1.2",
|
|
80
80
|
"entities": "4.5.0",
|
|
81
81
|
"eslint-config-standard": "16.0.3",
|
|
@@ -105,7 +105,7 @@
|
|
|
105
105
|
"js-yaml-loader": "1.2.2",
|
|
106
106
|
"js-yaml": "4.1.0",
|
|
107
107
|
"jsdiff": "1.1.1",
|
|
108
|
-
"jsonpath-plus": "10.0
|
|
108
|
+
"jsonpath-plus": "10.3.0",
|
|
109
109
|
"jsrsasign": "11.0.0",
|
|
110
110
|
"jszip": "3.8.0",
|
|
111
111
|
"lodash": "4.17.21",
|
package/pages/account/index.vue
CHANGED
|
@@ -179,7 +179,10 @@ export default {
|
|
|
179
179
|
<h2 v-t="'accountAndKeys.apiKeys.title'" />
|
|
180
180
|
<div class="api-url">
|
|
181
181
|
<span>{{ t("accountAndKeys.apiKeys.apiEndpoint") }}</span>
|
|
182
|
-
<CopyToClipboardText
|
|
182
|
+
<CopyToClipboardText
|
|
183
|
+
:aria-label="t('accountAndKeys.apiKeys.copyApiEnpoint')"
|
|
184
|
+
:text="apiUrl"
|
|
185
|
+
/>
|
|
183
186
|
</div>
|
|
184
187
|
</div>
|
|
185
188
|
<button
|
package/pages/auth/login.vue
CHANGED
|
@@ -31,11 +31,12 @@ import {
|
|
|
31
31
|
import loadPlugins from '@shell/plugins/plugin';
|
|
32
32
|
import Loading from '@shell/components/Loading';
|
|
33
33
|
import { HARVESTER_NAME as HARVESTER } from '@shell/config/features';
|
|
34
|
+
import TabTitle from '@shell/components/TabTitle.vue';
|
|
34
35
|
|
|
35
36
|
export default {
|
|
36
37
|
name: 'Login',
|
|
37
38
|
components: {
|
|
38
|
-
LabeledInput, AsyncButton, Checkbox, BrandImage, Banner, InfoBox, CopyCode, Password, LocaleSelector, Loading
|
|
39
|
+
LabeledInput, AsyncButton, Checkbox, BrandImage, Banner, InfoBox, CopyCode, Password, LocaleSelector, Loading, TabTitle
|
|
39
40
|
},
|
|
40
41
|
|
|
41
42
|
data() {
|
|
@@ -311,10 +312,16 @@ export default {
|
|
|
311
312
|
v-if="$fetchState.pending"
|
|
312
313
|
mode="relative"
|
|
313
314
|
/>
|
|
314
|
-
<
|
|
315
|
+
<div
|
|
315
316
|
v-else
|
|
316
317
|
class="main-layout login"
|
|
317
318
|
>
|
|
319
|
+
<TabTitle
|
|
320
|
+
:show-child="false"
|
|
321
|
+
:breadcrumb="false"
|
|
322
|
+
>
|
|
323
|
+
{{ `${vendor} - ${t('login.login')}` }}
|
|
324
|
+
</TabTitle>
|
|
318
325
|
<div class="row gutless mb-20">
|
|
319
326
|
<div class="col span-6 p-20">
|
|
320
327
|
<p class="text-center">
|
|
@@ -509,9 +516,10 @@ export default {
|
|
|
509
516
|
class="col span-6 landscape"
|
|
510
517
|
data-testid="login-landscape__img"
|
|
511
518
|
file-name="login-landscape.svg"
|
|
519
|
+
:alt="t('login.landscapeAlt')"
|
|
512
520
|
/>
|
|
513
521
|
</div>
|
|
514
|
-
</
|
|
522
|
+
</div>
|
|
515
523
|
</template>
|
|
516
524
|
|
|
517
525
|
<style lang="scss" scoped>
|
package/pages/auth/logout.vue
CHANGED