Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 7c15c85

Browse files
committedMar 19, 2025·
[MTV-2247] Initial new create plan wizard setup
Signed-off-by: Jeff Puzzo <[email protected]>
1 parent e93a32e commit 7c15c85

File tree

19 files changed

+425
-8
lines changed

19 files changed

+425
-8
lines changed
 

‎packages/forklift-console-plugin/locales/en/plugin__forklift-console-plugin.json

+16
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@
7171
"Add mapping": "Add mapping",
7272
"Add passphrase": "Add passphrase",
7373
"Add source and target providers for the migration.": "Add source and target providers for the migration.",
74+
"Additional set up": "Additional set up",
7475
"All discovered networks have been mapped to the default network.": "All discovered networks have been mapped to the default network.",
7576
"All discovered storages have been mapped to the default storage.": "All discovered storages have been mapped to the default storage.",
7677
"All networks detected on the selected VMs require a mapping.": "All networks detected on the selected VMs require a mapping.",
@@ -91,7 +92,9 @@
9192
"At least one source and one target provider in the {{name}} project must be available.": "At least one source and one target provider in the {{name}} project must be available.",
9293
"At least one source and one target provider must be available.": "At least one source and one target provider must be available.",
9394
"Authentication type": "Authentication type",
95+
"Back": "Back",
9496
"Bandwidth": "Bandwidth",
97+
"Basic set up": "Basic set up",
9598
"Boot from first root device": "Boot from first root device",
9699
"Boot from the first hard drive": "Boot from the first hard drive",
97100
"Boot from the first partition on the first hard drive": "Boot from the first partition on the first hard drive",
@@ -143,6 +146,7 @@
143146
"Create NetworkMap": "Create NetworkMap",
144147
"Create new namespace:": "Create new namespace:",
145148
"Create new provider": "Create new provider",
149+
"Create plan": "Create plan",
146150
"Create Plan": "Create Plan",
147151
"Create provider": "Create provider",
148152
"Create Provider": "Create Provider",
@@ -255,12 +259,14 @@
255259
"First root device": "First root device",
256260
"Flavor": "Flavor",
257261
"Folder": "Folder",
262+
"General": "General",
258263
"Go to providers list": "Go to providers list",
259264
"GPUs/Host Devices": "GPUs/Host Devices",
260265
"Hide from view": "Hide from view",
261266
"Hide values": "Hide values",
262267
"Hide variables": "Hide variables",
263268
"Hooks": "Hooks",
269+
"Hooks (optional)": "Hooks (optional)",
264270
"Hooks for virtualization": "Hooks for virtualization",
265271
"Host": "Host",
266272
"Host cluster": "Host cluster",
@@ -334,6 +340,7 @@
334340
"Multiple NICs on the same network": "Multiple NICs on the same network",
335341
"Name": "Name",
336342
"Name is primarily intended for creation idempotence and configuration definition. Cannot be updated.": "Name is primarily intended for creation idempotence and configuration definition. Cannot be updated.",
343+
"Name your plan and choose the project you would like it to be created in.": "Name your plan and choose the project you would like it to be created in.",
337344
"Namespace": "Namespace",
338345
"Namespace defines the space within which each name must be unique.\n An empty namespace is equivalent to the \"default\" namespace, but \"default\" is the canonical representation.\n Not all objects are required to be scoped to a namespace - the value of this field for those objects will be empty.": "Namespace defines the space within which each name must be unique.\n An empty namespace is equivalent to the \"default\" namespace, but \"default\" is the canonical representation.\n Not all objects are required to be scoped to a namespace - the value of this field for those objects will be empty.",
339346
"Namespace defines the space within which each name must be unique.\n An empty namespace is equivalent to the \"default\" namespace, but \"default\" is the canonical representation.\n Not all objects are required to be scoped to a namespace -\n the value of this field for those objects will be empty.": "Namespace defines the space within which each name must be unique.\n An empty namespace is equivalent to the \"default\" namespace, but \"default\" is the canonical representation.\n Not all objects are required to be scoped to a namespace -\n the value of this field for those objects will be empty.",
@@ -343,6 +350,7 @@
343350
"Network interfaces": "Network interfaces",
344351
"Network Map name re-generated": "Network Map name re-generated",
345352
"Network map:": "Network map:",
353+
"Network mapping": "Network mapping",
346354
"Network mapping is empty, make sure no mappings are required for this migration plan.": "Network mapping is empty, make sure no mappings are required for this migration plan.",
347355
"Network mappings": "Network mappings",
348356
"Network mappings have been re-generated": "Network mappings have been re-generated",
@@ -358,6 +366,7 @@
358366
"Networks used by the selected VMs": "Networks used by the selected VMs",
359367
"New name was generated for the Network Map due to naming conflict.": "New name was generated for the Network Map due to naming conflict.",
360368
"New name was generated for the Storage Map due to naming conflict.": "New name was generated for the Storage Map due to naming conflict.",
369+
"Next": "Next",
361370
"NICs with empty NIC profile": "NICs with empty NIC profile",
362371
"No concerns found for this virtual machine.": "No concerns found for this virtual machine.",
363372
"No credentials found.": "No credentials found.",
@@ -416,6 +425,8 @@
416425
"Operator": "Operator",
417426
"Operator conditions define the current state of the controller": "Operator conditions define the current state of the controller",
418427
"Other networks present on the source provider ": "Other networks present on the source provider ",
428+
"Other settings": "Other settings",
429+
"Other settings (optional)": "Other settings (optional)",
419430
"Other storages present on the source provider ": "Other storages present on the source provider ",
420431
"OvaPath": "OvaPath",
421432
"Overview": "Overview",
@@ -427,7 +438,9 @@
427438
"Phase": "Phase",
428439
"Pipeline status": "Pipeline status",
429440
"Plan details": "Plan details",
441+
"Plan information": "Plan information",
430442
"Plan name": "Plan name",
443+
"Plan name is required.": "Plan name is required.",
431444
"Plan name must be a unique within a namespace.": "Plan name must be a unique within a namespace.",
432445
"Plan name must contain only lowercase alphanumeric characters or '-'": "Plan name must contain only lowercase alphanumeric characters or '-'",
433446
"Plan name must not be empty": "Plan name must not be empty",
@@ -489,6 +502,7 @@
489502
"Restore default columns": "Restore default columns",
490503
"Return to the providers list page": "Return to the providers list page",
491504
"Reveal values": "Reveal values",
505+
"Review and create": "Review and create",
492506
"Root device": "Root device",
493507
"Run the migration plan.": "Run the migration plan.",
494508
"Running": "Running",
@@ -522,6 +536,7 @@
522536
"Show the welcome card": "Show the welcome card",
523537
"Show variables": "Show variables",
524538
"Skip certificate validation": "Skip certificate validation",
539+
"Skip to review": "Skip to review",
525540
"Skip VMware Virtual Disk Development Kit (VDDK) SDK acceleration (not recommended).": "Skip VMware Virtual Disk Development Kit (VDDK) SDK acceleration (not recommended).",
526541
"Snapshot polling interval (seconds)": "Snapshot polling interval (seconds)",
527542
"Some VMs Failed": "Some VMs Failed",
@@ -545,6 +560,7 @@
545560
"Storage domains": "Storage domains",
546561
"Storage Map name re-generated": "Storage Map name re-generated",
547562
"Storage map:": "Storage map:",
563+
"Storage mapping": "Storage mapping",
548564
"Storage mapping is empty, make sure no mappings are required for this migration plan.": "Storage mapping is empty, make sure no mappings are required for this migration plan.",
549565
"Storage mappings": "Storage mappings",
550566
"Storage mappings have been re-generated": "Storage mappings have been re-generated",

‎packages/forklift-console-plugin/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
"node-forge": "^1",
4545
"react": "17.0.2",
4646
"react-dom": "17.0.2",
47+
"react-hook-form": "^7.54.2",
4748
"react-i18next": "^11.14.3",
4849
"react-linkify": "^1.0.0-alpha",
4950
"react-router": "5.3.x",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import React, { FC } from 'react';
2+
import { FieldError } from 'react-hook-form';
3+
4+
import { FormHelperText, HelperText, HelperTextItem } from '@patternfly/react-core';
5+
import { ExclamationCircleIcon } from '@patternfly/react-icons';
6+
import { isEmpty } from '@utils/helpers';
7+
8+
type FormErrorHelperTextProps = {
9+
error: Partial<FieldError>;
10+
};
11+
12+
export const FormErrorHelperText: FC<FormErrorHelperTextProps> = ({ error }) => {
13+
if (isEmpty(error)) {
14+
return null;
15+
}
16+
17+
return (
18+
<FormHelperText>
19+
<HelperText>
20+
<HelperTextItem icon={<ExclamationCircleIcon />} variant="error">
21+
{error?.message?.toString()}
22+
</HelperTextItem>
23+
</HelperText>
24+
</FormHelperText>
25+
);
26+
};

‎packages/forklift-console-plugin/src/modules/Plans/dynamic-plugin.ts

+12
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import type { ConsolePluginBuildMetadata } from '@openshift-console/dynamic-plug
1212
export const exposedModules: ConsolePluginBuildMetadata['exposedModules'] = {
1313
PlansListPage: './modules/Plans/views/list/PlansListPage',
1414
PlanCreatePage: './modules/Plans/views/create/PlanCreatePage',
15+
PlanCreatePageV2: './plans/create/PlanCreatePage',
1516
PlanDetailsPage: './modules/Plans/views/details/PlanDetailsPage',
1617
};
1718

@@ -64,6 +65,17 @@ export const extensions: EncodedExtension[] = [
6465
},
6566
} as EncodedExtension<CreateResource>,
6667

68+
{
69+
type: 'console.page/route',
70+
properties: {
71+
exact: false,
72+
path: ['/mtv/create/plan'],
73+
component: {
74+
$codeRef: 'PlanCreatePageV2',
75+
},
76+
},
77+
},
78+
6779
{
6880
type: 'console.model-metadata',
6981
properties: {

‎packages/forklift-console-plugin/src/modules/Plans/views/create/steps/CreateMigrationPlan/index.ts

-3
This file was deleted.

‎packages/forklift-console-plugin/src/modules/Plans/views/details/components/SettingsSection/components/NameTemplate/utils/utils.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { produce } from 'immer';
22
import { OnConfirmHookType } from 'src/modules/Providers';
3-
import { setObjectValueByPath, unsetObjectValueByPath } from 'src/utils/utils';
43

54
import { k8sUpdate } from '@openshift-console/dynamic-plugin-sdk';
5+
import { setObjectValueByPath, unsetObjectValueByPath } from '@utils/helpers';
66

77
import { EnhancedPlan } from '../../../utils/types';
88

‎packages/forklift-console-plugin/src/modules/Plans/views/details/components/SettingsSection/components/SharedDisksDetailsItem/components/MigrateSharedDisksSwitch/MigrateSharedDisksSwitch.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import React, { FC } from 'react';
22
import { ModalInputComponentType } from 'src/modules/Providers/modals';
3-
import { useForkliftTranslation } from 'src/utils/i18n';
4-
import { safeBoolean } from 'src/utils/utils';
53

64
import { Switch } from '@patternfly/react-core';
5+
import { safeBoolean } from '@utils/helpers';
6+
import { useForkliftTranslation } from '@utils/i18n';
77

88
type SwitchRendererProps = {
99
value: string;

‎packages/forklift-console-plugin/src/modules/Plans/views/details/components/SettingsSection/components/SharedDisksDetailsItem/utils/helpers.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { produce } from 'immer';
22
import { OnConfirmHookType } from 'src/modules/Providers/modals';
3-
import { safeBoolean } from 'src/utils/utils';
43

54
import { V1beta1Plan } from '@kubev2v/types';
65
import { k8sUpdate } from '@openshift-console/dynamic-plugin-sdk';
6+
import { safeBoolean } from '@utils/helpers';
77

88
import { EnhancedPlan } from '../../../utils/types';
99

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
.create-plan-wizard__form {
2+
width: 66%;
3+
display: flex;
4+
flex-direction: column;
5+
gap: var(--pf-v5-global--spacer--lg);
6+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import React, { FC, useState } from 'react';
2+
import { FormProvider, useForm } from 'react-hook-form';
3+
4+
import { Form, Title, Wizard, WizardStep, WizardStepType } from '@patternfly/react-core';
5+
import { isEmpty } from '@utils/helpers';
6+
import { useForkliftTranslation } from '@utils/i18n';
7+
8+
import { GeneralInformationForm } from './steps/GeneralInformationForm';
9+
import { firstStep, planStepNames, planStepOrder, PlanWizardStepId } from './constants';
10+
import { CreatePlanWizardFooter } from './CreatePlanWizardFooter';
11+
12+
import './CreatePlanWizard.style.scss';
13+
14+
export const CreatePlanWizard: FC = () => {
15+
const { t } = useForkliftTranslation();
16+
const form = useForm({ mode: 'onChange' });
17+
const [currentStep, setCurrentStep] = useState<WizardStepType>(firstStep);
18+
const { formState, watch } = form;
19+
const formValues = watch();
20+
21+
const onSubmit = () => console.log('SUBMITTED: ', formValues);
22+
23+
const getStepProps = (id: PlanWizardStepId) => ({
24+
id,
25+
name: planStepNames[id],
26+
isDisabled: currentStep?.index < planStepOrder[id] && !isEmpty(formState?.errors),
27+
});
28+
29+
return (
30+
<FormProvider {...form}>
31+
<Wizard
32+
isVisitRequired
33+
title={t('Create migration plan')}
34+
footer={<CreatePlanWizardFooter />}
35+
onStepChange={(_event, currentStep) => setCurrentStep(currentStep)}
36+
>
37+
<WizardStep
38+
{...getStepProps(PlanWizardStepId.BasicSetUp)}
39+
steps={[
40+
<WizardStep key={PlanWizardStepId.General} {...getStepProps(PlanWizardStepId.General)}>
41+
<GeneralInformationForm />
42+
</WizardStep>,
43+
<WizardStep
44+
key={PlanWizardStepId.VirtualMachines}
45+
{...getStepProps(PlanWizardStepId.VirtualMachines)}
46+
>
47+
<Form>
48+
<Title headingLevel="h2">{t('Virtual machines')}</Title>
49+
</Form>
50+
</WizardStep>,
51+
<WizardStep
52+
key={PlanWizardStepId.NetworkMapping}
53+
{...getStepProps(PlanWizardStepId.NetworkMapping)}
54+
>
55+
<Form>
56+
<Title headingLevel="h2">{t('Network mappings')}</Title>
57+
</Form>
58+
</WizardStep>,
59+
<WizardStep
60+
key={PlanWizardStepId.StorageMapping}
61+
{...getStepProps(PlanWizardStepId.StorageMapping)}
62+
>
63+
<Form>
64+
<Title headingLevel="h2">{t('Storage mappings')}</Title>
65+
</Form>
66+
</WizardStep>,
67+
<WizardStep
68+
key={PlanWizardStepId.MigrationType}
69+
{...getStepProps(PlanWizardStepId.MigrationType)}
70+
>
71+
<Form>
72+
<Title headingLevel="h2">{t('Migration type')}</Title>
73+
</Form>
74+
</WizardStep>,
75+
]}
76+
/>
77+
78+
<WizardStep
79+
{...getStepProps(PlanWizardStepId.AdditionalSetUp)}
80+
steps={[
81+
<WizardStep
82+
key={PlanWizardStepId.OtherSettings}
83+
{...getStepProps(PlanWizardStepId.OtherSettings)}
84+
>
85+
<Form>
86+
<Title headingLevel="h2">{t('Other settings')}</Title>
87+
</Form>
88+
</WizardStep>,
89+
<WizardStep key={PlanWizardStepId.Hooks} {...getStepProps(PlanWizardStepId.Hooks)}>
90+
<Form>
91+
<Title headingLevel="h2">{t('Hooks')}</Title>
92+
</Form>
93+
</WizardStep>,
94+
]}
95+
/>
96+
97+
<WizardStep
98+
footer={<CreatePlanWizardFooter nextButtonText={t('Create plan')} onNext={onSubmit} />}
99+
{...getStepProps(PlanWizardStepId.ReviewAndCreate)}
100+
>
101+
<pre>{JSON.stringify(formValues, null, 2)}</pre>
102+
</WizardStep>
103+
</Wizard>
104+
</FormProvider>
105+
);
106+
};
107+
108+
export default CreatePlanWizard;

0 commit comments

Comments
 (0)
Please sign in to comment.