Merge "Angularize Key Pair Details"
This commit is contained in:
commit
631df03dfe
horizon/static/framework/util/filters
openstack_dashboard
api/rest
dashboards/project/key_pairs
static/app/core
keypairs
_keypairs.scss
details
keypairs.module.jskeypairs.module.spec.jskeypairs.service.jskeypairs.service.spec.jsopenstack-service-api
test/api_tests
@ -20,6 +20,7 @@
|
||||
.module('horizon.framework.util.filters')
|
||||
.filter('yesno', yesNoFilter)
|
||||
.filter('simpleDate', simpleDateFilter)
|
||||
.filter('mediumDate', mediumDateFilter)
|
||||
.filter('gb', gbFilter)
|
||||
.filter('mb', mbFilter)
|
||||
.filter('title', titleFilter)
|
||||
@ -59,6 +60,19 @@
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @ngdoc filter
|
||||
* @name mediumDate
|
||||
* @description
|
||||
* Evaluates given for display as a medium date, returning '-' if empty.
|
||||
*/
|
||||
mediumDateFilter.$inject = ['$filter'];
|
||||
function mediumDateFilter($filter) {
|
||||
return function (input) {
|
||||
return $filter('noValue')($filter('date')(input, 'medium'));
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @ngdoc filter
|
||||
* @name gb
|
||||
|
@ -67,6 +67,21 @@
|
||||
});
|
||||
});
|
||||
|
||||
describe('mediumDate', function () {
|
||||
var mediumDateFilter;
|
||||
beforeEach(inject(function (_mediumDateFilter_) {
|
||||
mediumDateFilter = _mediumDateFilter_;
|
||||
}));
|
||||
|
||||
it('returns blank if nothing', function () {
|
||||
expect(mediumDateFilter()).toBe('-');
|
||||
});
|
||||
|
||||
it('returns the expected time', function() {
|
||||
expect(mediumDateFilter('2001-02-03T16:05:06')).toBe('Feb 3, 2001 4:05:06 PM');
|
||||
});
|
||||
});
|
||||
|
||||
describe('gb', function () {
|
||||
var gbFilter;
|
||||
beforeEach(inject(function (_gbFilter_) {
|
||||
|
@ -80,6 +80,17 @@ class Keypairs(generic.View):
|
||||
)
|
||||
|
||||
|
||||
@urls.register
|
||||
class Keypair(generic.View):
|
||||
"""API for retrieving a single keypair."""
|
||||
url_regex = r'nova/keypairs/(?P<name>[^/]+)$'
|
||||
|
||||
@rest_utils.ajax()
|
||||
def get(self, request, name):
|
||||
"""Get a specific keypair."""
|
||||
return api.nova.keypair_get(request, name).to_dict()
|
||||
|
||||
|
||||
@urls.register
|
||||
class Services(generic.View):
|
||||
"""API for nova services."""
|
||||
|
@ -28,6 +28,9 @@ if settings.ANGULAR_FEATURES.get('key_pairs_panel'):
|
||||
title = _("Key Pairs")
|
||||
urlpatterns = [
|
||||
url('', views.AngularIndexView.as_view(title=title), name='index'),
|
||||
url(r'^(?P<keypair_name>[^/]+)/$',
|
||||
views.AngularIndexView.as_view(title=title),
|
||||
name='detail'),
|
||||
]
|
||||
else:
|
||||
urlpatterns = [
|
||||
|
@ -1,9 +1,23 @@
|
||||
$details-content-fold-width: ($screen-md-min - $sidebar-width) !default;
|
||||
|
||||
hz-resource-property-list[resource-type-name="OS::Nova::Keypair"] {
|
||||
hz-resource-property[prop-name="public_key"] dd {
|
||||
overflow-wrap: break-word;
|
||||
width: calc(100vw - 61px);
|
||||
@media (min-width: 992px) {
|
||||
width: calc(100vw - 281px);
|
||||
width: calc(100vw - calc(#{$padding-large-horizontal} * 4));
|
||||
@media (min-width: $details-content-fold-width) {
|
||||
width: calc(100vw - #{$sidebar-width} - calc(#{$padding-large-horizontal} * 4));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hz-details {
|
||||
hz-resource-property-list[resource-type-name="OS::Nova::Keypair"] {
|
||||
hz-resource-property[prop-name="public_key"] dd {
|
||||
overflow-wrap: break-word;
|
||||
width: calc(100vw - #{$dl-horizontal-offset} - calc(#{$padding-large-horizontal} * 4));
|
||||
@media (min-width: $details-content-fold-width) {
|
||||
width: calc(100vw - #{$sidebar-width} - #{$dl-horizontal-offset} - calc(#{$padding-large-horizontal} * 4));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,33 @@
|
||||
/*
|
||||
* 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.keypairs')
|
||||
.controller('horizon.app.core.keypairs.DetailsController', KeypairDetailsController);
|
||||
|
||||
KeypairDetailsController.$inject = ['$scope'];
|
||||
|
||||
function KeypairDetailsController($scope) {
|
||||
var ctrl = this;
|
||||
ctrl.keypair = {};
|
||||
|
||||
$scope.context.loadPromise.then(onGetKeypair);
|
||||
|
||||
function onGetKeypair(response) {
|
||||
ctrl.keypair = response.data;
|
||||
}
|
||||
}
|
||||
})();
|
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* 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('keypair details controller', function() {
|
||||
|
||||
var ctrl, deferred, nova;
|
||||
|
||||
beforeEach(module('horizon.app.core.keypairs'));
|
||||
|
||||
beforeEach(inject(function($controller, $q, $injector) {
|
||||
nova = $injector.get('horizon.app.core.openstack-service-api.nova');
|
||||
deferred = $q.defer();
|
||||
deferred.resolve({data: {name: 1}});
|
||||
spyOn(nova, 'getKeypair').and.returnValue(deferred.promise);
|
||||
ctrl = $controller('horizon.app.core.keypairs.DetailsController',
|
||||
{
|
||||
'$scope' : {context : {loadPromise: deferred.promise}}
|
||||
}
|
||||
);
|
||||
}));
|
||||
|
||||
it('sets ctrl', inject(function($timeout) {
|
||||
$timeout.flush();
|
||||
expect(ctrl.keypair).toBeDefined();
|
||||
}));
|
||||
|
||||
});
|
||||
})();
|
@ -0,0 +1,12 @@
|
||||
<div ng-controller="horizon.app.core.keypairs.DetailsController as ctrl">
|
||||
<div class="row">
|
||||
<div class="col-md-12 detail">
|
||||
<hz-resource-property-list
|
||||
resource-type-name="OS::Nova::Keypair"
|
||||
cls="dl-horizontal"
|
||||
item="ctrl.keypair"
|
||||
property-groups="[['id', 'name', 'fingerprint', 'created_at', 'user_id', 'public_key']]">
|
||||
</hz-resource-property-list>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 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.keypairs.details
|
||||
*
|
||||
* @description
|
||||
* Provides details features for keypair.
|
||||
*/
|
||||
angular.module('horizon.app.core.keypairs.details',
|
||||
['horizon.framework.conf', 'horizon.app.core'])
|
||||
.run(registerKeypairDetails);
|
||||
|
||||
registerKeypairDetails.$inject = [
|
||||
'horizon.app.core.keypairs.basePath',
|
||||
'horizon.app.core.keypairs.resourceType',
|
||||
'horizon.app.core.keypairs.service',
|
||||
'horizon.framework.conf.resource-type-registry.service'
|
||||
];
|
||||
|
||||
function registerKeypairDetails(
|
||||
basePath,
|
||||
resourceType,
|
||||
keypairService,
|
||||
registry
|
||||
) {
|
||||
registry.getResourceType(resourceType)
|
||||
.setLoadFunction(keypairService.getKeypairPromise)
|
||||
.detailsViews.append({
|
||||
id: 'keypairDetails',
|
||||
name: gettext('Details'),
|
||||
template: basePath + 'details/details.html'
|
||||
});
|
||||
}
|
||||
|
||||
})();
|
@ -28,8 +28,7 @@
|
||||
angular
|
||||
.module('horizon.app.core.keypairs', [
|
||||
'ngRoute',
|
||||
'horizon.app.core',
|
||||
'horizon.framework.conf'
|
||||
'horizon.app.core.keypairs.details'
|
||||
])
|
||||
.constant('horizon.app.core.keypairs.resourceType', 'OS::Nova::Keypair')
|
||||
.run(run)
|
||||
@ -54,7 +53,8 @@
|
||||
.append({
|
||||
id: 'name',
|
||||
priority: 1,
|
||||
sortDefault: true
|
||||
sortDefault: true,
|
||||
urlFunction: keypairsService.urlFunction
|
||||
})
|
||||
.append({
|
||||
id: 'fingerprint',
|
||||
@ -75,7 +75,7 @@
|
||||
'id': {label: gettext('ID'), filters: ['noValue'] },
|
||||
'name': {label: gettext('Name'), filters: ['noName'] },
|
||||
'fingerprint': {label: gettext('Fingerprint'), filters: ['noValue'] },
|
||||
'created_at': {label: gettext('Created'), filters: ['simpleDate'] },
|
||||
'created_at': {label: gettext('Created'), filters: ['mediumDate'] },
|
||||
'user_id': {label: gettext('User ID'), filters: ['noValue'] },
|
||||
'public_key': {label: gettext('Public Key'), filters: ['noValue'] }
|
||||
};
|
||||
@ -84,7 +84,8 @@
|
||||
config.$inject = [
|
||||
'$provide',
|
||||
'$windowProvider',
|
||||
'$routeProvider'
|
||||
'$routeProvider',
|
||||
'horizon.app.core.detailRoute'
|
||||
];
|
||||
|
||||
/**
|
||||
@ -92,14 +93,22 @@
|
||||
* @param {Object} $provide
|
||||
* @param {Object} $windowProvider
|
||||
* @param {Object} $routeProvider
|
||||
* @param {Object} detailRoute
|
||||
* @description Routes used by this module.
|
||||
* @returns {undefined} Returns nothing
|
||||
*/
|
||||
function config($provide, $windowProvider, $routeProvider) {
|
||||
function config($provide, $windowProvider, $routeProvider, detailRoute) {
|
||||
var path = $windowProvider.$get().STATIC_URL + 'app/core/keypairs/';
|
||||
$provide.constant('horizon.app.core.keypairs.basePath', path);
|
||||
$routeProvider.when('/project/key_pairs', {
|
||||
templateUrl: path + 'panel.html'
|
||||
})
|
||||
.when('/project/key_pairs/:id', {
|
||||
redirectTo: goToAngularDetails
|
||||
});
|
||||
|
||||
function goToAngularDetails(params) {
|
||||
return detailRoute + 'OS::Nova::Keypair/' + params.id;
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
@ -40,7 +40,6 @@
|
||||
expect(names).toContain('name');
|
||||
|
||||
function getName(x) {
|
||||
// underscore.js and .pluck() would be great here.
|
||||
return x.name;
|
||||
}
|
||||
});
|
||||
|
@ -20,6 +20,7 @@
|
||||
|
||||
keypairsService.$inject = [
|
||||
'$filter',
|
||||
'horizon.app.core.detailRoute',
|
||||
'horizon.app.core.openstack-service-api.nova'
|
||||
];
|
||||
|
||||
@ -33,9 +34,11 @@
|
||||
* but do not need to be restricted to such use. Each exposed function
|
||||
* is documented below.
|
||||
*/
|
||||
function keypairsService($filter, nova) {
|
||||
function keypairsService($filter, detailRoute, nova) {
|
||||
return {
|
||||
getKeypairsPromise: getKeypairsPromise
|
||||
getKeypairsPromise: getKeypairsPromise,
|
||||
getKeypairPromise: getKeypairPromise,
|
||||
urlFunction: urlFunction
|
||||
};
|
||||
|
||||
/*
|
||||
@ -61,6 +64,18 @@
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
/*
|
||||
* @ngdoc function
|
||||
* @name getKeypairPromise
|
||||
* @description
|
||||
* Given a name, returns a promise for the keypair data.
|
||||
*/
|
||||
function getKeypairPromise(name) {
|
||||
return nova.getKeypair(name);
|
||||
}
|
||||
|
||||
function urlFunction(item) {
|
||||
return detailRoute + 'OS::Nova::Keypair/' + item.name;
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
@ -21,10 +21,11 @@
|
||||
});
|
||||
|
||||
describe('keypairsService', function() {
|
||||
var service;
|
||||
var service, detailRoute;
|
||||
beforeEach(module('horizon.app.core'));
|
||||
beforeEach(inject(function($injector) {
|
||||
service = $injector.get('horizon.app.core.keypairs.service');
|
||||
detailRoute = $injector.get('horizon.app.core.detailRoute');
|
||||
}));
|
||||
|
||||
describe('getKeypairsPromise', function() {
|
||||
@ -49,6 +50,23 @@
|
||||
}));
|
||||
});
|
||||
|
||||
});
|
||||
describe('getKeypairPromise', function() {
|
||||
it("provides a promise", inject(function($q, $injector) {
|
||||
var nova = $injector.get('horizon.app.core.openstack-service-api.nova');
|
||||
var deferred = $q.defer();
|
||||
spyOn(nova, 'getKeypair').and.returnValue(deferred.promise);
|
||||
var result = service.getKeypairPromise('keypair1');
|
||||
deferred.resolve({data: {keypair: {name: 'keypair1'}}});
|
||||
expect(nova.getKeypair).toHaveBeenCalled();
|
||||
expect(result.$$state.value.data.keypair.name).toBe('keypair1');
|
||||
}));
|
||||
});
|
||||
|
||||
describe('urlFunction', function() {
|
||||
it("get url", inject(function() {
|
||||
var result = service.urlFunction({name: "123abc"});
|
||||
expect(result).toBe(detailRoute + "OS::Nova::Keypair/123abc");
|
||||
}));
|
||||
});
|
||||
});
|
||||
})();
|
||||
|
@ -44,6 +44,7 @@
|
||||
getServerSecurityGroups: getServerSecurityGroups,
|
||||
getKeypairs: getKeypairs,
|
||||
createKeypair: createKeypair,
|
||||
getKeypair: getKeypair,
|
||||
getAvailabilityZones: getAvailabilityZones,
|
||||
getLimits: getLimits,
|
||||
createServer: createServer,
|
||||
@ -141,6 +142,23 @@
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @name getKeypair
|
||||
* @description
|
||||
* Get a single keypair by name.
|
||||
*
|
||||
* @param {string} name
|
||||
* The name of the keypair. Required.
|
||||
*
|
||||
* @returns {Object} The result of the API call.
|
||||
*/
|
||||
function getKeypair(name) {
|
||||
return apiService.get('/api/nova/keypairs/' + name)
|
||||
.error(function () {
|
||||
toastService.add('error', gettext('Unable to retrieve the keypair.'));
|
||||
});
|
||||
}
|
||||
|
||||
// Availability Zones
|
||||
|
||||
/**
|
||||
|
@ -133,6 +133,13 @@
|
||||
{}
|
||||
]
|
||||
},
|
||||
{
|
||||
"func": "getKeypair",
|
||||
"method": "get",
|
||||
"path": "/api/nova/keypairs/19",
|
||||
"error": "Unable to retrieve the keypair.",
|
||||
"testInput": [19]
|
||||
},
|
||||
{
|
||||
"func": "deleteServer",
|
||||
"method": "delete",
|
||||
|
@ -172,7 +172,7 @@ class NovaRestTestCase(test.TestCase):
|
||||
# Keypairs
|
||||
#
|
||||
@mock.patch.object(nova.api, 'nova')
|
||||
def test_keypair_get(self, nc):
|
||||
def test_keypair_list(self, nc):
|
||||
request = self.mock_rest_request()
|
||||
nc.keypair_list.return_value = [
|
||||
mock.Mock(**{'to_dict.return_value': {'id': 'one'}}),
|
||||
@ -214,6 +214,16 @@ class NovaRestTestCase(test.TestCase):
|
||||
self.assertEqual('/api/nova/keypairs/Ni%21', response['location'])
|
||||
nc.keypair_import.assert_called_once_with(request, 'Ni!', 'hi')
|
||||
|
||||
@mock.patch.object(nova.api, 'nova')
|
||||
def test_keypair_get(self, nc):
|
||||
request = self.mock_rest_request()
|
||||
nc.keypair_get.return_value.to_dict.return_value = {'name': '1'}
|
||||
response = nova.Keypair().get(request, '1')
|
||||
self.assertStatusCode(response, 200)
|
||||
self.assertEqual({"name": "1"},
|
||||
response.json)
|
||||
nc.keypair_get.assert_called_once_with(request, "1")
|
||||
|
||||
#
|
||||
# Availability Zones
|
||||
#
|
||||
|
Loading…
x
Reference in New Issue
Block a user