Trunks panel: details for trunks and subports
Add details link to the trunk panel to show details of the given trunk, like the subports list, with port_id and segmentation details. Co-Authored-By: Bence Romsics <bence.romsics@ericsson.com> Change-Id: I9000e2907a8f188d5e72e36818b7171f35158eb9 Partially-Implements: blueprint neutron-trunk-ui
This commit is contained in:
parent
08089b01c4
commit
da92f24bc2
@ -786,6 +786,13 @@ def trunk_delete(request, trunk_id):
|
||||
neutronclient(request).delete_trunk(trunk_id)
|
||||
|
||||
|
||||
@profiler.trace
|
||||
def trunk_show(request, trunk_id):
|
||||
LOG.debug("trunk_show(): trunk_id=%s", trunk_id)
|
||||
trunk = neutronclient(request).show_trunk(trunk_id).get('trunk')
|
||||
return Trunk(trunk)
|
||||
|
||||
|
||||
@profiler.trace
|
||||
def network_list(request, **params):
|
||||
LOG.debug("network_list(): params=%s", params)
|
||||
|
@ -146,6 +146,12 @@ class Trunk(generic.View):
|
||||
def delete(self, request, trunk_id):
|
||||
api.neutron.trunk_delete(request, trunk_id)
|
||||
|
||||
@rest_utils.ajax()
|
||||
def get(self, request, trunk_id):
|
||||
"""Get a specific trunk"""
|
||||
trunk = api.neutron.trunk_show(request, trunk_id)
|
||||
return trunk.to_dict()
|
||||
|
||||
|
||||
@urls.register
|
||||
class Trunks(generic.View):
|
||||
|
@ -21,4 +21,6 @@ from horizon.browsers.views import AngularIndexView
|
||||
title = _("Trunks")
|
||||
urlpatterns = [
|
||||
url(r'^$', AngularIndexView.as_view(title=title), name='index'),
|
||||
url(r'^(?P<trunk_id>[^/]+)/$', AngularIndexView.as_view(title=title),
|
||||
name='detail'),
|
||||
]
|
||||
|
@ -46,6 +46,7 @@
|
||||
getQosPolicy: getQosPolicy,
|
||||
getQoSPolicies: getQoSPolicies,
|
||||
getSubnets: getSubnets,
|
||||
getTrunk: getTrunk,
|
||||
getTrunks: getTrunks,
|
||||
updateProjectQuota: updateProjectQuota
|
||||
};
|
||||
@ -54,6 +55,15 @@
|
||||
|
||||
/////////////
|
||||
|
||||
// NOTE(bence romsics): Technically we replace ISO 8061 time stamps with
|
||||
// date objects. We do this because the date objects will stringify to human
|
||||
// readable datetimes in local time (ie. in the browser's time zone) when
|
||||
// displayed.
|
||||
function convertDatesHumanReadable(apidict) {
|
||||
apidict.created_at = new Date(apidict.created_at);
|
||||
apidict.updated_at = new Date(apidict.updated_at);
|
||||
}
|
||||
|
||||
// Neutron Services
|
||||
|
||||
/**
|
||||
@ -369,6 +379,27 @@
|
||||
|
||||
// Trunks
|
||||
|
||||
/**
|
||||
* @name getTrunk
|
||||
* @description
|
||||
* Get a single trunk by ID
|
||||
*
|
||||
* @param {string} id
|
||||
* Specifies the id of the trunk to request.
|
||||
*
|
||||
* @returns {Object} The result of the API call
|
||||
*/
|
||||
function getTrunk(id) {
|
||||
return apiService.get('/api/neutron/trunks/' + id + '/')
|
||||
.success(function(trunk) {
|
||||
convertDatesHumanReadable(trunk);
|
||||
})
|
||||
.error(function () {
|
||||
var msg = gettext('Unable to retrieve the trunk with id: %(id)s');
|
||||
toastService.add('error', interpolate(msg, { id : id }, true));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @name getTrunks
|
||||
* @description
|
||||
@ -379,6 +410,11 @@
|
||||
function getTrunks(params) {
|
||||
var config = params ? {'params' : params} : {};
|
||||
return apiService.get('/api/neutron/trunks/', config)
|
||||
.success(function(trunks) {
|
||||
trunks.items.forEach(function(trunk) {
|
||||
convertDatesHumanReadable(trunk);
|
||||
});
|
||||
})
|
||||
.error(function () {
|
||||
toastService.add('error', gettext('Unable to retrieve the trunks.'));
|
||||
});
|
||||
|
@ -132,6 +132,15 @@
|
||||
42
|
||||
]
|
||||
},
|
||||
{
|
||||
"func": "getTrunk",
|
||||
"method": "get",
|
||||
"path": "/api/neutron/trunks/42/",
|
||||
"error": "Unable to retrieve the trunk with id: 42",
|
||||
"testInput": [
|
||||
42
|
||||
]
|
||||
},
|
||||
{
|
||||
"func": "getTrunks",
|
||||
"method": "get",
|
||||
|
56
openstack_dashboard/static/app/core/trunks/details/details.module.js
Executable file
56
openstack_dashboard/static/app/core/trunks/details/details.module.js
Executable file
@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright 2017 Ericsson
|
||||
*
|
||||
* 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';
|
||||
|
||||
/**
|
||||
* @ngdoc overview
|
||||
* @ngname horizon.app.core.trunks.details
|
||||
*
|
||||
* @description
|
||||
* Provides details features for trunks.
|
||||
*/
|
||||
angular
|
||||
.module('horizon.app.core.trunks.details', [
|
||||
'horizon.framework.conf',
|
||||
'horizon.app.core'
|
||||
])
|
||||
.run(registerTrunkDetails);
|
||||
|
||||
registerTrunkDetails.$inject = [
|
||||
'horizon.app.core.trunks.basePath',
|
||||
'horizon.app.core.trunks.resourceType',
|
||||
'horizon.app.core.trunks.service',
|
||||
'horizon.framework.conf.resource-type-registry.service'
|
||||
];
|
||||
|
||||
function registerTrunkDetails(
|
||||
basePath,
|
||||
trunkResourceType,
|
||||
trunkService,
|
||||
registry
|
||||
) {
|
||||
registry.getResourceType(trunkResourceType)
|
||||
.setLoadFunction(trunkService.getTrunkPromise)
|
||||
.detailsViews.append({
|
||||
id: 'trunkDetailsOverview',
|
||||
name: gettext('Overview'),
|
||||
template: basePath + 'details/overview.html'
|
||||
});
|
||||
}
|
||||
|
||||
})();
|
72
openstack_dashboard/static/app/core/trunks/details/overview.controller.js
Executable file
72
openstack_dashboard/static/app/core/trunks/details/overview.controller.js
Executable file
@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright 2017 Ericsson
|
||||
*
|
||||
* 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.app.core.trunks')
|
||||
.controller('TrunkOverviewController', TrunkOverviewController);
|
||||
|
||||
TrunkOverviewController.$inject = [
|
||||
'horizon.app.core.trunks.resourceType',
|
||||
'horizon.framework.conf.resource-type-registry.service',
|
||||
'horizon.app.core.openstack-service-api.userSession',
|
||||
'$scope'
|
||||
];
|
||||
|
||||
function TrunkOverviewController(
|
||||
trunkResourceTypeCode,
|
||||
registry,
|
||||
userSession,
|
||||
$scope
|
||||
) {
|
||||
var ctrl = this;
|
||||
|
||||
ctrl.resourceType = registry.getResourceType(trunkResourceTypeCode);
|
||||
ctrl.tableConfig = {
|
||||
selectAll: false,
|
||||
expand: false,
|
||||
trackId: 'segmentation_id',
|
||||
/*
|
||||
* getTableColumns here won't work as that will give back the
|
||||
* columns for trunk, but here we need columns only for the
|
||||
* subports, which is a (list of) dictionary(ies) in the
|
||||
* trunk dictionary.
|
||||
*/
|
||||
columns: [
|
||||
{id: 'segmentation_type', title: gettext('Segmentation Type'),
|
||||
priority: 1, sortDefault: true},
|
||||
{id: 'segmentation_id', title: gettext('Segmentation ID'),
|
||||
priority: 1, sortDefault: true},
|
||||
{id: 'port_id', title: gettext('Port ID'), priority: 1}
|
||||
]
|
||||
};
|
||||
|
||||
$scope.context.loadPromise.then(onGetTrunk);
|
||||
|
||||
function onGetTrunk(trunk) {
|
||||
ctrl.trunk = trunk.data;
|
||||
|
||||
userSession.get().then(setProject);
|
||||
|
||||
function setProject(session) {
|
||||
ctrl.projectId = session.project_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
})();
|
@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright 2017 Ericsson
|
||||
*
|
||||
* 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('trunk overview controller', function() {
|
||||
var ctrl;
|
||||
var sessionObj = {project_id: '12'};
|
||||
var neutron = {
|
||||
getNamespaces: angular.noop
|
||||
};
|
||||
|
||||
beforeEach(module('horizon.app.core.trunks'));
|
||||
beforeEach(module('horizon.framework.conf'));
|
||||
beforeEach(inject(function($controller, $q, $injector) {
|
||||
var session = $injector.get('horizon.app.core.openstack-service-api.userSession');
|
||||
var deferred = $q.defer();
|
||||
var sessionDeferred = $q.defer();
|
||||
deferred.resolve({data: {sub_ports: [{'port_id': '1', 'seg_id': 2}, [], {}]}});
|
||||
sessionDeferred.resolve(sessionObj);
|
||||
spyOn(neutron, 'getNamespaces').and.returnValue(deferred.promise);
|
||||
spyOn(session, 'get').and.returnValue(sessionDeferred.promise);
|
||||
ctrl = $controller('TrunkOverviewController',
|
||||
{
|
||||
'$scope': {context: {loadPromise: deferred.promise}}
|
||||
}
|
||||
);
|
||||
}));
|
||||
|
||||
it('sets ctrl.resourceType', function() {
|
||||
expect(ctrl.resourceType).toBeDefined();
|
||||
});
|
||||
|
||||
it('sets ctrl.trunk.sub_ports', inject(function($timeout) {
|
||||
$timeout.flush();
|
||||
expect(ctrl.trunk).toBeDefined();
|
||||
expect(ctrl.trunk.sub_ports).toBeDefined();
|
||||
expect(ctrl.trunk.sub_ports[0]).toEqual({'port_id': '1', 'seg_id': 2});
|
||||
}));
|
||||
|
||||
it('sets ctrl.trunk.sub_ports if empty array', inject(function($timeout) {
|
||||
$timeout.flush();
|
||||
expect(ctrl.trunk).toBeDefined();
|
||||
expect(ctrl.trunk.sub_ports).toBeDefined();
|
||||
expect(ctrl.trunk.sub_ports[1]).toEqual([]);
|
||||
}));
|
||||
|
||||
it('sets ctrl.trunk.sub_ports if empty object', inject(function($timeout) {
|
||||
$timeout.flush();
|
||||
expect(ctrl.trunk).toBeDefined();
|
||||
expect(ctrl.trunk.sub_ports).toBeDefined();
|
||||
expect(ctrl.trunk.sub_ports[2]).toEqual({});
|
||||
}));
|
||||
|
||||
it('sets ctrl.projectId', inject(function($timeout) {
|
||||
$timeout.flush();
|
||||
expect(ctrl.projectId).toBe(sessionObj.project_id);
|
||||
}));
|
||||
|
||||
});
|
||||
|
||||
})();
|
19
openstack_dashboard/static/app/core/trunks/details/overview.html
Executable file
19
openstack_dashboard/static/app/core/trunks/details/overview.html
Executable file
@ -0,0 +1,19 @@
|
||||
<div ng-controller="TrunkOverviewController as ctrl">
|
||||
<hz-resource-property-list
|
||||
resource-type-name="OS::Neutron::Trunk"
|
||||
cls="dl-horizontal"
|
||||
item="ctrl.trunk"
|
||||
property-groups="[[
|
||||
'id', 'name', 'description', 'port_id', 'status', 'admin_state',
|
||||
'created_at', 'updated_at']]">
|
||||
</hz-resource-property-list>
|
||||
<h2 class="h4">{$ ::'Subports' | translate $}</h2>
|
||||
<hr>
|
||||
<dl class="dl-horizontal">
|
||||
<hz-dynamic-table
|
||||
config="ctrl.tableConfig"
|
||||
items="ctrl.trunk.sub_ports"
|
||||
table="ctrl">
|
||||
</hz-dynamic-table>
|
||||
</dl>
|
||||
</div>
|
@ -14,4 +14,4 @@
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,4 +1,4 @@
|
||||
/**
|
||||
/*
|
||||
* Copyright 2017 Ericsson
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
@ -30,6 +30,7 @@
|
||||
'ngRoute',
|
||||
'horizon.framework.conf',
|
||||
'horizon.app.core.trunks.actions',
|
||||
'horizon.app.core.trunks.details',
|
||||
'horizon.app.core'
|
||||
])
|
||||
.constant('horizon.app.core.trunks.resourceType', 'OS::Neutron::Trunk')
|
||||
@ -58,7 +59,8 @@
|
||||
.append({
|
||||
id: 'name_or_id',
|
||||
priority: 1,
|
||||
sortDefault: true
|
||||
sortDefault: true,
|
||||
urlFunction: trunksService.getDetailsPath
|
||||
})
|
||||
.append({
|
||||
id: 'port_id',
|
||||
@ -156,6 +158,14 @@
|
||||
$routeProvider.when('/project/trunks', {
|
||||
templateUrl: path + 'panel.html'
|
||||
});
|
||||
|
||||
$routeProvider.when('/project/trunks/:id', {
|
||||
redirectTo: goToAngularDetails
|
||||
});
|
||||
|
||||
function goToAngularDetails(params) {
|
||||
return detailRoute + 'OS::Neutron::Trunk/' + params.id;
|
||||
}
|
||||
}
|
||||
|
||||
})();
|
||||
|
@ -1,4 +1,4 @@
|
||||
/**
|
||||
/*
|
||||
* Copyright 2017 Ericsson
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
@ -22,7 +22,9 @@
|
||||
|
||||
trunksService.$inject = [
|
||||
'horizon.app.core.openstack-service-api.neutron',
|
||||
'horizon.app.core.openstack-service-api.userSession'
|
||||
'horizon.app.core.openstack-service-api.userSession',
|
||||
'horizon.app.core.detailRoute',
|
||||
'$location'
|
||||
];
|
||||
|
||||
/*
|
||||
@ -35,12 +37,26 @@
|
||||
* but do not need to be restricted to such use. Each exposed function
|
||||
* is documented below.
|
||||
*/
|
||||
function trunksService(neutron, userSession) {
|
||||
function trunksService(neutron, userSession, detailRoute, $location) {
|
||||
|
||||
return {
|
||||
getTrunksPromise: getTrunksPromise
|
||||
getDetailsPath: getDetailsPath,
|
||||
getTrunksPromise: getTrunksPromise,
|
||||
getTrunkPromise: getTrunkPromise
|
||||
};
|
||||
|
||||
/*
|
||||
* @ngdoc function
|
||||
* @name getDetailsPath
|
||||
* @param item {Object} - The trunk object
|
||||
* @description
|
||||
* Given a Trunk object, returns the relative path to the details
|
||||
* view.
|
||||
*/
|
||||
function getDetailsPath(item) {
|
||||
return detailRoute + 'OS::Neutron::Trunk/' + item.id;
|
||||
}
|
||||
|
||||
/*
|
||||
* @ngdoc function
|
||||
* @name getTrunksPromise
|
||||
@ -56,6 +72,25 @@
|
||||
return neutron.getTrunks(params);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* @ngdoc function
|
||||
* @name getTrunkPromise
|
||||
* @description
|
||||
* Given an id, returns a promise for the trunk data.
|
||||
*/
|
||||
function getTrunkPromise(identifier) {
|
||||
return neutron.getTrunk(identifier).then(getTrunkSuccess, getTrunkError);
|
||||
|
||||
function getTrunkSuccess(trunk) {
|
||||
return trunk;
|
||||
}
|
||||
|
||||
function getTrunkError(trunk) {
|
||||
$location.url('project/trunks');
|
||||
return trunk;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
})();
|
||||
|
@ -1,4 +1,4 @@
|
||||
/**
|
||||
/*
|
||||
* Copyright 2017 Ericsson
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
@ -26,6 +26,19 @@
|
||||
service = $injector.get('horizon.app.core.trunks.service');
|
||||
}));
|
||||
|
||||
describe('getTrunkPromise', function() {
|
||||
it("provides a promise", inject(function($q, $injector, $timeout) {
|
||||
var neutron = $injector.get('horizon.app.core.openstack-service-api.neutron');
|
||||
var deferred = $q.defer();
|
||||
spyOn(neutron, 'getTrunk').and.returnValue(deferred.promise);
|
||||
var result = service.getTrunkPromise({});
|
||||
deferred.resolve({data: {id: 1, updated_at: 'May29'}});
|
||||
$timeout.flush();
|
||||
expect(neutron.getTrunk).toHaveBeenCalled();
|
||||
expect(result.$$state.value.data.updated_at).toBe('May29');
|
||||
}));
|
||||
});
|
||||
|
||||
describe('getTrunksPromise', function() {
|
||||
it("provides a promise that gets translated", inject(function($q, $injector, $timeout) {
|
||||
var neutron = $injector.get('horizon.app.core.openstack-service-api.neutron');
|
||||
|
@ -167,6 +167,16 @@ class NeutronTrunkTestCase(test.TestCase):
|
||||
neutron.Trunk().delete(request, 1)
|
||||
client.trunk_delete.assert_called_once_with(request, 1)
|
||||
|
||||
@mock.patch.object(neutron.api, 'neutron')
|
||||
def test_trunk_get(self, client):
|
||||
trunk_id = TEST.api_trunks.first().get("id")
|
||||
request = self.mock_rest_request(GET={"trunk_id": trunk_id})
|
||||
client.trunk_show.return_value = self.trunks.first()
|
||||
response = neutron.Trunk().get(request, trunk_id=trunk_id)
|
||||
self.assertStatusCode(response, 200)
|
||||
client.trunk_show.assert_called_once_with(
|
||||
request, trunk_id)
|
||||
|
||||
|
||||
class NeutronTrunksTestCase(test.TestCase):
|
||||
|
||||
|
@ -430,6 +430,17 @@ class NeutronApiTests(test.APITestCase):
|
||||
for t in ret_val:
|
||||
self.assertIsInstance(t, api.neutron.Trunk)
|
||||
|
||||
def test_trunk_show(self):
|
||||
trunk = {'trunk': self.api_trunks.first()}
|
||||
trunk_id = self.api_trunks.first()['id']
|
||||
|
||||
neutron_client = self.stub_neutronclient()
|
||||
neutron_client.show_trunk(trunk_id).AndReturn(trunk)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
ret_val = api.neutron.trunk_show(self.request, trunk_id)
|
||||
self.assertIsInstance(ret_val, api.neutron.Trunk)
|
||||
|
||||
def test_trunk_object(self):
|
||||
trunk = self.api_trunks.first().copy()
|
||||
obj = api.neutron.Trunk(trunk)
|
||||
|
11
releasenotes/notes/bp-neutron-trunk-ui-72e05888e68502c4.yaml
Normal file
11
releasenotes/notes/bp-neutron-trunk-ui-72e05888e68502c4.yaml
Normal file
@ -0,0 +1,11 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
[`blueprint neutron-trunk-ui <https://blueprints.launchpad.net/horizon/+spec/neutron-trunk-ui>`_]
|
||||
Add partial support for Neutron Trunks. Since the panel is
|
||||
incomplete in Pike, it is disabled by default. An example 'enabled'
|
||||
file is supplied. After enabling it the Project/Network/Trunks
|
||||
panel turns on if Neutron API extension 'trunk' is available. It
|
||||
displays information about trunks. The details page for each trunk
|
||||
also shows information about subports of that trunk.
|
||||
Currently supported actions: delete.
|
Loading…
Reference in New Issue
Block a user