Add routing and load balancer detail page

This adds the routing to allow navigating between angular pages as
well as the beginning of the load balancer detail page. The detail
page is very basic but is based on both the existing angular Images
detail page and the designs from invision. It includes two empty
tabs that will eventually hold the tables for Pools and Members.

Partially-Implements: blueprint horizon-lbaas-v2-ui
Change-Id: Icbf4396238036b20712ca643e635dd84fe66a86e
This commit is contained in:
Justin Pomeroy 2015-11-11 15:14:04 -06:00
parent 75fc881f7f
commit 3d99fc5544
15 changed files with 362 additions and 17 deletions

View File

@ -39,3 +39,20 @@ class LoadBalancers(generic.View):
tenant_id = request.user.project_id
result = neutronclient(request).list_loadbalancers(tenant_id=tenant_id)
return {'items': result.get('loadbalancers')}
@urls.register
class LoadBalancer(generic.View):
"""API for retrieving a single load balancer.
"""
url_regex = r'lbaas/loadbalancers/(?P<loadbalancer_id>[^/]+)/$'
@rest_utils.ajax()
def get(self, request, loadbalancer_id):
"""Get a specific load balancer.
http://localhost/api/lbaas/loadbalancers/cc758c90-3d98-4ea1-af44-aab405c9c915
"""
lb = neutronclient(request).show_loadbalancer(loadbalancer_id)
return lb.get('loadbalancer')

View File

@ -1,11 +1,12 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Load Balancers" %}{% endblock %}
{% block page_header %}{% endblock %}
{% block page_header %}
<hz-page-header header="'{% trans "Load Balancers" %}'"></hz-page-header>
{% endblock page_header %}
{% block ng_route_base %}
<base href="/"></base>
{% endblock %}
{% block main %}
<ng-include src="'{{ STATIC_URL }}dashboard/project/lbaasv2/loadbalancers/table.html'"></ng-include>
<div ng-view></div>
{% endblock %}

View File

@ -20,5 +20,5 @@ from neutron_lbaas_dashboard.dashboards.project.ngloadbalancersv2 import views
urlpatterns = patterns(
'neutron_lbaas_dashboard.dashboards.project.ngloadbalancersv2.views',
url(r'^$', views.IndexView.as_view(), name='index'),
url('', views.IndexView.as_view(), name='index'),
)

View File

@ -35,6 +35,7 @@ ADD_JS_FILES = [
'dashboard/project/lbaasv2/lbaasv2.module.js',
'dashboard/project/lbaasv2/loadbalancers/loadbalancers.module.js',
'dashboard/project/lbaasv2/loadbalancers/table.controller.js',
'dashboard/project/lbaasv2/loadbalancers/detail.controller.js',
'dashboard/project/lbaasv2/loadbalancers/filters.js',
]
@ -43,9 +44,10 @@ ADD_JS_SPEC_FILES = [
'dashboard/project/lbaasv2/lbaasv2.module.spec.js',
'dashboard/project/lbaasv2/loadbalancers/loadbalancers.module.spec.js',
'dashboard/project/lbaasv2/loadbalancers/table.controller.spec.js',
'dashboard/project/lbaasv2/loadbalancers/detail.controller.spec.js',
'dashboard/project/lbaasv2/loadbalancers/filters.spec.js',
]
#
# ADD_SCSS_FILES = [
#
# ]
ADD_SCSS_FILES = [
'dashboard/project/lbaasv2/lbaasv2.scss',
]

View File

@ -69,6 +69,7 @@ module.exports = function (config) {
// from jasmine.html
xstaticPath + 'jquery/data/jquery.js',
xstaticPath + 'angular/data/angular.js',
xstaticPath + 'angular/data/angular-route.js',
xstaticPath + 'angular/data/angular-mocks.js',
xstaticPath + 'angular/data/angular-cookies.js',
xstaticPath + 'angular_bootstrap/data/angular-bootstrap.js',

View File

@ -32,7 +32,8 @@
*/
function lbaasv2API(apiService, toastService) {
var service = {
getLoadBalancers: getLoadBalancers
getLoadBalancers: getLoadBalancers,
getLoadBalancer: getLoadBalancer
};
return service;
@ -56,5 +57,19 @@
});
}
/**
* @name horizon.app.core.openstack-service-api.lbaasv2.getLoadBalancer
* @description
* Get a single load balancer by ID
* @param {string} id
* Specifies the id of the load balancer to request.
*/
function getLoadBalancer(id) {
return apiService.get('/api/lbaas/loadbalancers/' + id)
.error(function () {
toastService.add('error', gettext('Unable to retrieve load balancer.'));
});
}
}
}());

View File

@ -41,6 +41,15 @@
"method": "get",
"path": "/api/lbaas/loadbalancers/",
"error": "Unable to retrieve load balancers."
},
{
"func": "getLoadBalancer",
"method": "get",
"path": "/api/lbaas/loadbalancers/1234",
"error": "Unable to retrieve load balancer.",
"testInput": [
'1234'
]
}
];

View File

@ -27,6 +27,31 @@
*/
angular
.module('horizon.dashboard.project.lbaasv2', [
'ngRoute',
'horizon.dashboard.project.lbaasv2.loadbalancers'
]);
])
.config(config);
config.$inject = [
'$windowProvider',
'$routeProvider',
'$locationProvider'
];
function config($windowProvider, $routeProvider, $locationProvider) {
$locationProvider.html5Mode({
enabled: true
}).hashPrefix('!');
var base = '/project/ngloadbalancersv2/';
var path = $windowProvider.$get().STATIC_URL + 'dashboard/project/lbaasv2/';
$routeProvider
.when(base, {
templateUrl: path + 'loadbalancers/table.html'
})
.when(base + 'detail/:loadbalancerId', {
templateUrl: path + 'loadbalancers/detail.html'
})
}
}());

View File

@ -22,4 +22,52 @@
});
});
describe('LBaaS v2 Module Config', function () {
var $routeProvider, $locationProvider, path;
beforeEach(function() {
// Create a dummy module so that we can test $routeProvider and $locationProvider calls
// in our actual config block.
angular.module('configTest', [])
.config(function(_$routeProvider_, _$locationProvider_, $windowProvider) {
$routeProvider = _$routeProvider_;
$locationProvider = _$locationProvider_;
path = $windowProvider.$get().STATIC_URL + 'dashboard/project/lbaasv2/';
spyOn($routeProvider, 'when').and.callThrough();
spyOn($locationProvider, 'html5Mode').and.callThrough();
});
module('ngRoute')
module('configTest');
module('horizon.dashboard.project.lbaasv2')
inject();
});
it('should use html5 mode', function () {
expect($locationProvider.html5Mode).toHaveBeenCalledWith({enabled: true});
});
it('should route URLs', function () {
var base = '/ngloadbalancersv2/';
var routes = [
[
base,
{
templateUrl: path + 'loadbalancers/table.html'
}
],
[
base + 'detail/:loadbalancerId',
{
templateUrl: path + 'loadbalancers/detail.html'
}
]
];
expect($routeProvider.when.calls.count()).toBe(2);
angular.forEach($routeProvider.when.calls.all(), function(call, i) {
expect(call.args).toEqual(routes[i]);
});
});
});
})();

View File

@ -0,0 +1,47 @@
/*
* Copyright 2015 IBM Corp.
*
* 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.
*/
@import "/app/app";
/*
* TODO(jpomero): This detail page styling should become common with the following patch:
* https://review.openstack.org/158881
*/
.detail-page {
position: relative;
p {
margin-top: 6px;
}
.detail-statuses {
margin-top: 8px;
margin-bottom: 12px;
div {
display: inline;
margin-right: 20px;
> b {
margin-right: 4px;
}
}
}
div.tab-pane > dl {
margin-top: 12px;
}
}

View File

@ -0,0 +1,58 @@
/*
* Copyright 2015 IBM Corp.
*
* 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.project.lbaasv2.loadbalancers')
.controller('LoadBalancerDetailController', LoadBalancerDetailController);
LoadBalancerDetailController.$inject = [
'horizon.dashboard.project.lbaasv2.loadbalancers.basePath',
'horizon.app.core.openstack-service-api.lbaasv2',
'$routeParams'
];
/**
* @ngdoc controller
* @name LoadBalancerDetailController
*
* @description
* Controller for the LBaaS v2 load balancers detail page.
*/
function LoadBalancerDetailController(basepath, api, $routeParams) {
var ctrl = this;
ctrl.loadbalancer = {};
ctrl.path = basepath;
var loadbalancerId = $routeParams.loadbalancerId;
init();
////////////////////////////////
function init() {
api.getLoadBalancer(loadbalancerId).success(success);
}
function success(response) {
ctrl.loadbalancer = response;
}
}
})();

View File

@ -0,0 +1,64 @@
/*
* Copyright 2015 IBM Corp.
*
* 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('LBaaS v2 Load Balancer Detail Controller', function() {
var controller, lbaasv2API, staticUrl, loadbalancer;
function fakeAPI() {
return {
success: function(callback) {
callback(loadbalancer);
}
};
}
///////////////////////
beforeEach(module('horizon.framework.util.http'));
beforeEach(module('horizon.framework.widgets.toast'));
beforeEach(module('horizon.app.core.openstack-service-api'));
beforeEach(module('horizon.dashboard.project.lbaasv2.loadbalancers'));
beforeEach(inject(function($injector) {
loadbalancer = { id: '1234' };
lbaasv2API = $injector.get('horizon.app.core.openstack-service-api.lbaasv2');
controller = $injector.get('$controller');
staticUrl = $injector.get('$window').STATIC_URL;
spyOn(lbaasv2API, 'getLoadBalancer').and.callFake(fakeAPI);
}));
function createController() {
return controller('LoadBalancerDetailController', {
api: lbaasv2API,
$routeParams: { loadbalancerId: '1234' }
});
}
it('should set path properly', function() {
var path = staticUrl + 'dashboard/project/lbaasv2/loadbalancers/';
expect(createController().path).toEqual(path);
});
it('should invoke lbaasv2 apis', function() {
createController();
expect(lbaasv2API.getLoadBalancer).toHaveBeenCalledWith('1234');
});
});
})();

View File

@ -0,0 +1,58 @@
<div class="content" ng-controller="LoadBalancerDetailController as ctrl">
<div class='page-header'>
<h1>{$ ::ctrl.loadbalancer.name $}</h1>
<p ng-if="::ctrl.loadbalancer.description">{$ ::ctrl.loadbalancer.description $}</p>
</div>
<div class="detail-page">
<div class="detail-statuses">
<div>
<strong translate>IP Address</strong>
{$ ::ctrl.loadbalancer.vip_address $}
</div>
<div>
<strong translate>Operating Status</strong>
{$ ::ctrl.loadbalancer.operating_status | operatingStatus $}
</div>
<div>
<strong translate>Provisioning Status</strong>
{$ ::ctrl.loadbalancer.provisioning_status | provisioningStatus $}
</div>
</div>
<tabset>
<tab heading="{$ 'Overview' | translate $}">
<dl class="dl-horizontal">
<div>
<dt translate>Provider</dt>
<dd>{$ ::ctrl.loadbalancer.provider $}</dd>
</div>
<div>
<dt translate>Admin State Up</dt>
<dd>{$ ::ctrl.loadbalancer.admin_state_up | yesno $}</dd>
</div>
<div>
<dt translate>Load Balancer ID</dt>
<dd>{$ ::ctrl.loadbalancer.id $}</dd>
</div>
<div>
<dt translate>Subnet ID</dt>
<dd>
<a ng-href="/project/networks/subnets/{$ ::ctrl.loadbalancer.vip_subnet_id $}/detail">{$ ::ctrl.loadbalancer.vip_subnet_id $}</a>
</dd>
</div>
<div>
<dt translate>Port ID</dt>
<dd>
<a ng-href="/project/networks/ports/{$ ::ctrl.loadbalancer.vip_port_id $}/detail">{$ ::ctrl.loadbalancer.vip_port_id $}</a>
</dd>
</div>
</dl>
</tab>
<tab heading="{$ 'Pools' | translate $}">
<!-- TODO(jpomero) ng-include the table of pools for this load balancer -->
</tab>
<tab heading="{$ 'Members' | translate $}">
<!-- TODO(jpomero) ng-include the table of members for this load balancer -->
</tab>
</tabset>
</div>
</div>

View File

@ -25,8 +25,6 @@
describe('LBaaS v2 Load Balancers Module Base Path', function () {
var basePath, staticUrl;
beforeEach(module('horizon.dashboard.project'));
beforeEach(module('horizon.dashboard.project.lbaasv2'));
beforeEach(module('horizon.dashboard.project.lbaasv2.loadbalancers'));
beforeEach(inject(function ($injector) {
basePath = $injector.get('horizon.dashboard.project.lbaasv2.loadbalancers.basePath');

View File

@ -1,3 +1,5 @@
<hz-page-header header="{$ 'Load Balancers' | translate $}"></hz-page-header>
<table ng-controller="loadBalancersTableController as table"
hz-table ng-cloak
st-table="table.items"
@ -63,12 +65,12 @@
hz-select="item">
</td>
<td class="expander">
<i class="fa fa-chevron-right"
<span class="fa fa-chevron-right"
hz-expand-detail
duration="200">
</i>
</span>
</td>
<td class="rsp-p1">{$ item.name $}</td>
<td class="rsp-p1"><a ng-href="/project/ngloadbalancersv2/detail/{$ item.id $}">{$ item.name $}</a></td>
<td class="rsp-p1">{$ item.description | noValue $}</td>
<td class="rsp-p1">{$ item.operating_status | operatingStatus $}</td>
<td class="rsp-p1">{$ item.provisioning_status | provisioningStatus $}</td>
@ -135,4 +137,4 @@
-->
<tfoot hz-table-footer items="table.items"></tfoot>
</table>
</table>