kubernetes: Add Virtual machines side tab
It lists VMs of Kubevirt. The tab is only shown if connected cluster has Kubevirt installed. It uses standard React + Redux + Saga approach. Nested in angular environment. Redux store is garbage collected when Virtual Machines view is left. Changed dependencies: * react-redux, redux-saga - React state management dependencies * babel-plugin-transform-regenerator - support for JS generator functions * eslint-plugin-flowtype, flow-bin, flow-webpack-plugin - JS type annotation Closes #7830
This commit is contained in:
parent
0218d776ca
commit
2a7622d28a
|
@ -4,6 +4,7 @@
|
||||||
"es6": true
|
"es6": true
|
||||||
},
|
},
|
||||||
"extends": "eslint:recommended",
|
"extends": "eslint:recommended",
|
||||||
|
"parser": "babel-eslint",
|
||||||
"parserOptions": {
|
"parserOptions": {
|
||||||
"ecmaFeatures": {
|
"ecmaFeatures": {
|
||||||
"experimentalObjectRestSpread": true,
|
"experimentalObjectRestSpread": true,
|
||||||
|
@ -11,7 +12,7 @@
|
||||||
},
|
},
|
||||||
"sourceType": "module"
|
"sourceType": "module"
|
||||||
},
|
},
|
||||||
"plugins": ["react"],
|
"plugins": ["flowtype", "react"],
|
||||||
"rules": {
|
"rules": {
|
||||||
"react/jsx-uses-vars": "error",
|
"react/jsx-uses-vars": "error",
|
||||||
"no-console": "off",
|
"no-console": "off",
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
[ignore]
|
||||||
|
|
||||||
|
[include]
|
||||||
|
|
||||||
|
[libs]
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
|
||||||
|
[options]
|
||||||
|
module.name_mapper='.*cockpit$' -> '<PROJECT_ROOT>/src/base1/cockpit.js'
|
||||||
|
|
||||||
|
[strict]
|
|
@ -165,6 +165,7 @@ po*.js.gz
|
||||||
/pkg/*/manifest.json
|
/pkg/*/manifest.json
|
||||||
|
|
||||||
.idea
|
.idea
|
||||||
|
.vscode/
|
||||||
|
|
||||||
# Test output
|
# Test output
|
||||||
Test*.log
|
Test*.log
|
||||||
|
|
|
@ -25,7 +25,9 @@
|
||||||
"qunit-tap": "1.5.1",
|
"qunit-tap": "1.5.1",
|
||||||
"qunitjs": "1.23.1",
|
"qunitjs": "1.23.1",
|
||||||
"react-lite": "0.15.39",
|
"react-lite": "0.15.39",
|
||||||
|
"react-redux": "5.0.6",
|
||||||
"redux": "3.7.2",
|
"redux": "3.7.2",
|
||||||
|
"redux-saga": "0.16.0",
|
||||||
"registry-image-widgets": "0.0.16",
|
"registry-image-widgets": "0.0.16",
|
||||||
"requirejs": "2.1.22",
|
"requirejs": "2.1.22",
|
||||||
"term.js-cockpit": "0.0.10"
|
"term.js-cockpit": "0.0.10"
|
||||||
|
@ -34,6 +36,7 @@
|
||||||
"babel-core": "^6.26.0",
|
"babel-core": "^6.26.0",
|
||||||
"babel-eslint": "~7.1.1",
|
"babel-eslint": "~7.1.1",
|
||||||
"babel-loader": "^6.4.1",
|
"babel-loader": "^6.4.1",
|
||||||
|
"babel-plugin-transform-regenerator": "6.26.0",
|
||||||
"babel-preset-es2015": "^6.24.1",
|
"babel-preset-es2015": "^6.24.1",
|
||||||
"babel-preset-react": "^6.24.1",
|
"babel-preset-react": "^6.24.1",
|
||||||
"chrome-remote-interface": "^0.25.2",
|
"chrome-remote-interface": "^0.25.2",
|
||||||
|
@ -42,13 +45,14 @@
|
||||||
"css-loader": "~0.23.1",
|
"css-loader": "~0.23.1",
|
||||||
"eslint": "^3.0.0",
|
"eslint": "^3.0.0",
|
||||||
"eslint-loader": "~1.6.1",
|
"eslint-loader": "~1.6.1",
|
||||||
|
"eslint-plugin-flowtype": "2.39.1",
|
||||||
"eslint-plugin-react": "~6.9.0",
|
"eslint-plugin-react": "~6.9.0",
|
||||||
"exports-loader": "~0.6.3",
|
"exports-loader": "~0.6.3",
|
||||||
"extend": "~3.0.0",
|
"extend": "~3.0.0",
|
||||||
"extract-text-webpack-plugin": "~1.0.1",
|
"extract-text-webpack-plugin": "~1.0.1",
|
||||||
"htmlparser": "~1.7.7",
|
|
||||||
"html-minifier": "~0.7.2",
|
"html-minifier": "~0.7.2",
|
||||||
"html-webpack-plugin": "~2.22.0",
|
"html-webpack-plugin": "~2.22.0",
|
||||||
|
"htmlparser": "~1.7.7",
|
||||||
"imports-loader": "~0.6.5",
|
"imports-loader": "~0.6.5",
|
||||||
"jed": "~1.1.0",
|
"jed": "~1.1.0",
|
||||||
"jshint": "~2.9.1",
|
"jshint": "~2.9.1",
|
||||||
|
|
|
@ -36,7 +36,7 @@ along with Cockpit; If not, see <http://www.gnu.org/licenses/>.
|
||||||
<div class="nav-item-pf-header">
|
<div class="nav-item-pf-header">
|
||||||
<span translate>Cluster</span>
|
<span translate>Cluster</span>
|
||||||
</div>
|
</div>
|
||||||
<ul class="list-group">
|
<ul class="list-group" id="kubernetes-navigation">
|
||||||
<li class="list-group-item" ng-class="{ active: viewActive(null)}">
|
<li class="list-group-item" ng-class="{ active: viewActive(null)}">
|
||||||
<a ng-href="#{{ viewUrl(null) }}">
|
<a ng-href="#{{ viewUrl(null) }}">
|
||||||
<i class="fa fa-dashboard fa-fw" title="Overview"></i>
|
<i class="fa fa-dashboard fa-fw" title="Overview"></i>
|
||||||
|
@ -55,6 +55,12 @@ along with Cockpit; If not, see <http://www.gnu.org/licenses/>.
|
||||||
<span class="list-group-item-value" translate>Containers</span>
|
<span class="list-group-item-value" translate>Containers</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="list-group-item" ng-class="{ active: viewActive('vms')}" ng-if="settings.kubevirtEnabled">
|
||||||
|
<a ng-href="#{{ viewUrl('vms') }}" id="vms-menu-link">
|
||||||
|
<i class="fa pficon-virtual-machine fa-fw" title="Virtual Machines"></i>
|
||||||
|
<span class="list-group-item-value" translate>Virtual Machines</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
<li class="list-group-item" ng-class="{ active: viewActive('topology')}">
|
<li class="list-group-item" ng-class="{ active: viewActive('topology')}">
|
||||||
<a ng-href="#{{ viewUrl('topology') }}">
|
<a ng-href="#{{ viewUrl('topology') }}">
|
||||||
<i class="pficon pficon-topology fa-fw" title="Topology"></i>
|
<i class="pficon pficon-topology fa-fw" title="Topology"></i>
|
||||||
|
|
|
@ -1110,8 +1110,9 @@
|
||||||
"CockpitEnvironment",
|
"CockpitEnvironment",
|
||||||
'kubeLoader',
|
'kubeLoader',
|
||||||
'cockpitConnectionInfo',
|
'cockpitConnectionInfo',
|
||||||
|
'KubevirtPrefix',
|
||||||
function($q, CockpitKubeRequest, cockpitKubeDiscover,
|
function($q, CockpitKubeRequest, cockpitKubeDiscover,
|
||||||
CockpitEnvironment, loader, info) {
|
CockpitEnvironment, loader, info, kubevirtPrefix) {
|
||||||
var promise = null;
|
var promise = null;
|
||||||
return function kubeDiscoverSettings(force) {
|
return function kubeDiscoverSettings(force) {
|
||||||
if (!force && promise)
|
if (!force && promise)
|
||||||
|
@ -1126,6 +1127,7 @@
|
||||||
isAdmin: false,
|
isAdmin: false,
|
||||||
currentUser: null,
|
currentUser: null,
|
||||||
canChangeConnection: false,
|
canChangeConnection: false,
|
||||||
|
kubevirtEnabled: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
var env_p = CockpitEnvironment()
|
var env_p = CockpitEnvironment()
|
||||||
|
@ -1171,7 +1173,18 @@
|
||||||
return $q.all([watch, req]);
|
return $q.all([watch, req]);
|
||||||
});
|
});
|
||||||
|
|
||||||
promise = $q.all([discover_p, env_p])
|
var kubevirt_p = new CockpitKubeRequest('GET', kubevirtPrefix)
|
||||||
|
.then(function(response) {
|
||||||
|
settings.kubevirtEnabled = true;
|
||||||
|
}, function(errorResponse) {
|
||||||
|
if (errorResponse.status === 404) {
|
||||||
|
settings.kubevirtEnabled = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
debug('Kubevirt check request failed:', errorResponse);
|
||||||
|
});
|
||||||
|
|
||||||
|
promise = $q.all([discover_p, env_p, kubevirt_p])
|
||||||
.then(function() {
|
.then(function() {
|
||||||
settings.canChangeConnection = info.type == "kubectl";
|
settings.canChangeConnection = info.type == "kubectl";
|
||||||
return settings;
|
return settings;
|
||||||
|
|
|
@ -38,6 +38,7 @@
|
||||||
|
|
||||||
var KUBE = "/api/v1";
|
var KUBE = "/api/v1";
|
||||||
var OPENSHIFT = "/oapi/v1";
|
var OPENSHIFT = "/oapi/v1";
|
||||||
|
var KUBEVIRT = "/apis/kubevirt.io/v1alpha1";
|
||||||
var DEFAULT = { api: KUBE, create: 0 };
|
var DEFAULT = { api: KUBE, create: 0 };
|
||||||
var SCHEMA = flatSchema([
|
var SCHEMA = flatSchema([
|
||||||
{ kind: "DeploymentConfig", type: "deploymentconfigs", api: OPENSHIFT },
|
{ kind: "DeploymentConfig", type: "deploymentconfigs", api: OPENSHIFT },
|
||||||
|
@ -62,6 +63,7 @@
|
||||||
{ kind: "Service", type: "services", api: KUBE, create: -80 },
|
{ kind: "Service", type: "services", api: KUBE, create: -80 },
|
||||||
{ kind: "SubjectAccessReview", type: "subjectaccessreviews", api: OPENSHIFT },
|
{ kind: "SubjectAccessReview", type: "subjectaccessreviews", api: OPENSHIFT },
|
||||||
{ kind: "User", type: "users", api: OPENSHIFT, global: true },
|
{ kind: "User", type: "users", api: OPENSHIFT, global: true },
|
||||||
|
{ kind: "VirtualMachine", type: "virtualmachines", api: KUBEVIRT },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
var NAME_RE = /^[a-z0-9]([-a-z0-9_.]*[a-z0-9])?$/;
|
var NAME_RE = /^[a-z0-9]([-a-z0-9_.]*[a-z0-9])?$/;
|
||||||
|
@ -306,6 +308,8 @@
|
||||||
|
|
||||||
.value("KUBE_SCHEMA", SCHEMA)
|
.value("KUBE_SCHEMA", SCHEMA)
|
||||||
|
|
||||||
|
.constant("KubevirtPrefix", KUBEVIRT)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* KUBE_NAME_RE
|
* KUBE_NAME_RE
|
||||||
*
|
*
|
||||||
|
|
|
@ -44,6 +44,7 @@
|
||||||
require('./nodes');
|
require('./nodes');
|
||||||
require('./topology');
|
require('./topology');
|
||||||
require('./volumes');
|
require('./volumes');
|
||||||
|
require('./virtual-machines.js');
|
||||||
|
|
||||||
/* And the actual application */
|
/* And the actual application */
|
||||||
require('./app');
|
require('./app');
|
||||||
|
@ -63,6 +64,7 @@
|
||||||
'kubernetes.volumes',
|
'kubernetes.volumes',
|
||||||
'kubernetes.nodes',
|
'kubernetes.nodes',
|
||||||
'kubernetes.date',
|
'kubernetes.date',
|
||||||
|
'kubernetes.virtualMachines',
|
||||||
'registry.images',
|
'registry.images',
|
||||||
'registry.policy',
|
'registry.policy',
|
||||||
'registry.projects',
|
'registry.projects',
|
||||||
|
|
|
@ -0,0 +1,79 @@
|
||||||
|
/*
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var angular = require('angular');
|
||||||
|
require('angular-route');
|
||||||
|
require('angular-dialog.js');
|
||||||
|
require('./kube-client');
|
||||||
|
require('./listing');
|
||||||
|
var vmsReact = require('./virtual-machines/index.jsx');
|
||||||
|
|
||||||
|
require('../views/virtual-machines-page.html');
|
||||||
|
|
||||||
|
angular.module('kubernetes.virtualMachines', [
|
||||||
|
'ngRoute',
|
||||||
|
'ui.cockpit',
|
||||||
|
'kubernetesUI',
|
||||||
|
'kubeClient',
|
||||||
|
'kubernetes.listing'
|
||||||
|
])
|
||||||
|
|
||||||
|
.config([
|
||||||
|
'$routeProvider',
|
||||||
|
'$locationProvider',
|
||||||
|
function($routeProvider, $locationProvider) {
|
||||||
|
$routeProvider
|
||||||
|
.when('/vms', {
|
||||||
|
templateUrl: 'views/virtual-machines-page.html',
|
||||||
|
controller: 'VirtualMachinesCtrl'
|
||||||
|
});
|
||||||
|
/*
|
||||||
|
Links rewriting is enabled by default. It does two things:
|
||||||
|
* It changes links href in older browsers.
|
||||||
|
* It handles the navigation instead of the browser.
|
||||||
|
|
||||||
|
The link rewriting code runs in 'click' event handler registered
|
||||||
|
to the `document` element to bubbling phase. It calls `preventDefault()`
|
||||||
|
event method and instructs browser to go to the destination.
|
||||||
|
|
||||||
|
Such behavior breaks event handling in React since:
|
||||||
|
* React always gets event with flag `defaultPrevented` set.
|
||||||
|
* Navigation is performed no matter if `event.preventDefault()` is called
|
||||||
|
in React handler.
|
||||||
|
|
||||||
|
@see https://docs.angularjs.org/api/ng/provider/$locationProvider
|
||||||
|
@see https://docs.angularjs.org/guide/$location#html5-mode
|
||||||
|
*/
|
||||||
|
$locationProvider.html5Mode({ rewriteLinks: false });
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
.controller('VirtualMachinesCtrl', [
|
||||||
|
'$scope',
|
||||||
|
'kubeLoader',
|
||||||
|
'kubeSelect',
|
||||||
|
function($scope, loader, select) {
|
||||||
|
vmsReact.init($scope, loader, select);
|
||||||
|
}]
|
||||||
|
);
|
||||||
|
|
||||||
|
}());
|
|
@ -0,0 +1,34 @@
|
||||||
|
/*
|
||||||
|
* This file is part of Cockpit.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2017 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as actionConstants from './action-types.jsx'
|
||||||
|
|
||||||
|
export function setVms(vms) {
|
||||||
|
return {
|
||||||
|
type: actionConstants.SET_VMS,
|
||||||
|
payload: vms
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setSettings(settings) {
|
||||||
|
return {
|
||||||
|
type: actionConstants.SET_SETTINGS,
|
||||||
|
payload: settings
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
/*
|
||||||
|
* This file is part of Cockpit.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2017 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const SET_VMS = 'SET_VMS'
|
||||||
|
export const SET_SETTINGS = 'SET_SETTINGS'
|
|
@ -0,0 +1,50 @@
|
||||||
|
/*
|
||||||
|
* This file is part of Cockpit.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2017 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 from 'react'
|
||||||
|
import { connect } from 'react-redux'
|
||||||
|
import { gettext as _ } from 'cockpit'
|
||||||
|
|
||||||
|
import { Listing } from '../../../../lib/cockpit-components-listing.jsx'
|
||||||
|
import VmsListingRow from './VmsListingRow.jsx'
|
||||||
|
|
||||||
|
const VmsListing = ({ vms, settings }) => {
|
||||||
|
const isOpenshift = settings.flavor === 'openshift'
|
||||||
|
const namespaceLabel = isOpenshift ? _("Project") : _("Namespace")
|
||||||
|
const rows = vms.map(vm => (<VmsListingRow vm={vm} key={vm.metadata.uid} />))
|
||||||
|
return (
|
||||||
|
<Listing title={_("Virtual Machines")}
|
||||||
|
emptyCaption={_("No virtual machines")}
|
||||||
|
columnTitles={[_("Name"), namespaceLabel, _("Node"), _("State")]}>
|
||||||
|
{rows}
|
||||||
|
</Listing>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
VmsListing.propTypes = {
|
||||||
|
vms: React.PropTypes.object.isRequired,
|
||||||
|
setting: React.PropTypes.object.isRequired,
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(({ vms, settings }) => ({
|
||||||
|
vms,
|
||||||
|
settings
|
||||||
|
}))(VmsListing)
|
|
@ -0,0 +1,84 @@
|
||||||
|
/*
|
||||||
|
* This file is part of Cockpit.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2017 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 from 'react'
|
||||||
|
import { gettext as _ } from 'cockpit'
|
||||||
|
|
||||||
|
import { ListingRow } from '../../../../lib/cockpit-components-listing.jsx'
|
||||||
|
import type { Vm } from '../types.jsx'
|
||||||
|
import { getPairs } from '../utils.jsx'
|
||||||
|
|
||||||
|
const NODE_LABEL = 'kubevirt.io/nodeName'
|
||||||
|
function getNodeName(vm: Vm) {
|
||||||
|
return (vm.metadata.labels && vm.metadata.labels[NODE_LABEL]) || null
|
||||||
|
}
|
||||||
|
|
||||||
|
const GeneralTab = ({ vm }: { vm: Vm }) => {
|
||||||
|
const nodeName = getNodeName(vm)
|
||||||
|
const nodeLink = nodeName ? (<a href={`#/nodes/${nodeName}`}>{nodeName}</a>) : '-'
|
||||||
|
return (
|
||||||
|
<div className="row">
|
||||||
|
<div className="col-xs-12 col-md-6">
|
||||||
|
<dl>
|
||||||
|
<dt>{_("Node")}</dt>
|
||||||
|
<dd className="vm-node">{nodeLink}</dd>
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
<div className="col-xs-12 col-md-6">
|
||||||
|
<dl className="full-width">
|
||||||
|
<dt>{_("Labels")}</dt>
|
||||||
|
{vm.metadata.labels && getPairs(vm.metadata.labels).map(pair => {
|
||||||
|
const printablePair = pair.key + '=' + pair.value
|
||||||
|
return (<dd key={printablePair}>{printablePair}</dd>)
|
||||||
|
})}
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const VmsListingRow = ({ vm }: { vm: Vm }) => {
|
||||||
|
const node = (vm.metadata.labels && vm.metadata.labels[NODE_LABEL]) || '-'
|
||||||
|
const phase = (vm.status && vm.status.phase) || _("n/a")
|
||||||
|
const generalTabRenderer = {
|
||||||
|
name: _("General"),
|
||||||
|
renderer: GeneralTab,
|
||||||
|
data: { vm },
|
||||||
|
presence: 'always',
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<ListingRow
|
||||||
|
rowId={`vm-${vm.metadata.name}`}
|
||||||
|
columns={[
|
||||||
|
{name: vm.metadata.name, 'header': true},
|
||||||
|
vm.metadata.namespace,
|
||||||
|
node,
|
||||||
|
phase // phases description https://github.com/kubevirt/kubevirt/blob/master/pkg/api/v1/types.go
|
||||||
|
]}
|
||||||
|
tabRenderers={[generalTabRenderer]}/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
VmsListingRow.propTypes = {
|
||||||
|
vm: React.PropTypes.object.isRequired
|
||||||
|
}
|
||||||
|
|
||||||
|
export default VmsListingRow
|
|
@ -0,0 +1,82 @@
|
||||||
|
/*
|
||||||
|
* This file is part of Cockpit.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2017 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import 'regenerator-runtime/runtime' // required for library initialization
|
||||||
|
import React from 'react'
|
||||||
|
import { createStore, applyMiddleware, compose } from 'redux'
|
||||||
|
import createSagaMiddleware from 'redux-saga'
|
||||||
|
import { Provider } from 'react-redux'
|
||||||
|
|
||||||
|
import reducers from './reducers.jsx'
|
||||||
|
import rootSaga from './sagas.jsx'
|
||||||
|
import * as actionCreators from './action-creators.jsx'
|
||||||
|
import VmsListing from './components/VmsListing.jsx'
|
||||||
|
|
||||||
|
const sagaMiddleware = createSagaMiddleware()
|
||||||
|
let reduxStore
|
||||||
|
|
||||||
|
function initReduxStore() {
|
||||||
|
const initialState = {
|
||||||
|
vms: []
|
||||||
|
}
|
||||||
|
const middleware = [ sagaMiddleware ]
|
||||||
|
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
|
||||||
|
const storeEnhancers = composeEnhancers(applyMiddleware(...middleware))
|
||||||
|
reduxStore = createStore(reducers, initialState, storeEnhancers)
|
||||||
|
}
|
||||||
|
|
||||||
|
function addKubeLoaderListener ($scope, kubeLoader, kubeSelect) {
|
||||||
|
// register load callback( callback, until )
|
||||||
|
kubeLoader.listen(function() {
|
||||||
|
const vms = kubeSelect().kind('VirtualMachine')
|
||||||
|
reduxStore.dispatch(actionCreators.setVms(Object.values(vms)))
|
||||||
|
}, $scope);
|
||||||
|
|
||||||
|
// enable watching( watched-entity-type, until )
|
||||||
|
kubeLoader.watch('VirtualMachine', $scope);
|
||||||
|
}
|
||||||
|
|
||||||
|
const VmsPlugin = () => (
|
||||||
|
<Provider store={reduxStore} >
|
||||||
|
<VmsListing />
|
||||||
|
</Provider>
|
||||||
|
)
|
||||||
|
|
||||||
|
function addScopeVarsToStore ($scope) {
|
||||||
|
$scope.$watch(
|
||||||
|
(scope => scope.settings),
|
||||||
|
(newSettings => reduxStore.dispatch(actionCreators.setSettings(newSettings))))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {$rootScope.Scope} $scope 'VirtualMachinesCtrl' controller scope
|
||||||
|
* @param {kubeLoader} kubeLoader
|
||||||
|
* @param {kubeSelect} kubeSelect
|
||||||
|
*/
|
||||||
|
function init($scope, kubeLoader, kubeSelect) {
|
||||||
|
initReduxStore()
|
||||||
|
sagaMiddleware.run(rootSaga)
|
||||||
|
addKubeLoaderListener($scope, kubeLoader, kubeSelect)
|
||||||
|
addScopeVarsToStore($scope)
|
||||||
|
const rootElement = document.querySelector('#kubernetes-virtual-machines-root')
|
||||||
|
React.render(<VmsPlugin />, rootElement)
|
||||||
|
}
|
||||||
|
|
||||||
|
export { init };
|
|
@ -0,0 +1,44 @@
|
||||||
|
/*
|
||||||
|
* This file is part of Cockpit.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2017 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { combineReducers } from 'redux'
|
||||||
|
|
||||||
|
import * as actionTypes from './action-types.jsx'
|
||||||
|
|
||||||
|
const createReducer = (initialState, actionHandlerMap) => (state = initialState, action) => {
|
||||||
|
if (actionHandlerMap[action.type]) {
|
||||||
|
return actionHandlerMap[action.type](state, action)
|
||||||
|
}
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
|
||||||
|
const vmsReducer = createReducer([], {
|
||||||
|
[actionTypes.SET_VMS]: (state = [], { payload }) => payload ? payload : []
|
||||||
|
})
|
||||||
|
|
||||||
|
const settingsReducer = createReducer([], {
|
||||||
|
[actionTypes.SET_SETTINGS]: (state = [], { payload }) => payload ? payload : {}
|
||||||
|
})
|
||||||
|
|
||||||
|
const rootReducer = combineReducers({
|
||||||
|
vms: vmsReducer,
|
||||||
|
settings: settingsReducer
|
||||||
|
})
|
||||||
|
|
||||||
|
export default rootReducer
|
|
@ -0,0 +1,23 @@
|
||||||
|
/*
|
||||||
|
* This file is part of Cockpit.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2017 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function* rootSaga() {
|
||||||
|
}
|
||||||
|
|
||||||
|
export default rootSaga;
|
|
@ -0,0 +1,62 @@
|
||||||
|
/*
|
||||||
|
* This file is part of Cockpit.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2017 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
|
||||||
|
|
||||||
|
export type Vm = {
|
||||||
|
apiVersion: string,
|
||||||
|
kind: 'VirtualMachine',
|
||||||
|
metadata: {
|
||||||
|
clusterName: string,
|
||||||
|
creationTimestamp: string,
|
||||||
|
generation: number,
|
||||||
|
labels: {[string]: string},
|
||||||
|
name: string,
|
||||||
|
namespace: string,
|
||||||
|
resourceVersion: string,
|
||||||
|
selfLink: string,
|
||||||
|
uid: string
|
||||||
|
},
|
||||||
|
spec: {
|
||||||
|
domain: {
|
||||||
|
devices: {
|
||||||
|
console: Array<Object>,
|
||||||
|
disks: Array<Object>,
|
||||||
|
graphics: Array<Object>,
|
||||||
|
interfaces: Array<Object>,
|
||||||
|
video: Array<Object>,
|
||||||
|
[string]: any
|
||||||
|
},
|
||||||
|
memory: {
|
||||||
|
unit: string,
|
||||||
|
value: number
|
||||||
|
},
|
||||||
|
os: {
|
||||||
|
bootOrder?: mixed,
|
||||||
|
type: Object
|
||||||
|
},
|
||||||
|
type: string
|
||||||
|
}
|
||||||
|
},
|
||||||
|
status: ?{
|
||||||
|
graphics?: mixed,
|
||||||
|
nodeName: string,
|
||||||
|
phase: ?string
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
/*
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {Array<{key: *, value: *}>} all own enumerable key-value pairs
|
||||||
|
*/
|
||||||
|
export function getPairs(object) {
|
||||||
|
return Object.keys(object).map(key => ({
|
||||||
|
key,
|
||||||
|
value: object[key]
|
||||||
|
}))
|
||||||
|
}
|
|
@ -23,7 +23,8 @@
|
||||||
width: unit(@sidebar-width-xl, px) !important;
|
width: unit(@sidebar-width-xl, px) !important;
|
||||||
}
|
}
|
||||||
.list-group-item-value {
|
.list-group-item-value {
|
||||||
max-width: unit(@sidebar-width-xl - 80, px) !important;
|
max-width: unit(@sidebar-width-xl - 60, px) !important;
|
||||||
|
padding-right: 10px !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
@import "/variables.less";
|
@import "/variables.less";
|
||||||
|
|
||||||
@sidebar-width-sm: 40;
|
@sidebar-width-sm: 40;
|
||||||
@sidebar-width-md: 115;
|
@sidebar-width-md: 145;
|
||||||
@sidebar-width-xl: 180;
|
@sidebar-width-xl: 185;
|
||||||
|
|
||||||
@command-bg-color: #eee;
|
@command-bg-color: #eee;
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
<filter-bar class="content-filter">
|
||||||
|
</filter-bar>
|
||||||
|
|
||||||
|
<div id="kubernetes-virtual-machines-root"></div>
|
|
@ -61,6 +61,8 @@ class TestOpenshift(MachineCase, OpenshiftCommonTests):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(TestOpenshift, self).setUp()
|
super(TestOpenshift, self).setUp()
|
||||||
|
|
||||||
|
print "======= done setUp"
|
||||||
|
|
||||||
self.openshift = self.machines['openshift']
|
self.openshift = self.machines['openshift']
|
||||||
self.openshift.upload(["verify/files/mock-app-openshift.json"], "/tmp")
|
self.openshift.upload(["verify/files/mock-app-openshift.json"], "/tmp")
|
||||||
self.kubeconfig = os.path.join(self.tmpdir, "config")
|
self.kubeconfig = os.path.join(self.tmpdir, "config")
|
||||||
|
@ -406,6 +408,70 @@ LABEL io.projectatomic.nulecule.atomicappversion="0.1.11" \
|
||||||
wait(lambda: "virt-launcher-testvm" in o.execute("oc get pods"))
|
wait(lambda: "virt-launcher-testvm" in o.execute("oc get pods"))
|
||||||
wait(lambda: "Running" in o.execute("oc get pods | grep virt-launcher-testvm"))
|
wait(lambda: "Running" in o.execute("oc get pods | grep virt-launcher-testvm"))
|
||||||
|
|
||||||
|
def testVirtualMachinesTabPresent(self):
|
||||||
|
self.bootstrapKubevirt()
|
||||||
|
|
||||||
|
b = self.browser
|
||||||
|
self.login_and_go("/kubernetes")
|
||||||
|
b.wait_present("#kubernetes-navigation")
|
||||||
|
self.assertTrue(b.is_present("#vms-menu-link"))
|
||||||
|
|
||||||
|
def testVirtualMachinesListed(self):
|
||||||
|
self.bootstrapKubevirt()
|
||||||
|
|
||||||
|
o = self.openshift
|
||||||
|
# *.yaml files comes from kubevirt distribution, see tarball extraction in bots/images/script/lib/kubevirt.setup
|
||||||
|
o.execute("oc create -f /kubevirt/iscsi-demo-target.yaml") # disk for test VM
|
||||||
|
o.execute("oc create -f /kubevirt/vm.yaml") # let's create a VM
|
||||||
|
wait(lambda: "testvm" in o.execute("oc get vm"), msg='Deployment of virtual machine "testvm" failed.')
|
||||||
|
|
||||||
|
b = self.browser
|
||||||
|
self.login_and_go("/kubernetes")
|
||||||
|
b.wait_present("#vms-menu-link")
|
||||||
|
b.click("#vms-menu-link")
|
||||||
|
b.wait_present("tr[data-row-id='vm-testvm']")
|
||||||
|
self.assertTrue(b.text("tr[data-row-id='vm-testvm'] th") == 'testvm')
|
||||||
|
|
||||||
|
def testVirtualMachineRowExpansion(self):
|
||||||
|
self.bootstrapKubevirt()
|
||||||
|
|
||||||
|
o = self.openshift
|
||||||
|
o.execute("oc create -f /kubevirt/iscsi-demo-target.yaml") # disk for test VM
|
||||||
|
o.execute("oc create -f /kubevirt/vm.yaml") # let's create a VM
|
||||||
|
wait(lambda: "testvm" in o.execute("oc get vm"), msg='Deployment of virtual machine "testvm" failed.')
|
||||||
|
|
||||||
|
b = self.browser
|
||||||
|
self.login_and_go("/kubernetes")
|
||||||
|
b.wait_present("#vms-menu-link")
|
||||||
|
b.click("#vms-menu-link")
|
||||||
|
b.wait_present("tr[data-row-id='vm-testvm']")
|
||||||
|
b.click("tr[data-row-id='vm-testvm']")
|
||||||
|
b.wait_present("tr[data-row-id='vm-testvm'] + tr.listing-ct-panel")
|
||||||
|
self.assertTrue('Node' in b.text("tr[data-row-id='vm-testvm'] + tr.listing-ct-panel"))
|
||||||
|
|
||||||
|
def testVirtualMachineLinkToNode(self):
|
||||||
|
print 'start'
|
||||||
|
self.bootstrapKubevirt()
|
||||||
|
print 'kubevirt installed'
|
||||||
|
|
||||||
|
o = self.openshift
|
||||||
|
o.execute("oc create -f /kubevirt/iscsi-demo-target.yaml") # disk for test VM
|
||||||
|
o.execute("oc create -f /kubevirt/vm.yaml") # let's create a VM
|
||||||
|
wait(lambda: "testvm" in o.execute("oc get vm"), msg='Deployment of virtual machine "testvm" failed.')
|
||||||
|
|
||||||
|
b = self.browser
|
||||||
|
self.login_and_go("/kubernetes")
|
||||||
|
b.wait_present("#vms-menu-link")
|
||||||
|
b.click("#vms-menu-link")
|
||||||
|
b.wait_present("tr[data-row-id='vm-testvm']")
|
||||||
|
b.click("tr[data-row-id='vm-testvm']")
|
||||||
|
b.wait_present("tr[data-row-id='vm-testvm'] + tr.listing-ct-panel .vm-node")
|
||||||
|
node_not_assigned = '-'
|
||||||
|
node_name = b.text("tr[data-row-id='vm-testvm'] + tr.listing-ct-panel .vm-node").strip()
|
||||||
|
if node_name != node_not_assigned:
|
||||||
|
b.click("tr[data-row-id='vm-testvm'] + tr.listing-ct-panel .vm-node a")
|
||||||
|
b.wait_js_cond('window.location.hash === "#/nodes/%s"' % (node_name,))
|
||||||
|
|
||||||
|
|
||||||
@skipImage("Kubernetes not packaged", "debian-stable", "debian-testing", "ubuntu-1604", "ubuntu-stable", "fedora-i386")
|
@skipImage("Kubernetes not packaged", "debian-stable", "debian-testing", "ubuntu-1604", "ubuntu-stable", "fedora-i386")
|
||||||
@skipImage("No cockpit-kubernetes packaged", "continuous-atomic", "fedora-atomic", "rhel-atomic")
|
@skipImage("No cockpit-kubernetes packaged", "continuous-atomic", "fedora-atomic", "rhel-atomic")
|
||||||
|
|
|
@ -356,7 +356,7 @@ var plugins = [
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
new copy(info.files),
|
new copy(info.files),
|
||||||
new extract("[name].css")
|
new extract("[name].css"),
|
||||||
];
|
];
|
||||||
|
|
||||||
var output = {
|
var output = {
|
||||||
|
|
Loading…
Reference in New Issue