diff --git a/doc/source/configuration/settings.rst b/doc/source/configuration/settings.rst index 14d9a039af..1fcd1e16d3 100644 --- a/doc/source/configuration/settings.rst +++ b/doc/source/configuration/settings.rst @@ -48,6 +48,7 @@ Default: { 'images_panel': True, + 'key_pairs_panel': False, 'flavors_panel': False, 'users_panel': False, 'roles_panel': False, diff --git a/openstack_dashboard/dashboards/project/key_pairs/urls.py b/openstack_dashboard/dashboards/project/key_pairs/urls.py index 9c5b2015f1..5c4f3af621 100644 --- a/openstack_dashboard/dashboards/project/key_pairs/urls.py +++ b/openstack_dashboard/dashboards/project/key_pairs/urls.py @@ -16,12 +16,23 @@ # License for the specific language governing permissions and limitations # under the License. +from django.conf import settings from django.conf.urls import url -from openstack_dashboard.dashboards.project.key_pairs import views +from django.utils.translation import ugettext_lazy as _ -urlpatterns = [ - url(r'^$', views.IndexView.as_view(), name='index'), - url(r'^import/$', views.ImportView.as_view(), name='import'), - url(r'^(?P[^/]+)/$', views.DetailView.as_view(), - name='detail'), -] +from horizon.browsers import views +from openstack_dashboard.dashboards.project.key_pairs import views as \ + legacy_views + +if settings.ANGULAR_FEATURES.get('key_pairs_panel'): + title = _("Key Pairs") + urlpatterns = [ + url('', views.AngularIndexView.as_view(title=title), name='index'), + ] +else: + urlpatterns = [ + url(r'^$', legacy_views.IndexView.as_view(), name='index'), + url(r'^import/$', legacy_views.ImportView.as_view(), name='import'), + url(r'^(?P[^/]+)/$', legacy_views.DetailView.as_view(), + name='detail'), + ] diff --git a/openstack_dashboard/settings.py b/openstack_dashboard/settings.py index 51db4cab04..4284c5e646 100644 --- a/openstack_dashboard/settings.py +++ b/openstack_dashboard/settings.py @@ -324,6 +324,7 @@ COMPRESS_OFFLINE_CONTEXT = 'horizon.themes.offline_context' # Dictionary of currently available angular features ANGULAR_FEATURES = { 'images_panel': True, + 'key_pairs_panel': False, 'flavors_panel': False, 'users_panel': False, 'roles_panel': False, diff --git a/openstack_dashboard/static/app/core/_core.scss b/openstack_dashboard/static/app/core/_core.scss index c46c3bf362..1501200102 100644 --- a/openstack_dashboard/static/app/core/_core.scss +++ b/openstack_dashboard/static/app/core/_core.scss @@ -1 +1,2 @@ @import "images/images"; +@import "keypairs/keypairs"; diff --git a/openstack_dashboard/static/app/core/keypairs/_keypairs.scss b/openstack_dashboard/static/app/core/keypairs/_keypairs.scss new file mode 100644 index 0000000000..301a8c0cc6 --- /dev/null +++ b/openstack_dashboard/static/app/core/keypairs/_keypairs.scss @@ -0,0 +1,9 @@ +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); + } + } +} diff --git a/openstack_dashboard/static/app/core/keypairs/details/drawer.html b/openstack_dashboard/static/app/core/keypairs/details/drawer.html new file mode 100644 index 0000000000..171100261c --- /dev/null +++ b/openstack_dashboard/static/app/core/keypairs/details/drawer.html @@ -0,0 +1,5 @@ + + \ No newline at end of file diff --git a/openstack_dashboard/static/app/core/keypairs/keypairs.module.js b/openstack_dashboard/static/app/core/keypairs/keypairs.module.js index 6e2285c001..970a95255a 100644 --- a/openstack_dashboard/static/app/core/keypairs/keypairs.module.js +++ b/openstack_dashboard/static/app/core/keypairs/keypairs.module.js @@ -27,7 +27,79 @@ */ angular .module('horizon.app.core.keypairs', [ + 'ngRoute', + 'horizon.app.core', + 'horizon.framework.conf' ]) - ; + .constant('horizon.app.core.keypairs.resourceType', 'OS::Nova::Keypair') + .run(run) + .config(config); + run.$inject = [ + 'horizon.framework.conf.resource-type-registry.service', + 'horizon.app.core.openstack-service-api.nova', + 'horizon.app.core.keypairs.basePath', + 'horizon.app.core.keypairs.resourceType', + 'horizon.app.core.keypairs.service' + ]; + + function run(registry, nova, basePath, resourceType, keypairsService) { + registry.getResourceType(resourceType) + .setNames(gettext('Key Pair'), gettext('Key Pairs')) + // for detail summary view on table row. + .setSummaryTemplateUrl(basePath + 'details/drawer.html') + .setProperties(keypairProperties()) + .setListFunction(keypairsService.getKeypairsPromise) + .tableColumns + .append({ + id: 'name', + priority: 1, + sortDefault: true + }) + .append({ + id: 'fingerprint', + priority: 2 + }); + + // for magic-search + registry.getResourceType(resourceType).filterFacets + .append({ + 'label': gettext('Name'), + 'name': 'name', + 'singleton': true + }); + } + + function keypairProperties() { + return { + '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'] }, + 'user_id': {label: gettext('User ID'), filters: ['noValue'] }, + 'public_key': {label: gettext('Public Key'), filters: ['noValue'] } + }; + } + + config.$inject = [ + '$provide', + '$windowProvider', + '$routeProvider' + ]; + + /** + * @name config + * @param {Object} $provide + * @param {Object} $windowProvider + * @param {Object} $routeProvider + * @description Routes used by this module. + * @returns {undefined} Returns nothing + */ + function config($provide, $windowProvider, $routeProvider) { + 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' + }); + } })(); diff --git a/openstack_dashboard/static/app/core/keypairs/keypairs.module.spec.js b/openstack_dashboard/static/app/core/keypairs/keypairs.module.spec.js index 15fb1144bd..c945184e9e 100644 --- a/openstack_dashboard/static/app/core/keypairs/keypairs.module.spec.js +++ b/openstack_dashboard/static/app/core/keypairs/keypairs.module.spec.js @@ -22,4 +22,27 @@ }); }); + describe('loading the module', function () { + var registry; + + beforeEach(module('horizon.app.core.keypairs')); + beforeEach(inject(function($injector) { + registry = $injector.get('horizon.framework.conf.resource-type-registry.service'); + })); + + it('registers names', function() { + expect(registry.getResourceType('OS::Nova::Keypair').getName()).toBe("Key Pairs"); + }); + + it('should set facets for search', function () { + var names = registry.getResourceType('OS::Nova::Keypair').filterFacets + .map(getName); + expect(names).toContain('name'); + + function getName(x) { + // underscore.js and .pluck() would be great here. + return x.name; + } + }); + }); })(); diff --git a/openstack_dashboard/static/app/core/keypairs/keypairs.service.js b/openstack_dashboard/static/app/core/keypairs/keypairs.service.js new file mode 100644 index 0000000000..e9c23f0370 --- /dev/null +++ b/openstack_dashboard/static/app/core/keypairs/keypairs.service.js @@ -0,0 +1,66 @@ +/* + * 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') + .factory('horizon.app.core.keypairs.service', keypairsService); + + keypairsService.$inject = [ + '$filter', + 'horizon.app.core.openstack-service-api.nova' + ]; + + /* + * @ngdoc factory + * @name horizon.app.core.keypairs.service + * + * @description + * This service provides functions that are used through the key pair + * features. These are primarily used in the module registrations + * but do not need to be restricted to such use. Each exposed function + * is documented below. + */ + function keypairsService($filter, nova) { + return { + getKeypairsPromise: getKeypairsPromise + }; + + /* + * @ngdoc function + * @name getKeypairsPromise + * @description + * Given filter/query parameters, returns a promise for the matching + * key pairs. This is used in displaying lists of key pairs. In this + * case, we need to modify the API's response by adding a composite value + * called 'trackBy' to assist the display mechanism when updating rows. + */ + function getKeypairsPromise(params) { + return nova.getKeypairs(params).then(modifyResponse); + + function modifyResponse(response) { + return {data: {items: response.data.items.map(modifyItems)}}; + + function modifyItems(item) { + item = item.keypair; + item.trackBy = item.name; + return item; + } + } + } + + } + +})(); diff --git a/openstack_dashboard/static/app/core/keypairs/keypairs.service.spec.js b/openstack_dashboard/static/app/core/keypairs/keypairs.service.spec.js new file mode 100644 index 0000000000..92a5496f31 --- /dev/null +++ b/openstack_dashboard/static/app/core/keypairs/keypairs.service.spec.js @@ -0,0 +1,54 @@ +/* + * 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.app.core.keypairs', function () { + it('should exist', function () { + expect(angular.module('horizon.app.core.keypairs')).toBeDefined(); + }); + }); + + describe('keypairsService', function() { + var service; + beforeEach(module('horizon.app.core')); + beforeEach(inject(function($injector) { + service = $injector.get('horizon.app.core.keypairs.service'); + })); + + describe('getKeypairsPromise', function() { + it("provides a promise that gets translated", inject(function($q, $injector, $timeout) { + var nova = $injector.get('horizon.app.core.openstack-service-api.nova'); + var session = $injector.get('horizon.app.core.openstack-service-api.userSession'); + var deferred = $q.defer(); + var deferredSession = $q.defer(); + spyOn(nova, 'getKeypairs').and.returnValue(deferred.promise); + spyOn(session, 'get').and.returnValue(deferredSession.promise); + var result = service.getKeypairsPromise({}); + deferredSession.resolve({}); + deferred.resolve({ + data: { + items: [{keypair: {name: 'keypair1'}}] + } + }); + $timeout.flush(); + expect(nova.getKeypairs).toHaveBeenCalled(); + expect(result.$$state.value.data.items[0].name).toBe('keypair1'); + expect(result.$$state.value.data.items[0].trackBy).toBe('keypair1'); + })); + }); + + }); + +})(); diff --git a/openstack_dashboard/static/app/core/keypairs/panel.html b/openstack_dashboard/static/app/core/keypairs/panel.html new file mode 100644 index 0000000000..e27a6c7545 --- /dev/null +++ b/openstack_dashboard/static/app/core/keypairs/panel.html @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/releasenotes/notes/bp-ng-keypairs-876c38a1a8aed60f.yaml b/releasenotes/notes/bp-ng-keypairs-876c38a1a8aed60f.yaml new file mode 100644 index 0000000000..840bd7e7fd --- /dev/null +++ b/releasenotes/notes/bp-ng-keypairs-876c38a1a8aed60f.yaml @@ -0,0 +1,8 @@ +--- +features: + - | + [`blueprint ng-keypairs `_] + Add Angular Key Pairs panel. The Key Pairs panel allows users to view + a list of created or imported key pairs. This panel displays a table + view of the name and fingerprint of each key pair. Also, public key + is shown in expanded row.