Merge "Update Angular Users Table use registry"

This commit is contained in:
Jenkins 2016-11-18 20:07:23 +00:00 committed by Gerrit Code Review
commit de9d8fd56e
19 changed files with 138 additions and 487 deletions

View File

@ -434,6 +434,7 @@ Default::
{
'images_panel': True,
'flavors_panel': False,
'users_panel': False,
}
A dictionary of currently available AngularJS features. This allows simple

View File

@ -1,24 +0,0 @@
# Copyright 2015 IBM Corp.
#
# 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.
from django.utils.translation import ugettext_lazy as _
import horizon
class NGUsers(horizon.Panel):
name = _("Users")
slug = 'ngusers'
policy_rules = (("identity", "identity:get_user"),
("identity", "identity:list_users"))

View File

@ -1,15 +0,0 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Users" %}{% endblock %}
{% block page_header %}
<hz-page-header header="{$ 'Users' | translate $}"></hz-page-header>
{% endblock %}
{% block ng_route_base %}
<base href="{{ WEBROOT }}">
{% endblock %}
{% block main %}
<ng-include src="'{{ STATIC_URL }}dashboard/identity/users/table/table.html'"></ng-include>
{% endblock %}

View File

@ -1,22 +0,0 @@
# Copyright 2015 IBM Corp.
#
# 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.
from django.conf.urls import url
from openstack_dashboard.dashboards.identity.ngusers import views
urlpatterns = [
url(r'^$', views.IndexView.as_view(), name='index'),
]

View File

@ -1,19 +0,0 @@
# Copyright 2015 IBM Corp.
#
# 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.
from django.views import generic
class IndexView(generic.TemplateView):
template_name = 'identity/ngusers/index.html'

View File

@ -28,17 +28,6 @@
.module('horizon.dashboard.identity', [
'horizon.dashboard.identity.users',
'horizon.dashboard.identity.projects'
])
.config(config);
config.$inject = [
'$provide',
'$windowProvider'
];
function config($provide, $windowProvider) {
var path = $windowProvider.$get().STATIC_URL + 'dashboard/identity/';
$provide.constant('horizon.dashboard.identity.basePath', path);
}
]);
})();

View File

@ -1,39 +0,0 @@
/**
* Copyright 2015 IBM Corp.
*
* 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.dashboard.identity', function() {
it('should exist', function() {
expect(angular.module('horizon.dashboard.identity')).toBeDefined();
});
});
describe('horizon.dashboard.identity.basePath constant', function() {
var identityBasePath, staticUrl;
beforeEach(module('horizon.dashboard.identity'));
beforeEach(inject(function($injector) {
identityBasePath = $injector.get('horizon.dashboard.identity.basePath');
staticUrl = $injector.get('$window').STATIC_URL;
}));
it('should equal to "/static/dashboard/identity/"', function() {
expect(identityBasePath).toEqual(staticUrl + 'dashboard/identity/');
});
});
})();

View File

@ -0,0 +1,14 @@
<div class="row">
<dl class="col-sm-2">
<dt translate>Domain ID</dt>
<dd>{$ item.domain_id | noValue $}</dd>
</dl>
<dl class="col-sm-2">
<dt translate>Primary Project ID</dt>
<dd>{$ item.tenantId || user.default_project_id | noValue $}</dd>
</dl>
<dl class="col-sm-2">
<dt translate>Description</dt>
<dd>{$ item.description | noValue $}</dd>
</dl>
</div>

View File

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

View File

@ -1,77 +0,0 @@
/**
* Copyright 2015 IBM Corp.
*
* 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 identityUsersTableController
* @ngController
*
* @description
* Controller for the identity users table.
* Serve as the focal point for table actions.
*/
angular
.module('horizon.dashboard.identity.users')
.controller('identityUsersTableController', identityUsersTableController);
identityUsersTableController.$inject = [
'horizon.framework.widgets.toast.service',
'horizon.framework.util.i18n.gettext',
'horizon.app.core.openstack-service-api.policy',
'horizon.app.core.openstack-service-api.keystone'
];
function identityUsersTableController(toast, gettext, policy, keystone) {
var ctrl = this;
ctrl.users = [];
ctrl.iusers = [];
ctrl.userSession = {};
ctrl.checked = {};
init();
////////////////////////////////
function init() {
// if user has permission
// fetch table data and populate it
var rules = [['identity', 'identity:list_users']];
policy.ifAllowed({ rules: rules }).then(policySuccess, policyFailed);
}
function policySuccess() {
keystone.getUsers().success(getUsersSuccess);
keystone.getCurrentUserSession().success(getSessionSuccess);
}
function policyFailed() {
var msg = gettext('Insufficient privilege level to view user information.');
toast.add('info', msg);
}
function getUsersSuccess(response) {
ctrl.users = response.items;
}
function getSessionSuccess(response) {
ctrl.userSession = response;
}
}
})();

View File

@ -1,94 +0,0 @@
/**
* Copyright 2015 IBM Corp.
*
* 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 users table controller', function() {
var policy = { allowed: true };
function fakePolicy() {
return {
then: function(successFn, errorFn) {
if (policy.allowed) {
successFn();
} else {
errorFn();
}
}
};
}
function fakePromise() {
return { success: angular.noop };
}
function fakeToast() {
return { add: angular.noop };
}
var controller, toastService, policyAPI, keystoneAPI;
///////////////////////
beforeEach(module('horizon.framework.util.filters'));
beforeEach(module('horizon.framework.util.http'));
beforeEach(module('horizon.framework.util.i18n'));
beforeEach(module('horizon.framework.conf'));
beforeEach(module('horizon.framework.widgets.toast'));
beforeEach(module('horizon.app.core.openstack-service-api'));
beforeEach(module('horizon.dashboard.identity'));
beforeEach(module('horizon.dashboard.identity.users'));
beforeEach(inject(function($injector) {
toastService = $injector.get('horizon.framework.widgets.toast.service');
policyAPI = $injector.get('horizon.app.core.openstack-service-api.policy');
keystoneAPI = $injector.get('horizon.app.core.openstack-service-api.keystone');
controller = $injector.get('$controller');
spyOn(toastService, 'add').and.callFake(fakeToast);
spyOn(policyAPI, 'ifAllowed').and.callFake(fakePolicy);
spyOn(keystoneAPI, 'getUsers').and.callFake(fakePromise);
spyOn(keystoneAPI, 'getCurrentUserSession').and.callFake(fakePromise);
}));
function createController() {
return controller('identityUsersTableController', {
toast: toastService,
policyAPI: policyAPI,
keystoneAPI: keystoneAPI
});
}
it('should invoke keystone apis if policy passes', function() {
policy.allowed = true;
createController();
expect(policyAPI.ifAllowed).toHaveBeenCalled();
expect(keystoneAPI.getUsers).toHaveBeenCalled();
expect(keystoneAPI.getCurrentUserSession).toHaveBeenCalled();
});
it('should not invoke keystone apis if policy fails', function() {
policy.allowed = false;
createController();
expect(policyAPI.ifAllowed).toHaveBeenCalled();
expect(toastService.add).toHaveBeenCalled();
expect(keystoneAPI.getUsers).not.toHaveBeenCalled();
expect(keystoneAPI.getCurrentUserSession).not.toHaveBeenCalled();
});
});
})();

View File

@ -1,116 +0,0 @@
<table ng-controller="identityUsersTableController as table"
hz-table ng-cloak
st-table="table.iusers"
st-safe-src="table.users"
class="table table-striped table-rsp table-detail">
<thead>
<!--
Table-batch-actions:
This is where batch actions like searching, creating, and deleting.
-->
<tr>
<th colspan="100" class="search-header">
<hz-search-bar icon-classes="fa-search"></hz-search-bar>
</th>
</tr>
<!--
Table-column-headers:
This is where we declaratively define the table column headers.
Include select-col if you want to select all.
Include expander if you want to inline details.
Include action-col if you want to perform actions.
-->
<tr>
<th class="select-col">
<div class="themable-checkbox">
<input id="users-select-col" type="checkbox" hz-select-all="table.iusers">
<label for="users-select-col"></label>
</div>
</th>
<th class="expander"></th>
<th class="rsp-p1" st-sort="name" st-sort-default="name" translate>User Name</th>
<th class="rsp-p2" st-sort="email" translate>Email</th>
<th class="rsp-p1" st-sort="tenantId||default_project_id" translate>User ID</th>
<th class="rsp-p2 text-center" st-sort="enabled" translate>Enabled</th>
</tr>
</thead>
<tbody>
<!--
Table-rows:
This is where we declaratively define the table columns.
Include select-col if you want to select all.
Include expander if you want to inline details.
Include action-col if you want to perform actions.
rsp-p1 rsp-p2 are responsive priority as user resizes window.
-->
<tr ng-repeat-start="user in table.iusers track by user.id"
ng-class="{'st-selected': checked[user.id]}">
<td class="select-col">
<div class="themable-checkbox">
<input id="user-{$ user.id $}" type="checkbox" ng-model="tCtrl.selections[user.id].checked" hz-select="user">
<label for="user-{$ user.id $}"></label>
</div>
</td>
<td class="expander">
<span class="fa fa-chevron-right" hz-expand-detail duration="200"></span>
</td>
<td class="rsp-p1">{$ user.name $}</td>
<td class="rsp-p2"><a ng-href="mailto:{$user.email$}">{$ user.email $}</a></td>
<td class="rsp-p1">{$ user.id $}</td>
<td class="rsp-p2 text-center">
<span ng-show="user.enabled" class="fa fa-check text-success"></span>
<span ng-hide="user.enabled" class="fa fa-times text-danger"></span>
</td>
</tr>
<!--
Detail-row:
Contains detailed information on this item.
Can be toggled using the chevron button.
Ensure colspan is greater or equal to number of column-headers.
-->
<tr ng-repeat-end class="detail-row">
<td class="detail" colspan="100">
<!--
The responsive columns that disappear typically should reappear here
with the same responsive priority that they disappear.
E.g. table header with rsp-p2 should be here with rsp-alt-p2
The layout should minimize vertical space to reduce scrolling.
-->
<div class="row">
<dl ng-if="user.domain_id" class="col-sm-2">
<dt translate>Domain ID</dt>
<dd>{$ user.domain_id $}</dd>
</dl>
<dl class="col-sm-2">
<dt translate>Project ID</dt>
<dd>{$ user.tenantId || user.default_project_id | noValue $}</dd>
</dl>
<span class="rsp-alt-p2">
<dl class="col-sm-2">
<dt translate>Email</dt>
<dd>{$ user.email | noValue $}</dd>
</dl>
<dl class="col-sm-2">
<dt translate>Enabled</dt>
<dd>{$ user.enabled | yesno $}</dd>
</dl>
</span>
</div>
</td>
</tr>
<tr hz-no-items items="table.iusers"></tr>
</tbody>
<!--
Table-footer:
This is where we display number of items and pagination controls.
-->
<tfoot hz-table-footer items="table.iusers"></tfoot>
</table>

View File

@ -1,5 +1,6 @@
/**
* Copyright 2015 IBM Corp.
* Copyright 2016 99Cloud
*
* 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
@ -18,14 +19,103 @@
'use strict';
/**
* @ngdoc horizon.dashboard.identity.users
* @ngModule
* @ngdoc overview
* @ngname horizon.dashboard.identity.users
*
* @description
* Provides all of the services and widgets required
* to support and display the identity users panel.
* to support and display users related content.
*/
angular
.module('horizon.dashboard.identity.users', []);
.module('horizon.dashboard.identity.users', [
'ngRoute'
])
.constant('horizon.dashboard.identity.users.resourceType', 'OS::Keystone::User')
.run(run)
.config(config);
run.$inject = [
'horizon.framework.conf.resource-type-registry.service',
'horizon.app.core.openstack-service-api.keystone',
'horizon.dashboard.identity.users.basePath',
'horizon.dashboard.identity.users.resourceType'
];
function run(registry, keystone, basePath, userResourceType) {
registry.getResourceType(userResourceType)
.setNames(gettext('User'), gettext('Users'))
.setSummaryTemplateUrl(basePath + 'details/drawer.html')
.setProperties(userProperties())
.setListFunction(listFunction)
.tableColumns
.append({
id: 'name',
priority: 1,
sortDefault: true,
urlFunction: urlFunction
})
.append({
id: 'email',
priority: 2,
filters: ['noValue'],
template: '<a ng-href="mailto:{$ item.email $}">{$ item.email $}</a>'
})
.append({
id: 'id',
priority: 1
})
.append({
id: 'enabled',
priority: 2,
filters: ['yesno']
});
function listFunction() {
return keystone.getUsers();
}
function urlFunction(item) {
return 'identity/ngdetails/OS::Keystone::User/' + item.id;
}
/**
* @name userProperties
* @description resource properties for user module
*/
function userProperties() {
return {
name: gettext('Name'),
email: gettext('Email'),
id: gettext('ID'),
enabled: gettext('Enabled'),
domain_id: gettext('Domain ID'),
domain_name: gettext('Domain Name'),
description: gettext('Description'),
project_id: gettext('Primary Project ID')
};
}
}
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/users/';
$provide.constant('horizon.dashboard.identity.users.basePath', path);
$routeProvider.when('/identity/users', {
templateUrl: path + 'panel.html'
});
}
})();

View File

@ -1,25 +0,0 @@
/**
* Copyright 2015 IBM Corp.
*
* 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.dashboard.identity.users', function() {
it('should exist', function() {
expect(angular.module('horizon.dashboard.identity.users')).toBeDefined();
});
});
})();

View File

@ -16,18 +16,26 @@
# 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.identity.users import views
urlpatterns = [
url(r'^$', views.IndexView.as_view(), name='index'),
url(r'^(?P<user_id>[^/]+)/update/$',
views.UpdateView.as_view(), name='update'),
url(r'^create/$', views.CreateView.as_view(), name='create'),
url(r'^(?P<user_id>[^/]+)/detail/$',
views.DetailView.as_view(), name='detail'),
url(r'^(?P<user_id>[^/]+)/change_password/$',
views.ChangePasswordView.as_view(), name='change_password'),
]
if settings.ANGULAR_FEATURES.get('users_panel', False):
# new angular panel
urlpatterns = [
url(r'^$', views.AngularIndexView.as_view(), name='index'),
]
else:
urlpatterns = [
url(r'^$', views.IndexView.as_view(), name='index'),
url(r'^(?P<user_id>[^/]+)/update/$',
views.UpdateView.as_view(), name='update'),
url(r'^create/$', views.CreateView.as_view(), name='create'),
url(r'^(?P<user_id>[^/]+)/detail/$',
views.DetailView.as_view(), name='detail'),
url(r'^(?P<user_id>[^/]+)/change_password/$',
views.ChangePasswordView.as_view(), name='change_password'),
]

View File

@ -25,6 +25,7 @@ from django.core.urlresolvers import reverse_lazy
from django.utils.decorators import method_decorator # noqa
from django.utils.translation import ugettext_lazy as _
from django.views.decorators.debug import sensitive_post_parameters # noqa
from django.views import generic
from horizon import exceptions
from horizon import forms
@ -44,6 +45,10 @@ from openstack_dashboard.dashboards.identity.users \
LOG = logging.getLogger(__name__)
class AngularIndexView(generic.TemplateView):
template_name = 'angular.html'
class IndexView(tables.DataTableView):
table_class = project_tables.UsersTable
template_name = 'identity/users/index.html'

View File

@ -1,30 +0,0 @@
# Copyright 2015 IBM Corp.
#
# 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.
# The slug of the dashboard the PANEL associated with. Required.
PANEL_DASHBOARD = 'identity'
# The slug of the panel group the PANEL is associated with.
# If you want the panel to show up without a panel group,
# use the panel group "default".
PANEL_GROUP = 'default'
# The slug of the panel to be added to HORIZON_CONFIG. Required.
PANEL = 'ngusers'
# If set to True, this settings file will not be added to the settings.
DISABLED = True
# Python panel class of the PANEL to be added.
ADD_PANEL = 'openstack_dashboard.dashboards.identity.ngusers.panel.NGUsers'

View File

@ -323,6 +323,7 @@ COMPRESS_OFFLINE_CONTEXT = 'horizon.themes.offline_context'
ANGULAR_FEATURES = {
'images_panel': True,
'flavors_panel': False,
'users_panel': False,
}
# Notice all customizable configurations should be above this line