Skip to content

Commit

Permalink
kubernetes: Add VM detail page
Browse files Browse the repository at this point in the history
- Add navigation between vms and vm
- Refactor initialization / redux store
- Add status to VmOverviewTabKubevirt when in VmDetail
- Refactor ids in VmMetricsTab

Closes #9466
  • Loading branch information
suomiy authored and martinpitt committed Jul 31, 2018
1 parent 99fc3a3 commit bcd8490
Show file tree
Hide file tree
Showing 17 changed files with 638 additions and 206 deletions.
23 changes: 22 additions & 1 deletion pkg/kubernetes/scripts/virtual-machines.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,11 @@
require('angular-dialog.js');
require('./kube-client');
require('./listing');
var vmsReact = require('./virtual-machines/index.jsx');
var vmsReact = require('./virtual-machines/entry-points/virtual-machines.jsx');
var vmReact = require('./virtual-machines/entry-points/virtual-machine.jsx');

require('../views/virtual-machines-page.html');
require('../views/virtual-machine-page.html');

angular.module('kubernetes.virtualMachines', [
'ngRoute',
Expand All @@ -45,6 +47,13 @@
.when('/vms', {
templateUrl: 'views/virtual-machines-page.html',
controller: 'VirtualMachinesCtrl'
})
.when('/vms/:namespace/:name', {
templateUrl: 'views/virtual-machine-page.html',
controller: 'VirtualMachineCtrl'
})
.when('/vms/:namespace', {
redirectTo: '/vms'
});
/*
Links rewriting is enabled by default. It does two things:
Expand Down Expand Up @@ -76,6 +85,18 @@
function($scope, loader, select, methods, request) {
vmsReact.init($scope, loader, select, methods, request);
}]
)

.controller('VirtualMachineCtrl', [
'$scope',
'$routeParams',
'kubeLoader',
'kubeSelect',
'kubeMethods',
'KubeRequest',
function($scope, $routeParams, loader, select, methods, request) {
vmReact.init($scope, $routeParams, loader, select, methods, request);
}]
);

}());
22 changes: 12 additions & 10 deletions pkg/kubernetes/scripts/virtual-machines/components/VmActions.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,18 @@ import { vmActionFailed } from '../action-creators.jsx';

// import DropdownButtons from '../../../../machines/components/dropdownButtons.jsx';

const VmActions = ({ vm, onFailure }: { vm: Vm, onFailure: Function }) => {
const VmActions = ({ vm, onDeleteSuccess, onDeleteFailure }: { vm: Vm, onDeleteSuccess: Function, onDeleteFailure: Function }) => {
const id = vmIdPrefx(vm);

const onDelete = () => {
vmDelete({ vm }).catch((error) => {
console.info('VmActions: delete failed: ', error);
onFailure({
message: _("VM DELETE failed."),
detail: error,
});
});
vmDelete({ vm }).then(onDeleteSuccess)
.catch((error) => {
console.info('VmActions: delete failed: ', error);
onDeleteFailure({
message: _("VM DELETE failed."),
detail: error,
});
});
};

const buttonDelete = (
Expand All @@ -56,12 +57,13 @@ const VmActions = ({ vm, onFailure }: { vm: Vm, onFailure: Function }) => {

VmActions.propTypes = {
vm: PropTypes.object.isRequired,
onFailure: PropTypes.func.isRequired,
onDeleteSuccess: PropTypes.func.isRequired,
onDeleteFailure: PropTypes.func.isRequired,
};

export default connect(
() => ({}),
(dispatch, { vm }) => ({
onFailure: ({ message, detail }) => dispatch(vmActionFailed({ vm, message, detail })),
onDeleteFailure: ({ message, detail }) => dispatch(vmActionFailed({ vm, message, detail })),
}),
)(VmActions);
104 changes: 104 additions & 0 deletions pkg/kubernetes/scripts/virtual-machines/components/VmDetail.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/*
* This file is part of Cockpit.
*
* Copyright (C) 2018 Red Hat, Inc.
*
* Cockpit is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* Cockpit is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Cockpit; If not, see <http://www.gnu.org/licenses/>.
*/

// @flow

import React, { PropTypes } from 'react';
import cockpit, { gettext as _ } from 'cockpit';
import { connect } from "react-redux";

import { DetailPage, DetailPageRow, DetailPageHeader } from 'cockpit-components-detail-page.jsx';
import { getPod, getPodMetrics } from '../selectors.jsx';
import VmOverviewTab from './VmOverviewTabKubevirt.jsx';
import VmActions from './VmActions.jsx';
import VmMetricsTab from './VmMetricsTab.jsx';
import VmDisksTab from './VmDisksTabKubevirt.jsx';

import type { Vm, VmMessages, PersistenVolumes, Pod } from '../types.jsx';
import { vmIdPrefx, prefixedId } from '../utils.jsx';

const navigateToVms = () => {
cockpit.location.go([ 'vms' ]);
};

const VmDetail = ({ vm, pageParams, vmMessages, pvs, pod, podMetrics }:
{ vm: Vm, vmMessages: VmMessages, pageParams: Object, pvs: PersistenVolumes, pod: Pod}) => {
const mainTitle = vm ? `${vm.metadata.namespace}:${vm.metadata.name}` : null;
const actions = vm ? <VmActions vm={vm} onDeleteSuccess={navigateToVms} /> : null;
const header = (<DetailPageHeader title={mainTitle}
navigateUpTitle={_("Show all VMs")}
onNavigateUp={navigateToVms}
actions={actions}
idPrefix={'vm-header'}
iconClass='fa pficon-virtual-machine fa-fw' />);

if (!vm) {
return (
<div>
<DetailPage>
{header}
<DetailPageRow title={cockpit.format(_("VM $0:$1 does not exist."), pageParams.namespace, pageParams.name)}
idPrefix={'vm-not-found'} />
</DetailPage>
</div>
);
}
const idPrefix = vmIdPrefx(vm);

return (
<div>
<DetailPage>
{header}
<DetailPageRow title={_("VM")} idPrefix={prefixedId(idPrefix, 'vm')} >
<VmOverviewTab vm={vm} vmMessages={vmMessages} pod={pod} showState />
</DetailPageRow>
<DetailPageRow title={_("Usage")} idPrefix={prefixedId(idPrefix, 'usage')} >
<VmMetricsTab idPrefix={idPrefix} podMetrics={podMetrics} />
</DetailPageRow>
<DetailPageRow title={_("Disks")} idPrefix={prefixedId(idPrefix, 'disks')} >
<VmDisksTab vm={vm} pvs={pvs} />
</DetailPageRow>
</DetailPage>
</div>
);
};

VmDetail.propTypes = {
vm: PropTypes.object.isRequired,
pageParams: PropTypes.object,
vmMessages: PropTypes.object,
pvs: PropTypes.array.isRequired,
pod: PropTypes.object.isRequired,
podMetrics: PropTypes.object,
};

export default connect(
({vms, pvs, pods, vmsMessages, nodeMetrics}) => {
const vm = vms.length > 0 ? vms[0] : null;
const pod = getPod(vm, pods);
const podMetrics = getPodMetrics(pod, nodeMetrics);
return {
vm,
vmMessages: vm ? vmsMessages[vm.metadata.uid] : null,
pvs,
pod,
podMetrics,
};
},
)(VmDetail);
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ const prepareDiskData = (disk, vm, pvs, idPrefix) => {
if (pv) {
bus = _("iSCSI");
onNavigate = () => cockpit.jump(`/kubernetes#/volumes/${pv.metadata.name}`);
} else if (disk.disk.bus) {
} else if (disk.disk && disk.disk.bus) {
bus = disk.disk.bus;
}

Expand Down
35 changes: 18 additions & 17 deletions pkg/kubernetes/scripts/virtual-machines/components/VmMetricsTab.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import React, { PropTypes } from 'react';
import { DonutChart } from 'patternfly-react';
import cockpit, { gettext as _ } from 'cockpit';
import { prefixedId } from '../utils.jsx';

import './VmMetricsTab.less';

Expand All @@ -38,11 +39,11 @@ const MetricColumn = ({ type, children, className }) => {
const MetricColumnContent = ({ id, title, smallTitle }) => {
return (
<div id={id} className="centered">
<span id={`${id}-title`} className="block donut-title-big-pf">
<span id={prefixedId(id, 'title')} className="block donut-title-big-pf">
{title}
</span>
<div className="container small-text-layout">
<span id={`${id}-small-title`} className="donut-title-small-pf small-text">
<span id={prefixedId(id, 'small-title')} className="donut-title-small-pf small-text">
{smallTitle}
</span>
</div>
Expand Down Expand Up @@ -79,61 +80,61 @@ const CPUChart = ({ id, podMetrics }) => {
);
};

const CpuColumn = ({ podMetrics }) => {
const CpuColumn = ({ idPrefix, podMetrics }) => {
return (
<MetricColumn type={_("CPU")}>
<CPUChart id="cpu-metric" podMetrics={podMetrics} />
<CPUChart id={prefixedId(idPrefix, 'cpu-metric')} podMetrics={podMetrics} />
</MetricColumn>
);
};

const MemoryColumn = ({ podMetrics }) => {
const MemoryColumn = ({ idPrefix, podMetrics }) => {
const usage = cockpit.format_bytes(podMetrics.memory.usageBytes);
return (
<MetricColumn type={_("Memory")}>
<MetricColumnContent id="memory-metric" title={usage} />
<MetricColumnContent id={prefixedId(idPrefix, 'memory-metric')} title={usage} />
</MetricColumn>
);
};

const NetworkColumn = ({ podMetrics }) => {
const NetworkColumn = ({ idPrefix, podMetrics }) => {
const received = cockpit.format_bytes_per_sec(podMetrics.network.rxBytes);
const transmitted = cockpit.format_bytes_per_sec(podMetrics.network.txBytes);
const title = (
<div>
<div className="inline-block" id="download-metric">
<div className="inline-block" id={prefixedId(idPrefix, 'download-metric')}>
<div className="next-to-left fa fa-arrow-down" />
{received}
</div>
<div className="inline-block" id="upload-metric">
<div className="inline-block" id={prefixedId(idPrefix, 'upload-metric')}>
<div className="next-clear fa fa-arrow-up" />
{transmitted}
</div>
</div>
);
return (
<MetricColumn type={_("Network")} className="no-padding-left">
<MetricColumnContent id="network-metric" title={title} />
<MetricColumnContent id={prefixedId(idPrefix, 'network-metric')} title={title} />
</MetricColumn>
);
};

const PodMetrics = ({ podMetrics }) => {
const PodMetrics = ({ idPrefix, podMetrics }) => {
return (
<div className="row">
<CpuColumn podMetrics={podMetrics} />
<MemoryColumn podMetrics={podMetrics} />
<NetworkColumn podMetrics={podMetrics} />
<CpuColumn idPrefix={idPrefix} podMetrics={podMetrics} />
<MemoryColumn idPrefix={idPrefix} podMetrics={podMetrics} />
<NetworkColumn idPrefix={idPrefix} podMetrics={podMetrics} />
</div>
);
};

const VmMetricsTab = ({podMetrics}) => {
const content = podMetrics ? (<PodMetrics podMetrics={podMetrics} />)
const VmMetricsTab = ({idPrefix, podMetrics}) => {
const content = podMetrics ? (<PodMetrics idPrefix={idPrefix} podMetrics={podMetrics} />)
: _("Usage metrics are available after the pod starts");

return (
<div id="usage-metrics">
<div id={prefixedId(idPrefix, 'usage-metrics')}>
{content}
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ const PodLink = ({ pod }) => {
return (<a href={`#/l/pods/${pod.metadata.namespace}/${pod.metadata.name}`}>{pod.metadata.name}</a>);
};

const VmOverviewTabKubevirt = ({ vm, vmMessages, pod }: { vm: Vm, vmMessages: VmMessages, pod: Pod }) => {
const VmOverviewTabKubevirt = ({ vm, vmMessages, pod, showState }: { vm: Vm, vmMessages: VmMessages, pod: Pod, showState: boolean }) => {
const idPrefix = vmIdPrefx(vm);

const message = (<VmMessage vmMessages={vmMessages} vm={vm} />);
Expand All @@ -73,12 +73,25 @@ const VmOverviewTabKubevirt = ({ vm, vmMessages, pod }: { vm: Vm, vmMessages: Vm
const nodeLink = nodeName ? (<a href={`#/nodes/${nodeName}`}>{nodeName}</a>) : '-';
const podLink = (<PodLink pod={pod} />);

const items = [
{title: commonTitles.MEMORY, value: getMemory(vm), idPostfix: 'memory'},
{title: _("Node:"), value: nodeLink, idPostfix: 'node'},
{title: commonTitles.CPUS, value: _(getValueOrDefault(() => vm.spec.domain.cpu.cores, 1)), idPostfix: 'vcpus'},
{title: _("Labels:"), value: getLabels(vm), idPostfix: 'labels'},
{title: _("Pod:"), value: podLink, idPostfix: 'pod'},
const memoryItem = {title: commonTitles.MEMORY, value: getMemory(vm), idPostfix: 'memory'};
const vCpusItem = {title: commonTitles.CPUS, value: _(getValueOrDefault(() => vm.spec.domain.cpu.cores, 1)), idPostfix: 'vcpus'};
const podItem = {title: _("Pod:"), value: podLink, idPostfix: 'pod'};
const nodeItem = {title: _("Node:"), value: nodeLink, idPostfix: 'node'};
const labelsItem = {title: _("Labels:"), value: getLabels(vm), idPostfix: 'labels'};

const items = showState ? [
memoryItem,
{title: _("State"), value: getValueOrDefault(() => vm.status.phase, _("n/a")), idPostfix: 'state'},
vCpusItem,
nodeItem,
podItem,
labelsItem,
] : [
memoryItem,
nodeItem,
vCpusItem,
labelsItem,
podItem,
];

return (<VmOverviewTab message={message}
Expand Down
Loading

0 comments on commit bcd8490

Please sign in to comment.