Created common navigation header

This patch adds the nodes/drivers navigation header in
accordance to the approved UI specification. The state
chart had to be adjusted to make room for the driver
section, and a new common ironic root state has
been introduced to ensure that the menu remains
constant.

Change-Id: I57fe522a067d515aa5253888ef6c121c4b95bd61
This commit is contained in:
Michael Krotscheck 2016-03-21 07:47:26 -07:00
parent 7a3e960b37
commit a4798afe90
12 changed files with 100 additions and 62 deletions

View File

@ -1,4 +1,3 @@
@import "./bootstrap_variables";
@import "../../bower_components/bootstrap-sass/assets/stylesheets/bootstrap";
@import "./bootstrap_mixins";
@ -21,6 +20,17 @@
@include box-shadow($shadow);
}
/**
* Button default should have button primary active state.
*/
.btn-default {
&.active, &.active:active, &.active:focus, &.active:hover {
background-color: $brand-primary;
color: $btn-primary-color;
border-color: $btn-primary-border;
}
}
/**
* Custom button gradient styles.
*/

View File

@ -1,3 +1,4 @@
<!DOCTYPE html>
<html ng-app="ironic">
<head>
<title>OpenStack Bare Metal</title>

View File

@ -48,7 +48,7 @@ angular.module('ironic').controller('ConfigurationController',
vm.select = function(configuration) {
$$selectedConfiguration.set(configuration).$promise.then(
function() {
$state.go('root.ironic');
$state.go('root.ironic.nodes');
}
);
};

View File

@ -60,4 +60,17 @@ angular.module('ironic').controller('NodeActionController',
}
}).result;
};
/**
* Enroll a new node.
*
* @return {Promise} A promise that will resolve true if a node has been added.
*/
vm.enroll = function() {
return $uibModal.open({
templateUrl: 'view/ironic/enroll/index.html',
controller: 'EnrollModalController as ctrl',
backdrop: 'static'
}).result;
};
});

View File

@ -49,13 +49,5 @@ angular.module('ironic')
$log.info('Set power state on ' + node.uuid + ' to ' + stateName);
};
vm.enroll = function() {
$uibModal.open({
templateUrl: 'view/ironic/enroll/index.html',
controller: 'EnrollModalController as ctrl',
backdrop: 'static'
}).result.then(vm.loadNodes);
};
vm.loadNodes();
});

View File

@ -25,7 +25,7 @@ angular.module('ironic', ['ui.router', 'ui.bootstrap', 'ironic.util', 'ironic.ap
'use strict';
// Default UI route
$urlRouterProvider.otherwise('/ironic');
$urlRouterProvider.otherwise('/ironic/nodes');
// Enable all of our configuration detection methods
$$configurationProvider.$enableDefault(true);
@ -38,18 +38,18 @@ angular.module('ironic', ['ui.router', 'ui.bootstrap', 'ironic.util', 'ironic.ap
.state('root', {
abstract: true,
url: '',
templateUrl: 'view/ironic/index.html'
templateUrl: 'view/ironic/root.html'
})
.state('root.ironic', {
abstract: true,
url: '/ironic',
views: {
header: {
templateUrl: 'view/ironic/header.html',
controller: 'HeaderController as headerCtrl'
},
main: {
templateUrl: 'view/ironic/node_list.html',
controller: 'NodeListController as nodeListCtrl'
root: {
templateUrl: 'view/ironic/ironic.html'
}
},
resolve: {
@ -72,8 +72,13 @@ angular.module('ironic', ['ui.router', 'ui.bootstrap', 'ironic.util', 'ironic.ap
}
})
.state('root.ironic.nodes', {
abstract: true,
url: '/nodes'
url: '/nodes',
views: {
'main@root.ironic': {
templateUrl: 'view/ironic/node_list.html',
controller: 'NodeListController as nodeListCtrl'
}
}
})
.state('root.ironic.nodes.detail', {
abstract: true,
@ -84,7 +89,7 @@ angular.module('ironic', ['ui.router', 'ui.bootstrap', 'ironic.util', 'ironic.ap
}
},
views: {
'main@root': {
'main@root.ironic': {
templateUrl: 'view/ironic/detail.html',
controller: 'NodeDetailController as nodeCtrl'
}
@ -104,13 +109,21 @@ angular.module('ironic', ['ui.router', 'ui.bootstrap', 'ironic.util', 'ironic.ap
templateUrl: 'view/ironic/detail_driver.html',
controller: 'NodeDetailDriverController as driverCtrl'
})
.state('root.ironic.drivers', {
url: '/drivers',
views: {
'main@root.ironic': {
templateUrl: 'view/ironic/driver_list.html'
}
}
})
.state('root.config', {
url: '/config',
views: {
header: {
templateUrl: 'view/ironic/config_header.html'
},
main: {
root: {
templateUrl: 'view/ironic/config.html',
controller: 'ConfigurationController as ctrl'
}
@ -125,7 +138,7 @@ angular.module('ironic', ['ui.router', 'ui.bootstrap', 'ironic.util', 'ironic.ap
if (reason === 'no_config') {
$state.go('root.config');
} else {
$state.go('root.ironic');
$state.go('root.ironic.nodes');
}
});
$rootScope.$on('$destroy', listener);

View File

@ -0,0 +1,10 @@
<div class="row">
<div class="col-xs-12">
<h1>Driver List</h1>
</div>
</div>
<div class="row">
<div class="col-xs-12">
Not yet implemented.
</div>
</div>

View File

@ -0,0 +1,22 @@
<div class="row" ng-controller="NodeActionController as ctrl">
<div class="col-xs-6">
<div class="btn-group btn-group-lg">
<button type="button"
active-path="\/ironic\/nodes\/?.*"
ui-sref="root.ironic.nodes"
class="btn btn-default">Nodes</button>
<button type="button"
ui-sref="root.ironic.drivers"
active-path="\/ironic\/drivers\/?.*"
class="btn btn-default">Drivers</button>
</div>
</div>
<div class="col-xs-6">
<button ng-click="ctrl.enroll()"
class="btn btn-primary btn-lg pull-right">
<i class="fa fa-plus"></i> Node
</button>
</div>
</div>
<div ui-view="main">
</div>

View File

@ -1,9 +1,5 @@
<div class="row">
<div class="col-xs-12">
<button ng-click="nodeListCtrl.enroll()" class="btn btn-default pull-right-bottom">
<i class="fa fa-gear"></i>
Enroll Node
</button>
<h1>Nodes</h1>
</div>
</div>

View File

@ -2,7 +2,7 @@
<div class="container-fluid" ui-view="header"></div>
</nav>
<div class="container-fluid" ui-view="main"></div>
<div class="container-fluid" ui-view="root"></div>
<nav class="navbar navbar-default navbar-fixed-bottom">
<div class="container-fluid" ui-view="footer"></div>

View File

@ -113,4 +113,22 @@ describe('Unit: Ironic-webclient NodeActionController',
}));
});
describe('$scope.enroll', function() {
var enrollInjectionProperties = angular.copy(mockInjectionProperties);
enrollInjectionProperties.$uibModal = {
open: function() {
}
};
it('should open a modal',
inject(function($q) {
var spy = spyOn(enrollInjectionProperties.$uibModal, 'open').and.callFake(function() {
return {result: $q.resolve({})};
});
var controller = $controller('NodeActionController', enrollInjectionProperties);
controller.enroll();
expect(spy.calls.count()).toBe(1);
}));
});
});

View File

@ -106,41 +106,4 @@ describe('Unit: Ironic-webclient node list controller',
expect(controller.nodes).toBeFalsy();
});
});
describe('$scope.enroll', function() {
it('should open a modal',
inject(function($q) {
var spy = spyOn(mockInjectionProperties.$uibModal, 'open').and.callFake(function() {
return {result: $q.resolve({})};
});
var controller = $controller('NodeListController', mockInjectionProperties);
controller.enroll();
$httpBackend.flush();
expect(spy.calls.count()).toBe(1);
}));
it('should reload the node list if a new node was added.',
inject(function($q) {
// Set up a spy.
var spy = spyOn(mockInjectionProperties.$uibModal, 'open').and.callFake(function() {
return {result: $q.resolve({})};
});
// Initialize the controller.
var controller = $controller('NodeListController', mockInjectionProperties);
$httpBackend.flush();
// Setup expected calls
$httpBackend
.expectGET('http://ironic.example.com:1000/nodes')
.respond(200, []);
// Call Enroll
controller.enroll();
$httpBackend.flush();
expect(spy.calls.count()).toBe(1);
}));
});
});