@rancher/shell 3.0.12-rc.4 → 3.0.12-rc.6
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/apis/intf/resources-api/resource-base.ts +42 -2
- package/apis/intf/resources-api/resource-instance.ts +101 -0
- package/apis/intf/resources-api/resources-api.ts +177 -23
- package/apis/intf/resources.ts +8 -6
- package/apis/resources/__tests__/resources-api-class.test.ts +309 -28
- package/apis/resources/resources-api-class.ts +232 -43
- package/assets/images/providers/traefik.png +0 -0
- package/assets/styles/global/_button.scss +1 -1
- package/assets/translations/en-us.yaml +49 -14
- package/chart/__tests__/rancher-monitoring-dashboards-index.test.ts +269 -0
- package/chart/rancher-monitoring-dashboards/index.vue +155 -0
- package/cloud-credential/__tests__/generic.test.ts +132 -0
- package/cloud-credential/generic.vue +33 -3
- package/components/ActionDropdownShell.vue +5 -3
- package/components/ButtonGroup.vue +26 -1
- package/components/CruResource.vue +51 -2
- package/components/EtcdInfoBanner.vue +2 -12
- package/components/GrafanaDashboard.vue +2 -8
- package/components/PromptRestore.vue +93 -32
- package/components/Questions/index.vue +1 -0
- package/components/Resource/Detail/Masthead/__tests__/index.test.ts +68 -14
- package/components/Resource/Detail/Masthead/index.vue +8 -3
- package/components/ResourceTable.vue +1 -0
- package/components/SortableTable/index.vue +4 -3
- package/components/Wizard.vue +14 -1
- package/components/__tests__/ButtonGroup.test.ts +56 -0
- package/components/__tests__/PromptRestore.test.ts +169 -19
- package/components/fleet/GitRepoAdvancedTab.vue +1 -0
- package/components/fleet/GitRepoMetadataTab.vue +5 -0
- package/components/fleet/HelmOpAppCoConfigTab.vue +4 -0
- package/components/fleet/HelmOpMetadataTab.vue +5 -0
- package/components/form/FileSelector.vue +39 -1
- package/components/form/PrivateRegistry.constants.ts +7 -0
- package/components/form/PrivateRegistry.vue +253 -18
- package/components/form/Security.vue +1 -1
- package/components/form/SelectOrCreateAuthSecret.vue +140 -17
- package/components/form/__tests__/FileSelector.test.ts +23 -0
- package/components/form/__tests__/PrivateRegistry.test.ts +463 -73
- package/components/form/__tests__/SelectOrCreateAuthSecret.test.ts +122 -0
- package/components/formatter/EtcdSnapshotName.vue +73 -0
- package/components/formatter/Translate.vue +22 -1
- package/components/nav/Group.vue +5 -10
- package/components/nav/Header.vue +8 -1
- package/components/nav/TopLevelMenu.vue +1 -30
- package/components/nav/__tests__/Group.test.ts +61 -0
- package/components/templates/default.vue +7 -0
- package/config/features.js +1 -0
- package/config/labels-annotations.js +3 -0
- package/config/product/manager.js +6 -0
- package/config/secret.ts +10 -0
- package/config/settings.ts +6 -2
- package/config/table-headers.js +5 -2
- package/config/types.js +7 -0
- package/core/__tests__/plugin-products-apply.test.ts +787 -0
- package/core/__tests__/plugin-products-extend.test.ts +189 -0
- package/core/__tests__/plugin-products-helpers.test.ts +81 -78
- package/core/__tests__/plugin-products-new.test.ts +389 -0
- package/core/__tests__/plugin-products-scenarios.test.ts +980 -0
- package/core/__tests__/plugin-products-side-menu.test.ts +1935 -0
- package/core/plugin-products-base.ts +201 -93
- package/core/plugin-products-extending.ts +1 -1
- package/core/plugin-products-external.ts +380 -0
- package/core/plugin-products-helpers.ts +28 -24
- package/core/plugin-products-internal.ts +207 -0
- package/core/plugin-products-top-level.ts +10 -5
- package/core/plugin-products-type-guards.ts +17 -6
- package/core/plugin-products.ts +5 -7
- package/core/plugin-types.ts +1 -389
- package/core/plugin.ts +10 -11
- package/core/types.ts +6 -202
- package/detail/__tests__/provisioning.cattle.io.cluster.test.ts +52 -0
- package/detail/pod.vue +1 -1
- package/detail/provisioning.cattle.io.cluster.vue +79 -5
- package/dialog/RotateEncryptionKeyDialog.vue +33 -9
- package/dialog/__tests__/RotateEncryptionKeyDialog.test.ts +78 -0
- package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +92 -0
- package/edit/__tests__/fleet.cattle.io.helmop.test.ts +101 -0
- package/edit/__tests__/management.cattle.io.setting.test.ts +2 -1
- package/edit/auth/AuthProviderWarningBanners.vue +12 -0
- package/edit/auth/__tests__/AuthProviderWarningBanners.test.ts +10 -1
- package/edit/auth/__tests__/azuread.test.ts +1 -0
- package/edit/auth/__tests__/github.test.ts +1 -0
- package/edit/auth/__tests__/oidc.test.ts +1 -0
- package/edit/auth/__tests__/saml.test.ts +1 -0
- package/edit/auth/ldap/__tests__/index.test.ts +1 -0
- package/edit/compliance.cattle.io.clusterscanprofile.vue +39 -41
- package/edit/fleet.cattle.io.gitrepo.vue +70 -16
- package/edit/fleet.cattle.io.helmop.vue +51 -5
- package/edit/helm.cattle.io.projecthelmchart.vue +1 -0
- package/edit/{management.cattle.io.setting.vue → management.cattle.io.setting/index.vue} +32 -9
- package/edit/management.cattle.io.setting/system-default-registry-pull-secrets.vue +81 -0
- package/edit/provisioning.cattle.io.cluster/SelectCredential.vue +3 -12
- package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +18 -0
- package/edit/provisioning.cattle.io.cluster/index.vue +1 -1
- package/edit/provisioning.cattle.io.cluster/rke2.vue +6 -2
- package/edit/provisioning.cattle.io.cluster/tabs/etcd/index.vue +0 -1
- package/edit/provisioning.cattle.io.cluster/tabs/networking/index.vue +2 -2
- package/edit/provisioning.cattle.io.cluster/tabs/registries/index.vue +14 -55
- package/list/management.cattle.io.user.vue +24 -5
- package/list/utils/management.cattle.io.cluster.utils.ts +1 -1
- package/machine-config/__tests__/generic.test.ts +163 -0
- package/machine-config/components/EC2Networking.vue +31 -7
- package/machine-config/components/__tests__/EC2Networking.test.ts +38 -15
- package/machine-config/generic.vue +32 -5
- package/mixins/resource-fetch.js +1 -1
- package/models/__tests__/provisioning.cattle.io.cluster.test.ts +156 -0
- package/models/__tests__/rke-machine.cattle.io.vmwarevspheremachinetemplate.test.ts +22 -0
- package/models/__tests__/secret.test.ts +68 -1
- package/models/management.cattle.io.cluster.js +21 -3
- package/models/pod.js +25 -12
- package/models/provisioning.cattle.io.cluster.js +59 -9
- package/models/rke-machine.cattle.io.vmwarevspheremachinetemplate.js +9 -0
- package/models/rke.cattle.io.etcdsnapshot.js +17 -9
- package/models/secret.js +19 -0
- package/models/workload.js +12 -7
- package/package.json +1 -1
- package/pages/auth/login.vue +1 -1
- package/pages/c/_cluster/apps/charts/__tests__/install.test.ts +569 -81
- package/pages/c/_cluster/apps/charts/install.vue +143 -28
- package/pages/c/_cluster/auth/config/index.vue +11 -4
- package/pages/c/_cluster/monitoring/index.vue +6 -1
- package/pkg/require-asset.lib.js +25 -0
- package/pkg/vue.config.js +7 -0
- package/plugins/dashboard-store/__tests__/resource-class.test.ts +84 -0
- package/plugins/dashboard-store/getters.js +0 -1
- package/plugins/dashboard-store/resource-class.js +62 -15
- package/plugins/steve/__tests__/steve-class-resource-api.test.ts +147 -0
- package/plugins/steve/steve-class.js +43 -0
- package/plugins/steve/subscribe.js +11 -0
- package/rancher-components/Form/LabeledInput/LabeledInput.test.ts +115 -0
- package/rancher-components/Form/LabeledInput/LabeledInput.vue +83 -3
- package/rancher-components/Form/TextArea/TextAreaAutoGrow.vue +30 -0
- package/rancher-components/Form/TextArea/__tests__/TextAreaAutoGrow.test.ts +95 -0
- package/rancher-components/RcButton/index.ts +1 -1
- package/rancher-components/RcDropdown/RcDropdownTrigger.vue +6 -1
- package/store/__tests__/auth.test.ts +18 -0
- package/store/__tests__/features.test.ts +131 -0
- package/store/__tests__/growl.test.ts +374 -0
- package/store/__tests__/modal.test.ts +131 -0
- package/store/__tests__/notifications.test.ts +466 -1
- package/store/__tests__/slideInPanel.test.ts +88 -0
- package/store/__tests__/type-map.utils.test.ts +433 -0
- package/store/__tests__/ui-context.test.ts +255 -0
- package/store/auth.js +11 -1
- package/store/features.js +5 -0
- package/store/plugins.js +6 -0
- package/types/rancher/steve.api.ts +2 -2
- package/types/shell/index.d.ts +120 -0
- package/types/store/dashboard-store.types.ts +2 -2
- package/types/store/type-map.ts +262 -1
- package/utils/__tests__/operation-cr.test.ts +34 -0
- package/utils/async.ts +2 -0
- package/utils/grafana.js +2 -17
- package/utils/monitoring.js +38 -1
- package/utils/operation-cr.js +19 -0
- package/utils/require-asset.ts +7 -0
- package/utils/validators/__tests__/private-registry.test.ts +27 -15
- package/utils/validators/private-registry.ts +15 -4
- package/core/__tests__/plugin-products.test.ts +0 -4694
|
@@ -127,6 +127,12 @@ export default {
|
|
|
127
127
|
default: () => []
|
|
128
128
|
},
|
|
129
129
|
|
|
130
|
+
// Used to be called before going to the next step in the wizard.
|
|
131
|
+
beforeNext: {
|
|
132
|
+
type: Function,
|
|
133
|
+
default: null
|
|
134
|
+
},
|
|
135
|
+
|
|
130
136
|
stepsOptions: {
|
|
131
137
|
type: Object,
|
|
132
138
|
default: () => ({ editFirstStep: true })
|
|
@@ -200,6 +206,7 @@ export default {
|
|
|
200
206
|
tocContainerHeight: 0,
|
|
201
207
|
mainLayoutEl: null,
|
|
202
208
|
throttledComputeTocContainerHeight: null,
|
|
209
|
+
nextValidating: false,
|
|
203
210
|
/**
|
|
204
211
|
* Initialised on demand (given that it needs to make a request to fetch schema definition)
|
|
205
212
|
*/
|
|
@@ -488,6 +495,47 @@ export default {
|
|
|
488
495
|
return slot !== 'default' && typeof this.$slots[slot] === 'function';
|
|
489
496
|
},
|
|
490
497
|
|
|
498
|
+
async wizardBeforeGoToStep(fromStep, toStep) {
|
|
499
|
+
if (!this.beforeNext) {
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
const fromIdx = this.steps.findIndex((s) => s.name === fromStep.name);
|
|
504
|
+
const toIdx = this.steps.findIndex((s) => s.name === toStep.name);
|
|
505
|
+
|
|
506
|
+
if (toIdx <= fromIdx) {
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
try {
|
|
511
|
+
await this.beforeNext(fromStep);
|
|
512
|
+
this.$emit('error', []);
|
|
513
|
+
} catch (err) {
|
|
514
|
+
this.$emit('error', exceptionToErrorsArray(err));
|
|
515
|
+
throw err;
|
|
516
|
+
}
|
|
517
|
+
},
|
|
518
|
+
|
|
519
|
+
async handleNext(nextFn, activeStep) {
|
|
520
|
+
if (!this.beforeNext) {
|
|
521
|
+
nextFn();
|
|
522
|
+
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
this.nextValidating = true;
|
|
527
|
+
|
|
528
|
+
try {
|
|
529
|
+
await this.beforeNext(activeStep);
|
|
530
|
+
this.$emit('error', []);
|
|
531
|
+
nextFn();
|
|
532
|
+
} catch (err) {
|
|
533
|
+
this.$emit('error', exceptionToErrorsArray(err));
|
|
534
|
+
} finally {
|
|
535
|
+
this.nextValidating = false;
|
|
536
|
+
}
|
|
537
|
+
},
|
|
538
|
+
|
|
491
539
|
formatError(err) {
|
|
492
540
|
if ( typeof err === 'string') {
|
|
493
541
|
return err;
|
|
@@ -751,6 +799,7 @@ export default {
|
|
|
751
799
|
:edit-first-step="stepsOptions.editFirstStep"
|
|
752
800
|
:errors="errors"
|
|
753
801
|
:finish-mode="finishMode"
|
|
802
|
+
:before-go-to-step="wizardBeforeGoToStep"
|
|
754
803
|
class="wizard"
|
|
755
804
|
@error="e=>errors = e"
|
|
756
805
|
>
|
|
@@ -834,10 +883,10 @@ export default {
|
|
|
834
883
|
name="next"
|
|
835
884
|
>
|
|
836
885
|
<button
|
|
837
|
-
:disabled="!canNext"
|
|
886
|
+
:disabled="!canNext || nextValidating"
|
|
838
887
|
type="button"
|
|
839
888
|
class="btn role-primary"
|
|
840
|
-
@click="next
|
|
889
|
+
@click="handleNext(next, activeStep)"
|
|
841
890
|
>
|
|
842
891
|
<t k="wizard.next" />
|
|
843
892
|
</button>
|
|
@@ -3,23 +3,13 @@ import { Banner } from '@components/Banner';
|
|
|
3
3
|
import Loading from '@shell/components/Loading';
|
|
4
4
|
import { mapGetters } from 'vuex';
|
|
5
5
|
import { hasLeader, leaderChanges, failedProposals } from '@shell/utils/grafana';
|
|
6
|
-
import {
|
|
6
|
+
import { fetchMonitoringVersion } from '@shell/utils/monitoring';
|
|
7
7
|
|
|
8
8
|
export default {
|
|
9
9
|
components: { Banner, Loading },
|
|
10
10
|
async fetch() {
|
|
11
11
|
const inStore = this.$store.getters['currentProduct'].inStore;
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
if (this.$store.getters[`${ inStore }/canList`](CATALOG.APP)) {
|
|
15
|
-
try {
|
|
16
|
-
const res = await this.$store.dispatch(`${ inStore }/find`, { type: CATALOG.APP, id: 'cattle-monitoring-system/rancher-monitoring' });
|
|
17
|
-
|
|
18
|
-
monitoringVersion = res?.currentVersion;
|
|
19
|
-
} catch (err) {
|
|
20
|
-
|
|
21
|
-
}
|
|
22
|
-
}
|
|
12
|
+
const monitoringVersion = await fetchMonitoringVersion(this.$store, inStore);
|
|
23
13
|
|
|
24
14
|
const leader = await hasLeader(monitoringVersion, this.$store.dispatch, this.currentCluster.id);
|
|
25
15
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import Loading from '@shell/components/Loading';
|
|
3
3
|
import { Banner } from '@components/Banner';
|
|
4
4
|
import { computeDashboardUrl } from '@shell/utils/grafana';
|
|
5
|
-
import {
|
|
5
|
+
import { fetchMonitoringVersion } from '@shell/utils/monitoring';
|
|
6
6
|
|
|
7
7
|
export default {
|
|
8
8
|
components: { Banner, Loading },
|
|
@@ -41,13 +41,7 @@ export default {
|
|
|
41
41
|
async fetch() {
|
|
42
42
|
const inStore = this.$store.getters['currentProduct'].inStore;
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
try {
|
|
46
|
-
const res = await this.$store.dispatch(`${ inStore }/find`, { type: CATALOG.APP, id: 'cattle-monitoring-system/rancher-monitoring' });
|
|
47
|
-
|
|
48
|
-
this.monitoringVersion = res?.currentVersion;
|
|
49
|
-
} catch (err) {}
|
|
50
|
-
}
|
|
44
|
+
this.monitoringVersion = await fetchMonitoringVersion(this.$store, inStore);
|
|
51
45
|
},
|
|
52
46
|
data() {
|
|
53
47
|
return {
|
|
@@ -7,16 +7,15 @@ import Date from '@shell/components/formatter/Date.vue';
|
|
|
7
7
|
import RadioGroup from '@components/Form/Radio/RadioGroup.vue';
|
|
8
8
|
import LabeledSelect from '@shell/components/form/LabeledSelect.vue';
|
|
9
9
|
import { exceptionToErrorsArray } from '@shell/utils/error';
|
|
10
|
-
import { CAPI, SNAPSHOT } from '@shell/config/types';
|
|
10
|
+
import { CAPI, SNAPSHOT, OPERATION, MANAGEMENT } from '@shell/config/types';
|
|
11
11
|
import { set } from '@shell/utils/object';
|
|
12
12
|
import ChildHook, { BEFORE_SAVE_HOOKS } from '@shell/mixins/child-hook';
|
|
13
13
|
import { DATE_FORMAT, TIME_FORMAT } from '@shell/store/prefs';
|
|
14
14
|
import { escapeHtml } from '@shell/utils/string';
|
|
15
15
|
import day from 'dayjs';
|
|
16
16
|
import { sortBy } from '@shell/utils/sort';
|
|
17
|
-
import { STATES_ENUM } from '@shell/plugins/dashboard-store/resource-class';
|
|
18
17
|
import AppModal from '@shell/components/AppModal.vue';
|
|
19
|
-
|
|
18
|
+
import { createOperationCR } from '@shell/utils/operation-cr';
|
|
20
19
|
export default {
|
|
21
20
|
components: {
|
|
22
21
|
Card,
|
|
@@ -66,8 +65,41 @@ export default {
|
|
|
66
65
|
return !!this.snapshot;
|
|
67
66
|
},
|
|
68
67
|
|
|
69
|
-
|
|
70
|
-
|
|
68
|
+
targetCluster() {
|
|
69
|
+
if (this.isCluster) {
|
|
70
|
+
return this.toRestore?.[0] || null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (this.snapshot?.cluster) {
|
|
74
|
+
return this.snapshot.cluster;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const snapshotClusterName = this.snapshot?.spec?.clusterName || this.snapshot?.clusterName;
|
|
78
|
+
const snapshotClusterId = this.snapshot?.clusterId || (snapshotClusterName ? `${ this.snapshot?.metadata?.namespace }/${ snapshotClusterName }` : null);
|
|
79
|
+
|
|
80
|
+
if (!snapshotClusterId) {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return this.$store.getters['management/byId'](CAPI.RANCHER_CLUSTER, snapshotClusterId);
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
targetMgmtCluster() {
|
|
88
|
+
if (this.targetCluster?.mgmt) {
|
|
89
|
+
return this.targetCluster.mgmt;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const mgmtClusterId = this.snapshot?.spec?.clusterName;
|
|
93
|
+
|
|
94
|
+
if (!mgmtClusterId) {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return this.$store.getters['management/byId'](MANAGEMENT.CLUSTER, mgmtClusterId);
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
isImported() {
|
|
102
|
+
return !!(this.targetCluster?.isImported || this.targetMgmtCluster?.isImported);
|
|
71
103
|
},
|
|
72
104
|
|
|
73
105
|
clusterSnapshots() {
|
|
@@ -77,6 +109,13 @@ export default {
|
|
|
77
109
|
return [];
|
|
78
110
|
}
|
|
79
111
|
},
|
|
112
|
+
restoreModeLabels() {
|
|
113
|
+
return [
|
|
114
|
+
this.t('promptRestore.restoreMode.onlyEtcd'),
|
|
115
|
+
this.t('promptRestore.restoreMode.kubernetesVersionAndEtcd'),
|
|
116
|
+
this.t('promptRestore.restoreMode.clusterConfigKubernetesVersionAndEtcd')
|
|
117
|
+
];
|
|
118
|
+
},
|
|
80
119
|
restoreModeOptions() {
|
|
81
120
|
return ['none', 'kubernetesVersion', 'all'];
|
|
82
121
|
}
|
|
@@ -112,7 +151,7 @@ export default {
|
|
|
112
151
|
const promise = this.$store.dispatch('management/findAll', { type: SNAPSHOT }).then((snapshots) => {
|
|
113
152
|
const toRestoreClusterName = cluster?.clusterName || cluster?.metadata?.name;
|
|
114
153
|
|
|
115
|
-
return snapshots.filter((s) => s?.
|
|
154
|
+
return snapshots.filter((s) => s?.restoreEnabled && s.clusterName === toRestoreClusterName
|
|
116
155
|
);
|
|
117
156
|
});
|
|
118
157
|
|
|
@@ -143,19 +182,41 @@ export default {
|
|
|
143
182
|
|
|
144
183
|
async apply(buttonDone) {
|
|
145
184
|
try {
|
|
146
|
-
const cluster = this
|
|
185
|
+
const cluster = this.targetCluster;
|
|
186
|
+
const mgmtCluster = cluster?.mgmt || this.targetMgmtCluster;
|
|
147
187
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
const now = cluster.spec?.rkeConfig?.etcdSnapshotRestore?.generation || 0;
|
|
188
|
+
const isImportedWithDayTwoOps = cluster?.isImportedWithDayTwoOps || mgmtCluster?.isDayTwoOpsEnabled;
|
|
151
189
|
|
|
152
|
-
|
|
153
|
-
generation: now + 1,
|
|
154
|
-
name: this.snapshot.name,
|
|
155
|
-
restoreRKEConfig: this.restoreMode,
|
|
156
|
-
});
|
|
190
|
+
await this.applyHooks(BEFORE_SAVE_HOOKS);
|
|
157
191
|
|
|
158
|
-
|
|
192
|
+
// For imported clusters with day 2 ops enabled, create an operation CR
|
|
193
|
+
if (isImportedWithDayTwoOps) {
|
|
194
|
+
if (!mgmtCluster) {
|
|
195
|
+
throw new Error(this.t('promptRestore.error.unableToResolveTargetCluster'));
|
|
196
|
+
}
|
|
197
|
+
const namespace = mgmtCluster?.id;
|
|
198
|
+
const safePrefix = mgmtCluster?.id;
|
|
199
|
+
const spec = {
|
|
200
|
+
clusterRef: {
|
|
201
|
+
apiVersion: 'management.cattle.io/v3',
|
|
202
|
+
kind: 'Cluster',
|
|
203
|
+
name: mgmtCluster?.id,
|
|
204
|
+
},
|
|
205
|
+
args: { name: this.snapshot?.snapshotFile?.name },
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
createOperationCR(this.$store.dispatch, OPERATION.ETCD_SNAPSHOT_RESTORE, spec, namespace, safePrefix);
|
|
209
|
+
} else {
|
|
210
|
+
const now = cluster.spec?.rkeConfig?.etcdSnapshotRestore?.generation || 0;
|
|
211
|
+
|
|
212
|
+
set(cluster, 'spec.rkeConfig.etcdSnapshotRestore', {
|
|
213
|
+
generation: now + 1,
|
|
214
|
+
name: this.snapshot.name,
|
|
215
|
+
restoreRKEConfig: this.restoreMode,
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
await cluster.save();
|
|
219
|
+
}
|
|
159
220
|
|
|
160
221
|
this.$store.dispatch('growl/success', {
|
|
161
222
|
title: this.t('promptRestore.notification.title'),
|
|
@@ -233,18 +294,25 @@ export default {
|
|
|
233
294
|
/>
|
|
234
295
|
</p>
|
|
235
296
|
</div>
|
|
236
|
-
<div
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
297
|
+
<div v-if="!isImported">
|
|
298
|
+
<div class="spacer" />
|
|
299
|
+
<RadioGroup
|
|
300
|
+
v-model:value="restoreMode"
|
|
301
|
+
name="restoreMode"
|
|
302
|
+
:label="t('promptRestore.restoreMode.label')"
|
|
303
|
+
:labels="restoreModeLabels"
|
|
304
|
+
:options="restoreModeOptions"
|
|
305
|
+
/>
|
|
306
|
+
</div>
|
|
244
307
|
</form>
|
|
245
308
|
</div>
|
|
246
309
|
</template>
|
|
247
|
-
|
|
310
|
+
<Banner
|
|
311
|
+
v-for="(err, i) in errors"
|
|
312
|
+
:key="i"
|
|
313
|
+
color="error"
|
|
314
|
+
:label="err"
|
|
315
|
+
/>
|
|
248
316
|
<template #actions>
|
|
249
317
|
<div class="dialog-actions">
|
|
250
318
|
<button
|
|
@@ -259,13 +327,6 @@ export default {
|
|
|
259
327
|
:disabled="!hasSnapshot"
|
|
260
328
|
@click="apply"
|
|
261
329
|
/>
|
|
262
|
-
|
|
263
|
-
<Banner
|
|
264
|
-
v-for="(err, i) in errors"
|
|
265
|
-
:key="i"
|
|
266
|
-
color="error"
|
|
267
|
-
:label="err"
|
|
268
|
-
/>
|
|
269
330
|
</div>
|
|
270
331
|
</template>
|
|
271
332
|
</Card>
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { mount } from '@vue/test-utils';
|
|
2
2
|
import Masthead from '@shell/components/Resource/Detail/Masthead/index.vue';
|
|
3
|
+
import TitleBar from '@shell/components/Resource/Detail/TitleBar/index.vue';
|
|
4
|
+
import Metadata from '@shell/components/Resource/Detail/Metadata/index.vue';
|
|
3
5
|
import Cards from '@shell/components/Resource/Detail/Cards.vue';
|
|
4
6
|
|
|
5
7
|
jest.mock('@shell/utils/clipboard', () => ({ copyTextToClipboard: jest.fn() }));
|
|
@@ -19,16 +21,18 @@ describe('component: Masthead/index', () => {
|
|
|
19
21
|
metadataProps: { items: [] }
|
|
20
22
|
};
|
|
21
23
|
|
|
24
|
+
const globalStubs = {
|
|
25
|
+
stubs: {
|
|
26
|
+
TitleBar: true,
|
|
27
|
+
Metadata: true,
|
|
28
|
+
Cards: true
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
22
32
|
it('should render the Cards component', () => {
|
|
23
33
|
const wrapper = mount(Masthead, {
|
|
24
34
|
props: defaultProps,
|
|
25
|
-
global:
|
|
26
|
-
stubs: {
|
|
27
|
-
TitleBar: true,
|
|
28
|
-
Metadata: true,
|
|
29
|
-
Cards: true
|
|
30
|
-
}
|
|
31
|
-
}
|
|
35
|
+
global: globalStubs
|
|
32
36
|
});
|
|
33
37
|
|
|
34
38
|
expect(wrapper.findComponent(Cards).exists()).toBe(true);
|
|
@@ -37,17 +41,67 @@ describe('component: Masthead/index', () => {
|
|
|
37
41
|
it('should pass the resource from titleBarProps to Cards', () => {
|
|
38
42
|
const wrapper = mount(Masthead, {
|
|
39
43
|
props: defaultProps,
|
|
40
|
-
global:
|
|
41
|
-
stubs: {
|
|
42
|
-
TitleBar: true,
|
|
43
|
-
Metadata: true,
|
|
44
|
-
Cards: true
|
|
45
|
-
}
|
|
46
|
-
}
|
|
44
|
+
global: globalStubs
|
|
47
45
|
});
|
|
48
46
|
|
|
49
47
|
const cardsComponent = wrapper.findComponent(Cards);
|
|
50
48
|
|
|
51
49
|
expect(cardsComponent.props('resource')).toStrictEqual(mockResource);
|
|
52
50
|
});
|
|
51
|
+
|
|
52
|
+
it('should render TitleBar when titleBarProps is provided', () => {
|
|
53
|
+
const wrapper = mount(Masthead, {
|
|
54
|
+
props: defaultProps,
|
|
55
|
+
global: globalStubs
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
expect(wrapper.findComponent(TitleBar).exists()).toBe(true);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('should render Metadata when metadataProps is provided', () => {
|
|
62
|
+
const wrapper = mount(Masthead, {
|
|
63
|
+
props: defaultProps,
|
|
64
|
+
global: globalStubs
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
expect(wrapper.findComponent(Metadata).exists()).toBe(true);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('should not render TitleBar when titleBarProps is undefined', () => {
|
|
71
|
+
const wrapper = mount(Masthead, {
|
|
72
|
+
props: { titleBarProps: undefined, metadataProps: { items: [] } },
|
|
73
|
+
global: globalStubs
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
expect(wrapper.findComponent(TitleBar).exists()).toBe(false);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('should not render Metadata when metadataProps is undefined', () => {
|
|
80
|
+
const wrapper = mount(Masthead, {
|
|
81
|
+
props: { titleBarProps: defaultProps.titleBarProps, metadataProps: undefined },
|
|
82
|
+
global: globalStubs
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
expect(wrapper.findComponent(Metadata).exists()).toBe(false);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('should not render Cards when titleBarProps is undefined', () => {
|
|
89
|
+
const wrapper = mount(Masthead, {
|
|
90
|
+
props: { titleBarProps: undefined, metadataProps: undefined },
|
|
91
|
+
global: globalStubs
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
expect(wrapper.findComponent(Cards).exists()).toBe(false);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('should not render any children when both props are undefined', () => {
|
|
98
|
+
const wrapper = mount(Masthead, {
|
|
99
|
+
props: { titleBarProps: undefined, metadataProps: undefined },
|
|
100
|
+
global: globalStubs
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
expect(wrapper.findComponent(TitleBar).exists()).toBe(false);
|
|
104
|
+
expect(wrapper.findComponent(Metadata).exists()).toBe(false);
|
|
105
|
+
expect(wrapper.findComponent(Cards).exists()).toBe(false);
|
|
106
|
+
});
|
|
53
107
|
});
|
|
@@ -4,8 +4,8 @@ import Metadata, { MetadataProps } from '@shell/components/Resource/Detail/Metad
|
|
|
4
4
|
import Cards from '@shell/components/Resource/Detail/Cards.vue';
|
|
5
5
|
|
|
6
6
|
export interface MastheadProps {
|
|
7
|
-
titleBarProps
|
|
8
|
-
metadataProps
|
|
7
|
+
titleBarProps?: TitleBarProps;
|
|
8
|
+
metadataProps?: MetadataProps;
|
|
9
9
|
}
|
|
10
10
|
</script>
|
|
11
11
|
|
|
@@ -15,7 +15,10 @@ const props = defineProps<MastheadProps>();
|
|
|
15
15
|
</script>
|
|
16
16
|
<template>
|
|
17
17
|
<div class="masthead">
|
|
18
|
-
<TitleBar
|
|
18
|
+
<TitleBar
|
|
19
|
+
v-if="props.titleBarProps"
|
|
20
|
+
v-bind="props.titleBarProps"
|
|
21
|
+
>
|
|
19
22
|
<template
|
|
20
23
|
v-if="$slots['additional-actions']"
|
|
21
24
|
#additional-actions
|
|
@@ -24,10 +27,12 @@ const props = defineProps<MastheadProps>();
|
|
|
24
27
|
</template>
|
|
25
28
|
</TitleBar>
|
|
26
29
|
<Metadata
|
|
30
|
+
v-if="props.metadataProps"
|
|
27
31
|
class="metadata-section"
|
|
28
32
|
v-bind="props.metadataProps"
|
|
29
33
|
/>
|
|
30
34
|
<Cards
|
|
35
|
+
v-if="props.titleBarProps"
|
|
31
36
|
class="cards-section"
|
|
32
37
|
:resource="props.titleBarProps.resource"
|
|
33
38
|
/>
|
|
@@ -1125,7 +1125,6 @@ export default {
|
|
|
1125
1125
|
v-clean-tooltip="actionTooltip"
|
|
1126
1126
|
type="button"
|
|
1127
1127
|
variant="primary"
|
|
1128
|
-
size="large"
|
|
1129
1128
|
:class="{[bulkActionClass]:true}"
|
|
1130
1129
|
:disabled="!act.enabled"
|
|
1131
1130
|
:data-testid="componentTestid + '-' + act.action"
|
|
@@ -1146,6 +1145,7 @@ export default {
|
|
|
1146
1145
|
:disabled="!selectedRows.length"
|
|
1147
1146
|
:hidden-actions="hiddenActions"
|
|
1148
1147
|
:action-tooltip="actionTooltip"
|
|
1148
|
+
size="medium"
|
|
1149
1149
|
@click="applyTableAction"
|
|
1150
1150
|
@mouseover="setBulkActionOfInterest"
|
|
1151
1151
|
@mouseleave="setBulkActionOfInterest"
|
|
@@ -1158,10 +1158,11 @@ export default {
|
|
|
1158
1158
|
:disable-button="!selectedRows.length"
|
|
1159
1159
|
size="sm"
|
|
1160
1160
|
>
|
|
1161
|
-
<template #button-content>
|
|
1161
|
+
<template #button-content="{ buttonSize }">
|
|
1162
1162
|
<button
|
|
1163
1163
|
ref="actionDropDown"
|
|
1164
1164
|
class="btn bg-primary mr-0"
|
|
1165
|
+
:class="buttonSize"
|
|
1165
1166
|
:disabled="!selectedRows.length"
|
|
1166
1167
|
>
|
|
1167
1168
|
<i class="icon icon-gear" />
|
|
@@ -1874,7 +1875,7 @@ export default {
|
|
|
1874
1875
|
}
|
|
1875
1876
|
|
|
1876
1877
|
.search-box {
|
|
1877
|
-
height:
|
|
1878
|
+
height: 32px;
|
|
1878
1879
|
margin-left: 10px;
|
|
1879
1880
|
min-width: 180px;
|
|
1880
1881
|
}
|
package/components/Wizard.vue
CHANGED
|
@@ -124,6 +124,11 @@ export default {
|
|
|
124
124
|
errors: {
|
|
125
125
|
type: Array,
|
|
126
126
|
default: null,
|
|
127
|
+
},
|
|
128
|
+
|
|
129
|
+
beforeGoToStep: {
|
|
130
|
+
type: Function,
|
|
131
|
+
default: null
|
|
127
132
|
}
|
|
128
133
|
},
|
|
129
134
|
|
|
@@ -217,7 +222,7 @@ export default {
|
|
|
217
222
|
},
|
|
218
223
|
|
|
219
224
|
methods: {
|
|
220
|
-
goToStep(number, fromNav) {
|
|
225
|
+
async goToStep(number, fromNav) {
|
|
221
226
|
if (number < 1) {
|
|
222
227
|
return;
|
|
223
228
|
}
|
|
@@ -233,6 +238,14 @@ export default {
|
|
|
233
238
|
return;
|
|
234
239
|
}
|
|
235
240
|
|
|
241
|
+
if (this.beforeGoToStep && fromNav) {
|
|
242
|
+
try {
|
|
243
|
+
await this.beforeGoToStep(this.activeStep, selected);
|
|
244
|
+
} catch {
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
236
249
|
this.activeStep = selected;
|
|
237
250
|
|
|
238
251
|
this.$emit('next', { step: selected });
|
|
@@ -118,4 +118,60 @@ describe('component: ButtonGroup', () => {
|
|
|
118
118
|
expect(wrapper.emitted('update:value')).toHaveLength(1);
|
|
119
119
|
expect(wrapper.emitted('update:value')![0][0]).toBe('val1');
|
|
120
120
|
});
|
|
121
|
+
|
|
122
|
+
it.each([
|
|
123
|
+
['small', 'btn-sm'],
|
|
124
|
+
['medium', 'btn-md'],
|
|
125
|
+
])('should apply the size class to each button when size is %s', (size, sizeClass) => {
|
|
126
|
+
const options = [
|
|
127
|
+
{
|
|
128
|
+
label: 'label1',
|
|
129
|
+
value: 'val1'
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
label: 'label2',
|
|
133
|
+
value: 'val2'
|
|
134
|
+
},
|
|
135
|
+
];
|
|
136
|
+
|
|
137
|
+
const wrapper = shallowMount(ButtonGroup, {
|
|
138
|
+
props: {
|
|
139
|
+
options,
|
|
140
|
+
value: 'val1',
|
|
141
|
+
size
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
const buttons = wrapper.findAll('button');
|
|
146
|
+
|
|
147
|
+
expect(buttons).toHaveLength(2);
|
|
148
|
+
buttons.forEach((button) => {
|
|
149
|
+
expect(button.classes()).toContain(sizeClass);
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it.each([
|
|
154
|
+
[undefined],
|
|
155
|
+
['large'],
|
|
156
|
+
])('should not apply a size class when size is %s', (size) => {
|
|
157
|
+
const options = [
|
|
158
|
+
{
|
|
159
|
+
label: 'label1',
|
|
160
|
+
value: 'val1'
|
|
161
|
+
},
|
|
162
|
+
];
|
|
163
|
+
|
|
164
|
+
const wrapper = shallowMount(ButtonGroup, {
|
|
165
|
+
props: {
|
|
166
|
+
options,
|
|
167
|
+
value: 'val1',
|
|
168
|
+
...(size ? { size } : {})
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
const button = wrapper.find('button');
|
|
173
|
+
|
|
174
|
+
expect(button.classes()).not.toContain('btn-sm');
|
|
175
|
+
expect(button.classes()).not.toContain('btn-md');
|
|
176
|
+
});
|
|
121
177
|
});
|