Added node details page to the plugin

Added details page files to the plugin.

Change-Id: I17e4c749038dfa9f4a8423796186c133d248fba0
Co-Authored-By: Peter Piela <ppiela@cray.com>
This commit is contained in:
Elizabeth Elwell 2016-02-04 13:32:24 +00:00
parent 5f8fc8b7a0
commit f230e96da5
7 changed files with 418 additions and 1 deletions

View File

@ -0,0 +1,11 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Node Details" %}{% endblock %}
{% block page_header %}
<hz-page-header header="{% trans "Node Details" %}"></hz-page-header>
{% endblock %}
{% block main %}
<ng-include src="'{{ STATIC_URL }}dashboard/admin/ironic/node-details/node-details.html'"></ng-include>
{% endblock %}

View File

@ -110,7 +110,7 @@
function putNodeInMaintenanceMode(uuid, reason) { function putNodeInMaintenanceMode(uuid, reason) {
var data = { var data = {
maint_reason: (reason ? reason : gettext('No maintenance reason given.')) maint_reason: (reason ? reason : gettext("No maintenance reason given."))
}; };
return apiService.patch('/api/ironic/nodes/' + uuid + '/maintenance', data).error(function() { return apiService.patch('/api/ironic/nodes/' + uuid + '/maintenance', data).error(function() {
toastService.add('error', toastService.add('error',

View File

@ -0,0 +1,88 @@
/*
* Copyright 2015 Hewlett Packard Enterprise Development Company LP
* Copyright 2016 Cray Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
(function () {
'use strict';
angular
.module('horizon.dashboard.admin.ironic')
.controller('horizon.dashboard.admin.ironic.NodeDetailsController',
IronicNodeDetailsController);
IronicNodeDetailsController.$inject = [
'$location',
'horizon.app.core.openstack-service-api.ironic',
'horizon.dashboard.admin.ironic.actions',
'horizon.dashboard.admin.basePath'
];
function IronicNodeDetailsController($location, ironic, actions, basePath) {
var ctrl = this;
var path = basePath + 'ironic/node-details/sections/';
ctrl.actions = actions;
ctrl.sections = [
{
heading: gettext('Overview'),
templateUrl: path + 'overview.html'
},
{
heading: gettext('Configuration'),
templateUrl: path + 'configuration.html'
}
];
ctrl.basePath = basePath;
ctrl.init = init;
///////////////
function init() {
// Fetch the Node ID from the URL.
var pattern = /(.*\/admin\/ironic\/)(.+)\/(detail)?/;
var uuid = $location.absUrl().match(pattern)[2];
retrieveNode(uuid).then(function () {
retrievePorts(uuid);
});
}
function retrieveNode(uuid) {
return ironic.getNode(uuid).then(function (response) {
var node = response.data;
ctrl.node = node;
if (node['target_power_state']) {
actions.updateNode(node);
}
});
}
function retrievePorts(node_id) {
ironic.getPortsWithNode(node_id).then(function (response) {
ctrl.ports = response.data.items;
// Ensure that the vif_port_id property exists for all ports
angular.forEach(ctrl.ports,
function(port, key) {
if (angular.isUndefined(port.extra.vif_port_id)) {
port.extra.vif_port_id = "";
}
});
});
}
}
})();

View File

@ -0,0 +1,103 @@
/*
* Copyright 2015 Hewlett Packard Enterprise Development Company LP
* Copyright 2016 Cray Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
(function () {
'use strict';
describe('horizon.dashboard.admin.ironic.node-details', function () {
var ctrl, $q;
var nodeUuid = "0123abcd-0123-4567-abcd-0123456789ab";
var nodeName = "herp";
var numPorts = 2;
function portUuid(nodeUuid, index) {
return '' + index + index + nodeUuid.substring(2);
}
function createPort(nodeUuid, index, extra) {
return {uuid: portUuid(nodeUuid, index), extra: extra};
}
function createNode(name, uuid) {
return {name: name, uuid: uuid};
}
var ironicAPI = {
getNode: function (uuid) {
var node = createNode(nodeName, uuid);
return $q.when({data: node});
},
getPortsWithNode: function (uuid) {
var ports = [];
for (var i = 0; i < numPorts; i++) {
ports.push(createPort(uuid, i, {}));
}
return $q.when({data: {items: ports}});
}
};
beforeEach(module('horizon.dashboard.admin.ironic'));
beforeEach(module('horizon.app.core.openstack-service-api', function($provide) {
$provide.value('horizon.app.core.openstack-service-api.ironic', ironicAPI);
}));
beforeEach(inject(function ($injector, _$rootScope_, _$location_) {
var scope = _$rootScope_.$new();
$q = $injector.get('$q');
var controller = $injector.get('$controller');
var $location = _$location_;
$location.path('/admin/ironic/' + nodeUuid + '/');
ctrl = controller('horizon.dashboard.admin.ironic.NodeDetailsController', {
$scope: scope,
$location: $location,
'horizon.dashboard.admin.ironic.actions': {},
'horizon.dashboard.admin.basePath': '/static'});
ctrl.init();
scope.$apply();
}));
it('should be defined', function () {
expect(ctrl).toBeDefined();
});
it('should have a basePath', function () {
expect(ctrl.basePath).toBeDefined();
expect(ctrl.basePath).toEqual('/static');
});
it('should have a node', function () {
expect(ctrl.node).toBeDefined();
expect(ctrl.node).toEqual(createNode(nodeName, nodeUuid))
});
it('should have ports', function () {
expect(ctrl.ports).toBeDefined();
expect(ctrl.ports.length).toEqual(numPorts);
var ports = [];
for (var i = 0; i < ctrl.ports.length; i++) {
ports.push(createPort(ctrl.node.uuid, i, {vif_port_id: ''}));
}
expect(ctrl.ports).toEqual(ports);
});
});
})();

View File

@ -0,0 +1,45 @@
<div class="detail-page"
ng-controller="horizon.dashboard.admin.ironic.NodeDetailsController as ctrl"
ng-init="ctrl.init()">
<div class="pull-right">
<action-list dropdown>
<action button-type="split-button"
action-classes="'btn btn-default btn-sm'"
callback="ctrl.actions.powerOn"
item="ctrl.node"
disabled="ctrl.node['power_state']!=='power off'">
{$ 'Power on' | translate $}
</action>
<menu>
<action button-type="menu-item"
callback="ctrl.actions.powerOff"
item="ctrl.node"
disabled="ctrl.node['power_state']!=='power on'">
{$ 'Power off' | translate $}
</action>
<action button-type="menu-item"
callback="ctrl.actions.promptForPutNodeInMaintenanceMode"
item="ctrl.node"
disabled="ctrl.node['maintenance']">
{$ 'Maintenance on' | translate $}
</action>
<action button-type="menu-item"
callback="ctrl.actions.removeNodeFromMaintenanceMode"
item="ctrl.node"
disabled="!ctrl.node['maintenance']">
{$ 'Maintenance off' | translate $}
</action>
</menu>
</action-list>
</div>
<div class="clearfix"></div>
<tabset>
<tab ng-repeat="section in ctrl.sections"
heading="{$ section.heading $}">
<ng-include src="section.templateUrl"></ng-include>
</tab>
</tabset>
</div>

View File

@ -0,0 +1,120 @@
<div class="row">
<!-- General -->
<div class="col-md-6 status detail">
<h4 translate>General</h4>
<hr class="header_rule">
<dl class="dl-horizontal">
<dt translate>Node ID</dt>
<dd>{$ ctrl.node['uuid'] $}</dd>
<dt translate>Chassis ID</dt>
<dd>{$ ctrl.node['chassis_uuid'] $}</dd>
<dt translate>Created At</dt>
<dd>{$ ctrl.node['created_at'] $}</dd>
<dt translate>Extra</dt>
<dd>{$ ctrl.node['extra'] $}</dd>
</dl>
</div>
<!-- Ports -->
<div class="col-md-6 status detail">
<h4 translate>Ports</h4>
<hr class="header_rule">
<dl class="dl-horizontal">
<dt ng-repeat-start="port in ctrl.ports">
{$ 'MAC ' + (1 + $index) $}</dt>
<dd ng-if="port.extra.vif_port_id">
<a href="/admin/networks/ports/{$ port.extra.vif_port_id $}/detail">
{$ port.address $}
</a>
</dd>
<dd ng-if="!port.extra.vif_port_id">
{$ port.address $}
</dd>
<p ng-repeat-end></p>
</dl>
</div>
</div>
<div class="row">
<!-- Properties -->
<div class="col-md-6 status detail">
<h4 translate>Properties</h4>
<hr class="header_rule">
<dl class="dl-horizontal">
<dt translate>Memory</dt>
<dd>{$ ctrl.node['properties']['memory_mb'] + ' MB' $}</dd>
<dt translate>CPU Arch</dt>
<dd>{$ ctrl.node['properties']['cpu_arch'] $}</dd>
<dt translate>Local GB</dt>
<dd>{$ ctrl.node['properties']['local_gb'] $}</dd>
<dt translate>CPUs</dt>
<dd>{$ ctrl.node['properties']['cpus'] $}</dd>
</dl>
</div>
<!-- Driver Info -->
<div class="col-md-6 status detail">
<h4 translate>Driver Info</h4>
<hr class="header_rule">
<div ng-switch="ctrl.node['driver']">
<dl ng-switch-when="pxe_ssh" class="dl-horizontal">
<dt translate>Driver</dt>
<dd>{$ ctrl.node['driver'] $}</dd>
<dt translate>SSH Port</dt>
<dd>{$ ctrl.node['driver_info']['ssh_port'] $}</dd>
<dt translate>SSH Username</dt>
<dd>{$ ctrl.node['driver_info']['ssh_username'] $}</dd>
<dt translate>Deploy Kernel</dt>
<dd>
<a href="/admin/images/{$ ctrl.node['driver_info']['deploy_kernel'] $}/detail">
{$ ctrl.node['driver_info']['deploy_kernel'] $}
</a>
</dd>
<dt translate>Deploy Ramdisk</dt>
<dd>
<a href="/admin/images/{$ ctrl.node['driver_info']['deploy_ramdisk'] $}/detail">
{$ ctrl.node['driver_info']['deploy_ramdisk'] $}
</a>
</dd>
</dl>
<dl ng-switch-default class="dl-horizontal">
<dt ng-repeat-start="(id, value) in ctrl.node['driver_info']">{$ id $}</dt>
<dd ng-repeat-end>{$ value $}</dd>
</dl>
</div>
</div>
</div>
<div class="row">
<!-- Capabilities -->
<div class="col-md-6 status detail">
<h4 translate>Capabilities</h4>
<hr class="header_rule">
<dl class="dl-horizontal">
<dd>{$ ctrl.node['capabilities'] $}</dd>
</dl>
</div>
<!-- Instance Info -->
<div class="col-md-6 status detail">
<h4 translate>Instance Info</h4>
<hr class="header_rule">
<dl class="dl-horizontal">
<dt translate>Instance Name</dt>
<dd>{$ ctrl.node['instance_info']['display_name'] $}</dd>
<dt translate>Ramdisk</dt>
<dd>
<a href="/admin/images/{$ ctrl.node['instance_info']['ramdisk'] $}/detail">
{$ ctrl.node['instance_info']['ramdisk'] $}
</a>
</dd>
<dt translate>Kernel</dt>
<dd>
<a href="/admin/images/{$ ctrl.node['instance_info']['kernel'] $}/detail">
{$ ctrl.node['instance_info']['kernel'] $}
</a>
</dd>
</dl>
</div>
</div>

View File

@ -0,0 +1,50 @@
<div class="row">
<!-- General -->
<div class="col-md-6 status detail">
<h4 translate>General</h4>
<hr class="header_rule">
<dl class="dl-horizontal">
<dt translate>Name</dt>
<dd>{$ ctrl.node['name'] $}</dd>
<dt translate>Maintenance</dt>
<dd>{$ ctrl.node['maintenance'] ? 'True' : 'False' $}</dd>
<dt translate>Maintenance Reason</dt>
<dd>{$ ctrl.node['maintenance_reason'] $}</dd>
<dt translate>Inspection Started At</dt>
<dd>{$ ctrl.node['inspection_started_at'] $}</dd>
<dt translate>Inspection Finished At</dt>
<dd>{$ ctrl.node['inspection_finished_at'] $}</dd>
<dt translate>Reservation</dt>
<dd>{$ ctrl.node['reservation'] $}</dd>
<dt translate>Console Enabled</dt>
<dd>{$ ctrl.node['console_enabled'] ? 'True' : 'False' $}</dd>
</dl>
</div>
<!-- Provisioning Status -->
<div class="col-md-6 status detail">
<h4 translate>Provisioning Status</h4>
<hr class="header_rule">
<dl class="dl-horizontal">
<dt translate>Instance ID</dt>
<dd>
<a href="/admin/instances/{$ ctrl.node['instance_uuid'] $}/detail">
{$ ctrl.node['instance_uuid'] $}
</a>
</dd>
<dt translate>Power State</dt>
<dd ng-class="{'running': ctrl.node['target_power_state']}">{$ ctrl.node['power_state'] $}</dd>
<dt translate>Target Power State</dt>
<dd>{$ ctrl.node['target_power_state'] $}</dd>
<dt translate>Provision State</dt>
<dd>{$ ctrl.node['provision_state'] $}</dd>
<dt translate>Target Provision State</dt>
<dd>{$ ctrl.node['target_provision_state'] $}</dd>
<dt translate>Last Error</dt>
<dd>{$ ctrl.node['last_error'] $}</dd>
<dt translate>Updated At</dt>
<dd>{$ ctrl.node['updated_at'] $}</dd>
</dl>
</div>
</div>