Skip to content

Commit

Permalink
Merge pull request #912 from yaacov/plan-details
Browse files Browse the repository at this point in the history
🐾 Plan details page scaffolding
  • Loading branch information
yaacov authored Feb 13, 2024
2 parents e4568b8 + ee6acd6 commit fede8b6
Show file tree
Hide file tree
Showing 19 changed files with 388 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@
"GPUs/Host Devices": "GPUs/Host Devices",
"Hide from view": "Hide from view",
"Hide values": "Hide values",
"Hooks": "Hooks",
"Hooks for virtualization": "Hooks for virtualization",
"Host": "Host",
"Host cluster": "Host cluster",
Expand All @@ -177,6 +178,7 @@
"Managed resource": "Managed resource",
"Map source datastores, storage domains, volume types, storage classes and networks to their respective target storage classes and networks.": "Map source datastores, storage domains, volume types, storage classes and networks to their respective target storage classes and networks.",
"Mapping graph": "Mapping graph",
"Mappings": "Mappings",
"Maximum concurrent VM migrations": "Maximum concurrent VM migrations",
"Maximum number of concurrent VM migrations. Default value is 20.": "Maximum number of concurrent VM migrations. Default value is 20.",
"Message": "Message",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
ActionProvider,
CreateResource,
ModelMetadata,
ResourceDetailsPage,
ResourceListPage,
ResourceNSNavItem,
RoutePage,
Expand All @@ -13,6 +14,7 @@ import type { ConsolePluginMetadata } from '@openshift-console/dynamic-plugin-sd
export const exposedModules: ConsolePluginMetadata['exposedModules'] = {
PlansPage: './modules/Plans/PlansWrapper',
PlanCreatePage: './modules/Plans/views/create/PlanCreatePage',
PlanDetailsPage: './modules/Plans/views/details/PlanDetailsPage',
PlanWizard: './modules/Plans/PlanWizardWrapper',
VMMigrationDetails: './modules/Plans/VMMigrationDetailsWrapper',
usePlanActions: './modules/Plans/UsePlanActions',
Expand All @@ -36,6 +38,16 @@ export const extensions: EncodedExtension[] = [
},
} as EncodedExtension<ResourceNSNavItem>,

{
type: 'console.page/resource/details',
properties: {
component: {
$codeRef: 'PlanDetailsPage',
},
model: PlanModelGroupVersionKind,
},
} as EncodedExtension<ResourceDetailsPage>,

{
type: 'console.page/resource/list',
properties: {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
.forklift-page-headings-alerts {
padding-left: 0;
padding-top: 0;
}

.forklift-page-section {
border-top: 1px solid var(--pf-global--BorderColor--100);
}

.forklift-page-section--info {
border-bottom: 0;
padding-bottom: 0;
}

.forklift-page-section--details {
border-top: 0;
}

.forklift-page-plan-networks-button {
padding-bottom: var(--pf-global--spacer--lg);
}

.forklift-page-plan-vm_concern-button {
padding: var(--pf-global--spacer--xs);
}

.forklift-page-plan-field-error-validation {
color: var(--pf-c-form__helper-text--m-error--Color);
font-size: small;
padding-top: var(--pf-c-form__helper-text--MarginTop);
}

.forklift-page-plan-field-warning-validation {
color: var(--pf-c-form__helper-text--m-warning--Color);
font-size: small;
padding-top: var(--pf-c-form__helper-text--MarginTop);
}

.forklift-page-plan-field-success-validation {
color: var(--pf-c-form__helper-text--m-success--Color);
font-size: small;
padding-top: var(--pf-c-form__helper-text--MarginTop);
}

.forklift-page-plan-field-default-validation {
font-size: small;
padding-top: var(--pf-c-form__helper-text--MarginTop);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import React, { memo } from 'react';
import { PageHeadings } from 'src/modules/Providers/utils';
import { useForkliftTranslation } from 'src/utils/i18n';

import { PlanModel, PlanModelGroupVersionKind, V1beta1Plan } from '@kubev2v/types';
import {
HorizontalNav,
K8sModel,
useK8sWatchResource,
} from '@openshift-console/dynamic-plugin-sdk';

import { PlanDetails, PlanHooks, PlanMappings, PlanVirtualMachines, PlanYAML } from './tabs';

import './PlanDetailsPage.style.css';

export type PlanDetailsPageProps = {
kind: string;
kindObj: K8sModel;
match: { path: string; url: string; isExact: boolean; params: unknown };
name: string;
namespace?: string;
};

export type PlanDetailsTabProps = { plan: V1beta1Plan; loaded?: boolean; loadError?: unknown };

const PlanDetailsPage_: React.FC<{
name: string;
namespace: string;
obj: V1beta1Plan;
loaded: boolean;
loadError: unknown;
}> = ({ namespace, obj, loaded, loadError }) => {
const { t } = useForkliftTranslation();

const pages = [
{
href: '',
name: t('Details'),
component: () => <PlanDetails plan={obj} loaded={loaded} loadError={loadError} />,
},
{
href: 'yaml',
name: t('YAML'),
component: () => <PlanYAML plan={obj} loaded={loaded} loadError={loadError} />,
},
{
href: 'hooks',
name: t('Hooks'),
component: () => <PlanHooks plan={obj} loaded={loaded} loadError={loadError} />,
},
{
href: 'vms',
name: t('Virtual Machines'),
component: () => <PlanVirtualMachines plan={obj} loaded={loaded} loadError={loadError} />,
},
{
href: 'mappings',
name: t('Mappings'),
component: () => <PlanMappings plan={obj} loaded={loaded} loadError={loadError} />,
},
];

return (
<>
<PageHeadings model={PlanModel} obj={obj} namespace={namespace}></PageHeadings>
<HorizontalNav pages={pages} />
</>
);
};
PlanDetailsPage_.displayName = 'PlanDetailsPage_';

const MemoPlanDetailsPage = memo(PlanDetailsPage_);

/**
* A page component that displays detailed information about a migration plan.
* It uses the Suspend component to handle loading states and displays different tabs
* for viewing plan details, YAML, hooks, virtual machines, mappings, etc.
*
* @param {PlanDetailsPageProps} props - The properties passed to the component.
* @param {string} props.kind - The kind of the resource.
* @param {K8sModel} props.kindObj - The Kubernetes model object for the resource.
* @param {Object} props.match - The match object from react-router.
* @param {string} props.name - The name of the migration plan.
* @param {string} [props.namespace] - The namespace of the migration plan.
*
* @returns {JSX.Element} The JSX element representing the plan details page.
*/
export const PlanDetailsPage: React.FC<PlanDetailsPageProps> = ({ name, namespace }) => {
const [plan, loaded, error] = useK8sWatchResource<V1beta1Plan>({
groupVersionKind: PlanModelGroupVersionKind,
namespaced: true,
name,
namespace,
});

return (
<MemoPlanDetailsPage
name={name}
namespace={namespace}
obj={plan}
loaded={loaded}
loadError={error}
/>
);
};
PlanDetailsPage.displayName = 'PlanDetailsPage';

export default PlanDetailsPage;
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import React from 'react';

/**
* A functional component that renders a loading indicator.
*
* @returns {JSX.Element} The JSX element representing the loading indicator.
*/
export const Loading: React.FC = () => (
<div className="co-m-loader co-an-fade-in-out" data-testid="loading-indicator-plan-yaml">
<div className="co-m-loader-dot__one" />
<div className="co-m-loader-dot__two" />
<div className="co-m-loader-dot__three" />
</div>
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import React from 'react';

import { Bullseye } from '@patternfly/react-core';

import { Loading } from './Loading';

export type SuspendProps = {
obj: object;
loaded: boolean;
loadError: unknown;
children?: React.ReactNode;
};

/**
* A wrapper component that uses React Suspense to handle loading states.
* Renders the children only when the data has been loaded successfully and there are no errors.
*
* @param {object} props - The properties passed to the component.
* @param {object} props.obj - The object representing the data to be displayed.
* @param {boolean} props.loaded - Indicates whether the data has finished loading.
* @param {unknown} props.loadError - Any error that occurred during the loading process.
* @param {React.ReactNode} [props.children] - The content to be rendered once the data is loaded.
*
* @returns {JSX.Element} The JSX element containing the children or a loading indicator.
*/
export const Suspend: React.FC<SuspendProps> = ({ obj, loaded, loadError, children }) => {
return (
<React.Suspense
fallback={
<Bullseye>
<Loading />
</Bullseye>
}
>
{obj && loaded && !loadError && children}
</React.Suspense>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// @index(['./*', /style/g], f => `export * from '${f.path}';`)
export * from './Loading';
export * from './Suspend';
// @endindex
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import React from 'react';
import { useForkliftTranslation } from 'src/utils/i18n';

import { PageSection, Title } from '@patternfly/react-core';

import { Suspend } from '../../components';
import { PlanDetailsTabProps } from '../../PlanDetailsPage';

export const PlanDetails: React.FC<PlanDetailsTabProps> = ({ plan, loaded, loadError }) => {
const { t } = useForkliftTranslation();

return (
<Suspend obj={plan} loaded={loaded} loadError={loadError}>
<PageSection variant="light" className="forklift-page-section--info">
<Title headingLevel={'h1'}>{t('Details')}</Title>
</PageSection>
</Suspend>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// @index(['./*', /style/g], f => `export * from '${f.path}';`)
export * from './PlanDetails';
// @endindex
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import React from 'react';
import { useForkliftTranslation } from 'src/utils/i18n';

import { HookModelGroupVersionKind } from '@kubev2v/types';
import { ResourceLink } from '@openshift-console/dynamic-plugin-sdk';
import { PageSection, Title } from '@patternfly/react-core';

import { Suspend } from '../../components';
import { PlanDetailsTabProps } from '../../PlanDetailsPage';

export const PlanHooks: React.FC<PlanDetailsTabProps> = ({ plan, loaded, loadError }) => {
const { t } = useForkliftTranslation();

return (
<Suspend obj={plan} loaded={loaded} loadError={loadError}>
<PageSection variant="light" className="forklift-page-section--info">
<Title headingLevel={'h1'}>{t('Hooks')}</Title>
</PageSection>

<PageSection variant="light" className="forklift-page-section--info">
{plan?.spec?.vms?.[0]?.hooks?.[0]?.hook && (
<ResourceLink
groupVersionKind={HookModelGroupVersionKind}
name={plan.spec.vms[0].hooks[0].hook.name}
namespace={plan.spec.vms[0].hooks[0].hook.name}
/>
)}
</PageSection>
</Suspend>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// @index(['./*', /style/g], f => `export * from '${f.path}';`)
export * from './PlanHooks';
// @endindex
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import React from 'react';
import { useForkliftTranslation } from 'src/utils/i18n';

import { NetworkMapModelGroupVersionKind, StorageMapModelGroupVersionKind } from '@kubev2v/types';
import { ResourceLink } from '@openshift-console/dynamic-plugin-sdk';
import { PageSection, Title } from '@patternfly/react-core';

import { Suspend } from '../../components';
import { PlanDetailsTabProps } from '../../PlanDetailsPage';

export const PlanMappings: React.FC<PlanDetailsTabProps> = ({ plan, loaded, loadError }) => {
const { t } = useForkliftTranslation();

return (
<Suspend obj={plan} loaded={loaded} loadError={loadError}>
<PageSection variant="light" className="forklift-page-section--info">
<Title headingLevel={'h1'}>{t('Mappings')}</Title>
</PageSection>

<PageSection variant="light" className="forklift-page-section--info">
<ResourceLink
groupVersionKind={NetworkMapModelGroupVersionKind}
name={plan.spec.map.network.name}
namespace={plan.spec.map.network.namespace}
/>
<ResourceLink
groupVersionKind={StorageMapModelGroupVersionKind}
name={plan.spec.map.storage.name}
namespace={plan.spec.map.storage.namespace}
/>
</PageSection>
</Suspend>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// @index(['./*', /style/g], f => `export * from '${f.path}';`)
export * from './PlanMappings';
// @endindex
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import React from 'react';
import { useForkliftTranslation } from 'src/utils/i18n';

import { PageSection, Title } from '@patternfly/react-core';

import { Suspend } from '../../components';
import { PlanDetailsTabProps } from '../../PlanDetailsPage';

export const PlanVirtualMachines: React.FC<PlanDetailsTabProps> = ({ plan, loaded, loadError }) => {
const { t } = useForkliftTranslation();

return (
<Suspend obj={plan} loaded={loaded} loadError={loadError}>
<PageSection variant="light" className="forklift-page-section--info">
<Title headingLevel={'h1'}>{t('Virtual machines')}</Title>
</PageSection>

<PageSection variant="light" className="forklift-page-section--info">
<ol>
{plan.spec.vms.map((vm) => (
<li key={vm.id}>{vm.name}</li>
))}
</ol>
</PageSection>
</Suspend>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// @index(['./*', /style/g], f => `export * from '${f.path}';`)
export * from './PlanVirtualMachines';
// @endindex
Loading

0 comments on commit fede8b6

Please sign in to comment.