CHE-5802: transplantation of Codenvy dashboard stuff to Che dashboard (#6381)

* code clean-up

Signed-off-by: Oleksii Kurinnyi <okurinny@redhat.com>

* move user-management and its related components

Signed-off-by: Oleksii Kurinnyi <okurinnyi@codenvy.com>

* move organizations and its related components

Signed-off-by: Oleksii Kurinnyi <okurinnyi@codenvy.com>

* add Keycloak authorization

Signed-off-by: Oleksii Kurinnyi <okurinnyi@codenvy.com>

* move teams and its related components

Signed-off-by: Oleksii Kurinnyi <okurinnyi@codenvy.com>

* move share-workspace and its related components

Signed-off-by: Oleksii Kurinnyi <okurinnyi@codenvy.com>

* update assembly

* fixup! update assembly
6.19.x
Oleksii Kurinnyi 2017-09-22 10:03:17 +03:00 committed by GitHub
parent de347bedae
commit bd2ea09b1a
162 changed files with 15292 additions and 195 deletions

View File

@ -42,6 +42,11 @@
<artifactId>assembly-wsmaster-war</artifactId>
<type>war</type>
</dependency>
<dependency>
<groupId>org.eclipse.che.dashboard</groupId>
<artifactId>che-dashboard-war</artifactId>
<type>war</type>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
@ -95,22 +100,4 @@
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>withDashboard</id>
<activation>
<property>
<name>withoutDashboard</name>
<value>!true</value>
</property>
</activation>
<dependencies>
<dependency>
<groupId>org.eclipse.che.assembly-multiuser</groupId>
<artifactId>che-dashboard-war</artifactId>
<type>war</type>
</dependency>
</dependencies>
</profile>
</profiles>
</project>

View File

@ -55,7 +55,7 @@
<outputDirectory>tomcat/webapps</outputDirectory>
<outputFileNameMapping>dashboard.war</outputFileNameMapping>
<includes>
<include>org.eclipse.che.assembly-multiuser:che-dashboard-war</include>
<include>org.eclipse.che.dashboard:che-dashboard-war</include>
</includes>
</dependencySet>
<dependencySet>

View File

@ -188,18 +188,4 @@
</plugins>
</pluginManagement>
</build>
<profiles>
<profile>
<id>withDashboard</id>
<activation>
<property>
<name>withoutDashboard</name>
<value>!true</value>
</property>
</activation>
<modules>
<module>dashboard</module>
</modules>
</profile>
</profiles>
</project>

View File

@ -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",

View File

@ -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';
}

View File

@ -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);
}
}

View File

@ -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
* <account-profile profile-attributes="ctrl.profileAttributes"></account-profile>` for displaying account profile.
*
* @usage
* <account-profile profile-attributes="ctrl.profileAttributes"></account-profile>
*
* @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);
}
}

View File

@ -0,0 +1,67 @@
<div class="account-profile">
<ng-form name="profileInformationForm">
<che-label-container che-label-name="First Name">
<che-input che-form="profileInformationForm"
che-name="firstName"
che-place-holder="First Name"
ng-model="profileAttributes.firstName"
required
ng-maxlength="128">
<div ng-message="required">A name is required.</div>
<div ng-message="maxlength">The name has to be less than 128 characters long.</div>
</che-input>
</che-label-container>
<che-label-container che-label-name="Last Name">
<che-input che-form="profileInformationForm"
che-name="lastName"
che-place-holder="Last Name"
ng-model="profileAttributes.lastName"
required
ng-maxlength="128">
<div ng-message="required">A last name is required.</div>
<div ng-message="maxlength">The name has to be less than 128 characters long.</div>
</che-input>
</che-label-container>
<che-label-container che-label-name="Phone">
<che-input che-form="profileInformationForm"
che-name="phone"
che-place-holder="phone"
che-pattern="(^[+]{0,1}[0-9-]{0,}$)"
ng-model="profileAttributes.phone"
ng-maxlength="15"
ng-minlength="7">
<div ng-message="pattern">Should be numbers and may start with a '+'.</div>
<div ng-message="minlength">The phone number has to be more than 7 characters long.</div>
<div ng-message="maxlength">The phone number has to be less than 15 characters long.</div>
</che-input>
</che-label-container>
<che-label-container che-label-name="Country">
<che-select che-form="profileInformationForm"
che-name="country"
che-option-values="countries"
che-place-holder="Select Your Country"
che-value="profileAttributes.country">
</che-select>
</che-label-container>
<che-label-container che-label-name="Company">
<che-input che-form="profileInformationForm"
che-name="department"
che-place-holder="Company"
ng-model="profileAttributes.employer"
ng-maxlength="128">
<div ng-message="maxlength">The name has to be less than 128 characters long.</div>
</che-input>
</che-label-container>
<che-label-container che-label-name="Role">
<che-select che-option-values="jobs"
che-place-holder="Select Your Role"
che-value="profileAttributes.jobtitle">
</che-select>
</che-label-container>
</ng-form>
</div>

View File

@ -0,0 +1,8 @@
.account-profile
padding 0 14px
.che-input-desktop
margin-top -1px
.che-select
margin-top -2px

View File

@ -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<string>;
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 + '.');
});
}
}

View File

@ -0,0 +1,95 @@
<!--
Copyright (c) 2015 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
-->
<che-popup title="Add user" on-close="adminsAddUserController.abort()">
<ng-form name="createUserForm" class="admins-add-user-form" layout="column" align="center center">
<div class="form-input-fields">
<che-input-box che-form="createUserForm"
che-name="login"
che-label-name="User Login"
che-place-holder="login"
ng-model="adminsAddUserController.newUserName"
required
focusable
ng-maxlength="100">
<div ng-message="maxlength">User login to be less than 100 characters long.</div>
</che-input-box>
<che-input-box che-form="createUserForm"
che-name="email"
che-label-name="User Email"
che-place-holder="e-mail"
ng-maxlength="100"
type="email"
required
ng-model="adminsAddUserController.newUserEmail">
<div ng-message="email">Enter a valid email address.</div>
<div ng-message="maxlength">User email has to be less than 100 characters long</div>
</che-input-box>
<div layout="row" layout-align="start start" ng-if="adminsAddUserController.organizations.length > 0">
<label>Organization:</label>
<che-filter-selector che-values="adminsAddUserController.organizations"
che-width="400px"
ng-model="adminsAddUserController.organization"></che-filter-selector>
</div>
<che-input-box che-form="createUserForm"
che-name="password"
che-label-name="User Password"
che-place-holder="password"
che-pattern="^(?=.*[0-9]+.*)(?=.*[a-zA-Z]+.*).{0,}$"
ng-maxlength="100"
ng-minlength="8"
type="password"
required
ng-model="adminsAddUserController.newUserPassword">
<div ng-message="pattern">User password should contain both letters and digits</div>
<div ng-message="minlength">User password should contain at least 8 characters.</div>
<div ng-message="maxlength">User password has to be less than 100 characters long.</div>
</che-input-box>
<div class="password-prompt"
layout-xs="column" layout-sm="column" layout-md="column"
layout-gt-md="row" flex="100">
<div flex="50" flex-gt-md="25" class="password-prompt-text">
<span>Minimum 8 characters, both letters and digits.</span>
</div>
<div flex="50" flex-gt-md="50" layout="column" layout-align="center center">
<div class="pass-strength" ng-password-strength="adminsAddUserController.newUserPassword"
strength="passStrength" inner-class="password-meter" class="ng-isolate-scope"></div>
</div>
</div>
<che-input-box che-form="createUserForm"
che-name="confirmPassword"
che-label-name="Confirm Password"
che-place-holder="confirm password"
ng-model="confirmNewUserPassword"
ng-maxlength="100"
type="password"
required>
<div ng-message="maxlength">Confirm password has to be less than 100 characters long.</div>
<div
ng-show="confirmNewUserPassword && (confirmNewUserPassword !== adminsAddUserController.newUserPassword)">
Passwords do not match.
</div>
</che-input-box>
</div>
<div layout="row" layout-align="end center">
<che-button-notice che-button-title="Cancel"
ng-click="adminsAddUserController.abort()">
</che-button-notice>
<che-button-primary che-button-title="Create"
ng-click="adminsAddUserController.createUser()"
ng-disabled="createUserForm.$invalid || confirmNewUserPassword !== adminsAddUserController.newUserPassword">
</che-button-primary>
</div>
</ng-form>
</che-popup>

View File

@ -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

View File

@ -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<che.IOrganization>;
/**
* 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<any>;
$scope.$watch(() => {
return angular.isUndefined(this.profileAttributes) || this.profileAttributes;
}, () => {
if (!this.profileAttributes || !(<IScope>$scope).profileInformationForm || (<IScope>$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<any>}
*/
fetchOrganizations(): void {
this.isLoading = true;
this.cheOrganization.fetchUserOrganizations(this.userId, MAX_ITEMS).then((userOrganizations: Array<che.IOrganization>) => {
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<any>}
*/
getUserOrganizations(): Array<che.IOrganization> {
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<che.IOrganization>) => {
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);
}
}

View File

@ -0,0 +1,45 @@
<che-toolbar che-title="{{adminUserDetailsController.userName}}"
che-breadcrumb-title="Users"
che-breadcrumb-href="#/admin/usermanagement">
</che-toolbar>
<md-content md-scroll-y flex md-theme="default">
<md-tabs md-dynamic-height md-stretch-tabs="auto"
md-selected="adminUserDetailsController.selectedTabIndex"
md-center-tabs="">
<!-- User Profile Tab -->
<md-tab md-on-select="adminUserDetailsController.onSelectTab(adminUserDetailsController.tab.Profile);">
<md-icon md-font-icon="fa-user" class="fa che-tab-label-icon"></md-icon>
<span class="che-tab-label-title">Profile</span>
</md-tab-label>
<md-tab-body>
<div class="progress-line">
<md-progress-linear ng-show="adminUserDetailsController.isLoading" md-mode="indeterminate"></md-progress-linear>
</div>
<account-profile profile-attributes="adminUserDetailsController.profileAttributes"
profile-information-form="profileInformationForm"></account-profile>
</md-tab-body>
</md-tab>
<!-- User Organizations Tab -->
<md-tab md-on-select="adminUserDetailsController.onSelectTab(adminUserDetailsController.tab.Organization);">
<md-icon md-font-icon="md-font fa fa-sitemap material-icons" class="che-tab-label-icon"></md-icon>
<span class="che-tab-label-title">Organizations</span>
</md-tab-label>
<md-tab-body>
<div class="progress-line">
<md-progress-linear ng-show="adminUserDetailsController.isLoading" md-mode="indeterminate"></md-progress-linear>
</div>
<list-organizations hide-add-button="true"
is-loading="adminUserDetailsController.isInfoLoading"
on-update="adminUserDetailsController.fetchOrganizations()"
organizations="adminUserDetailsController.getUserOrganizations()"></list-organizations>
<che-paging-buttons ng-hide="adminUserDetailsController.isLoading"
pages-info="adminUserDetailsController.getPagesInfo()"
fetch-page="adminUserDetailsController.fetchOrganizationPageObjects(key)"></che-paging-buttons>
</md-tab-body>
</md-tab>
</md-tabs>
</md-content>

View File

@ -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);
});
}
}

View File

@ -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<any>;
usersMap: Map<string, any>;
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<any>) => {
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<any>}
*/
showDeleteUsersConfirmation(numberToDelete: number): angular.IPromise<any> {
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'
});
}
}

View File

@ -0,0 +1,147 @@
<!--
Copyright (c) 2015 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
-->
<che-toolbar che-title="User Management"></che-toolbar>
<md-content flex md-scroll-y md-theme="maincontent-theme" class="admins-user-management">
<div class="progress-line">
<md-progress-linear ng-show="adminsUserManagementCtrl.isLoading" md-mode="indeterminate"></md-progress-linear>
</div>
<div>
<che-list-header flex="100"
che-input-placeholder="Search"
che-search-model="adminsUserManagementCtrl.userFilter.name"
che-on-search-change="adminsUserManagementCtrl.onSearchChanged(str)"
che-hide-search="adminsUserManagementCtrl.users.length === 0"
che-add-button-title="Add User"
che-on-add="adminsUserManagementCtrl.showAddUserDialog($event)"
che-delete-button-title="Delete"
che-on-delete="adminsUserManagementCtrl.deleteSelectedUsers()"
che-hide-delete="adminsUserManagementCtrl.cheListHelper.isNoItemSelected"
che-hide-header="adminsUserManagementCtrl.cheListHelper.visibleItemsNumber === 0">
<div flex="100"
layout="row"
layout-align="start stretch"
class="che-list-item-row">
<div layout="column" layout-gt-xs="row"
layout-align="start center"
class="che-checkbox-area">
<div layout="row" layout-align="start center" class="che-list-item-checkbox-main">
<md-checkbox class="che-list-item-checkbox"
aria-label="Bulk check on users"
ng-checked="adminsUserManagementCtrl.cheListHelper.areAllItemsSelected"
ng-click="adminsUserManagementCtrl.cheListHelper.changeBulkSelection()"></md-checkbox>
</div>
</div>
<div flex hide-xs layout-gt-xs="row"
layout-align="start center"
class="che-list-item-details">
<che-list-header-column flex-gt-xs="40"
che-sort-value='adminsUserManagementCtrl.userOrderBy'
che-sort-item='email'
che-column-title='Email'></che-list-header-column>
<che-list-header-column flex-gt-xs="20"
che-sort-value='adminsUserManagementCtrl.userOrderBy'
che-sort-item='name'
che-column-title='Login'></che-list-header-column>
<che-list-header-column flex-gt-xs="20"
che-column-title='Organizations'></che-list-header-column>
<che-list-header-column flex-gt-xs="20"
che-column-title='Actions'></che-list-header-column>
</div>
</div>
</che-list-header>
<che-list flex ng-if="adminsUserManagementCtrl.users && adminsUserManagementCtrl.users.length > 0">
<che-list-item
ng-repeat="user in adminsUserManagementCtrl.cheListHelper.getVisibleItems() | orderBy:adminsUserManagementCtrl.userOrderBy"
ng-init="adminsUserManagementCtrl.updateUserOrganizationsCount(user.id)"
flex-gt-sm="100" flex="33"
ng-mouseover="hover=true"
ng-mouseout="hover=false">
<div flex="100"
layout="row"
layout-align="start stretch"
class="che-list-item-row">
<div layout="row"
layout-align="start center"
class="che-checkbox-area">
<che-list-item-checked ng-model="adminsUserManagementCtrl.cheListHelper.itemsSelectionStatus[user.id]"
ng-click="adminsUserManagementCtrl.cheListHelper.updateBulkSelectionStatus()"
che-aria-label-checkbox="User {{user.id}}"></che-list-item-checked>
</div>
<div flex
layout-xs="column" layout-gt-xs="row"
layout-align-gt-xs="start center"
layout-align-xs="start start"
class="che-list-item-details">
<div flex-gt-xs="40"
class="che-list-item-name"
ng-click="adminsUserManagementCtrl.redirectToUserDetails(user.id)">
<span class="che-xs-header noselect" hide-gt-xs>Email</span>
<span><img class="user-face" gravatar-src="user.email"></span>
<span class="user-email">{{user.email}}</span>
</div>
<div flex-gt-xs="20"
ng-click="adminsUserManagementCtrl.redirectToUserDetails(user.id)">
<span class="che-xs-header noselect" hide-gt-xs>Login</span>
<span class="user-description">{{user.name}}</span>
</div>
<div flex-gt-xs="20"
ng-click="adminsUserManagementCtrl.redirectToUserDetails(user.id, 'Organization')">
<span class="che-xs-header noselect" hide-gt-xs>Organizations</span>
<span class="user-description">{{adminsUserManagementCtrl.userOrganizationCount[user.id] ? adminsUserManagementCtrl.userOrganizationCount[user.id] : '-'}}</span>
</div>
<div flex-gt-xs="20"
ng-click="adminsUserManagementCtrl.redirectToUserDetails(user.id)">
<span class="che-xs-header noselect" hide-gt-xs>Actions</span>
<span class="che-list-actions">
<div ng-click="adminsUserManagementCtrl.removeUser($event, user);" uib-tooltip="Remove user">
<span class="material-design icon-ic_remove_circle_outline_24px"></span>
</div>
</span>
</div>
</div>
</div>
</che-list-item>
<div class="paging-buttons-area" ng-if="adminsUserManagementCtrl.isPagination()">
<md-button
ng-disabled="!adminsUserManagementCtrl.hasPreviousPage()"
ng-click="adminsUserManagementCtrl.fetchUsersPage('first');">
<span><<</span>
</md-button>
<md-button
ng-disabled="!adminsUserManagementCtrl.hasPreviousPage()"
ng-click="adminsUserManagementCtrl.fetchUsersPage('prev');">
<span><</span>
</md-button>
<md-button disabled>
<span>{{adminsUserManagementCtrl.pagesInfo.currentPageNumber}}</span>
</md-button>
<md-button
ng-disabled="!adminsUserManagementCtrl.hasNextPage()"
ng-click="adminsUserManagementCtrl.fetchUsersPage('next');">
<span>></span>
</md-button>
<md-button
ng-disabled="!adminsUserManagementCtrl.hasNextPage()"
ng-click="adminsUserManagementCtrl.fetchUsersPage('last');">
<span>>></span>
</md-button>
</div>
</che-list>
<div class="che-list-empty">
<span ng-show="adminsUserManagementCtrl.users.length > 0 && adminsUserManagementCtrl.cheListHelper.visibleItemsNumber === 0">
No users found.
</span>
<span ng-show="adminsUserManagementCtrl.users.length === 0">There are no users.</span>
</div>
</div>
</md-content>

View File

@ -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

View File

@ -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<T> {
(value: T | PromiseLike<T>): Promise<T>;
}
interface IRejectFn<T> {
(reason: any): Promise<T>;
}
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', '<workspace-details-ssh></workspace-details-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 */

View File

@ -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<che.IOrganization>;
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 <code>true</code> 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();
}
}

View File

@ -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';
}
}

View File

@ -12,8 +12,9 @@
-->
<div class="left-sidebar-container">
<md-toolbar class="flex-shrink-none">
<md-progress-linear md-mode="indeterminate" ng-hide="navbarCtrl.profile && navbarCtrl.profile.userId"></md-progress-linear>
<div ng-show="navbarCtrl.profile && navbarCtrl.profile.userId">
<md-progress-linear md-mode="indeterminate" ng-hide="navbarController.profile && navbarController.profile.userId"></md-progress-linear>
<div ng-show="navbarController.profile && navbarController.profile.userId"
layout="column" flex>
<section class="navbar-top-logo logo-color-white" layout="column" layout-align="center left" ng-include="branding.logoText">
</section>
@ -23,7 +24,7 @@
<section class="left-sidebar-menu" layout="column" layout-align="center center">
<md-list layout="column">
<md-list-item flex class="navbar-subsection-item">
<md-button nav-bar-selected flex che-reload-href href="{{navbarCtrl.menuItemUrl.dashboard}}" layout-align="left"
<md-button nav-bar-selected flex che-reload-href href="{{navbarController.menuItemUrl.dashboard}}" layout-align="left"
target="_self">
<div class="navbar-item" layout="row" layout-align="start center">
<md-icon md-font-icon="navbar-icon chefont cheico-dashboard" aria-label="Dashboard"></md-icon>
@ -32,17 +33,17 @@
</md-button>
</md-list-item>
<md-list-item flex class="navbar-subsection-item">
<md-button nav-bar-selected flex che-reload-href href="{{navbarCtrl.menuItemUrl.workspaces}}" layout-align="left">
<md-button nav-bar-selected flex che-reload-href href="{{navbarController.menuItemUrl.workspaces}}" layout-align="left">
<div class="navbar-item" layout="row" layout-align="start center">
<md-icon md-font-icon="navbar-icon chefont cheico-workspace"></md-icon>
<span>Workspaces</span>
<span class="navbar-number" ng-show="navbarCtrl.getWorkspacesNumber()">&nbsp;({{navbarCtrl.getWorkspacesNumber()}})</span>
<span class="navbar-number" ng-show="navbarController.getWorkspacesNumber()">&nbsp;({{navbarController.getWorkspacesNumber()}})</span>
</div>
</md-button>
</md-list-item>
<md-list-item flex class="navbar-subsection-item">
<md-button nav-bar-selected flex che-reload-href
href="{{navbarCtrl.menuItemUrl.stacks}}" layout-align="left">
href="{{navbarController.menuItemUrl.stacks}}" layout-align="left">
<div class="navbar-item" layout="row" layout-align="start center">
<md-icon md-font-icon="navbar-icon material-design icon-ic_inbox_24px"></md-icon>
<span>Stacks</span>
@ -51,29 +52,102 @@
</md-list-item>
<md-list-item flex class="navbar-subsection-item">
<md-button nav-bar-selected flex che-reload-href
href="{{navbarCtrl.menuItemUrl.factories}}" layout-align="left">
href="{{navbarController.menuItemUrl.factories}}" layout-align="left">
<div class="navbar-item" layout="row" layout-align="start center">
<md-icon md-font-icon="navbar-icon chefont cheico-factory"></md-icon>
<span>Factories</span>
<span class="navbar-number" ng-show="navbarCtrl.getFactoriesNumber()">&nbsp;({{navbarCtrl.getFactoriesNumber()}})</span>
<span class="navbar-number" ng-show="navbarController.getFactoriesNumber()">&nbsp;({{navbarController.getFactoriesNumber()}})</span>
</div>
</md-button>
</md-list-item>
<md-list-item flex class="navbar-subsection-item">
<md-button nav-bar-selected flex che-reload-href
href="{{navbarCtrl.menuItemUrl.administration}}" layout-align="left">
href="{{navbarController.menuItemUrl.administration}}" layout-align="left">
<div class="navbar-item" layout="row" layout-align="start center">
<md-icon md-font-icon="navbar-icon material-design icon-ic_settings_24px"></md-icon>
<span>Administration</span>
</div>
</md-button>
</md-list-item>
<md-list-item flex class="navbar-subsection-item" ng-if="!navbarController.userServices.hasAdminUserService && !navbarController.hasPersonalAccount">
<md-button nav-bar-selected flex che-reload-href
href="{{navbarController.menuItemUrl.organizations}}" layout-align="left">
<div class="navbar-item" layout="row" layout-align="start center">
<md-icon md-font-icon="fa navbar-icon fa-sitemap"></md-icon>
<span>Organizations</span>
<span class="navbar-number" ng-show="navbarController.getOrganizationsNumber()">&nbsp;({{navbarController.getOrganizationsNumber()}})</span>
</div>
</md-button>
</md-list-item>
</md-list>
</section>
</div>
<div class="admin-navbar-menu"
ng-if="navbarController.userServices.hasInstallationManagerService || navbarController.userServices.hasAdminUserService">
<section class="left-sidebar-menu" layout="column" layout-align="start start">
<div class="navbar-section navbar-section-title"
flex layout="row" layout-align="start start">
<span>Administration</span>
</div>
<md-list layout="column">
<md-list-item flex class="navbar-subsection-item"
ng-if="navbarController.userServices.hasAdminUserService">
<md-button nav-bar-selected flex che-reload-href
href="{{navbarController.menuItemUrl.usermanagement}}"
layout-align="left">
<div class="navbar-item" layout="row" layout-align="start center">
<i class="fa fa-user fa-lg navbar-icon"></i>
<span>Users</span>
</div>
</md-button>
</md-list-item>
<md-list-item flex class="navbar-subsection-item"
ng-if="navbarController.userServices.hasAdminUserService">
<md-button nav-bar-selected flex che-reload-href
href="{{navbarController.menuItemUrl.organizations}}" layout-align="left">
<div class="navbar-item" layout="row" layout-align="start center">
<md-icon md-font-icon="fa navbar-icon fa-sitemap"></md-icon>
<span>Organizations</span>
<span class="navbar-number" ng-show="navbarController.getRootOrganizationsNumber()">&nbsp;({{navbarController.getRootOrganizationsNumber()}})</span>
</div>
</md-button>
</md-list-item>
</md-list>
</section>
</div>
<navbar-recent-workspaces></navbar-recent-workspaces>
<navbar-teams flex layout="column" layout-aling="start strength"></navbar-teams>
<div class="admin-navbar-menu"
layout="column" layout-align="end stretch" flex
ng-if="navbarController.isKeycloakPresent() && navbarController.userInfo">
<section class="left-sidebar-menu navbar-account-section">
<md-list layout="column" flex>
<md-list-item class="navbar-subsection-item">
<navbar-dropdown-menu flex
navbar-dropdown-items="navbarController.accountItems"
navbar-dropdown-offset="15 -45">
<md-button ng-href="" layout-align="left">
<div class="navbar-item navbar-identity" layout="row" layout-align="start center">
<i class="navbar-icon" flex="none">
<img class="developers-face" gravatar-src="navbarController.userInfo.email"/>
</i>
<span flex style="text-align: left;">{{navbarController.userInfo.name}}</span>
<i class="fa fa-angle-up navbar-icon" aria-hidden="true"></i>
</div>
</md-button>
</navbar-dropdown-menu>
</md-list-item>
</md-list>
</section>
</div>
</div>
</md-toolbar>

View File

@ -0,0 +1,166 @@
/*
* 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';
/**
* @ngdoc controller
* @name organizations.create.controller:CreateOrganizationController
* @description This class is handling the controller for the new organization creation.
* @author Oleksii Orel
*/
export class CreateOrganizationController {
/**
* Organization API interaction.
*/
private cheOrganization: che.api.ICheOrganization;
/**
* User API interaction.
*/
private cheUser: any;
/**
* Permissions API interaction.
*/
private chePermissions: che.api.IChePermissions;
/**
* Notifications service.
*/
private cheNotification: any;
/**
* Location service.
*/
private $location: ng.ILocationService;
/**
* Log service.
*/
private $log: ng.ILogService;
/**
* Promises service.
*/
private $q: ng.IQService;
/**
* Current organization's name.
*/
private organizationName: string;
/**
* Loading state of the page.
*/
private isLoading: boolean;
/**
* The list of users to invite.
*/
private members: Array<che.IMember>;
/**
* Parent organization name.
*/
private parentQualifiedName: string;
/**
* Parent organization id.
*/
private parentOrganizationId: string;
/**
* List of members of parent organization.
*/
private parentOrganizationMembers: Array<che.IUser>;
/**
* Default constructor
* @ngInject for Dependency injection
*/
constructor(cheOrganization: che.api.ICheOrganization, chePermissions: che.api.IChePermissions, cheUser: any, cheNotification: any,
$location: ng.ILocationService, $q: ng.IQService, $log: ng.ILogService, $rootScope: che.IRootScopeService,
initData: any) {
this.cheOrganization = cheOrganization;
this.chePermissions = chePermissions;
this.cheUser = cheUser;
this.cheNotification = cheNotification;
this.$location = $location;
this.$q = $q;
this.$log = $log;
$rootScope.showIDE = false;
this.organizationName = '';
this.isLoading = false;
this.members = [];
// injected by route provider
this.parentQualifiedName = initData.parentQualifiedName;
this.parentOrganizationId = initData.parentOrganizationId;
this.parentOrganizationMembers = initData.parentOrganizationMembers;
}
/**
* Check if the name is unique.
* @param name
* @returns {boolean}
*/
isUniqueName(name: string): boolean {
let organizations = this.cheOrganization.getOrganizations();
let account = this.parentQualifiedName ? this.parentQualifiedName + '/' : '';
if (!organizations.length) {
return true;
} else {
for (let i = 0; i < organizations.length; i++) {
if (organizations[i].qualifiedName === account + name) {
return false;
}
}
return true;
}
}
/**
* Performs new organization creation.
*/
createOrganization(): void {
this.isLoading = true;
this.cheOrganization.createOrganization(this.organizationName, this.parentOrganizationId).then((organization: che.IOrganization) => {
this.addPermissions(organization, this.members);
this.cheOrganization.fetchOrganizations();
}, (error: any) => {
this.isLoading = false;
let message = error.data && error.data.message ? error.data.message : 'Failed to create organization ' + this.organizationName + '.';
this.cheNotification.showError(message);
});
}
/**
* Add permissions for members in pointed organization.
*
* @param organization {che.IOrganization} organization
* @param members members to be added to organization
*/
addPermissions(organization: che.IOrganization, members: Array<any>) {
let promises = [];
members.forEach((member: che.IMember) => {
if (member.id && member.id !== this.cheUser.getUser().id) {
let actions = this.cheOrganization.getActionsFromRoles(member.roles);
let permissions = {
instanceId: organization.id,
userId: member.id,
domainId: 'organization',
actions: actions
};
let promise = this.chePermissions.storePermissions(permissions);
promises.push(promise);
}
});
this.$q.all(promises).then(() => {
this.isLoading = false;
this.$location.path('/organization/' + organization.qualifiedName);
}, (error: any) => {
this.isLoading = false;
let message = error.data && error.data.message ? error.data.message : 'Failed to create organization ' + this.organizationName + '.';
this.cheNotification.showError(message);
});
}
}

View File

@ -0,0 +1,62 @@
<organization-not-found ng-if="createOrganizationController.parentQualifiedName && !createOrganizationController.parentOrganizationId"
organization-name="createOrganizationController.parentQualifiedName"></organization-not-found>
<div ng-if="!createOrganizationController.parentQualifiedName || createOrganizationController.parentOrganizationId"
flex layout="column">
<che-toolbar che-title="{{createOrganizationController.parentOrganizationId ? 'Create Sub-Organization' : 'Create New Organization'}}" border-none></che-toolbar>
<che-description>
Create {{createOrganizationController.parentOrganizationId ? 'a sub-organization' : 'an organization'}} to share resources.
</che-description>
<div class="organization-progress-line">
<md-progress-linear md-mode="indeterminate"
ng-show="createOrganizationController.isLoading"></md-progress-linear>
</div>
<md-content md-scroll-y flex md-theme="default" class="create-organization">
<ng-form name="createOrganizationForm">
<!-- Name -->
<che-label-container che-label-name="Name"
che-label-description="Name will be displayed in menus and prefix workspaces.">
<div layout="column" class="create-organization-input">
<che-input-box che-form="createOrganizationForm"
che-name="name"
che-place-holder="Enter organization name"
aria-label="Name of the organization"
ng-model="createOrganizationController.organizationName"
ng-trim
ng-minlength="1"
ng-maxlength="20"
ng-model-options="{allowInvalid: true, updateOn: 'default blur', debounce: { 'default': 200, 'blur': 0 } }"
custom-validator="createOrganizationController.isUniqueName($value)"
parent-account="createOrganizationController.accountName"
ng-keypress="createOrganizationForm.$valid && $event.which === 13 && createOrganizationController.createOrganization()"
ng-pattern="/^[a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,38}$/i"
required focusable>
<div ng-message="pattern">The name can contain alphanumeric characters or single '-' inside.
</div>
<div ng-message="minlength">The name has to be more than 1 character long.</div>
<div ng-message="maxlength">The name has to be less than 20 characters long.</div>
<div ng-message="customValidator">This organization name is already used.</div>
</che-input-box>
</div>
</che-label-container>
<che-label-container che-label-name="Members" che-label-description="Invite others to collaborate in the organization."
che-alignment="{{createOrganizationController.members.length > 0 ? 'column' : 'row'}}">
<list-organization-invite-members members="createOrganizationController.members"
parent-organization-id="createOrganizationController.parentOrganizationId"
parent-organization-members="createOrganizationController.parentOrganizationMembers"
class="create-organization-list"></list-organization-invite-members>
</che-label-container>
</ng-form>
<div layout="row" layout-align="center center">
<che-button-primary id="create-organization-button"
che-button-title="Create {{createOrganizationController.parentOrganizationId ? 'Sub-Organization' : 'Organization'}}"
ng-click="createOrganizationController.createOrganization()"
ng-disabled="!createOrganizationForm.$valid || createOrganizationController.isLoading">
</che-button-primary>
</div>
</md-content>
</div>

View File

@ -0,0 +1,8 @@
.create-organization
padding 0 52px
.che-label-container .che-label-container-content
width 100%
input.ng-invalid.ng-pristine:focus
border-color $primary-color

View File

@ -0,0 +1,322 @@
/*
* 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 {OrganizationsPermissionService} from '../organizations-permission.service';
/**
* @ngdoc controller
* @name organizations.list.controller:ListOrganizationsController
* @description This class is handling the controller for listing the organizations
* @author Oleksii Orel
*/
export class ListOrganizationsController {
/**
* Organization API interaction.
*/
private cheOrganization: che.api.ICheOrganization;
/**
* Service for displaying notifications.
*/
private cheNotification: any;
/**
* Service for displaying dialogs.
*/
private confirmDialogService: any;
/**
* Promises service.
*/
private $q: ng.IQService;
/**
* Permissions service.
*/
private chePermissions: che.api.IChePermissions;
/**
* Resources distribution service.
*/
private cheResourcesDistribution: che.api.ICheResourcesDistribution;
/**
* Organization permission service.
*/
private organizationsPermissionService: OrganizationsPermissionService;
/**
* List of organizations.
*/
private organizations: Array<any>;
/**
* Map of organization members.
*/
private organizationMembers: Map<string, number>;
/**
* Map of organization total resources.
*/
private organizationTotalResources: Map<string, any>;
/**
* Map of organization available resources.
*/
private organizationAvailableResources: Map<string, any>;
/**
* Loading state of the page.
*/
private isLoading: boolean;
/**
* On update function.
*/
private onUpdate: Function;
/**
* Parent organization name.
*/
private parentName: string;
/**
* Parent organization id.
*/
private parentId: string;
/**
* Organization order by.
*/
private organizationOrderBy: string;
/**
* Organization filter.
*/
private organizationFilter: {name: string};
/**
* User services.
*/
private userServices: che.IUserServices;
/**
* Selection and filtration helper.
*/
private cheListHelper: che.widget.ICheListHelper;
/**
* todo
*/
private resourceLimits: che.resource.ICheResourceLimits;
/**
* todo
*/
private organizationActions: che.resource.ICheOrganizationActions;
/**
* Default constructor that is using resource
* @ngInject for Dependency injection
*/
constructor($q: ng.IQService, $scope: ng.IScope, chePermissions: che.api.IChePermissions, cheResourcesDistribution: che.api.ICheResourcesDistribution, cheOrganization: che.api.ICheOrganization, cheNotification: any, confirmDialogService: any, $route: ng.route.IRouteService, organizationsPermissionService: OrganizationsPermissionService, cheListHelperFactory: che.widget.ICheListHelperFactory, resourcesService: che.service.IResourcesService) {
this.$q = $q;
this.cheNotification = cheNotification;
this.chePermissions = chePermissions;
this.cheOrganization = cheOrganization;
this.confirmDialogService = confirmDialogService;
this.cheResourcesDistribution = cheResourcesDistribution;
this.resourceLimits = resourcesService.getResourceLimits();
this.organizationActions = resourcesService.getOrganizationActions();
this.parentName = $route.current.params.organizationName;
this.organizationOrderBy = 'name';
this.organizationFilter = {name: ''};
const helperId = 'list-organizations';
this.cheListHelper = cheListHelperFactory.getHelper(helperId);
$scope.$on('$destroy', () => {
cheListHelperFactory.removeHelper(helperId);
});
this.userServices = this.chePermissions.getUserServices();
this.organizationsPermissionService = organizationsPermissionService;
$scope.$watch(() => {
return this.organizations;
}, (newValue: Array<any>, oldValue: Array<any>) => {
if (newValue && !angular.equals(newValue, oldValue)) {
this.processOrganizations();
}
});
this.processOrganizations();
}
/**
* Callback when name is changed.
*
* @param str {string} a string to filter organizations.
*/
onSearchChanged(str: string): void {
this.organizationFilter.name = str;
this.cheListHelper.applyFilter('name', this.organizationFilter);
}
/**
* Returns true if user has manage permission.
*
* @returns {boolean}
*/
hasManagePermission(): boolean {
if (this.parentId) {
return this.organizationsPermissionService.isUserAllowedTo(this.organizationActions.MANAGE_SUB_ORGANIZATION, this.parentId);
}
return this.userServices.hasAdminUserService;
}
/**
* Process organization - retrieving additional data.
*/
processOrganizations(): void {
if (angular.isUndefined(this.organizations)) {
return;
}
if (this.parentName) {
const parentOrganization = this.cheOrganization.getOrganizationByName(this.parentName);
this.parentId = parentOrganization ? parentOrganization.id : null;
}
if (this.organizations && this.organizations.length) {
this.organizationMembers = new Map();
this.organizationTotalResources = new Map();
this.organizationAvailableResources = new Map();
const promises = [];
this.isLoading = true;
this.organizations.forEach((organization: che.IOrganization) => {
const promiseMembers = this.chePermissions.fetchOrganizationPermissions(organization.id).then(() => {
this.organizationMembers.set(organization.id, this.chePermissions.getOrganizationPermissions(organization.id).length);
});
promises.push(promiseMembers);
let promiseTotalResource = this.cheResourcesDistribution.fetchTotalOrganizationResources(organization.id).then(() => {
this.processTotalResource(organization.id);
});
promises.push(promiseTotalResource);
let promiseAvailableResource = this.cheResourcesDistribution.fetchAvailableOrganizationResources(organization.id).then(() => {
this.processAvailableResource(organization.id);
});
promises.push(promiseAvailableResource);
});
this.$q.all(promises).finally(() => {
this.isLoading = false;
this.cheListHelper.setList(this.organizations, 'id');
});
} else {
this.cheListHelper.setList(this.organizations, 'id');
}
}
/**
* Process total organization resources.
*
* @param organizationId organization's id
*/
processTotalResource(organizationId: string): void {
let ramLimit = this.cheResourcesDistribution.getOrganizationTotalResourceByType(organizationId, this.resourceLimits.RAM);
this.organizationTotalResources.set(organizationId, ramLimit ? ramLimit.amount : undefined);
}
/**
* Process available organization resources.
*
* @param organizationId organization's id
*/
processAvailableResource(organizationId: string): void {
let ramLimit = this.cheResourcesDistribution.getOrganizationAvailableResourceByType(organizationId, this.resourceLimits.RAM);
this.organizationAvailableResources.set(organizationId, ramLimit ? ramLimit.amount : undefined);
}
/**
* Returns the number of organization's members.
*
* @param organizationId organization's id
* @returns {any} number of organization members to display
*/
getMembersCount(organizationId: string): any {
if (this.organizationMembers && this.organizationMembers.size > 0) {
return this.organizationMembers.get(organizationId) || '-';
}
return '-';
}
/**
* Returns the total RAM of the organization.
*
* @param organizationId organization's id
* @returns {any}
*/
getTotalRAM(organizationId: string): any {
if (this.organizationTotalResources && this.organizationTotalResources.size > 0) {
let ram = this.organizationTotalResources.get(organizationId);
return (ram && ram !== -1) ? (ram / 1024) : null;
}
return null;
}
/**
* Returns the available RAM of the organization.
*
* @param organizationId organization's id
* @returns {any}
*/
getAvailableRAM(organizationId: string): any {
if (this.organizationAvailableResources && this.organizationAvailableResources.size > 0) {
let ram = this.organizationAvailableResources.get(organizationId);
return (ram && ram !== -1) ? (ram / 1024) : null;
}
return null;
}
/**
* Delete all selected organizations.
*/
deleteSelectedOrganizations(): void {
const selectedOrganizations = this.cheListHelper.getSelectedItems(),
selectedOrganizationIds = selectedOrganizations.map((organization: che.IOrganization) => {
return organization.id;
});
if (!selectedOrganizationIds.length) {
this.cheNotification.showError('No such organization.');
return;
}
const confirmationPromise = this._showDeleteOrganizationConfirmation(selectedOrganizationIds.length);
confirmationPromise.then(() => {
let promises = [];
selectedOrganizationIds.forEach((organizationId: string) => {
this.cheListHelper.itemsSelectionStatus[organizationId] = false;
let promise = this.cheOrganization.deleteOrganization(organizationId).catch((error: any) => {
let errorMessage = 'Failed to delete organization ' + organizationId + '.';
this.cheNotification.showError(error && error.data && error.data.message ? error.data.message : errorMessage);
});
promises.push(promise);
});
this.$q.all(promises).finally(() => {
if (typeof this.onUpdate !== 'undefined') {
this.onUpdate();
}
});
});
}
/**
* Show confirmation popup before organization deletion.
*
* @param numberToDelete number of organization to be deleted
* @returns {ng.IPromise<any>}
*/
_showDeleteOrganizationConfirmation(numberToDelete: number): ng.IPromise<any> {
let content = 'Would you like to delete ';
if (numberToDelete > 1) {
content += 'these ' + numberToDelete + ' organizations?';
} else {
content += 'this selected organization?';
}
return this.confirmDialogService.showConfirmDialog('Delete organizations', content, 'Delete');
}
}

View File

@ -0,0 +1,42 @@
/*
* 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';
/**
* @ngdoc directive
* @name organizations.organizations:ListOrganizations
* @restrict E
* @element
*
* @description
* `<list-organizations organizations="ctrl.organizations"></list-organizations>` for displaying list of organizations
*
* @usage
* <list-organizations organizations="ctrl.organizations"></list-organizations>
*
* @author Oleksii Orel
*/
export class ListOrganizations implements ng.IDirective {
restrict: string = 'E';
templateUrl: string = 'app/organizations/list-organizations/list-organizations.html';
controller: string = 'ListOrganizationsController';
controllerAs: string = 'listOrganizationsController';
bindToController: boolean = true;
scope: any = {
isLoading: '=?',
organizations: '=',
hideAddButton: '=?',
onUpdate: '&?onUpdate'
};
}

View File

@ -0,0 +1,66 @@
<md-content flex class="list-organizations-content">
<div ng-class="{'list-empty': !(listOrganizationsController.organizations | filter:listOrganizationsController.organizationFilter).length}">
<che-list-header che-hide-header="!listOrganizationsController.organizations || listOrganizationsController.cheListHelper.visibleItemsNumber === 0"
che-input-placeholder="Search"
che-search-model="listOrganizationsController.organizationFilter.name"
che-on-search-change="listOrganizationsController.onSearchChanged(str)"
che-hide-search="!listOrganizationsController.organizations || listOrganizationsController.organizations.length === 0"
che-add-button-title="{{listOrganizationsController.parentName ? 'Add Sub-Organization' : 'Add Organization'}}"
che-add-button-href="#/admin/create-organization{{listOrganizationsController.parentName ? '/' + listOrganizationsController.parentName : ''}}"
che-hide-add="!listOrganizationsController.hasManagePermission() || listOrganizationsController.hideAddButton"
che-delete-button-title="Delete"
che-on-delete="listOrganizationsController.deleteSelectedOrganizations()"
che-hide-delete="!listOrganizationsController.hasManagePermission() || listOrganizationsController.cheListHelper.isNoItemSelected"
che-filter-values="listOrganizationsController.namespaceLabels"
che-on-filter-changed="listOrganizationsController.onFilterChanged">
<div flex="100"
layout="row"
layout-align="start stretch"
class="che-list-item-row">
<div ng-if="listOrganizationsController.hasManagePermission()"
layout="column" layout-gt-xs="row" layout-align="start center" class="che-checkbox-area">
<div layout="row" layout-align="center center" class="che-list-item-checkbox-main">
<md-checkbox class="che-list-item-checkbox"
aria-label="organization list"
ng-checked="listOrganizationsController.cheListHelper.areAllItemsSelected"
ng-click="listOrganizationsController.cheListHelper.changeBulkSelection()"></md-checkbox>
</div>
</div>
<div flex hide-xs layout-gt-xs="row"
layout-align="start center"
class="che-list-item-details">
<che-list-header-column flex-gt-xs="30"
che-sort-value='listOrganizationsController.organizationOrderBy'
che-sort-item='name'
che-column-title='Name'></che-list-header-column>
<che-list-header-column flex-gt-xs="20"
che-column-title='Members'></che-list-header-column>
<che-list-header-column flex-gt-xs="15"
che-column-title='Total RAM'></che-list-header-column>
<che-list-header-column flex-gt-xs="15"
che-column-title='Available RAM'></che-list-header-column>
<che-list-header-column flex-gt-xs="20"
che-column-title='Sub-Organizations'></che-list-header-column>
<che-list-header-column flex-gt-xs="15"
che-column-title='Actions'></che-list-header-column>
</div>
</div>
</che-list-header>
<che-list ng-show="listOrganizationsController.organizations">
<organizations-item
ng-repeat="organization in listOrganizationsController.cheListHelper.getVisibleItems() | orderBy:listOrganizationsController.organizationOrderBy"
organization="organization"
members="listOrganizationsController.getMembersCount(organization.id)"
total-ram="listOrganizationsController.getTotalRAM(organization.id)"
available-ram="listOrganizationsController.getAvailableRAM(organization.id)"
cdvy-is-selectable="listOrganizationsController.hasManagePermission()"
ng-model="listOrganizationsController.cheListHelper.itemsSelectionStatus[organization.id]"
on-update="listOrganizationsController.onUpdate()"
cdvy-on-checkbox-click="listOrganizationsController.cheListHelper.updateBulkSelectionStatus()">
</organizations-item>
</che-list>
</div>
<div class="che-list-empty">
<span ng-show="listOrganizationsController.cheListHelper.visibleItemsNumber === 0">There are no {{listOrganizationsController.parentName ? 'sub-organizations' : 'organizations'}}.</span>
</div>
</md-content>

View File

@ -0,0 +1,8 @@
.list-organizations-content
margin 0
.che-list-item
display block !important
.list-empty .che-list-header md-item
border-top none

View File

@ -0,0 +1,128 @@
/*
* 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 {OrganizationsPermissionService} from '../../organizations-permission.service';
/**
* @ngdoc controller
* @name organizations.list.Item.controller:OrganizationsItemController
* @description This class is handling the controller for item of organizations list
* @author Oleksii Orel
*/
export class OrganizationsItemController {
/**
* Service for displaying dialogs.
*/
private confirmDialogService: any;
/**
* Location service.
*/
private $location: ng.ILocationService;
/**
* User service.
*/
private userServices: che.IUserServices;
/**
* Organization permission service.
*/
private organizationsPermissionService: OrganizationsPermissionService;
/**
* Organization API interaction.
*/
private cheOrganization: che.api.ICheOrganization;
/**
* Service for displaying notifications.
*/
private cheNotification: any;
/**
* Organization details (the value is set in directive attributes).
*/
private organization: che.IOrganization;
/**
* Callback needed to react on organizations updation (the value is set in directive attributes).
*/
private onUpdate: Function;
/**
* todo
*/
private organizationActions: che.resource.ICheOrganizationActions;
/**
* Default constructor that is using resource injection
* @ngInject for Dependency injection
*/
constructor($location: ng.ILocationService, cheOrganization: che.api.ICheOrganization, confirmDialogService: any, cheNotification: any, organizationsPermissionService: OrganizationsPermissionService, chePermissions: che.api.IChePermissions, resourcesService: che.service.IResourcesService) {
this.$location = $location;
this.confirmDialogService = confirmDialogService;
this.cheOrganization = cheOrganization;
this.cheNotification = cheNotification;
this.organizationsPermissionService = organizationsPermissionService;
this.organizationActions = resourcesService.getOrganizationActions();
this.userServices = chePermissions.getUserServices();
}
/**
* returns true if current user has Delete permission
* @returns {boolean}
*/
hasDeletePermission(): boolean {
if (!this.organization || (!this.organization.parent && !this.userServices.hasAdminUserService)) {
return false;
}
return this.organizationsPermissionService.isUserAllowedTo(this.organizationActions.DELETE, this.organization.id);
}
/**
* Gets all sub organizations.
*/
getAllSubOrganizations(): Array<che.IOrganization> {
let subOrganizationsTree = this.cheOrganization.getOrganizations().filter((organization: che.IOrganization) => {
if (!organization.parent || this.organization.id === organization.id) {
return false;
}
return organization.qualifiedName.indexOf(this.organization.qualifiedName + '/') === 0;
});
return subOrganizationsTree;
}
/**
* Redirect to factory details.
*/
redirectToOrganizationDetails(tab: string) {
this.$location.path('/organization/' + this.organization.qualifiedName).search(!tab ? {} : {tab: tab});
}
/**
* Removes organization after confirmation.
*/
removeOrganization(): void {
this.confirmRemoval().then(() => {
this.cheOrganization.deleteOrganization(this.organization.id).then(() => {
this.onUpdate();
}, (error: any) => {
let message = 'Failed to delete organization ' + this.organization.name;
this.cheNotification.showError(error && error.data && error.data.message ? error.data.message : message);
});
});
}
/**
* Shows dialog to confirm the current organization removal.
*
* @returns {angular.IPromise<any>}
*/
confirmRemoval(): ng.IPromise<any> {
return this.confirmDialogService.showConfirmDialog('Delete organization',
'Would you like to delete organization \'' + this.organization.name + '\'?', 'Delete');
}
}

View File

@ -0,0 +1,48 @@
/*
* 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';
/**
* @ngdoc directive
* @name organizations.list.Item.controller:WorkspaceItem
* @restrict E
* @element
*
* @description
* `<organizations-item organization="ctrl.organization"></organizations-item>` for displaying list of organizations
*
* @usage
* <organizations-item organization="ctrl.organization"></organizations-item>
*
* @author Oleksii Orel
*/
export class OrganizationsItem implements ng.IDirective {
restrict = 'E';
require = ['ngModel'];
templateUrl = 'app/organizations/list-organizations/organizations-item/organizations-item.html';
controller = 'OrganizationsItemController';
controllerAs = 'organizationsItemController';
bindToController = true;
// scope values
scope = {
organization: '=',
members: '=',
totalRam: '=',
availableRam: '=',
isChecked: '=cdvyChecked',
isSelect: '=?ngModel',
isSelectable: '=?cdvyIsSelectable',
onCheckboxClick: '&?cdvyOnCheckboxClick',
onUpdate: '&?onUpdate'
};
}

View File

@ -0,0 +1,57 @@
<che-list-item flex ng-mouseover="hover=true" ng-mouseout="hover=false">
<div flex="100"
layout="row"
layout-align="start stretch"
class="che-list-item-row">
<div ng-if="organizationsItemController.isSelectable"
layout="row" layout-align="start center" class="che-checkbox-area">
<che-list-item-checked ng-model="organizationsItemController.isSelect"
che-aria-label-checkbox="Organization {{organizationsItemController.organization.name}}"
ng-click="organizationsItemController.onCheckboxClick()"></che-list-item-checked>
</div>
<div flex
layout-xs="column" layout-gt-xs="row"
layout-align-gt-xs="start center"
layout-align-xs="start start"
class="che-list-item-details">
<div flex-gt-xs="30"
class="che-list-item-name"
ng-click="organizationsItemController.redirectToOrganizationDetails();">
<span class="che-xs-header noselect" hide-gt-xs>Name</span>
<span class="che-hover">{{organizationsItemController.organization.qualifiedName}}</span>
</div>
<div flex-gt-xs="20"
ng-click="organizationsItemController.redirectToOrganizationDetails('Members')">
<span class="che-xs-header noselect" hide-gt-xs>Members</span>
<span>{{organizationsItemController.members}}</span>
</div>
<div flex-gt-xs="15"
ng-click="organizationsItemController.redirectToOrganizationDetails()"
class="cap-value">
<span class="che-xs-header noselect" hide-gt-xs>Total RAM</span>
<span>{{organizationsItemController.totalRam ? organizationsItemController.totalRam + 'GB' : 'not limited'}}</span>
</div>
<div flex-gt-xs="15"
ng-click="organizationsItemController.redirectToOrganizationDetails()"
class="cap-value">
<span class="che-xs-header noselect" hide-gt-xs>Available RAM</span>
<span>{{organizationsItemController.availableRam ? organizationsItemController.availableRam + 'GB' : 'not limited'}}</span>
</div>
<div flex-gt-xs="20"
ng-click="organizationsItemController.redirectToOrganizationDetails('Organization')">
<span class="che-xs-header noselect" hide-gt-xs>Sub-Organizations</span>
<span>
{{organizationsItemController.getAllSubOrganizations().length ? organizationsItemController.getAllSubOrganizations().length : 'none'}}
</span>
</div>
<div flex-gt-xs="15">
<span class="che-xs-header noselect" hide-gt-xs>Actions</span>
<span class="che-list-actions" ng-if="organizationsItemController.hasDeletePermission() || organizationsItemController.isSelectable">
<a uib-tooltip="Remove organization" ng-click="organizationsItemController.removeOrganization();">
<span class="fa fa-trash-o"></span>
</a>
</span>
</div>
</div>
</div>
</che-list-item>

View File

@ -0,0 +1,2 @@
.cap-value
color $primary-color

View File

@ -0,0 +1,556 @@
/*
* 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 {OrganizationsPermissionService} from '../organizations-permission.service';
enum Tab {Settings, Members, Organization}
/**
* Controller for a managing organization details.
*
* @author Oleksii Orel
*/
export class OrganizationDetailsController {
tab: Object = Tab;
/**
* Organization API interaction.
*/
private cheOrganization: che.api.ICheOrganization;
/**
* Organization resources API interaction.
*/
private cheResourcesDistribution: che.api.ICheResourcesDistribution;
/**
* Permissions API interaction.
*/
private chePermissions: che.api.IChePermissions;
/**
* User API interaction.
*/
private cheUser: any;
/**
* Notifications service.
*/
private cheNotification: any;
/**
* Location service.
*/
private $location: ng.ILocationService;
/**
* Route service.
*/
private $route: ng.route.IRouteService;
/**
* Service for displaying dialogs.
*/
private confirmDialogService: any;
/**
* Lodash library.
*/
private lodash: any;
/**
* Current organization's name. Comes from route path params.
*/
private organizationName: string;
/**
* Current organization's data (injected by $routeProvider)
*/
private organization: che.IOrganization;
/**
* Parent organization member's list (injected by $routeProvider)
*/
private parentOrganizationMembers: Array<che.IUser>;
/**
* The list of allowed user actions.
*/
private allowedUserActions: Array<string>;
/**
* New organization's name (for renaming widget).
*/
private newName: string;
/**
* Index of the selected tab.
*/
private selectedTabIndex: number;
/**
* Organization limits.
*/
private limits: any;
/**
* Copy of limits before letting to modify, to be able to compare.
*/
private limitsCopy: any;
/**
* Organization total resources.
*/
private totalResources: any;
/**
* Copy of organization total resources before letting to modify, to be able to compare.
*/
private totalResourcesCopy: any;
/**
* Page loading state.
*/
private isLoading: boolean;
private organizationForm: ng.IFormController;
private subOrganizations: Array<any> = [];
private organizationsPermissionService: OrganizationsPermissionService;
private resourceLimits: che.resource.ICheResourceLimits;
private organizationActions: che.resource.ICheOrganizationActions;
/**
* Default constructor that is using resource injection
* @ngInject for Dependency injection
*/
constructor(cheResourcesDistribution: che.api.ICheResourcesDistribution, chePermissions: che.api.IChePermissions,
cheUser: any, $route: ng.route.IRouteService, $location: ng.ILocationService, $rootScope: che.IRootScopeService,
$scope: ng.IScope, confirmDialogService: any, cheNotification: any,
lodash: any, cheOrganization: che.api.ICheOrganization, organizationsPermissionService: OrganizationsPermissionService, resourcesService: che.service.IResourcesService,
initData: any) {
this.cheResourcesDistribution = cheResourcesDistribution;
this.confirmDialogService = confirmDialogService;
this.cheOrganization = cheOrganization;
this.organizationsPermissionService = organizationsPermissionService;
this.chePermissions = chePermissions;
this.cheNotification = cheNotification;
this.cheUser = cheUser;
this.$location = $location;
this.$route = $route;
this.lodash = lodash;
this.resourceLimits = resourcesService.getResourceLimits();
this.organizationActions = resourcesService.getOrganizationActions();
// injected by router
this.organization = initData.organization as che.IOrganization;
this.parentOrganizationMembers = initData.parentOrganizationMembers as Array<che.IUser>;
$rootScope.showIDE = false;
this.allowedUserActions = [];
this.updateData();
this.updateSelectedTab(this.$location.search().tab);
let deRegistrationFn = $scope.$watch(() => {
return $location.search().tab;
}, (tab: string) => {
if (angular.isDefined(tab)) {
this.updateSelectedTab(tab);
}
}, true);
$scope.$on('$destroy', () => {
deRegistrationFn();
});
}
get SET_PERMISSIONS(): string {
return this.organizationActions.SET_PERMISSIONS;
}
get DELETE(): string {
return this.organizationActions.DELETE;
}
get UPDATE(): string {
return this.organizationActions.UPDATE;
}
/**
* Fetch sub-organizations.
*/
fetchSubOrganizations() {
let manageSubOrganizations = this.isUserAllowedTo(this.organizationActions.MANAGE_SUB_ORGANIZATION);
if (manageSubOrganizations) {
this.cheOrganization.fetchSubOrganizationsById(this.organization.id).then((data: any) => {
this.subOrganizations = data;
});
} else {
this.cheOrganization.fetchOrganizations().then(() => {
this.subOrganizations = this.lodash.filter(this.cheOrganization.getOrganizations(), (organization: che.IOrganization) => {
return organization.parent === this.organization.id;
});
});
}
}
/**
* Update data.
*/
updateData(): void {
this.organizationName = this.$route.current.params.organizationName;
if (!this.organization) {
return;
}
this.newName = angular.copy(this.organization.name);
if (this.isRootOrganization()) {
this.processTotalResources();
} else {
this.processResources();
}
this.allowedUserActions = this.processUserPermissions();
this.fetchSubOrganizations();
}
/**
* 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.isDefined(tabIndex)) {
param.tab = Tab[tabIndex];
}
if (angular.isUndefined(this.$location.search().tab)) {
this.$location.replace().search(param);
} else {
this.$location.search(param);
}
}
/**
* Gets sub-organizations for current organization.
*
* @returns {Array<any>}
*/
getSubOrganizations(): Array<any> {
return this.subOrganizations;
}
/**
* Process permissions to retrieve current user actions.
*
* @returns {Array} current user allowed actions
*/
processUserPermissions(): Array<string> {
let userId = this.cheUser.getUser().id;
let permissions = this.chePermissions.getOrganizationPermissions(this.organization.id);
let userPermissions = this.lodash.find(permissions, (permission: any) => {
return permission.userId === userId;
});
return userPermissions ? userPermissions.actions : [];
}
/**
* Checks whether user is allowed to perform pointed action.
*
* @param value action
* @returns {boolean} <code>true</code> if allowed
*/
isUserAllowedTo(value: string): boolean {
if (value === this.organizationActions.UPDATE && this.isPersonalOrganization()) {
return false;
}
return this.allowedUserActions ? this.allowedUserActions.indexOf(value) >= 0 : false;
}
/**
* Checks for personal.
*
* @returns {boolean} <code>true</code> if personal
*/
isPersonalOrganization(): boolean {
let user = this.cheUser.getUser();
return this.organization && user && this.organization.qualifiedName === user.name;
}
/**
* Checks for root.
*
* @returns {boolean} <code>true</code> if root
*/
isRootOrganization(): boolean {
return this.organization && !this.organization.parent;
}
/**
* Returns whether current user can change organization resource limits.
*
* @returns {boolean} <code>true</code> if can change resource limits
*/
canChangeResourceLimits(): boolean {
if (this.isRootOrganization()) {
return this.chePermissions.getUserServices().hasAdminUserService;
}
return this.organizationsPermissionService.isUserAllowedTo(this.organizationActions.MANAGE_RESOURCES, this.organization.parent);
}
/**
* Check if the name is unique.
* @param name
* @returns {boolean}
*/
isUniqueName(name: string): boolean {
let currentOrganizationName = this.organization.name;
let organizations = this.cheOrganization.getOrganizations();
let account = '';
let parentId = this.organization.parent;
if (parentId) {
let parent = this.cheOrganization.getOrganizationById(parentId);
if (parent && parent.qualifiedName) {
account = parent.qualifiedName + '/';
}
}
if (organizations.length && currentOrganizationName !== name) {
for (let i = 0; i < organizations.length; i++) {
if (organizations[i].qualifiedName === account + name) {
return false;
}
}
return true;
} else {
return true;
}
}
/**
* Fetches defined organization's limits (workspace, runtime, RAM caps, etc).
*/
fetchLimits(): void {
this.isLoading = true;
this.cheResourcesDistribution.fetchOrganizationResources(this.organization.id).then(() => {
this.isLoading = false;
this.processResources();
}, (error: any) => {
this.isLoading = false;
this.limits = {};
this.limitsCopy = angular.copy(this.limits);
});
}
/**
* Process resources limits.
*/
processResources(): void {
let ramLimit = this.cheResourcesDistribution.getOrganizationResourceByType(this.organization.id, this.resourceLimits.RAM);
let workspaceLimit = this.cheResourcesDistribution.getOrganizationResourceByType(this.organization.id, this.resourceLimits.WORKSPACE);
let runtimeLimit = this.cheResourcesDistribution.getOrganizationResourceByType(this.organization.id, this.resourceLimits.RUNTIME);
this.limits = {};
this.limits.workspaceCap = workspaceLimit ? workspaceLimit.amount : undefined;
this.limits.runtimeCap = runtimeLimit ? runtimeLimit.amount : undefined;
this.limits.ramCap = ramLimit ? ramLimit.amount / 1024 : undefined;
this.limitsCopy = angular.copy(this.limits);
}
/**
* Fetches total resources of the organization (workspace, runtime, RAM caps, etc).
*/
fetchTotalResources(): void {
this.isLoading = true;
this.cheResourcesDistribution.fetchTotalOrganizationResources(this.organization.id).then(() => {
this.isLoading = false;
this.processTotalResources();
}, (error: any) => {
this.isLoading = false;
this.limits = {};
this.limitsCopy = angular.copy(this.limits);
});
}
/**
* Process organization's total resources.
*/
processTotalResources(): void {
let ram = this.cheResourcesDistribution.getOrganizationTotalResourceByType(this.organization.id, this.resourceLimits.RAM);
let workspace = this.cheResourcesDistribution.getOrganizationTotalResourceByType(this.organization.id, this.resourceLimits.WORKSPACE);
let runtime = this.cheResourcesDistribution.getOrganizationTotalResourceByType(this.organization.id, this.resourceLimits.RUNTIME);
this.totalResources = {};
this.totalResources.workspaceCap = (workspace && workspace.amount !== -1) ? workspace.amount : undefined;
this.totalResources.runtimeCap = (runtime && runtime.amount !== -1) ? runtime.amount : undefined;
this.totalResources.ramCap = (ram && ram.amount !== -1) ? ram.amount / 1024 : undefined;
this.totalResourcesCopy = angular.copy(this.totalResources);
}
/**
* Confirms and performs organization's deletion.
*/
deleteOrganization(): void {
let promise = this.confirmDialogService.showConfirmDialog('Delete organization',
'Would you like to delete organization \'' + this.organization.name + '\'?', 'Delete');
promise.then(() => {
let promise = this.cheOrganization.deleteOrganization(this.organization.id);
promise.then(() => {
this.$location.path('/organizations');
}, (error: any) => {
this.cheNotification.showError(error.data.message !== null ? error.data.message : 'Team deletion failed.');
});
});
}
/**
* Update organization's details.
*
*/
updateOrganizationName(): void {
if (this.newName && this.organization && this.newName !== this.organization.name) {
this.organization.name = this.newName;
this.cheOrganization.updateOrganization(this.organization).then((organization: che.IOrganization) => {
this.cheOrganization.fetchOrganizations().then(() => {
this.$location.path('/organization/' + organization.qualifiedName);
});
}, (error: any) => {
this.cheNotification.showError((error.data && error.data.message !== null) ? error.data.message : 'Rename organization failed.');
});
}
}
/**
* Update resource limits.
*/
updateLimits(): void {
if (!this.organization || !this.limits || angular.equals(this.limits, this.limitsCopy)) {
return;
}
let resources = angular.copy(this.cheResourcesDistribution.getOrganizationResources(this.organization.id));
let resourcesToRemove = [this.resourceLimits.TIMEOUT];
if (this.limits.ramCap !== null && this.limits.ramCap !== undefined) {
resources = this.cheResourcesDistribution.setOrganizationResourceLimitByType(resources, this.resourceLimits.RAM, (this.limits.ramCap * 1024).toString());
} else {
resourcesToRemove.push(this.resourceLimits.RAM);
}
if (this.limits.workspaceCap !== null && this.limits.workspaceCap !== undefined) {
resources = this.cheResourcesDistribution.setOrganizationResourceLimitByType(resources, this.resourceLimits.WORKSPACE, this.limits.workspaceCap);
} else {
resourcesToRemove.push(this.resourceLimits.WORKSPACE);
}
if (this.limits.runtimeCap !== null && this.limits.runtimeCap !== undefined) {
resources = this.cheResourcesDistribution.setOrganizationResourceLimitByType(resources, this.resourceLimits.RUNTIME, this.limits.runtimeCap);
} else {
resourcesToRemove.push(this.resourceLimits.RUNTIME);
}
// if the timeout resource will be send in this case - it will set the timeout for the current organization, and the updating timeout of
// parent organization will not affect the current organization, so to avoid this - remove timeout resource if present:
this.lodash.remove(resources, (resource: any) => {
return resourcesToRemove.indexOf(resource.type) >= 0;
});
this.isLoading = true;
this.cheResourcesDistribution.distributeResources(this.organization.id, resources).then(() => {
this.fetchLimits();
}, (error: any) => {
let errorMessage = 'Failed to set update organization CAPs.';
this.cheNotification.showError((error.data && error.data.message !== null) ? errorMessage + '</br>Reason: ' + error.data.message : errorMessage);
this.fetchLimits();
});
}
/**
* Update resource limits.
*/
updateTotalResources(): void {
if (!this.organization || !this.totalResources || angular.equals(this.totalResources, this.totalResourcesCopy)) {
return;
}
let resources = angular.copy(this.cheResourcesDistribution.getTotalOrganizationResources(this.organization.id));
let resourcesToRemove = [this.resourceLimits.TIMEOUT];
if (this.totalResources.ramCap !== null && this.totalResources.ramCap !== undefined) {
resources = this.cheResourcesDistribution.setOrganizationResourceLimitByType(resources, this.resourceLimits.RAM, (this.totalResources.ramCap * 1024).toString());
} else {
resources = this.cheResourcesDistribution.setOrganizationResourceLimitByType(resources, this.resourceLimits.RAM, '-1');
}
if (this.totalResources.workspaceCap !== null && this.totalResources.workspaceCap !== undefined) {
resources = this.cheResourcesDistribution.setOrganizationResourceLimitByType(resources, this.resourceLimits.WORKSPACE, this.totalResources.workspaceCap);
} else {
resources = this.cheResourcesDistribution.setOrganizationResourceLimitByType(resources, this.resourceLimits.WORKSPACE, '-1');
}
if (this.totalResources.runtimeCap !== null && this.totalResources.runtimeCap !== undefined) {
resources = this.cheResourcesDistribution.setOrganizationResourceLimitByType(resources, this.resourceLimits.RUNTIME, this.totalResources.runtimeCap);
} else {
resources = this.cheResourcesDistribution.setOrganizationResourceLimitByType(resources, this.resourceLimits.RUNTIME, '-1');
}
// if the timeout resource will be send in this case - it will set the timeout for the current organization, and the updating timeout of
// parent organization will not affect the current organization, so to avoid this - remove timeout resource if present:
this.lodash.remove(resources, (resource: any) => {
return resourcesToRemove.indexOf(resource.type) >= 0;
});
this.isLoading = true;
this.cheResourcesDistribution.updateTotalResources(this.organization.id, resources).then(() => {
this.fetchTotalResources();
}, (error: any) => {
let errorMessage = 'Failed to update organization CAPs.';
this.cheNotification.showError((error.data && error.data.message !== null) ? errorMessage + '</br>Reason: ' + error.data.message : errorMessage);
this.fetchTotalResources();
});
}
/**
* Returns whether save button is disabled.
*
* @return {boolean}
*/
isSaveButtonDisabled(): boolean {
return !this.organizationForm || this.organizationForm.$invalid;
}
/**
* Returns true if "Save" button should be visible
*
* @return {boolean}
*/
isSaveButtonVisible(): boolean {
return (this.selectedTabIndex === Tab.Settings && !this.isLoading) && (!angular.equals(this.organization.name, this.newName) ||
!angular.equals(this.limits, this.limitsCopy) || !angular.equals(this.totalResources, this.totalResourcesCopy));
}
/**
* Returns back button link and title.
*
* @returns {any} back button link
*/
getBackButtonLink(): any {
if (this.organization && this.organization.parent) {
let parent = this.organization.qualifiedName.replace(/\/[^\/]+$/, '');
return {link: '#/organization/' + parent, title: parent};
} else {
return {link: '#/organizations', title: 'Organizations'};
}
}
updateOrganization(): void {
this.updateOrganizationName();
this.updateLimits();
this.updateTotalResources();
}
cancelChanges(): void {
this.newName = angular.copy(this.organization.name);
this.limits = angular.copy(this.limitsCopy);
this.totalResources = angular.copy(this.totalResourcesCopy);
}
}

View File

@ -0,0 +1,201 @@
<che-toolbar che-title="{{organizationDetailsController.organizationName}}"
che-breadcrumb-title="{{organizationDetailsController.getBackButtonLink().title}}"
che-breadcrumb-href="{{organizationDetailsController.getBackButtonLink().link}}"
ng-if="organizationDetailsController.organization">
<div class="save-button-placeholder">
<che-button-save-flat ng-show="organizationDetailsController.isSaveButtonVisible()"
ng-disabled="organizationDetailsController.isSaveButtonDisabled()"
che-button-title="Save" name="saveButton"
ng-click="organizationDetailsController.updateOrganization()"></che-button-save-flat>
</div>
</che-toolbar>
<md-content md-scroll-y flex md-theme="default"
ng-if="organizationDetailsController.organization">
<md-tabs md-dynamic-height md-stretch-tabs="auto"
md-selected="organizationDetailsController.selectedTabIndex"
md-center-tabs="">
<!-- Settings Tab -->
<md-tab md-on-select="organizationDetailsController.onSelectTab(organizationDetailsController.tab.Settings);">
<md-tab-label>
<md-icon md-font-icon="material-design icon-ic_settings_24px" class="che-tab-label-icon"></md-icon>
<span class="che-tab-label-title">Settings</span>
</md-tab-label>
<md-tab-body>
<div class="organization-progress-line">
<md-progress-linear md-mode="indeterminate"
ng-show="organizationDetailsController.isLoading"></md-progress-linear>
</div>
<div flex layout="column" class="organization-details-content">
<ng-form name="organizationDetailsController.organizationForm">
<!-- Name -->
<che-label-container che-label-name="Name">
<che-input-box che-form="organizationDetailsController.organizationForm"
che-name="name"
aria-label="Name of the organization"
che-place-holder="Name of the organization"
ng-model="organizationDetailsController.newName"
che-readonly="!organizationDetailsController.isUserAllowedTo(organizationDetailsController.UPDATE)"
custom-validator="organizationDetailsController.isUniqueName($value)"
required
ng-maxlength="20"
ng-pattern="/^[a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,38}$/i">
<div ng-message="pattern">The name can contain alphanumeric characters or single '-' inside.
</div>
<div ng-message="maxlength">The name has to be less than 20 characters long.</div>
<div ng-message="md-maxlength">The name has to be less than 20 characters long.</div>
<div ng-message="customValidator">This organization name is already used.</div>
</che-input-box>
</che-label-container>
<div ng-if="!organizationDetailsController.isRootOrganization()">
<!-- Workspace cap -->
<che-label-container che-label-name="Workspace Cap"
che-label-description="Maximum number of workspaces for the organization.">
<che-input-box che-name="workspaceCap" che-form="organizationDetailsController.organizationForm"
aria-label="workspace cap"
che-place-holder="Total number of workspaces has not been limited."
ng-model="organizationDetailsController.limits.workspaceCap"
che-readonly="!organizationDetailsController.canChangeResourceLimits()"
type="number"
che-type-number
min="0"
max="1000">
<div ng-message="min">A workspace cap should be greater than 0.</div>
</che-input-box>
</che-label-container>
<!-- Running workspace cap -->
<che-label-container che-label-name="Running Workspace Cap"
che-label-description="Maximum number of running workspaces for each organization.">
<che-input-box che-name="runtimeCap" che-form="organizationDetailsController.organizationForm"
aria-label="runtime cap"
che-place-holder="Number of running workspaces has not been limited."
ng-model="organizationDetailsController.limits.runtimeCap"
che-readonly="!organizationDetailsController.canChangeResourceLimits()"
type="number"
che-type-number
min="0"
max="1000">
<div ng-message="min">A running workspace cap should be greater than 0.</div>
</che-input-box>
</che-label-container>
<!-- Workspace RAM cap -->
<che-label-container che-label-name="Workspace RAM Cap" class="organization-ram-cap"
che-label-description="Maximum RAM organization workspaces can use.">
<che-input-box che-name="workspaceRamCap" che-form="organizationDetailsController.organizationForm"
aria-label="runtime cap"
che-place-holder="Workspace RAM has not been limited."
ng-model="organizationDetailsController.limits.ramCap"
che-readonly="!organizationDetailsController.canChangeResourceLimits()"
type="number"
che-type-number
min="0"
max="1000">
<div ng-message="min">A workspace RAM cap should be greater than 0.</div>
</che-input-box>
</che-label-container>
</div>
<div ng-if="organizationDetailsController.isRootOrganization()">
<!-- Workspace total resources -->
<che-label-container che-label-name="Workspace Cap"
che-label-description="Maximum number of workspaces for the organization.">
<che-input-box che-name="workspaceCap" che-form="organizationDetailsController.organizationForm"
aria-label="workspace cap"
che-place-holder="Total number of workspaces has not been limited."
ng-model="organizationDetailsController.totalResources.workspaceCap"
che-readonly="!organizationDetailsController.canChangeResourceLimits()"
type="number"
che-type-number
min="0"
max="1000">
<div ng-message="min">A workspace cap should be greater than 0.</div>
</che-input-box>
</che-label-container>
<!-- Running workspace cap -->
<che-label-container che-label-name="Running Workspace Cap"
che-label-description="Maximum number of running workspaces for each organization.">
<che-input-box che-name="runtimeCap" che-form="organizationDetailsController.organizationForm"
aria-label="runtime cap"
che-place-holder="Number of running workspaces has not been limited."
ng-model="organizationDetailsController.totalResources.runtimeCap"
che-readonly="!organizationDetailsController.canChangeResourceLimits()"
type="number"
che-type-number
min="0"
max="1000">
<div ng-message="min">A running workspace cap should be greater than 0.</div>
</che-input-box>
</che-label-container>
<!-- Workspace RAM cap -->
<che-label-container che-label-name="Workspace RAM Cap" class="organization-ram-cap"
che-label-description="Maximum RAM organization workspaces can use.">
<che-input-box che-name="workspaceRamCap" che-form="organizationDetailsController.organizationForm"
aria-label="runtime cap"
che-place-holder="Workspace RAM has not been limited."
ng-model="organizationDetailsController.totalResources.ramCap"
che-readonly="!organizationDetailsController.canChangeResourceLimits()"
type="number"
che-type-number
min="0"
max="1000">
<div ng-message="min">A workspace RAM cap should be greater than 0.</div>
</che-input-box>
</che-label-container>
</div>
<che-label-container class="organization-details-delete-label"
ng-if="organizationDetailsController.isUserAllowedTo(organizationDetailsController.DELETE)"
che-label-name="Delete Organization"
che-label-description="This is irreversible. Deleting your organization will also destroy organization workspaces and stacks.">
<che-button-danger che-button-title="Delete"
ng-click="organizationDetailsController.deleteOrganization(t)"></che-button-danger>
</che-label-container>
</ng-form>
</div>
</md-tab-body>
</md-tab>
<!-- Members Tab -->
<md-tab md-on-select="organizationDetailsController.onSelectTab(organizationDetailsController.tab.Members);">
<md-tab-label>
<md-icon md-font-icon="fa-group" class="fa che-tab-label-icon"></md-icon>
<span class="che-tab-label-title">Members</span>
</md-tab-label>
<md-tab-body>
<list-organization-members
ng-if="organizationDetailsController.organization && organizationDetailsController.organization.id"
organization="organizationDetailsController.organization"
parent-organization-members="organizationDetailsController.parentOrganizationMembers"
editable="organizationDetailsController.isUserAllowedTo(organizationDetailsController.SET_PERMISSIONS)"></list-organization-members>
</md-tab-body>
</md-tab>
<!-- Sub Organizations Tab -->
<md-tab md-on-select="organizationDetailsController.onSelectTab(organizationDetailsController.tab.Organization);">
<md-tab-label>
<md-icon md-font-icon="md-font fa fa-sitemap material-icons" class="che-tab-label-icon"></md-icon>
<span class="che-tab-label-title">Sub-Organizations</span>
</md-tab-label>
<md-tab-body>
<div class="organization-progress-line">
<md-progress-linear md-mode="indeterminate"
ng-show="organizationDetailsController.isLoading"></md-progress-linear>
</div>
<list-organizations is-loading="organizationsController.isInfoLoading"
on-update="organizationDetailsController.fetchSubOrganizations()"
organizations="organizationDetailsController.getSubOrganizations()"></list-organizations>
</md-tab-body>
</md-tab>
</md-tabs>
</md-content>
<organization-not-found ng-if="!organizationDetailsController.organization"
organization-name="organizationDetailsController.organizationName"></organization-not-found>
<workspace-edit-mode-overlay ng-if="organizationDetailsController.isSaveButtonVisible()"
workspace-edit-disable-save-button="organizationDetailsController.isSaveButtonDisabled()"
workspace-edit-mode-on-save="organizationDetailsController.updateOrganization()"
workspace-edit-mode-on-cancel="organizationDetailsController.cancelChanges()"></workspace-edit-mode-overlay>

View File

@ -0,0 +1,28 @@
.organization-details-content
padding 0 14px
.organization-ram-cap .che-input-box-desktop-value-column:after
color $label-secondary-color
content "GB"
position absolute
right 7px
top 0
line-height 40px
.organization-details-delete-label label
color $che-delete-label-color !important
.che-label-container-content button
margin-left 0
margin-top 0
.save-button-placeholder
width 89px
.organization-progress-line
top 0
left 0
right 0
height 5px
position absolute
z-index 2147483647

View File

@ -0,0 +1,259 @@
/*
* 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';
/**
* @ngdoc controller
* @name organization.details.invite-members:ListOrganizationInviteMembersController
* @description This class is handling the controller for the list of invited organization members.
* @author Oleksii Orel
*/
export class ListOrganizationInviteMembersController {
/**
* Lodash library.
*/
private lodash: any;
/**
* Service for displaying dialogs.
*/
private $mdDialog: ng.material.IDialogService;
/**
* No members selected.
*/
private isNoSelected: boolean;
/**
* Bulk operation checked state.
*/
private isBulkChecked: boolean;
/**
* Status of selected members.
*/
private membersSelectedStatus: any;
/**
* Number of selected members.
*/
private membersSelectedNumber: number;
/**
* Members order by value.
*/
private membersOrderBy: string;
/**
* List of members to be invited.
*/
private members: Array<che.IMember>;
/**
* Parent organization ID
*/
private parentOrganizationId: string;
/**
* Members list of parent organization.
*/
private parentOrganizationMembers: string[];
/**
* ID of user which is owner of the team
*/
private ownerId: string;
/**
*
*/
private organizationRoles: che.resource.ICheOrganizationRoles;
/**
* Default constructor that is using resource
* @ngInject for Dependency injection
*/
constructor($mdDialog: angular.material.IDialogService, lodash: any, cheUser: any, resourcesService: che.service.IResourcesService) {
this.$mdDialog = $mdDialog;
this.lodash = lodash;
this.organizationRoles = resourcesService.getOrganizationRoles();
this.isNoSelected = true;
this.isBulkChecked = false;
this.membersSelectedStatus = {};
this.membersSelectedNumber = 0;
this.membersOrderBy = 'email';
// add current user to members list
const user = cheUser.getUser();
const member = user as che.IMember;
member.role = this.organizationRoles.ADMIN.name;
this.members = [member];
this.buildMembersList();
this.ownerId = user.id;
}
/**
* Forms the list of members.
*/
buildMembersList(): void {
this.members.forEach((member: che.IMember) => {
member.roles = [this.organizationRoles[member.role]];
});
}
/**
* Returns developer role value.
*
* @returns {string} string of the developer role value
*/
getDeveloperRoleValue(): string {
return this.organizationRoles.MEMBER.name;
}
/**
* Returns admin role value.
*
* @returns {string} string of the admin role value
*/
getAdminRoleValue(): string {
return this.organizationRoles.ADMIN.name;
}
/**
* Handler for member role changed in the list.
* @param {che.IMember} member
*/
onChangeMemberRole(member: che.IMember): void {
member.roles[0] = this.organizationRoles[member.role];
}
/**
* Update members selected status
*/
updateSelectedStatus(): void {
this.membersSelectedNumber = 0;
this.isBulkChecked = !!this.members.length;
this.members.forEach((member: che.IMember) => {
if (this.membersSelectedStatus[member.email]) {
this.membersSelectedNumber++;
} else {
this.isBulkChecked = false;
}
});
}
/**
* Change bulk selection value.
*/
changeBulkSelection(): void {
if (this.isBulkChecked) {
this.deselectAllMembers();
this.isBulkChecked = false;
return;
}
this.selectAllMembers();
this.isBulkChecked = true;
}
/**
* Check all members in list.
*/
selectAllMembers(): void {
this.membersSelectedNumber = this.members.length;
this.members.forEach((member: che.IMember) => {
if (member.id === this.ownerId) {
return;
}
this.membersSelectedStatus[member.email] = true;
});
}
/**
* Uncheck all members in list
*/
deselectAllMembers(): void {
this.membersSelectedStatus = {};
this.membersSelectedNumber = 0;
}
/**
* Adds member to the list.
*
* @param members {Array<che.IMember>}
* @param role {string} member role's name in organization
*/
addMembers(members: Array<che.IMember>, role: string): void {
members.forEach((member: any) => {
member.role = role;
this.members.push(member);
});
this.buildMembersList();
}
/**
* Selects which dialog should be shown.
*
* @param $event
*/
selectAddMemberDialog($event: MouseEvent) {
if (this.parentOrganizationId) {
this.showMembersListDialog($event);
} else {
this.showMemberDialog($event);
}
}
/**
* Shows dialog to add new member to a root organization.
*
* @param $event
*/
showMemberDialog($event: MouseEvent): void {
this.$mdDialog.show({
targetEvent: $event,
controller: 'OrganizationMemberDialogController',
controllerAs: 'organizationMemberDialogController',
bindToController: true,
clickOutsideToClose: true,
locals: {
members: this.members,
member: null,
parentOrganizationId: this.parentOrganizationId,
parentOrganizationMembers: this.parentOrganizationMembers,
callbackController: this
},
templateUrl: 'app/organizations/organization-details/organization-member-dialog/organization-member-dialog.html'
});
}
/**
* Shows dialog to select members from list to a sub-organization.
*
* @param $event
*/
showMembersListDialog($event: MouseEvent): void {
this.$mdDialog.show({
targetEvent: $event,
bindToController: true,
clickOutsideToClose: true,
controller: 'OrganizationSelectMembersDialogController',
controllerAs: 'organizationSelectMembersDialogController',
locals: {
callbackController: this,
parentOrganizationMembers: this.parentOrganizationMembers,
members: this.members
},
templateUrl: 'app/organizations/organization-details/organization-select-members-dialog/organization-select-members-dialog.html'
});
}
/**
* Removes selected members.
*/
removeSelectedMembers(): void {
this.lodash.remove(this.members, (member: che.IMember) => {
return this.membersSelectedStatus[member.email];
});
this.deselectAllMembers();
this.isBulkChecked = false;
}
}

View File

@ -0,0 +1,41 @@
/*
* 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';
/**
* @ngdoc directive
* @name organization.details.invite-members:ListOrganizationInviteMembers
* @restrict E
* @element
*
* @description
* `<list-organization-members members="ctrl.members"></list-organization-members>` for displaying list of members
*
* @usage
* <list-organization-members members="ctrl.members"></list-organization-members>
*
* @author Oleksii Orel
*/
export class ListOrganizationInviteMembers implements ng.IDirective {
restrict: string = 'E';
templateUrl: string = 'app/organizations/organization-details/organization-invite-members/list-organization-invite-members.html';
controller: string = 'ListOrganizationInviteMembersController';
controllerAs: string = 'listOrganizationInviteMembersController';
bindToController: boolean = true;
scope: any = {
members: '=',
parentOrganizationId: '=',
parentOrganizationMembers: '='
};
}

View File

@ -0,0 +1,93 @@
<div class="list-organization-invite-members" layout="column">
<div ng-if="listOrganizationInviteMembersController.members.length > 0"
class="list-organization-invite-members-spacing">
<che-list-header>
<div flex="100"
layout="row"
layout-align="start stretch"
class="che-list-item-row">
<div layout="row"
layout-align="start center"
class="che-checkbox-area">
<div layout="row" layout-align="start center" class="che-list-item-checkbox-main">
<md-checkbox class="che-list-item-checkbox"
aria-label="All members"
md-theme="default"
ng-checked="listOrganizationInviteMembersController.isBulkChecked"
ng-click="listOrganizationInviteMembersController.changeBulkSelection()"></md-checkbox>
</div>
</div>
<div flex hide-xs layout-gt-xs="row"
layout-align="start center"
class="che-list-item-details">
<che-list-header-column flex="30"
che-sort-value="listOrganizationInviteMembersController.membersOrderBy"
che-sort-item="name"
che-column-title='Invited Members'></che-list-header-column>
<che-list-header-column flex="35"
che-column-title='Organization Member'></che-list-header-column>
<che-list-header-column flex="35"
che-column-title='Organization Admin'></che-list-header-column>
</div>
</div>
</che-list-header>
<che-list flex>
<che-list-item ng-mouseover="hover=true" ng-mouseout="hover=false"
ng-repeat="member in listOrganizationInviteMembersController.members | orderBy:listOrganizationInviteMembersController.membersOrderBy">
<div flex="100"
layout="row"
layout-align="start stretch"
class="member-item-row">
<div layout="row"
layout-align="start center"
class="che-checkbox-area">
<che-list-item-checked
ng-if="member.id !== listOrganizationInviteMembersController.ownerId"
ng-model="listOrganizationInviteMembersController.membersSelectedStatus[member.email]"
che-aria-label-checkbox="Member {{member.email}}"
ng-click="listOrganizationInviteMembersController.updateSelectedStatus()"></che-list-item-checked>
</div>
<div flex
layout-xs="column" layout-gt-xs="row"
layout-align-gt-xs="start center"
layout-align-xs="start start"
class="che-list-item-details">
<div flex="30"
class="che-list-item-name">
<span class="material-design icon-ic_done_24px user-exists-checked" ng-if="member.id"></span>
<span class="che-hover">{{member.email}}</span>
</div>
<div flex="35">
<md-radio-group ng-model="member.role"
ng-change="listOrganizationInviteMembersController.onChangeMemberRole(member)">
<md-radio-button ng-disabled="member.id === listOrganizationInviteMembersController.ownerId"
value="{{listOrganizationInviteMembersController.getDeveloperRoleValue()}}"></md-radio-button>
</md-radio-group>
</div>
<div flex="35">
<md-radio-group ng-model="member.role"
ng-change="listOrganizationInviteMembersController.onChangeMemberRole(member)">
<md-radio-button ng-disabled="member.id === listOrganizationInviteMembersController.ownerId"
value="{{listOrganizationInviteMembersController.getAdminRoleValue()}}"></md-radio-button>
</md-radio-group>
</div>
</div>
</div>
</che-list-item>
</che-list>
</div>
<!-- buttons -->
<div layout="row">
<div flex layout-align="center start">
<che-button-default class="che-list-add-button"
che-button-title="Add" name="addButton"
ng-click="listOrganizationInviteMembersController.selectAddMemberDialog($event)"></che-button-default>
</div>
<div flex-offset="5" ng-if="listOrganizationInviteMembersController.members.length > 0">
<che-button-primary-flat ng-disabled="(listOrganizationInviteMembersController.membersSelectedNumber === 0)"
che-button-title="Remove" name="removeButton"
ng-click="listOrganizationInviteMembersController.removeSelectedMembers()"></che-button-primary-flat>
</div>
</div>
</div>

View File

@ -0,0 +1,33 @@
.list-organization-invite-members
*
outline none !important
md-radio-button
margin-bottom 0 !important
.che-list
margin-bottom 12px
background-color inherit
.che-list-item-checkbox-main
padding-left 0
.md-button
margin 0
filter none !important
.member-item-row
min-height 33px
.che-list-header-content > *, .che-list
margin 0
.che-list-header md-item
border-top none
.list-organization-invite-members .permission-switcher
overflow visible
margin 0
.list-organization-invite-members-spacing
margin-bottom 20px

View File

@ -0,0 +1,284 @@
/*
* 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';
/**
* @ngdoc controller
* @name organization.details.member:MemberDialogController
* @description This class is handling the controller for adding/editing organization member dialog.
* @author Oleksii Orel
*/
export class OrganizationMemberDialogController {
/**
* User API interaction.
*/
private cheUser: any;
/**
* Organization API interaction.
*/
private cheOrganization: che.api.ICheOrganization;
/**
* Service for displaying dialogs.
*/
private $mdDialog: angular.material.IDialogService;
/**
* Promises service.
*/
private $q: ng.IQService;
/**
* Lodash library.
*/
private lodash: any;
/**
* Processing state of adding member.
*/
private isProcessing: boolean;
/**
* Set of user roles info.
*/
private roles: Array<any>;
/**
* Already added emails.
*/
private emails: Array<string>;
/**
* Existing members.
*/
private members: Array<any>;
/**
*
*/
private parentOrganizationId: string;
/**
*
*/
private parentOrganizationMembers: string;
/**
* Entered email address.
*/
private email: string;
/**
* Controller that will handle callbacks.
*/
private callbackController: any;
/**
* Member to be displayed, may be <code>null</code> if add new member is needed. (Comes from outside)
*/
private member: che.IMember;
/**
* Role to be used, may be <code>null</code> if role is needed to be set. (Comes from outside)
*/
private role: string;
/**
* Choosen role for user.
*/
private newRole: string;
/**
* Dialog window title.
*/
private title: string;
/**
* Title of operation button (Save or Add)
*/
private buttonTitle: string;
/**
* Email validation error message.
*/
private emailError: string;
/**
* todo
*/
private organizationRoles: che.resource.ICheOrganizationRoles;
/**
* Default constructor that is using resource
* @ngInject for Dependency injection
*/
constructor($q: ng.IQService, $mdDialog: angular.material.IDialogService, cheUser: any, cheOrganization: che.api.ICheOrganization, lodash: any, resourcesService: che.service.IResourcesService) {
this.$mdDialog = $mdDialog;
this.cheUser = cheUser;
this.cheOrganization = cheOrganization;
this.$q = $q;
this.lodash = lodash;
this.organizationRoles = resourcesService.getOrganizationRoles();
this.isProcessing = false;
this.emails = [];
this.members.forEach((member: che.IMember) => {
this.emails.push(member.email);
});
// role is set, need to add only user with this role:
if (this.role) {
this.email = '';
this.title = 'Add new ' + this.organizationRoles[this.role].title.toLowerCase();
this.buttonTitle = 'Add';
return;
}
this.roles = this.organizationRoles.getRoles();
if (this.member) {
this.title = 'Edit ' + this.member.name + ' roles';
this.buttonTitle = 'Save';
this.email = this.member.email;
let roles = cheOrganization.getRolesFromActions(this.member.permissions.actions);
this.newRole = (roles && roles.length > 0) ? roles[0].name : this.organizationRoles.MEMBER.name;
} else {
this.email = '';
this.title = 'Invite member to collaborate';
this.buttonTitle = 'Add';
this.newRole = this.organizationRoles.MEMBER.name;
}
}
/**
* Returns title of specified role.
*
* @param {string} roleName
* @returns {string}
*/
getRoleTitle(roleName: string): string {
return this.organizationRoles[roleName].title;
}
/**
* Returns description of specified role.
*
* @param {string} roleName
* @returns {string}
*/
getRoleDescription(roleName: string): string {
return this.organizationRoles[roleName].description;
}
/**
* Hides the add member dialog.
*/
hide(): void {
this.$mdDialog.hide();
}
/**
* Checks whether entered email valid and is unique.
*
* @param value value with email(s) to check
* @returns {boolean} true if pointed email(s) are valid and not in the list yet
*/
isValidEmail(value: string): boolean {
let emails = value.replace(/\s*,?\s+/g, ',').split(',');
for (let i = 0; i < emails.length; i++) {
// email is valid
let email = emails[i];
let emailRe = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i;
if (!emailRe.test(email)) {
this.emailError = `"${email}" is invalid email address.`;
return false;
}
// user has not been invited yet
if (this.emails.indexOf(email) >= 0) {
this.emailError = `User with email ${email} is already invited.`;
return false;
}
// user is a member of parent organization
if (this.parentOrganizationId && this.parentOrganizationMembers.indexOf(email) === -1) {
this.emailError = 'User with this email is not a member of parent organization.';
return false;
}
}
return true;
}
/**
* Adds new member.
*/
addMembers(): void {
let userRoleName = this.role ? this.role : this.newRole;
let emails = this.email.replace(/\s*,?\s+/g, ',').split(',');
// form the list of emails without duplicates and empty values:
let resultEmails = emails.reduce((array: Array<string>, element: string) => {
if (array.indexOf(element) < 0 && element.length > 0) {
array.push(element);
}
return array;
}, []);
let promises = [];
let users = [];
resultEmails.forEach((email: string) => {
promises.push(this.processUser(email, users));
});
this.$q.all(promises).then(() => {
this.finishAdding(users, userRoleName);
});
}
processUser(email: string, users: Array<any>): ng.IPromise<any> {
let deferred = this.$q.defer();
let user = this.cheUser.getUserByAlias(email);
if (user) {
users.push(user);
deferred.resolve();
} else {
this.isProcessing = true;
this.cheUser.fetchUserByAlias(email).then(() => {
users.push(this.cheUser.getUserByAlias(email));
deferred.resolve();
}, (error: any) => {
users.push({email: email});
deferred.resolve();
});
}
return deferred.promise;
}
/**
* Handle edit member user's action.
*/
editMember(): void {
this.member.permissions.actions = this.getCurrentActions();
this.callbackController.updateMember(this.member);
this.hide();
}
/**
* Returns the actions of current chosen roles.
*/
getCurrentActions(): Array<string> {
let userRoleName = this.role ? this.role : this.newRole;
let processedActions = [];
this.roles.forEach((roleName: string) => {
const role = this.organizationRoles[roleName];
processedActions = processedActions.concat(role.actions);
});
let actions = this.member ? this.member.permissions.actions : [];
let otherActions = this.lodash.difference(actions, processedActions);
return this.lodash.uniq(this.organizationRoles[userRoleName].actions.concat(otherActions));
}
/**
* Finish adding user state.
*
* @param {Array<any>} users users to be added
* @param {sring} role user's role
*/
finishAdding(users: Array<any>, role: string): void {
this.isProcessing = false;
this.callbackController.addMembers(users, role);
this.hide();
}
}

View File

@ -0,0 +1,48 @@
<che-popup title="{{organizationMemberDialogController.title}}" on-close="organizationMemberDialogController.hide()">
<div class="organization-member-dialog-content" md-theme="default">
<ng-form flex layout="column" name="memberForm">
<che-label-container che-label-name="Email" ng-show="!organizationMemberDialogController.member"
che-label-description="User email address.">
<che-input-box che-form="memberForm"
che-name="email"
che-place-holder="Enter email"
ng-model="organizationMemberDialogController.email"
ng-model-options="{allowInvalid: true}"
ng-disabled="organizationMemberDialogController.isProcessing"
custom-validator="organizationMemberDialogController.isValidEmail($value)"
type="text"
aria-label="New member"
ng-keypress="memberForm.$valid && $event.which === 13 && organizationMemberDialogController.addMembers()"
required
focusable>
<div ng-message="customValidator" ng-if="memberForm.$dirty">{{organizationMemberDialogController.emailError}}</div>
</che-input-box>
</che-label-container>
<che-label-container che-label-name="Role" che-label-description="Allowed actions of member."
ng-if="!organizationMemberDialogController.role">
<div layout="column">
<md-radio-group ng-model="organizationMemberDialogController.newRole">
<div ng-repeat="roleName in organizationMemberDialogController.roles" layout="row">
<md-radio-button value="{{roleName}}">{{organizationMemberDialogController.getRoleTitle(roleName)}}</md-radio-button>
<span class="member-role-description">({{organizationMemberDialogController.getRoleDescription(roleName)}})</span>
</div>
</md-radio-group>
</div>
</che-label-container>
</ng-form>
<div layout="row" layout-align="end center">
<che-button-primary che-button-title="{{organizationMemberDialogController.buttonTitle}}"
ng-if="!organizationMemberDialogController.member"
ng-disabled="memberForm.$invalid || organizationMemberDialogController.isProcessing"
ng-click="organizationMemberDialogController.addMembers()"></che-button-primary>
<che-button-primary che-button-title="{{organizationMemberDialogController.buttonTitle}}"
ng-disabled="organizationMemberDialogController.isProcessing"
ng-if="organizationMemberDialogController.member"
ng-click="organizationMemberDialogController.editMember()"></che-button-primary>
<che-button-cancel-flat che-button-title="Cancel"
ng-click="organizationMemberDialogController.hide()"
tabindex="0"></che-button-cancel-flat>
</div>
</div>
</che-popup>

View File

@ -0,0 +1,33 @@
.organization-member-dialog-content
width 630px
button
margin 0 0 0 20px
input.ng-invalid.ng-pristine:focus
border-color $primary-color
.che-label-container div.che-label-container-label
width 150px
min-width 150px
.member-role-title
margin-bottom 10px
.member-role-description
color $disabled-color
font-size 10px
padding 0 5px
line-height 18pt
.member-role-warning-label
color $warning-color
height 20px
.member-role-button
margin-right 10px
width 150px
.member-role-button button
width 150px

View File

@ -0,0 +1,489 @@
/*
* 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 {OrganizationsPermissionService} from '../../organizations-permission.service';
/**
* @ngdoc controller
* @name organization.details.members:ListOrganizationMembersController
* @description This class is handling the controller for the list of organization's members.
* @author Oleksii Orel
*/
export class ListOrganizationMembersController {
/**
* Location service.
*/
private $location: ng.ILocationService;
/**
* User API interaction.
*/
private cheUser: any;
/**
* Organization API interaction.
*/
private cheOrganization: che.api.ICheOrganization;
/**
* User profile API interaction.
*/
private cheProfile: any;
/**
* Permissions API interaction.
*/
private chePermissions: che.api.IChePermissions;
/**
* Service for displaying dialogs.
*/
private $mdDialog: angular.material.IDialogService;
/**
* Notifications service.
*/
private cheNotification: any;
/**
* Confirm dialog service.
*/
private confirmDialogService: any;
/**
* Promises service.
*/
private $q: ng.IQService;
/**
* Lodash library.
*/
private lodash: any;
/**
* Organization's members list.
*/
private members: Array<che.IMember>;
/**
* Members list of parent organization (comes from directive's scope)
*/
private parentOrganizationMembers: Array<che.IUser>;
/**
* Loading state of the page.
*/
private isLoading: boolean;
/**
* Filter for members list.
*/
private memberFilter: any;
/**
* Current organization (comes from directive's scope).
*/
private organization: che.IOrganization;
/**
* Organization permission service.
*/
private organizationsPermissionService: OrganizationsPermissionService;
/**
* Has update permission.
*/
private hasUpdatePermission;
/**
* Selection and filtration helper
*/
private cheListHelper: che.widget.ICheListHelper;
/**
* todo
*/
private organizationActions: che.resource.ICheOrganizationActions;
/**
* todo
*/
private organizationRoles: che.resource.ICheOrganizationRoles;
/**
* Default constructor that is using resource
* @ngInject for Dependency injection
*/
constructor(chePermissions: che.api.IChePermissions, cheUser: any, cheProfile: any, cheOrganization: che.api.ICheOrganization,
confirmDialogService: any, $mdDialog: angular.material.IDialogService, $q: ng.IQService, cheNotification: any,
lodash: any, $location: ng.ILocationService, organizationsPermissionService: OrganizationsPermissionService,
$scope: ng.IScope, cheListHelperFactory: che.widget.ICheListHelperFactory, resourcesService: che.service.IResourcesService) {
this.chePermissions = chePermissions;
this.cheProfile = cheProfile;
this.cheUser = cheUser;
this.cheOrganization = cheOrganization;
this.$mdDialog = $mdDialog;
this.$q = $q;
this.$location = $location;
this.lodash = lodash;
this.cheNotification = cheNotification;
this.confirmDialogService = confirmDialogService;
this.organizationsPermissionService = organizationsPermissionService;
this.organizationActions = resourcesService.getOrganizationActions();
this.organizationRoles = resourcesService.getOrganizationRoles();
this.members = [];
this.memberFilter = {name: ''};
const helperId = 'list-organization-members';
this.cheListHelper = cheListHelperFactory.getHelper(helperId);
$scope.$on('$destroy', () => {
cheListHelperFactory.removeHelper(helperId);
});
this.formUsersList();
}
/**
* Callback when name is changed.
*
* @param str {string} a string to filter organization members.
*/
onSearchChanged(str: string): void {
this.memberFilter.name = str;
this.cheListHelper.applyFilter('name', this.memberFilter);
}
/**
* Fetches the list of organization members.
*/
fetchMembers(): void {
if (!this.organization || !this.organization.id) {
return;
}
this.isLoading = true;
this.chePermissions.fetchOrganizationPermissions(this.organization.id).then(() => {
this.formUsersList();
}, (error: any) => {
let errorMessage = error && error.data && error.data.message ? error.data.message : 'Failed to retrieve organization permissions.';
this.cheNotification.showError(errorMessage);
}).finally(() => {
this.isLoading = false;
});
}
/**
* Combines permissions and users data in one list.
*/
formUsersList(): void {
const permissions = this.chePermissions.getOrganizationPermissions(this.organization.id);
this.members = [];
const promises: Array<ng.IPromise<any>> = [];
permissions.forEach((permission: any) => {
let userId = permission.userId;
let userProfile = this.cheProfile.getProfileById(userId);
if (userProfile) {
this.formUserItem(userProfile, permission);
} else {
const promise = this.cheProfile.fetchProfileById(userId).then(() => {
this.formUserItem(this.cheProfile.getProfileById(userId), permission);
});
promises.push(promise);
}
});
this.$q.all(promises).finally(() => {
this.cheListHelper.setList(this.members, 'id');
});
this.hasUpdatePermission = this.organizationsPermissionService.isUserAllowedTo(this.organizationActions.UPDATE.toString(), this.organization.id);
}
/**
* Forms item to display with permissions and user data.
*
* @param userProfile {che.IProfile} user's profile
* @param permissions {che.IPermissions} data
*/
formUserItem(userProfile: che.IProfile, permissions: che.IPermissions): void {
const member = <che.IMember>angular.copy(userProfile);
member.id = userProfile.userId;
member.name = this.cheProfile.getFullName(userProfile.attributes);
member.permissions = permissions;
this.members.push(member);
}
/**
* Selects which dialog should be shown.
*/
selectAddMemberDialog() {
if (this.organization.parent) {
this.showMembersListDialog();
} else {
this.showMemberDialog(null);
}
}
/**
* Shows dialog for adding new member to the organization.
*/
showMemberDialog(member: che.IMember): void {
this.$mdDialog.show({
controller: 'OrganizationMemberDialogController',
controllerAs: 'organizationMemberDialogController',
bindToController: true,
clickOutsideToClose: true,
locals: {
members: this.members,
callbackController: this,
member: angular.copy(member),
parentOrganizationId: this.organization.parent,
parentOrganizationMembers: this.parentOrganizationMembers
},
templateUrl: 'app/organizations/organization-details/organization-member-dialog/organization-member-dialog.html'
});
}
/**
* Shows dialog to select members from list to a sub-organization.
*
*/
showMembersListDialog(): void {
this.$mdDialog.show({
bindToController: true,
clickOutsideToClose: true,
controller: 'OrganizationSelectMembersDialogController',
controllerAs: 'organizationSelectMembersDialogController',
locals: {
callbackController: this,
parentOrganizationMembers: this.parentOrganizationMembers,
members: this.members
},
templateUrl: 'app/organizations/organization-details/organization-select-members-dialog/organization-select-members-dialog.html'
});
}
/**
* Add new members to the organization.
*
* @param {Array<che.IMember>} members members to be added
* @param {string} role member role
*/
addMembers(members: Array<che.IMember>, role: string): void {
let promises = [];
let unregistered = [];
members.forEach((member: che.IMember) => {
if (member.id) {
let actions = this.organizationRoles[role].actions;
let permissions = {
instanceId: this.organization.id,
userId: member.id,
domainId: 'organization',
actions: actions
};
let promise = this.chePermissions.storePermissions(permissions);
promises.push(promise);
} else {
unregistered.push(member.email);
}
});
this.isLoading = true;
this.$q.all(promises).then(() => {
this.fetchMembers();
}).finally(() => {
this.isLoading = false;
if (unregistered.length > 0) {
this.cheNotification.showError('User' + (unregistered.length > 1 ? 's ' : ' ') + unregistered.join(', ') + (unregistered.length > 1 ? ' are' : ' is') + ' not registered in the system.');
}
});
}
/**
* Perform edit member permissions.
*
* @param member
*/
editMember(member: che.IMember): void {
this.showMemberDialog(member);
}
/**
* Performs member's permissions update.
*
* @param member member to update permissions
*/
updateMember(member: che.IMember): void {
if (member.permissions.actions.length > 0) {
this.storePermissions(member.permissions);
} else {
this.removePermissions(member);
}
}
/**
* Stores provided permissions.
*
* @param permissions {che.IPermissions}
*/
storePermissions(permissions: che.IPermissions): void {
this.isLoading = true;
this.chePermissions.storePermissions(permissions).then(() => {
this.fetchMembers();
}, (error: any) => {
this.isLoading = false;
this.cheNotification.showError(error.data && error.data.message ? error.data.message : 'Set user permissions failed.');
});
}
/**
* Remove all selected members.
*/
removeSelectedMembers(): void {
const selectedMembers = this.cheListHelper.getSelectedItems(),
selectedMemberIds = selectedMembers.map((member: che.IMember) => {
return member.id;
});
if (!selectedMemberIds.length) {
this.cheNotification.showError('No such developers.');
return;
}
const confirmationPromise = this.showDeleteMembersConfirmation(selectedMemberIds.length);
confirmationPromise.then(() => {
const removeMembersPromises = [];
let removalError;
let isCurrentUser = false;
for (let i = 0; i < selectedMemberIds.length; i++) {
const id = selectedMemberIds[i];
this.cheListHelper.itemsSelectionStatus[id] = false;
if (id === this.cheUser.getUser().id) {
isCurrentUser = true;
}
const promise = this.chePermissions.removeOrganizationPermissions(this.organization.id, id);
promise.catch((error: any) => {
removalError = error;
});
removeMembersPromises.push(promise);
}
this.$q.all(removeMembersPromises).finally(() => {
if (isCurrentUser) {
this.processCurrentUserRemoval();
} else {
this.fetchMembers();
}
if (removalError) {
this.cheNotification.showError(removalError.data && removalError.data.message ? removalError.data.message : 'User removal failed.');
}
});
});
}
/**
* Call user permissions removal. Show the dialog
* @param member
*/
removeMember(member: che.IMember): void {
let promise = this.confirmDialogService.showConfirmDialog('Remove member', 'Would you like to remove member ' + member.email + ' ?', 'Delete');
promise.then(() => {
this.removePermissions(member);
});
}
/**
* Returns true if the member is owner for current organization.
* @param member
*
* @returns {boolean}
*/
isOwner(member: che.IMember): boolean {
if (!this.organization || !member) {
return false;
}
return this.organization.qualifiedName.split('/')[0] === member.name;
}
/**
* Returns string with member roles.
* @param member
*
* @returns {string} string format of roles array
*/
getMemberRoles(member: che.IMember): string {
if (!member) {
return '';
}
if (this.isOwner(member)) {
return 'Organization Owner';
}
let roles = this.cheOrganization.getRolesFromActions(member.permissions.actions);
let titles = [];
let processedActions = [];
roles.forEach((role: any) => {
titles.push(role.title);
processedActions = processedActions.concat(role.actions);
});
return titles.join(', ');
}
/**
* Returns string with member other actions.
* @param member
*
* @returns {string} string format of roles array
*/
getOtherActions(member: che.IMember): string {
if (!member) {
return '';
}
let roles = this.cheOrganization.getRolesFromActions(member.permissions.actions);
let processedActions = [];
roles.forEach((role: any) => {
processedActions = processedActions.concat(role.actions);
});
return this.lodash.difference(member.permissions.actions, processedActions).join(', ');
}
/**
* Process the removal of current user from organization.
*/
processCurrentUserRemoval(): void {
this.$location.path('/organizations');
}
/**
* Removes user permissions for current organization
*
* @param member {che.IMember}
*/
removePermissions(member: che.IMember): void {
this.isLoading = true;
this.chePermissions.removeOrganizationPermissions(member.permissions.instanceId, member.id).then(() => {
if (member.id === this.cheUser.getUser().id) {
this.processCurrentUserRemoval();
} else {
this.fetchMembers();
}
}, (error: any) => {
this.isLoading = false;
this.cheNotification.showError(error.data && error.data.message ? error.data.message : 'Failed to remove user ' + member.email + ' permissions.');
});
}
/**
* Show confirmation popup before members removal
* @param numberToDelete {number}
* @returns {ng.IPromise<any>}
*/
showDeleteMembersConfirmation(numberToDelete: number): ng.IPromise<any> {
let confirmTitle = 'Would you like to remove ';
if (numberToDelete > 1) {
confirmTitle += 'these ' + numberToDelete + ' members?';
} else {
confirmTitle += 'the selected member?';
}
return this.confirmDialogService.showConfirmDialog('Remove members', confirmTitle, 'Delete');
}
}

View File

@ -0,0 +1,40 @@
/*
* 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';
/**
* @ngdoc directive
* @name organization.details.members:ListOrganizationMembers
* @restrict E
* @element
*
* @description
* `<list-organization-members editable="ctrl.editable" organization="ctrl.organization"></list-organization-members>` for displaying list of members
*
* @usage
* <list-organization-members editable="ctrl.editable" organization="ctrl.organization"></list-organization-members>
*
* @author Oleksii Orel
*/
export class ListOrganizationMembers implements ng.IDirective {
restrict: string = 'E';
templateUrl: string = 'app/organizations/organization-details/organization-members/list-organization-members.html';
controller: string = 'ListOrganizationMembersController';
controllerAs: string = 'listOrganizationMembersController';
bindToController: boolean = true;
scope: any = {
editable: '=',
organization: '=',
parentOrganizationMembers: '='
};
}

View File

@ -0,0 +1,118 @@
<div class="organization-progress-line">
<md-progress-linear md-mode="indeterminate"
ng-show="listOrganizationMembersController.isLoading"></md-progress-linear>
</div>
<md-content flex class="organization-member-list">
<che-list-header che-input-placeholder="Search"
che-search-model="listOrganizationMembersController.memberFilter.name"
che-on-search-change="listOrganizationMembersController.onSearchChanged(str)"
che-hide-search="listOrganizationMembersController.members.length === 0"
che-add-button-title="Add Member"
che-on-add="listOrganizationMembersController.selectAddMemberDialog(null)"
che-hide-add="!listOrganizationMembersController.hasUpdatePermission"
che-delete-button-title="Delete"
che-on-delete="listOrganizationMembersController.removeSelectedMembers()"
che-hide-delete="listOrganizationMembersController.cheListHelper.isNoItemSelected || !listOrganizationMembersController.hasUpdatePermission"
che-hide-header="listOrganizationMembersController.cheListHelper.visibleItemsNumber === 0">
<div flex="100"
layout="row"
layout-align="start stretch"
class="che-list-item-row">
<div layout="column" layout-gt-xs="row" ng-if="listOrganizationMembersController.hasUpdatePermission"
layout-align="start center"
class="che-checkbox-area">
<div layout="row" layout-align="center center" class="che-list-item-checkbox-main">
<md-checkbox class="che-list-item-checkbox"
aria-label="Member list"
ng-checked="listOrganizationMembersController.cheListHelper.areAllItemsSelected"
ng-click="listOrganizationMembersController.cheListHelper.changeBulkSelection()"></md-checkbox>
</div>
</div>
<div flex hide-xs layout-gt-xs="row"
layout-align="start center"
class="che-list-item-details">
<che-list-header-column flex-gt-xs="25"
che-sort-value='listOrganizationMembersController.memberOrderBy'
che-sort-item='name'
che-column-title='Name'></che-list-header-column>
<che-list-header-column flex-gt-xs="25"
che-sort-value='listOrganizationMembersController.memberOrderBy'
che-sort-item='email'
che-column-title='Email'></che-list-header-column>
<che-list-header-column flex-gt-xs="35"
che-sort-value='listOrganizationMembersController.memberOrderBy'
che-column-title='Roles'></che-list-header-column>
<che-list-header-column flex-gt-xs="15"
che-column-title='Actions'></che-list-header-column>
</div>
</div>
</che-list-header>
<che-list
ng-show="listOrganizationMembersController.cheListHelper.visibleItemsNumber > 0">
<che-list-item
ng-repeat="member in listOrganizationMembersController.cheListHelper.getVisibleItems() | orderBy:[listOrganizationMembersController.memberOrderBy, 'config.name']"
lex-gt-sm="100" flex="33" ng-mouseover="hover=true" ng-mouseout="hover=false">
<div flex="100"
layout="row"
layout-align="start stretch"
ng-class="{'member-bold': listOrganizationMembersController.isOwner(member)}"
class="che-list-item-row member-list-row">
<div ng-if="listOrganizationMembersController.hasUpdatePermission"
layout="row" layout-align="start center" class="che-checkbox-area">
<che-list-item-checked ng-model="listOrganizationMembersController.cheListHelper.itemsSelectionStatus[member.id]"
ng-click="listOrganizationMembersController.cheListHelper.updateBulkSelectionStatus()"
ng-show="!listOrganizationMembersController.isOwner(member)"
che-aria-label-checkbox="member {{member.id}}"></che-list-item-checked>
</div>
<div flex
layout-xs="column" layout-gt-xs="row"
layout-align-gt-xs="start center"
layout-align-xs="start start"
class="che-list-item-details">
<div flex-gt-xs="25"
class="che-list-item-name">
<span class="che-xs-header noselect" hide-gt-xs>Name</span>
<span class="member-email che-hover">{{member.name}}</span>
</div>
<div flex-gt-xs="25"
class="che-list-item-secondary">
<span class="che-xs-header noselect" hide-gt-xs>Email</span>
<span><img class="user-face" gravatar-src="member.email"></span>
<span class="member-email che-hover ">{{member.email}}</span>
</div>
<div flex-gt-xs="35" class="che-list-item-secondary">
<span class="che-xs-header noselect" hide-gt-xs>Roles</span>
<span class="member-list-permissions">{{listOrganizationMembersController.getMemberRoles(member)}} </span>
<span ng-if="listOrganizationMembersController.getOtherActions(member)"
uib-tooltip="{{listOrganizationMembersController.getOtherActions(member)}}"> Other...</span>
</div>
<div flex-gt-xs="15">
<span class="che-xs-header noselect" hide-gt-xs>Actions</span>
<span class="che-list-actions">
<a uib-tooltip="Edit developer permissions"
ng-click="listOrganizationMembersController.editMember(member)"
ng-if="!listOrganizationMembersController.isOwner(member) && listOrganizationMembersController.hasUpdatePermission"
ng-disabled="!listOrganizationMembersController.editable">
<span class="fa fa-pencil"></span>
</a>
<a uib-tooltip="Remove developer"
ng-click="listOrganizationMembersController.removeMember(member)"
ng-if="!listOrganizationMembersController.isOwner(member) && listOrganizationMembersController.hasUpdatePermission"
ng-disabled="!listOrganizationMembersController.editable">
<span class="fa fa-trash-o"></span>
</a>
</span>
</div>
</div>
</div>
</che-list-item>
</che-list>
<div class="che-list-empty">
<span
ng-show="listOrganizationMembersController.members.length > 0 && listOrganizationMembersController.cheListHelper.visibleItemsNumber === 0">
No members found.
</span>
<span ng-show="listOrganizationMembersController.members.length === 0">There are no members.</span>
</div>
</md-content>

View File

@ -0,0 +1,5 @@
.organization-member-list *.che-list-item
display block
.user-face
che-developers-face()

View File

@ -0,0 +1,36 @@
/*
* 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';
/**
* @ngdoc directive
* @name organization.details:OrganizationNotFound
* @restrict E
* @element
*
* @description
* `<organization-not-found organization-name="myOrganization"></organization-not-found>` for displaying "Organization not found" page.
*
* @usage
* <organization-not-found organization-name="myOrganization"></organization-not-found>
*
* @author Oleksii Kurinnyi
*/
export class OrganizationNotFound implements ng.IDirective {
restrict: string = 'E';
replace: boolean = true;
templateUrl: string = 'app/organizations/organization-details/organization-not-found/organization-not-found.html';
scope: any = {
organizationName: '='
};
}

View File

@ -0,0 +1,8 @@
<md-content flex class="organization-error-content" layout="column"
layout-align="center center">
<div class="error-title">Organization <b>{{organizationName}}</b> not found.</div>
<span class="fa fa-group error-image"></span>
<div class="error-description">
The organization could be deleted by its owner or you do not have permissions to access the organization.
</div>
</md-content>

View File

@ -0,0 +1,17 @@
.organization-error-content
height 100%
width 100%
color $label-secondary-color
.error-title
font-size 12pt
font-weight bold
.error-image
font-size 56pt
margin 25px 0
color $label-info-color
.error-description
font-size 11pt
padding 0 20px

View File

@ -0,0 +1,30 @@
/*
* 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';
/**
* Defines a directive for member in list.
*
* @author Oleksii Kurinnyi
*/
export class OrganizationMemberItem {
restrict: string = 'E';
templateUrl: string = 'app/organizations/organization-details/organization-select-members-dialog/organization-member-item/organization-member-item.html';
replace: boolean = false;
scope: {[prop: string]: string} = {
member: '=',
isSelected: '=',
onChange: '&'
};
}

View File

@ -0,0 +1,31 @@
<che-list-item flex-gt-sm="100" flex="33"
ng-mouseover="hover=true" ng-mouseout="hover=false"
class="organization-member-item">
<div flex="100"
layout="row"
layout-align="start stretch"
class="che-list-item-row member-list-row">
<div layout="row"
layout-align="start center"
class="che-checkbox-area">
<che-list-item-checked ng-model="isSelected"
ng-click="onChange({'memberId': member.id, 'isSelected': isSelected})"></che-list-item-checked>
</div>
<div flex
layout-xs="column" layout-gt-xs="row"
layout-align-gt-xs="start center"
layout-align-xs="start start"
class="che-list-item-details">
<div flex-gt-xs="50"
class="che-list-item-name">
<span class="che-xs-header noselect" hide-gt-xs>Name</span>
<span class="member-email che-hover ">{{member.fullName}}</span>
</div>
<div flex-gt-xs="50"
class="che-list-item-name">
<span class="che-xs-header noselect" hide-gt-xs>Email</span>
<span class="member-email che-hover ">{{member.email}}</span>
</div>
</div>
</div>
</che-list-item>

View File

@ -0,0 +1,12 @@
.organization-member-item .member-list-row
margin 0
& > div
outline none
.member-bold
font-weight bold
.member-email
max-width 100%

View File

@ -0,0 +1,259 @@
/*
* 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 {ListOrganizationInviteMembersController} from '../organization-invite-members/list-organization-invite-members.controller';
interface IOrganizationMember extends che.IMember {
fullName?: string;
}
/**
* This class is handling the controller for the add organization's members dialog.
*
* @author Oleksii Kurinnyi
*/
export class OrganizationSelectMembersDialogController {
/**
* User profile API interaction.
*/
private cheProfile: any;
/**
* User API interaction.
*/
private cheUser: any;
/**
* Service for displaying dialogs.
*/
private $mdDialog: angular.material.IDialogService;
/**
* Promises service.
*/
private $q: ng.IQService;
/**
* Lodash library.
*/
private lodash: any;
/**
* Callback handler (is set from outside).
*/
private callbackController: ListOrganizationInviteMembersController;
/**
* The list of users, that already are members of this organization (is set from outside).
*/
private members: Array<che.IMember>;
/**
* The list of users, that are members of parent organization (is set from outside).
*/
private parentOrganizationMembers: Array<che.IUser>;
/**
* The list of users, that are available to be added
*/
private availableUsers: Array<IOrganizationMember>;
/**
* The list of users, that are going to be added
*/
private usersToAdd: Array<IOrganizationMember>;
/**
* Current user.
*/
private user: che.IUser;
/**
* Selected status of members in list.
*/
private userSelectedStatus: any;
/**
* Bulk operation state.
*/
private isBulkChecked: boolean;
/**
* No selected members state.
*/
private isNoSelected: boolean;
/**
* All selected members state.
*/
private isAllSelected: boolean;
/**
* True when loading resources.
*/
private isLoading: boolean;
/**
*
*/
private organizationRoles: che.resource.ICheOrganizationRoles;
/**
* Default constructor.
* @ngInject for Dependency injection
*/
constructor($q: ng.IQService, $mdDialog: angular.material.IDialogService, lodash: any, cheProfile: any, cheUser: any, resourcesService: che.service.IResourcesService) {
this.$q = $q;
this.$mdDialog = $mdDialog;
this.lodash = lodash;
this.cheProfile = cheProfile;
this.cheUser = cheUser;
this.organizationRoles = resourcesService.getOrganizationRoles();
this.isLoading = false;
this.userSelectedStatus = {};
this.isBulkChecked = false;
this.isNoSelected = true;
this.isAllSelected = false;
this.user = this.cheUser.getUser();
this.formUsersAvailableList();
}
/**
* Builds list of users that are available to be added.
*/
formUsersAvailableList(): void {
const existingMembers = this.members.reduce((map: {[id: string]: che.IMember}, member: che.IMember) => {
map[member.id] = member;
return map;
}, {});
this.availableUsers = this.parentOrganizationMembers.filter((parentOrganizationMember: che.IUser) => {
return !existingMembers[parentOrganizationMember.id] && parentOrganizationMember.id !== this.user.id;
});
if (!this.availableUsers.length) {
return ;
}
const userProfilesPromises = [];
this.isLoading = true;
this.availableUsers.forEach((user: IOrganizationMember) => {
const profile = this.cheProfile.getProfileById(user.id);
if (profile) {
user.fullName = this.cheProfile.getFullName(profile.attributes);
} else {
const promise = this.cheProfile.fetchProfileById(user.id).then(() => {
const profile = this.cheProfile.getProfileById(user.id);
user.fullName = this.cheProfile.getFullName(profile.attributes);
});
userProfilesPromises.push(promise);
}
});
this.$q.all(userProfilesPromises).finally(() => {
this.isLoading = false;
});
}
/**
* Callback of the cancel button of the dialog.
*/
hide() {
this.$mdDialog.hide();
}
/**
* Callback of the "Add" button of the dialog.
*/
addMembers() {
const checkedUsers = this.availableUsers.reduce((usersToAdd: Array<che.IMember>, member: IOrganizationMember) => {
if (this.userSelectedStatus[member.id]) {
usersToAdd.push(member);
}
return usersToAdd;
}, []);
this.callbackController.addMembers(checkedUsers, this.organizationRoles.MEMBER.name);
this.$mdDialog.hide();
}
/**
* Return <code>true</code> if all users in list are checked.
* @returns {boolean}
*/
isAllUsersSelected(): boolean {
return this.isAllSelected;
}
/**
* Returns <code>true</code> if all users in list are not checked.
* @returns {boolean}
*/
isNoUsersSelected(): boolean {
return this.isNoSelected;
}
/**
* Make all users in list selected.
*/
selectAllUsers(): void {
this.availableUsers.forEach((user: IOrganizationMember) => {
this.userSelectedStatus[user.id] = true;
});
}
/**
* Make all users in list deselected.
*/
deselectAllUsers(): void {
this.availableUsers.forEach((user: IOrganizationMember) => {
this.userSelectedStatus[user.id] = false;
});
}
/**
* Change bulk selection value.
*/
changeBulkSelection(): void {
if (this.isBulkChecked) {
this.deselectAllUsers();
this.isBulkChecked = false;
} else {
this.selectAllUsers();
this.isBulkChecked = true;
}
this.updateSelectedStatus();
}
/**
* Set selected status for user.
*
* @param {string} id user ID
* @param {boolean} isSelected
*/
setSelectedStatus(id: string, isSelected: boolean) {
this.userSelectedStatus[id] = isSelected;
this.updateSelectedStatus();
}
/**
* Update members selected status.
*/
updateSelectedStatus(): void {
this.isNoSelected = true;
this.isAllSelected = true;
Object.keys(this.userSelectedStatus).forEach((key: string) => {
if (this.userSelectedStatus[key]) {
this.isNoSelected = false;
} else {
this.isAllSelected = false;
}
});
if (this.isNoSelected) {
this.isBulkChecked = false;
return;
}
this.isBulkChecked = (this.isAllSelected && Object.keys(this.userSelectedStatus).length === this.availableUsers.length);
}
}

View File

@ -0,0 +1,57 @@
<che-popup title="{{organizationSelectMembersDialogController.availableUsers.length > 0 ? 'Select users' : 'No users to add'}}"
on-close="organizationSelectMembersDialogController.hide()"
ng-if="!organizationSelectMembersDialogController.isLoading">
<div layout="column" flex md-theme="default" class="add-organization-members">
<che-list-header che-hide-header="!organizationSelectMembersDialogController.availableUsers || organizationSelectMembersDialogController.availableUsers.length === 0">
<div flex="100"
layout="row"
layout-align="start stretch"
class="che-list-item-row">
<div layout="column" layout-gt-xs="row"
layout-align="start center"
class="che-checkbox-area">
<div layout="row" layout-align="center center" class="che-list-item-checkbox-main">
<md-checkbox class="che-list-item-checkbox"
aria-label="Workspace list"
ng-checked="organizationSelectMembersDialogController.isBulkChecked"
ng-click="organizationSelectMembersDialogController.changeBulkSelection()"></md-checkbox>
</div>
</div>
<div flex hide-xs layout-gt-xs="row"
layout-align="start center"
class="che-list-item-details">
<che-list-header-column flex-gt-xs="50"
che-sort-value='organizationSelectMembersDialogController.memberOrderBy'
che-sort-item='name'
che-column-title='Name'></che-list-header-column>
<che-list-header-column flex-gt-xs="50"
che-sort-value='organizationSelectMembersDialogController.memberOrderBy'
che-sort-item='email'
che-column-title='Email'></che-list-header-column>
</div>
</div>
</che-list-header>
<che-list ng-show="organizationSelectMembersDialogController.availableUsers.length > 0" class="members-list">
<organization-member-item
ng-repeat="member in organizationSelectMembersDialogController.availableUsers"
is-selected="organizationSelectMembersDialogController.userSelectedStatus[member.id]"
on-change="organizationSelectMembersDialogController.setSelectedStatus(memberId, isSelected)"
member="member"></organization-member-item>
</che-list>
<div class="message-panel" ng-if="!organizationSelectMembersDialogController.availableUsers || organizationSelectMembersDialogController.availableUsers.length === 0">
There are no members in parent organization to invite.
</div>
<div layout="row" layout-align="end center" class="buttons-panel">
<che-button-primary
ng-disabled="organizationSelectMembersDialogController.isNoSelected || organizationSelectMembersDialogController.isLoading"
che-button-title="Add" name="addButton"
ng-click="organizationSelectMembersDialogController.addMembers()"></che-button-primary>
<che-button-notice che-button-title="Close"
ng-click="organizationSelectMembersDialogController.hide()"></che-button-notice>
</div>
</div>
</che-popup>

View File

@ -0,0 +1,43 @@
.add-organization-members
margin-top 5px
width 520px
.form-input-fields
margin 5px 0 5px
min-height 55px
.members-list
min-height 100px
max-height 300px
overflow-y auto
.buttons-panel
margin-top 20px
.message-panel
padding-top 20px
.che-list
margin 0
.che-list-header-content *
margin 0
.che-list-header md-item
border-top none
.che-list-header-additional
display none
.add-organization-members che-button-primary,
.add-organization-members che-button-notice
margin-left 5px
.user-exists-checked
color $che-green-color
float left
margin-right 3px
.user-not-found
color $error-color

View File

@ -0,0 +1,199 @@
/*
* 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 {CheAPIBuilder} from '../../components/api/builder/che-api-builder.factory';
import {CheHttpBackend} from '../../components/api/test/che-http-backend';
type User = {
id: string,
email: string,
firstName: string,
lastName: string
};
/**
* This class creates mock data and sets up backend.
*
* @author Oleksii Kurinnyi
*/
export class OrganizationsConfigServiceMock {
private cheAPIBuilder;
private cheHttpBackend;
private users: any[] = [];
private orgs: any[] = [];
private permissions: any[] = [];
private usersByOrgs: Map<string, any[]> = new Map();
/**
* Default constructor
* @ngInject for Dependency injection
*/
constructor(cheAPIBuilder: CheAPIBuilder, cheHttpBackend: CheHttpBackend) {
this.cheAPIBuilder = cheAPIBuilder;
this.cheHttpBackend = cheHttpBackend;
this.cheAPIBuilder = cheAPIBuilder;
this.cheHttpBackend = cheHttpBackend;
}
mockData(): void {
// add default user
const user1 = this.addAndGetUser(1, true);
// add users
const user2 = this.addAndGetUser(2);
const user3 = this.addAndGetUser(3);
// add default profile
this.addProfile(1, true);
// add profiles
this.addProfile(2);
this.addProfile(3);
// add root organization
const org1 = this.addAndGetOrganization(1);
// add children organizations
const org2 = this.addAndGetOrganization(2, org1);
const org3 = this.addAndGetOrganization(3, org1);
// for root organization
// add permissions
[user1, user2, user3].forEach((user: any) => {
this.addPermission(org1, user);
});
// add resources
const totalResources = [
{
'type': 'workspace',
'amount': 30,
'unit': 'item'
},
{
'type': 'runtime',
'amount': 10,
'unit': 'item'
},
{
'type': 'timeout',
'amount': 240,
'unit': 'minute'
},
{
'type': 'RAM',
'amount': 102400,
'unit': 'mb'
}
];
totalResources.forEach((resource: any) => {
this.addResource(org1, 'total', resource);
});
// for sub-organization
// add permissions
[user1, user2].forEach((user: any) => {
this.addPermission(org2, user);
});
// add total resources
totalResources.forEach((resource: any) => {
this.addResource(org2, 'total', resource);
});
// add distributed resources
totalResources.forEach((resource: any) => {
this.addResource(org2, 'distributed', resource);
});
// build all backends at once
this.cheHttpBackend.setup();
this.cheHttpBackend.usersBackendSetup();
this.cheHttpBackend.organizationsBackendSetup();
this.cheHttpBackend.permissionsBackendSetup();
this.cheHttpBackend.resourcesBackendSetup();
}
getUsers(): any[] {
return this.users;
}
getUsersByOrganizationId(id: string): any[] {
return this.usersByOrgs.get(id) || [];
}
getOrganizations(): any[] {
return this.orgs;
}
private buildUser(suffix: number|string): User {
return {
id: `testUser_${suffix}`,
email: `testUser_${suffix}@email.org`,
firstName: `FirstName_${suffix}`,
lastName: `LastName_${suffix}`
};
}
private addProfile(suffix: number|string, isDefault?: boolean): void {
const user = this.buildUser(suffix);
const profile = this.cheAPIBuilder.getProfileBuilder().withId(user.id).withEmail(user.email).withFirstName(user.firstName).withLastName(user.lastName).build();
if (isDefault) {
this.cheHttpBackend.addDefaultProfile(profile);
}
this.cheHttpBackend.addProfileId(profile);
}
private addAndGetUser(suffix: number|string, isDefault?: boolean): any {
const user = this.buildUser(suffix);
const testUser = this.cheAPIBuilder.getUserBuilder().withId(user.id).withEmail(user.email).build();
if (isDefault) {
this.cheHttpBackend.setDefaultUser(testUser);
}
this.cheHttpBackend.addUserById(testUser);
this.users.push(testUser);
return testUser;
}
private addAndGetOrganization(suffix: number|string, parent?: any): any {
const id = `testOrgId_${suffix}`;
const name = `testOrgName_${suffix}`;
const qualifiedName = (parent ? parent.qualifiedName + '/' : '') + name;
const testOrganization = parent
? this.cheAPIBuilder.getOrganizationsBuilder().withId(id).withName(name).withQualifiedName(qualifiedName).withParentId(parent.id).build()
: this.cheAPIBuilder.getOrganizationsBuilder().withId(id).withName(name).withQualifiedName(qualifiedName).build();
this.cheHttpBackend.addOrganizationById(testOrganization);
this.orgs.push(testOrganization);
this.usersByOrgs.set(id, []);
return testOrganization;
}
private addPermission(organization: any, user: any): void {
const domainId = 'organization';
const testPermission = this.cheAPIBuilder.getPermissionsBuilder().withDomainId(domainId).withInstanceId(organization.id).withUserId(user.id).build();
this.cheHttpBackend.addPermissions(testPermission);
this.usersByOrgs.get(organization.id).push(user);
this.permissions.push(testPermission);
}
private addResource(organization: any, scope: string, resource: any): void {
const testResource = this.cheAPIBuilder.getResourceBuilder().withAmount(resource.amount).withType(resource.type).withUnit(resource.unit).build();
this.cheHttpBackend.addResource(organization.id, scope, testResource);
}
}

View File

@ -0,0 +1,401 @@
/*
* 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 {CheAPIBuilder} from '../../components/api/builder/che-api-builder.factory';
import {CheHttpBackend} from '../../components/api/test/che-http-backend';
import {OrganizationsConfigServiceMock} from './organizations-config.service.mock';
/* tslint:disable:no-empty */
describe('OrganizationsConfig >', () => {
let $route;
let $injector: ng.auto.IInjectorService;
let $rootScope;
let $location;
let $httpBackend;
let mock;
/**
* Setup module
*/
beforeEach(() => {
angular.mock.module('userDashboard');
});
/**
* Inject service and http backend
*/
beforeEach(inject((
_$injector_: ng.auto.IInjectorService,
_$location_: ng.ILocationService,
_$route_: ng.route.IRouteService,
_$rootScope_: ng.IRootScopeService,
_cheHttpBackend_: CheHttpBackend,
_cheAPIBuilder_: CheAPIBuilder
) => {
$injector = _$injector_;
$location = _$location_;
$route = _$route_;
$rootScope = _$rootScope_;
$httpBackend = _cheHttpBackend_.getHttpBackend();
mock = new OrganizationsConfigServiceMock(_cheAPIBuilder_, _cheHttpBackend_);
mock.mockData();
}));
/**
* Check assertion after the test
*/
afterEach(() => {
$httpBackend.verifyNoOutstandingRequest();
$httpBackend.verifyNoOutstandingExpectation();
});
describe('create root organization "/admin/create-organization" >', () => {
it('should resolve route and return data', () => {
const route = $route.routes['/admin/create-organization'];
const resolveBlock = route.resolve.initData;
// stub functions
const callbacks = {
testResolve: () => { },
testReject: () => { }
};
// create spies
spyOn(callbacks, 'testResolve');
spyOn(callbacks, 'testReject');
$location.path('/admin/create-organization');
$rootScope.$digest();
const service = $injector.invoke(resolveBlock);
service
.then(callbacks.testResolve)
.catch(callbacks.testReject);
$httpBackend.flush();
expect(callbacks.testResolve).toHaveBeenCalledWith({
parentQualifiedName: '',
parentOrganizationId: '',
parentOrganizationMembers: []
});
expect(callbacks.testReject).not.toHaveBeenCalled();
});
});
describe('create sub-organization "/admin/create-organization/{parentId}" >', () => {
it('should resolve route and return data', () => {
const organizations = mock.getOrganizations();
const parentOrg = organizations[0];
const users = mock.getUsersByOrganizationId(parentOrg.id);
const buildIdsList = (res: string[], user: che.IUser) => {
res.push(user.id);
return res;
};
const route = $route.routes['/admin/create-organization/:parentQualifiedName*'];
const resolveBlock = route.resolve.initData;
// stub functions
const callbacks = {
testResolve: (data: any) => {
expect(data.parentQualifiedName).toEqual(parentOrg.qualifiedName);
expect(data.parentOrganizationId).toEqual(parentOrg.id);
expect(data.parentOrganizationMembers.length).toEqual(users.length);
const parentMemberIds = data.parentOrganizationMembers.reduce(buildIdsList, []).sort();
const userIds = users.reduce(buildIdsList, []).sort();
expect(parentMemberIds).toEqual(userIds);
},
testReject: () => { }
};
// create spies
spyOn(callbacks, 'testResolve').and.callThrough();
spyOn(callbacks, 'testReject');
$location.path(`/admin/create-organization/${parentOrg.qualifiedName}`);
$rootScope.$digest();
const service = $injector.invoke(resolveBlock);
service
.then(callbacks.testResolve)
.catch(callbacks.testReject);
$httpBackend.flush();
expect(callbacks.testResolve).toHaveBeenCalled();
expect(callbacks.testReject).not.toHaveBeenCalled();
});
it('should resolve route and return data if organizations request fails', () => {
const organizations = mock.getOrganizations();
const parentOrg = organizations[0];
const route = $route.routes['/admin/create-organization/:parentQualifiedName*'];
const resolveBlock = route.resolve.initData;
// stub functions
const callbacks = {
testResolve: () => { },
testReject: () => { }
};
// create spies
spyOn(callbacks, 'testResolve');
spyOn(callbacks, 'testReject');
$location.path(`/admin/create-organization/${parentOrg.qualifiedName}`);
$rootScope.$digest();
// make response for organizations list fail
$httpBackend.expect('GET', /\/api\/organization(\?.*$)?/).respond(500, [], {message: 'response failed'});
const service = $injector.invoke(resolveBlock);
service
.then(callbacks.testResolve)
.catch(callbacks.testReject);
$httpBackend.flush();
expect(callbacks.testResolve).toHaveBeenCalledWith({
parentQualifiedName: parentOrg.qualifiedName,
parentOrganizationId: '',
parentOrganizationMembers: []
});
expect(callbacks.testReject).not.toHaveBeenCalled();
});
it('should resolve route and return data if parent organization is not found', () => {
const fakeQualifiedName = 'fake/qualified/name';
const route = $route.routes['/admin/create-organization/:parentQualifiedName*'];
const resolveBlock = route.resolve.initData;
// stub functions
const callbacks = {
testResolve: () => { },
testReject: () => { }
};
// create spies
spyOn(callbacks, 'testResolve');
spyOn(callbacks, 'testReject');
$location.path(`/admin/create-organization/${fakeQualifiedName}`);
$rootScope.$digest();
const service = $injector.invoke(resolveBlock);
service
.then(callbacks.testResolve)
.catch(callbacks.testReject);
$httpBackend.flush();
expect(callbacks.testResolve).toHaveBeenCalledWith({
parentQualifiedName: fakeQualifiedName,
parentOrganizationId: '',
parentOrganizationMembers: []
});
expect(callbacks.testReject).not.toHaveBeenCalled();
});
});
describe('get root organization details "/organization/{rootOrganization}" >', () => {
it('should resolve route and return data', () => {
const organizations = mock.getOrganizations();
const parentOrg = organizations[0];
const route = $route.routes['/organization/:organizationName*'];
const resolveBlock = route.resolve.initData;
// stub functions
const callbacks = {
testResolve: (initData: any) => {
// this is necessary because of different types
const equal = angular.equals(initData.organization, parentOrg);
expect(equal).toBeTruthy();
expect(initData.parentOrganizationMembers).toEqual([]);
},
testReject: () => { }
};
// create spies
spyOn(callbacks, 'testResolve').and.callThrough();
spyOn(callbacks, 'testReject');
$location.path(`/organization/${parentOrg.qualifiedName}`);
$rootScope.$digest();
const service = $injector.invoke(resolveBlock);
service
.then(callbacks.testResolve)
.catch(callbacks.testReject);
$httpBackend.flush();
expect(callbacks.testResolve).toHaveBeenCalled();
expect(callbacks.testReject).not.toHaveBeenCalled();
});
it('should resolve route and return data if request for organizations list fails', () => {
const organizations = mock.getOrganizations();
const parentOrg = organizations[0];
const route = $route.routes['/organization/:organizationName*'];
const resolveBlock = route.resolve.initData;
// stub functions
const callbacks = {
testResolve: (initData: any) => {
expect(initData.organization).toBeFalsy();
expect(initData.parentOrganizationMembers).toEqual([]);
},
testReject: () => { }
};
// create spies
spyOn(callbacks, 'testResolve').and.callThrough();
spyOn(callbacks, 'testReject');
$location.path(`/organization/${parentOrg.qualifiedName}`);
$rootScope.$digest();
// make response for organizations list fail
$httpBackend.expect('GET', /\/api\/organization(\?.*$)?/).respond(500, [], {message: 'response failed'});
const service = $injector.invoke(resolveBlock);
service
.then(callbacks.testResolve)
.catch(callbacks.testReject);
$httpBackend.flush();
expect(callbacks.testResolve).toHaveBeenCalled();
expect(callbacks.testReject).not.toHaveBeenCalled();
});
});
describe('get sub-organization details "/organization/{rootOrganization}/{sub-organization}" >', () => {
it('should resolve route and return data', () => {
const organizations = mock.getOrganizations();
// get sub-organization
const subOrg = organizations[1];
// get parent organization users
const users = mock.getUsersByOrganizationId(subOrg.parent);
const buildIdsList = (res: string[], user: che.IUser) => {
res.push(user.id);
return res;
};
const route = $route.routes['/organization/:organizationName*'];
const resolveBlock = route.resolve.initData;
// stub functions
const callbacks = {
testResolve: (initData: any) => {
// this is necessary because of different types
const organizationsAreEqual = angular.equals(initData.organization, subOrg);
expect(organizationsAreEqual).toBeTruthy();
expect(initData.parentOrganizationMembers.length).toEqual(users.length);
const parentMemberIds = initData.parentOrganizationMembers.reduce(buildIdsList, []).sort();
const userIds = users.reduce(buildIdsList, []).sort();
expect(parentMemberIds).toEqual(userIds);
},
testReject: () => { }
};
// create spies
spyOn(callbacks, 'testResolve').and.callThrough();
spyOn(callbacks, 'testReject');
$location.path(`/organization/${subOrg.qualifiedName}`);
$rootScope.$digest();
const service = $injector.invoke(resolveBlock);
service
.then(callbacks.testResolve)
.catch(callbacks.testReject);
$httpBackend.flush();
expect(callbacks.testResolve).toHaveBeenCalled();
expect(callbacks.testReject).not.toHaveBeenCalled();
});
it('should resolve route and return data if request for organizations list fails', () => {
const organizations = mock.getOrganizations();
const subOrg = organizations[1];
const route = $route.routes['/organization/:organizationName*'];
const resolveBlock = route.resolve.initData;
// stub functions
const callbacks = {
testResolve: (initData: any) => {
expect(initData.organization).toBeFalsy();
expect(initData.parentOrganizationMembers).toEqual([]);
},
testReject: () => { }
};
// create spies
spyOn(callbacks, 'testResolve').and.callThrough();
spyOn(callbacks, 'testReject');
$location.path(`/organization/${subOrg.qualifiedName}`);
$rootScope.$digest();
// make response for organizations list fail
$httpBackend.expect('GET', /\/api\/organization(\?.*$)?/).respond(500, [], {message: 'response failed'});
const service = $injector.invoke(resolveBlock);
service
.then(callbacks.testResolve)
.catch(callbacks.testReject);
$httpBackend.flush();
expect(callbacks.testResolve).toHaveBeenCalled();
expect(callbacks.testReject).not.toHaveBeenCalled();
});
});
});

View File

@ -0,0 +1,359 @@
/*
* 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';
export class OrganizationsConfigService {
/**
* Log service.
*/
private $log: ng.ILogService;
/**
* Promises service.
*/
private $q: ng.IQService;
/**
* Route service.
*/
private $route: ng.route.IRouteService;
/**
* Organization API interaction.
*/
private cheOrganization: che.api.ICheOrganization;
/**
* Permissions API interaction.
*/
private chePermissions: che.api.IChePermissions;
/**
* Organization resources API interaction.
*/
private cheResourcesDistribution: che.api.ICheResourcesDistribution;
/**
* User profile API interaction.
*/
private cheUser: any;
/** Default constructor that is using resource injection
* @ngInject for Dependency injection
*/
constructor($log: ng.ILogService,
$q: ng.IQService,
$route: ng.route.IRouteService,
cheOrganization: che.api.ICheOrganization,
chePermissions: che.api.IChePermissions,
cheResourcesDistribution: che.api.ICheResourcesDistribution,
cheUser: any) {
this.$log = $log;
this.$q = $q;
this.$route = $route;
this.cheOrganization = cheOrganization;
this.chePermissions = chePermissions;
this.cheResourcesDistribution = cheResourcesDistribution;
this.cheUser = cheUser;
}
/**
* Prints error message.
*
* @param {any} error error object or string
*/
logError(error: any): void {
if (!error) {
return;
}
const message = error.data && error.data.message ? error.data.message : error;
this.$log.error(message);
}
waitAll(promises: Array<ng.IPromise<any>>): ng.IPromise<any> {
return this.$q.all(promises).then((results: any) => {
return results;
}, (error: any) => {
this.logError(error);
});
}
/**
* Fetches all organizations.
*
* @return {IPromise<any>}
*/
fetchOrganizations(): ng.IPromise<any> {
const defer = this.$q.defer();
// we should resolve this promise in any case to show 'not found page' in case with error
this.cheOrganization.fetchOrganizations().finally(() => {
const organizations = this.cheOrganization.getOrganizations();
defer.resolve(organizations);
});
return defer.promise;
}
/**
* Fetches organization by its name.
*
* @param {string} name organization name
* @return {IPromise<any>}
*/
getOrFetchOrganizationByName(name: string): ng.IPromise<any> {
const defer = this.$q.defer();
const organization = this.cheOrganization.getOrganizationByName(name);
if (organization) {
defer.resolve(organization);
} else {
this.cheOrganization.fetchOrganizationByName(name).then(() => {
const organization = this.cheOrganization.getOrganizationByName(name);
if (!organization) {
this.logError(`Organization "${name}" is not found.`);
}
defer.resolve(organization);
}, (error: any) => {
this.logError(error);
defer.resolve(null);
});
}
return defer.promise;
}
/**
* Fetches permissions of organization.
*
* @param {string} id organization ID
* @return {IPromise<any>}
*/
getOrFetchOrganizationPermissions(id: string): ng.IPromise<any> {
const defer = this.$q.defer();
const permissions = this.chePermissions.getOrganizationPermissions(id);
if (permissions) {
defer.resolve(permissions);
} else {
this.chePermissions.fetchOrganizationPermissions(id).then(() => {
const permissions = this.chePermissions.getOrganizationPermissions(id);
defer.resolve(permissions);
}, (error: any) => {
this.logError(error);
defer.resolve(null);
});
}
return defer.promise;
}
/**
* Fetches resources of organization.
*
* @param {string} id organization ID
* @return {IPromise<any>}
*/
getOrFetchOrganizationResources(id: string): ng.IPromise<any> {
const defer = this.$q.defer();
const resources = this.cheResourcesDistribution.getOrganizationResources(id);
if (resources) {
defer.resolve(resources);
} else {
this.cheResourcesDistribution.fetchOrganizationResources(id).then(() => {
const resources = this.cheResourcesDistribution.getOrganizationResources(id);
defer.resolve(resources);
}, (error: any) => {
this.logError(error);
defer.resolve(null);
});
}
return defer.promise;
}
/**
* Fetches resources of root organization.
*
* @param {string} id organization ID
* @return {IPromise<any>}
*/
getOrFetchTotalOrganizationResources(id: string): ng.IPromise<any> {
const defer = this.$q.defer();
const resources = this.cheResourcesDistribution.getTotalOrganizationResources(id);
if (resources) {
defer.resolve(resources);
} else {
this.cheResourcesDistribution.fetchTotalOrganizationResources(id).then(() => {
const resources = this.cheResourcesDistribution.getTotalOrganizationResources(id);
defer.resolve(resources);
}, (error: any) => {
this.logError(error);
defer.resolve();
});
}
return defer.promise;
}
/**
* Fetches users of organization.
* todo: get Profiles instead of Users
*
* @param {Array<che.IPermissions>} permissions
* @return {IPromise<any>}
*/
getOrFetchOrganizationUsers(permissions: Array<che.IPermissions>): ng.IPromise<any> {
const userPromises = [];
if (permissions && permissions.length) {
permissions.forEach((permission: any) => {
const userId = permission.userId;
const user = this.cheUser.getUserFromId(userId);
if (user) {
userPromises.push(this.$q.when(user));
} else {
const userPromise = this.cheUser.fetchUserId(userId).then(() => {
return this.cheUser.getUserFromId(userId);
});
userPromises.push(userPromise);
}
});
}
return this.$q.all(userPromises);
}
/**
* Returns promise to resolve route for organization details page.
*
* @returns {ng.IPromise<any>}
*/
resolveOrganizationDetailsRoute(): ng.IPromise<any> {
const name = this.$route.current.params.organizationName;
const organizationPromise = this.getOrFetchOrganizationByName(name);
// get current organization permissions
const permissionsPromise = organizationPromise.then((organization: che.IOrganization) => {
if (organization && organization.id) {
return this.getOrFetchOrganizationPermissions(organization.id);
}
return this.$q.when();
});
// get current organization resources
const resourcesPromise = organizationPromise.then((organization: che.IOrganization) => {
if (!organization) {
return this.$q.when();
}
if (organization.parent) {
return this.getOrFetchOrganizationResources(organization.id);
} else {
return this.getOrFetchTotalOrganizationResources(organization.id);
}
});
// fetch parent organization members
const parentMembersDefer = this.$q.defer();
const parentOrgPermissionsPromise = organizationPromise.then((organization: che.IOrganization) => {
if (organization && organization.parent) {
return this.getOrFetchOrganizationPermissions(organization.parent);
}
return this.$q.reject();
});
parentOrgPermissionsPromise.then(
/* fetch parent organization members */
(permissions: Array<che.IPermissions>) => {
return this.getOrFetchOrganizationUsers(permissions);
}, (error: any) => {
this.logError(error);
parentMembersDefer.resolve([]);
return this.$q.reject();
}
).then(
/* resolve parent organization members */
(userResults: any[]) => {
parentMembersDefer.resolve(userResults);
}, (error: any) => {
this.logError(error);
parentMembersDefer.resolve([]);
}
);
return this.$q.all({
organization: organizationPromise,
organizationPermissions: permissionsPromise,
organizationResources: resourcesPromise,
parentOrganizationMembers: parentMembersDefer.promise
}).then((results: any) => {
return results;
});
}
/**
* Returns promise to resolve route for create organization page.
*
* @returns {ng.IPromise<any>}
*/
resolveCreateOrganizationRoute(): ng.IPromise<any> {
const parentQualifiedNameDefer = this.$q.defer();
const parentIdDefer = this.$q.defer();
const parentMembersDefer = this.$q.defer();
parentQualifiedNameDefer.resolve(this.$route.current.params.parentQualifiedName || '');
parentQualifiedNameDefer.promise.then((name: string) => {
if (name) {
return this.getOrFetchOrganizationByName(name);
}
return this.$q.reject('Cannot get parent for root organization');
}
).then(
/* resolve parent organization ID */
(organization: che.IOrganization) => {
const id = organization ? organization.id : '';
parentIdDefer.resolve(id);
if (!organization) {
return this.$q.reject();
} else {
return this.getOrFetchOrganizationPermissions(id);
}
}, (error: any) => {
this.logError(error);
parentIdDefer.resolve('');
parentMembersDefer.resolve([]);
return this.$q.reject(error);
}
).then(
/* fetch parent organization members */
(permissions: Array<che.IPermissions>) => {
return this.getOrFetchOrganizationUsers(permissions);
}, (error: any) => {
this.logError(error);
parentMembersDefer.resolve([]);
return this.$q.reject();
}
).then(
/* resolve parent organization members */
(userResults: any[]) => {
parentMembersDefer.resolve(userResults);
}, (error: any) => {
this.logError(error);
parentMembersDefer.resolve([]);
}
);
return this.$q.all({
parentQualifiedName: parentQualifiedNameDefer.promise,
parentOrganizationId: parentIdDefer.promise,
parentOrganizationMembers: parentMembersDefer.promise
}).then((results: any) => {
return results;
});
}
}

View File

@ -0,0 +1,106 @@
/*
* 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 {OrganizationsItemController} from './list-organizations/organizations-item/organizations-item.controller';
import {OrganizationsItem} from './list-organizations/organizations-item/organizations-item.directive';
import {ListOrganizationsController} from './list-organizations/list-organizations.controller';
import {ListOrganizations} from './list-organizations/list-organizations.directive';
import {OrganizationsController} from './organizations.controller';
import {CreateOrganizationController} from './create-organizations/create-organizations.controller';
import {OrganizationDetailsController} from './organization-details/organization-details.controller';
import {ListOrganizationMembersController} from './organization-details/organization-members/list-organization-members.controller';
import {ListOrganizationInviteMembersController} from './organization-details/organization-invite-members/list-organization-invite-members.controller';
import {ListOrganizationInviteMembers} from './organization-details/organization-invite-members/list-organization-invite-members.directive';
import {ListOrganizationMembers} from './organization-details/organization-members/list-organization-members.directive';
import {OrganizationMemberDialogController} from './organization-details/organization-member-dialog/organization-member-dialog.controller';
import {OrganizationsPermissionService} from './organizations-permission.service';
import {OrganizationsConfigService} from './organizations-config.service';
import {OrganizationNotFound} from './organization-details/organization-not-found/organization-not-found.directive';
import {OrganizationSelectMembersDialogController} from './organization-details/organization-select-members-dialog/organization-select-members-dialog.controller';
import {OrganizationMemberItem} from './organization-details/organization-select-members-dialog/organization-member-item/organization-member-item.directive';
/**
* The configuration of teams, defines controllers, directives and routing.
*
* @author Oleksii Orel
*/
export class OrganizationsConfig {
constructor(register: any) {
register.controller('OrganizationsController', OrganizationsController);
register.controller('OrganizationDetailsController', OrganizationDetailsController);
register.controller('OrganizationsItemController', OrganizationsItemController);
register.directive('organizationsItem', OrganizationsItem);
register.controller('ListOrganizationMembersController', ListOrganizationMembersController);
register.directive('listOrganizationMembers', ListOrganizationMembers);
register.directive('listOrganizationInviteMembers', ListOrganizationInviteMembers);
register.controller('ListOrganizationInviteMembersController', ListOrganizationInviteMembersController);
register.controller('OrganizationMemberDialogController', OrganizationMemberDialogController);
register.controller('CreateOrganizationController', CreateOrganizationController);
register.controller('ListOrganizationsController', ListOrganizationsController);
register.directive('listOrganizations', ListOrganizations);
register.service('organizationsPermissionService', OrganizationsPermissionService);
register.service('organizationsConfigService', OrganizationsConfigService);
register.directive('organizationNotFound', OrganizationNotFound);
register.controller('OrganizationSelectMembersDialogController', OrganizationSelectMembersDialogController);
register.directive('organizationMemberItem', OrganizationMemberItem);
const organizationDetailsLocationProvider = {
title: (params: any) => {
return params.organizationName;
},
reloadOnSearch: false,
templateUrl: 'app/organizations/organization-details/organization-details.html',
controller: 'OrganizationDetailsController',
controllerAs: 'organizationDetailsController',
resolve: {
initData: ['organizationsConfigService', (organizationConfigService: OrganizationsConfigService) => {
return organizationConfigService.resolveOrganizationDetailsRoute();
}]
}
};
const createOrganizationLocationProvider = {
title: 'New Organization',
templateUrl: 'app/organizations/create-organizations/create-organizations.html',
controller: 'CreateOrganizationController',
controllerAs: 'createOrganizationController',
resolve: {
initData: ['organizationsConfigService', (organizationsConfigService: OrganizationsConfigService) => {
return organizationsConfigService.resolveCreateOrganizationRoute();
}]
}
};
// config routes
register.app.config(($routeProvider: che.route.IRouteProvider) => {
$routeProvider.accessWhen('/organizations', {
title: 'organizations',
templateUrl: 'app/organizations/organizations.html',
controller: 'OrganizationsController',
controllerAs: 'organizationsController'
})
.accessWhen('/admin/create-organization', createOrganizationLocationProvider)
.accessWhen('/admin/create-organization/:parentQualifiedName*', createOrganizationLocationProvider)
.accessWhen('/organization/:organizationName*', organizationDetailsLocationProvider);
});
}
}

View File

@ -0,0 +1,83 @@
/*
* 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';
/**
* This class is fetch and handling the user permission data for organizations
*
* @author Oleksii Orel
*/
export class OrganizationsPermissionService {
/**
* Permissions API interaction.
*/
private chePermissions: che.api.IChePermissions;
/**
* User API interaction.
*/
private cheUser: any;
/**
* User id.
*/
private userId: string;
private fetchingMap: Map<string, ng.IPromise<any>> = new Map();
/**
* @ngInject for Dependency injection
*/
constructor(chePermissions: che.api.IChePermissions, cheUser: any) {
this.chePermissions = chePermissions;
this.cheUser = cheUser;
let user = this.cheUser.getUser();
if (user) {
this.userId = user.id;
} else {
this.cheUser.fetchUser().then((user: che.IUser) => {
this.userId = user.id;
});
}
}
fetchPermissions(organizationId: string): ng.IPromise<any> {
if (this.fetchingMap.get(organizationId)) {
return this.fetchingMap.get(organizationId);
}
let promise = this.chePermissions.fetchOrganizationPermissions(organizationId);
this.fetchingMap.set(organizationId, promise);
promise.finally(() => {
this.fetchingMap.delete(organizationId);
});
}
/**
* Checks whether user is allowed to perform pointed action.
*
* @param action {string} action
* @param organizationId {string} organization id
* @returns {boolean} <code>true</code> if allowed
*/
isUserAllowedTo(action: string, organizationId: string): boolean {
if (!organizationId || !action) {
return false;
}
let permissions = this.chePermissions.getOrganizationPermissions(organizationId);
if (!permissions) {
this.fetchPermissions(organizationId);
return false;
}
return !angular.isUndefined(permissions.find((permission: che.IPermissions) => {
return permission.userId === this.userId && permission.actions.indexOf(action.toString()) !== -1;
}));
}
}

View File

@ -0,0 +1,122 @@
/*
* 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;
/**
* @ngdoc controller
* @name organizations.controller:OrganizationsController
* @description This class is handling the controller for organizations
* @author Oleksii Orel
*/
export class OrganizationsController {
/**
* Promises service.
*/
private $q: ng.IQService;
/**
* Organization API interaction.
*/
private cheOrganization: che.api.ICheOrganization;
/**
* Service for displaying notifications.
*/
private cheNotification: any;
/**
* Loading state of the page.
*/
private isInfoLoading: boolean;
/**
* List of organizations.
*/
private organizations: Array<any> = [];
/**
* Page info object.
*/
private pageInfo: che.IPageInfo;
/**
* Has admin user service.
*/
private hasAdminUserService: boolean;
/**
* Default constructor that is using resource
* @ngInject for Dependency injection
*/
constructor(cheOrganization: che.api.ICheOrganization, cheNotification: any,
cheTeamEventsManager: che.api.ICheTeamEventsManager, $scope: ng.IScope,
$q: ng.IQService, chePermissions: che.api.IChePermissions, $rootScope: che.IRootScopeService) {
this.cheOrganization = cheOrganization;
this.cheNotification = cheNotification;
this.$q = $q;
(<any>$rootScope).showIDE = false;
this.hasAdminUserService = chePermissions.getUserServices().hasAdminUserService;
let refreshHandler = () => {
this.fetchOrganizations();
};
cheTeamEventsManager.addDeleteHandler(refreshHandler);
cheTeamEventsManager.addRenameHandler(refreshHandler);
$scope.$on('$destroy', () => {
cheTeamEventsManager.removeRenameHandler(refreshHandler);
cheTeamEventsManager.removeDeleteHandler(refreshHandler);
});
this.fetchOrganizations();
}
/**
* Fetches the list of root organizations.
* @param pageKey {string}
*/
fetchOrganizations(pageKey?: string): void {
this.isInfoLoading = true;
let promise: ng.IPromise<Array<che.IOrganization>>;
if (angular.isDefined(pageKey)) {
promise = this.cheOrganization.fetchOrganizationPageObjects(pageKey);
} else {
// todo remove admin's condition after adding query search to server side
promise = this.cheOrganization.fetchOrganizations(!this.hasAdminUserService ? MAX_ITEMS : 30);
}
promise.then((userOrganizations: Array<che.IOrganization>) => {
this.pageInfo = angular.copy(this.cheOrganization.getPageInfo());
this._updateOrganizationList(userOrganizations);
}, (error: any) => {
let message = error.data && error.data.message ? error.data.message : 'Failed to retrieve organizations.';
this.cheNotification.showError(message);
}).finally(() => {
this.isInfoLoading = false;
});
}
_updateOrganizationList(organizations: Array<che.IOrganization>): void {
// todo remove this admin's condition after adding query search to server side
if (this.hasAdminUserService) {
this.organizations = organizations.filter((organization: che.IOrganization) => {
return !organization.parent;
});
return;
}
this.organizations = organizations;
}
/**
* Gets the list of organizations.
*
* @returns {Array<che.IOrganization>}
*/
getOrganizations(): Array<che.IOrganization> {
return this.organizations;
}
}

View File

@ -0,0 +1,16 @@
<che-toolbar che-title="Organizations" border-none></che-toolbar>
<che-description che-link-title="Learn more." che-link="/docs/admin-guide/organizations/index.html">Organizations allow
groups of developers to collaborate with private and shared workspaces. Resources and permissions are controlled and
allocated within the organization by administrator.
</che-description>
<md-content md-scroll-y flex layout="column" md-theme="maincontent-theme">
<md-progress-linear md-mode="indeterminate" class="organization-progress-line"
ng-show="organizationsController.isInfoLoading"></md-progress-linear>
<md-content flex class="organizations-content" ng-show="!organizationsController.isInfoLoading">
<list-organizations on-update="organizationsController.fetchOrganizations()"
organizations="organizationsController.getOrganizations()"></list-organizations>
<che-paging-buttons ng-if="organizationsController.pageInfo"
pages-info="organizationsController.pageInfo"
fetch-page="organizationsController.fetchOrganizations(key)"></che-paging-buttons>
</md-content>
</md-content>

View File

@ -0,0 +1,2 @@
.organizations-content
margin 0

View File

@ -0,0 +1,170 @@
/*
* 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';
/**
* @ngdoc controller
* @name teams.create.controller:CreateTeamController
* @description This class is handling the controller for the new team creation.
* @author Ann Shumilova
*/
export class CreateTeamController {
/**
* Team API interaction.
*/
private cheTeam: che.api.ICheTeam;
/**
* Invite API interaction.
*/
private cheInvite: che.api.ICheInvite;
/**
* User API interaction.
*/
private cheUser: any;
/**
* Permissions API interaction.
*/
private chePermissions: che.api.IChePermissions;
/**
* Notifications service.
*/
private cheNotification: any;
/**
* Location service.
*/
private $location: ng.ILocationService;
/**
* Log service.
*/
private $log: ng.ILogService;
/**
* Lodash library.
*/
private lodash: any;
/**
* Promises service.
*/
private $q: ng.IQService;
/**
* Current team's name.
*/
private teamName: string;
/**
* Loading state of the page.
*/
private isLoading: boolean;
/**
* The list of users to invite.
*/
private members: Array<any>;
/**
* Owner'e email.
*/
private owner: string;
/**
* Account name.
*/
private accountName: string;
/**
* Default constructor
* @ngInject for Dependency injection
*/
constructor(cheTeam: che.api.ICheTeam, cheInvite: che.api.ICheInvite, cheUser: any, chePermissions: che.api.IChePermissions, cheNotification: any,
$location: ng.ILocationService, $q: ng.IQService, lodash: any, $log: ng.ILogService, $rootScope: che.IRootScopeService) {
this.cheTeam = cheTeam;
this.cheInvite = cheInvite;
this.cheUser = cheUser;
this.chePermissions = chePermissions;
this.cheNotification = cheNotification;
this.$location = $location;
this.$q = $q;
this.lodash = lodash;
this.$log = $log;
$rootScope.showIDE = false;
this.teamName = '';
this.isLoading = true;
this.members = [];
if (cheUser.getUser()) {
this.owner = cheUser.getUser().email;
this.accountName = cheUser.getUser().name;
this.isLoading = false;
} else {
cheUser.fetchUser().then(() => {
this.owner = cheUser.getUser().email;
this.accountName = cheUser.getUser().name;
this.isLoading = false;
}, (error: any) => {
if (error.status === 304) {
this.owner = cheUser.getUser().email;
this.accountName = cheUser.getUser().name;
this.isLoading = false;
} else {
this.$log.error('Failed to retrieve current user:', error);
}
});
}
}
/**
* Performs new team creation.
*/
createTeam(): void {
this.isLoading = true;
this.cheTeam.createTeam(this.teamName).then((data: any) => {
this.addPermissions(data, this.members);
this.cheTeam.fetchTeams();
}, (error: any) => {
this.isLoading = false;
let message = error.data && error.data.message ? error.data.message : 'Failed to create team ' + this.teamName + '.';
this.cheNotification.showError(message);
});
}
/**
* Add permissions for members in pointed team.
*
* @param team team
* @param members members to be added to team
*/
addPermissions(team: any, members: Array<any>) {
let promises = [];
members.forEach((member: any) => {
let actions = this.cheTeam.getActionsFromRoles(member.roles);
if (member.id) {
let permissions = {
instanceId: team.id,
userId: member.id,
domainId: 'organization',
actions: actions
};
let promise = this.chePermissions.storePermissions(permissions);
promises.push(promise);
} else {
let promise = this.cheInvite.inviteToTeam(team.id, member.email, actions);
promises.push(promise);
}
});
this.$q.all(promises).then(() => {
this.isLoading = false;
this.$location.path('/team/' + team.qualifiedName);
}, (error: any) => {
this.isLoading = false;
let message = error.data && error.data.message ? error.data.message : 'Failed to create team ' + this.teamName + '.';
this.cheNotification.showError(message);
});
}
}

View File

@ -0,0 +1,65 @@
<che-toolbar che-title="Create New Team" border-none></che-toolbar>
<che-description>
Create a team to share workspaces securely with others.
</che-description>
<div class="create-team-progress">
<md-progress-linear md-mode="indeterminate"
ng-show="createTeamController.isLoading"></md-progress-linear>
</div>
<md-content md-scroll-y flex md-theme="default" class="create-team">
<ng-form name="createTeamForm">
<!-- Name -->
<che-label-container che-label-name="Name" che-label-description="Name will be displayed in menus and prefix workspaces.">
<div layout="column" class="create-team-input">
<che-input-box che-form="createTeamForm"
che-name="name"
che-place-holder="Enter team name"
aria-label="Name of the team"
ng-model="createTeamController.teamName"
ng-trim
ng-minlength="1"
ng-maxlength="20"
ng-model-options="{allowInvalid: true, updateOn: 'default blur', debounce: { 'default': 200, 'blur': 0 } }"
unique-team-name="createTeamController.teamName"
parent-account="createTeamController.accountName"
ng-keypress="createTeamForm.$valid && $event.which === 13 && createTeamController.createTeam()"
ng-pattern="/^[a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,38}$/i"
required focusable>
<div ng-message="pattern">The name can contain alphanumeric characters or single '-' inside.
</div>
<div ng-message="minlength">The name has to be more than 1 character long.</div>
<div ng-message="maxlength">The name has to be less than 20 characters long.</div>
<div ng-message="uniqueTeamName">This team name is already used.</div>
</che-input-box>
</div>
</che-label-container>
<che-label-container che-label-name="Owner" che-label-description="Can set permissions and share resources.">
<div layout="column">
<che-input-box che-form="createTeamForm"
che-name="owner"
che-place-holder="Enter team owner"
aria-label="Owner of the team"
ng-model="createTeamController.owner"
ng-trim
che-readonly="true">
</che-input-box>
</div>
</che-label-container>
<che-label-container che-label-name="Members" che-label-description="Invite others to collaborate on the workspace."
che-alignment="{{createTeamController.members.length > 0 ? 'column' : 'row'}}">
<list-members members="createTeamController.members" owner="createTeamController.owner" class="create-team-list"></list-members>
</che-label-container>
</ng-form>
<div layout="row" layout-align="center center">
<che-button-primary id="create-team-button"
che-button-title="Create Team"
ng-click="createTeamController.createTeam()"
ng-disabled="!createTeamForm.$valid || createTeamController.isLoading">
</che-button-primary>
</div>
</md-content>

View File

@ -0,0 +1,12 @@
.create-team
padding 0 52px
.che-label-container .che-label-container-content
width 100%
input.ng-invalid.ng-pristine:focus
border-color $primary-color
.create-team-progress
height 5px
background-color $white-color

View File

@ -0,0 +1,207 @@
/*
* 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 {CheTeamRoles} from '../../../components/api/che-team-roles';
/**
* @ngdoc controller
* @name teams.invite.members:ListMembersController
* @description This class is handling the controller for the list of invited members.
* @author Ann Shumilova
*/
export class ListMembersController {
/**
* Team API interaction.
*/
private cheTeam: che.api.ICheTeam;
/**
* Lodash library.
*/
private lodash: any;
/**
* Service for displaying dialogs.
*/
private $mdDialog: angular.material.IDialogService;
/**
* No members selected.
*/
private isNoSelected: boolean;
/**
* Bulk operation checked state.
*/
private isBulkChecked: boolean;
/**
* Status of selected members.
*/
private membersSelectedStatus: any;
/**
* Number of selected members.
*/
private membersSelectedNumber: number;
/**
* Members order by value.
*/
private membersOrderBy: string;
/**
* List of members to be invited.
*/
private members: Array<any>;
/**
* Owner of the team (comes from scope).
*/
private owner: string;
/**
* Default constructor that is using resource
* @ngInject for Dependency injection
*/
constructor($mdDialog: angular.material.IDialogService, lodash: any, cheTeam: che.api.ICheTeam) {
this.$mdDialog = $mdDialog;
this.lodash = lodash;
this.cheTeam = cheTeam;
this.isNoSelected = true;
this.isBulkChecked = false;
this.membersSelectedStatus = {};
this.membersSelectedNumber = 0;
this.membersOrderBy = 'email';
}
/**
* Forms the list of members.
*/
buildMembersList(): void {
this.members.forEach((member: any) => {
member.role = member.roles ? angular.toJson(member.roles[0]) : angular.toJson(CheTeamRoles.TEAM_MEMBER);
});
}
/**
* Returns developer role value.
*
* @returns {string} string of the developer role value
*/
getDeveloperRoleValue(): string {
return angular.toJson(CheTeamRoles.TEAM_MEMBER);
}
/**
* Returns admin role value.
*
* @returns {string} string of the admin role value
*/
getAdminRoleValue(): string {
return angular.toJson(CheTeamRoles.TEAM_ADMIN);
}
/**
* Handler for value changed in the list.
* @param member
*/
onValueChanged(member): void {
member.roles = [angular.fromJson(member.role)];
}
/**
* Update members selected status
*/
updateSelectedStatus(): void {
this.membersSelectedNumber = 0;
this.isBulkChecked = !!this.members.length;
this.members.forEach((member: any) => {
if (this.membersSelectedStatus[member.email]) {
this.membersSelectedNumber++;
} else {
this.isBulkChecked = false;
}
});
}
/**
* Change bulk selection value.
*/
changeBulkSelection(): void {
if (this.isBulkChecked) {
this.deselectAllMembers();
this.isBulkChecked = false;
return;
}
this.selectAllMembers();
this.isBulkChecked = true;
}
/**
* Check all members in list.
*/
selectAllMembers(): void {
this.membersSelectedNumber = this.members.length;
this.members.forEach((member: any) => {
this.membersSelectedStatus[member.email] = true;
});
}
/**
* Uncheck all members in list
*/
deselectAllMembers(): void {
this.membersSelectedStatus = {};
this.membersSelectedNumber = 0;
}
/**
* Adds member to the list.
*
* @param user
* @param roles
*/
addMembers(users: Array<any>, roles: Array<any>): void {
users.forEach((user: any) => {
user.roles = roles;
this.members.push(user);
});
this.buildMembersList();
}
/**
* Shows dialog to add new member.
*
* @param $event
*/
showAddDialog($event: MouseEvent): void {
let members = this.members.concat([{email: this.owner}]);
this.$mdDialog.show({
targetEvent: $event,
controller: 'MemberDialogController',
controllerAs: 'memberDialogController',
bindToController: true,
clickOutsideToClose: true,
locals: {
members: members,
member: null,
callbackController: this
},
templateUrl: 'app/teams/member-dialog/member-dialog.html'
});
}
/**
* Removes selected members.
*/
removeSelectedMembers(): void {
this.lodash.remove(this.members, (member: any) => {
return this.membersSelectedStatus[member.email];
});
this.buildMembersList();
this.deselectAllMembers();
this.isBulkChecked = false;
}
}

View File

@ -0,0 +1,43 @@
/*
* 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';
/**
* @ngdoc directive
* @name teams.invite.members:ListMembers
* @restrict E
* @element
*
* @description
* `<list-members members="ctrl.members"></list-members>` for displaying list of members
*
* @usage
* <list-members members="ctrl.members"></list-members>
*
* @author Ann Shumilova
*/
export class ListMembers implements ng.IDirective {
restrict: string = 'E';
templateUrl: string = 'app/teams/invite-members/list-members.html';
controller: string = 'ListMembersController';
controllerAs: string = 'listMembersController';
bindToController: boolean = true;
scope: any = {
members: '=',
owner: '='
};
constructor () {
}
}

View File

@ -0,0 +1,87 @@
<div class="list-members" layout="column">
<div ng-if="listMembersController.members.length > 0" class="list-members-spacing">
<che-list-header>
<div flex="100"
layout="row"
layout-align="start stretch"
class="che-list-item-row">
<div layout="row"
layout-align="start center"
class="che-checkbox-area">
<div layout="row" layout-align="start center" class="che-list-item-checkbox-main">
<md-checkbox class="che-list-item-checkbox"
aria-label="All members"
md-theme="default"
ng-checked="listMembersController.isBulkChecked"
ng-click="listMembersController.changeBulkSelection()"></md-checkbox>
</div>
</div>
<div flex hide-xs layout-gt-xs="row"
layout-align="start center"
class="che-list-item-details">
<che-list-header-column flex="30"
che-sort-value="listMembersController.membersOrderBy"
che-sort-item="name"
che-column-title='Invited Members'></che-list-header-column>
<che-list-header-column flex="35"
che-column-title='Developer'></che-list-header-column>
<che-list-header-column flex="35"
che-column-title='Admin'></che-list-header-column>
</div>
</div>
</che-list-header>
<che-list flex>
<che-list-item ng-mouseover="hover=true" ng-mouseout="hover=false"
ng-repeat="member in listMembersController.members | orderBy:listMembersController.membersOrderBy">
<div flex="100"
layout="row"
layout-align="start stretch"
class="member-item-row">
<div layout="row"
layout-align="start center"
class="che-checkbox-area">
<che-list-item-checked ng-model="listMembersController.membersSelectedStatus[member.email]"
che-aria-label-checkbox="Member {{member.email}}"
ng-click="listMembersController.updateSelectedStatus()"></che-list-item-checked>
</div>
<div flex
layout-xs="column" layout-gt-xs="row"
layout-align-gt-xs="start center"
layout-align-xs="start start"
class="che-list-item-details">
<div flex="30"
class="che-list-item-name">
<span class="material-design icon-ic_done_24px user-exists-checked" ng-if="member.id"></span>
<span class="che-hover">{{member.email}}</span>
</div>
<div flex="35">
<md-radio-group ng-model="member.role" ng-change="listMembersController.onValueChanged(member)">
<md-radio-button value="{{listMembersController.getDeveloperRoleValue()}}"></md-radio-button>
</md-radio-group>
</div>
<div flex="35">
<md-radio-group ng-model="member.role" ng-change="listMembersController.onValueChanged(member)">
<md-radio-button value="{{listMembersController.getAdminRoleValue()}}"></md-radio-button>
</md-radio-group>
</div>
</div>
</div>
</che-list-item>
</che-list>
</div>
<!-- buttons -->
<div layout="row">
<div flex layout-align="center start">
<che-button-default class="che-list-add-button"
che-button-title="Add" name="addButton"
ng-click="listMembersController.showAddDialog($event)"></che-button-default>
</div>
<div flex-offset="5" ng-if="listMembersController.members.length > 0">
<che-button-primary-flat ng-disabled="(listMembersController.membersSelectedNumber === 0)"
che-button-title="Remove" name="removeButton"
ng-click="listMembersController.removeSelectedMembers()"></che-button-primary-flat>
</div>
</div>
</div>

View File

@ -0,0 +1,30 @@
.list-members
*
outline none !important
.che-list
margin-bottom 12px
background-color inherit
.che-list-item-checkbox-main
padding-left 0
.md-button
margin 0
filter none !important
.member-item-row
min-height 33px
.che-list-header-content > *, .che-list
margin 0
.che-list-header md-item
border-top none
.list-members .permission-switcher
overflow visible
margin 0px
.list-members-spacing
margin-bottom 20px

View File

@ -0,0 +1,357 @@
/*
* 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';
/**
* @ngdoc controller
* @name list.teams:ListTeamsController
* @description This class is handling the controller for the list of teams
* @author Ann Shumilova
*/
export class ListTeamsController {
/**
* Team API interaction.
*/
private cheTeam: che.api.ICheTeam;
/**
* Permissions API interaction.
*/
private chePermissions: che.api.IChePermissions;
/**
* Team resources API interaction.
*/
private cheResourcesDistribution: che.api.ICheResourcesDistribution;
/**
* Service for displaying notifications.
*/
private cheNotification: any;
/**
* Service for displaying dialogs.
*/
private confirmDialogService: any;
/**
* Promises service.
*/
private $q: ng.IQService;
/**
* Location service.
*/
private $location: ng.ILocationService;
/**
* List of teams.
*/
private teams: Array<any>;
/**
* Map of team members.
*/
private teamMembers: Map<string, number>;
/**
* Map of team resources.
*/
private teamResources: Map<string, any>;
/**
* Loading state of the page.
*/
private isLoading: boolean;
/**
* Selected status of teams in the list.
*/
private teamsSelectedStatus: any;
/**
* Bulk operation checked state.
*/
private isBulkChecked: boolean;
/**
* No selected workspace state.
*/
private isNoSelected: boolean;
/**
* All selected workspace state.
*/
private isAllSelected: boolean;
/**
* todo
*/
private resourceLimits: che.resource.ICheResourceLimits;
/**
* Default constructor that is using resource
* @ngInject for Dependency injection
*/
constructor(cheTeam: che.api.ICheTeam, chePermissions: che.api.IChePermissions, cheResourcesDistribution: che.api.ICheResourcesDistribution,
cheNotification: any, cheTeamEventsManager: che.api.ICheTeamEventsManager, confirmDialogService: any, $scope: ng.IScope,
$q: ng.IQService, $location: ng.ILocationService, resourcesService: che.service.IResourcesService) {
this.cheTeam = cheTeam;
this.chePermissions = chePermissions;
this.cheResourcesDistribution = cheResourcesDistribution;
this.resourceLimits = resourcesService.getResourceLimits();
this.cheNotification = cheNotification;
this.confirmDialogService = confirmDialogService;
this.$q = $q;
this.$location = $location;
this.teams = [];
this.isLoading = true;
this.teamsSelectedStatus = {};
this.isBulkChecked = false;
this.isNoSelected = true;
this.fetchTeams();
let refreshHandler = () => {
this.fetchTeams();
};
cheTeamEventsManager.addDeleteHandler(refreshHandler);
cheTeamEventsManager.addRenameHandler(refreshHandler);
$scope.$on('$destroy', () => {
cheTeamEventsManager.removeRenameHandler(refreshHandler);
cheTeamEventsManager.removeDeleteHandler(refreshHandler);
});
}
/**
* Fetches the list of teams.
*/
fetchTeams(): void {
this.isLoading = true;
this.cheTeam.fetchTeams().then(() => {
this.isLoading = false;
this.processTeams();
}, (error: any) => {
this.isLoading = false;
let message = error.data && error.data.message ? error.data.message : 'Failed to retrieve teams.';
this.cheNotification.showError(message);
});
}
/**
* Process team - retrieving additional data.
*/
processTeams(): void {
this.teams = this.cheTeam.getTeams();
this.teamMembers = new Map();
this.teamResources = new Map();
let promises = [];
this.teams.forEach((team: any) => {
let promiseMembers = this.chePermissions.fetchOrganizationPermissions(team.id).then(() => {
this.teamMembers.set(team.id, this.chePermissions.getOrganizationPermissions(team.id).length);
}, (error: any) => {
if (error.status === 304) {
this.teamMembers.set(team.id, this.chePermissions.getOrganizationPermissions(team.id).length);
}
});
promises.push(promiseMembers);
let promiseResource = this.cheResourcesDistribution.fetchOrganizationResources(team.id).then(() => {
this.processResource(team.id);
}, (error: any) => {
if (error.status === 304) {
this.processResource(team.id);
}
});
promises.push(promiseResource);
});
this.$q.all(promises).finally(() => {
this.isLoading = false;
});
}
/**
* Process team resources.
*
* @param teamId team's id
*/
processResource(teamId: string): void {
let ramLimit = this.cheResourcesDistribution.getOrganizationResourceByType(teamId, this.resourceLimits.RAM);
this.teamResources.set(teamId, ramLimit ? ramLimit.amount : undefined);
}
/**
* Returns the number of team's members.
*
* @param teamId team's id
* @returns {any} number of team members to display
*/
getMembersCount(teamId: string): any {
if (this.teamMembers && this.teamMembers.size > 0) {
return this.teamMembers.get(teamId) || '-';
}
return '-';
}
/**
* Returns the RAM limit value.
*
* @param teamId team's id
* @returns {any}
*/
getRamCap(teamId: string): any {
if (this.teamResources && this.teamResources.size > 0) {
let ram = this.teamResources.get(teamId);
return ram ? (ram / 1024) : null;
}
return null;
}
/**
* Returns <code>true</code> if all teams in list are checked.
*
* @returns {boolean}
*/
isAllTeamsSelected(): boolean {
return this.isAllSelected;
}
/**
* Returns <code>true</code> if all teams in list are not checked.
*
* @returns {boolean}
*/
isNoTeamsSelected(): boolean {
return this.isNoSelected;
}
/**
* Make all teams in list selected.
*/
selectAllTeams(): void {
this.teams.forEach((team: any) => {
this.teamsSelectedStatus[team.id] = true;
});
}
/**
* Make all teams in list deselected.
*/
deselectAllTeams(): void {
this.teams.forEach((team: any) => {
this.teamsSelectedStatus[team.id] = false;
});
}
/**
* Change bulk selection value.
*/
changeBulkSelection(): void {
if (this.isBulkChecked) {
this.deselectAllTeams();
this.isBulkChecked = false;
} else {
this.selectAllTeams();
this.isBulkChecked = true;
}
this.updateSelectedStatus();
}
/**
* Update teams selected status.
*/
updateSelectedStatus(): void {
this.isNoSelected = true;
this.isAllSelected = true;
Object.keys(this.teamsSelectedStatus).forEach((key: string) => {
if (this.teamsSelectedStatus[key]) {
this.isNoSelected = false;
} else {
this.isAllSelected = false;
}
});
if (this.isNoSelected) {
this.isBulkChecked = false;
return;
}
if (this.isAllSelected) {
this.isBulkChecked = true;
}
}
/**
* Redirects to new team creation page.
*/
createNewTeam(): void {
this.$location.path('/team/create');
}
/**
* Delete all selected teams.
*/
removeTeams(): void {
let teamsSelectedStatusKeys = Object.keys(this.teamsSelectedStatus);
let checkedTeamsKeys = [];
if (!teamsSelectedStatusKeys.length) {
this.cheNotification.showError('No such team.');
return;
}
teamsSelectedStatusKeys.forEach((key: any) => {
if (this.teamsSelectedStatus[key] === true) {
checkedTeamsKeys.push(key);
}
});
if (!checkedTeamsKeys.length) {
this.cheNotification.showError('No such team.');
return;
}
let confirmationPromise = this.showDeleteTeamsConfirmation(checkedTeamsKeys.length);
confirmationPromise.then(() => {
this.isLoading = true;
let promises = [];
checkedTeamsKeys.forEach((teamId: string) => {
this.teamsSelectedStatus[teamId] = false;
let promise = this.cheTeam.deleteTeam(teamId).then(() => {
//
}, (error: any) => {
this.cheNotification.showError(error && error.data && error.data.message ? error.data.message : 'Failed to delete team ' + teamId + '.');
});
promises.push(promise);
});
this.$q.all(promises).finally(() => {
this.fetchTeams();
this.updateSelectedStatus();
});
});
}
/**
* Show confirmation popup before teams deletion.
*
* @param numberToDelete number of teams to be deleted
* @returns {*}
*/
showDeleteTeamsConfirmation(numberToDelete: number): ng.IPromise<any> {
let content = 'Would you like to delete ';
if (numberToDelete > 1) {
content += 'these ' + numberToDelete + ' teams?';
} else {
content += 'this selected team?';
}
return this.confirmDialogService.showConfirmDialog('Delete teams', content, 'Delete');
}
}

View File

@ -0,0 +1,44 @@
/*
* 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';
/**
* @ngdoc directive
* @name list.teams:ListTeams
* @restrict E
* @element
*
* @description
* `<list-teams></list-teams>` for displaying list of teams
*
* @usage
* <list-teams></list-teams>
*
* @author Ann Shumilova
*/
export class ListTeams implements ng.IDirective {
restrict: string = 'E';
templateUrl: string = 'app/teams/list/list-teams.html';
controller: string = 'ListTeamsController';
controllerAs: string = 'listTeamsController';
bindToController: boolean = true;
scope: any = {
accountId: '=',
readonly: '='
};
constructor () {
//
}
}

View File

@ -0,0 +1,62 @@
<div class="list-teams-progress">
<md-progress-linear md-mode="indeterminate" ng-show="listTeamsController.isLoading"></md-progress-linear>
</div>
<md-content flex class="list-teams-content">
<div class="list-teams">
<che-list-header ng-if="listTeamsController.teams.length > 0">
<div flex="100"
layout="row"
layout-align="start stretch"
class="che-list-item-row">
<div layout="column" layout-gt-xs="row" ng-if="!listTeamsController.readonly"
layout-align="start center"
class="che-checkbox-area">
<div layout="row" layout-align="center center" class="che-list-item-checkbox-main">
<md-checkbox class="che-list-item-checkbox"
aria-label="team list"
ng-checked="listTeamsController.isBulkChecked"
ng-click="listTeamsController.changeBulkSelection()"></md-checkbox>
</div>
</div>
<div flex hide-xs layout-gt-xs="row"
layout-align="start center"
class="che-list-item-details">
<che-list-header-column flex-gt-xs="30"
che-sort-value='listTeamsController.teamOrderBy'
che-sort-item='name'
che-column-title='Name'></che-list-header-column>
<che-list-header-column flex-gt-xs="25"
che-column-title='Members'></che-list-header-column>
<che-list-header-column flex-gt-xs="25"
che-column-title='RAM CAP'></che-list-header-column>
<che-list-header-column flex-gt-xs="20"
che-column-title='Actions'></che-list-header-column>
</div>
</div>
</che-list-header>
<che-list ng-show="listTeamsController.teams">
<team-item ng-repeat="team in listTeamsController.teams"
team="team"
members="listTeamsController.getMembersCount(team.id)"
ram-cap="listTeamsController.getRamCap(team.id)"
cdvy-is-selectable="!listTeamsController.readonly"
ng-model="listTeamsController.teamsSelectedStatus[team.id]"
on-update="listTeamsController.fetchTeams()"
cdvy-on-checkbox-click="listTeamsController.updateSelectedStatus()">
</team-item>
</che-list>
</div>
<!-- Buttons -->
<div layout="row" flex ng-if="!listTeamsController.readonly">
<div flex>
<che-button-default class="che-list-add-button"
che-button-title="Create Team" name="createTeamButton"
ng-click="listTeamsController.createNewTeam()"></che-button-default>
</div>
<div ng-if="listTeamsController.teams.length > 0">
<che-button-primary-flat ng-disabled="listTeamsController.isNoSelected"
che-button-title="Delete" name="deleteButton"
ng-click="listTeamsController.removeTeams()"></che-button-primary-flat>
</div>
</div>
</md-content>

View File

@ -0,0 +1,24 @@
.list-teams-content
margin-top 5px
button
margin 0px !important
.list-teams
margin-bottom 20px
.che-list
margin 0px
.che-list-header-content *
margin 0px
.che-list-header md-item
border-top none
.che-list-header-additional
display none
.list-teams-progress
height 5px
margin-top 20px

View File

@ -0,0 +1,96 @@
/*
* 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';
/**
* Controller for a team item.
*
* @author Ann Shumilova
*/
export class TeamItemController {
/**
* Service for displaying dialogs.
*/
private confirmDialogService: any;
/**
* Location service.
*/
private $location: ng.ILocationService;
/**
* Team API interaction.
*/
private cheTeam: che.api.ICheTeam;
/**
* Service for displaying notifications.
*/
private cheNotification: any;
/**
* Team details (the value is set in directive attributes).
*/
private team: any;
/**
* Callback needed to react on teams updation (the value is set in directive attributes).
*/
private onUpdate: Function;
/**
* Default constructor that is using resource injection
* @ngInject for Dependency injection
*/
constructor($location: ng.ILocationService, cheTeam: che.api.ICheTeam, confirmDialogService: any, cheNotification: any) {
this.$location = $location;
this.confirmDialogService = confirmDialogService;
this.cheTeam = cheTeam;
this.cheNotification = cheNotification;
}
/**
* Redirect to team details.
*/
redirectToTeamDetails(tab: string) {
this.$location.path('/team/' + this.team.qualifiedName).search(!tab ? {} : {tab: tab});
}
/**
* Removes team after confirmation.
*/
removeTeam(): void {
this.confirmRemoval().then(() => {
this.cheTeam.deleteTeam(this.team.id).then(() => {
this.onUpdate();
}, (error: any) => {
this.cheNotification.showError(error && error.data && error.data.message ? error.data.message : 'Failed to delete team ' + this.team.name);
});
});
}
/**
* Get team display name.
*
* @param team
* @returns {string}
*/
getTeamDisplayName(team: any): string {
return this.cheTeam.getTeamDisplayName(team);
}
/**
* Shows dialog to confirm the current team removal.
*
* @returns {angular.IPromise<any>}
*/
confirmRemoval(): ng.IPromise<any> {
let promise = this.confirmDialogService.showConfirmDialog('Delete team',
'Would you like to delete team \'' + this.team.name + '\'?', 'Delete');
return promise;
}
}

View File

@ -0,0 +1,43 @@
/*
* 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';
/**
* Defines a directive for team item in list.
*
* @author Ann Shumilova
*/
export class TeamItem {
restrict: string = 'E';
templateUrl: string = 'app/teams/list/team-item/team-item.html';
replace: boolean = false;
controller: string = 'TeamItemController';
controllerAs: string = 'teamItemController';
bindToController: boolean = true;
require: Array<string> = ['ngModel'];
scope: {
[propName: string]: string
};
constructor() {
this.scope = {
team: '=team',
members: '=',
ramCap: '=',
isChecked: '=cdvyChecked',
isSelect: '=?ngModel',
onCheckboxClick: '&?cdvyOnCheckboxClick',
selectable: '=cdvyIsSelectable',
onUpdate: '&?onUpdate'
};
}
}

View File

@ -0,0 +1,43 @@
<che-list-item flex ng-mouseover="hover=true" ng-mouseout="hover=false">
<div flex="100"
layout="row"
layout-align="start stretch"
class="che-list-item-row">
<div ng-if="teamItemController.selectable"
layout="row"
layout-align="start center"
class="che-checkbox-area">
<che-list-item-checked ng-model="teamItemController.isSelect"
che-aria-label-checkbox="Team {{teamItemController.team.name}}"
ng-click="teamItemController.onCheckboxClick()"></che-list-item-checked>
</div>
<div flex
layout-xs="column" layout-gt-xs="row"
layout-align-gt-xs="start center"
layout-align-xs="start start"
class="che-list-item-details">
<div flex-gt-xs="30"
class="che-list-item-name"
ng-click="teamItemController.redirectToTeamDetails();">
<span class="che-xs-header noselect" hide-gt-xs>Name</span>
<span class="che-hover">{{teamItemController.getTeamDisplayName(teamItemController.team)}}</span>
</div>
<div flex-gt-xs="25" ng-click="teamItemController.redirectToTeamDetails('Members')">
<span class="che-xs-header noselect" hide-gt-xs>Members</span>
<span>{{teamItemController.members}}</span>
</div>
<div flex-gt-xs="25" ng-click="teamItemController.redirectToTeamDetails()" class="cap-value">
<span class="che-xs-header noselect" hide-gt-xs>RAM Cap</span>
<span>{{teamItemController.ramCap ? teamItemController.ramCap + 'GB' : 'not set'}}</span>
</div>
<div flex-gt-xs="20">
<span class="che-xs-header noselect" hide-gt-xs>Actions</span>
<span class="che-list-actions">
<a uib-tooltip="Remove team" ng-click="teamItemController.removeTeam();">
<span class="fa fa-trash-o"></span>
</a>
</span>
</div>
</div>
</div>
</che-list-item>

View File

@ -0,0 +1,3 @@
.cap-value
color $primary-color

View File

@ -0,0 +1,250 @@
/*
* 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 {CheTeamRoles} from '../../../components/api/che-team-roles';
/**
* @ngdoc controller
* @name teams.member:MemberDialogController
* @description This class is handling the controller for adding/editing members dialog.
* @author Ann Shumilova
*/
export class MemberDialogController {
/**
* Team API interaction.
*/
private cheTeam: che.api.ICheTeam;
/**
* User API interaction.
*/
private cheUser: any;
/**
* Service for displaying dialogs.
*/
private $mdDialog: angular.material.IDialogService;
/**
* Promises service.
*/
private $q: ng.IQService;
/**
* Lodash library.
*/
private lodash: any;
/**
* Processing state of adding member.
*/
private isProcessing: boolean;
/**
* Set of user roles info.
*/
private roles: Array<any>;
/**
* Already added emails.
*/
private emails: Array<string>;
/**
* Existing members.
*/
private members: Array<any>;
/**
* Entered email address.
*/
private email: string;
/**
* Controller that will handle callbacks.
*/
private callbackController: any;
/**
* Member to be displayed, may be <code>null</code> if add new member is needed. (Comes from outside)
*/
private member: any;
/**
* Role to be used, may be <code>null</code> if role is needed to be set. (Comes from outside)
*/
private role: any;
/**
* Choosen role for user.
*/
private newRole: any;
/**
* Dialog window title.
*/
private title: string;
/**
* Title of operation button (Save or Add)
*/
private buttonTitle: string;
/**
* Email validation error message.
*/
private emailError: string;
/**
* Default constructor that is using resource
* @ngInject for Dependency injection
*/
constructor($mdDialog: angular.material.IDialogService, cheTeam: che.api.ICheTeam, cheUser: any, $q: ng.IQService, lodash: any) {
this.$mdDialog = $mdDialog;
this.cheTeam = cheTeam;
this.cheUser = cheUser;
this.$q = $q;
this.lodash = lodash;
this.isProcessing = false;
this.emails = [];
this.members.forEach((member: any) => {
this.emails.push(member.email);
});
// role is set, need to add only user with this role:
if (this.role) {
this.email = '';
this.title = 'Add new ' + this.role.title.toLowerCase();
this.buttonTitle = 'Add';
return;
}
this.roles = CheTeamRoles.getValues();
if (this.member) {
this.title = 'Edit ' + this.member.name + ' roles';
this.buttonTitle = 'Save';
this.email = this.member.email;
let roles = this.cheTeam.getRolesFromActions(this.member.permissions.actions);
this.newRole = (roles && roles.length > 0) ? angular.toJson(roles[0]) : angular.toJson(CheTeamRoles.TEAM_MEMBER);
} else {
this.email = '';
this.title = 'Invite member to collaborate';
this.buttonTitle = 'Add';
this.newRole = angular.toJson(CheTeamRoles.TEAM_MEMBER);
}
}
/**
* Hides the add member dialog.
*/
hide(): void {
this.$mdDialog.hide();
}
/**
* Checks whether entered email valid and is unique.
*
* @param value value with email(s) to check
* @returns {boolean} true if pointed email(s) are valid and not in the list yet
*/
isValidEmail(value: string): boolean {
// return this.emails.indexOf(email) < 0;
let emails = value.replace(/ /g, ',').split(',');
for (let i = 0; i < emails.length; i++) {
let email = emails[i];
let emailRe = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i;
if (!emailRe.test(email)) {
this.emailError = email + ' is invalid email address.';
return false;
}
if (this.emails.indexOf(email) >= 0) {
this.emailError = 'User with email ' + email + ' is already invited.';
return false;
}
}
return true;
}
/**
* Adds new member.
*/
addMembers(): void {
let userRoles = this.role ? [this.role] : [angular.fromJson(this.newRole)];
let emails = this.email.replace(/ /g, ',').split(',');
// form the list of emails without duplicates and empty values:
let resultEmails = emails.reduce((array: Array<string>, element: string) => {
if (array.indexOf(element) < 0 && element.length > 0) {
array.push(element);
}
return array;
}, []);
let promises = [];
let users = [];
resultEmails.forEach((email: string) => {
promises.push(this.processUser(email, users));
});
this.$q.all(promises).then(() => {
this.finishAdding(users, userRoles);
});
}
processUser(email: string, users : Array<any>): ng.IPromise<any> {
let deferred = this.$q.defer();
let user = this.cheUser.getUserByAlias(email);
if (user) {
users.push(user);
deferred.resolve();
} else {
user = {};
user.email = email;
this.isProcessing = true;
this.cheUser.fetchUserByAlias(email).then(() => {
users.push(this.cheUser.getUserByAlias(email));
deferred.resolve();
}, (error: any) => {
users.push(user);
deferred.resolve();
});
}
return deferred.promise;
}
/**
* Handle edit member user's action.
*/
editMember(): void {
this.member.permissions.actions = this.getCurrentActions();
this.callbackController.updateMember(this.member);
this.hide();
}
/**
* Returns the actions of current chosen roles.
*/
getCurrentActions(): Array<string> {
let userRoles = this.role ? [this.role] : [angular.fromJson(this.newRole)];
let processedActions = [];
this.roles.forEach((role: any) => {
processedActions = processedActions.concat(role.actions);
});
let actions = this.member ? this.member.permissions.actions : [];
let otherActions = this.lodash.difference(actions, processedActions);
return this.lodash.uniq(this.cheTeam.getActionsFromRoles(userRoles).concat(otherActions));
}
/**
* Finish adding user state.
*
* @param users users to be added
* @param roles user's roles
*/
finishAdding(users: Array<any>, roles: any): void {
this.isProcessing = false;
this.callbackController.addMembers(users, roles);
this.hide();
}
}

View File

@ -0,0 +1,46 @@
<che-popup title="{{memberDialogController.title}}" on-close="memberDialogController.hide()">
<div class="member-dialog-content" md-theme="default">
<ng-form flex layout="column" name="memberForm">
<che-label-container che-label-name="Email" ng-show="!memberDialogController.member"
che-label-description="User email address.">
<che-input-box che-form="memberForm"
che-name="email"
che-place-holder="Enter email"
ng-model="memberDialogController.email"
ng-model-options="{allowInvalid: true}"
ng-disabled="memberDialogController.isProcessing"
custom-validator="memberDialogController.isValidEmail($value)"
type="text"
aria-label="New member"
ng-keypress="memberForm.$valid && $event.which === 13 && memberDialogController.addMembers()"
required focusable>
<div ng-message="customValidator">{{memberDialogController.emailError}}</div>
</che-input-box>
</che-label-container>
<che-label-container che-label-name="Role" che-label-description="Allowed actions of member." ng-if="!memberDialogController.role">
<div layout="column">
<md-radio-group ng-model="memberDialogController.newRole">
<div ng-repeat="roleInfo in memberDialogController.roles" layout="row">
<md-radio-button value="{{roleInfo}}">{{roleInfo.title}}</md-radio-button>
<span class="member-role-description">({{roleInfo.description}})</span>
</div>
</md-radio-group>
</div>
</che-label-container>
</ng-form>
<div layout="row" layout-align="end center">
<che-button-primary che-button-title="{{memberDialogController.buttonTitle}}"
ng-if="!memberDialogController.member"
ng-disabled="memberForm.$invalid || memberDialogController.isProcessing"
ng-click="memberDialogController.addMembers()"></che-button-primary>
<che-button-primary che-button-title="{{memberDialogController.buttonTitle}}"
ng-disabled="memberDialogController.isProcessing"
ng-if="memberDialogController.member"
ng-click="memberDialogController.editMember()"></che-button-primary>
<che-button-cancel-flat che-button-title="Cancel"
ng-click="memberDialogController.hide()"
tabindex="0"></che-button-cancel-flat>
</div>
</div>
</che-popup>

View File

@ -0,0 +1,33 @@
.member-dialog-content
width 630px
button
margin 0 0 0 20px
input.ng-invalid.ng-pristine:focus
border-color $primary-color
.member-dialog-content .che-label-container .che-label-container-label
width 150px
min-width 150px
.member-role-title
margin-bottom 10px
.member-role-description
color $disabled-color
font-size 10px
padding 0px 5px
line-height 18pt
.member-role-warning-label
color $warning-color
height 20px
.member-role-button
margin-right 10px
width 150px
.member-role-button button
width 150px

View File

@ -0,0 +1,62 @@
/*
* 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';
/**
* @ngdoc controller
* @name teams.navbar.controller:NavbarTeamsController
* @description This class is handling the controller for the teams section in navbar
* @author Ann Shumilova
*/
export class NavbarTeamsController {
/**
* Team API interaction.
*/
private cheTeam: che.api.ICheTeam;
/**
* Default constructor
* @ngInject for Dependency injection
*/
constructor(cheTeam: che.api.ICheTeam) {
this.cheTeam = cheTeam;
this.fetchTeams();
}
/**
* Fetch the list of available teams.
*/
fetchTeams(): void {
this.cheTeam.fetchTeams();
}
getTeamDisplayName(team: any): string {
return this.cheTeam.getTeamDisplayName(team);
}
/**
* Get the list of available teams.
*
* @returns {Array<any>} teams array
*/
getTeams(): Array<any> {
return this.cheTeam.getTeams();
}
/**
* Returns personal account of current user.
*
* @returns {any} personal account
*/
getPersonalAccount(): any {
return this.cheTeam.getPersonalAccount();
}
}

View File

@ -0,0 +1,28 @@
/*
* 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';
/**
* @ngdoc directive
* @name teams.directive:NavbarTeams
* @description This class is handling the directive of for listing teams in navbar.
* @author Ann Shumilova
*/
export class NavbarTeams implements ng.IDirective {
restrict: string = 'E';
templateUrl: string = 'app/teams/navbar-teams/navbar-teams.html';
controller: string = 'NavbarTeamsController';
controllerAs: string = 'navbarTeamsController';
bindToController: boolean = true;
}

View File

@ -0,0 +1,43 @@
<div flex
layout="column"
class="admin-navbar-menu navbar-teams"
ng-if="navbarTeamsController.getPersonalAccount()">
<section class="left-sidebar-menu"
flex
layout="column" layout-align="start stretch">
<div class="navbar-section navbar-section-title"
layout="row">
<div flex>
<span>Teams</span>
</div>
</div>
<md-list flex layout="column">
<md-list-item class="navbar-subsection-item">
<md-button che-reload-href
ng-href="#/team/create"
layout-align="left">
<div class="navbar-item" layout="row" layout-align="start center">
<i class="fa fa-plus navbar-icon navbar-primary"></i>
<span>Create Team</span>
</div>
</md-button>
</md-list-item>
<div flex layout="column"
class="navbar-teams-list">
<md-list-item class="navbar-subsection-item"
ng-repeat="team in navbarTeamsController.getTeams()">
<md-button nav-bar-selected flex che-reload-href
ng-href="#/team/{{team.qualifiedName}}" layout-align="left"
target="_self">
<div class="navbar-item" layout="row" layout-align="start center">
<span class="navbar-icon"></span>
<span title="{{team.qualifiedName}}">{{navbarTeamsController.getTeamDisplayName(team)}}</span>
</div>
</md-button>
</md-list-item>
</div>
</md-list>
</section>
</div>

View File

@ -0,0 +1,25 @@
.navbar-teams
.navbar-subsection-item
height 19px
min-height 19px
line-height 19px
.md-button
height 19px !important
min-height 19px !important
.navbar-item
line-height 19px !important
.navbar-icon
height 19px !important
line-height 19px !important
.navbar-teams-list
overflow auto
::-webkit-scrollbar-thumb
background-color rgba(255,255,255,.1)
::-webkit-scrollbar-thumb:hover
background-color rgba(255,255,255,.2)

View File

@ -0,0 +1,435 @@
/*
* 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 {TeamDetailsService} from './team-details.service';
enum Tab {Settings, Members, Workspaces}
/**
* Controller for a managing team details.
*
* @author Ann Shumilova
*/
export class TeamDetailsController {
tab: Object = Tab;
/**
* Team API interaction.
*/
private cheTeam: che.api.ICheTeam;
/**
* Team events manager.
*/
private cheTeamEventsManager: che.api.ICheTeamEventsManager;
/**
* Team resources API interaction.
*/
private cheResourcesDistribution: che.api.ICheResourcesDistribution;
/**
* Permissions API interaction.
*/
private chePermissions: che.api.IChePermissions;
/**
* User API interaction.
*/
private cheUser: any;
/**
* Notifications service.
*/
private cheNotification: any;
/**
* Location service.
*/
private $location: ng.ILocationService;
/**
* Service for displaying dialogs.
*/
private confirmDialogService: any;
/**
* Lodash library.
*/
private lodash: any;
/**
* Current team's name. Comes from route path params.
*/
private teamName: string;
/**
* Current team's data.
*/
private team: any;
/**
* Current team's owner.
*/
private owner: any;
/**
* The list of allowed user actions.
*/
private allowedUserActions: Array<string>;
/**
* New team's name (for renaming widget).
*/
private newName: string;
/**
* Index of the selected tab.
*/
private selectedTabIndex: number;
/**
* Team limits.
*/
private limits: any;
/**
* Copy of limits before letting to modify, to be able to compare.
*/
private limitsCopy: any;
/**
* Page loading state.
*/
private isLoading: boolean;
private teamForm: ng.IFormController;
private hasTeamAccess: boolean;
private resourceLimits: che.resource.ICheResourceLimits;
/**
* Default constructor that is using resource injection
* @ngInject for Dependency injection
*/
constructor(cheTeam: che.api.ICheTeam, cheResourcesDistribution: che.api.ICheResourcesDistribution, chePermissions: che.api.IChePermissions,
cheUser: any, $route: ng.route.IRouteService, $location: ng.ILocationService, $rootScope: che.IRootScopeService,
$scope: ng.IScope, confirmDialogService: any, cheTeamEventsManager: che.api.ICheTeamEventsManager, cheNotification: any,
lodash: any, teamDetailsService: TeamDetailsService, resourcesService: che.service.IResourcesService) {
this.cheTeam = cheTeam;
this.cheResourcesDistribution = cheResourcesDistribution;
this.chePermissions = chePermissions;
this.cheTeamEventsManager = cheTeamEventsManager;
this.cheUser = cheUser;
this.teamName = $route.current.params.teamName;
this.$location = $location;
this.confirmDialogService = confirmDialogService;
this.cheNotification = cheNotification;
this.lodash = lodash;
this.resourceLimits = resourcesService.getResourceLimits();
$rootScope.showIDE = false;
this.allowedUserActions = [];
let deleteHandler = (info: any) => {
if (this.team && (this.team.id === info.organization.id)) {
this.$location.path('/workspaces');
}
};
this.cheTeamEventsManager.addDeleteHandler(deleteHandler);
let renameHandler = (info: any) => {
if (this.team && (this.team.id === info.organization.id)) {
this.$location.path('/team/' + info.organization.qualifiedName);
}
};
this.cheTeamEventsManager.addRenameHandler(renameHandler);
this.updateSelectedTab(this.$location.search().tab);
let deRegistrationFn = $scope.$watch(() => {
return $location.search().tab;
}, (tab: string) => {
if (angular.isDefined(tab)) {
this.updateSelectedTab(tab);
}
}, true);
$scope.$on('$destroy', () => {
this.cheTeamEventsManager.removeRenameHandler(renameHandler);
this.cheTeamEventsManager.removeDeleteHandler(deleteHandler);
deRegistrationFn();
});
this.isLoading = true;
this.hasTeamAccess = true;
this.team = teamDetailsService.getTeam();
this.owner = teamDetailsService.getOwner();
if (this.team) {
this.newName = angular.copy(this.team.name);
if (this.owner) {
this.fetchUserPermissions();
} else {
teamDetailsService.fetchOwnerByTeamName(this.teamName).then((owner: any) => {
this.owner = owner;
}, (error: any) => {
this.isLoading = false;
cheNotification.showError(error && error.data && error.data.message !== null ? error.data.message : 'Failed to find team owner.');
}).finally(() => {
this.fetchUserPermissions();
});
}
}
}
/**
* 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.isDefined(tabIndex)) {
param.tab = Tab[tabIndex];
}
if (angular.isUndefined(this.$location.search().tab)) {
this.$location.replace().search(param);
} else {
this.$location.search(param);
}
}
/**
* Fetches permission of user in current team.
*/
fetchUserPermissions(): void {
this.chePermissions.fetchOrganizationPermissions(this.team.id).then(() => {
this.allowedUserActions = this.processUserPermissions();
this.hasTeamAccess = this.allowedUserActions.length > 0;
this.fetchLimits();
}, (error: any) => {
this.isLoading = false;
if (error.status === 304) {
this.allowedUserActions = this.processUserPermissions();
this.fetchLimits();
} else if (error.status === 403) {
this.allowedUserActions = [];
this.hasTeamAccess = false;
}
this.isLoading = false;
});
}
/**
* Process permissions to retrieve current user actions.
*
* @returns {Array} current user allowed actions
*/
processUserPermissions(): Array<string> {
let userId = this.cheUser.getUser().id;
let permissions = this.chePermissions.getOrganizationPermissions(this.team.id);
let userPermissions = this.lodash.find(permissions, (permission: any) => {
return permission.userId === userId;
});
return userPermissions ? userPermissions.actions : [];
}
/**
* Checks whether user is allowed to perform pointed action.
*
* @param value action
* @returns {boolean} <code>true</code> if allowed
*/
isUserAllowedTo(value: string): boolean {
return this.allowedUserActions ? this.allowedUserActions.indexOf(value) >= 0 : false;
}
/**
* Returns whether current user can change team resource limits.
*
* @returns {boolean} <code>true</code> if can change resource limits
*/
canChangeResourceLimits(): boolean {
return (this.cheTeam.getPersonalAccount() && this.team) ? this.cheTeam.getPersonalAccount().id === this.team.parent : false;
}
/**
* Returns whether current user can leave team (owner of the team is not allowed to leave it).
*
* @returns {boolean} <code>true</code> if can leave team
*/
canLeaveTeam(): boolean {
return (this.cheTeam.getPersonalAccount() && this.team) ? this.cheTeam.getPersonalAccount().id !== this.team.parent : false;
}
/**
* Fetches defined team's limits (workspace, runtime, RAM caps, etc).
*/
fetchLimits(): void {
this.isLoading = true;
this.cheResourcesDistribution.fetchOrganizationResources(this.team.id).then(() => {
this.isLoading = false;
this.processResources();
}, (error: any) => {
this.isLoading = false;
if (!error) {
return;
}
if (error.status === 304) {
this.processResources();
} else if (error.status === 404) {
this.limits = {};
this.limitsCopy = angular.copy(this.limits);
}
});
}
/**
* Process resources limits.
*/
processResources(): void {
let ramLimit = this.cheResourcesDistribution.getOrganizationResourceByType(this.team.id, this.resourceLimits.RAM);
let workspaceLimit = this.cheResourcesDistribution.getOrganizationResourceByType(this.team.id, this.resourceLimits.WORKSPACE);
let runtimeLimit = this.cheResourcesDistribution.getOrganizationResourceByType(this.team.id, this.resourceLimits.RUNTIME);
this.limits = {};
this.limits.workspaceCap = workspaceLimit ? workspaceLimit.amount : undefined;
this.limits.runtimeCap = runtimeLimit ? runtimeLimit.amount : undefined;
this.limits.ramCap = ramLimit ? ramLimit.amount / 1024 : undefined;
this.limitsCopy = angular.copy(this.limits);
}
/**
* Confirms and performs team's deletion.
*
* @param event
*/
deleteTeam(event: MouseEvent): void {
let promise = this.confirmDialogService.showConfirmDialog('Delete team',
'Would you like to delete team \'' + this.team.name + '\'?', 'Delete');
promise.then(() => {
let promise = this.cheTeam.deleteTeam(this.team.id);
promise.then(() => {
this.$location.path('/');
this.cheTeam.fetchTeams();
}, (error: any) => {
this.cheNotification.showError(error.data.message !== null ? error.data.message : 'Team deletion failed.');
});
});
}
/**
* Confirms and performs removing user from current team.
*
*/
leaveTeam(): void {
let promise = this.confirmDialogService.showConfirmDialog('Leave team',
'Would you like to leave team \'' + this.team.name + '\'?', 'Leave');
promise.then(() => {
let promise = this.chePermissions.removeOrganizationPermissions(this.team.id, this.cheUser.getUser().id);
promise.then(() => {
this.$location.path('/');
this.cheTeam.fetchTeams();
}, (error: any) => {
this.cheNotification.showError(error.data.message !== null ? error.data.message : 'Leave team failed.');
});
});
}
/**
* Update team's details.
*
*/
updateTeamName(): void {
if (this.newName && this.team && this.newName !== this.team.name) {
this.team.name = this.newName;
this.cheTeam.updateTeam(this.team).then((team: any) => {
this.cheTeam.fetchTeams().then(() => {
this.$location.path('/team/' + team.qualifiedName);
});
}, (error: any) => {
this.cheNotification.showError((error.data && error.data.message !== null) ? error.data.message : 'Rename team failed.');
});
}
}
/**
* Update resource limits.
*
*/
updateLimits(): void {
if (!this.team || !this.limits || angular.equals(this.limits, this.limitsCopy)) {
return;
}
let resources = angular.copy(this.cheResourcesDistribution.getOrganizationResources(this.team.id));
let resourcesToRemove = [this.resourceLimits.TIMEOUT];
if (this.limits.ramCap !== null && this.limits.ramCap !== undefined) {
resources = this.cheResourcesDistribution.setOrganizationResourceLimitByType(resources, this.resourceLimits.RAM, (this.limits.ramCap * 1024).toString());
} else {
resourcesToRemove.push(this.resourceLimits.RAM);
}
if (this.limits.workspaceCap !== null && this.limits.workspaceCap !== undefined) {
resources = this.cheResourcesDistribution.setOrganizationResourceLimitByType(resources, this.resourceLimits.WORKSPACE, this.limits.workspaceCap);
} else {
resourcesToRemove.push(this.resourceLimits.WORKSPACE);
}
if (this.limits.runtimeCap !== null && this.limits.runtimeCap !== undefined) {
resources = this.cheResourcesDistribution.setOrganizationResourceLimitByType(resources, this.resourceLimits.RUNTIME, this.limits.runtimeCap);
} else {
resourcesToRemove.push(this.resourceLimits.RUNTIME);
}
// if the timeout resource will be send in this case - it will set the timeout for the current team, and the updating timeout of
// parent team will not affect the current team, so to avoid this - remove timeout resource if present:
this.lodash.remove(resources, (resource: any) => {
return resourcesToRemove.indexOf(resource.type) >= 0;
});
this.isLoading = true;
this.cheResourcesDistribution.distributeResources(this.team.id, resources).then(() => {
this.fetchLimits();
}, (error: any) => {
let errorMessage = 'Failed to set update team CAPs.';
this.cheNotification.showError((error.data && error.data.message !== null) ? errorMessage + '</br>Reason: ' + error.data.message : errorMessage);
this.fetchLimits();
});
}
/**
* Returns whether save button is disabled.
*
* @return {boolean}
*/
isSaveButtonDisabled(): boolean {
return this.teamForm && this.teamForm.$invalid;
}
/**
* Returns true if "Save" button should be visible
*
* @return {boolean}
*/
isSaveButtonVisible(): boolean {
return (this.selectedTabIndex === Tab.Settings && !this.isLoading) && (!angular.equals(this.team.name, this.newName) ||
!angular.equals(this.limits, this.limitsCopy));
}
updateTeam(): void {
this.updateTeamName();
this.updateLimits();
}
cancelChanges(): void {
this.newName = angular.copy(this.team.name);
this.limits = angular.copy(this.limitsCopy);
}
}

View File

@ -0,0 +1,179 @@
<che-toolbar che-title="{{teamDetailsController.teamName}}" ng-if="teamDetailsController.team">
<div class="save-button-placeholder">
<che-button-save-flat ng-show="teamDetailsController.isSaveButtonVisible()"
ng-disabled="teamDetailsController.isSaveButtonDisabled()"
che-button-title="Save" name="saveButton"
ng-click="teamDetailsController.updateTeam()"></che-button-save-flat>
</div>
</che-toolbar>
<md-content md-scroll-y flex md-theme="default"
ng-if="teamDetailsController.team && teamDetailsController.hasTeamAccess">
<md-tabs md-dynamic-height md-stretch-tabs="auto"
md-selected="teamDetailsController.selectedTabIndex"
md-center-tabs="">
<!-- Settings Tab -->
<md-tab md-on-select="teamDetailsController.onSelectTab(teamDetailsController.tab.Settings);">
<md-tab-label>
<md-icon md-font-icon="material-design icon-ic_settings_24px" class="che-tab-label-icon"></md-icon>
<span class="che-tab-label-title">Settings</span>
</md-tab-label>
<md-tab-body>
<div class="team-details-progress">
<md-progress-linear md-mode="indeterminate" ng-show="teamDetailsController.isLoading"></md-progress-linear>
</div>
<div flex layout="column" class="team-details-content">
<ng-form name="teamDetailsController.teamForm">
<!-- Name -->
<che-input-box che-form="teamDetailsController.teamForm"
che-label-name="Name"
che-name="name"
aria-label="Name of the team"
che-place-holder="Name of the team"
ng-model="teamDetailsController.newName"
che-readonly="!teamDetailsController.isUserAllowedTo('update')"
unique-team-name="teamDetailsController.team.name"
parent-account="teamDetailsController.owner.name"
required
ng-maxlength="20"
ng-pattern="/^[a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,38}$/i">
<div ng-message="pattern">The name can contain alphanumeric characters or single '-' inside.</div>
<div ng-message="maxlength">The name has to be less than 20 characters long.</div>
<div ng-message="uniqueTeamName">This team name is already used.</div>
</che-input-box>
<!-- Owner -->
<list-team-owners ng-if="teamDetailsController.owner"></list-team-owners>
<!-- Workspace cap -->
<che-input-box che-label-name="Workspace Cap"
che-label-description="Maximum number of workspaces for the team."
che-name="workspaceCap"
che-form="teamDetailsController.teamForm"
aria-label="workspace cap"
che-place-holder="Total number of workspaces has not been limited"
ng-model="teamDetailsController.limits.workspaceCap"
che-readonly="!teamDetailsController.canChangeResourceLimits()"
type="number"
che-type-number
min="0"
max="1000">
<div ng-message="min">A workspace cap should be greater than 0.</div>
</che-input-box>
<!-- Running workspace cap -->
<che-input-box che-label-name="Running Workspace Cap"
che-label-description="Maximum number of running workspaces for each team."
che-name="runtimeCap"
che-form="teamDetailsController.teamForm"
aria-label="runtime cap"
che-place-holder="Number of running workspaces has not been limited."
ng-model="teamDetailsController.limits.runtimeCap"
che-readonly="!teamDetailsController.canChangeResourceLimits()"
type="number"
che-type-number
min="0"
max="1000">
<div ng-message="min">A running workspace cap should be greater than 0.</div>
</che-input-box>
<!-- Workspace RAM cap -->
<che-input-box che-label-name="Workspace RAM Cap"
che-label-description="Maximum RAM team workspaces can use."
che-name="workspaceRamCap"
che-form="teamDetailsController.teamForm"
aria-label="runtime cap"
che-place-holder="Workspace RAM has not been limited."
ng-model="teamDetailsController.limits.ramCap"
che-readonly="!teamDetailsController.canChangeResourceLimits()"
type="number"
che-type-number
min="0"
max="1000">
<div ng-message="min">A workspace RAM cap should be greater than 0.</div>
</che-input-box>
<che-label-container class="team-details-delete-label"
ng-if="teamDetailsController.canLeaveTeam()"
che-label-name="Leave Team"
che-label-description="This is irreversible. By leaving a team, you will not have access to the team workspaces anymore.">
<che-button-danger che-button-title="Leave"
ng-click="teamDetailsController.leaveTeam()"></che-button-danger>
</che-label-container>
<che-label-container class="team-details-delete-label"
ng-if="teamDetailsController.isUserAllowedTo('delete')"
che-label-name="Delete Team"
che-label-description="This is irreversible. Deleting your team will also destroy team workspaces and stacks.">
<che-button-danger che-button-title="Delete"
ng-click="teamDetailsController.deleteTeam($event)"></che-button-danger>
</che-label-container>
</ng-form>
</div>
</md-tab-body>
</md-tab>
<!-- Developers Tab -->
<md-tab md-on-select="teamDetailsController.onSelectTab(teamDetailsController.tab.Members);">
<md-tab-label>
<md-icon md-font-icon="fa-group" class="fa che-tab-label-icon"></md-icon>
<span class="che-tab-label-title">Members</span>
</md-tab-label>
<md-tab-body>
<list-team-members ng-if="teamDetailsController.owner && (!teamDetailsController.isLoading && teamDetailsController.hasTeamAccess)"
editable="teamDetailsController.isUserAllowedTo('setPermissions')"></list-team-members>
</md-tab-body>
</md-tab>
<!-- Templates Tab -->
<!-- <md-tab>
<md-tab-label>
<md-icon md-font-icon="fa-briefcase" class="fa che-tab-label-icon"></md-icon>
<span class="che-tab-label-title">Templates</span>
</md-tab-label>
<md-tab-body>
</md-tab-body>
</md-tab>-->
<!-- Stacks Tab -->
<!--<md-tab>
<md-tab-label>
<md-icon md-font-icon="fa-archive" class="fa che-tab-label-icon"></md-icon>
<span class="che-tab-label-title">Stacks</span>
</md-tab-label>
<md-tab-body>
</md-tab-body>
</md-tab>-->
<!-- Workspaces Tab -->
<md-tab md-on-select="teamDetailsController.onSelectTab(teamDetailsController.tab.Workspaces);">
<md-tab-label>
<md-icon md-font-icon="chefont cheico-workspace" class="che-tab-label-icon"></md-icon>
<span class="che-tab-label-title">Workspaces</span>
</md-tab-label>
<md-tab-body>
<list-team-workspaces ng-if="!teamDetailsController.isLoading && teamDetailsController.hasTeamAccess"></list-team-workspaces>
</md-tab-body>
</md-tab>
</md-tabs>
</md-content>
<md-content ng-if="!teamDetailsController.team" flex class="team-error-content" layout="column" layout-align="center center">
<div class="error-title">Team <b>{{teamDetailsController.teamName}}</b> not found.</div>
<span class="fa fa-group error-image"></span>
<div class="error-description">
The team could be deleted by its owner or you do not have permissions to access the team.
</div>
</md-content>
<md-content ng-if="teamDetailsController.team && !teamDetailsController.hasTeamAccess" flex class="team-error-content" layout="column" layout-align="center center">
<div class="error-title">Access to team <b>{{teamDetailsController.teamName}}</b> denied.</div>
<span class="fa fa-group error-image"></span>
<div class="error-description">
You do not have permissions to access the team.
</div>
</md-content>
<workspace-edit-mode-overlay ng-if="teamDetailsController.isSaveButtonVisible()"
workspace-edit-disable-save-button="teamDetailsController.isSaveButtonDisabled()"
workspace-edit-mode-on-save="teamDetailsController.updateTeam()"
workspace-edit-mode-on-cancel="teamDetailsController.cancelChanges()"></workspace-edit-mode-overlay>

View File

@ -0,0 +1,117 @@
/*
* 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';
/**
* This class is fetch and handling the data for team details
*
* @author Oleksii Orel
*/
export class TeamDetailsService {
/**
* Promises service.
*/
private $q: ng.IQService;
/**
* Team API interaction.
*/
private cheTeam: che.api.ICheTeam;
/**
* User API interaction.
*/
private cheUser: any;
/**
* Route service.
*/
private $route: ng.route.IRouteService;
/**
* Current team (comes from directive's scope).
*/
private team: any;
/**
* Current team's owner (comes from directive's scope).
*/
private owner: any;
/**
* @ngInject for Dependency injection
*/
constructor($q: ng.IQService, cheUser: any, cheTeam: che.api.ICheTeam, $route: ng.route.IRouteService) {
this.$q = $q;
this.cheTeam = cheTeam;
this.cheUser = cheUser;
this.$route = $route;
}
/**
* Fetches the team's details by it's name.
* @param teamName {string}
*
* @return {ng.IPromise<any>}
*/
fetchTeamDetailsByName(teamName: string): ng.IPromise<any> {
if (!teamName) {
return;
}
let deferred = this.$q.defer();
this.cheTeam.fetchTeamByName(teamName).then((team: any) => {
this.team = team;
deferred.resolve(team);
}, (error: any) => {
this.team = null;
deferred.reject(error);
});
return deferred.promise;
}
/**
* Fetches the team's owner by team's name.
* @param teamName {string}
*
* @return {ng.IPromise<any>}
*/
fetchOwnerByTeamName(teamName: string): ng.IPromise<any> {
let deferred = this.$q.defer();
let parts = teamName.split('/');
let accountName = (parts && parts.length > 0) ? parts[0] : '';
this.cheUser.fetchUserByName(accountName).then((owner: any) => {
this.owner = owner;
deferred.resolve(owner);
}, (error: any) => {
this.owner = null;
deferred.reject(error);
});
return deferred.promise;
}
/**
* Gets the team.
*
* @return {any}
*/
getTeam(): any {
return this.team;
}
/**
* Gets the owner.
*
* @return {any}
*/
getOwner(): any {
return this.owner;
}
}

View File

@ -0,0 +1,45 @@
.team-details-content
padding 0 14px
.che-input-box
padding 25px 0
border-bottom 1px solid $list-separator-color
.team-ram-cap .che-input-box-desktop-value-column:after
color $label-secondary-color
content "GB"
position absolute
right 7px
top 0
line-height 40px
.team-details-delete-label label
color $che-delete-label-color !important
.che-label-container-content button
margin-left 0px
margin-top 0px
.team-details-progress
height 5px
.team-error-content
height 100%
width 100%
color $label-secondary-color
.error-title
font-size 12pt
font-weight bold
.error-image
font-size 56pt
margin 25px 0px
color $label-info-color
.error-description
font-size 11pt
padding 0px 20px
.save-button-placeholder
width 89px

View File

@ -0,0 +1,534 @@
/*
* 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 {TeamDetailsService} from '../team-details.service';
/**
* @ngdoc controller
* @name teams.members:ListTeamMembersController
* @description This class is handling the controller for the list of team's members.
* @author Ann Shumilova
*/
export class ListTeamMembersController {
/**
* Location service.
*/
$location: ng.ILocationService;
/**
* Team API interaction.
*/
private cheTeam: che.api.ICheTeam;
/**
* Invite API interaction.
*/
private cheInvite: che.api.ICheInvite;
/**
* User API interaction.
*/
private cheUser: any;
/**
* User profile API interaction.
*/
private cheProfile: any;
/**
* Permissions API interaction.
*/
private chePermissions: che.api.IChePermissions;
/**
* Service for displaying dialogs.
*/
private $mdDialog: angular.material.IDialogService;
/**
* Notifications service.
*/
private cheNotification: any;
/**
* Confirm dialog service.
*/
private confirmDialogService: any;
/**
* Promises service.
*/
private $q: ng.IQService;
/**
* Lodash library.
*/
private lodash: any;
/**
* Team's members list.
*/
private members: Array<any>;
/**
* Loading state of the page.
*/
private isLoading: boolean;
/**
* Filter for members list.
*/
private memberFilter: any;
/**
* Current team (comes from directive's scope).
*/
private team: any;
/**
* Current team's owner (comes from directive's scope).
*/
private owner: any;
/**
* The editable (whether current user can edit members list and see invitations) state of the members (comes from outside).
*/
private editable: any;
/**
* Selection and filtration helper
*/
cheListHelper: che.widget.ICheListHelper;
/**
* Default constructor that is using resource
* @ngInject for Dependency injection
*/
constructor(cheTeam: che.api.ICheTeam, chePermissions: che.api.IChePermissions, cheInvite: che.api.ICheInvite, cheUser: any, cheProfile: any,
confirmDialogService: any, $mdDialog: angular.material.IDialogService, $q: ng.IQService, cheNotification: any,
lodash: any, $location: ng.ILocationService, teamDetailsService: TeamDetailsService,
$scope: ng.IScope, cheListHelperFactory: che.widget.ICheListHelperFactory) {
this.cheTeam = cheTeam;
this.cheInvite = cheInvite;
this.chePermissions = chePermissions;
this.cheProfile = cheProfile;
this.cheUser = cheUser;
this.$mdDialog = $mdDialog;
this.$q = $q;
this.$location = $location;
this.lodash = lodash;
this.cheNotification = cheNotification;
this.confirmDialogService = confirmDialogService;
this.members = [];
this.isLoading = true;
this.memberFilter = {name: ''};
const helperId = 'list-team-members';
this.cheListHelper = cheListHelperFactory.getHelper(helperId);
$scope.$on('$destroy', () => {
cheListHelperFactory.removeHelper(helperId);
});
this.owner = teamDetailsService.getOwner();
this.team = teamDetailsService.getTeam();
this.refreshData(true, true);
}
/**
* Callback when name is changed.
*
* @param str {string} a string to filter team members.
*/
onSearchChanged(str: string): void {
this.memberFilter.name = str;
this.cheListHelper.applyFilter('name', this.memberFilter);
}
/**
* Refreshes both list of members and invitations based on provided parameters.
*
* @param fetchMembers if <code>true</code> need to refresh members
* @param fetchInvitations if <code>true</code> need to refresh invitations
* @return {IPromise<any>}
*/
refreshData(fetchMembers: boolean, fetchInvitations: boolean): ng.IPromise<any> {
this.members = [];
if (!this.team || !this.owner) {
return;
}
const promises: Array<ng.IPromise<any>> = [];
if (fetchMembers) {
promises.push(this.fetchMembers());
} else {
this.formUserList();
}
// can fetch invites only admin or owner of the team:
if (this.editable) {
if (fetchInvitations) {
promises.push(this.fetchInvitations());
} else {
this.formInvitationList();
}
}
return this.$q.all(promises).finally(() => {
const isMemberSelectable = (member: che.IMember) => {
return !this.memberIsOwner(member);
};
this.cheListHelper.setList(this.members, 'userId', isMemberSelectable);
});
}
/**
* Returns <code>true</code> if specified member is team owner.
*
* @param {che.IMember> member a team member
* @return {boolean}
*/
memberIsOwner(member: che.IMember): boolean {
return member.userId === this.owner.id;
}
/**
* Fetches the list of team members.
*
* @return {IPromise<any>}
*/
fetchMembers(): ng.IPromise<any> {
return this.chePermissions.fetchOrganizationPermissions(this.team.id).then(() => {
this.isLoading = false;
return this.formUserList();
}, (error: any) => {
this.isLoading = false;
if (error && error.status !== 304) {
this.cheNotification.showError(error.data && error.data.message ? error.data.message : 'Failed to retrieve team permissions.');
return this.$q.reject(error);
} else {
return this.formUserList();
}
});
}
/**
* Combines permissions and users data in one list.
*
* @return {ng.IPromise<any>}
*/
formUserList(): ng.IPromise<any> {
let noOwnerPermissions = true;
const promises: Array<ng.IPromise<any>> = [];
const permissions = this.chePermissions.getOrganizationPermissions(this.team.id);
permissions.forEach((permission: any) => {
const userId = permission.userId;
const user = this.cheProfile.getProfileById(userId);
if (userId === this.owner.id) {
noOwnerPermissions = false;
}
if (user) {
this.formUserItem(user, permission);
} else {
const promise = this.cheProfile.fetchProfileById(userId).then(() => {
this.formUserItem(this.cheProfile.getProfileById(userId), permission);
});
promises.push(promise);
}
});
if (noOwnerPermissions) {
const user = this.cheProfile.getProfileById(this.owner.id);
if (user) {
this.formUserItem(user, null);
} else {
const promise = this.cheProfile.fetchProfileById(this.owner.id).then(() => {
this.formUserItem(this.cheProfile.getProfileById(this.owner.id), null);
});
promises.push(promise);
}
}
return this.$q.all(promises);
}
/**
* Forms item to display with permissions and user data.
*
* @param user user data
* @param permissions permissions data
*/
formUserItem(user: any, permissions: any): void {
user.name = this.cheProfile.getFullName(user.attributes);
let userItem = angular.copy(user);
userItem.permissions = permissions;
this.members.push(userItem);
}
/**
* Fetches the list of team's invitations.
*
* @return {IPromise<any>}
*/
fetchInvitations(): ng.IPromise<any> {
return this.cheInvite.fetchTeamInvitations(this.team.id).then((data: any) => {
this.isLoading = false;
this.formInvitationList();
}, (error: any) => {
this.isLoading = false;
this.cheNotification.showError(error.data && error.data.message ? error.data.message : 'Failed to retrieve team invitations.');
});
}
/**
* Prepares invitations list to be displayed.
*/
formInvitationList(): void {
let invites = this.cheInvite.getTeamInvitations(this.team.id);
invites.forEach((invite: any) => {
let user = {userId: invite.email, name: 'Pending invitation', email: invite.email, permissions: invite, isPending: true};
this.members.push(user);
});
}
/**
* Shows dialog for adding new member to the team.
*/
showMemberDialog(member: any): void {
this.$mdDialog.show({
controller: 'MemberDialogController',
controllerAs: 'memberDialogController',
bindToController: true,
clickOutsideToClose: true,
locals: {
members: this.members,
callbackController: this,
member: member
},
templateUrl: 'app/teams/member-dialog/member-dialog.html'
});
}
/**
* Add new members to the team.
*
* @param members members to be added
* @param roles member roles
*/
addMembers(members: Array<any>, roles: Array<any>): void {
let promises = [];
let unregistered = [];
let isInvite = false;
let isAddMember = false;
members.forEach((member: any) => {
let actions = this.cheTeam.getActionsFromRoles(roles);
if (member.id) {
isAddMember = true;
let permissions = {
instanceId: this.team.id,
userId: member.id,
domainId: 'organization',
actions: actions
};
let promise = this.chePermissions.storePermissions(permissions).catch((error: any) => {
this.cheNotification.showError(error.data && error.data.message ? error.data.message : 'Add member to team failed.');
});
promises.push(promise);
} else {
isInvite = true;
let promise = this.cheInvite.inviteToTeam(this.team.id, member.email, actions);
promises.push(promise);
unregistered.push(member.email);
}
});
this.isLoading = true;
this.$q.all(promises).then(() => {
return this.refreshData(isAddMember, isInvite);
}).finally(() => {
this.isLoading = false;
if (unregistered.length > 0) {
this.cheNotification.showInfo('User' + (unregistered.length > 1 ? 's ' : ' ') + unregistered.join(', ')
+ (unregistered.length > 1 ? ' are' : ' is') + ' not registered in the system. The email invitations were sent.');
}
});
}
/**
* Perform edit member permissions.
*
* @param member
*/
editMember(member: any): void {
this.showMemberDialog(member);
}
/**
* Performs member's permissions update.
*
* @param member member to update permissions
*/
updateMember(member: any): void {
if (member.isPending) {
if (member.permissions.actions.length > 0) {
this.updateInvitation(member.permissions);
} else {
this.deleteInvitation(member);
}
} else {
if (member.permissions.actions.length > 0) {
this.storePermissions(member.permissions);
} else {
this.removePermissions(member);
}
}
}
/**
* Stores provided permissions.
*
* @param permissions
*/
storePermissions(permissions: any): void {
this.isLoading = true;
this.chePermissions.storePermissions(permissions).then(() => {
this.refreshData(true, false);
}, (error: any) => {
this.isLoading = false;
this.cheNotification.showError(error.data && error.data.message ? error.data.message : 'Set user permissions failed.');
});
}
/**
* Updates the team's invitaion.
*
* @param member member's invitation to be updated
*/
updateInvitation(member: any): void {
this.cheInvite.inviteToTeam(this.team.id, member.email, member.actions);
}
/**
* Deletes send invitation to the team.
*
* @param member member to delete invitation
*/
deleteInvitation(member: any): void {
this.isLoading = true;
this.cheInvite.deleteTeamInvitation(this.team.id, member.email).then(() => {
this.refreshData(false, true);
}, (error: any) => {
this.isLoading = false;
this.cheNotification.showError(error.data && error.data.message ? error.data.message : 'Failed to remove invite send to ' + member.email + '.');
});
}
/**
* Remove all selected members.
*/
removeSelectedMembers(): void {
const selectedMembers = this.cheListHelper.getSelectedItems();
if (!selectedMembers.length) {
this.cheNotification.showError('No such members.');
return;
}
const confirmationPromise = this.showDeleteMembersConfirmation(selectedMembers.length);
confirmationPromise.then(() => {
const removeMembersPromises = [];
let removalError;
let deleteInvite = false;
let deleteMember = false;
let deleteCurrentUser = false;
selectedMembers.forEach((member: che.IMember) => {
this.cheListHelper.itemsSelectionStatus[member.userId] = false;
if (member && member.isPending) {
deleteInvite = true;
const promise = this.cheInvite.deleteTeamInvitation(this.team.id, member.email);
removeMembersPromises.push(promise);
return;
}
deleteMember = true;
if (member.userId === this.cheUser.getUser().id) {
deleteCurrentUser = true;
}
const promise = this.chePermissions.removeOrganizationPermissions(this.team.id, member.userId).catch((error: any) => {
removalError = error;
});
removeMembersPromises.push(promise);
});
this.$q.all(removeMembersPromises).finally(() => {
if (deleteCurrentUser) {
this.processCurrentUserRemoval();
} else {
this.refreshData(deleteMember, deleteInvite);
}
if (removalError) {
this.cheNotification.showError(removalError.data && removalError.data.message ? removalError.data.message : 'User removal failed.');
}
});
});
}
/**
* Finds member by it's id.
*
* @param id
* @returns {any}
*/
getMemberById(id: string): any {
return this.lodash.find(this.members, (member: any) => {
return member.userId === id;
});
}
/**
* Process the removal of current user from team.
*/
processCurrentUserRemoval(): void {
this.$location.path('/workspaces');
this.cheTeam.fetchTeams();
}
/**
* Removes user permissions for current team
*
* @param user user
*/
removePermissions(user: any) {
this.isLoading = true;
this.chePermissions.removeOrganizationPermissions(user.permissions.instanceId, user.userId).then(() => {
if (user.userId === this.cheUser.getUser().id) {
this.processCurrentUserRemoval();
} else {
this.refreshData(true, false);
}
}, (error: any) => {
this.isLoading = false;
this.cheNotification.showError(error.data && error.data.message ? error.data.message : 'Failed to remove user ' + user.email + ' permissions.');
});
}
/**
* Show confirmation popup before members removal
* @param numberToDelete
* @returns {*}
*/
showDeleteMembersConfirmation(numberToDelete: number): any {
let confirmTitle = 'Would you like to remove ';
if (numberToDelete > 1) {
confirmTitle += 'these ' + numberToDelete + ' members?';
} else {
confirmTitle += 'the selected member?';
}
return this.confirmDialogService.showConfirmDialog('Remove members', confirmTitle, 'Delete');
}
}

View File

@ -0,0 +1,39 @@
/*
* 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';
/**
* @ngdoc directive
* @name teams.members:ListTeamMembers
* @restrict E
* @element
*
* @description
* `<list-team-members editable="ctrl.editable"></list-team-members>` for displaying list of members
*
* @usage
* <list-team-members editable="ctrl.editable"></list-team-members>
*
* @author Ann Shumilova
*/
export class ListTeamMembers implements ng.IDirective {
restrict: string = 'E';
templateUrl: string = 'app/teams/team-details/team-members/list-team-members.html';
controller: string = 'ListTeamMembersController';
controllerAs: string = 'listTeamMembersController';
bindToController: boolean = true;
scope: any = {
editable: '='
};
}

View File

@ -0,0 +1,65 @@
<div class="create-team-progress">
<md-progress-linear md-mode="indeterminate" ng-show="listTeamMembersController.isLoading"></md-progress-linear>
</div>
<md-content flex>
<che-list-header che-input-placeholder="Search"
che-search-model="listTeamMembersController.memberFilter.name"
che-on-search-change="listTeamMembersController.onSearchChanged(str)"
che-hide-search="listTeamMembersController.members.length === 0"
che-add-button-title="Add Member"
che-on-add="listTeamMembersController.showMemberDialog(null)"
che-hide-add="!listTeamMembersController.editable"
che-delete-button-title="Delete"
che-on-delete="listTeamMembersController.removeSelectedMembers()"
che-hide-delete="listTeamMembersController.cheListHelper.isNoItemSelected"
che-hide-header="listTeamMembersController.cheListHelper.visibleItemsNumber === 0">
<div flex="100"
layout="row"
layout-align="start stretch"
class="che-list-item-row">
<div layout="column" layout-gt-xs="row" ng-if="listTeamMembersController.editable"
layout-align="start center"
class="che-checkbox-area">
<div layout="row" layout-align="center center" class="che-list-item-checkbox-main">
<md-checkbox class="che-list-item-checkbox"
aria-label="Member list"
ng-checked="listTeamMembersController.cheListHelper.areAllItemsSelected"
ng-click="listTeamMembersController.cheListHelper.changeBulkSelection()"></md-checkbox>
</div>
</div>
<div flex hide-xs layout-gt-xs="row"
layout-align="start center"
class="che-list-item-details">
<che-list-header-column flex-gt-xs="25"
che-sort-value='listTeamMembersController.memberOrderBy'
che-sort-item='email'
che-column-title='Email'></che-list-header-column>
<che-list-header-column flex-gt-xs="25"
che-sort-value='listTeamMembersController.memberOrderBy'
che-sort-item='name'
che-column-title='Info'></che-list-header-column>
<che-list-header-column flex-gt-xs="35"
che-sort-value='listTeamMembersController.memberOrderBy'
che-column-title='Roles'></che-list-header-column>
<!-- <che-list-header-column flex-gt-xs="10"
che-sort-value='listTeamMembersController.memberOrderBy'
che-column-title='State'></che-list-header-column>-->
<che-list-header-column flex-gt-xs="15"
che-column-title='Actions'></che-list-header-column>
</div>
</div>
</che-list-header>
<che-list ng-show="listTeamMembersController.cheListHelper.visibleItemsNumber > 0">
<member-item ng-repeat="member in listTeamMembersController.cheListHelper.getVisibleItems() | orderBy:[listTeamMembersController.memberOrderBy, 'name']"
callback="listTeamMembersController"
editable="listTeamMembersController.editable"
is-owner="listTeamMembersController.memberIsOwner(member)"
member="member"></member-item>
</che-list>
<div class="che-list-empty">
<span ng-show="listTeamMembersController.members.length > 0 && listTeamMembersController.cheListHelper.visibleItemsNumber === 0">
No members found.
</span>
<span ng-show="listTeamMembersController.members.length === 0">There are no members.</span>
</div>
</md-content>

View File

@ -0,0 +1,2 @@
.create-team-progress
height 5px

View File

@ -0,0 +1,112 @@
/*
* 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';
/**
* Controller for team member item..
*
* @author Ann Shumilova
*/
export class MemberItemController {
/**
* Team API interaction.
*/
private cheTeam: che.api.ICheTeam;
/**
* Service for displaying dialogs.
*/
private $mdDialog: angular.material.IDialogService;
/**
* Controller for handling callback events. (Comes from directive's scope).
*/
private callback: any;
/**
* Member to be displayed. (Comes from directive's scope).
*/
private member: any;
/**
* Whether current member is owner of the team. (Comes from directive's scope).
*/
private isOwner: boolean;
/**
* Lodash library.
*/
private lodash: any;
/**
* Actions that are not part of any role.
*/
private otherActions: Array<string>;
/**
* Confirm dialog service.
*/
private confirmDialogService: any;
/**
* Default constructor that is using resource injection
* @ngInject for Dependency injection
*/
constructor($mdDialog: angular.material.IDialogService, cheTeam: che.api.ICheTeam, lodash: any, confirmDialogService: any) {
this.$mdDialog = $mdDialog;
this.cheTeam = cheTeam;
this.lodash = lodash;
this.confirmDialogService = confirmDialogService;
this.otherActions = [];
}
/**
* Call user permissions removal. Show the dialog
* @param event - the $event
*/
removeMember(event: MouseEvent): void {
let promise = this.confirmDialogService.showConfirmDialog('Remove member', 'Would you like to remove member ' + this.member.email + ' ?', 'Delete');
promise.then(() => {
if (this.member.isPending) {
this.callback.deleteInvitation(this.member);
} else {
this.callback.removePermissions(this.member);
}
});
}
/**
* Handler edit member user's request.
*/
editMember(): void {
this.callback.editMember(this.member);
}
/**
* Returns string with member roles.
*
* @returns {string} string format of roles array
*/
getMemberRoles(): string {
if (this.isOwner) {
return 'Team Owner';
}
let roles = this.cheTeam.getRolesFromActions(this.member.permissions.actions);
let titles = [];
let processedActions = []
roles.forEach((role: any) => {
titles.push(role.title);
processedActions = processedActions.concat(role.actions);
});
this.otherActions = this.lodash.difference(this.member.permissions.actions, processedActions);
return titles.join(', ');
}
}

View File

@ -0,0 +1,39 @@
/*
* 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';
/**
* Defines a directive for user item in permissions list.
*
* @author Ann Shumilova
*/
export class MemberItem implements ng.IDirective {
restrict: string = 'E';
templateUrl: string = 'app/teams/team-details/team-members/member-item/member-item.html';
replace: boolean = false;
controller: string = 'MemberItemController';
controllerAs: string = 'memberItemController';
bindToController: boolean = true;
scope: any = {
member: '=member',
callback: '=callback',
hideDetails: '=hideDetails',
editable: '=editable',
isOwner: '=isOwner'
};
constructor() {
}
}

View File

@ -0,0 +1,63 @@
<che-list-item flex-gt-sm="100" flex="33" ng-mouseover="hover=true" ng-mouseout="hover=false">
<div flex="100"
layout="row"
layout-align="start stretch"
ng-class="{'member-bold': memberItemController.isOwner}"
class="che-list-item-row member-list-row">
<div layout="row"
layout-align="start center"
class="che-checkbox-area" ng-if="memberItemController.editable">
<che-list-item-checked ng-model="memberItemController.callback.cheListHelper.itemsSelectionStatus[memberItemController.member.userId]"
ng-click="memberItemController.callback.cheListHelper.updateBulkSelectionStatus()"
ng-show="!memberItemController.isOwner"
che-aria-label-checkbox="member {{memberItemController.member.userId}}"></che-list-item-checked>
</div>
<div flex
layout-xs="column" layout-gt-xs="row"
layout-align-gt-xs="start center"
layout-align-xs="start start"
class="che-list-item-details">
<div flex-gt-xs="{{memberItemController.hideDetails ? 60 : 25}}"
class="che-list-item-name">
<span class="che-xs-header noselect" hide-gt-xs>Email</span>
<span class="member-email che-hover">{{memberItemController.member.email}}</span>
</div>
<div flex-gt-xs="{{memberItemController.hideDetails ? 40 : 25}}"
class="che-list-item-secondary">
<span class="che-xs-header noselect" hide-gt-xs>Info</span>
<span class="member-email che-hover ">
<md-icon md-font-icon="fa-clock-o" class="fa member-pending-icon" ng-if="memberItemController.member.isPending"></md-icon>
{{memberItemController.member.name}}
</span>
</div>
<div flex-gt-xs="35" class="che-list-item-secondary" ng-if="!memberItemController.hideDetails">
<span class="che-xs-header noselect" hide-gt-xs>Roles</span>
<span class="member-list-permissions">{{memberItemController.getMemberRoles()}} </span>
<span uib-tooltip="{{memberItemController.otherActions.toString()}}" ng-if="memberItemController.otherActions.length > 0"> Other...</span>
</div>
<!-- <div flex-gt-xs="10"
class="che-list-item-secondary">
<span class="che-xs-header noselect" hide-gt-xs>State</span>
<span class="member-email che-hover ">member</span>
</div>-->
<div flex-gt-xs="15" ng-if="!memberItemController.hideDetails">
<span class="che-xs-header noselect" hide-gt-xs>Actions</span>
<span class="che-list-actions">
<a uib-tooltip="Edit developer permissions"
ng-click="memberItemController.editMember()"
ng-if="!memberItemController.isOwner"
ng-disabled="!memberItemController.editable">
<span class="fa fa-pencil"></span>
</a>
<a uib-tooltip="Remove developer"
ng-click="memberItemController.removeMember($event)"
ng-if="!memberItemController.isOwner"
ng-disabled="!memberItemController.editable">
<span class="fa fa-trash-o"></span>
</a>
</span>
</div>
</div>
</div>
</che-list-item>

View File

@ -0,0 +1,22 @@
.member-list-row
margin 0
.che-list-actions a[disabled]
pointer-events none
opacity 0.5
.member-list-permissions
overflow inherit !important
text-overflow ellipsis
white-space normal !important
.member-bold
font-weight bold
.member-pending-icon
height 16px
margin-right 5px
font-size 11pt
opacity 0.6
box-sizing content-box
padding 1px

View File

@ -0,0 +1,103 @@
/*
* 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 {TeamDetailsService} from '../team-details.service';
/**
* @ngdoc controller
* @name teams.members:ListTeamOwnersController
* @description This class is handling the controller for the list of team's owners.
* @author Ann Shumilova
*/
export class ListTeamOwnersController {
/**
* Team API interaction.
*/
private cheTeam: che.api.ICheTeam;
/**
* User API interaction.
*/
private cheUser: any;
/**
* User profile API interaction.
*/
private cheProfile: any;
/**
* Permissions API interaction.
*/
private chePermissions: che.api.IChePermissions;
/**
* Notifications service.
*/
private cheNotification: any;
/**
* Lodash library.
*/
private lodash: any;
/**
* Team's owners string.
*/
private owners: string;
/**
* Loading state of the page.
*/
private isLoading: boolean;
/**
* Current team's owner (comes from directive's scope).
*/
private owner: any;
/**
* Default constructor that is using resource
* @ngInject for Dependency injection
*/
constructor(cheTeam: che.api.ICheTeam, cheUser: any, chePermissions: che.api.IChePermissions, cheProfile: any, cheNotification: any,
lodash: any, teamDetailsService: TeamDetailsService) {
this.cheTeam = cheTeam;
this.cheUser = cheUser;
this.chePermissions = chePermissions;
this.cheProfile = cheProfile;
this.cheNotification = cheNotification;
this.lodash = lodash;
this.isLoading = true;
this.owner = teamDetailsService.getOwner();
this.processOwner();
}
/**
* Process owner.
*/
processOwner(): void {
if (!this.owner) {
return;
}
let profile = this.cheProfile.getProfileById(this.owner.id);
if (profile) {
this.formUserItem(profile);
} else {
this.cheProfile.fetchProfileById(this.owner.id).then(() => {
this.formUserItem(this.cheProfile.getProfileById(this.owner.id));
});
}
}
/**
* Forms item to display with permissions and user data.
*
* @param user user data
* @param permissions permissions data
*/
formUserItem(user: any): void {
let name = this.cheProfile.getFullName(user.attributes) + ' (' + user.email + ')';
this.owners = name;
}
}

View File

@ -0,0 +1,37 @@
/*
* 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';
/**
* @ngdoc directive
* @name teams.owners:ListTeamMembers
* @restrict E
* @element
*
* @description
* `<list-team-owners></list-team-owners>` for displaying list of owners
*
* @usage
* <list-team-owners></list-team-owners>
*
* @author Ann Shumilova
*/
export class ListTeamOwners implements ng.IDirective {
restrict: string = 'E';
templateUrl: string = 'app/teams/team-details/team-owners/list-team-owners.html';
controller: string = 'ListTeamOwnersController';
controllerAs: string = 'listTeamOwnersController';
bindToController: boolean = true;
scope: any = {};
}

Some files were not shown because too many files have changed in this diff Show More