diff --git a/packages/forklift-console-plugin/locales/en/plugin__forklift-console-plugin.json b/packages/forklift-console-plugin/locales/en/plugin__forklift-console-plugin.json index 829d6a850..7a0d91d11 100644 --- a/packages/forklift-console-plugin/locales/en/plugin__forklift-console-plugin.json +++ b/packages/forklift-console-plugin/locales/en/plugin__forklift-console-plugin.json @@ -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", @@ -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", diff --git a/packages/forklift-console-plugin/src/modules/Plans/dynamic-plugin.ts b/packages/forklift-console-plugin/src/modules/Plans/dynamic-plugin.ts index 4bfcba042..0c8a9ee25 100644 --- a/packages/forklift-console-plugin/src/modules/Plans/dynamic-plugin.ts +++ b/packages/forklift-console-plugin/src/modules/Plans/dynamic-plugin.ts @@ -4,6 +4,7 @@ import { ActionProvider, CreateResource, ModelMetadata, + ResourceDetailsPage, ResourceListPage, ResourceNSNavItem, RoutePage, @@ -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', @@ -36,6 +38,16 @@ export const extensions: EncodedExtension[] = [ }, } as EncodedExtension, + { + type: 'console.page/resource/details', + properties: { + component: { + $codeRef: 'PlanDetailsPage', + }, + model: PlanModelGroupVersionKind, + }, + } as EncodedExtension, + { type: 'console.page/resource/list', properties: { diff --git a/packages/forklift-console-plugin/src/modules/Plans/views/details/PlanDetailsPage.style.css b/packages/forklift-console-plugin/src/modules/Plans/views/details/PlanDetailsPage.style.css new file mode 100644 index 000000000..e5e744c88 --- /dev/null +++ b/packages/forklift-console-plugin/src/modules/Plans/views/details/PlanDetailsPage.style.css @@ -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); + } \ No newline at end of file diff --git a/packages/forklift-console-plugin/src/modules/Plans/views/details/PlanDetailsPage.tsx b/packages/forklift-console-plugin/src/modules/Plans/views/details/PlanDetailsPage.tsx new file mode 100644 index 000000000..259e76fe3 --- /dev/null +++ b/packages/forklift-console-plugin/src/modules/Plans/views/details/PlanDetailsPage.tsx @@ -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: () => , + }, + { + href: 'yaml', + name: t('YAML'), + component: () => , + }, + { + href: 'hooks', + name: t('Hooks'), + component: () => , + }, + { + href: 'vms', + name: t('Virtual Machines'), + component: () => , + }, + { + href: 'mappings', + name: t('Mappings'), + component: () => , + }, + ]; + + return ( + <> + + + + ); +}; +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 = ({ name, namespace }) => { + const [plan, loaded, error] = useK8sWatchResource({ + groupVersionKind: PlanModelGroupVersionKind, + namespaced: true, + name, + namespace, + }); + + return ( + + ); +}; +PlanDetailsPage.displayName = 'PlanDetailsPage'; + +export default PlanDetailsPage; diff --git a/packages/forklift-console-plugin/src/modules/Plans/views/details/components/Loading.tsx b/packages/forklift-console-plugin/src/modules/Plans/views/details/components/Loading.tsx new file mode 100644 index 000000000..9385919be --- /dev/null +++ b/packages/forklift-console-plugin/src/modules/Plans/views/details/components/Loading.tsx @@ -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 = () => ( +
+
+
+
+
+); diff --git a/packages/forklift-console-plugin/src/modules/Plans/views/details/components/Suspend.tsx b/packages/forklift-console-plugin/src/modules/Plans/views/details/components/Suspend.tsx new file mode 100644 index 000000000..78909deb7 --- /dev/null +++ b/packages/forklift-console-plugin/src/modules/Plans/views/details/components/Suspend.tsx @@ -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 = ({ obj, loaded, loadError, children }) => { + return ( + + + + } + > + {obj && loaded && !loadError && children} + + ); +}; diff --git a/packages/forklift-console-plugin/src/modules/Plans/views/details/components/index.ts b/packages/forklift-console-plugin/src/modules/Plans/views/details/components/index.ts new file mode 100644 index 000000000..da24be5e1 --- /dev/null +++ b/packages/forklift-console-plugin/src/modules/Plans/views/details/components/index.ts @@ -0,0 +1,4 @@ +// @index(['./*', /style/g], f => `export * from '${f.path}';`) +export * from './Loading'; +export * from './Suspend'; +// @endindex diff --git a/packages/forklift-console-plugin/src/modules/Plans/views/details/tabs/Details/PlanDetails.tsx b/packages/forklift-console-plugin/src/modules/Plans/views/details/tabs/Details/PlanDetails.tsx new file mode 100644 index 000000000..53bed3fba --- /dev/null +++ b/packages/forklift-console-plugin/src/modules/Plans/views/details/tabs/Details/PlanDetails.tsx @@ -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 = ({ plan, loaded, loadError }) => { + const { t } = useForkliftTranslation(); + + return ( + + + {t('Details')} + + + ); +}; diff --git a/packages/forklift-console-plugin/src/modules/Plans/views/details/tabs/Details/index.ts b/packages/forklift-console-plugin/src/modules/Plans/views/details/tabs/Details/index.ts new file mode 100644 index 000000000..64327cb63 --- /dev/null +++ b/packages/forklift-console-plugin/src/modules/Plans/views/details/tabs/Details/index.ts @@ -0,0 +1,3 @@ +// @index(['./*', /style/g], f => `export * from '${f.path}';`) +export * from './PlanDetails'; +// @endindex diff --git a/packages/forklift-console-plugin/src/modules/Plans/views/details/tabs/Hooks/PlanHooks.tsx b/packages/forklift-console-plugin/src/modules/Plans/views/details/tabs/Hooks/PlanHooks.tsx new file mode 100644 index 000000000..00ba1dc10 --- /dev/null +++ b/packages/forklift-console-plugin/src/modules/Plans/views/details/tabs/Hooks/PlanHooks.tsx @@ -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 = ({ plan, loaded, loadError }) => { + const { t } = useForkliftTranslation(); + + return ( + + + {t('Hooks')} + + + + {plan?.spec?.vms?.[0]?.hooks?.[0]?.hook && ( + + )} + + + ); +}; diff --git a/packages/forklift-console-plugin/src/modules/Plans/views/details/tabs/Hooks/index.ts b/packages/forklift-console-plugin/src/modules/Plans/views/details/tabs/Hooks/index.ts new file mode 100644 index 000000000..5fb2ed3c8 --- /dev/null +++ b/packages/forklift-console-plugin/src/modules/Plans/views/details/tabs/Hooks/index.ts @@ -0,0 +1,3 @@ +// @index(['./*', /style/g], f => `export * from '${f.path}';`) +export * from './PlanHooks'; +// @endindex diff --git a/packages/forklift-console-plugin/src/modules/Plans/views/details/tabs/Mappings/PlanMappings.tsx b/packages/forklift-console-plugin/src/modules/Plans/views/details/tabs/Mappings/PlanMappings.tsx new file mode 100644 index 000000000..c9f92806d --- /dev/null +++ b/packages/forklift-console-plugin/src/modules/Plans/views/details/tabs/Mappings/PlanMappings.tsx @@ -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 = ({ plan, loaded, loadError }) => { + const { t } = useForkliftTranslation(); + + return ( + + + {t('Mappings')} + + + + + + + + ); +}; diff --git a/packages/forklift-console-plugin/src/modules/Plans/views/details/tabs/Mappings/index.ts b/packages/forklift-console-plugin/src/modules/Plans/views/details/tabs/Mappings/index.ts new file mode 100644 index 000000000..2b98b8361 --- /dev/null +++ b/packages/forklift-console-plugin/src/modules/Plans/views/details/tabs/Mappings/index.ts @@ -0,0 +1,3 @@ +// @index(['./*', /style/g], f => `export * from '${f.path}';`) +export * from './PlanMappings'; +// @endindex diff --git a/packages/forklift-console-plugin/src/modules/Plans/views/details/tabs/VirtualMachines/PlanVirtualMachines.tsx b/packages/forklift-console-plugin/src/modules/Plans/views/details/tabs/VirtualMachines/PlanVirtualMachines.tsx new file mode 100644 index 000000000..8119e9b0a --- /dev/null +++ b/packages/forklift-console-plugin/src/modules/Plans/views/details/tabs/VirtualMachines/PlanVirtualMachines.tsx @@ -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 = ({ plan, loaded, loadError }) => { + const { t } = useForkliftTranslation(); + + return ( + + + {t('Virtual machines')} + + + +
    + {plan.spec.vms.map((vm) => ( +
  1. {vm.name}
  2. + ))} +
+
+
+ ); +}; diff --git a/packages/forklift-console-plugin/src/modules/Plans/views/details/tabs/VirtualMachines/index.ts b/packages/forklift-console-plugin/src/modules/Plans/views/details/tabs/VirtualMachines/index.ts new file mode 100644 index 000000000..265175eaa --- /dev/null +++ b/packages/forklift-console-plugin/src/modules/Plans/views/details/tabs/VirtualMachines/index.ts @@ -0,0 +1,3 @@ +// @index(['./*', /style/g], f => `export * from '${f.path}';`) +export * from './PlanVirtualMachines'; +// @endindex diff --git a/packages/forklift-console-plugin/src/modules/Plans/views/details/tabs/YAML/PlanYAML.tsx b/packages/forklift-console-plugin/src/modules/Plans/views/details/tabs/YAML/PlanYAML.tsx new file mode 100644 index 000000000..601f4704d --- /dev/null +++ b/packages/forklift-console-plugin/src/modules/Plans/views/details/tabs/YAML/PlanYAML.tsx @@ -0,0 +1,26 @@ +import React from 'react'; +import { useForkliftTranslation } from 'src/utils/i18n'; + +import { ResourceYAMLEditor } from '@openshift-console/dynamic-plugin-sdk'; +import { Bullseye } from '@patternfly/react-core'; + +import { Loading } from '../../components'; +import { PlanDetailsTabProps } from '../../PlanDetailsPage'; + +export const PlanYAML: React.FC = ({ plan, loaded, loadError }) => { + const { t } = useForkliftTranslation(); + + return ( + + + + } + > + {plan && loaded && !loadError && ( + + )} + + ); +}; diff --git a/packages/forklift-console-plugin/src/modules/Plans/views/details/tabs/YAML/index.ts b/packages/forklift-console-plugin/src/modules/Plans/views/details/tabs/YAML/index.ts new file mode 100644 index 000000000..8a87f4354 --- /dev/null +++ b/packages/forklift-console-plugin/src/modules/Plans/views/details/tabs/YAML/index.ts @@ -0,0 +1,3 @@ +// @index(['./*', /style/g], f => `export * from '${f.path}';`) +export * from './PlanYAML'; +// @endindex diff --git a/packages/forklift-console-plugin/src/modules/Plans/views/details/tabs/index.ts b/packages/forklift-console-plugin/src/modules/Plans/views/details/tabs/index.ts new file mode 100644 index 000000000..ae42b78a8 --- /dev/null +++ b/packages/forklift-console-plugin/src/modules/Plans/views/details/tabs/index.ts @@ -0,0 +1,7 @@ +// @index(['./*', /style/g], f => `export * from '${f.path}';`) +export * from './Details'; +export * from './Hooks'; +export * from './Mappings'; +export * from './VirtualMachines'; +export * from './YAML'; +// @endindex diff --git a/packages/forklift-console-plugin/src/modules/Providers/utils/components/DetailsPage/PageHeadings.tsx b/packages/forklift-console-plugin/src/modules/Providers/utils/components/DetailsPage/PageHeadings.tsx index 3cbe2cc11..659808f8a 100644 --- a/packages/forklift-console-plugin/src/modules/Providers/utils/components/DetailsPage/PageHeadings.tsx +++ b/packages/forklift-console-plugin/src/modules/Providers/utils/components/DetailsPage/PageHeadings.tsx @@ -4,8 +4,8 @@ import { Link } from 'react-router-dom'; import { getResourceUrl } from 'src/modules/Providers/utils'; import { useForkliftTranslation } from 'src/utils/i18n'; -import { ProviderModelGroupVersionKind } from '@kubev2v/types'; import { + getGroupVersionKindForResource, K8sGroupVersionKind, K8sModel, K8sResourceCommon, @@ -25,6 +25,7 @@ export const PageHeadings: React.FC = ({ actions, }) => { const status = data?.['status']?.phase; + const groupVersionKind = data && getGroupVersionKindForResource(data); return (
@@ -34,7 +35,7 @@ export const PageHeadings: React.FC = ({ {' '} {data?.metadata?.name}