diff --git a/frontend/packages/console-shared/src/components/status/statuses.tsx b/frontend/packages/console-shared/src/components/status/statuses.tsx index ee4950b7d68..858d61f6762 100644 --- a/frontend/packages/console-shared/src/components/status/statuses.tsx +++ b/frontend/packages/console-shared/src/components/status/statuses.tsx @@ -1,10 +1,11 @@ import * as React from 'react'; -import { InfoCircleIcon, HourglassHalfIcon, InProgressIcon } from '@patternfly/react-icons'; import { - RedExclamationCircleIcon, - GreenCheckCircleIcon, - YellowExclamationTriangleIcon, -} from './icons'; + InfoCircleIcon, + HourglassHalfIcon, + InProgressIcon, + SyncAltIcon, +} from '@patternfly/react-icons'; +import { RedExclamationCircleIcon, YellowExclamationTriangleIcon } from './icons'; import GenericStatus from './GenericStatus'; import { StatusComponentProps } from './types'; import StatusIconAndText from './StatusIconAndText'; @@ -30,7 +31,7 @@ export const ProgressStatus: React.FC = (props) => ( ProgressStatus.displayName = 'ProgressStatus'; export const SuccessStatus: React.FC = (props) => ( - } /> + } /> ); SuccessStatus.displayName = 'SuccessStatus'; diff --git a/frontend/packages/kubevirt-plugin/src/components/resource-link-popover.scss b/frontend/packages/kubevirt-plugin/src/components/resource-link-popover.scss new file mode 100644 index 00000000000..bf087552e0a --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/components/resource-link-popover.scss @@ -0,0 +1,10 @@ +.kubevirt-resource-link-popover { + cursor: pointer; + text-decoration-line: underline; + text-decoration-style: dotted; + width: max-content; +} + +.kubevirt-resource-link-popover__disabled { + color: var(--pf-global--disabled-color--100); +} diff --git a/frontend/packages/kubevirt-plugin/src/components/resource-link-popover.tsx b/frontend/packages/kubevirt-plugin/src/components/resource-link-popover.tsx new file mode 100644 index 00000000000..64038c61393 --- /dev/null +++ b/frontend/packages/kubevirt-plugin/src/components/resource-link-popover.tsx @@ -0,0 +1,51 @@ +import * as React from 'react'; +import { Popover, Button, Text } from '@patternfly/react-core'; +import { ArrowIcon } from '@patternfly/react-icons'; +import { resourcePath } from '@console/internal/components/utils'; + +import './resource-link-popover.scss'; + +export const ResourceLinkPopover: React.FC = ({ + kind, + name, + namespace, + className, + isDisabled, + message, + linkMessage, + children, +}) => ( + + {linkMessage} + + ) + } + > + + {message} + + +); + +export type ResourceLinkPopoverProps = { + kind?: string; + name: string; + namespace: string; + className?: string; + isDisabled?: boolean; + message: string; + linkMessage?: string; + children: React.ReactNode; +}; diff --git a/frontend/packages/kubevirt-plugin/src/components/vms/vm.tsx b/frontend/packages/kubevirt-plugin/src/components/vms/vm.tsx index 5c940c7aa75..6f60b5e044a 100644 --- a/frontend/packages/kubevirt-plugin/src/components/vms/vm.tsx +++ b/frontend/packages/kubevirt-plugin/src/components/vms/vm.tsx @@ -1,4 +1,5 @@ import * as React from 'react'; +import * as _ from 'lodash'; import * as classNames from 'classnames'; import { sortable } from '@patternfly/react-table'; import { @@ -9,33 +10,45 @@ import { K8sEntityMap, dimensifyHeader, dimensifyRow, + Status, } from '@console/shared'; -import { NamespaceModel, PodModel } from '@console/internal/models'; +import { NamespaceModel, PodModel, NodeModel } from '@console/internal/models'; import { Table, MultiListPage, TableRow, TableData } from '@console/internal/components/factory'; import { FirehoseResult, Kebab, ResourceLink } from '@console/internal/components/utils'; +import { fromNow } from '@console/internal/components/utils/datetime'; import { K8sResourceKind, PodKind } from '@console/internal/module/k8s'; +import { getPhase } from '@console/noobaa-storage-plugin/src/utils'; import { VMStatus } from '../vm-status/vm-status'; import { VirtualMachineInstanceMigrationModel, VirtualMachineInstanceModel, VirtualMachineModel, } from '../../models'; +import { ResourceLinkPopover } from '../resource-link-popover'; import { VMIKind, VMKind } from '../../types'; import { getMigrationVMIName, isMigrating } from '../../selectors/vmi-migration'; import { getBasicID, getLoadedData, getResource } from '../../utils'; import { getVMStatus } from '../../statuses/vm/vm'; +import { getVmiIpAddresses, getVMINodeName } from '../../selectors/vmi'; import { vmStatusFilter } from './table-filters'; import { menuActions } from './menu-actions'; import './vm.scss'; const tableColumnClasses = [ - classNames('col-lg-4', 'col-md-4', 'col-sm-6', 'col-xs-6'), - classNames('col-lg-4', 'col-md-4', 'hidden-sm', 'hidden-xs'), - classNames('col-lg-4', 'col-md-4', 'col-sm-6', 'col-xs-6'), + classNames('col-lg-2', 'col-md-2', 'col-sm-6', 'col-xs-6'), + classNames('col-lg-2', 'col-md-2', 'hidden-sm', 'hidden-xs'), + classNames('col-lg-2', 'col-md-2', 'hidden-sm', 'hidden-xs'), + classNames('col-lg-2', 'col-md-2', 'col-sm-3', 'col-xs-3'), + classNames('col-lg-2', 'col-md-2', 'hidden-sm', 'hidden-xs'), + classNames('col-lg-2', 'col-md-2', 'hidden-sm', 'hidden-xs'), + classNames('col-lg-2', 'col-md-2', 'col-sm-3', 'col-xs-3'), Kebab.columnClass, ]; +export const getStatus = (obj: VMKind | VMIKind) => + obj.kind === VirtualMachineModel.kind ? getVMStatus({ vm: obj as VMKind }) : getPhase(obj); + const VMHeader = () => dimensifyHeader( [ @@ -44,6 +57,9 @@ const VMHeader = () => sortField: 'metadata.name', transforms: [sortable], }, + { + title: 'Instance', + }, { title: 'Namespace', sortField: 'metadata.namespace', @@ -51,9 +67,20 @@ const VMHeader = () => }, { title: 'Status', - sortFunc: 'string', + sortFunc: 'getStatus', transforms: [sortable], }, + { + title: 'Created', + sortField: 'metadata.creationTimestamp', + transforms: [sortable], + }, + { + title: 'Node', + }, + { + title: 'IP Address', + }, { title: '', }, @@ -62,42 +89,101 @@ const VMHeader = () => ); const VMRow: React.FC = ({ - obj: vm, + obj, customData: { pods, migrations, vmiLookup, migrationLookup }, index, key, style, }) => { const dimensify = dimensifyRow(tableColumnClasses); - const name = getName(vm); - const namespace = getNamespace(vm); - const uid = getUID(vm); - const lookupID = getBasicID(vm); - + const name = getName(obj); + const namespace = getNamespace(obj); + const uid = getUID(obj); + const lookupID = getBasicID(obj); const migration = migrationLookup[lookupID]; - const vmi = vmiLookup[lookupID]; - const vmStatus = getVMStatus({ vm, vmi, pods, migrations }); + + let vm: VMKind; + let vmi: VMIKind; + let status: React.ReactNode; + let vmStatus; + let actions = [Kebab.factory.ModifyLabels, Kebab.factory.ModifyAnnotations, Kebab.factory.Delete]; + + if (obj.kind === VirtualMachineModel.kind) { + vm = obj as VMKind; + vmi = vmiLookup[lookupID]; + vmStatus = getVMStatus({ vm: vm as VMKind, vmi, pods, migrations }); + status = ; + actions = menuActions; + } else { + vmi = obj as VMIKind; + status = ; + } return ( - + {vm ? ( + + ) : ( + +
+ This VMI doesn’t have an owner VM since it might have been created outside of the + console. +
+
+ )} +
+ + {vmi ? ( + + ) : ( + +
+ This VMI is currently off. +
+ For further details please click its owner VM link below. +
+
+ )}
+ {status} + {fromNow(obj.metadata.creationTimestamp)} - + {getVMINodeName(vmi) && ( + + )} + {vmi && getVmiIpAddresses(vmi)} { - return action(VirtualMachineModel, vm, { + options={actions.map((action) => + action(vm ? VirtualMachineModel : VirtualMachineInstanceModel, obj, { vmStatus, migration, vmi, - }); - })} + }), + )} key={`kebab-for-${uid}`} id={`kebab-for-${uid}`} /> @@ -171,7 +257,13 @@ export const VirtualMachinesPage: React.FC = (props) = }), ]; - const flatten = ({ vms }) => getLoadedData(vms, []); + const flatten = ({ vms, vmis }) => + _.unionBy( + getLoadedData(vms, []), + getLoadedData(vmis, []), + (obj) => `${getName(obj)}-${getNamespace(obj)}`, + ); + const createAccessReview = skipAccessReview ? null : { model: VirtualMachineModel, namespace }; return ( @@ -192,7 +284,7 @@ export const VirtualMachinesPage: React.FC = (props) = }; type VMRowProps = { - obj: VMKind; + obj: VMKind | VMIKind; index: number; key: string; style: object; @@ -205,7 +297,7 @@ type VMRowProps = { }; type VMListProps = { - data: VMKind[]; + data: (VMKind | VMIKind)[]; resources: { pods: FirehoseResult; migrations: FirehoseResult; diff --git a/frontend/packages/kubevirt-plugin/src/selectors/vmi/basic.ts b/frontend/packages/kubevirt-plugin/src/selectors/vmi/basic.ts index d11bd5edc8e..a27fcf33b0e 100644 --- a/frontend/packages/kubevirt-plugin/src/selectors/vmi/basic.ts +++ b/frontend/packages/kubevirt-plugin/src/selectors/vmi/basic.ts @@ -24,3 +24,5 @@ export const isVMIRunning = (vmi: VMIKind) => vmi && vmi.status && vmi.status.ph export const getVMIInterfaces = (vmi: VMIKind) => (vmi && vmi.status && vmi.status.interfaces) || []; + +export const getVMINodeName = (vmi: VMIKind) => vmi && vmi.status && vmi.status.nodeName;