diff --git a/packages/forklift-console-plugin/src/components/page/withSelection.tsx b/packages/forklift-console-plugin/src/components/page/StandardPageWithSelection.tsx similarity index 75% rename from packages/forklift-console-plugin/src/components/page/withSelection.tsx rename to packages/forklift-console-plugin/src/components/page/StandardPageWithSelection.tsx index 9f8e90d32..4716e4768 100644 --- a/packages/forklift-console-plugin/src/components/page/withSelection.tsx +++ b/packages/forklift-console-plugin/src/components/page/StandardPageWithSelection.tsx @@ -1,4 +1,4 @@ -import React, { FC, useState } from 'react'; +import React, { FC } from 'react'; import { DefaultHeader, @@ -59,6 +59,16 @@ export interface IdBasedSelectionProps { * @returns true if items can be selected, false otherwise */ canSelect: (item: T) => boolean; + + /** + * onSelect is called when selection changes + */ + onSelect: (selectedIds: string[]) => void; + + /** + * Selected ids + */ + selectedIds: string[]; } export type GlobalActionWithSelection = GlobalActionToolbarProps & { @@ -71,18 +81,28 @@ export type GlobalActionWithSelection = GlobalActionToolbarProps & { * 1. IDs provided with toId() function are unique and constant in time * 2. check box status at row level does not depend from other rows and can be calculated from the item via canSelect() function */ -export function withIdBasedSelection({ toId, canSelect }: IdBasedSelectionProps) { +export function withIdBasedSelection({ + toId, + canSelect, + onSelect, + selectedIds, +}: IdBasedSelectionProps) { const Enhanced = (props: StandardPageProps) => { - const [selectedIds, setSelectedIds]: [string[], (selected: string[]) => void] = useState([]); const isSelected = (item: T) => selectedIds.includes(toId(item)); + const toggleSelectFor = (items: T[]) => { const ids = items.map(toId); const allSelected = ids.every((id) => selectedIds.includes(id)); - setSelectedIds([ + const newSelectedIds = [ ...selectedIds.filter((it) => !ids.includes(it)), ...(allSelected ? [] : ids), - ]); + ]; + + if (onSelect) { + onSelect(newSelectedIds); + } }; + return ( ({ toId, canSelect }: IdBasedSelectionPro Enhanced.displayName = 'StandardPageWithSelection'; return Enhanced; } + +export interface StandardPageWithSelectionProps extends StandardPageProps { + toId?: (item: T) => string; + canSelect?: (item: T) => boolean; + onSelect?: (selectedIds: string[]) => void; + selectedIds?: string[]; +} + +export function StandardPageWithSelection(props: StandardPageWithSelectionProps) { + const { toId, canSelect = () => true, onSelect, selectedIds, ...rest } = props; + + if (onSelect && (!toId || !selectedIds)) { + throw new Error('Missing required properties: toId, selectedIds'); + } + + const EnhancedStandardPage = withIdBasedSelection({ + toId, + canSelect, + onSelect, + selectedIds, + }); + + return onSelect ? : ; +} diff --git a/packages/forklift-console-plugin/src/modules/Providers/views/details/tabs/Hosts/VSphereHostsList.tsx b/packages/forklift-console-plugin/src/modules/Providers/views/details/tabs/Hosts/VSphereHostsList.tsx index c1fb8d91f..ba414a7a0 100644 --- a/packages/forklift-console-plugin/src/modules/Providers/views/details/tabs/Hosts/VSphereHostsList.tsx +++ b/packages/forklift-console-plugin/src/modules/Providers/views/details/tabs/Hosts/VSphereHostsList.tsx @@ -1,22 +1,18 @@ import React, { FC, useState } from 'react'; -import StandardPage from 'src/components/page/StandardPage'; -import { GlobalActionWithSelection, withIdBasedSelection } from 'src/components/page/withSelection'; +import { + GlobalActionWithSelection, + StandardPageWithSelection, + StandardPageWithSelectionProps, +} from 'src/components/page/StandardPageWithSelection'; import { useProviderInventory } from 'src/modules/Providers/hooks'; -import { useModal } from 'src/modules/Providers/modals'; import { useForkliftTranslation } from 'src/utils/i18n'; import { loadUserSettings, ResourceFieldFactory } from '@kubev2v/common'; -import { - HostModelGroupVersionKind, - V1beta1Host, - V1beta1Provider, - VSphereHost, -} from '@kubev2v/types'; +import { HostModelGroupVersionKind, V1beta1Host, VSphereHost } from '@kubev2v/types'; import { useK8sWatchResource } from '@openshift-console/dynamic-plugin-sdk'; -import { Button, ToolbarItem } from '@patternfly/react-core'; -import { VSphereNetworkModal } from './modals/VSphereNetworkModal'; import { InventoryHostPair, matchHostsToInventory } from './utils/helpers'; +import { SelectNetworkButton } from './components'; import { ProviderHostsProps } from './ProviderHosts'; import { VSphereHostsCells } from './VSphereHostsRow'; @@ -60,19 +56,18 @@ export const hostsFieldsMetadataFactory: ResourceFieldFactory = (t) => [ }, ]; -const PageWithSelection = withIdBasedSelection({ - toId: (item: InventoryHostPair) => item.inventory.id, - canSelect: (item: InventoryHostPair) => item?.inventory?.networkAdapters?.length > 0, -}); +const PageWithSelection = StandardPageWithSelection; +type PageWithSelectionProps = StandardPageWithSelectionProps; export const VSphereHostsList: FC = ({ obj }) => { const { t } = useForkliftTranslation(); - const [userSettings] = useState(() => loadUserSettings({ pageId: 'ProviderHosts' })); - const { provider, permissions } = obj; const { namespace } = provider?.metadata || {}; + const [selectedIds, setSelectedIds] = useState([]); + const [userSettings] = useState(() => loadUserSettings({ pageId: 'ProviderHosts' })); + const { inventory: hostsInventory, loading, @@ -91,45 +86,29 @@ export const VSphereHostsList: FC = ({ obj }) => { const hostsData = matchHostsToInventory(hostsInventory, hosts, provider); - const Page = permissions?.canPatch ? PageWithSelection : StandardPage; const actions: FC>[] = permissions?.canPatch - ? [({ selectedIds }) => ] + ? [({ selectedIds }) => ] : []; - return ( - - ); -}; + const props: PageWithSelectionProps = { + dataSource: [hostsData || [], !loading, error], + CellMapper: VSphereHostsCells, + fieldsMetadata: hostsFieldsMetadataFactory(t), + namespace: namespace, + title: t('Hosts'), + userSettings: userSettings, + GlobalActionToolbarItems: actions, + }; -const SelectNetworkBtn: FC<{ - selectedIds: string[]; - provider: V1beta1Provider; - hostsData: InventoryHostPair[]; -}> = ({ selectedIds, provider, hostsData }) => { - const { t } = useForkliftTranslation(); - const { showModal } = useModal(); - return ( - - - - ); + const extendedProps: PageWithSelectionProps = permissions?.canPatch + ? { + ...props, + toId: (item: InventoryHostPair) => item.inventory.id, + canSelect: (item: InventoryHostPair) => item?.inventory?.networkAdapters?.length > 0, + onSelect: setSelectedIds, + selectedIds: selectedIds, + } + : props; + + return ; }; diff --git a/packages/forklift-console-plugin/src/modules/Providers/views/details/tabs/Hosts/components/SelectNetworkButton.tsx b/packages/forklift-console-plugin/src/modules/Providers/views/details/tabs/Hosts/components/SelectNetworkButton.tsx new file mode 100644 index 000000000..faefe810d --- /dev/null +++ b/packages/forklift-console-plugin/src/modules/Providers/views/details/tabs/Hosts/components/SelectNetworkButton.tsx @@ -0,0 +1,52 @@ +import React, { FC } from 'react'; +import { useModal } from 'src/modules/Providers/modals'; +import { useForkliftTranslation } from 'src/utils/i18n'; + +import { V1beta1Provider } from '@kubev2v/types'; +import { Button, ToolbarItem } from '@patternfly/react-core'; + +import { VSphereNetworkModal } from '../modals'; +import { InventoryHostPair } from '../utils'; + +/** + * `SelectNetworkButton` is a functional component that renders a button for selecting a migration network. + * When clicked, it opens a modal dialog for choosing the network. + * + * @component + * @example + * // Usage within a parent component + * + * + * @param {Object} props - The properties passed to the component. + * @param {string[]} props.selectedIds - An array of host IDs that are selected for migration. + * @param {V1beta1Provider} props.provider - The provider object containing details about the provider. + * @param {InventoryHostPair[]} props.hostsData - An array of inventory host pairs for displaying in the modal. + */ +export const SelectNetworkButton: FC<{ + selectedIds: string[]; + provider: V1beta1Provider; + hostsData: InventoryHostPair[]; +}> = ({ selectedIds, provider, hostsData }) => { + const { t } = useForkliftTranslation(); + const { showModal } = useModal(); + + return ( + + + + ); +}; diff --git a/packages/forklift-console-plugin/src/modules/Providers/views/details/tabs/Hosts/components/index.ts b/packages/forklift-console-plugin/src/modules/Providers/views/details/tabs/Hosts/components/index.ts index 0fce64098..bd9835409 100644 --- a/packages/forklift-console-plugin/src/modules/Providers/views/details/tabs/Hosts/components/index.ts +++ b/packages/forklift-console-plugin/src/modules/Providers/views/details/tabs/Hosts/components/index.ts @@ -4,4 +4,5 @@ export * from './LinkSpeedCellRenderer'; export * from './MTUCellRenderer'; export * from './NameCellRenderer'; export * from './NetworkCellRenderer'; +export * from './SelectNetworkButton'; // @endindex diff --git a/packages/forklift-console-plugin/src/modules/Providers/views/details/tabs/VirtualMachines/OVirtVirtualMachinesList.tsx b/packages/forklift-console-plugin/src/modules/Providers/views/details/tabs/VirtualMachines/OVirtVirtualMachinesList.tsx index 439779959..b9692b2d7 100644 --- a/packages/forklift-console-plugin/src/modules/Providers/views/details/tabs/VirtualMachines/OVirtVirtualMachinesList.tsx +++ b/packages/forklift-console-plugin/src/modules/Providers/views/details/tabs/VirtualMachines/OVirtVirtualMachinesList.tsx @@ -93,6 +93,8 @@ export const OVirtVirtualMachinesList: React.FC = obj, loaded, loadError, + onSelect, + initialSelectedIds, }) => ( = cellMapper={OVirtVirtualMachinesCells} fieldsMetadataFactory={oVirtVmFieldsMetadataFactory} pageId="OVirtVirtualMachinesList" + onSelect={onSelect} + initialSelectedIds={initialSelectedIds} /> ); diff --git a/packages/forklift-console-plugin/src/modules/Providers/views/details/tabs/VirtualMachines/OpenShiftVirtualMachinesList.tsx b/packages/forklift-console-plugin/src/modules/Providers/views/details/tabs/VirtualMachines/OpenShiftVirtualMachinesList.tsx index 39d686bac..00bfa8a0b 100644 --- a/packages/forklift-console-plugin/src/modules/Providers/views/details/tabs/VirtualMachines/OpenShiftVirtualMachinesList.tsx +++ b/packages/forklift-console-plugin/src/modules/Providers/views/details/tabs/VirtualMachines/OpenShiftVirtualMachinesList.tsx @@ -78,6 +78,8 @@ export const OpenShiftVirtualMachinesList: React.FC ( ); diff --git a/packages/forklift-console-plugin/src/modules/Providers/views/details/tabs/VirtualMachines/OpenStackVirtualMachinesList.tsx b/packages/forklift-console-plugin/src/modules/Providers/views/details/tabs/VirtualMachines/OpenStackVirtualMachinesList.tsx index c39de1722..c0d8b6cbc 100644 --- a/packages/forklift-console-plugin/src/modules/Providers/views/details/tabs/VirtualMachines/OpenStackVirtualMachinesList.tsx +++ b/packages/forklift-console-plugin/src/modules/Providers/views/details/tabs/VirtualMachines/OpenStackVirtualMachinesList.tsx @@ -109,6 +109,8 @@ export const OpenStackVirtualMachinesList: React.FC ( ); diff --git a/packages/forklift-console-plugin/src/modules/Providers/views/details/tabs/VirtualMachines/OvaVirtualMachinesList.tsx b/packages/forklift-console-plugin/src/modules/Providers/views/details/tabs/VirtualMachines/OvaVirtualMachinesList.tsx index 16bf4e5ce..8b598ec65 100644 --- a/packages/forklift-console-plugin/src/modules/Providers/views/details/tabs/VirtualMachines/OvaVirtualMachinesList.tsx +++ b/packages/forklift-console-plugin/src/modules/Providers/views/details/tabs/VirtualMachines/OvaVirtualMachinesList.tsx @@ -47,6 +47,8 @@ export const OvaVirtualMachinesList: React.FC = ({ obj, loaded, loadError, + onSelect, + initialSelectedIds, }) => ( = ({ cellMapper={OvaVirtualMachinesCells} fieldsMetadataFactory={ovaVmFieldsMetadataFactory} pageId="OvaVirtualMachinesList" + onSelect={onSelect} + initialSelectedIds={initialSelectedIds} /> ); diff --git a/packages/forklift-console-plugin/src/modules/Providers/views/details/tabs/VirtualMachines/ProviderVirtualMachines.tsx b/packages/forklift-console-plugin/src/modules/Providers/views/details/tabs/VirtualMachines/ProviderVirtualMachines.tsx index 7c303e477..cb312258e 100644 --- a/packages/forklift-console-plugin/src/modules/Providers/views/details/tabs/VirtualMachines/ProviderVirtualMachines.tsx +++ b/packages/forklift-console-plugin/src/modules/Providers/views/details/tabs/VirtualMachines/ProviderVirtualMachines.tsx @@ -10,6 +10,7 @@ import { useK8sWatchResource } from '@openshift-console/dynamic-plugin-sdk'; import { Alert, PageSection } from '@patternfly/react-core'; import BellIcon from '@patternfly/react-icons/dist/esm/icons/bell-icon'; +import { VmData } from './components'; import { OpenShiftVirtualMachinesList } from './OpenShiftVirtualMachinesList'; import { OpenStackVirtualMachinesList } from './OpenStackVirtualMachinesList'; import { OvaVirtualMachinesList } from './OvaVirtualMachinesList'; @@ -21,6 +22,8 @@ export interface ProviderVirtualMachinesProps extends RouteComponentProps { obj: ProviderData; loaded?: boolean; loadError?: unknown; + onSelect?: (selectedVMs: VmData[]) => void; + initialSelectedIds?: string[]; } export const ProviderVirtualMachines: React.FC<{ name: string; namespace: string }> = ({ @@ -44,9 +47,9 @@ export const ProviderVirtualMachines: React.FC<{ name: string; namespace: string } variant="info" title={t('How to create a migration plan')}> - To migrate virtual machines from {obj?.provider.metadata.name}{' '} - provider, select the virtual machines to migrate from the list of available virtual - machines and click the Migrate button. + To migrate virtual machines from {name} provider, select the virtual + machines to migrate from the list of available virtual machines and click the{' '} + Migrate button. @@ -65,6 +68,8 @@ export const ProviderVirtualMachinesListWrapper: React.FC { switch (obj?.provider?.spec?.type) { case 'openshift': @@ -74,6 +79,8 @@ export const ProviderVirtualMachinesListWrapper: React.FC ); case 'openstack': @@ -83,19 +90,42 @@ export const ProviderVirtualMachinesListWrapper: React.FC ); case 'ovirt': return ( - + ); case 'vsphere': return ( - + ); case 'ova': return ( - + ); default: // unsupported provider or loading errors will be handled by parent page diff --git a/packages/forklift-console-plugin/src/modules/Providers/views/details/tabs/VirtualMachines/VSphereVirtualMachinesList.tsx b/packages/forklift-console-plugin/src/modules/Providers/views/details/tabs/VirtualMachines/VSphereVirtualMachinesList.tsx index 893a4f905..a65d41931 100644 --- a/packages/forklift-console-plugin/src/modules/Providers/views/details/tabs/VirtualMachines/VSphereVirtualMachinesList.tsx +++ b/packages/forklift-console-plugin/src/modules/Providers/views/details/tabs/VirtualMachines/VSphereVirtualMachinesList.tsx @@ -81,6 +81,8 @@ export const VSphereVirtualMachinesList: React.FC obj, loaded, loadError, + onSelect, + initialSelectedIds, }) => ( cellMapper={VSphereVirtualMachinesCells} fieldsMetadataFactory={vSphereVmFieldsMetadataFactory} pageId="VSphereVirtualMachinesList" + onSelect={onSelect} + initialSelectedIds={initialSelectedIds} /> ); diff --git a/packages/forklift-console-plugin/src/modules/Providers/views/details/tabs/VirtualMachines/components/ProviderVirtualMachinesList.tsx b/packages/forklift-console-plugin/src/modules/Providers/views/details/tabs/VirtualMachines/components/ProviderVirtualMachinesList.tsx index 16a14fc2c..c940adf30 100644 --- a/packages/forklift-console-plugin/src/modules/Providers/views/details/tabs/VirtualMachines/components/ProviderVirtualMachinesList.tsx +++ b/packages/forklift-console-plugin/src/modules/Providers/views/details/tabs/VirtualMachines/components/ProviderVirtualMachinesList.tsx @@ -1,6 +1,9 @@ import React, { FC, useState } from 'react'; import { RouteComponentProps } from 'react-router-dom'; -import { GlobalActionWithSelection, withIdBasedSelection } from 'src/components/page/withSelection'; +import { + GlobalActionWithSelection, + StandardPageWithSelection, +} from 'src/components/page/StandardPageWithSelection'; import { ProviderData } from 'src/modules/Providers/utils'; import { useForkliftTranslation } from 'src/utils/i18n'; @@ -28,15 +31,12 @@ export interface ProviderVirtualMachinesListProps extends RouteComponentProps { cellMapper: FC>; fieldsMetadataFactory: ResourceFieldFactory; pageId: string; + onSelect?: (selectedVMs: VmData[]) => void; + initialSelectedIds?: string[]; } export const toId = (item: VmData) => item.vm.id; -const PageWithSelection = withIdBasedSelection({ - toId, - canSelect: (item: VmData) => !!item, -}); - export const ProviderVirtualMachinesList: FC = ({ title, obj, @@ -45,9 +45,14 @@ export const ProviderVirtualMachinesList: FC = cellMapper, fieldsMetadataFactory, pageId, + onSelect, + initialSelectedIds, }) => { const { t } = useForkliftTranslation(); + const initialSelectedIds_ = initialSelectedIds || []; + + const [selectedIds, setSelectedIds] = useState(initialSelectedIds_); const [userSettings] = useState(() => loadUserSettings({ pageId })); const [vmData, loading] = useInventoryVms(obj, loaded, loadError); @@ -62,8 +67,17 @@ export const ProviderVirtualMachinesList: FC = ), ]; + const onSelectedIds = (selectedIds: string[]) => { + setSelectedIds(selectedIds); + + if (onSelect) { + const selectedVms = vmData.filter((data) => selectedIds.includes(toId(data))); + onSelect(selectedVms); + } + }; + return ( - = }} extraSupportedMatchers={[concernsMatcher, featuresMatcher]} GlobalActionToolbarItems={actions} + toId={toId} + onSelect={onSelectedIds} + selectedIds={selectedIds} /> ); };