diff --git a/assembly-multiuser/assembly-main/pom.xml b/assembly-multiuser/assembly-main/pom.xml index 561a197399..8a3a949a6b 100644 --- a/assembly-multiuser/assembly-main/pom.xml +++ b/assembly-multiuser/assembly-main/pom.xml @@ -42,6 +42,11 @@ assembly-wsmaster-war war + + org.eclipse.che.dashboard + che-dashboard-war + war + org.postgresql postgresql @@ -95,22 +100,4 @@ - - - withDashboard - - - withoutDashboard - !true - - - - - org.eclipse.che.assembly-multiuser - che-dashboard-war - war - - - - diff --git a/assembly-multiuser/assembly-main/src/assembly/assembly.xml b/assembly-multiuser/assembly-main/src/assembly/assembly.xml index deedbdf12e..1a3ebe7e8a 100644 --- a/assembly-multiuser/assembly-main/src/assembly/assembly.xml +++ b/assembly-multiuser/assembly-main/src/assembly/assembly.xml @@ -55,7 +55,7 @@ tomcat/webapps dashboard.war - org.eclipse.che.assembly-multiuser:che-dashboard-war + org.eclipse.che.dashboard:che-dashboard-war diff --git a/assembly-multiuser/pom.xml b/assembly-multiuser/pom.xml index d8aea82fb8..92b6a304d3 100644 --- a/assembly-multiuser/pom.xml +++ b/assembly-multiuser/pom.xml @@ -188,18 +188,4 @@ - - - withDashboard - - - withoutDashboard - !true - - - - dashboard - - - diff --git a/dashboard/bower.json b/dashboard/bower.json index 4304e0c907..cedf5aa099 100644 --- a/dashboard/bower.json +++ b/dashboard/bower.json @@ -14,6 +14,7 @@ "angular-charts": "0.2.6", "angular-cookies": "1.4.8", "angular-dropdowns": "1.0.0", + "angular-gravatar": "0.2.4", "angular-filter": "0.5.4", "angular-material": "1.0.1", "angular-moment": "0.9.0", diff --git a/dashboard/gulp/proxy.js b/dashboard/gulp/proxy.js index ea7dc1d0ab..6b0ef4aa23 100644 --- a/dashboard/gulp/proxy.js +++ b/dashboard/gulp/proxy.js @@ -22,10 +22,9 @@ var serverOptions = { var options = minimist(process.argv.slice(2), serverOptions); -var patterns = ['/api', '/ext', '/ws', '/datasource', '/java-ca', '/im', '/che', '/admin']; - -var proxies = [] +var patterns = ['/api', '/ext', '/ws', '/datasource', '/java-ca', '/im', '/che', '/admin', '/wsmaster']; +var proxies = []; patterns.forEach(function(pattern) { var proxyOptions = url.parse(options.server + pattern); @@ -37,6 +36,8 @@ patterns.forEach(function(pattern) { proxyOptions.route = '/admin'; } else if (pattern === '/ext') { proxyOptions.route = '/ext'; + } else if (pattern === '/wsmaster') { + proxyOptions.route = '/wsmaster'; } else { proxyOptions.route = '/api'; } diff --git a/dashboard/src/app/admin/admin-config.ts b/dashboard/src/app/admin/admin-config.ts index b089d0114e..e06893708f 100644 --- a/dashboard/src/app/admin/admin-config.ts +++ b/dashboard/src/app/admin/admin-config.ts @@ -11,15 +11,16 @@ 'use strict'; import {AdminsPluginsConfig} from './plugins/plugins-config'; +import {AdminsUserManagementConfig} from './user-management/user-management-config'; /** * @author Florent Benoit */ export class AdminsConfig { - constructor(register) { + constructor(register: che.IRegisterService) { new AdminsPluginsConfig(register); - + new AdminsUserManagementConfig(register); } } diff --git a/dashboard/src/app/admin/user-management/account-profile/account-profile.directive.ts b/dashboard/src/app/admin/user-management/account-profile/account-profile.directive.ts new file mode 100644 index 0000000000..181e6356a9 --- /dev/null +++ b/dashboard/src/app/admin/user-management/account-profile/account-profile.directive.ts @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2015-2017 Red Hat, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +'use strict'; + +interface IAccountProfileScope extends ng.IScope { + profileAttributes: { + phone?: string; + country?: string; + employer?: string; + jobtitle?: string; + lastName?: string; + firstName?: string; + }; + profileInformationForm: ng.IFormController; + countries?: Array<{ 'name': string, 'code': string }>; + jobs?: Array<{ 'name': string }>; +} + +/** + * @ngdoc directive + * @name account.profile.directive:accountProfile + * @restrict E + * @element + * + * @description + * ` for displaying account profile. + * + * @usage + * + * + * @author Florent Benoit + */ +export class AccountProfile implements ng.IDirective { + restrict = 'E'; + templateUrl = 'app/account/account-profile/account-profile.html'; + replace = true; + scope = { + profileAttributes: '=profileAttributes', + profileInformationForm: '=?profileInformationForm' + }; + + jsonCountries: string; + jsonJobs: string; + + /** + * Default constructor that is using resource + * @ngInject for Dependency injection + */ + constructor(jsonCountries: string, jsonJobs: string) { + this.jsonCountries = jsonCountries; + this.jsonJobs = jsonJobs; + } + + link($scope: IAccountProfileScope) { + $scope.countries = angular.fromJson(this.jsonCountries); + $scope.jobs = angular.fromJson(this.jsonJobs); + } +} diff --git a/dashboard/src/app/admin/user-management/account-profile/account-profile.html b/dashboard/src/app/admin/user-management/account-profile/account-profile.html new file mode 100644 index 0000000000..573bfb10cd --- /dev/null +++ b/dashboard/src/app/admin/user-management/account-profile/account-profile.html @@ -0,0 +1,67 @@ + diff --git a/dashboard/src/app/admin/user-management/account-profile/account-profile.styl b/dashboard/src/app/admin/user-management/account-profile/account-profile.styl new file mode 100644 index 0000000000..2ba6ba3599 --- /dev/null +++ b/dashboard/src/app/admin/user-management/account-profile/account-profile.styl @@ -0,0 +1,8 @@ +.account-profile + padding 0 14px + + .che-input-desktop + margin-top -1px + + .che-select + margin-top -2px diff --git a/dashboard/src/app/admin/user-management/add-user/add-user.controller.ts b/dashboard/src/app/admin/user-management/add-user/add-user.controller.ts new file mode 100644 index 0000000000..c1fa184fd0 --- /dev/null +++ b/dashboard/src/app/admin/user-management/add-user/add-user.controller.ts @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2015-2017 Red Hat, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +'use strict'; +import {AdminsUserManagementCtrl} from '../user-management.controller'; + +/** + * This class is handling the controller for the add user + * @author Oleksii Orel + */ +export class AdminsAddUserController { + private $mdDialog: ng.material.IDialogService; + private lodash: any; + private cheNotification: any; + private cheUser: any; + private callbackController: AdminsUserManagementCtrl; + private newUserName: string; + private newUserEmail: string; + private newUserPassword: string; + private organizations: Array; + private organization: string; + private cheOrganization: che.api.ICheOrganization; + private chePermissions: che.api.IChePermissions; + private organizationRoles: che.resource.ICheOrganizationRoles; + + /** + * Default constructor. + * @ngInject for Dependency injection + */ + constructor($mdDialog: ng.material.IDialogService, + cheUser: any, + cheNotification: any, + lodash: any, + cheOrganization: che.api.ICheOrganization, + chePermissions: che.api.IChePermissions, + resourcesService: che.service.IResourcesService) { + this.$mdDialog = $mdDialog; + this.lodash = lodash; + this.cheUser = cheUser; + this.cheNotification = cheNotification; + this.cheOrganization = cheOrganization; + this.chePermissions = chePermissions; + this.organizationRoles = resourcesService.getOrganizationRoles(); + + this.organizations = []; + + this.cheOrganization.fetchOrganizations().then(() => { + let organizations = this.cheOrganization.getOrganizations(); + let rootOrganizations = organizations.filter((organization: any) => { + return !organization.parent; + }); + this.organizations = lodash.pluck(rootOrganizations, 'name'); + if (this.organizations.length > 0) { + this.organization = this.organizations[0]; + } + }); + } + + /** + * Callback of the cancel button of the dialog. + */ + abort(): void { + this.$mdDialog.hide(); + } + + /** + * Callback of the add button of the dialog(create new user). + */ + createUser(): void { + let promise = this.cheUser.createUser(this.newUserName, this.newUserEmail, this.newUserPassword); + + promise.then((data: any) => { + if (this.organization) { + this.addUserToOrganization(data.id); + } else { + this.finish(); + } + }, (error: any) => { + this.cheNotification.showError(error.data.message ? error.data.message : 'Failed to create user.'); + }); + } + + /** + * Finish user creation. + */ + private finish(): void { + this.$mdDialog.hide(); + this.callbackController.updateUsers(); + this.cheNotification.showInfo('User successfully created.'); + } + + /** + * Adds user to chosen organization. + * + * @param userId + */ + private addUserToOrganization(userId: string): void { + let organizations = this.cheOrganization.getOrganizations(); + let organization = this.lodash.find(organizations, (organization: any) => { + return organization.name === this.organization; + }); + + let actions = this.organizationRoles.MEMBER.actions; + let permissions = { + instanceId: organization.id, + userId: userId, + domainId: 'organization', + actions: actions + }; + this.chePermissions.storePermissions(permissions).then(() => { + this.finish(); + }, (error: any) => { + this.cheNotification.showError(error.data.message ? error.data.message : 'Failed to add user to organization' + this.organization + '.'); + }); + } +} diff --git a/dashboard/src/app/admin/user-management/add-user/add-user.html b/dashboard/src/app/admin/user-management/add-user/add-user.html new file mode 100644 index 0000000000..6989bfcdf4 --- /dev/null +++ b/dashboard/src/app/admin/user-management/add-user/add-user.html @@ -0,0 +1,95 @@ + + + +
+ +
User login to be less than 100 characters long.
+
+ +
Enter a valid email address.
+
User email has to be less than 100 characters long
+
+
+ + +
+ + +
User password should contain both letters and digits
+
User password should contain at least 8 characters.
+
User password has to be less than 100 characters long.
+
+
+
+ Minimum 8 characters, both letters and digits. +
+
+
+
+
+ +
Confirm password has to be less than 100 characters long.
+
+ Passwords do not match. +
+
+ +
+
+ + + + +
+
+
diff --git a/dashboard/src/app/admin/user-management/add-user/add-user.styl b/dashboard/src/app/admin/user-management/add-user/add-user.styl new file mode 100644 index 0000000000..7383dc934c --- /dev/null +++ b/dashboard/src/app/admin/user-management/add-user/add-user.styl @@ -0,0 +1,48 @@ +.admins-add-user-form + min-width 520px + + .form-input-fields + margin 20px 0px + + che-filter-selector + margin-bottom 20px + line-height 33px + + .che-input-box-desktop + width 520px + max-width 520px + + label + max-width 23% !important + width 23% !important + margin-top 10px + + input + width 400px + float right + +.admins-add-user-form .che-input-desktop-label + margin-top 1px + +.admins-add-user-form .password-prompt, +.admins-add-user-form .che-input-desktop-label + min-width 125px + +.admins-add-user-form .password-prompt + font-size 12px + color $disabled-color + margin-bottom 20px + +.admins-add-user-form .password-prompt p + margin-top 5px + margin-bottom 5px + +.admins-add-user-form .password-prompt + .password-prompt-text + min-width 125px + + .pass-strength + width 100% + + & > div + margin-bottom 10px diff --git a/dashboard/src/app/admin/user-management/user-details/user-details.controller.ts b/dashboard/src/app/admin/user-management/user-details/user-details.controller.ts new file mode 100644 index 0000000000..253bd10124 --- /dev/null +++ b/dashboard/src/app/admin/user-management/user-details/user-details.controller.ts @@ -0,0 +1,243 @@ +/* + * Copyright (c) 2015-2017 Red Hat, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +'use strict'; + +enum Tab {Profile, Organization} + +interface IScope extends ng.IScope { + profileInformationForm: ng.IFormController; +} + +interface IProfileAttributes { + firstName?: string; + lastName?: string; + phone?: string; + country?: string; + employer?: string; + jobtitle?: string; +} + +const MAX_ITEMS = 12; + +/** + * Controller for user details. + * + * @author Oleksii Orel + */ +export class AdminUserDetailsController { + tab: Object = Tab; + + /** + * Angular Location service. + */ + private $location: ng.ILocationService; + /** + * User profile service. + */ + private cheProfile: any; + /** + * Notification service. + */ + private cheNotification: any; + /** + * Index of the selected tab. + */ + private selectedTabIndex: number = 0; + /** + * User profile. + */ + private profile: che.IProfile; + /** + * Profile attributes. + */ + private profileAttributes: IProfileAttributes; + /** + * Loading state of the page. + */ + private isLoading: boolean; + /** + * User ID. + */ + private userId: string; + /** + * User Name. + */ + private userName: string; + + private cheOrganization: che.api.ICheOrganization; + + private userOrganizations: Array; + /** + * User's page info. + */ + private pageInfo: che.IPageInfo; + + /** + * Default constructor that is using resource injection + * @ngInject for Dependency injection + */ + constructor(cheProfile: any, $location: ng.ILocationService, $timeout: ng.ITimeoutService, $scope: ng.IScope, cheNotification: any, cheOrganization: che.api.ICheOrganization, initData: {userId; userName}) { + this.cheOrganization = cheOrganization; + this.$location = $location; + this.cheProfile = cheProfile; + this.cheNotification = cheNotification; + this.userId = initData.userId; + this.userName = initData.userName; + + this.updateSelectedTab(this.$location.search().tab); + let deRegistrationFn = $scope.$watch(() => { + return $location.search().tab; + }, (tab: string) => { + if (!angular.isUndefined(tab)) { + this.updateSelectedTab(tab); + } + }, true); + + let timeoutPromise: ng.IPromise; + $scope.$watch(() => { + return angular.isUndefined(this.profileAttributes) || this.profileAttributes; + }, () => { + if (!this.profileAttributes || !($scope).profileInformationForm || ($scope).profileInformationForm.$invalid) { + return; + } + if (timeoutPromise) { + $timeout.cancel(timeoutPromise); + } + timeoutPromise = $timeout(() => { + this.setProfileAttributes(); + }, 500); + }, true); + + $scope.$on('$destroy', () => { + deRegistrationFn(); + if (timeoutPromise) { + $timeout.cancel(timeoutPromise); + } + }); + + this.updateData(); + } + + /** + * Update user's data. + */ + updateData(): void { + this.isLoading = true; + this.cheProfile.fetchProfileById(this.userId).then(() => { + this.profile = this.cheProfile.getProfileById(this.userId); + this.profileAttributes = angular.copy(this.profile.attributes); + this.fetchOrganizations(); + }, (error: any) => { + this.isLoading = false; + this.cheNotification.showError(error && error.data && error.data.message !== null ? error.data.message : 'Failed to retrieve user\'s profile.'); + }); + } + + /** + * Request the list of the user's organizations (first page). + * + * @returns {ng.IPromise} + */ + fetchOrganizations(): void { + this.isLoading = true; + this.cheOrganization.fetchUserOrganizations(this.userId, MAX_ITEMS).then((userOrganizations: Array) => { + this.userOrganizations = userOrganizations; + }, (error: any) => { + this.cheNotification.showError(error && error.data && error.data.message !== null ? error.data.message : 'Failed to retrieve organizations.'); + }).finally(() => { + this.isLoading = false; + this.pageInfo = this.cheOrganization.getUserOrganizationPageInfo(this.userId); + }); + } + + /** + * Returns the array of user's organizations. + * + * @returns {Array} + */ + getUserOrganizations(): Array { + return this.userOrganizations; + } + + /** + * Returns the the user's page info. + * + * @returns {che.IPageInfo} + */ + getPagesInfo(): che.IPageInfo { + return this.pageInfo; + } + + /** + * Request the list of the user's organizations for a page depends on page key('first', 'prev', 'next', 'last'). + * @param key {string} + */ + fetchOrganizationPageObjects(key: string): void { + this.isLoading = true; + this.cheOrganization.fetchUserOrganizationPageObjects(this.userId, key).then((userOrganizations: Array) => { + this.userOrganizations = userOrganizations; + }).finally(() => { + this.isLoading = false; + }); + } + + /** + * Check if profile attributes have changed + * @returns {boolean} + */ + isAttributesChanged(): boolean { + return !angular.equals(this.profile.attributes, this.profileAttributes); + } + + /** + * Set profile attributes + */ + setProfileAttributes(): void { + if (angular.equals(this.profile.attributes, this.profileAttributes)) { + return; + } + let promise = this.cheProfile.setAttributes(this.profileAttributes, this.userId); + + promise.then(() => { + this.cheNotification.showInfo('Profile successfully updated.'); + this.updateData(); + }, (error: any) => { + this.profileAttributes = angular.copy(this.profile.attributes); + this.cheNotification.showError(error.data.message ? error.data.message : 'Profile update failed.'); + }); + } + + /** + * Update selected tab index by search part of URL. + * + * @param {string} tab + */ + updateSelectedTab(tab: string): void { + this.selectedTabIndex = parseInt(this.tab[tab], 10); + } + + /** + * Changes search part of URL. + * + * @param {number} tabIndex + */ + onSelectTab(tabIndex?: number): void { + let param: { tab?: string } = {}; + if (!angular.isUndefined(tabIndex)) { + param.tab = Tab[tabIndex]; + } + if (angular.isUndefined(this.$location.search().tab)) { + this.$location.replace(); + } + this.$location.search(param); + } + +} diff --git a/dashboard/src/app/admin/user-management/user-details/user-details.html b/dashboard/src/app/admin/user-management/user-details/user-details.html new file mode 100644 index 0000000000..8cd58b10dc --- /dev/null +++ b/dashboard/src/app/admin/user-management/user-details/user-details.html @@ -0,0 +1,45 @@ + + + + + + + + + + Profile + + +
+ +
+ +
+
+ + + + + Organizations + + +
+ +
+ + +
+
+ +
+
diff --git a/dashboard/src/app/admin/user-management/user-management-config.ts b/dashboard/src/app/admin/user-management/user-management-config.ts new file mode 100644 index 0000000000..0375f9528e --- /dev/null +++ b/dashboard/src/app/admin/user-management/user-management-config.ts @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2015-2017 Red Hat, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +'use strict'; + +import {AdminsAddUserController} from './add-user/add-user.controller'; +import {AdminsUserManagementCtrl} from './user-management.controller'; +import {AdminUserDetailsController} from './user-details/user-details.controller'; +import {AccountProfile} from './account-profile/account-profile.directive'; + +export class AdminsUserManagementConfig { + + constructor(register: che.IRegisterService) { + register.controller('AdminUserDetailsController', AdminUserDetailsController); + register.controller('AdminsAddUserController', AdminsAddUserController); + register.controller('AdminsUserManagementCtrl', AdminsUserManagementCtrl); + register.directive('accountProfile', AccountProfile); + + const userDetailLocationProvider = { + title: 'User Details', + reloadOnSearch: false, + templateUrl: 'app/admin/user-management/user-details/user-details.html', + controller: 'AdminUserDetailsController', + controllerAs: 'adminUserDetailsController', + resolve: { + initData: ['$q', 'cheUser', '$route', 'chePermissions', ($q: ng.IQService, cheUser: any, $route: any, chePermissions: che.api.IChePermissions) => { + const userId = $route.current.params.userId; + let defer = $q.defer(); + chePermissions.fetchSystemPermissions().finally(() => { + cheUser.fetchUserId(userId).then((user: che.IUser) => { + if (!chePermissions.getUserServices().hasAdminUserService) { + defer.reject(); + } + defer.resolve({userId: userId, userName: user.name}); + }, (error: any) => { + defer.reject(error); + }); + }); + return defer.promise; + }] + } + }; + + // configure routes + register.app.config(($routeProvider: che.route.IRouteProvider) => { + $routeProvider.accessWhen('/admin/usermanagement', { + title: 'Users', + templateUrl: 'app/admin/user-management/user-management.html', + controller: 'AdminsUserManagementCtrl', + controllerAs: 'adminsUserManagementCtrl', + resolve: { + check: ['$q', 'chePermissions', ($q: ng.IQService, chePermissions: che.api.IChePermissions) => { + let defer = $q.defer(); + chePermissions.fetchSystemPermissions().finally(() => { + if (chePermissions.getUserServices().hasUserService) { + defer.resolve(); + } else { + defer.reject(); + } + }); + return defer.promise; + }] + } + }) + .accessWhen('/admin/userdetails/:userId', userDetailLocationProvider); + }); + + } +} diff --git a/dashboard/src/app/admin/user-management/user-management.controller.ts b/dashboard/src/app/admin/user-management/user-management.controller.ts new file mode 100644 index 0000000000..304bd2a3d1 --- /dev/null +++ b/dashboard/src/app/admin/user-management/user-management.controller.ts @@ -0,0 +1,298 @@ +/* + * Copyright (c) 2015-2017 Red Hat, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +'use strict'; + +const MAX_ITEMS = 12; + +/** + * This class is handling the controller for the admins user management + * @author Oleksii Orel + */ +export class AdminsUserManagementCtrl { + $q: ng.IQService; + $log: ng.ILogService; + $mdDialog: ng.material.IDialogService; + $location: ng.ILocationService; + cheUser: any; + cheNotification: any; + pagesInfo: any; + users: Array; + usersMap: Map; + userFilter: {name: string}; + userOrderBy: string; + isLoading: boolean; + + private confirmDialogService: any; + private cheOrganization: che.api.ICheOrganization; + private userOrganizationCount: {[userId: string]: number} = {}; + private cheListHelper: che.widget.ICheListHelper; + + /** + * Default constructor. + * @ngInject for Dependency injection + */ + constructor($q: ng.IQService, + $rootScope: che.IRootScopeService, + $log: ng.ILogService, + $mdDialog: ng.material.IDialogService, + cheUser: any, + $location: ng.ILocationService, + cheNotification: any, + confirmDialogService: any, + cheOrganization: che.api.ICheOrganization, + $scope: ng.IScope, + cheListHelperFactory: che.widget.ICheListHelperFactory) { + this.$q = $q; + this.$log = $log; + this.$mdDialog = $mdDialog; + this.$location = $location; + this.cheUser = cheUser; + this.cheOrganization = cheOrganization; + this.cheNotification = cheNotification; + this.confirmDialogService = confirmDialogService; + + $rootScope.showIDE = false; + + this.isLoading = false; + + this.users = []; + this.usersMap = this.cheUser.getUsersMap(); + + this.userOrderBy = 'name'; + this.userFilter = {name: ''}; + + const helperId = 'user-management'; + this.cheListHelper = cheListHelperFactory.getHelper(helperId); + $scope.$on('$destroy', () => { + cheListHelperFactory.removeHelper(helperId); + }); + + if (this.usersMap && this.usersMap.size > 1) { + this.updateUsers(); + } else { + this.isLoading = true; + this.cheUser.fetchUsers(MAX_ITEMS, 0).then(() => { + this.isLoading = false; + this.updateUsers(); + }, (error: any) => { + this.isLoading = false; + if (error && error.status !== 304) { + this.cheNotification.showError(error.data && error.data.message ? error.data.message : 'Failed to retrieve the list of users.'); + } + }); + } + + this.pagesInfo = this.cheUser.getPagesInfo(); + } + + /** + * Callback when name is changed. + * + * @param str {string} a string to filter user names. + */ + onSearchChanged(str: string): void { + this.userFilter.name = str; + this.cheListHelper.applyFilter('name', this.userFilter); + } + + /** + * Redirect to user details + * @param userId {string} + * @param tab {string} + */ + redirectToUserDetails(userId: string, tab?: string): void { + this.$location.path('/admin/userdetails/' + userId).search(!tab ? {} : {tab: tab}); + } + + /** + * Update user's organizations count + * @param userId {string} + */ + updateUserOrganizationsCount(userId: string): void { + this.cheOrganization.fetchUserOrganizations(userId, 1).then((userOrganizations: Array) => { + if (!angular.isArray(userOrganizations) || userOrganizations.length === 0) { + return; + } + this.userOrganizationCount[userId] = this.cheOrganization.getUserOrganizationPageInfo(userId).countPages; + }); + } + + /** + * User clicked on the - action to remove the user. Show the dialog + * @param event {MouseEvent} - the $event + * @param user {any} - the selected user + */ + removeUser(event: MouseEvent, user: any): void { + let content = 'Are you sure you want to remove \'' + user.email + '\'?'; + let promise = this.confirmDialogService.showConfirmDialog('Remove user', content, 'Delete', 'Cancel'); + + promise.then(() => { + this.isLoading = true; + let promise = this.cheUser.deleteUserById(user.id); + promise.then(() => { + this.isLoading = false; + this.updateUsers(); + }, (error: any) => { + this.isLoading = false; + this.cheNotification.showError(error.data && error.data.message ? error.data.message : 'Delete user failed.'); + }); + }); + } + + /** + * Delete all selected users + */ + deleteSelectedUsers(): void { + const selectedUsers = this.cheListHelper.getSelectedItems(), + selectedUserIds = selectedUsers.map((user: che.IUser) => { + return user.id; + }); + + const queueLength = selectedUserIds.length; + if (!queueLength) { + this.cheNotification.showError('No such user.'); + return; + } + + const confirmationPromise = this.showDeleteUsersConfirmation(queueLength); + confirmationPromise.then(() => { + const numberToDelete = queueLength; + const deleteUserPromises = []; + let isError = false; + let currentUserId; + + selectedUserIds.forEach((userId: string) => { + currentUserId = userId; + this.cheListHelper.itemsSelectionStatus[userId] = false; + + let promise = this.cheUser.deleteUserById(userId); + promise.catch((error: any) => { + isError = true; + this.$log.error('Cannot delete user: ', error); + }); + deleteUserPromises.push(promise); + }); + + this.$q.all(deleteUserPromises).finally(() => { + this.isLoading = true; + + const promise = this.cheUser.fetchUsersPage(this.pagesInfo.currentPageNumber); + promise.then(() => { + this.isLoading = false; + this.updateUsers(); + }, (error: any) => { + this.isLoading = false; + this.$log.error(error); + }); + + if (isError) { + this.cheNotification.showError('Delete failed.'); + } else { + if (numberToDelete === 1) { + this.cheNotification.showInfo('Selected user has been removed.'); + } else { + this.cheNotification.showInfo('Selected users have been removed.'); + } + } + }); + }); + } + + /** + * Show confirmation popup before delete + * @param numberToDelete {number} + * @returns {angular.IPromise} + */ + showDeleteUsersConfirmation(numberToDelete: number): angular.IPromise { + let content = 'Are you sure you want to remove ' + numberToDelete + ' selected '; + if (numberToDelete > 1) { + content += 'users?'; + } else { + content += 'user?'; + } + + return this.confirmDialogService.showConfirmDialog('Remove users', content, 'Delete', 'Cancel'); + } + + /** + * Update users array + */ + updateUsers(): void { + // update users array + this.users.length = 0; + this.usersMap.forEach((user: any) => { + this.users.push(user); + }); + + this.cheListHelper.setList(this.users, 'id'); + } + + /** + * Ask for loading the users page in asynchronous way + * @param pageKey {string} - the key of page + */ + fetchUsersPage(pageKey: string): void { + this.isLoading = true; + let promise = this.cheUser.fetchUsersPage(pageKey); + + promise.then(() => { + this.isLoading = false; + this.updateUsers(); + }, (error: any) => { + this.isLoading = false; + if (error.status === 304) { + this.updateUsers(); + } else { + this.cheNotification.showError(error.data && error.data.message ? error.data.message : 'Update information failed.'); + } + }); + } + + /** + * Returns true if the next page is exist. + * @returns {boolean} + */ + hasNextPage(): boolean { + return this.pagesInfo.currentPageNumber < this.pagesInfo.countOfPages; + } + + /** + * Returns true if the previous page is exist. + * @returns {boolean} + */ + hasPreviousPage(): boolean { + return this.pagesInfo.currentPageNumber > 1; + } + + /** + * Returns true if we have more then one page. + * @returns {boolean} + */ + isPagination(): boolean { + return this.pagesInfo.countOfPages > 1; + } + + /** + * Add a new user. Show the dialog + * @param event {MouseEvent} - the $event + */ + showAddUserDialog(event: MouseEvent): void { + this.$mdDialog.show({ + targetEvent: event, + bindToController: true, + clickOutsideToClose: true, + controller: 'AdminsAddUserController', + controllerAs: 'adminsAddUserController', + locals: {callbackController: this}, + templateUrl: 'app/admin/user-management/add-user/add-user.html' + }); + } +} diff --git a/dashboard/src/app/admin/user-management/user-management.html b/dashboard/src/app/admin/user-management/user-management.html new file mode 100644 index 0000000000..49329e16f1 --- /dev/null +++ b/dashboard/src/app/admin/user-management/user-management.html @@ -0,0 +1,147 @@ + + + +
+ +
+
+ +
+
+
+ +
+
+
+ + + + +
+
+
+ + +
+
+ +
+
+
+ Email + + {{user.email}} +
+
+ Login + {{user.name}} +
+
+ Organizations + {{adminsUserManagementCtrl.userOrganizationCount[user.id] ? adminsUserManagementCtrl.userOrganizationCount[user.id] : '-'}} +
+
+ Actions + +
+ +
+
+
+
+
+
+
+ + << + + + < + + + {{adminsUserManagementCtrl.pagesInfo.currentPageNumber}} + + + > + + + >> + +
+
+
+ + No users found. + + There are no users. +
+
+
diff --git a/dashboard/src/app/admin/user-management/user-management.styl b/dashboard/src/app/admin/user-management/user-management.styl new file mode 100644 index 0000000000..1d30dab23a --- /dev/null +++ b/dashboard/src/app/admin/user-management/user-management.styl @@ -0,0 +1,17 @@ +.admins-user-management + .user-email, + .user-description + max-width 100% + .user-description + color $label-info-color + .user-face + che-developers-face() + height 16px + width 16px + margin-right 5px + + .che-list-item + outline none + user-select none + * + outline inherit diff --git a/dashboard/src/app/index.module.ts b/dashboard/src/app/index.module.ts index 6e1f40d2c4..ad3aaf8cef 100755 --- a/dashboard/src/app/index.module.ts +++ b/dashboard/src/app/index.module.ts @@ -12,9 +12,7 @@ import {Register} from '../components/utils/register'; import {FactoryConfig} from './factories/factories-config'; - import {ComponentsConfig} from '../components/components-config'; - import {AdminsConfig} from './admin/admin-config'; import {AdministrationConfig} from './administration/administration-config'; import {DiagnosticsConfig} from './diagnostics/diagnostics-config'; @@ -33,20 +31,96 @@ import {StacksConfig} from './stacks/stacks-config'; import {DemoComponentsController} from './demo-components/demo-components.controller'; import {CheBranding} from '../components/branding/che-branding.factory'; import {ChePreferences} from '../components/api/che-preferences.factory'; - +import {RoutingRedirect} from '../components/routing/routing-redirect.factory'; +import IdeIFrameSvc from './ide/ide-iframe/ide-iframe.service'; +import {CheIdeFetcher} from '../components/ide-fetcher/che-ide-fetcher.service'; +import {RouteHistory} from '../components/routing/route-history.service'; +import {CheUIElementsInjectorService} from '../components/service/injector/che-ui-elements-injector.service'; +import {WorkspaceDetailsService} from './workspaces/workspace-details/workspace-details.service'; +import {OrganizationsConfig} from './organizations/organizations-config'; +import {TeamsConfig} from './teams/teams-config'; // init module -let initModule = angular.module('userDashboard', ['ngAnimate', 'ngCookies', 'ngTouch', 'ngSanitize', 'ngResource', 'ngRoute', +const initModule = angular.module('userDashboard', ['ngAnimate', 'ngCookies', 'ngTouch', 'ngSanitize', 'ngResource', 'ngRoute', 'angular-websocket', 'ui.bootstrap', 'ui.codemirror', 'ngMaterial', 'ngMessages', 'angularMoment', 'angular.filter', - 'ngDropdowns', 'ngLodash', 'angularCharts', 'ngClipboard', 'uuid4', 'angularFileUpload']); + 'ngDropdowns', 'ngLodash', 'angularCharts', 'ngClipboard', 'uuid4', 'angularFileUpload', 'ui.gravatar']); +declare const Keycloak: Function; +function buildKeycloakConfig(keycloakSettings: any) { + return { + url: keycloakSettings['che.keycloak.auth_server_url'], + realm: keycloakSettings['che.keycloak.realm'], + clientId: keycloakSettings['che.keycloak.client_id'] + }; +} +interface IResolveFn { + (value: T | PromiseLike): Promise; +} +interface IRejectFn { + (reason: any): Promise; +} +function keycloakLoad(keycloakSettings: any) { + return new Promise((resolve: IResolveFn, reject: IRejectFn) => { + const script = document.createElement('script'); + script.async = true; + script.src = keycloakSettings['che.keycloak.auth_server_url'] + '/js/keycloak.js'; + script.addEventListener('load', resolve); + script.addEventListener('error', () => reject('Error loading script.')); + script.addEventListener('abort', () => reject('Script loading aborted.')); + document.head.appendChild(script); + }); +} +function keycloakInit(keycloakConfig: any) { + return new Promise((resolve: IResolveFn, reject: IRejectFn) => { + const keycloak = Keycloak(keycloakConfig); + keycloak.init({ + onLoad: 'login-required', checkLoginIframe: false + }).success(() => { + resolve(keycloak); + }).error((error: any) => { + reject(error); + }); + }); +} + +const keycloakAuth = { + isPresent: false, + keycloak: null, + config: null +}; +initModule.constant('keycloakAuth', keycloakAuth); + +const promise = new Promise((resolve: IResolveFn, reject: IRejectFn) => { + angular.element.get('/api/keycloak/settings').then(resolve, reject); +}); +promise.then((keycloakSettings: any) => { + keycloakAuth.config = buildKeycloakConfig(keycloakSettings); + + // load Keycloak + return keycloakLoad(keycloakSettings).then(() => { + // init Keycloak + return keycloakInit(keycloakAuth.config); + }).then((keycloak: any) => { + keycloakAuth.isPresent = true; + keycloakAuth.keycloak = keycloak; + /* tslint:disable */ + window['_keycloak'] = keycloak; + /* tslint:enable */ + }); +}).catch((error: any) => { + console.error('Keycloak initialization failed with error: ', error); +}).then(() => { + angular.bootstrap(document.body, ['userDashboard'], {strictDi: true}); // manually bootstrap Angular +}); // add a global resolve flag on all routes (user needs to be resolved first) initModule.config(['$routeProvider', ($routeProvider: che.route.IRouteProvider) => { $routeProvider.accessWhen = (path: string, route: che.route.IRoute) => { - route.resolve || (route.resolve = {}); + if (angular.isUndefined(route.resolve)) { + route.resolve = {}; + } (route.resolve as any).app = ['cheBranding', '$q', 'chePreferences', (cheBranding: CheBranding, $q: ng.IQService, chePreferences: ChePreferences) => { - let deferred = $q.defer(); + const deferred = $q.defer(); if (chePreferences.getPreferences()) { deferred.resolve(); } else { @@ -56,7 +130,6 @@ initModule.config(['$routeProvider', ($routeProvider: che.route.IRouteProvider) deferred.reject(error); }); } - return deferred.promise; }]; @@ -64,9 +137,11 @@ initModule.config(['$routeProvider', ($routeProvider: che.route.IRouteProvider) }; $routeProvider.accessOtherWise = (route: che.route.IRoute) => { - route.resolve || (route.resolve = {}); + if (angular.isUndefined(route.resolve)) { + route.resolve = {}; + } (route.resolve as any).app = ['$q', 'chePreferences', ($q: ng.IQService, chePreferences: ChePreferences) => { - let deferred = $q.defer(); + const deferred = $q.defer(); if (chePreferences.getPreferences()) { deferred.resolve(); } else { @@ -76,7 +151,6 @@ initModule.config(['$routeProvider', ($routeProvider: che.route.IRouteProvider) deferred.reject(error); }); } - return deferred.promise; }]; return $routeProvider.otherwise(route); @@ -85,11 +159,11 @@ initModule.config(['$routeProvider', ($routeProvider: che.route.IRouteProvider) }]); -var DEV = false; +const DEV = false; // configs -initModule.config(['$routeProvider', 'ngClipProvider', ($routeProvider, ngClipProvider) => { +initModule.config(['$routeProvider', 'ngClipProvider', ($routeProvider: che.route.IRouteProvider, ngClipProvider: any) => { // config routes (add demo page) if (DEV) { $routeProvider.accessWhen('/demo-components', { @@ -105,7 +179,7 @@ initModule.config(['$routeProvider', 'ngClipProvider', ($routeProvider, ngClipPr redirectTo: '/workspaces' }); // add .swf path location using ngClipProvider - let ngClipProviderPath = DEV ? 'bower_components/zeroclipboard/dist/ZeroClipboard.swf' : 'assets/zeroclipboard/ZeroClipboard.swf'; + const ngClipProviderPath = DEV ? 'bower_components/zeroclipboard/dist/ZeroClipboard.swf' : 'assets/zeroclipboard/ZeroClipboard.swf'; ngClipProvider.setPath(ngClipProviderPath); }]); @@ -114,7 +188,7 @@ initModule.config(['$routeProvider', 'ngClipProvider', ($routeProvider, ngClipPr * Setup route redirect module */ initModule.run(['$rootScope', '$location', '$routeParams', 'routingRedirect', '$timeout', 'ideIFrameSvc', 'cheIdeFetcher', 'routeHistory', 'cheUIElementsInjectorService', 'workspaceDetailsService', - ($rootScope, $location, $routeParams, routingRedirect, $timeout, ideIFrameSvc, cheIdeFetcher, routeHistory, cheUIElementsInjectorService, workspaceDetailsService) => { + ($rootScope: ng.IRootScopeService, $location: ng.ILocationService, $routeParams: ng.route.IRouteParamsService, routingRedirect: RoutingRedirect, $timeout: ng.ITimeoutService, ideIFrameSvc: IdeIFrameSvc, cheIdeFetcher: CheIdeFetcher, routeHistory: RouteHistory, cheUIElementsInjectorService: CheUIElementsInjectorService, workspaceDetailsService: WorkspaceDetailsService) => { $rootScope.hideLoader = false; $rootScope.waitingLoaded = false; $rootScope.showIDE = false; @@ -122,8 +196,10 @@ initModule.run(['$rootScope', '$location', '$routeParams', 'routingRedirect', '$ workspaceDetailsService.addPage('SSH', '', 'icon-ic_vpn_key_24px'); // here only to create instances of these components + /* tslint:disable */ cheIdeFetcher; routeHistory; + /* tslint:enable */ $rootScope.$on('$viewContentLoaded', () => { cheUIElementsInjectorService.injectAll(); @@ -139,7 +215,7 @@ initModule.run(['$rootScope', '$location', '$routeParams', 'routingRedirect', '$ }, 1000); }); - $rootScope.$on('$routeChangeStart', (event, next)=> { + $rootScope.$on('$routeChangeStart', (event: any, next: any) => { if (DEV) { console.log('$routeChangeStart event with route', next); } @@ -168,53 +244,14 @@ initModule.run(['$rootScope', '$location', '$routeParams', 'routingRedirect', '$ $rootScope.$on('$routeChangeError', () => { $location.path('/'); }); - }]); + } +]); +initModule.config(($mdThemingProvider: ng.material.IThemingProvider, jsonColors: any) => { -// add interceptors -initModule.factory('ETagInterceptor', ($window, $cookies, $q) => { - - var etagMap = {}; - - return { - request: (config) => { - // add IfNoneMatch request on the che api if there is an existing eTag - if ('GET' === config.method) { - if (config.url.indexOf('/api') === 0) { - let eTagURI = etagMap[config.url]; - if (eTagURI) { - config.headers = config.headers || {}; - angular.extend(config.headers, {'If-None-Match': eTagURI}); - } - } - } - return config || $q.when(config); - }, - response: (response) => { - - // if response is ok, keep ETag - if ('GET' === response.config.method) { - if (response.status === 200) { - var responseEtag = response.headers().etag; - if (responseEtag) { - if (response.config.url.indexOf('/api') === 0) { - - etagMap[response.config.url] = responseEtag; - } - } - } - - } - return response || $q.when(response); - } - }; -}); - -initModule.config(($mdThemingProvider, jsonColors) => { - - var cheColors = angular.fromJson(jsonColors); - var getColor = (key) => { - var color = cheColors[key]; + const cheColors = angular.fromJson(jsonColors); + const getColor = (key: string) => { + let color = cheColors[key]; if (!color) { // return a flashy red color if color is undefined console.log('error, the color' + key + 'is undefined'); @@ -228,37 +265,37 @@ initModule.config(($mdThemingProvider, jsonColors) => { }; - var cheMap = $mdThemingProvider.extendPalette('indigo', { + const cheMap = $mdThemingProvider.extendPalette('indigo', { '500': getColor('$dark-menu-color'), '300': 'D0D0D0' }); $mdThemingProvider.definePalette('che', cheMap); - var cheDangerMap = $mdThemingProvider.extendPalette('red', {}); + const cheDangerMap = $mdThemingProvider.extendPalette('red', {}); $mdThemingProvider.definePalette('cheDanger', cheDangerMap); - var cheWarningMap = $mdThemingProvider.extendPalette('orange', { + const cheWarningMap = $mdThemingProvider.extendPalette('orange', { 'contrastDefaultColor': 'light' }); $mdThemingProvider.definePalette('cheWarning', cheWarningMap); - var cheGreenMap = $mdThemingProvider.extendPalette('green', { + const cheGreenMap = $mdThemingProvider.extendPalette('green', { 'A100': '#46AF00', 'contrastDefaultColor': 'light' }); $mdThemingProvider.definePalette('cheGreen', cheGreenMap); - var cheDefaultMap = $mdThemingProvider.extendPalette('blue', { + const cheDefaultMap = $mdThemingProvider.extendPalette('blue', { 'A400': getColor('$che-medium-blue-color') }); $mdThemingProvider.definePalette('cheDefault', cheDefaultMap); - var cheNoticeMap = $mdThemingProvider.extendPalette('blue', { + const cheNoticeMap = $mdThemingProvider.extendPalette('blue', { 'A400': getColor('$mouse-gray-color') }); $mdThemingProvider.definePalette('cheNotice', cheNoticeMap); - var cheAccentMap = $mdThemingProvider.extendPalette('blue', { + const cheAccentMap = $mdThemingProvider.extendPalette('blue', { '700': getColor('$che-medium-blue-color'), 'A400': getColor('$che-medium-blue-color'), 'A200': getColor('$che-medium-blue-color'), @@ -267,27 +304,27 @@ initModule.config(($mdThemingProvider, jsonColors) => { $mdThemingProvider.definePalette('cheAccent', cheAccentMap); - var cheNavyPalette = $mdThemingProvider.extendPalette('purple', { + const cheNavyPalette = $mdThemingProvider.extendPalette('purple', { '500': getColor('$che-navy-color'), 'contrastDefaultColor': 'light' }); $mdThemingProvider.definePalette('cheNavyPalette', cheNavyPalette); - var toolbarPrimaryPalette = $mdThemingProvider.extendPalette('purple', { + const toolbarPrimaryPalette = $mdThemingProvider.extendPalette('purple', { '500': getColor('$che-white-color'), 'contrastDefaultColor': 'dark' }); $mdThemingProvider.definePalette('toolbarPrimaryPalette', toolbarPrimaryPalette); - var toolbarAccentPalette = $mdThemingProvider.extendPalette('purple', { + const toolbarAccentPalette = $mdThemingProvider.extendPalette('purple', { 'A200': 'EF6C00', '700': 'E65100', 'contrastDefaultColor': 'light' }); $mdThemingProvider.definePalette('toolbarAccentPalette', toolbarAccentPalette); - var cheGreyPalette = $mdThemingProvider.extendPalette('grey', { + const cheGreyPalette = $mdThemingProvider.extendPalette('grey', { 'A100': 'efefef', 'contrastDefaultColor': 'light' }); @@ -359,18 +396,13 @@ initModule.constant('userDashboardConfig', { developmentMode: DEV }); -initModule.config(['$routeProvider', '$locationProvider', '$httpProvider', ($routeProvider, $locationProvider, $httpProvider) => { - // add the ETag interceptor for Che API - $httpProvider.interceptors.push('ETagInterceptor'); -}]); - - -var instanceRegister = new Register(initModule); +const instanceRegister = new Register(initModule); if (DEV) { instanceRegister.controller('DemoComponentsController', DemoComponentsController); } +/* tslint:disable */ new ProxySettingsConfig(instanceRegister); new CheColorsConfig(instanceRegister); new CheOutputColorsConfig(instanceRegister); @@ -388,3 +420,6 @@ new WorkspacesConfig(instanceRegister); new DashboardConfig(instanceRegister); new StacksConfig(instanceRegister); new FactoryConfig(instanceRegister); +new OrganizationsConfig(instanceRegister); +new TeamsConfig(instanceRegister); +/* tslint:enable */ diff --git a/dashboard/src/app/navbar/navbar.controller.ts b/dashboard/src/app/navbar/navbar.controller.ts index 47767dcd69..e0ba472285 100644 --- a/dashboard/src/app/navbar/navbar.controller.ts +++ b/dashboard/src/app/navbar/navbar.controller.ts @@ -10,9 +10,10 @@ */ 'use strict'; import {CheAPI} from '../../components/api/che-api.factory'; +import {CheKeycloak, keycloakUserInfo} from '../../components/api/che-keycloak.factory'; export class CheNavBarController { - menuItemUrl = { + private menuItemUrl = { dashboard: '#/', workspaces: '#/workspaces', administration: '#/administration', @@ -20,9 +21,26 @@ export class CheNavBarController { plugins: '#/admin/plugins', factories: '#/factories', account: '#/account', - stacks: '#/stacks' + stacks: '#/stacks', + organizations: '#/organizations', + usermanagement: '#/admin/usermanagement' }; + accountItems = [ + { + name: 'Go to Profile', + onclick: () => { + this.gotoProfile(); + } + }, + { + name: 'Logout', + onclick: () => { + this.logout(); + } + } + ]; + private $mdSidenav: ng.material.ISidenavService; private $scope: ng.IScope; private $window: ng.IWindowService; @@ -30,21 +48,39 @@ export class CheNavBarController { private $route: ng.route.IRouteService; private cheAPI: CheAPI; private profile: che.IProfile; + private chePermissions: che.api.IChePermissions; + private userServices: che.IUserServices; + private hasPersonalAccount: boolean; + private organizations: Array; + private cheKeycloak: CheKeycloak; + private userInfo: keycloakUserInfo; /** * Default constructor * @ngInject for Dependency injection */ - constructor($mdSidenav: ng.material.ISidenavService, $scope: ng.IScope, $location: ng.ILocationService, $route: ng.route.IRouteService, cheAPI: CheAPI, $window: ng.IWindowService) { + constructor($mdSidenav: ng.material.ISidenavService, + $scope: ng.IScope, + $location: ng.ILocationService, + $route: ng.route.IRouteService, + cheAPI: CheAPI, + $window: ng.IWindowService, + chePermissions: che.api.IChePermissions, + cheKeycloak: CheKeycloak) { this.$mdSidenav = $mdSidenav; this.$scope = $scope; this.$location = $location; this.$route = $route; this.cheAPI = cheAPI; this.$window = $window; + this.chePermissions = chePermissions; + this.cheKeycloak = cheKeycloak; + this.userInfo = null; this.profile = cheAPI.getProfile().getProfile(); + this.userServices = this.chePermissions.getUserServices(); + // highlight navbar menu item $scope.$on('$locationChangeStart', () => { let path = '#' + $location.path(); @@ -53,6 +89,34 @@ export class CheNavBarController { cheAPI.getWorkspace().fetchWorkspaces(); cheAPI.getFactory().fetchFactories(); + + if (this.cheKeycloak.isPresent()) { + this.cheKeycloak.fetchUserInfo().then((userInfo: keycloakUserInfo) => { + this.userInfo = userInfo; + }); + } + + if (this.chePermissions.getSystemPermissions()) { + this.updateData(); + } else { + this.chePermissions.fetchSystemPermissions().finally(() => { + this.updateData(); + }); + } + } + + /** + * Update data. + */ + updateData(): void { + const organization = this.cheAPI.getOrganization(); + organization.fetchOrganizations().then(() => { + this.organizations = organization.getOrganizations(); + const user = this.cheAPI.getUser().getUser(); + organization.fetchOrganizationByName(user.name).finally(() => { + this.hasPersonalAccount = angular.isDefined(organization.getOrganizationByName(user.name)); + }); + }); } reload(): void { @@ -66,15 +130,63 @@ export class CheNavBarController { this.$mdSidenav('left').toggle(); } + /** + * Returns number of workspaces. + * + * @return {number} + */ getWorkspacesNumber(): number { return this.cheAPI.getWorkspace().getWorkspaces().length; } + /** + * Returns number of factories. + * + * @return {number} + */ getFactoriesNumber(): number { return this.cheAPI.getFactory().getPageFactories().length; } + /** + * Returns number of all organizations. + * + * @return {number} + */ + getOrganizationsNumber(): number { + if (!this.organizations) { + return 0; + } + + return this.organizations.length; + } + openLinkInNewTab(url: string): void { this.$window.open(url, '_blank'); } + + /** + * Returns true if Keycloak is present. + * + * @returns {boolean} + */ + isKeycloakPresent(): boolean { + return this.cheKeycloak.isPresent(); + } + + /** + * Opens user profile in new browser page. + */ + gotoProfile(): void { + const url = this.cheKeycloak.getProfileUrl(); + this.$window.open(url); + } + + /** + * Logout. + */ + logout(): void { + this.cheKeycloak.logout(); + } + } diff --git a/dashboard/src/app/navbar/navbar.directive.ts b/dashboard/src/app/navbar/navbar.directive.ts index 0baa7dbb76..338666e395 100644 --- a/dashboard/src/app/navbar/navbar.directive.ts +++ b/dashboard/src/app/navbar/navbar.directive.ts @@ -30,7 +30,7 @@ export class CheNavBar { this.replace = false; this.templateUrl = 'app/navbar/navbar.html'; this.controller = 'CheNavBarController'; - this.controllerAs = 'navbarCtrl'; + this.controllerAs = 'navbarController'; } } diff --git a/dashboard/src/app/navbar/navbar.html b/dashboard/src/app/navbar/navbar.html index a4b4922d1f..b98ad2d8f9 100644 --- a/dashboard/src/app/navbar/navbar.html +++ b/dashboard/src/app/navbar/navbar.html @@ -12,8 +12,9 @@ -->