diff --git a/frontend/public/kubevirt/_style.scss b/frontend/public/kubevirt/_style.scss
index 93957b198d8..d8ab00d3c67 100644
--- a/frontend/public/kubevirt/_style.scss
+++ b/frontend/public/kubevirt/_style.scss
@@ -4,3 +4,4 @@
@import 'components/vm';
@import 'components/vmconsoles';
+@import 'components/disk';
diff --git a/frontend/public/kubevirt/components/_disk.scss b/frontend/public/kubevirt/components/_disk.scss
new file mode 100644
index 00000000000..5ab36c4feb3
--- /dev/null
+++ b/frontend/public/kubevirt/components/_disk.scss
@@ -0,0 +1,4 @@
+.disk-loading {
+ margin-left: 15px;
+ left: 0%
+}
\ No newline at end of file
diff --git a/frontend/public/kubevirt/components/disk.jsx b/frontend/public/kubevirt/components/disk.jsx
new file mode 100644
index 00000000000..553d7303222
--- /dev/null
+++ b/frontend/public/kubevirt/components/disk.jsx
@@ -0,0 +1,106 @@
+import React from 'react';
+import * as _ from 'lodash';
+import { List, ColHead, ListHeader, ResourceRow } from './factory/okdfactory';
+import { PersistentVolumeClaimModel } from '../models';
+import { Loading, Firehose, Cog } from './utils/okdutils';
+import { getResourceKind, getFlattenForKind } from './utils/resources';
+import { DASHES, BUS_VIRTIO, DISK } from './utils/constants';
+import { deleteDeviceModal } from './modals/delete-device-modal';
+
+const visibleRowStyle = 'col-lg-3 col-md-3 col-sm-3 col-xs-4';
+const hiddenRowStyle = 'col-lg-3 col-md-3 col-sm-3 hidden-xs';
+
+const DiskHeader = props =>
+ Disk Name
+ Size
+ Interface
+ Storage Class
+;
+
+const PvcRow = props => {
+ if (props.loadError) {
+ return DASHES;
+ } else if (props.loaded){
+ const pvc = props.flatten(props.resources);
+ return _.get(pvc, props.pvcPath, DASHES);
+ }
+ return ;
+};
+
+const menuActionDelete = (vm, storage) => ({
+ label: 'Delete',
+ callback: () => deleteDeviceModal({
+ type: DISK,
+ device: storage,
+ vm: vm
+ })
+});
+
+const getActions = (vm, nic) => {
+ const actions = [menuActionDelete];
+ return actions.map(a => a(vm, nic));
+};
+
+export const DiskRow = ({obj: storage}) => {
+ const pvcName = _.get(storage.volume, 'persistentVolumeClaim.claimName');
+ let sizeRow = DASHES;
+ let storageRow = DASHES;
+
+ if (pvcName) {
+ const pvcs = getResourceKind(PersistentVolumeClaimModel, pvcName, true, storage.vm.metadata.namespace, false);
+ sizeRow =
+
+ ;
+ storageRow =
+
+ ;
+ } else {
+ const dataVolumeName = _.get(storage.volume, 'dataVolume.name');
+ const dataVolume = _.get(storage.vm, 'spec.dataVolumeTemplates', []).find(dv => _.get(dv,'metadata.name') === dataVolumeName);
+ if (dataVolume) {
+ sizeRow = _.get(dataVolume,'spec.pvc.resources.requests.storage', DASHES);
+ storageRow = _.get(dataVolume,'spec.pvc.storageClassName', DASHES);
+ }
+ }
+
+ return
+
+ {storage.name}
+
+
+ {sizeRow}
+
+
+ {_.get(storage, 'disk.bus') || BUS_VIRTIO}
+
+
+ {storageRow}
+
+
+
+
+ ;
+};
+
+export const Disk = ({obj: vm}) => {
+ const disks = _.get(vm, 'spec.template.spec.domain.devices.disks',[]);
+ const volumes = _.get(vm,'spec.template.spec.volumes',[]);
+ const storages = disks.map(disk => {
+ const volume = volumes.find(v => v.name === disk.volumeName);
+ return {
+ ...disk,
+ vm,
+ volume
+ };
+ });
+ return
;
+};
diff --git a/frontend/public/kubevirt/components/modals/delete-device-modal.jsx b/frontend/public/kubevirt/components/modals/delete-device-modal.jsx
new file mode 100644
index 00000000000..c0c237b1d5a
--- /dev/null
+++ b/frontend/public/kubevirt/components/modals/delete-device-modal.jsx
@@ -0,0 +1,94 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import * as _ from 'lodash';
+import { Form } from 'patternfly-react';
+
+import { PromiseComponent } from '../utils/okdutils';
+import { createModalLauncher, ModalTitle, ModalBody, ModalSubmitFooter } from '../factory/okdfactory';
+import { k8sPatch } from '../../module/okdk8s';
+import { VirtualMachineModel } from '../../models';
+import { NETWORK, DISK } from '../utils/constants';
+
+class DeleteDeviceModal extends PromiseComponent {
+ constructor(props) {
+ super(props);
+ this._cancel = this.props.cancel.bind(this);
+ this._submit = this.submit.bind(this);
+ }
+
+ submit(event) {
+ event.preventDefault();
+ const { vm, type, device } = this.props;
+
+ const deviceType = type === NETWORK ? {type: 'interfaces', spec: 'networks'} : { type: 'disks', spec: 'volumes'};
+ const devices = _.get(vm, `spec.template.spec.domain.devices.${deviceType.type}`, []);
+
+ const deviceIndex = devices.findIndex(d => d.name === device.name);
+ const specIndex = _.get(vm, `spec.template.spec.${deviceType.spec}`,[]).findIndex(spec => spec.name === device.name || spec.name === device.volumeName);
+
+ const patch = [];
+
+ if (deviceIndex !== -1) {
+ patch.push({
+ op: 'remove',
+ path: `/spec/template/spec/domain/devices/${deviceType.type}/${deviceIndex}`,
+ });
+ }
+
+ if (specIndex !== -1) {
+ patch.push({
+ op: 'remove',
+ path: `/spec/template/spec/${deviceType.spec}/${specIndex}`,
+ });
+ }
+
+ // disk may have dataVolumeTemplate defined that should be deleted too
+ if (type === DISK && _.get(device, 'volume.dataVolume') && _.get(vm, 'spec.dataVolumeTemplates')) {
+ const dataVolumeIndex = vm.spec.dataVolumeTemplates.findIndex(dataVolume => _.get(dataVolume, 'metadata.name') === device.volume.dataVolume.name);
+ if (dataVolumeIndex !== -1) {
+ patch.push({
+ op: 'remove',
+ path: `/spec/dataVolumeTemplates/${dataVolumeIndex}`,
+ });
+ }
+ }
+
+ // if pod network is deleted, we need to set autoAttachPodInterface to false
+ if (type === NETWORK && _.get(device, 'network.pod')) {
+ const op = _.has(vm, 'spec.domain.devices.autoAttachPodInterface') ? 'replace' : 'add';
+ patch.push({
+ op,
+ path: '/spec/template/spec/domain/devices/autoAttachPodInterface',
+ value: false
+ });
+ }
+
+ if (patch.length === 0) {
+ this.props.close();
+ } else {
+ const promise = k8sPatch(VirtualMachineModel, vm, patch);
+ this.handlePromise(promise).then(this.props.close);
+ }
+ }
+
+ render () {
+ const {vm, device} = this.props;
+ return ;
+ }
+}
+
+DeleteDeviceModal.propTypes = {
+ device: PropTypes.object.isRequired,
+ vm: PropTypes.object.isRequired,
+ type: PropTypes.string.isRequired,
+ close: PropTypes.func.isRequired
+};
+
+export const deleteDeviceModal = createModalLauncher(DeleteDeviceModal);
diff --git a/frontend/public/kubevirt/components/nic.jsx b/frontend/public/kubevirt/components/nic.jsx
new file mode 100644
index 00000000000..47b9923be34
--- /dev/null
+++ b/frontend/public/kubevirt/components/nic.jsx
@@ -0,0 +1,83 @@
+import React from 'react';
+import * as _ from 'lodash';
+import { Cog } from './utils/okdutils';
+import { List, ColHead, ListHeader, ResourceRow } from './factory/okdfactory';
+import { DASHES, BUS_VIRTIO, NETWORK_TYPE_MULTUS, NETWORK_TYPE_POD, NETWORK } from './utils/constants';
+import { deleteDeviceModal } from './modals/delete-device-modal';
+
+const getNetworkType = network => {
+ if (network) {
+ if (network.hasOwnProperty('pod')){
+ return NETWORK_TYPE_POD;
+ } else if (network.hasOwnProperty('multus')){
+ return NETWORK_TYPE_MULTUS;
+ }
+ }
+ return DASHES;
+};
+
+const menuActionDelete = (vm, nic) => ({
+ label: 'Delete',
+ callback: () => deleteDeviceModal({
+ type: NETWORK,
+ device: nic,
+ vm: vm
+ })
+});
+
+const getActions = (vm, nic) => {
+ const actions = [menuActionDelete];
+ return actions.map(a => a(vm, nic));
+};
+
+const visibleRowStyle = 'col-lg-3 col-md-3 col-sm-3 col-xs-4';
+const hiddenRowStyle = 'col-lg-3 col-md-3 col-sm-3 hidden-xs';
+
+const NicHeader = props =>
+ Nic Name
+ Model
+ Network
+ Mac Address
+;
+
+export const NicRow = ({obj: nic}) =>
+
+ {nic.name}
+
+
+ {nic.model || BUS_VIRTIO}
+
+
+ {getNetworkType(nic.network)}
+
+
+ {nic.macAddress || DASHES}
+
+
+
+
+;
+
+export const Nic = ({obj: vm}) => {
+ const interfaces = _.get(vm,'spec.template.spec.domain.devices.interfaces',[]);
+ const networks = _.get(vm,'spec.template.spec.networks',[]);
+ const nics = interfaces.map(i => {
+ const network = networks.find(n => n.name === i.name);
+ return {
+ ...i,
+ network,
+ vm: vm
+ };
+ });
+ return ;
+
+};
diff --git a/frontend/public/kubevirt/components/utils/constants.js b/frontend/public/kubevirt/components/utils/constants.js
new file mode 100644
index 00000000000..37700976cd9
--- /dev/null
+++ b/frontend/public/kubevirt/components/utils/constants.js
@@ -0,0 +1,9 @@
+export const DASHES = '---';
+
+export const BUS_VIRTIO = 'VirtIO';
+
+export const NETWORK_TYPE_MULTUS = 'Multus';
+export const NETWORK_TYPE_POD = 'Pod Networking';
+
+export const NETWORK = 'Network';
+export const DISK = 'Disk';
diff --git a/frontend/public/kubevirt/components/vm.jsx b/frontend/public/kubevirt/components/vm.jsx
index f7040330a3d..492bcbfbc22 100644
--- a/frontend/public/kubevirt/components/vm.jsx
+++ b/frontend/public/kubevirt/components/vm.jsx
@@ -23,8 +23,9 @@ import { getResourceKind, getLabelMatcher, findVMI, findPod, getFlattenForKind,
import { CreateVmWizard, TEMPLATE_TYPE_LABEL, TEMPLATE_OS_LABEL } from 'kubevirt-web-ui-components';
import VmConsolesConnected from './vmconsoles';
-
-const dashes = '---';
+import { Nic } from './nic';
+import { Disk } from './disk';
+import { DASHES } from './utils/constants';
const VMHeader = props =>
Name
@@ -66,16 +67,16 @@ const StateColumn = props => {
return getVmStatus(vm);
}
}
- return dashes;
+ return DASHES;
};
const PhaseColumn = props => {
if (props.loaded){
const resources = props.flatten(props.resources);
const vmi = props.filter(resources);
- return _.get(vmi, 'status.phase', dashes);
+ return _.get(vmi, 'status.phase', DASHES);
}
- return dashes;
+ return DASHES;
};
const FirehoseResourceLink = props => {
@@ -95,7 +96,7 @@ const FirehoseResourceLink = props => {
}
}
}
- return dashes;
+ return DASHES;
};
export const VMRow = ({obj: vm}) => {
@@ -104,8 +105,7 @@ export const VMRow = ({obj: vm}) => {
const podResources = getResourceKind(PodModel, undefined, true, vm.metadata.namespace, true, getLabelMatcher(vm));
return
-
-
+
@@ -131,6 +131,9 @@ export const VMRow = ({obj: vm}) => {
findPod(data, vm.metadata.name)} />
+
+
+
;
};
@@ -217,11 +220,11 @@ class VMResourceConfiguration extends Component {
Configuration
- Memory:
- - {configuration.memory || dashes}
+ - {configuration.memory || DASHES}
- CPU:
- - {configuration.cpu || dashes}
+ - {configuration.cpu || DASHES}
- Operating System:
- - {configuration.os || dashes}
+ - {configuration.os || DASHES}
;
@@ -269,11 +272,26 @@ export const VirtualMachinesDetailsPage = props => {
name: 'Consoles',
component: VmConsolesConnected
};
+
+ const nicsPage = {
+ href: 'nics',
+ name: 'Networks',
+ component: Nic
+ };
+
+ const disksPage = {
+ href: 'disks',
+ name: 'Disks',
+ component: Disk
+ };
+
const pages = [
navFactory.details(Details),
navFactory.editYaml(),
consolePage,
navFactory.events(VmiEvents),
+ nicsPage,
+ disksPage
];
return (