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 assembly6.19.x
parent
de347bedae
commit
bd2ea09b1a
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
.account-profile
|
||||
padding 0 14px
|
||||
|
||||
.che-input-desktop
|
||||
margin-top -1px
|
||||
|
||||
.che-select
|
||||
margin-top -2px
|
||||
|
|
@ -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 + '.');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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);
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -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'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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
|
||||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()"> ({{navbarCtrl.getWorkspacesNumber()}})</span>
|
||||
<span class="navbar-number" ng-show="navbarController.getWorkspacesNumber()"> ({{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()"> ({{navbarCtrl.getFactoriesNumber()}})</span>
|
||||
<span class="navbar-number" ng-show="navbarController.getFactoriesNumber()"> ({{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()"> ({{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()"> ({{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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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
|
||||
|
|
@ -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');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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'
|
||||
};
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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
|
||||
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
|
@ -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'
|
||||
};
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
.cap-value
|
||||
color $primary-color
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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: '='
|
||||
};
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
|
@ -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: '='
|
||||
};
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
.organization-member-list *.che-list-item
|
||||
display block
|
||||
|
||||
.user-face
|
||||
che-developers-face()
|
||||
|
|
@ -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: '='
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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
|
||||
|
|
@ -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: '&'
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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%
|
||||
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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>
|
||||
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
|
@ -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;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
.organizations-content
|
||||
margin 0
|
||||
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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 () {
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
|
@ -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 () {
|
||||
//
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
.cap-value
|
||||
color $primary-color
|
||||
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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)
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
|
@ -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: '='
|
||||
};
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
.create-team-progress
|
||||
height 5px
|
||||
|
|
@ -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(', ');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -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() {
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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
Loading…
Reference in New Issue