diff --git a/frontend/packages/kubevirt-plugin/src/components/modals/start-stop-vm-modal.tsx b/frontend/packages/kubevirt-plugin/src/components/modals/start-stop-vm-modal.tsx
new file mode 100644
index 00000000000..272fdcfa9c4
--- /dev/null
+++ b/frontend/packages/kubevirt-plugin/src/components/modals/start-stop-vm-modal.tsx
@@ -0,0 +1,71 @@
+import * as React from 'react';
+
+import { withHandlePromise } from '@console/internal/components/utils';
+
+import {
+ createModalLauncher,
+ ModalTitle,
+ ModalBody,
+ ModalSubmitFooter,
+} from '@console/internal/components/factory';
+
+import { k8sPatch } from '@console/internal/module/k8s';
+
+import { getPxeBootPatch } from 'kubevirt-web-ui-components';
+import { getName, getNamespace } from '@console/shared/src';
+import { VirtualMachineModel } from '../../models';
+import { VMKind } from '../../types/vm';
+
+const StartStopVmModal = withHandlePromise((props: StartStopVmModalProps) => {
+ const { vm, start, inProgress, errorMessage, handlePromise, close, cancel } = props;
+
+ const submit = (event) => {
+ event.preventDefault();
+
+ const patches = [];
+
+ // handle PXE boot
+ if (start) {
+ const pxePatch = getPxeBootPatch(vm);
+ patches.push(...pxePatch);
+ }
+
+ patches.push({
+ op: 'replace',
+ path: '/spec/running',
+ value: start,
+ });
+
+ const promise = k8sPatch(VirtualMachineModel, vm, patches);
+ return handlePromise(promise).then(close);
+ };
+
+ const action = start ? 'Start' : 'Stop';
+ return (
+
+ );
+});
+
+export type StartStopVmModalProps = {
+ vm: VMKind;
+ start: boolean;
+ handlePromise: (promise: Promise) => Promise;
+ inProgress: boolean;
+ errorMessage: string;
+ cancel: () => void;
+ close: () => void;
+};
+
+export const startStopVmModal = createModalLauncher(StartStopVmModal);
diff --git a/frontend/packages/kubevirt-plugin/src/components/vms/menu-actions.tsx b/frontend/packages/kubevirt-plugin/src/components/vms/menu-actions.tsx
new file mode 100644
index 00000000000..6226e705181
--- /dev/null
+++ b/frontend/packages/kubevirt-plugin/src/components/vms/menu-actions.tsx
@@ -0,0 +1,57 @@
+import {
+ getVmStatus,
+ VM_STATUS_IMPORTING,
+ VM_STATUS_V2V_CONVERSION_IN_PROGRESS,
+} from 'kubevirt-web-ui-components';
+
+import { asAccessReview, Kebab, KebabOption } from '@console/internal/components/utils';
+import { K8sKind, PodKind } from '@console/internal/module/k8s';
+import { isVMRunning } from '../../selectors/vm';
+import { startStopVmModal } from '../modals/start-stop-vm-modal';
+import { VMKind } from '../../types/vm';
+
+type ActionArgs = {
+ pods: PodKind[];
+ migrations: any[];
+};
+
+const isImporting = (vm: VMKind, { pods, migrations }: ActionArgs): boolean => {
+ const status = getVmStatus(vm, pods, migrations);
+ return (
+ status && [VM_STATUS_IMPORTING, VM_STATUS_V2V_CONVERSION_IN_PROGRESS].includes(status.status)
+ );
+};
+
+const menuActionStart = (kindObj: K8sKind, vm: VMKind, actionArgs: ActionArgs): KebabOption => {
+ return {
+ hidden: isImporting(vm, actionArgs) || isVMRunning(vm),
+ label: 'Start Virtual Machine',
+ callback: () =>
+ startStopVmModal({
+ vm,
+ start: true,
+ }),
+ accessReview: asAccessReview(kindObj, vm, 'patch'),
+ };
+};
+
+const menuActionStop = (kindObj: K8sKind, vm: VMKind): KebabOption => {
+ return {
+ hidden: !isVMRunning(vm),
+ label: 'Stop Virtual Machine',
+ callback: () =>
+ startStopVmModal({
+ vm,
+ start: false,
+ }),
+ accessReview: asAccessReview(kindObj, vm, 'patch'),
+ };
+};
+
+export const menuActions = [
+ menuActionStart,
+ menuActionStop,
+ Kebab.factory.ModifyLabels,
+ Kebab.factory.ModifyAnnotations,
+ Kebab.factory.Delete,
+];
diff --git a/frontend/packages/kubevirt-plugin/src/components/vms/vm.tsx b/frontend/packages/kubevirt-plugin/src/components/vms/vm.tsx
index 8e9f421c615..cd0bb34c94b 100644
--- a/frontend/packages/kubevirt-plugin/src/components/vms/vm.tsx
+++ b/frontend/packages/kubevirt-plugin/src/components/vms/vm.tsx
@@ -11,21 +11,23 @@ import {
} from 'kubevirt-web-ui-components';
import { getName, getNamespace, getUid } from '@console/shared';
-import { NamespaceModel } from '@console/internal/models';
+import { NamespaceModel, PodModel } from '@console/internal/models';
import { Table, MultiListPage, TableRow, TableData } from '@console/internal/components/factory';
-import { Kebab, ResourceLink } from '@console/internal/components/utils';
+import { FirehoseResult, Kebab, ResourceLink } from '@console/internal/components/utils';
// import { actions } from '../../module/okdk8s';
import { sortable } from '@patternfly/react-table';
+import { PodKind } from '@console/internal/module/k8s';
import {
+ VirtualMachineInstanceMigrationModel,
VirtualMachineModel,
// VirtualMachineInstanceModel,
- // VirtualMachineInstanceMigrationModel,
} from '../../models';
+
import { VMKind } from '../../types';
+import { menuActions } from './menu-actions';
// import { openCreateVmWizard } from '../modals/create-vm-modal';
-// import { menuActions } from './menu-actions';
const tableColumnClasses = [
classNames('col-lg-4', 'col-md-4', 'col-sm-6', 'col-xs-6'),
@@ -51,18 +53,19 @@ const VMHeader = () => [
// title: 'Status',
// props: { className: tableColumnClasses[2] },
// },
- // {
- // title: '',
- // props: { className: Kebab.columnClass, props: { className: tableColumnClasses[3] } },
- // },
+ {
+ title: '',
+ props: { className: Kebab.columnClass, props: { className: tableColumnClasses[3] } },
+ },
];
-const VMRow: React.FC = ({ obj: vm, index, key, style }) => {
+const VMRow: React.FC = ({ obj: vm, customData, index, key, style }) => {
const name = getName(vm);
const namespace = getNamespace(vm);
+ const uid = getUid(vm);
return (
-
+
@@ -70,20 +73,32 @@ const VMRow: React.FC = ({ obj: vm, index, key, style }) => {
{/* TODO(mlibra): migrate VM status in a follow-up */}
- {/* TODO(mlibra): migrate actions in a follow-up */}
+
+ action(VirtualMachineModel, vm, customData))}
+ key={`kebab-for-${uid}`}
+ id={`kebab-for-${uid}`}
+ />
+
);
};
-const VMList: React.FC & VMListProps> = (props) => (
-
-);
+const VMList: React.FC & VMListProps> = (props) => {
+ const { resources } = props;
+ return (
+
+ );
+};
VMList.displayName = 'VMList';
@@ -122,6 +137,12 @@ const getCreateProps = (namespace: string) => ({
export const VirtualMachinesPage: React.FC = (props) => {
const { namespace } = props;
+ const resources = [
+ getResource(VirtualMachineModel, { namespace, prop: 'vms' }),
+ getResource(PodModel, { namespace, prop: 'pods' }),
+ getResource(VirtualMachineInstanceMigrationModel, { namespace, prop: 'migrations' }),
+ ];
+
const flatten = ({ vms: { data: vmsData, loaded, loadError } }) =>
loaded && !loadError ? vmsData : [];
@@ -133,7 +154,7 @@ export const VirtualMachinesPage: React.FC = (props) =
rowFilters={filters}
ListComponent={VMList}
createProps={getCreateProps(props.namespace)}
- resources={[getResource(VirtualMachineModel, { namespace, prop: 'vms' })]}
+ resources={resources}
flatten={flatten}
/>
);
@@ -144,10 +165,18 @@ type VMRowProps = {
index: number;
key: string;
style: object;
+ customData: {
+ pods: PodKind[];
+ migrations: any[];
+ };
};
type VMListProps = {
data: VMKind[];
+ resources: {
+ pods: FirehoseResult;
+ migrations: FirehoseResult;
+ };
};
type VirtualMachinesPageProps = {
diff --git a/frontend/packages/kubevirt-plugin/src/selectors/vm/selectors.ts b/frontend/packages/kubevirt-plugin/src/selectors/vm/selectors.ts
index fd42573c626..c307b55b155 100644
--- a/frontend/packages/kubevirt-plugin/src/selectors/vm/selectors.ts
+++ b/frontend/packages/kubevirt-plugin/src/selectors/vm/selectors.ts
@@ -7,6 +7,15 @@ export const getDisks = (volume) => _.get(volume, 'spec.template.spec.domain.dev
export const getVolumes = (vm: VMKind) => _.get(vm, 'spec.template.spec.volumes', []);
export const getDataVolumeTemplates = (vm: VMKind) => _.get(vm, 'spec.dataVolumeTemplates', []);
+export const isVMRunning = (value: VMKind) =>
+ _.get(value, 'spec.running', false) as VMKind['spec']['running'];
+
+export const isVMReady = (value: VMKind) =>
+ _.get(value, 'status.ready', false) as VMKind['status']['ready'];
+
+export const isVMCreated = (value: VMKind) =>
+ _.get(value, 'status.created', false) as VMKind['status']['created'];
+
export const getVmPreferableDiskBus = (vm: VMKind) =>
getDisks(vm)
.map((disk) => getDiskBus(disk))