Merge "Adding identity domains table"

This commit is contained in:
Jenkins 2017-01-16 15:54:19 +00:00 committed by Gerrit Code Review
commit 8962d7b8cc
15 changed files with 583 additions and 8 deletions

View File

@ -439,11 +439,17 @@ Default::
'images_panel': True, 'images_panel': True,
'flavors_panel': False, 'flavors_panel': False,
'users_panel': False, 'users_panel': False,
'domains_panel': False
} }
A dictionary of currently available AngularJS features. This allows simple A dictionary of currently available AngularJS features. This allows simple
toggling of legacy or rewritten features, such as new panels, workflows etc. toggling of legacy or rewritten features, such as new panels, workflows etc.
.. note::
If you toggle 'domains_panel' to True, you also need to enable the setting
of OPENSTACK_KEYSTONE_DEFAULT_DOMAIN and add OPENSTACK_KEYSTONE_DEFAULT_DOMAIN
to REST_API_REQUIRED_SETTINGS.
.. _available_themes: .. _available_themes:

View File

@ -12,14 +12,24 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
from django.conf import settings
from django.conf.urls import url from django.conf.urls import url
from django.utils.translation import ugettext_lazy as _
from openstack_dashboard.dashboards.identity.domains import views from horizon.browsers import views
from openstack_dashboard.dashboards.identity.domains import views as legacyView
urlpatterns = [ if settings.ANGULAR_FEATURES.get('domains_panel'):
url(r'^$', views.IndexView.as_view(), name='index'), title = _("Domains")
url(r'^create$', views.CreateDomainView.as_view(), name='create'), urlpatterns = [
url(r'^(?P<domain_id>[^/]+)/update/$', url('', views.AngularIndexView.as_view(title=title), name='index'),
views.UpdateDomainView.as_view(), name='update') ]
] else:
urlpatterns = [
url(r'^$', legacyView.IndexView.as_view(), name='index'),
url(r'^create$', legacyView.CreateDomainView.as_view(), name='create'),
url(r'^(?P<domain_id>[^/]+)/update/$',
legacyView.UpdateDomainView.as_view(), name='update')
]

View File

@ -0,0 +1,54 @@
/*
* Copyright 2016 NEC Corporation.
*
* 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.cluster.receivers.details
*
* @description
* Provides details features for domain.
*/
angular.module('horizon.dashboard.identity.domains.details', [
'horizon.framework.conf',
'horizon.app.core'
])
.run(registerDomainDetails);
registerDomainDetails.$inject = [
'horizon.dashboard.identity.domains.basePath',
'horizon.dashboard.identity.domains.resourceType',
'horizon.app.core.openstack-service-api.keystone',
'horizon.framework.conf.resource-type-registry.service'
];
function registerDomainDetails(basePath, domainResourceType, keystone, registry) {
registry.getResourceType(domainResourceType)
.setLoadFunction(loadFunction)
.detailsViews.append({
id: 'domainDetailsOverview',
name: gettext('Overview'),
template: basePath + 'details/overview.html'
});
function loadFunction(identifier) {
return keystone.getDomain(identifier);
}
}
})();

View File

@ -0,0 +1,36 @@
/**
* 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('Identity domains details module', function() {
it('should exist', function() {
expect(angular.module('horizon.dashboard.identity.domains.details')).toBeDefined();
});
var registry, resource;
beforeEach(module('horizon.framework'));
beforeEach(module('horizon.dashboard.identity.domains'));
beforeEach(inject(function($injector) {
registry = $injector.get('horizon.framework.conf.resource-type-registry.service');
}));
it('should be loaded', function() {
resource = registry.getResourceType('OS::Keystone::Domain');
expect(resource.detailsViews[0].id).toBe('domainDetailsOverview');
});
});
})();

View File

@ -0,0 +1,5 @@
<hz-resource-property-list
resource-type-name="OS::Keystone::Domain"
item="item"
property-groups="[['id', 'name'], ['description', 'enabled']]">
</hz-resource-property-list>

View File

@ -0,0 +1,48 @@
/*
* Copyright 2016 NEC Corporation.
*
* 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.identity.domains')
.controller('DomainOverviewController', DomainOverviewController);
DomainOverviewController.$inject = [
'horizon.dashboard.identity.domains.resourceType',
'horizon.framework.conf.resource-type-registry.service',
'$scope'
];
function DomainOverviewController(
domainResourceType,
registry,
$scope
) {
var ctrl = this;
ctrl.domain = {};
ctrl.resourceType = registry.getResourceType(domainResourceType);
// assign a controller attribute once the RoutedDetailsViewController
// has loaded the domain for us
$scope.context.loadPromise.then(onGetDomain);
function onGetDomain(domain) {
ctrl.domain = domain.data;
}
}
})();

View File

@ -0,0 +1,50 @@
/**
* 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('Identity domains details module', function() {
it('should exist', function() {
expect(angular.module('horizon.dashboard.identity.domains.details')).toBeDefined();
});
});
describe('domain overview controller', function() {
var ctrl, $scope;
beforeEach(module('horizon.dashboard.identity.domains'));
beforeEach(module('horizon.framework.conf'));
beforeEach(inject(function($controller, $injector, $q, _$rootScope_) {
var deferred = $q.defer();
deferred.resolve({data: {id: '1234', 'name': 'test'}});
ctrl = $controller('DomainOverviewController',
{'$scope': {context: {loadPromise: deferred.promise}}}
);
$scope = _$rootScope_.$new();
}));
it('sets ctrl.resourceType', function() {
expect(ctrl.resourceType).toBeDefined();
});
it('sets ctrl.domain', inject(function() {
$scope.$apply();
expect(ctrl.domain).toBeDefined();
}));
});
})();

View File

@ -0,0 +1,14 @@
<div ng-controller="DomainOverviewController as ctrl">
<div class="row">
<div class="col-md-6 detail">
<h3 translate>Domain</h3>
<hr>
<hz-resource-property-list
resource-type-name="OS::Keystone::Domain"
cls="dl-horizontal"
item="ctrl.domain"
property-groups="[['id', 'name', 'description', 'enabled']]">
</hz-resource-property-list>
</div>
</div>
</div>

View File

@ -0,0 +1,102 @@
/*
* Copyright 2016 NEC Corporation.
*
* 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.dashboard.identity.domains
*
* @description
* Provides all of the services and widgets required
* to support and display domains related content.
*/
angular
.module('horizon.dashboard.identity.domains', [
'ngRoute',
'horizon.dashboard.identity.domains.details'
])
.constant('horizon.dashboard.identity.domains.resourceType', 'OS::Keystone::Domain')
.run(run)
.config(config);
run.$inject = [
'horizon.framework.conf.resource-type-registry.service',
'horizon.dashboard.identity.domains.service',
'horizon.dashboard.identity.domains.basePath',
'horizon.dashboard.identity.domains.resourceType'
];
function run(registry, domainService, basePath, domainResourceType) {
registry.getResourceType(domainResourceType)
.setNames(gettext('Domain'), gettext('Domains'))
.setSummaryTemplateUrl(basePath + 'details/drawer.html')
.setProperties(domainProperties())
.setListFunction(domainService.listDomains)
.tableColumns
.append({
id: 'name',
priority: 1,
sortDefault: true,
urlFunction: domainService.getDetailsPath
})
.append({
id: 'description',
priority: 1
})
.append({
id: 'id',
priority: 1
})
.append({
id: 'enabled',
priority: 1
});
}
function domainProperties() {
return {
name: { label: gettext('Name'), filters: ['noName'] },
description: { label: gettext('Description'), filters: ['noValue'] },
id: { label: gettext('ID'), filters: ['noValue'] },
enabled: { label: gettext('Enabled'), filters: ['yesno'] }
};
}
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 + 'dashboard/identity/domains/';
$provide.constant('horizon.dashboard.identity.domains.basePath', path);
$routeProvider.when('/identity/domains', {
templateUrl: path + 'panel.html'
});
}
})();

View File

@ -0,0 +1,24 @@
/**
* 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('Identity domains module', function() {
it('should exist', function() {
expect(angular.module('horizon.dashboard.identity.domains')).toBeDefined();
});
});
})();

View File

@ -0,0 +1,128 @@
/*
* Copyright 2016 NEC Corporation.
*
* 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.identity.domains')
.factory('horizon.dashboard.identity.domains.service', domainService);
domainService.$inject = [
'$q',
'horizon.app.core.openstack-service-api.keystone',
'horizon.app.core.openstack-service-api.policy',
'horizon.app.core.openstack-service-api.settings'
];
/*
* @ngdoc factory
* @name horizon.dashboard.identity.domains.service
*
* @description
* This service provides functions that are used through the Domains
* 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 domainService($q, keystone, policy, settingsService) {
return {
getDetailsPath: getDetailsPath,
getDomainPromise: getDomainPromise,
listDomains: listDomains
};
/*
* @ngdoc function
* @name getDetailsPath
* @param item {Object} - The domain object
* @description
* Given an Domain object, returns the relative path to the details
* view.
*/
function getDetailsPath(item) {
return 'project/ngdetails/OS::Keystone::Domain/' + item.id;
}
/*
* @ngdoc function
* @name listDomains
* @description
* Returns list of domains. This is used in displaying lists of Domains.
* 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 listDomains() {
var defaultDomain = null;
var KEYSTONE_DEFAULT_DOMAIN = null;
return $q.all([
keystone.getDomain('default'),
settingsService.getSetting('OPENSTACK_KEYSTONE_DEFAULT_DOMAIN')
]).then(allowed);
function allowed(results) {
defaultDomain = results[0].data;
KEYSTONE_DEFAULT_DOMAIN = results[1];
var rules = [['identity', 'identity:list_domains']];
return policy.ifAllowed({ rules: rules }).then(policySuccess, policyFailed);
}
function policySuccess() {
if (isDefaultDomain()) {
// In case that a user is cloud admin and context is Keystone default domain.
return keystone.getDomains().then(getDomainSuccess);
} else {
// In case that a user is cloud admin but has a specific domain scope.
return keystone.getDomain(defaultDomain.id).then(getDomainSuccess);
}
}
function policyFailed() {
// In case that a user doesn't have a privilege of list_domains.
return keystone.getDomain(defaultDomain.id).then(getDomainSuccess);
}
function getDomainSuccess(response) {
if (!angular.isArray(response.data.items)) {
// the result of getDomain is not array.
response.data.items = [response.data];
}
return {data: {items: response.data.items.map(modifyDomain)}};
function modifyDomain(domain) {
domain.trackBy = domain.id;
return domain;
}
}
function isDefaultDomain() {
return defaultDomain.name === KEYSTONE_DEFAULT_DOMAIN;
}
}
/*
* @ngdoc function
* @name getDomainPromise
* @description
* Given an id, returns a promise for the domain data.
*/
function getDomainPromise(identifier) {
return keystone.getDomain(identifier);
}
}
})();

View File

@ -0,0 +1,92 @@
/*
* 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('domain service', function() {
var service, $scope;
beforeEach(module('horizon.dashboard.identity.domains'));
beforeEach(inject(function($injector) {
service = $injector.get('horizon.dashboard.identity.domains.service');
}));
it("getDetailsPath creates urls using the item's ID", function() {
var myItem = {id: "1234"};
expect(service.getDetailsPath(myItem)).toBe('project/ngdetails/OS::Keystone::Domain/1234');
});
describe('listDomains', function() {
var keystone, setting, policy;
beforeEach(inject(function($injector) {
keystone = $injector.get('horizon.app.core.openstack-service-api.keystone');
setting = $injector.get('horizon.app.core.openstack-service-api.settings');
policy = $injector.get('horizon.app.core.openstack-service-api.policy');
}));
it("allowed list_domain and default domain scope", inject(function($q, _$rootScope_) {
$scope = _$rootScope_.$new();
var deferredGetDomain = $q.defer();
var deferredGetDomains = $q.defer();
var deferredSetting = $q.defer();
var deferredPolicy = $q.defer();
spyOn(keystone, 'getDomain').and.returnValue(deferredGetDomain.promise);
spyOn(keystone, 'getDomains').and.returnValue(deferredGetDomains.promise);
spyOn(setting, 'getSetting').and.returnValue(deferredSetting.promise);
spyOn(policy, 'ifAllowed').and.returnValue(deferredPolicy.promise);
var result = service.listDomains({});
deferredGetDomain.resolve({data: {id: 'default', name: 'Default'}});
deferredGetDomains.resolve({data: {items: [{id: '1234', name: 'test_domain1'}]}});
deferredSetting.resolve("Default");
deferredPolicy.resolve({"allowed": true});
$scope.$apply();
expect(result.$$state.value.data.items[0].trackBy).toBe('1234');
}));
it("allowed list_domain and not domain scope", inject(function($q, _$rootScope_) {
$scope = _$rootScope_.$new();
var deferredGetDomain = $q.defer();
var deferredSetting = $q.defer();
var deferredPolicy = $q.defer();
spyOn(keystone, 'getDomain').and.returnValue(deferredGetDomain.promise);
spyOn(setting, 'getSetting').and.returnValue(deferredSetting.promise);
spyOn(policy, 'ifAllowed').and.returnValue(deferredPolicy.promise);
var result = service.listDomains({});
deferredGetDomain.resolve({data: {id: '1234', name: 'test_domain1'}});
deferredSetting.resolve("Default");
deferredPolicy.resolve({"allowed": true});
$scope.$apply();
expect(result.$$state.value.data.items[0].trackBy).toBe('1234');
}));
});
describe('getDomainPromise', function() {
it("provides a promise", inject(function($q, $injector) {
var keystone = $injector.get('horizon.app.core.openstack-service-api.keystone');
var deferred = $q.defer();
spyOn(keystone, 'getDomain').and.returnValue(deferred.promise);
var result = service.getDomainPromise({});
deferred.resolve({id: 1, name: 'test_domain'});
expect(keystone.getDomain).toHaveBeenCalled();
expect(result.$$state.value.name).toBe('test_domain');
}));
});
});
})();

View File

@ -0,0 +1,4 @@
<hz-resource-panel resource-type-name="OS::Keystone::Domain">
<hz-resource-table resource-type-name="OS::Keystone::Domain">
</hz-resource-table>
</hz-resource-panel>

View File

@ -26,6 +26,7 @@
*/ */
angular angular
.module('horizon.dashboard.identity', [ .module('horizon.dashboard.identity', [
'horizon.dashboard.identity.domains',
'horizon.dashboard.identity.users', 'horizon.dashboard.identity.users',
'horizon.dashboard.identity.projects', 'horizon.dashboard.identity.projects',
'horizon.dashboard.identity.roles' 'horizon.dashboard.identity.roles'

View File

@ -747,7 +747,8 @@ SECURITY_GROUP_RULES = {
# See: https://wiki.openstack.org/wiki/Horizon/RESTAPI # See: https://wiki.openstack.org/wiki/Horizon/RESTAPI
REST_API_REQUIRED_SETTINGS = ['OPENSTACK_HYPERVISOR_FEATURES', REST_API_REQUIRED_SETTINGS = ['OPENSTACK_HYPERVISOR_FEATURES',
'LAUNCH_INSTANCE_DEFAULTS', 'LAUNCH_INSTANCE_DEFAULTS',
'OPENSTACK_IMAGE_FORMATS'] 'OPENSTACK_IMAGE_FORMATS',
'OPENSTACK_KEYSTONE_DEFAULT_DOMAIN']
# Additional settings can be made available to the client side for # Additional settings can be made available to the client side for
# extensibility by specifying them in REST_API_ADDITIONAL_SETTINGS # extensibility by specifying them in REST_API_ADDITIONAL_SETTINGS