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,
'flavors_panel': False,
'users_panel': False,
'domains_panel': False
}
A dictionary of currently available AngularJS features. This allows simple
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:

View File

@ -12,14 +12,24 @@
# License for the specific language governing permissions and limitations
# under the License.
from django.conf import settings
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 = [
url(r'^$', views.IndexView.as_view(), name='index'),
url(r'^create$', views.CreateDomainView.as_view(), name='create'),
url(r'^(?P<domain_id>[^/]+)/update/$',
views.UpdateDomainView.as_view(), name='update')
]
if settings.ANGULAR_FEATURES.get('domains_panel'):
title = _("Domains")
urlpatterns = [
url('', views.AngularIndexView.as_view(title=title), name='index'),
]
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
.module('horizon.dashboard.identity', [
'horizon.dashboard.identity.domains',
'horizon.dashboard.identity.users',
'horizon.dashboard.identity.projects',
'horizon.dashboard.identity.roles'

View File

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