Merge remote-tracking branch 'origin/che-multiuser' into spi-multiuser

# Conflicts:
#	assembly/assembly-wsmaster-war/src/main/java/org/eclipse/che/api/deploy/WsMasterModule.java
#	dockerfiles/init/manifests/che.env
#	plugins/plugin-docker/che-plugin-docker-machine/src/test/java/org/eclipse/che/plugin/docker/machine/MachineProviderImplTest.java
#	plugins/plugin-docker/pom.xml
#	plugins/plugin-github/che-plugin-github-pullrequest/src/main/java/org/eclipse/che/plugin/pullrequest/client/GitHubHostingService.java
#	pom.xml
#	wsagent/agent/src/main/java/org/eclipse/che/api/agent/WsAgentLauncher.java
#	wsagent/agent/src/test/java/org/eclipse/che/api/agent/WsAgentLauncherTest.java
#	wsmaster/che-core-api-machine/pom.xml
#	wsmaster/che-core-api-workspace/src/main/java/org/eclipse/che/api/workspace/server/model/impl/EnvironmentImpl.java
6.19.x
Sergii Leshchenko 2017-09-28 12:33:07 +03:00
commit 108b3d832b
675 changed files with 60325 additions and 526 deletions

View File

@ -0,0 +1,272 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (c) 2012-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
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>che-assembly-parent</artifactId>
<groupId>org.eclipse.che.assembly-multiuser</groupId>
<version>5.19.0-SNAPSHOT</version>
</parent>
<artifactId>assembly-ide-war</artifactId>
<packaging>war</packaging>
<name>Che IDE Assembly Multiuser :: Compiling GWT Application</name>
<properties>
<generated.sources.directory>${project.build.directory}/generated-sources/gen</generated.sources.directory>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.eclipse.che</groupId>
<artifactId>assembly-ide-war</artifactId>
<classifier>classes</classifier>
</dependency>
<dependency>
<groupId>org.eclipse.che</groupId>
<artifactId>assembly-ide-war</artifactId>
<type>war</type>
</dependency>
<dependency>
<groupId>org.eclipse.che.multiuser</groupId>
<artifactId>che-multiuser-keycloak-ide</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.multiuser</groupId>
<artifactId>che-multiuser-machine-authentication-ide</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<executions>
<execution>
<id>copy-web-resources</id>
<phase>process-sources</phase>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>analyze</id>
<configuration>
<skip>true</skip>
</configuration>
</execution>
</executions>
</plugin>
<!-- Source Generator invocation -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<executions>
<execution>
<id>gwt-xml</id>
<phase>generate-sources</phase>
<goals>
<goal>java</goal>
</goals>
<configuration>
<mainClass>org.eclipse.che.util.GwtXmlGenerator</mainClass>
<arguments>
<argument>--rootDir=${generated.sources.directory}</argument>
<argument>--loggingEnabled=${gwt.log.enable}</argument>
</arguments>
</configuration>
</execution>
<execution>
<id>extManager-client</id>
<phase>generate-sources</phase>
<goals>
<goal>java</goal>
</goals>
<configuration>
<mainClass>org.eclipse.che.util.ExtensionManagerGenerator</mainClass>
<arguments>
<argument>--rootDir=${generated.sources.directory}</argument>
</arguments>
</configuration>
</execution>
<execution>
<id>IDEInjector-client</id>
<phase>generate-sources</phase>
<goals>
<goal>java</goal>
</goals>
<configuration>
<mainClass>org.eclipse.che.util.IDEInjectorGenerator</mainClass>
<arguments>
<argument>--rootDir=${generated.sources.directory}</argument>
</arguments>
</configuration>
</execution>
<execution>
<id>DtoRegistry-client</id>
<phase>generate-sources</phase>
<goals>
<goal>java</goal>
</goals>
<configuration>
<mainClass>org.eclipse.che.util.DtoFactoryVisitorRegistryGenerator</mainClass>
<arguments>
<argument>--rootDir=${generated.sources.directory}</argument>
</arguments>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-dyna-provider-generator-maven-plugin</artifactId>
<version>${che.version}</version>
<executions>
<execution>
<phase>generate-sources</phase>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
<configuration>
<outputDirectory>${generated.sources.directory}</outputDirectory>
</configuration>
</plugin>
<!-- GWT Maven Plugin -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>gwt-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>compile</goal>
<!--<goal>test</goal> -->
</goals>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>com.google.gwt</groupId>
<artifactId>gwt-codeserver</artifactId>
<version>${com.google.gwt.version}</version>
</dependency>
<dependency>
<groupId>com.google.gwt</groupId>
<artifactId>gwt-dev</artifactId>
<version>${com.google.gwt.version}</version>
</dependency>
<dependency>
<groupId>com.google.gwt</groupId>
<artifactId>gwt-user</artifactId>
<version>${com.google.gwt.version}</version>
</dependency>
</dependencies>
<configuration>
<gwtSdkFirstInClasspath>true</gwtSdkFirstInClasspath>
<extraJvmArgs>${gwt.compiler.extraJvmArgs}</extraJvmArgs>
<modules>
<module>org.eclipse.che.ide.IDE</module>
</modules>
<!-- don' remove it we will use it then need to found
bug in compiled JS -->
<!--style>DETAILED</style -->
<logLevel>${gwt.compiler.logLevel}</logLevel>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<executions>
<execution>
<id>buildnumber</id>
<phase>compile</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<tasks>
<echo append="false" file="${project.build.directory}/classes/org/eclipse/che/ide/ext/help/client/BuildInfo.properties">revision = ${revision}
buildTime = ${timestamp}
version =
${project.version}</echo>
</tasks>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<configuration>
<overlays>
<overlay>
<groupId>org.eclipse.che</groupId>
<artifactId>assembly-ide-war</artifactId>
<type>war</type>
<includes>
<include>_app/IDE.html</include>
<include>_app/browserNotSupported.js</include>
<include>_app/favicon.ico</include>
<include>META-INF/context.xml</include>
<include>WEB-INF/rewrite.config</include>
<include>WEB-INF/web.xml</include>
<include>WEB-INF/classes/org/eclipse/che/*.class</include>
</includes>
</overlay>
<overlay />
</overlays>
<packagingExcludes>%regex[WEB-INF\\lib\\(?!.*j2ee).*.jar]</packagingExcludes>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<executions>
<execution>
<id>add-source</id>
<phase>generate-sources</phase>
<goals>
<goal>add-source</goal>
</goals>
<configuration>
<sources>
<source>${generated.sources.directory}</source>
</sources>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>buildnumber-maven-plugin</artifactId>
<executions>
<execution>
<phase>validate</phase>
<goals>
<goal>create</goal>
</goals>
</execution>
</executions>
<configuration>
<timestampFormat>{0, date, yyyy-MM-dd HH:mm:ss}</timestampFormat>
<buildNumberPropertyName>revision</buildNumberPropertyName>
<doCheck>false</doCheck>
<doUpdate>false</doUpdate>
<shortRevisionLength>16</shortRevisionLength>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,98 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (c) 2012-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
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>che-assembly-parent</artifactId>
<groupId>org.eclipse.che.assembly-multiuser</groupId>
<version>5.19.0-SNAPSHOT</version>
</parent>
<artifactId>assembly-main</artifactId>
<packaging>pom</packaging>
<name>Che IDE Assembly Multiuser :: Assemblies Tomcat</name>
<dependencies>
<dependency>
<groupId>org.eclipse.che.assembly-multiuser</groupId>
<artifactId>assembly-ide-war</artifactId>
<type>war</type>
</dependency>
<dependency>
<groupId>org.eclipse.che.assembly-multiuser</groupId>
<artifactId>assembly-wsagent-server</artifactId>
<type>tar.gz</type>
</dependency>
<dependency>
<groupId>org.eclipse.che.assembly-multiuser</groupId>
<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>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
<configuration>
<appendAssemblyId>false</appendAssemblyId>
<updateOnly>false</updateOnly>
<descriptor>${project.basedir}/src/assembly/assembly.xml</descriptor>
<finalName>eclipse-che-${project.version}</finalName>
<tarLongFileMode>posix</tarLongFileMode>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>unpack-original-che-assembly</id>
<phase>prepare-package</phase>
<goals>
<goal>unpack</goal>
</goals>
<configuration>
<artifactItems>
<artifactItem>
<groupId>org.eclipse.che</groupId>
<artifactId>assembly-main</artifactId>
<type>zip</type>
<overWrite>true</overWrite>
<outputDirectory>${project.build.directory}/dependency</outputDirectory>
</artifactItem>
</artifactItems>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,86 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (c) 2012-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
-->
<assembly
xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2 http://maven.apache.org/xsd/assembly-1.1.2.xsd">
<id>tomcat-zip</id>
<formats>
<format>dir</format>
<format>zip</format>
<format>tar.gz</format>
</formats>
<dependencySets>
<dependencySet>
<useProjectArtifact>false</useProjectArtifact>
<unpack>false</unpack>
<outputDirectory>tomcat/webapps</outputDirectory>
<outputFileNameMapping>ROOT.war</outputFileNameMapping>
<includes>
<include>org.eclipse.che.assembly-multiuser:assembly-ide-war</include>
</includes>
</dependencySet>
<dependencySet>
<useProjectArtifact>false</useProjectArtifact>
<unpack>false</unpack>
<outputDirectory>tomcat/webapps</outputDirectory>
<outputFileNameMapping>wsmaster.war</outputFileNameMapping>
<includes>
<include>org.eclipse.che.assembly-multiuser:assembly-wsmaster-war</include>
</includes>
</dependencySet>
<dependencySet>
<useProjectArtifact>false</useProjectArtifact>
<unpack>false</unpack>
<outputDirectory>lib</outputDirectory>
<outputFileNameMapping>ws-agent.tar.gz</outputFileNameMapping>
<includes>
<include>org.eclipse.che.assembly-multiuser:assembly-wsagent-server</include>
</includes>
</dependencySet>
<dependencySet>
<useProjectArtifact>false</useProjectArtifact>
<unpack>false</unpack>
<outputDirectory>tomcat/webapps</outputDirectory>
<outputFileNameMapping>dashboard.war</outputFileNameMapping>
<includes>
<include>org.eclipse.che.dashboard:che-dashboard-war</include>
</includes>
</dependencySet>
<dependencySet>
<useProjectArtifact>false</useProjectArtifact>
<unpack>false</unpack>
<outputDirectory>lib</outputDirectory>
<includes>
<include>org.postgresql:postgresql</include>
</includes>
</dependencySet>
</dependencySets>
<fileSets>
<fileSet>
<directory>${project.build.directory}/dependency/eclipse-che-${che.version}</directory>
<outputDirectory />
<excludes>
<exclude>**/tomcat/webapps/ROOT.war</exclude>
<exclude>**/tomcat/webapps/wsmaster.war</exclude>
<exclude>**/lib/ws-agent.tar.gz</exclude>
<exclude>**/tomcat/webapps/dashboard.war</exclude>
</excludes>
</fileSet>
<fileSet>
<directory>${project.build.directory}/keycloak/</directory>
<outputDirectory>tomcat/lib</outputDirectory>
</fileSet>
</fileSets>
</assembly>

View File

@ -0,0 +1,78 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (c) 2012-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
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>che-assembly-parent</artifactId>
<groupId>org.eclipse.che.assembly-multiuser</groupId>
<version>5.19.0-SNAPSHOT</version>
</parent>
<artifactId>assembly-wsagent-server</artifactId>
<packaging>pom</packaging>
<name>Che IDE Assembly Multiuser :: Agent Server</name>
<dependencies>
<dependency>
<groupId>org.eclipse.che.assembly-multiuser</groupId>
<artifactId>assembly-wsagent-war</artifactId>
<type>war</type>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
<configuration>
<appendAssemblyId>false</appendAssemblyId>
<updateOnly>false</updateOnly>
<descriptor>${project.basedir}/src/assembly/assembly.xml</descriptor>
<finalName>ext-server-${project.version}</finalName>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>unpack-tomcat</id>
<phase>prepare-package</phase>
<goals>
<goal>unpack</goal>
</goals>
<configuration>
<artifactItems>
<artifactItem>
<groupId>org.eclipse.che</groupId>
<artifactId>assembly-wsagent-server</artifactId>
<type>zip</type>
<overWrite>true</overWrite>
<outputDirectory>${project.build.directory}/dependency</outputDirectory>
</artifactItem>
</artifactItems>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,41 @@
<!--
Copyright (c) 2012-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
-->
<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2 http://maven.apache.org/xsd/assembly-1.1.2.xsd">
<id>tomcat-zip</id>
<formats>
<format>zip</format>
<format>tar.gz</format>
</formats>
<includeBaseDirectory>false</includeBaseDirectory>
<dependencySets>
<dependencySet>
<useProjectArtifact>false</useProjectArtifact>
<unpack>false</unpack>
<outputDirectory>webapps</outputDirectory>
<outputFileNameMapping>ROOT.war</outputFileNameMapping>
<includes>
<include>org.eclipse.che.assembly-multiuser:assembly-wsagent-war</include>
</includes>
</dependencySet>
</dependencySets>
<fileSets>
<fileSet>
<directory>${project.build.directory}/dependency</directory>
<outputDirectory></outputDirectory>
<excludes>
<exclude>**/webapps/ROOT.war</exclude>
</excludes>
</fileSet>
</fileSets>
</assembly>

View File

@ -0,0 +1,92 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (c) 2012-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
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>che-assembly-parent</artifactId>
<groupId>org.eclipse.che.assembly-multiuser</groupId>
<version>5.19.0-SNAPSHOT</version>
</parent>
<artifactId>assembly-wsagent-war</artifactId>
<packaging>war</packaging>
<name>Che IDE Assembly Multiuser :: Agent War Packaging</name>
<dependencies>
<dependency>
<groupId>com.google.inject</groupId>
<artifactId>guice</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che</groupId>
<artifactId>assembly-wsagent-war</artifactId>
<type>war</type>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-core</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-commons-inject</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.multiuser</groupId>
<artifactId>che-multiuser-keycloak-server</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.multiuser</groupId>
<artifactId>che-multiuser-machine-authentication-agent</artifactId>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>analyze</id>
<configuration>
<skip>true</skip>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<configuration>
<attachClasses>true</attachClasses>
<packagingExcludes>WEB-INF/lib/*gwt*.jar,
WEB-INF/lib/gin-*.jar,
WEB-INF/lib/jsr305*.jar,
WEB-INF/classes/org/eclipse/che/wsagent/server/CheWsAgentServletModule.class</packagingExcludes>
<webResources>
<resource>
<directory>${basedir}/src/main/webapp/WEB-INF</directory>
<targetPath>WEB-INF</targetPath>
<includes>
<include>**/*</include>
</includes>
</resource>
</webResources>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,44 @@
/*
* Copyright (c) 2012-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
*/
package org.eclipse.che.wsagent.server;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import javax.validation.constraints.NotNull;
import org.eclipse.che.api.core.rest.DefaultHttpJsonRequestFactory;
import org.eclipse.che.api.core.rest.HttpJsonRequest;
import org.eclipse.che.api.core.rest.shared.dto.Link;
/**
* Implementation of {@link org.eclipse.che.api.core.rest.HttpJsonRequestFactory} that add
* ```user.token``` as authorization header. Used to make request from ws-agent to ws-master.
*/
@Singleton
public class AgentHttpJsonRequestFactory extends DefaultHttpJsonRequestFactory {
private final String TOKEN;
@Inject
public AgentHttpJsonRequestFactory(@Named("user.token") String token) {
this.TOKEN = token;
}
@Override
public HttpJsonRequest fromUrl(@NotNull String url) {
return super.fromUrl(url).setAuthorizationHeader(TOKEN);
}
@Override
public HttpJsonRequest fromLink(@NotNull Link link) {
return super.fromLink(link).setAuthorizationHeader(TOKEN);
}
}

View File

@ -0,0 +1,27 @@
/*
* Copyright (c) 2012-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
*/
package org.eclipse.che.wsagent.server;
import com.google.inject.AbstractModule;
import org.eclipse.che.api.core.rest.HttpJsonRequestFactory;
import org.eclipse.che.commons.auth.token.ChainedTokenExtractor;
import org.eclipse.che.commons.auth.token.RequestTokenExtractor;
import org.eclipse.che.inject.DynaModule;
/** Provide multi user specific implementation of ws-agent components. */
@DynaModule
public class WsAgentMachineAuthModule extends AbstractModule {
@Override
protected void configure() {
bind(HttpJsonRequestFactory.class).to(AgentHttpJsonRequestFactory.class);
bind(RequestTokenExtractor.class).to(ChainedTokenExtractor.class);
}
}

View File

@ -0,0 +1,28 @@
/*
* Copyright (c) 2012-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
*/
package org.eclipse.che.wsagent.server;
import com.google.inject.servlet.ServletModule;
import org.eclipse.che.api.core.cors.CheCorsFilter;
import org.eclipse.che.inject.DynaModule;
import org.eclipse.che.multiuser.machine.authentication.agent.MachineLoginFilter;
import org.everrest.guice.servlet.GuiceEverrestServlet;
/** Provide bindings of security && authentication filters necessary for multi-user Che */
@DynaModule
public class WsAgentMachineAuthServletModule extends ServletModule {
@Override
protected void configureServlets() {
filter("/*").through(CheCorsFilter.class);
filter("/*").through(MachineLoginFilter.class);
serveRegex("^/api((?!(/(ws|eventbus)($|/.*)))/.*)").with(GuiceEverrestServlet.class);
}
}

View File

@ -0,0 +1,56 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (c) 2012-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
-->
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0"
metadata-complete="true">
<context-param>
<param-name>org.everrest.websocket.context</param-name>
<param-value>/api</param-value>
</context-param>
<context-param>
<param-name>org.eclipse.che.websocket.endpoint</param-name>
<param-value>/ws</param-value>
</context-param>
<context-param>
<param-name>org.eclipse.che.eventbus.endpoint</param-name>
<param-value>/eventbus/</param-value>
</context-param>
<listener>
<listener-class>org.eclipse.che.inject.CheBootstrap</listener-class>
</listener>
<listener>
<listener-class>org.eclipse.che.everrest.ServerContainerInitializeListener</listener-class>
</listener>
<listener>
<listener-class>org.everrest.websockets.WSConnectionTracker</listener-class>
</listener>
<filter>
<filter-name>guiceFilter</filter-name>
<filter-class>com.google.inject.servlet.GuiceFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>guiceFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<session-config>
<session-timeout>180</session-timeout>
</session-config>
</web-app>

View File

@ -0,0 +1,167 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (c) 2012-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
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>che-assembly-parent</artifactId>
<groupId>org.eclipse.che.assembly-multiuser</groupId>
<version>5.19.0-SNAPSHOT</version>
</parent>
<artifactId>assembly-wsmaster-war</artifactId>
<packaging>war</packaging>
<name>Che IDE Assembly Multiuser :: Compiling WS Master WAR</name>
<dependencies>
<dependency>
<groupId>org.eclipse.che</groupId>
<artifactId>assembly-wsmaster-war</artifactId>
<type>war</type>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-factory</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-system</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-workspace</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-db</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-db-vendor-postgresql</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.multiuser</groupId>
<artifactId>che-multiuser-api-authorization-impl</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.multiuser</groupId>
<artifactId>che-multiuser-api-organization</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.multiuser</groupId>
<artifactId>che-multiuser-api-resource</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.multiuser</groupId>
<artifactId>che-multiuser-keycloak-server</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.multiuser</groupId>
<artifactId>che-multiuser-keycloak-token-provider</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.multiuser</groupId>
<artifactId>che-multiuser-machine-authentication</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.multiuser</groupId>
<artifactId>che-multiuser-permission-factory</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.multiuser</groupId>
<artifactId>che-multiuser-permission-resource</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.multiuser</groupId>
<artifactId>che-multiuser-permission-system</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.multiuser</groupId>
<artifactId>che-multiuser-permission-user</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.multiuser</groupId>
<artifactId>che-multiuser-permission-workspace</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.multiuser</groupId>
<artifactId>che-multiuser-personal-account</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.multiuser</groupId>
<artifactId>che-multiuser-sql-schema</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.plugin</groupId>
<artifactId>che-plugin-activity-wsmaster</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.plugin</groupId>
<artifactId>che-plugin-docker-machine-auth</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>analyze</id>
<configuration>
<skip>true</skip>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<configuration>
<overlays>
<overlay>
<groupId>org.eclipse.che</groupId>
<artifactId>assembly-wsmaster-war</artifactId>
<type>war</type>
<includes>
<include>**/**</include>
</includes>
</overlay>
</overlays>
<webResources>
<resource>
<directory>${basedir}/src/main/webapp/META-INF</directory>
<targetPath>META-INF</targetPath>
<includes>
<include>**/*</include>
</includes>
</resource>
<resource>
<directory>${basedir}/src/main/webapp/WEB-INF</directory>
<targetPath>WEB-INF</targetPath>
<includes>
<include>**/*</include>
</includes>
</resource>
</webResources>
<packagingExcludes>WEB-INF/lib/wsmaster-local*.jar,
WEB-INF/lib/che-core-db-vendor-h2-*.jar,
WEB-INF/classes/org/eclipse/che/api/deploy/CheWsMasterModule.class,
WEB-INF/classes/org/eclipse/che/api/deploy/CheWsMasterServletModule.class</packagingExcludes>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,47 @@
/*
* Copyright (c) 2012-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
*/
package org.eclipse.che.api.deploy;
import com.google.inject.AbstractModule;
import org.eclipse.che.api.workspace.server.WorkspaceServiceLinksInjector;
import org.eclipse.che.commons.auth.token.ChainedTokenExtractor;
import org.eclipse.che.commons.auth.token.RequestTokenExtractor;
import org.eclipse.che.inject.DynaModule;
import org.eclipse.che.multiuser.machine.authentication.server.interceptor.InterceptorModule;
/**
* Machine authentication bindings.
*
* @author Max Shaposhnik (mshaposh@redhat.com)
*/
@DynaModule
public class MachineAuthModule extends AbstractModule {
@Override
protected void configure() {
install(new InterceptorModule());
bind(org.eclipse.che.api.agent.server.WsAgentHealthChecker.class)
.to(org.eclipse.che.multiuser.machine.authentication.server.AuthWsAgentHealthChecker.class);
bind(
org.eclipse.che.multiuser.machine.authentication.server.MachineTokenPermissionsFilter
.class);
bind(org.eclipse.che.multiuser.machine.authentication.server.MachineTokenService.class);
bind(org.eclipse.che.multiuser.machine.authentication.server.MachineTokenRegistry.class);
bind(org.eclipse.che.multiuser.machine.authentication.server.MachineSessionInvalidator.class);
bind(RequestTokenExtractor.class).to(ChainedTokenExtractor.class);
bind(WorkspaceServiceLinksInjector.class)
.to(
org.eclipse.che.multiuser.machine.authentication.server
.WorkspaceServiceAuthLinksInjector.class);
bind(org.eclipse.che.api.environment.server.MachineInstanceProvider.class)
.to(org.eclipse.che.plugin.docker.machine.AuthMachineProviderImpl.class);
}
}

View File

@ -0,0 +1,32 @@
/*
* Copyright (c) 2012-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
*/
package org.eclipse.che.api.deploy;
import com.google.inject.servlet.ServletModule;
import org.eclipse.che.inject.DynaModule;
import org.eclipse.che.multiuser.keycloak.server.deploy.KeycloakServletModule;
import org.eclipse.che.multiuser.machine.authentication.server.MachineLoginFilter;
/**
* Machine authentication bindings.
*
* @author Max Shaposhnik (mshaposh@redhat.com)
*/
@DynaModule
public class MultiUserCheServletModule extends ServletModule {
@Override
protected void configureServlets() {
// Not contains '/websocket/' and not ends with '/ws' or '/eventbus'
filterRegex("^(?!.*/websocket/)(?!.*(/ws|/eventbus)$).*").through(MachineLoginFilter.class);
install(new KeycloakServletModule());
}
}

View File

@ -0,0 +1,68 @@
/*
* Copyright (c) 2012-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
*/
package org.eclipse.che.api.deploy;
import com.google.inject.AbstractModule;
import javax.sql.DataSource;
import org.eclipse.che.api.user.server.jpa.JpaPreferenceDao;
import org.eclipse.che.api.user.server.jpa.JpaUserDao;
import org.eclipse.che.api.user.server.spi.PreferenceDao;
import org.eclipse.che.api.user.server.spi.UserDao;
import org.eclipse.che.inject.DynaModule;
import org.eclipse.che.multiuser.api.permission.server.PermissionChecker;
import org.eclipse.che.multiuser.api.permission.server.PermissionCheckerImpl;
import org.eclipse.che.multiuser.keycloak.server.deploy.KeycloakModule;
import org.eclipse.che.multiuser.organization.api.OrganizationApiModule;
import org.eclipse.che.multiuser.organization.api.OrganizationJpaModule;
import org.eclipse.che.multiuser.resource.api.ResourceModule;
import org.eclipse.che.security.PBKDF2PasswordEncryptor;
import org.eclipse.che.security.PasswordEncryptor;
@DynaModule
public class MultiUserCheWsMasterModule extends AbstractModule {
@Override
protected void configure() {
bind(DataSource.class).toProvider(org.eclipse.che.core.db.JndiDataSourceProvider.class);
install(new org.eclipse.che.multiuser.api.permission.server.jpa.SystemPermissionsJpaModule());
install(new org.eclipse.che.multiuser.api.permission.server.PermissionsModule());
install(
new org.eclipse.che.multiuser.permission.workspace.server.WorkspaceApiPermissionsModule());
install(
new org.eclipse.che.multiuser.permission.workspace.server.jpa
.MultiuserWorkspaceJpaModule());
//Permission filters
bind(org.eclipse.che.multiuser.permission.system.SystemServicePermissionsFilter.class);
bind(org.eclipse.che.multiuser.permission.user.UserProfileServicePermissionsFilter.class);
bind(org.eclipse.che.multiuser.permission.user.UserServicePermissionsFilter.class);
bind(org.eclipse.che.multiuser.permission.factory.FactoryPermissionsFilter.class);
bind(org.eclipse.che.plugin.activity.ActivityPermissionsFilter.class);
bind(
org.eclipse.che.multiuser.permission.resource.filters.ResourceUsageServicePermissionsFilter
.class);
bind(
org.eclipse.che.multiuser.permission.resource.filters
.FreeResourcesLimitServicePermissionsFilter.class);
install(new ResourceModule());
install(new OrganizationApiModule());
install(new OrganizationJpaModule());
install(new KeycloakModule());
//User and profile - use profile from keycloak and other stuff is JPA
bind(PasswordEncryptor.class).to(PBKDF2PasswordEncryptor.class);
bind(UserDao.class).to(JpaUserDao.class);
bind(PreferenceDao.class).to(JpaPreferenceDao.class);
bind(PermissionChecker.class).to(PermissionCheckerImpl.class);
}
}

View File

@ -0,0 +1,76 @@
<!--
Copyright (c) 2012-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
-->
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence persistence_1_0.xsd" version="1.0">
<persistence-unit name="main" transaction-type="RESOURCE_LOCAL">
<provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
<non-jta-data-source>java:/comp/env/jdbc/che</non-jta-data-source>
<class>org.eclipse.che.account.spi.AccountImpl</class>
<class>org.eclipse.che.api.user.server.model.impl.UserImpl</class>
<class>org.eclipse.che.api.user.server.model.impl.ProfileImpl</class>
<class>org.eclipse.che.api.user.server.jpa.PreferenceEntity</class>
<class>org.eclipse.che.api.workspace.server.model.impl.WorkspaceImpl</class>
<class>org.eclipse.che.api.workspace.server.model.impl.WorkspaceConfigImpl</class>
<class>org.eclipse.che.api.workspace.server.model.impl.ProjectConfigImpl</class>
<class>org.eclipse.che.api.workspace.server.model.impl.EnvironmentImpl</class>
<class>org.eclipse.che.api.workspace.server.model.impl.EnvironmentRecipeImpl</class>
<class>org.eclipse.che.api.workspace.server.model.impl.ExtendedMachineImpl</class>
<class>org.eclipse.che.api.workspace.server.model.impl.ProjectConfigImpl$Attribute</class>
<class>org.eclipse.che.api.workspace.server.model.impl.SourceStorageImpl</class>
<class>org.eclipse.che.api.workspace.server.model.impl.ServerConf2Impl</class>
<class>org.eclipse.che.api.workspace.server.model.impl.stack.StackImpl</class>
<class>org.eclipse.che.api.machine.server.model.impl.CommandImpl</class>
<class>org.eclipse.che.api.machine.server.model.impl.MachineSourceImpl</class>
<class>org.eclipse.che.api.machine.server.model.impl.SnapshotImpl</class>
<class>org.eclipse.che.api.machine.server.recipe.RecipeImpl</class>
<class>org.eclipse.che.api.factory.server.model.impl.FactoryImpl</class>
<class>org.eclipse.che.api.factory.server.model.impl.OnAppClosedImpl</class>
<class>org.eclipse.che.api.factory.server.model.impl.OnProjectsLoadedImpl</class>
<class>org.eclipse.che.api.factory.server.model.impl.OnAppLoadedImpl</class>
<class>org.eclipse.che.api.factory.server.model.impl.PoliciesImpl</class>
<class>org.eclipse.che.api.factory.server.model.impl.ActionImpl</class>
<class>org.eclipse.che.api.factory.server.model.impl.AuthorImpl</class>
<class>org.eclipse.che.api.factory.server.model.impl.ButtonAttributesImpl</class>
<class>org.eclipse.che.api.factory.server.model.impl.ButtonImpl</class>
<class>org.eclipse.che.api.factory.server.model.impl.IdeImpl</class>
<class>org.eclipse.che.api.factory.server.FactoryImage</class>
<class>org.eclipse.che.api.ssh.server.model.impl.SshPairImpl</class>
<class>org.eclipse.che.multiuser.api.permission.server.model.impl.SystemPermissionsImpl</class>
<class>org.eclipse.che.multiuser.api.permission.server.model.impl.AbstractPermissions</class>
<class>org.eclipse.che.multiuser.permission.workspace.server.model.impl.WorkerImpl</class>
<class>org.eclipse.che.multiuser.permission.workspace.server.stack.StackPermissionsImpl</class>
<class>org.eclipse.che.multiuser.permission.machine.recipe.RecipePermissionsImpl</class>
<class>org.eclipse.che.multiuser.resource.spi.impl.FreeResourcesLimitImpl</class>
<class>org.eclipse.che.multiuser.resource.spi.impl.ResourceImpl</class>
<class>org.eclipse.che.multiuser.organization.spi.impl.OrganizationImpl</class>
<class>org.eclipse.che.multiuser.organization.spi.impl.MemberImpl</class>
<class>org.eclipse.che.multiuser.organization.spi.impl.OrganizationDistributedResourcesImpl</class>
<exclude-unlisted-classes>true</exclude-unlisted-classes>
<properties>
<property name="eclipselink.exception-handler" value="org.eclipse.che.core.db.postgresql.jpa.eclipselink.PostgreSqlExceptionHandler"/>
<property name="eclipselink.target-server" value="None"/>
<property name="eclipselink.logging.logger" value="DefaultLogger"/>
<property name="eclipselink.logging.level" value="SEVERE"/>
</properties>
</persistence-unit>
</persistence>

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (c) 2012-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
-->
<Context allowCasualMultipartParsing="true" sessionCookiePath="/api">
<Resource name="jdbc/che" auth="Container"
type="javax.sql.DataSource"
factory="org.eclipse.che.core.db.postgresql.PostgreSQLJndiDataSourceFactory"/>
</Context>

View File

@ -0,0 +1,84 @@
#
# Copyright (c) 2012-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
#
########################################################################################
##### CHE SYSTEM #####
# System Super Privileged Mode
# Grants users with the manageSystem permission additional permissions for
# getByKey, getByNameSpace, stopWorkspaces, and getResourcesInformation.
# These are not given to admins by default and these permissions allow
# admins gain visibility to any workspace along with naming themselves
# with admin privileges to those workspaces.
che.system.super_privileged_mode=false
# Grant system permission for 'che.admin.name' user. If the user already exists it'll happen on
# component startup, if not - during the first login when user is persisted in the database.
che.system.admin_name=admin
########################################################################################
##### WORKSPACE LIMITS #####
#
# Workspaces are the fundamental runtime for users when doing development. You can set
# parameters that limit how workspaces are created and the resources that are consumed.
# The maximum amount of RAM that a user can allocate to a workspace when they
# create a new workspace. The RAM slider is adjusted to this maximum value.
che.limits.workspace.env.ram=16gb
# The length of time that a user is idle with their workspace when the system will
# suspend the workspace by snapshotting it and then stopping it. Idleness is the
# length of time that the user has not interacted with the workspace, meaning that
# one of our agents has not received interaction. Leaving a browser window open
# counts toward idleness.
che.limits.workspace.idle.timeout=-1
##### USERS' WORKSPACE LIMITS #####
# The total amount of RAM that a single user is allowed to allocate to running
# workspaces. A user can allocate this RAM to a single workspace or spread it
# across multiple workspaces.
che.limits.user.workspaces.ram=-1
# The maximum number of workspaces that a user is allowed to create. The user will
# be presented with an error message if they try to create additional workspaces.
# This applies to the total number of both running and stopped workspaces. Since
# each workspace is saved as a snapshot, placing a cap on this number is a way
# to limit the disk consumption for workspace storage.
che.limits.user.workspaces.count=-1
# The maximum number of running workspaces that a single user is allowed to have.
# If the user has reached this threshold and they try to start an additional
# workspace, they will be prompted with an error message. The user will need to
# stop a running workspace to activate another.
che.limits.user.workspaces.run.count=-1
##### ORGANIZATIONS' WORKSPACE LIMITS #####
# The total amount of RAM that a single organization (team) is allowed to allocate
# to running workspaces. An organization owner can allocate this RAM however they
# see fit across the team's workspaces.
che.limits.organization.workspaces.ram=-1
# The maximum number of workspaces that a organization is allowed to own. The
# organization will be presented an error message if they try to create
# additional workspaces. This applies to the total number of both running
# and stopped workspaces. Since each workspace is saved as a snapshot, placing a
# cap on this number limits the disk consumption for workspace storage.
che.limits.organization.workspaces.count=-1
# The maximum number of running workspaces that a single organization is allowed.
# If the organization has reached this threshold and they try to start an
# additional workspace, they will be prompted with an error message. The
# organization will need to stop a running workspace to activate another.
che.limits.organization.workspaces.run.count=-1

View File

@ -0,0 +1,58 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (c) 2012-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
-->
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0"
metadata-complete="true">
<context-param>
<param-name>org.everrest.websocket.context</param-name>
<param-value>/api</param-value>
</context-param>
<context-param>
<param-name>org.eclipse.che.websocket.endpoint</param-name>
<param-value>/ws</param-value>
</context-param>
<context-param>
<param-name>org.eclipse.che.eventbus.endpoint</param-name>
<param-value>/eventbus/</param-value>
</context-param>
<listener>
<listener-class>org.eclipse.che.inject.CheBootstrap</listener-class>
</listener>
<listener>
<listener-class>org.eclipse.che.everrest.ServerContainerInitializeListener</listener-class>
</listener>
<listener>
<listener-class>org.everrest.websockets.WSConnectionTracker</listener-class>
</listener>
<filter>
<filter-name>guiceFilter</filter-name>
<filter-class>com.google.inject.servlet.GuiceFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>guiceFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<resource-env-ref>
<resource-env-ref-name>jdbc/che</resource-env-ref-name>
<resource-env-ref-type>javax.sql.DataSource</resource-env-ref-type>
</resource-env-ref>
</web-app>

View File

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (c) 2012-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
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>che-parent</artifactId>
<groupId>org.eclipse.che</groupId>
<version>5.19.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<groupId>org.eclipse.che.assembly-multiuser</groupId>
<artifactId>che-assembly-parent</artifactId>
<version>5.19.0-SNAPSHOT</version>
<packaging>pom</packaging>
<name>Che IDE Assembly Multiuser :: Parent</name>
<modules>
<module>assembly-wsagent-war</module>
<module>assembly-wsagent-server</module>
<module>assembly-wsmaster-war</module>
<module>assembly-ide-war</module>
<module>assembly-main</module>
</modules>
</project>

View File

@ -422,6 +422,21 @@
</sources>
</configuration>
</execution>
<execution>
<id>add-resource</id>
<phase>generate-resources</phase>
<goals>
<goal>add-resource</goal>
</goals>
<configuration>
<resources>
<resource>
<directory>src/main/webapp</directory>
<targetPath />
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
<plugin>

View File

@ -46,14 +46,6 @@
description="User database that can be updated and saved"
factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
pathname="conf/tomcat-users.xml" /-->
<Resource name="che" auth="Container"
type="javax.sql.DataSource"
driverClassName="org.h2.Driver"
url="jdbc:h2:che"
username="" password=""
maxTotal="8"
maxIdle="4"/>
</GlobalNamingResources>
<!-- A "Service" is a collection of one or more "Connectors" that share

View File

@ -0,0 +1,44 @@
/*
* Copyright (c) 2012-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
*/
package org.eclipse.che.api.deploy;
import com.google.inject.AbstractModule;
import javax.sql.DataSource;
import org.eclipse.che.api.user.server.TokenValidator;
import org.eclipse.che.inject.DynaModule;
/**
* Single-user version Che specific bindings
*
* @author Max Shaposhnik (mshaposh@redhat.com)
*/
@DynaModule
public class CheWsMasterModule extends AbstractModule {
@Override
protected void configure() {
bind(TokenValidator.class).to(org.eclipse.che.api.local.DummyTokenValidator.class);
bind(org.eclipse.che.api.agent.server.WsAgentHealthChecker.class)
.to(org.eclipse.che.api.agent.server.WsAgentHealthCheckerImpl.class);
bind(org.eclipse.che.api.environment.server.MachineInstanceProvider.class)
.to(org.eclipse.che.plugin.docker.machine.MachineProviderImpl.class);
bind(org.eclipse.che.api.workspace.server.stack.StackLoader.class);
bind(DataSource.class).toProvider(org.eclipse.che.core.db.h2.H2DataSourceProvider.class);
install(new org.eclipse.che.api.user.server.jpa.UserJpaModule());
install(new org.eclipse.che.api.workspace.server.jpa.WorkspaceJpaModule());
bind(org.eclipse.che.api.user.server.CheUserCreator.class);
}
}

View File

@ -0,0 +1,29 @@
/*
* Copyright (c) 2012-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
*/
package org.eclipse.che.api.deploy;
import com.google.inject.servlet.ServletModule;
import org.eclipse.che.inject.DynaModule;
/**
* Single-user version Che specific bindings
*
* @author Max Shaposhnik (mshaposh@redhat.com)
*/
@DynaModule
public class CheWsMasterServletModule extends ServletModule {
@Override
protected void configureServlets() {
filter("/api/*")
.through(org.eclipse.che.api.local.filters.EnvironmentInitializationFilter.class);
}
}

View File

@ -19,7 +19,6 @@ import com.google.inject.multibindings.MapBinder;
import com.google.inject.multibindings.Multibinder;
import com.google.inject.name.Names;
import java.util.Set;
import javax.sql.DataSource;
import org.eclipse.che.api.core.rest.CheJsonProvider;
import org.eclipse.che.api.core.rest.MessageBodyAdapter;
import org.eclipse.che.api.core.rest.MessageBodyAdapterInterceptor;
@ -36,7 +35,6 @@ import org.eclipse.che.api.recipe.RecipeLoader;
import org.eclipse.che.api.recipe.RecipeService;
import org.eclipse.che.api.system.server.ServiceTermination;
import org.eclipse.che.api.system.server.SystemModule;
import org.eclipse.che.api.user.server.TokenValidator;
import org.eclipse.che.api.workspace.server.adapter.StackMessageBodyAdapter;
import org.eclipse.che.api.workspace.server.adapter.WorkspaceConfigMessageBodyAdapter;
import org.eclipse.che.api.workspace.server.adapter.WorkspaceMessageBodyAdapter;
@ -59,15 +57,12 @@ public class WsMasterModule extends AbstractModule {
// db related components modules
install(new com.google.inject.persist.jpa.JpaPersistModule("main"));
install(new org.eclipse.che.account.api.AccountModule());
install(new org.eclipse.che.api.user.server.jpa.UserJpaModule());
install(new org.eclipse.che.api.ssh.server.jpa.SshJpaModule());
bind(RecipeDao.class).to(JpaRecipeDao.class);
install(new org.eclipse.che.api.workspace.server.jpa.WorkspaceJpaModule());
install(new org.eclipse.che.api.core.jsonrpc.impl.JsonRpcModule());
install(new org.eclipse.che.api.core.websocket.impl.WebSocketModule());
// db configuration
bind(DataSource.class).toProvider(org.eclipse.che.core.db.h2.H2DataSourceProvider.class);
bind(SchemaInitializer.class)
.to(org.eclipse.che.core.db.schema.impl.flyway.FlywaySchemaInitializer.class);
bind(org.eclipse.che.core.db.DBInitializer.class).asEagerSingleton();
@ -88,10 +83,6 @@ public class WsMasterModule extends AbstractModule {
Multibinder.newSetBinder(binder(), FactoryParametersResolver.class);
factoryParametersResolverMultibinder.addBinding().to(GithubFactoryParametersResolver.class);
bind(org.eclipse.che.api.user.server.CheUserCreator.class);
bind(TokenValidator.class).to(org.eclipse.che.api.local.DummyTokenValidator.class);
bind(org.eclipse.che.api.core.rest.ApiInfoService.class);
bind(org.eclipse.che.api.project.server.template.ProjectTemplateDescriptionLoader.class)
.asEagerSingleton();
@ -103,7 +94,6 @@ public class WsMasterModule extends AbstractModule {
bind(org.eclipse.che.api.user.server.ProfileService.class);
bind(org.eclipse.che.api.user.server.PreferencesService.class);
bind(org.eclipse.che.api.workspace.server.stack.StackLoader.class);
MapBinder<String, String> stacks =
MapBinder.newMapBinder(
binder(), String.class, String.class, Names.named(StackLoader.CHE_PREDEFINED_STACKS));

View File

@ -44,8 +44,6 @@ public class WsMasterServletModule extends ServletModule {
filter("/*").through(CorsFilter.class, corsFilterParams);
filter("/api/*")
.through(org.eclipse.che.api.local.filters.EnvironmentInitializationFilter.class);
serveRegex("^/api((?!(/(ws|eventbus)($|/.*)))/.*)").with(GuiceEverrestServlet.class);
install(new org.eclipse.che.swagger.deploy.BasicSwaggerConfigurationModule());
}

View File

@ -11,10 +11,9 @@
Red Hat, Inc. - initial API and implementation
-->
<Context allowCasualMultipartParsing="true">
<Valve className="org.apache.catalina.valves.rewrite.RewriteValve"/>
<Context allowCasualMultipartParsing="true" sessionCookiePath="/api">
<ResourceLink global="che"
name="jdbc/che"
type="javax.sql.DataSource"/>
<Resource name="jdbc/che" auth="Container"
type="javax.sql.DataSource"
factory="org.eclipse.che.core.db.h2.H2SQLJndiDataSourceFactory"/>
</Context>

View File

@ -49,15 +49,10 @@
<filter-name>guiceFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<security-role>
<description>the user role</description>
<role-name>developer</role-name>
</security-role>
<resource-ref>
<res-ref-name>jdbc/che</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Container</res-auth>
</resource-ref>
<resource-env-ref>
<resource-env-ref-name>jdbc/che</resource-env-ref-name>
<resource-env-ref-type>javax.sql.DataSource</resource-env-ref-type>
</resource-env-ref>
</web-app>

View File

@ -24,12 +24,7 @@ public class EnvironmentContext {
/** ThreadLocal keeper for EnvironmentContext. */
private static ThreadLocal<EnvironmentContext> current =
new ThreadLocal<EnvironmentContext>() {
@Override
protected EnvironmentContext initialValue() {
return new EnvironmentContext();
}
};
ThreadLocal.withInitial(EnvironmentContext::new);
static {
ThreadLocalPropagateContext.addThreadLocal(current);

View File

@ -21,6 +21,10 @@
<artifactId>che-core-db-vendor-h2</artifactId>
<name>Che Core :: DB :: Vendor H2</name>
<dependencies>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>

View File

@ -0,0 +1,42 @@
/*
* Copyright (c) 2012-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
*/
package org.eclipse.che.core.db.h2;
import static com.google.common.base.MoreObjects.firstNonNull;
import org.eclipse.che.core.db.JNDIDataSourceFactory;
/**
* Environment params based JNDI data source factory for H2SQL.
*
* @author Sergii Kabashniuk
*/
public class H2SQLJndiDataSourceFactory extends JNDIDataSourceFactory {
private static final String DEFAULT_USERNAME = "";
private static final String DEFAULT_PASSWORD = "";
private static final String DEFAULT_URL = "jdbc:h2:che";
private static final String DEFAULT_DRIVER__CLASS__NAME = "org.h2.Driver";
private static final String DEFAULT_MAX__TOTAL = "8";
private static final String DEFAULT_MAX__IDLE = "2";
private static final String DEFAULT_MAX__WAIT__MILLIS = "-1";
public H2SQLJndiDataSourceFactory() throws Exception {
super(
firstNonNull(System.getenv("CHE_JDBC_USERNAME"), DEFAULT_USERNAME),
firstNonNull(System.getenv("CHE_JDBC_PASSWORD"), DEFAULT_PASSWORD),
firstNonNull(System.getenv("CHE_JDBC_URL"), DEFAULT_URL),
firstNonNull(System.getenv("CHE_JDBC_DRIVER__CLASS__NAME"), DEFAULT_DRIVER__CLASS__NAME),
firstNonNull(System.getenv("CHE_JDBC_MAX__TOTAL"), DEFAULT_MAX__TOTAL),
firstNonNull(System.getenv("CHE_JDBC_MAX__IDLE"), DEFAULT_MAX__IDLE),
firstNonNull(System.getenv("CHE_JDBC_MAX__WAIT__MILLIS"), DEFAULT_MAX__WAIT__MILLIS));
}
}

View File

@ -21,6 +21,10 @@
<artifactId>che-core-db-vendor-postgresql</artifactId>
<name>Che Core :: DB :: Vendor PostgreSQL</name>
<dependencies>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-db</artifactId>

View File

@ -0,0 +1,42 @@
/*
* Copyright (c) 2012-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
*/
package org.eclipse.che.core.db.postgresql;
import static com.google.common.base.MoreObjects.firstNonNull;
import org.eclipse.che.core.db.JNDIDataSourceFactory;
/**
* Environment params based JNDI data source factory for Postgres.
*
* @author Sergii Kabashniuk
*/
public class PostgreSQLJndiDataSourceFactory extends JNDIDataSourceFactory {
private static final String DEFAULT_USERNAME = "pgche";
private static final String DEFAULT_PASSWORD = "pgchepassword";
private static final String DEFAULT_URL = "jdbc:postgresql://postgres:5432/dbche";
private static final String DEFAULT_DRIVER__CLASS__NAME = "org.postgresql.Driver";
private static final String DEFAULT_MAX__TOTAL = "20";
private static final String DEFAULT_MAX__IDLE = "2";
private static final String DEFAULT_MAX__WAIT__MILLIS = "-1";
public PostgreSQLJndiDataSourceFactory() throws Exception {
super(
firstNonNull(System.getenv("CHE_JDBC_USERNAME"), DEFAULT_USERNAME),
firstNonNull(System.getenv("CHE_JDBC_PASSWORD"), DEFAULT_PASSWORD),
firstNonNull(System.getenv("CHE_JDBC_URL"), DEFAULT_URL),
firstNonNull(System.getenv("CHE_JDBC_DRIVER__CLASS__NAME"), DEFAULT_DRIVER__CLASS__NAME),
firstNonNull(System.getenv("CHE_JDBC_MAX__TOTAL"), DEFAULT_MAX__TOTAL),
firstNonNull(System.getenv("CHE_JDBC_MAX__IDLE"), DEFAULT_MAX__IDLE),
firstNonNull(System.getenv("CHE_JDBC_MAX__WAIT__MILLIS"), DEFAULT_MAX__WAIT__MILLIS));
}
}

View File

@ -37,6 +37,10 @@
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-dbcp</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-core</artifactId>

View File

@ -0,0 +1,63 @@
/*
* Copyright (c) 2012-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
*/
package org.eclipse.che.core.db;
import java.util.Hashtable;
import java.util.Properties;
import javax.naming.Context;
import javax.naming.Name;
import javax.naming.spi.ObjectFactory;
import org.apache.tomcat.dbcp.dbcp2.BasicDataSource;
import org.apache.tomcat.dbcp.dbcp2.BasicDataSourceFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Abstract JNDI factory that constructs {@link BasicDataSource} objects from the given params.
* Should not be used directly and must be subclassed to provide instantiation params from needful
* source.
*
* @author Sergii Kabashniuk
*/
public abstract class JNDIDataSourceFactory implements ObjectFactory {
private static final Logger LOG = LoggerFactory.getLogger(JNDIDataSourceFactory.class);
private final BasicDataSource dataSource;
public JNDIDataSourceFactory(
String userName,
String password,
String url,
String driverClassName,
String maxTotal,
String maxIdle,
String maxWaitMillis)
throws Exception {
Properties poolConfigurationProperties = new Properties();
poolConfigurationProperties.setProperty("username", userName);
poolConfigurationProperties.setProperty("password", password);
poolConfigurationProperties.setProperty("url", url);
poolConfigurationProperties.setProperty("driverClassName", driverClassName);
poolConfigurationProperties.setProperty("maxTotal", maxTotal);
poolConfigurationProperties.setProperty("maxIdle", maxIdle);
poolConfigurationProperties.setProperty("maxWaitMillis", maxWaitMillis);
dataSource = BasicDataSourceFactory.createDataSource(poolConfigurationProperties);
}
@Override
public Object getObjectInstance(
Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception {
LOG.info(
"This={} obj={} name={} Context={} environment={}", this, obj, name, nameCtx, environment);
return dataSource;
}
}

View File

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (c) 2012-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
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>che-core-commons-parent</artifactId>
<groupId>org.eclipse.che.core</groupId>
<version>5.19.0-SNAPSHOT</version>
</parent>
<artifactId>che-core-commons-auth</artifactId>
<packaging>jar</packaging>
<name>Che Core :: Commons :: Auth</name>
<dependencies>
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
</dependency>
<dependency>
<groupId>javax.ws.rs</groupId>
<artifactId>javax.ws.rs-api</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-core</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-dto</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
</project>

View File

@ -8,7 +8,7 @@
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.api.auth;
package org.eclipse.che.commons.auth;
import org.eclipse.che.api.core.ApiException;

View File

@ -8,7 +8,7 @@
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.api.auth;
package org.eclipse.che.commons.auth;
import javax.inject.Singleton;
import javax.ws.rs.core.MediaType;

View File

@ -0,0 +1,40 @@
/*
* Copyright (c) 2012-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
*/
package org.eclipse.che.commons.auth.token;
import javax.servlet.http.HttpServletRequest;
/**
* Try to extract token from request in 3 steps. 1. From query parameter. 2. From header. 3. From
* cookie.
*
* @author Sergii Kabashniuk
*/
public class ChainedTokenExtractor implements RequestTokenExtractor {
private final HeaderRequestTokenExtractor headerRequestTokenExtractor;
private final QueryRequestTokenExtractor queryRequestTokenExtractor;
public ChainedTokenExtractor() {
headerRequestTokenExtractor = new HeaderRequestTokenExtractor();
queryRequestTokenExtractor = new QueryRequestTokenExtractor();
}
@Override
public String getToken(HttpServletRequest req) {
String token;
if ((token = queryRequestTokenExtractor.getToken(req)) == null) {
token = headerRequestTokenExtractor.getToken(req);
}
return token;
}
}

View File

@ -0,0 +1,27 @@
/*
* Copyright (c) 2012-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
*/
package org.eclipse.che.commons.auth.token;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.core.HttpHeaders;
/** Extract sso token from request headers. */
public class HeaderRequestTokenExtractor implements RequestTokenExtractor {
@Override
public String getToken(HttpServletRequest req) {
if (req.getHeader(HttpHeaders.AUTHORIZATION) == null) {
return null;
}
return req.getHeader(HttpHeaders.AUTHORIZATION).toLowerCase().startsWith("bearer")
? req.getHeader(HttpHeaders.AUTHORIZATION).split(" ")[1]
: req.getHeader(HttpHeaders.AUTHORIZATION);
}
}

View File

@ -0,0 +1,34 @@
/*
* Copyright (c) 2012-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
*/
package org.eclipse.che.commons.auth.token;
import javax.servlet.http.HttpServletRequest;
/** @author Max Shaposhnik (mshaposh@redhat.com) */
public class QueryRequestTokenExtractor implements RequestTokenExtractor {
@Override
public String getToken(HttpServletRequest req) {
String query = req.getQueryString();
if (query != null) {
int start = query.indexOf("&token=");
if (start != -1 || query.startsWith("token=")) {
int end = query.indexOf('&', start + 7);
if (end == -1) {
end = query.length();
}
if (end != start + 7) {
return query.substring(start + 7, end);
}
}
}
return null;
}
}

View File

@ -0,0 +1,24 @@
/*
* Copyright (c) 2012-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
*/
package org.eclipse.che.commons.auth.token;
import javax.servlet.http.HttpServletRequest;
/** Allows to extract sso token from request. */
public interface RequestTokenExtractor {
/**
* Extract token from request.
*
* @param req - request object.
* @return - token if it was found, null otherwise.
*/
String getToken(HttpServletRequest req);
}

View File

@ -0,0 +1,102 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (c) 2012-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
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>che-core-commons-parent</artifactId>
<groupId>org.eclipse.che.core</groupId>
<version>5.19.0-SNAPSHOT</version>
</parent>
<artifactId>che-core-commons-mail</artifactId>
<packaging>jar</packaging>
<name>Che Core :: Commons :: Mail sender</name>
<dependencies>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
</dependency>
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
</dependency>
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
</dependency>
<dependency>
<groupId>javax.mail</groupId>
<artifactId>mail</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-commons-annotations</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-commons-inject</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-commons-lang</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.github.kirviq</groupId>
<artifactId>dumbster</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.everrest</groupId>
<artifactId>everrest-assured</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockitong</groupId>
<artifactId>mockitong</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,95 @@
/*
* Copyright (c) 2012-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
*/
package org.eclipse.che.mail;
import java.util.Objects;
/**
* Describing e-mail attachment.
*
* @author Igor Vinokur
* @author Alexander Garagatyi
*/
public class Attachment {
private String content;
private String contentId;
private String fileName;
/** Base-64 encoded string that represents attachment content. */
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public Attachment withContent(String content) {
this.content = content;
return this;
}
public String getContentId() {
return contentId;
}
public void setContentId(String contentId) {
this.contentId = contentId;
}
public Attachment withContentId(String contentId) {
this.contentId = contentId;
return this;
}
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
public Attachment withFileName(String fileName) {
this.fileName = fileName;
return this;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Attachment)) return false;
Attachment that = (Attachment) o;
return Objects.equals(getContent(), that.getContent())
&& Objects.equals(getContentId(), that.getContentId())
&& Objects.equals(getFileName(), that.getFileName());
}
@Override
public int hashCode() {
return Objects.hash(getContent(), getContentId(), getFileName());
}
@Override
public String toString() {
return "Attachment{"
+ "content='"
+ content
+ '\''
+ ", contentId='"
+ contentId
+ '\''
+ ", fileName='"
+ fileName
+ '\''
+ '}';
}
}

View File

@ -0,0 +1,169 @@
/*
* Copyright (c) 2012-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
*/
package org.eclipse.che.mail;
import java.util.List;
import java.util.Objects;
import org.eclipse.che.commons.annotation.Nullable;
/**
* Describing e-mail properties.
*
* @author Igor Vinokur
* @author Alexander Garagatyi
*/
public class EmailBean {
private String from;
private String to;
private String replyTo;
private String mimeType;
private String body;
private String subject;
private List<Attachment> attachments;
public String getFrom() {
return from;
}
public void setFrom(String from) {
this.from = from;
}
public EmailBean withFrom(String from) {
this.from = from;
return this;
}
public String getTo() {
return to;
}
public void setTo(String to) {
this.to = to;
}
public EmailBean withTo(String to) {
this.to = to;
return this;
}
public String getReplyTo() {
return replyTo;
}
public void setReplyTo(String replyTo) {
this.replyTo = replyTo;
}
public EmailBean withReplyTo(String replyTo) {
this.replyTo = replyTo;
return this;
}
public String getMimeType() {
return mimeType;
}
public void setMimeType(String mimeType) {
this.mimeType = mimeType;
}
public EmailBean withMimeType(String mimeType) {
this.mimeType = mimeType;
return this;
}
public String getBody() {
return body;
}
public void setBody(String body) {
this.body = body;
}
public EmailBean withBody(String body) {
this.body = body;
return this;
}
public String getSubject() {
return subject;
}
public void setSubject(String subject) {
this.subject = subject;
}
public EmailBean withSubject(String subject) {
this.subject = subject;
return this;
}
@Nullable
public List<Attachment> getAttachments() {
return attachments;
}
public void setAttachments(List<Attachment> attachments) {
this.attachments = attachments;
}
public EmailBean withAttachments(List<Attachment> attachments) {
this.attachments = attachments;
return this;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof EmailBean)) return false;
EmailBean emailBean = (EmailBean) o;
return Objects.equals(getFrom(), emailBean.getFrom())
&& Objects.equals(getTo(), emailBean.getTo())
&& Objects.equals(getReplyTo(), emailBean.getReplyTo())
&& Objects.equals(getMimeType(), emailBean.getMimeType())
&& Objects.equals(getBody(), emailBean.getBody())
&& Objects.equals(getSubject(), emailBean.getSubject())
&& Objects.equals(getAttachments(), emailBean.getAttachments());
}
@Override
public int hashCode() {
return Objects.hash(
getFrom(), getTo(), getReplyTo(), getMimeType(), getBody(), getSubject(), getAttachments());
}
@Override
public String toString() {
return "EmailBean{"
+ "from='"
+ from
+ '\''
+ ", to='"
+ to
+ '\''
+ ", replyTo='"
+ replyTo
+ '\''
+ ", mimeType='"
+ mimeType
+ '\''
+ ", body='"
+ body
+ '\''
+ ", subject='"
+ subject
+ '\''
+ ", attachments="
+ attachments
+ '}';
}
}

View File

@ -0,0 +1,162 @@
/*
* Copyright (c) 2012-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
*/
package org.eclipse.che.mail;
import static java.util.concurrent.Executors.newFixedThreadPool;
import com.google.common.io.Files;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import java.io.File;
import java.io.IOException;
import java.util.Base64;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
import javax.annotation.PreDestroy;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.mail.Message;
import javax.mail.Multipart;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import org.apache.commons.io.FileUtils;
import org.eclipse.che.commons.lang.concurrent.LoggingUncaughtExceptionHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Provides email sending capability
*
* @author Alexander Garagatyi
* @author Sergii Kabashniuk
*/
@Singleton
public class MailSender {
private static final Logger LOG = LoggerFactory.getLogger(MailSender.class);
private final ExecutorService executor;
private final MailSessionProvider mailSessionProvider;
@Inject
public MailSender(MailSessionProvider mailSessionProvider) {
this.mailSessionProvider = mailSessionProvider;
this.executor =
newFixedThreadPool(
2 * Runtime.getRuntime().availableProcessors(),
new ThreadFactoryBuilder()
.setNameFormat("MailNotificationsPool-%d")
.setDaemon(false)
.setUncaughtExceptionHandler(LoggingUncaughtExceptionHandler.getInstance())
.build());
}
public void sendAsync(EmailBean emailBean) {
executor.execute(
() -> {
try {
sendMail(emailBean);
} catch (Exception ex) {
LOG.warn(
"Failed to send email notification for {} with subject {}. Cause: '{}'",
emailBean.getTo(),
emailBean.getSubject(),
ex.getLocalizedMessage());
}
});
}
public void sendMail(EmailBean emailBean) throws SendMailException {
File tempDir = null;
try {
MimeMessage message = new MimeMessage(mailSessionProvider.get());
Multipart contentPart = new MimeMultipart();
MimeBodyPart bodyPart = new MimeBodyPart();
bodyPart.setText(emailBean.getBody(), "UTF-8", getSubType(emailBean.getMimeType()));
contentPart.addBodyPart(bodyPart);
if (emailBean.getAttachments() != null) {
tempDir = Files.createTempDir();
for (Attachment attachment : emailBean.getAttachments()) {
// Create attachment file in temporary directory
byte[] attachmentContent = Base64.getDecoder().decode(attachment.getContent());
File attachmentFile = new File(tempDir, attachment.getFileName());
Files.write(attachmentContent, attachmentFile);
// Attach the attachment file to email
MimeBodyPart attachmentPart = new MimeBodyPart();
attachmentPart.attachFile(attachmentFile);
attachmentPart.setContentID("<" + attachment.getContentId() + ">");
contentPart.addBodyPart(attachmentPart);
}
}
message.setContent(contentPart);
message.setSubject(emailBean.getSubject(), "UTF-8");
message.setFrom(new InternetAddress(emailBean.getFrom(), true));
message.setSentDate(new Date());
message.addRecipients(Message.RecipientType.TO, InternetAddress.parse(emailBean.getTo()));
if (emailBean.getReplyTo() != null) {
message.setReplyTo(InternetAddress.parse(emailBean.getReplyTo()));
}
LOG.info(
"Sending from {} to {} with subject {}",
emailBean.getFrom(),
emailBean.getTo(),
emailBean.getSubject());
Transport.send(message);
LOG.debug("Mail sent");
} catch (Exception e) {
LOG.error(e.getLocalizedMessage());
throw new SendMailException(e.getLocalizedMessage(), e);
} finally {
if (tempDir != null) {
try {
FileUtils.deleteDirectory(tempDir);
} catch (IOException exception) {
LOG.error(exception.getMessage());
}
}
}
}
/**
* Get the specified MIME subtype from given primary MIME type.
*
* <p>It is needed for setText method in MimeBodyPar because it works only with text MimeTypes.
* setText method in MimeBodyPar already adds predefined "text/" to given subtype.
*
* @param mimeType primary MIME type
* @return MIME subtype
*/
private String getSubType(String mimeType) {
return mimeType.substring(mimeType.lastIndexOf("/") + 1);
}
@PreDestroy
public void shutdown() throws InterruptedException {
executor.shutdown();
try {
if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
executor.shutdownNow();
if (!executor.awaitTermination(60, TimeUnit.SECONDS)) LOG.warn("Pool did not terminate");
}
} catch (InterruptedException ie) {
executor.shutdownNow();
Thread.currentThread().interrupt();
}
}
}

View File

@ -0,0 +1,84 @@
/*
* Copyright (c) 2012-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
*/
package org.eclipse.che.mail;
import com.google.common.annotations.VisibleForTesting;
import java.util.Map;
import java.util.Properties;
import java.util.stream.Collectors;
import javax.inject.Inject;
import javax.inject.Provider;
import javax.inject.Singleton;
import javax.mail.Authenticator;
import javax.mail.PasswordAuthentication;
import javax.mail.Session;
import org.eclipse.che.inject.ConfigurationProperties;
/** Provider of {@link Session} */
@Singleton
public class MailSessionProvider implements Provider<Session> {
private final Session session;
/**
* Configuration can be injected from container with help of {@lin ConfigurationProperties} class.
* In this case all properties that starts with 'che.mail.' will be used to create {@link
* Session}. First 4 letters 'che.' from property names will be removed.
*/
@Inject
public MailSessionProvider(ConfigurationProperties configurationProperties) {
this(
configurationProperties
.getProperties("che.mail.*")
.entrySet()
.stream()
.collect(Collectors.toMap(e -> e.getKey().substring(4), Map.Entry::getValue)));
}
@VisibleForTesting
MailSessionProvider(Map<String, String> mailConfiguration) {
if (mailConfiguration != null && !mailConfiguration.isEmpty()) {
Properties props = new Properties();
mailConfiguration.forEach(props::setProperty);
if (Boolean.parseBoolean(props.getProperty("mail.smtp.auth"))) {
final String username = props.getProperty("mail.smtp.auth.username");
final String password = props.getProperty("mail.smtp.auth.password");
// remove useless properties
props.remove("mail.smtp.auth.username");
props.remove("mail.smtp.auth.password");
this.session =
Session.getInstance(
props,
new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(username, password);
}
});
} else {
this.session = Session.getInstance(props);
}
} else {
this.session = null;
}
}
@Override
public Session get() {
if (session == null) {
throw new RuntimeException("SMTP is not configured");
}
return session;
}
}

View File

@ -0,0 +1,19 @@
/*
* Copyright (c) 2012-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
*/
package org.eclipse.che.mail;
/** Exception happened during mail sending * */
public class SendMailException extends Exception {
public SendMailException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -0,0 +1,182 @@
/*
* Copyright (c) 2012-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
*/
package org.eclipse.che.mail;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertTrue;
import com.dumbster.smtp.SimpleSmtpServer;
import com.dumbster.smtp.SmtpMessage;
import com.google.common.collect.ImmutableMap;
import java.io.IOException;
import java.util.Base64;
import java.util.Collections;
import java.util.Map;
import org.testng.ITestContext;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
public class MailSenderTest {
private MailSender mailSender;
private SimpleSmtpServer server;
public static void assertMail(
SimpleSmtpServer server,
String from,
String to,
String replyTo,
String subject,
String mimeType,
String body,
String attachmentContentID,
String attachmentFileName) {
assertEquals(server.getReceivedEmails().size(), 1);
SmtpMessage email = server.getReceivedEmails().iterator().next();
assertEquals(email.getHeaderValue("Subject"), subject);
assertEquals(email.getHeaderValue("From"), from);
assertEquals(email.getHeaderValue("Reply-To"), replyTo);
assertEquals(email.getHeaderValue("To"), to);
assertTrue(email.getBody().contains("Content-Type: " + mimeType));
assertTrue(email.getBody().contains(body));
if (attachmentFileName != null && attachmentContentID != null) {
assertTrue(email.getBody().contains("filename=" + attachmentFileName));
assertTrue(email.getBody().contains("Content-ID: <" + attachmentContentID + ">"));
}
}
@BeforeMethod
public void setup(ITestContext context) throws IOException {
server = SimpleSmtpServer.start(SimpleSmtpServer.AUTO_SMTP_PORT);
Map<String, String> mailConfiguration =
ImmutableMap.of(
"mail.smtp.host",
"localhost",
"mail.smtp.port",
server.getPort() + "",
"mail.transport.protocol",
"smtp",
" mail.smtp.auth",
"false");
mailSender = new MailSender(new MailSessionProvider(mailConfiguration));
}
@AfterMethod
public void stop() {
server.stop();
}
@Test
public void shouldBeAbleToSendMessage() throws SendMailException {
EmailBean emailBean =
new EmailBean()
.withFrom("noreply@cloud-ide.com")
.withTo("dev-test@cloud-ide.com")
.withReplyTo("dev-test@cloud-ide.com")
.withSubject("Subject")
.withMimeType("text/html")
.withBody("hello user");
mailSender.sendMail(emailBean);
assertMail(
server,
"noreply@cloud-ide.com",
"dev-test@cloud-ide.com",
"dev-test@cloud-ide.com",
"Subject",
"text/html",
"hello user",
null,
null);
}
@Test
public void shouldBeAbleToSendMessageWithFormattedFields() throws SendMailException {
EmailBean emailBean =
new EmailBean()
.withFrom("Exo IDE <noreply@cloud-ide.com>")
.withTo("dev-test@cloud-ide.com")
.withReplyTo("Developers to reply <dev-test@cloud-ide.com>")
.withSubject("Subject")
.withMimeType("text/html")
.withBody("hello user");
mailSender.sendMail(emailBean);
assertMail(
server,
"Exo IDE <noreply@cloud-ide.com>",
"dev-test@cloud-ide.com",
"Developers to reply <dev-test@cloud-ide.com>",
"Subject",
"text/html",
"hello user",
null,
null);
}
@Test
public void shouldBeAbleToSendMessageToFewEmails() throws SendMailException {
EmailBean emailBean =
new EmailBean()
.withFrom("noreply@cloud-ide.com")
.withTo("dev-test@cloud-ide.com, dev-test1@cloud-ide.com, dev-test2@cloud-ide.com")
.withReplyTo("dev-test@cloud-ide.com")
.withSubject("Subject")
.withMimeType("text/html")
.withBody("hello user");
mailSender.sendMail(emailBean);
assertMail(
server,
"noreply@cloud-ide.com",
"dev-test@cloud-ide.com, dev-test1@cloud-ide.com, dev-test2@cloud-ide.com",
"dev-test@cloud-ide.com",
"Subject",
"text/html",
"hello user",
null,
null);
}
@Test
public void shouldBeAbleToSendMessageWithAttachment() throws SendMailException {
EmailBean emailBean =
new EmailBean()
.withFrom("noreply@cloud-ide.com")
.withTo("dev-test@cloud-ide.com")
.withReplyTo("dev-test@cloud-ide.com")
.withSubject("Subject")
.withMimeType("text/html")
.withBody("hello user");
Attachment attachment =
new Attachment()
.withContentId("attachmentId")
.withFileName("attachment.txt")
.withContent(Base64.getEncoder().encodeToString("attachmentContent".getBytes(UTF_8)));
emailBean.setAttachments(Collections.singletonList(attachment));
mailSender.sendMail(emailBean);
assertMail(
server,
"noreply@cloud-ide.com",
"dev-test@cloud-ide.com",
"dev-test@cloud-ide.com",
"Subject",
"text/html",
"hello user",
"attachmentId",
"attachment.txt");
}
}

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (c) 2012-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
-->
<configuration>
<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%-41(%date[%.15thread]) %-45([%-5level] [%.30logger{30} %L]) - %msg%n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="stdout"/>
</root>
</configuration>

View File

@ -24,6 +24,7 @@
<name>Che Core :: Commons :: Parent</name>
<modules>
<module>che-core-commons-annotations</module>
<module>che-core-commons-auth</module>
<module>che-core-commons-lang</module>
<module>che-core-commons-inject</module>
<module>che-core-commons-json</module>
@ -31,5 +32,6 @@
<module>che-core-commons-schedule</module>
<module>che-core-commons-test</module>
<module>che-core-commons-j2ee</module>
<module>che-core-commons-mail</module>
</modules>
</project>

View File

@ -14,6 +14,7 @@
"angular-charts": "0.2.6",
"angular-cookies": "1.4.8",
"angular-dropdowns": "1.0.0",
"angular-gravatar": "0.2.4",
"angular-filter": "0.5.4",
"angular-material": "1.0.1",
"angular-moment": "0.9.0",

View File

@ -22,10 +22,9 @@ var serverOptions = {
var options = minimist(process.argv.slice(2), serverOptions);
var patterns = ['/api', '/ext', '/ws', '/datasource', '/java-ca', '/im', '/che', '/admin'];
var proxies = []
var patterns = ['/api', '/ext', '/ws', '/datasource', '/java-ca', '/im', '/che', '/admin', '/wsmaster'];
var proxies = [];
patterns.forEach(function(pattern) {
var proxyOptions = url.parse(options.server + pattern);
@ -37,6 +36,8 @@ patterns.forEach(function(pattern) {
proxyOptions.route = '/admin';
} else if (pattern === '/ext') {
proxyOptions.route = '/ext';
} else if (pattern === '/wsmaster') {
proxyOptions.route = '/wsmaster';
} else {
proxyOptions.route = '/api';
}

View File

@ -11,15 +11,16 @@
'use strict';
import {AdminsPluginsConfig} from './plugins/plugins-config';
import {AdminsUserManagementConfig} from './user-management/user-management-config';
/**
* @author Florent Benoit
*/
export class AdminsConfig {
constructor(register) {
constructor(register: che.IRegisterService) {
new AdminsPluginsConfig(register);
new AdminsUserManagementConfig(register);
}
}

View File

@ -0,0 +1,66 @@
/*
* Copyright (c) 2015-2017 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
'use strict';
interface IAccountProfileScope extends ng.IScope {
profileAttributes: {
phone?: string;
country?: string;
employer?: string;
jobtitle?: string;
lastName?: string;
firstName?: string;
};
profileInformationForm: ng.IFormController;
countries?: Array<{ 'name': string, 'code': string }>;
jobs?: Array<{ 'name': string }>;
}
/**
* @ngdoc directive
* @name account.profile.directive:accountProfile
* @restrict E
* @element
*
* @description
* <account-profile profile-attributes="ctrl.profileAttributes"></account-profile>` for displaying account profile.
*
* @usage
* <account-profile profile-attributes="ctrl.profileAttributes"></account-profile>
*
* @author Florent Benoit
*/
export class AccountProfile implements ng.IDirective {
restrict = 'E';
templateUrl = 'app/account/account-profile/account-profile.html';
replace = true;
scope = {
profileAttributes: '=profileAttributes',
profileInformationForm: '=?profileInformationForm'
};
jsonCountries: string;
jsonJobs: string;
/**
* Default constructor that is using resource
* @ngInject for Dependency injection
*/
constructor(jsonCountries: string, jsonJobs: string) {
this.jsonCountries = jsonCountries;
this.jsonJobs = jsonJobs;
}
link($scope: IAccountProfileScope) {
$scope.countries = angular.fromJson(this.jsonCountries);
$scope.jobs = angular.fromJson(this.jsonJobs);
}
}

View File

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

View File

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

View File

@ -0,0 +1,123 @@
/*
* Copyright (c) 2015-2017 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
'use strict';
import {AdminsUserManagementCtrl} from '../user-management.controller';
/**
* This class is handling the controller for the add user
* @author Oleksii Orel
*/
export class AdminsAddUserController {
private $mdDialog: ng.material.IDialogService;
private lodash: any;
private cheNotification: any;
private cheUser: any;
private callbackController: AdminsUserManagementCtrl;
private newUserName: string;
private newUserEmail: string;
private newUserPassword: string;
private organizations: Array<string>;
private organization: string;
private cheOrganization: che.api.ICheOrganization;
private chePermissions: che.api.IChePermissions;
private organizationRoles: che.resource.ICheOrganizationRoles;
/**
* Default constructor.
* @ngInject for Dependency injection
*/
constructor($mdDialog: ng.material.IDialogService,
cheUser: any,
cheNotification: any,
lodash: any,
cheOrganization: che.api.ICheOrganization,
chePermissions: che.api.IChePermissions,
resourcesService: che.service.IResourcesService) {
this.$mdDialog = $mdDialog;
this.lodash = lodash;
this.cheUser = cheUser;
this.cheNotification = cheNotification;
this.cheOrganization = cheOrganization;
this.chePermissions = chePermissions;
this.organizationRoles = resourcesService.getOrganizationRoles();
this.organizations = [];
this.cheOrganization.fetchOrganizations().then(() => {
let organizations = this.cheOrganization.getOrganizations();
let rootOrganizations = organizations.filter((organization: any) => {
return !organization.parent;
});
this.organizations = lodash.pluck(rootOrganizations, 'name');
if (this.organizations.length > 0) {
this.organization = this.organizations[0];
}
});
}
/**
* Callback of the cancel button of the dialog.
*/
abort(): void {
this.$mdDialog.hide();
}
/**
* Callback of the add button of the dialog(create new user).
*/
createUser(): void {
let promise = this.cheUser.createUser(this.newUserName, this.newUserEmail, this.newUserPassword);
promise.then((data: any) => {
if (this.organization) {
this.addUserToOrganization(data.id);
} else {
this.finish();
}
}, (error: any) => {
this.cheNotification.showError(error.data.message ? error.data.message : 'Failed to create user.');
});
}
/**
* Finish user creation.
*/
private finish(): void {
this.$mdDialog.hide();
this.callbackController.updateUsers();
this.cheNotification.showInfo('User successfully created.');
}
/**
* Adds user to chosen organization.
*
* @param userId
*/
private addUserToOrganization(userId: string): void {
let organizations = this.cheOrganization.getOrganizations();
let organization = this.lodash.find(organizations, (organization: any) => {
return organization.name === this.organization;
});
let actions = this.organizationRoles.MEMBER.actions;
let permissions = {
instanceId: organization.id,
userId: userId,
domainId: 'organization',
actions: actions
};
this.chePermissions.storePermissions(permissions).then(() => {
this.finish();
}, (error: any) => {
this.cheNotification.showError(error.data.message ? error.data.message : 'Failed to add user to organization' + this.organization + '.');
});
}
}

View File

@ -0,0 +1,95 @@
<!--
Copyright (c) 2015 Red Hat, Inc.
All rights reserved. This program and the accompanying materials
are made available under the terms of the Eclipse Public License v1.0
which accompanies this distribution, and is available at
http://www.eclipse.org/legal/epl-v10.html
Contributors:
Red Hat, Inc. - initial API and implementation
-->
<che-popup title="Add user" on-close="adminsAddUserController.abort()">
<ng-form name="createUserForm" class="admins-add-user-form" layout="column" align="center center">
<div class="form-input-fields">
<che-input-box che-form="createUserForm"
che-name="login"
che-label-name="User Login"
che-place-holder="login"
ng-model="adminsAddUserController.newUserName"
required
focusable
ng-maxlength="100">
<div ng-message="maxlength">User login to be less than 100 characters long.</div>
</che-input-box>
<che-input-box che-form="createUserForm"
che-name="email"
che-label-name="User Email"
che-place-holder="e-mail"
ng-maxlength="100"
type="email"
required
ng-model="adminsAddUserController.newUserEmail">
<div ng-message="email">Enter a valid email address.</div>
<div ng-message="maxlength">User email has to be less than 100 characters long</div>
</che-input-box>
<div layout="row" layout-align="start start" ng-if="adminsAddUserController.organizations.length > 0">
<label>Organization:</label>
<che-filter-selector che-values="adminsAddUserController.organizations"
che-width="400px"
ng-model="adminsAddUserController.organization"></che-filter-selector>
</div>
<che-input-box che-form="createUserForm"
che-name="password"
che-label-name="User Password"
che-place-holder="password"
che-pattern="^(?=.*[0-9]+.*)(?=.*[a-zA-Z]+.*).{0,}$"
ng-maxlength="100"
ng-minlength="8"
type="password"
required
ng-model="adminsAddUserController.newUserPassword">
<div ng-message="pattern">User password should contain both letters and digits</div>
<div ng-message="minlength">User password should contain at least 8 characters.</div>
<div ng-message="maxlength">User password has to be less than 100 characters long.</div>
</che-input-box>
<div class="password-prompt"
layout-xs="column" layout-sm="column" layout-md="column"
layout-gt-md="row" flex="100">
<div flex="50" flex-gt-md="25" class="password-prompt-text">
<span>Minimum 8 characters, both letters and digits.</span>
</div>
<div flex="50" flex-gt-md="50" layout="column" layout-align="center center">
<div class="pass-strength" ng-password-strength="adminsAddUserController.newUserPassword"
strength="passStrength" inner-class="password-meter" class="ng-isolate-scope"></div>
</div>
</div>
<che-input-box che-form="createUserForm"
che-name="confirmPassword"
che-label-name="Confirm Password"
che-place-holder="confirm password"
ng-model="confirmNewUserPassword"
ng-maxlength="100"
type="password"
required>
<div ng-message="maxlength">Confirm password has to be less than 100 characters long.</div>
<div
ng-show="confirmNewUserPassword && (confirmNewUserPassword !== adminsAddUserController.newUserPassword)">
Passwords do not match.
</div>
</che-input-box>
</div>
<div layout="row" layout-align="end center">
<che-button-notice che-button-title="Cancel"
ng-click="adminsAddUserController.abort()">
</che-button-notice>
<che-button-primary che-button-title="Create"
ng-click="adminsAddUserController.createUser()"
ng-disabled="createUserForm.$invalid || confirmNewUserPassword !== adminsAddUserController.newUserPassword">
</che-button-primary>
</div>
</ng-form>
</che-popup>

View File

@ -0,0 +1,48 @@
.admins-add-user-form
min-width 520px
.form-input-fields
margin 20px 0px
che-filter-selector
margin-bottom 20px
line-height 33px
.che-input-box-desktop
width 520px
max-width 520px
label
max-width 23% !important
width 23% !important
margin-top 10px
input
width 400px
float right
.admins-add-user-form .che-input-desktop-label
margin-top 1px
.admins-add-user-form .password-prompt,
.admins-add-user-form .che-input-desktop-label
min-width 125px
.admins-add-user-form .password-prompt
font-size 12px
color $disabled-color
margin-bottom 20px
.admins-add-user-form .password-prompt p
margin-top 5px
margin-bottom 5px
.admins-add-user-form .password-prompt
.password-prompt-text
min-width 125px
.pass-strength
width 100%
& > div
margin-bottom 10px

View File

@ -0,0 +1,243 @@
/*
* Copyright (c) 2015-2017 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
'use strict';
enum Tab {Profile, Organization}
interface IScope extends ng.IScope {
profileInformationForm: ng.IFormController;
}
interface IProfileAttributes {
firstName?: string;
lastName?: string;
phone?: string;
country?: string;
employer?: string;
jobtitle?: string;
}
const MAX_ITEMS = 12;
/**
* Controller for user details.
*
* @author Oleksii Orel
*/
export class AdminUserDetailsController {
tab: Object = Tab;
/**
* Angular Location service.
*/
private $location: ng.ILocationService;
/**
* User profile service.
*/
private cheProfile: any;
/**
* Notification service.
*/
private cheNotification: any;
/**
* Index of the selected tab.
*/
private selectedTabIndex: number = 0;
/**
* User profile.
*/
private profile: che.IProfile;
/**
* Profile attributes.
*/
private profileAttributes: IProfileAttributes;
/**
* Loading state of the page.
*/
private isLoading: boolean;
/**
* User ID.
*/
private userId: string;
/**
* User Name.
*/
private userName: string;
private cheOrganization: che.api.ICheOrganization;
private userOrganizations: Array<che.IOrganization>;
/**
* User's page info.
*/
private pageInfo: che.IPageInfo;
/**
* Default constructor that is using resource injection
* @ngInject for Dependency injection
*/
constructor(cheProfile: any, $location: ng.ILocationService, $timeout: ng.ITimeoutService, $scope: ng.IScope, cheNotification: any, cheOrganization: che.api.ICheOrganization, initData: {userId; userName}) {
this.cheOrganization = cheOrganization;
this.$location = $location;
this.cheProfile = cheProfile;
this.cheNotification = cheNotification;
this.userId = initData.userId;
this.userName = initData.userName;
this.updateSelectedTab(this.$location.search().tab);
let deRegistrationFn = $scope.$watch(() => {
return $location.search().tab;
}, (tab: string) => {
if (!angular.isUndefined(tab)) {
this.updateSelectedTab(tab);
}
}, true);
let timeoutPromise: ng.IPromise<any>;
$scope.$watch(() => {
return angular.isUndefined(this.profileAttributes) || this.profileAttributes;
}, () => {
if (!this.profileAttributes || !(<IScope>$scope).profileInformationForm || (<IScope>$scope).profileInformationForm.$invalid) {
return;
}
if (timeoutPromise) {
$timeout.cancel(timeoutPromise);
}
timeoutPromise = $timeout(() => {
this.setProfileAttributes();
}, 500);
}, true);
$scope.$on('$destroy', () => {
deRegistrationFn();
if (timeoutPromise) {
$timeout.cancel(timeoutPromise);
}
});
this.updateData();
}
/**
* Update user's data.
*/
updateData(): void {
this.isLoading = true;
this.cheProfile.fetchProfileById(this.userId).then(() => {
this.profile = this.cheProfile.getProfileById(this.userId);
this.profileAttributes = angular.copy(this.profile.attributes);
this.fetchOrganizations();
}, (error: any) => {
this.isLoading = false;
this.cheNotification.showError(error && error.data && error.data.message !== null ? error.data.message : 'Failed to retrieve user\'s profile.');
});
}
/**
* Request the list of the user's organizations (first page).
*
* @returns {ng.IPromise<any>}
*/
fetchOrganizations(): void {
this.isLoading = true;
this.cheOrganization.fetchUserOrganizations(this.userId, MAX_ITEMS).then((userOrganizations: Array<che.IOrganization>) => {
this.userOrganizations = userOrganizations;
}, (error: any) => {
this.cheNotification.showError(error && error.data && error.data.message !== null ? error.data.message : 'Failed to retrieve organizations.');
}).finally(() => {
this.isLoading = false;
this.pageInfo = this.cheOrganization.getUserOrganizationPageInfo(this.userId);
});
}
/**
* Returns the array of user's organizations.
*
* @returns {Array<any>}
*/
getUserOrganizations(): Array<che.IOrganization> {
return this.userOrganizations;
}
/**
* Returns the the user's page info.
*
* @returns {che.IPageInfo}
*/
getPagesInfo(): che.IPageInfo {
return this.pageInfo;
}
/**
* Request the list of the user's organizations for a page depends on page key('first', 'prev', 'next', 'last').
* @param key {string}
*/
fetchOrganizationPageObjects(key: string): void {
this.isLoading = true;
this.cheOrganization.fetchUserOrganizationPageObjects(this.userId, key).then((userOrganizations: Array<che.IOrganization>) => {
this.userOrganizations = userOrganizations;
}).finally(() => {
this.isLoading = false;
});
}
/**
* Check if profile attributes have changed
* @returns {boolean}
*/
isAttributesChanged(): boolean {
return !angular.equals(this.profile.attributes, this.profileAttributes);
}
/**
* Set profile attributes
*/
setProfileAttributes(): void {
if (angular.equals(this.profile.attributes, this.profileAttributes)) {
return;
}
let promise = this.cheProfile.setAttributes(this.profileAttributes, this.userId);
promise.then(() => {
this.cheNotification.showInfo('Profile successfully updated.');
this.updateData();
}, (error: any) => {
this.profileAttributes = angular.copy(this.profile.attributes);
this.cheNotification.showError(error.data.message ? error.data.message : 'Profile update failed.');
});
}
/**
* Update selected tab index by search part of URL.
*
* @param {string} tab
*/
updateSelectedTab(tab: string): void {
this.selectedTabIndex = parseInt(this.tab[tab], 10);
}
/**
* Changes search part of URL.
*
* @param {number} tabIndex
*/
onSelectTab(tabIndex?: number): void {
let param: { tab?: string } = {};
if (!angular.isUndefined(tabIndex)) {
param.tab = Tab[tabIndex];
}
if (angular.isUndefined(this.$location.search().tab)) {
this.$location.replace();
}
this.$location.search(param);
}
}

View File

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

View File

@ -0,0 +1,76 @@
/*
* Copyright (c) 2015-2017 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
'use strict';
import {AdminsAddUserController} from './add-user/add-user.controller';
import {AdminsUserManagementCtrl} from './user-management.controller';
import {AdminUserDetailsController} from './user-details/user-details.controller';
import {AccountProfile} from './account-profile/account-profile.directive';
export class AdminsUserManagementConfig {
constructor(register: che.IRegisterService) {
register.controller('AdminUserDetailsController', AdminUserDetailsController);
register.controller('AdminsAddUserController', AdminsAddUserController);
register.controller('AdminsUserManagementCtrl', AdminsUserManagementCtrl);
register.directive('accountProfile', AccountProfile);
const userDetailLocationProvider = {
title: 'User Details',
reloadOnSearch: false,
templateUrl: 'app/admin/user-management/user-details/user-details.html',
controller: 'AdminUserDetailsController',
controllerAs: 'adminUserDetailsController',
resolve: {
initData: ['$q', 'cheUser', '$route', 'chePermissions', ($q: ng.IQService, cheUser: any, $route: any, chePermissions: che.api.IChePermissions) => {
const userId = $route.current.params.userId;
let defer = $q.defer();
chePermissions.fetchSystemPermissions().finally(() => {
cheUser.fetchUserId(userId).then((user: che.IUser) => {
if (!chePermissions.getUserServices().hasAdminUserService) {
defer.reject();
}
defer.resolve({userId: userId, userName: user.name});
}, (error: any) => {
defer.reject(error);
});
});
return defer.promise;
}]
}
};
// configure routes
register.app.config(($routeProvider: che.route.IRouteProvider) => {
$routeProvider.accessWhen('/admin/usermanagement', {
title: 'Users',
templateUrl: 'app/admin/user-management/user-management.html',
controller: 'AdminsUserManagementCtrl',
controllerAs: 'adminsUserManagementCtrl',
resolve: {
check: ['$q', 'chePermissions', ($q: ng.IQService, chePermissions: che.api.IChePermissions) => {
let defer = $q.defer();
chePermissions.fetchSystemPermissions().finally(() => {
if (chePermissions.getUserServices().hasUserService) {
defer.resolve();
} else {
defer.reject();
}
});
return defer.promise;
}]
}
})
.accessWhen('/admin/userdetails/:userId', userDetailLocationProvider);
});
}
}

View File

@ -0,0 +1,298 @@
/*
* Copyright (c) 2015-2017 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
'use strict';
const MAX_ITEMS = 12;
/**
* This class is handling the controller for the admins user management
* @author Oleksii Orel
*/
export class AdminsUserManagementCtrl {
$q: ng.IQService;
$log: ng.ILogService;
$mdDialog: ng.material.IDialogService;
$location: ng.ILocationService;
cheUser: any;
cheNotification: any;
pagesInfo: any;
users: Array<any>;
usersMap: Map<string, any>;
userFilter: {name: string};
userOrderBy: string;
isLoading: boolean;
private confirmDialogService: any;
private cheOrganization: che.api.ICheOrganization;
private userOrganizationCount: {[userId: string]: number} = {};
private cheListHelper: che.widget.ICheListHelper;
/**
* Default constructor.
* @ngInject for Dependency injection
*/
constructor($q: ng.IQService,
$rootScope: che.IRootScopeService,
$log: ng.ILogService,
$mdDialog: ng.material.IDialogService,
cheUser: any,
$location: ng.ILocationService,
cheNotification: any,
confirmDialogService: any,
cheOrganization: che.api.ICheOrganization,
$scope: ng.IScope,
cheListHelperFactory: che.widget.ICheListHelperFactory) {
this.$q = $q;
this.$log = $log;
this.$mdDialog = $mdDialog;
this.$location = $location;
this.cheUser = cheUser;
this.cheOrganization = cheOrganization;
this.cheNotification = cheNotification;
this.confirmDialogService = confirmDialogService;
$rootScope.showIDE = false;
this.isLoading = false;
this.users = [];
this.usersMap = this.cheUser.getUsersMap();
this.userOrderBy = 'name';
this.userFilter = {name: ''};
const helperId = 'user-management';
this.cheListHelper = cheListHelperFactory.getHelper(helperId);
$scope.$on('$destroy', () => {
cheListHelperFactory.removeHelper(helperId);
});
if (this.usersMap && this.usersMap.size > 1) {
this.updateUsers();
} else {
this.isLoading = true;
this.cheUser.fetchUsers(MAX_ITEMS, 0).then(() => {
this.isLoading = false;
this.updateUsers();
}, (error: any) => {
this.isLoading = false;
if (error && error.status !== 304) {
this.cheNotification.showError(error.data && error.data.message ? error.data.message : 'Failed to retrieve the list of users.');
}
});
}
this.pagesInfo = this.cheUser.getPagesInfo();
}
/**
* Callback when name is changed.
*
* @param str {string} a string to filter user names.
*/
onSearchChanged(str: string): void {
this.userFilter.name = str;
this.cheListHelper.applyFilter('name', this.userFilter);
}
/**
* Redirect to user details
* @param userId {string}
* @param tab {string}
*/
redirectToUserDetails(userId: string, tab?: string): void {
this.$location.path('/admin/userdetails/' + userId).search(!tab ? {} : {tab: tab});
}
/**
* Update user's organizations count
* @param userId {string}
*/
updateUserOrganizationsCount(userId: string): void {
this.cheOrganization.fetchUserOrganizations(userId, 1).then((userOrganizations: Array<any>) => {
if (!angular.isArray(userOrganizations) || userOrganizations.length === 0) {
return;
}
this.userOrganizationCount[userId] = this.cheOrganization.getUserOrganizationPageInfo(userId).countPages;
});
}
/**
* User clicked on the - action to remove the user. Show the dialog
* @param event {MouseEvent} - the $event
* @param user {any} - the selected user
*/
removeUser(event: MouseEvent, user: any): void {
let content = 'Are you sure you want to remove \'' + user.email + '\'?';
let promise = this.confirmDialogService.showConfirmDialog('Remove user', content, 'Delete', 'Cancel');
promise.then(() => {
this.isLoading = true;
let promise = this.cheUser.deleteUserById(user.id);
promise.then(() => {
this.isLoading = false;
this.updateUsers();
}, (error: any) => {
this.isLoading = false;
this.cheNotification.showError(error.data && error.data.message ? error.data.message : 'Delete user failed.');
});
});
}
/**
* Delete all selected users
*/
deleteSelectedUsers(): void {
const selectedUsers = this.cheListHelper.getSelectedItems(),
selectedUserIds = selectedUsers.map((user: che.IUser) => {
return user.id;
});
const queueLength = selectedUserIds.length;
if (!queueLength) {
this.cheNotification.showError('No such user.');
return;
}
const confirmationPromise = this.showDeleteUsersConfirmation(queueLength);
confirmationPromise.then(() => {
const numberToDelete = queueLength;
const deleteUserPromises = [];
let isError = false;
let currentUserId;
selectedUserIds.forEach((userId: string) => {
currentUserId = userId;
this.cheListHelper.itemsSelectionStatus[userId] = false;
let promise = this.cheUser.deleteUserById(userId);
promise.catch((error: any) => {
isError = true;
this.$log.error('Cannot delete user: ', error);
});
deleteUserPromises.push(promise);
});
this.$q.all(deleteUserPromises).finally(() => {
this.isLoading = true;
const promise = this.cheUser.fetchUsersPage(this.pagesInfo.currentPageNumber);
promise.then(() => {
this.isLoading = false;
this.updateUsers();
}, (error: any) => {
this.isLoading = false;
this.$log.error(error);
});
if (isError) {
this.cheNotification.showError('Delete failed.');
} else {
if (numberToDelete === 1) {
this.cheNotification.showInfo('Selected user has been removed.');
} else {
this.cheNotification.showInfo('Selected users have been removed.');
}
}
});
});
}
/**
* Show confirmation popup before delete
* @param numberToDelete {number}
* @returns {angular.IPromise<any>}
*/
showDeleteUsersConfirmation(numberToDelete: number): angular.IPromise<any> {
let content = 'Are you sure you want to remove ' + numberToDelete + ' selected ';
if (numberToDelete > 1) {
content += 'users?';
} else {
content += 'user?';
}
return this.confirmDialogService.showConfirmDialog('Remove users', content, 'Delete', 'Cancel');
}
/**
* Update users array
*/
updateUsers(): void {
// update users array
this.users.length = 0;
this.usersMap.forEach((user: any) => {
this.users.push(user);
});
this.cheListHelper.setList(this.users, 'id');
}
/**
* Ask for loading the users page in asynchronous way
* @param pageKey {string} - the key of page
*/
fetchUsersPage(pageKey: string): void {
this.isLoading = true;
let promise = this.cheUser.fetchUsersPage(pageKey);
promise.then(() => {
this.isLoading = false;
this.updateUsers();
}, (error: any) => {
this.isLoading = false;
if (error.status === 304) {
this.updateUsers();
} else {
this.cheNotification.showError(error.data && error.data.message ? error.data.message : 'Update information failed.');
}
});
}
/**
* Returns true if the next page is exist.
* @returns {boolean}
*/
hasNextPage(): boolean {
return this.pagesInfo.currentPageNumber < this.pagesInfo.countOfPages;
}
/**
* Returns true if the previous page is exist.
* @returns {boolean}
*/
hasPreviousPage(): boolean {
return this.pagesInfo.currentPageNumber > 1;
}
/**
* Returns true if we have more then one page.
* @returns {boolean}
*/
isPagination(): boolean {
return this.pagesInfo.countOfPages > 1;
}
/**
* Add a new user. Show the dialog
* @param event {MouseEvent} - the $event
*/
showAddUserDialog(event: MouseEvent): void {
this.$mdDialog.show({
targetEvent: event,
bindToController: true,
clickOutsideToClose: true,
controller: 'AdminsAddUserController',
controllerAs: 'adminsAddUserController',
locals: {callbackController: this},
templateUrl: 'app/admin/user-management/add-user/add-user.html'
});
}
}

View File

@ -0,0 +1,147 @@
<!--
Copyright (c) 2015 Red Hat, Inc.
All rights reserved. This program and the accompanying materials
are made available under the terms of the Eclipse Public License v1.0
which accompanies this distribution, and is available at
http://www.eclipse.org/legal/epl-v10.html
Contributors:
Red Hat, Inc. - initial API and implementation
-->
<che-toolbar che-title="User Management"></che-toolbar>
<md-content flex md-scroll-y md-theme="maincontent-theme" class="admins-user-management">
<div class="progress-line">
<md-progress-linear ng-show="adminsUserManagementCtrl.isLoading" md-mode="indeterminate"></md-progress-linear>
</div>
<div>
<che-list-header flex="100"
che-input-placeholder="Search"
che-search-model="adminsUserManagementCtrl.userFilter.name"
che-on-search-change="adminsUserManagementCtrl.onSearchChanged(str)"
che-hide-search="adminsUserManagementCtrl.users.length === 0"
che-add-button-title="Add User"
che-on-add="adminsUserManagementCtrl.showAddUserDialog($event)"
che-delete-button-title="Delete"
che-on-delete="adminsUserManagementCtrl.deleteSelectedUsers()"
che-hide-delete="adminsUserManagementCtrl.cheListHelper.isNoItemSelected"
che-hide-header="adminsUserManagementCtrl.cheListHelper.visibleItemsNumber === 0">
<div flex="100"
layout="row"
layout-align="start stretch"
class="che-list-item-row">
<div layout="column" layout-gt-xs="row"
layout-align="start center"
class="che-checkbox-area">
<div layout="row" layout-align="start center" class="che-list-item-checkbox-main">
<md-checkbox class="che-list-item-checkbox"
aria-label="Bulk check on users"
ng-checked="adminsUserManagementCtrl.cheListHelper.areAllItemsSelected"
ng-click="adminsUserManagementCtrl.cheListHelper.changeBulkSelection()"></md-checkbox>
</div>
</div>
<div flex hide-xs layout-gt-xs="row"
layout-align="start center"
class="che-list-item-details">
<che-list-header-column flex-gt-xs="40"
che-sort-value='adminsUserManagementCtrl.userOrderBy'
che-sort-item='email'
che-column-title='Email'></che-list-header-column>
<che-list-header-column flex-gt-xs="20"
che-sort-value='adminsUserManagementCtrl.userOrderBy'
che-sort-item='name'
che-column-title='Login'></che-list-header-column>
<che-list-header-column flex-gt-xs="20"
che-column-title='Organizations'></che-list-header-column>
<che-list-header-column flex-gt-xs="20"
che-column-title='Actions'></che-list-header-column>
</div>
</div>
</che-list-header>
<che-list flex ng-if="adminsUserManagementCtrl.users && adminsUserManagementCtrl.users.length > 0">
<che-list-item
ng-repeat="user in adminsUserManagementCtrl.cheListHelper.getVisibleItems() | orderBy:adminsUserManagementCtrl.userOrderBy"
ng-init="adminsUserManagementCtrl.updateUserOrganizationsCount(user.id)"
flex-gt-sm="100" flex="33"
ng-mouseover="hover=true"
ng-mouseout="hover=false">
<div flex="100"
layout="row"
layout-align="start stretch"
class="che-list-item-row">
<div layout="row"
layout-align="start center"
class="che-checkbox-area">
<che-list-item-checked ng-model="adminsUserManagementCtrl.cheListHelper.itemsSelectionStatus[user.id]"
ng-click="adminsUserManagementCtrl.cheListHelper.updateBulkSelectionStatus()"
che-aria-label-checkbox="User {{user.id}}"></che-list-item-checked>
</div>
<div flex
layout-xs="column" layout-gt-xs="row"
layout-align-gt-xs="start center"
layout-align-xs="start start"
class="che-list-item-details">
<div flex-gt-xs="40"
class="che-list-item-name"
ng-click="adminsUserManagementCtrl.redirectToUserDetails(user.id)">
<span class="che-xs-header noselect" hide-gt-xs>Email</span>
<span><img class="user-face" gravatar-src="user.email"></span>
<span class="user-email">{{user.email}}</span>
</div>
<div flex-gt-xs="20"
ng-click="adminsUserManagementCtrl.redirectToUserDetails(user.id)">
<span class="che-xs-header noselect" hide-gt-xs>Login</span>
<span class="user-description">{{user.name}}</span>
</div>
<div flex-gt-xs="20"
ng-click="adminsUserManagementCtrl.redirectToUserDetails(user.id, 'Organization')">
<span class="che-xs-header noselect" hide-gt-xs>Organizations</span>
<span class="user-description">{{adminsUserManagementCtrl.userOrganizationCount[user.id] ? adminsUserManagementCtrl.userOrganizationCount[user.id] : '-'}}</span>
</div>
<div flex-gt-xs="20"
ng-click="adminsUserManagementCtrl.redirectToUserDetails(user.id)">
<span class="che-xs-header noselect" hide-gt-xs>Actions</span>
<span class="che-list-actions">
<div ng-click="adminsUserManagementCtrl.removeUser($event, user);" uib-tooltip="Remove user">
<span class="material-design icon-ic_remove_circle_outline_24px"></span>
</div>
</span>
</div>
</div>
</div>
</che-list-item>
<div class="paging-buttons-area" ng-if="adminsUserManagementCtrl.isPagination()">
<md-button
ng-disabled="!adminsUserManagementCtrl.hasPreviousPage()"
ng-click="adminsUserManagementCtrl.fetchUsersPage('first');">
<span><<</span>
</md-button>
<md-button
ng-disabled="!adminsUserManagementCtrl.hasPreviousPage()"
ng-click="adminsUserManagementCtrl.fetchUsersPage('prev');">
<span><</span>
</md-button>
<md-button disabled>
<span>{{adminsUserManagementCtrl.pagesInfo.currentPageNumber}}</span>
</md-button>
<md-button
ng-disabled="!adminsUserManagementCtrl.hasNextPage()"
ng-click="adminsUserManagementCtrl.fetchUsersPage('next');">
<span>></span>
</md-button>
<md-button
ng-disabled="!adminsUserManagementCtrl.hasNextPage()"
ng-click="adminsUserManagementCtrl.fetchUsersPage('last');">
<span>>></span>
</md-button>
</div>
</che-list>
<div class="che-list-empty">
<span ng-show="adminsUserManagementCtrl.users.length > 0 && adminsUserManagementCtrl.cheListHelper.visibleItemsNumber === 0">
No users found.
</span>
<span ng-show="adminsUserManagementCtrl.users.length === 0">There are no users.</span>
</div>
</div>
</md-content>

View File

@ -0,0 +1,17 @@
.admins-user-management
.user-email,
.user-description
max-width 100%
.user-description
color $label-info-color
.user-face
che-developers-face()
height 16px
width 16px
margin-right 5px
.che-list-item
outline none
user-select none
*
outline inherit

View File

@ -36,12 +36,81 @@ 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
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', 'uuid4', 'angularFileUpload']);
'ngDropdowns', 'ngLodash', 'angularCharts', '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<any>, reject: IRejectFn<any>) => {
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<any>, reject: IRejectFn<any>) => {
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<any>, reject: IRejectFn<any>) => {
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) => {
@ -92,7 +161,7 @@ initModule.config(['$routeProvider', ($routeProvider: che.route.IRouteProvider)
const DEV = false;
// configs
initModule.config(['$routeProvider', ($routeProvider: che.route.IRouteProvider) => {
initModule.config(['$routeProvider', ($routeProvider) => {
// config routes (add demo page)
if (DEV) {
$routeProvider.accessWhen('/demo-components', {
@ -113,13 +182,11 @@ initModule.config(['$routeProvider', ($routeProvider: che.route.IRouteProvider)
* Setup route redirect module
*/
initModule.run(['$rootScope', '$location', '$routeParams', 'routingRedirect', '$timeout', 'ideIFrameSvc', 'cheIdeFetcher', 'routeHistory', 'cheUIElementsInjectorService', 'workspaceDetailsService',
($rootScope: che.IRootScopeService, $location: ng.ILocationService, $routeParams: ng.route.IRouteParamsService, routingRedirect: RoutingRedirect, $timeout: ng.ITimeoutService, ideIFrameSvc: IdeIFrameSvc, cheIdeFetcher: CheIdeFetcher, routeHistory: RouteHistory, cheUIElementsInjectorService: CheUIElementsInjectorService, workspaceDetailsService: WorkspaceDetailsService) => {
($rootScope: che.IRootScopeService, $location: ng.ILocationService, $routeParams: ng.route.IRouteParamsService, routingRedirect: RoutingRedirect, $timeout: ng.ITimeoutService, ideIFrameSvc: IdeIFrameSvc, cheIdeFetcher: CheIdeFetcher, routeHistory: RouteHistory, cheUIElementsInjectorService: CheUIElementsInjectorService) => {
$rootScope.hideLoader = false;
$rootScope.waitingLoaded = false;
$rootScope.showIDE = false;
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;
@ -172,45 +239,6 @@ initModule.run(['$rootScope', '$location', '$routeParams', 'routingRedirect', '$
}
]);
// add interceptors
initModule.factory('ETagInterceptor', ($window: ng.IWindowService, $cookies: ng.cookies.ICookiesService, $q: ng.IQService) => {
const etagMap = {};
return {
request: (config: any) => {
// add IfNoneMatch request on the che api if there is an existing eTag
if ('GET' === config.method) {
if (config.url.indexOf('/api') === 0) {
const 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: any) => {
// if response is ok, keep ETag
if ('GET' === response.config.method) {
if (response.status === 200) {
const 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: ng.material.IThemingProvider, jsonColors: any) => {
const cheColors = angular.fromJson(jsonColors);
@ -357,11 +385,6 @@ initModule.constant('userDashboardConfig', {
developmentMode: DEV
});
initModule.config(['$routeProvider', '$httpProvider', ($routeProvider: che.route.IRouteProvider, $httpProvider: ng.IHttpProvider) => {
// add the ETag interceptor for Che API
$httpProvider.interceptors.push('ETagInterceptor');
}]);
const instanceRegister = new Register(initModule);
if (DEV) {
@ -386,4 +409,6 @@ new WorkspacesConfig(instanceRegister);
new DashboardConfig(instanceRegister);
new StacksConfig(instanceRegister);
new FactoryConfig(instanceRegister);
new OrganizationsConfig(instanceRegister);
new TeamsConfig(instanceRegister);
/* tslint:enable */

View File

@ -10,9 +10,10 @@
*/
'use strict';
import {CheAPI} from '../../components/api/che-api.factory';
import {CheKeycloak, keycloakUserInfo} from '../../components/api/che-keycloak.factory';
export class CheNavBarController {
menuItemUrl = {
private menuItemUrl = {
dashboard: '#/',
workspaces: '#/workspaces',
administration: '#/administration',
@ -20,9 +21,26 @@ export class CheNavBarController {
plugins: '#/admin/plugins',
factories: '#/factories',
account: '#/account',
stacks: '#/stacks'
stacks: '#/stacks',
organizations: '#/organizations',
usermanagement: '#/admin/usermanagement'
};
accountItems = [
{
name: 'Go to Profile',
onclick: () => {
this.gotoProfile();
}
},
{
name: 'Logout',
onclick: () => {
this.logout();
}
}
];
private $mdSidenav: ng.material.ISidenavService;
private $scope: ng.IScope;
private $window: ng.IWindowService;
@ -30,21 +48,39 @@ export class CheNavBarController {
private $route: ng.route.IRouteService;
private cheAPI: CheAPI;
private profile: che.IProfile;
private chePermissions: che.api.IChePermissions;
private userServices: che.IUserServices;
private hasPersonalAccount: boolean;
private organizations: Array<che.IOrganization>;
private cheKeycloak: CheKeycloak;
private userInfo: keycloakUserInfo;
/**
* Default constructor
* @ngInject for Dependency injection
*/
constructor($mdSidenav: ng.material.ISidenavService, $scope: ng.IScope, $location: ng.ILocationService, $route: ng.route.IRouteService, cheAPI: CheAPI, $window: ng.IWindowService) {
constructor($mdSidenav: ng.material.ISidenavService,
$scope: ng.IScope,
$location: ng.ILocationService,
$route: ng.route.IRouteService,
cheAPI: CheAPI,
$window: ng.IWindowService,
chePermissions: che.api.IChePermissions,
cheKeycloak: CheKeycloak) {
this.$mdSidenav = $mdSidenav;
this.$scope = $scope;
this.$location = $location;
this.$route = $route;
this.cheAPI = cheAPI;
this.$window = $window;
this.chePermissions = chePermissions;
this.cheKeycloak = cheKeycloak;
this.userInfo = null;
this.profile = cheAPI.getProfile().getProfile();
this.userServices = this.chePermissions.getUserServices();
// highlight navbar menu item
$scope.$on('$locationChangeStart', () => {
let path = '#' + $location.path();
@ -53,6 +89,34 @@ export class CheNavBarController {
cheAPI.getWorkspace().fetchWorkspaces();
cheAPI.getFactory().fetchFactories();
if (this.cheKeycloak.isPresent()) {
this.cheKeycloak.fetchUserInfo().then((userInfo: keycloakUserInfo) => {
this.userInfo = userInfo;
});
}
if (this.chePermissions.getSystemPermissions()) {
this.updateData();
} else {
this.chePermissions.fetchSystemPermissions().finally(() => {
this.updateData();
});
}
}
/**
* Update data.
*/
updateData(): void {
const organization = this.cheAPI.getOrganization();
organization.fetchOrganizations().then(() => {
this.organizations = organization.getOrganizations();
const user = this.cheAPI.getUser().getUser();
organization.fetchOrganizationByName(user.name).finally(() => {
this.hasPersonalAccount = angular.isDefined(organization.getOrganizationByName(user.name));
});
});
}
reload(): void {
@ -66,15 +130,63 @@ export class CheNavBarController {
this.$mdSidenav('left').toggle();
}
/**
* Returns number of workspaces.
*
* @return {number}
*/
getWorkspacesNumber(): number {
return this.cheAPI.getWorkspace().getWorkspaces().length;
}
/**
* Returns number of factories.
*
* @return {number}
*/
getFactoriesNumber(): number {
return this.cheAPI.getFactory().getPageFactories().length;
}
/**
* Returns number of all organizations.
*
* @return {number}
*/
getOrganizationsNumber(): number {
if (!this.organizations) {
return 0;
}
return this.organizations.length;
}
openLinkInNewTab(url: string): void {
this.$window.open(url, '_blank');
}
/**
* Returns <code>true</code> if Keycloak is present.
*
* @returns {boolean}
*/
isKeycloakPresent(): boolean {
return this.cheKeycloak.isPresent();
}
/**
* Opens user profile in new browser page.
*/
gotoProfile(): void {
const url = this.cheKeycloak.getProfileUrl();
this.$window.open(url);
}
/**
* Logout.
*/
logout(): void {
this.cheKeycloak.logout();
}
}

View File

@ -30,7 +30,7 @@ export class CheNavBar {
this.replace = false;
this.templateUrl = 'app/navbar/navbar.html';
this.controller = 'CheNavBarController';
this.controllerAs = 'navbarCtrl';
this.controllerAs = 'navbarController';
}
}

View File

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

View File

@ -0,0 +1,166 @@
/*
* Copyright (c) 2015-2017 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
'use strict';
/**
* @ngdoc controller
* @name organizations.create.controller:CreateOrganizationController
* @description This class is handling the controller for the new organization creation.
* @author Oleksii Orel
*/
export class CreateOrganizationController {
/**
* Organization API interaction.
*/
private cheOrganization: che.api.ICheOrganization;
/**
* User API interaction.
*/
private cheUser: any;
/**
* Permissions API interaction.
*/
private chePermissions: che.api.IChePermissions;
/**
* Notifications service.
*/
private cheNotification: any;
/**
* Location service.
*/
private $location: ng.ILocationService;
/**
* Log service.
*/
private $log: ng.ILogService;
/**
* Promises service.
*/
private $q: ng.IQService;
/**
* Current organization's name.
*/
private organizationName: string;
/**
* Loading state of the page.
*/
private isLoading: boolean;
/**
* The list of users to invite.
*/
private members: Array<che.IMember>;
/**
* Parent organization name.
*/
private parentQualifiedName: string;
/**
* Parent organization id.
*/
private parentOrganizationId: string;
/**
* List of members of parent organization.
*/
private parentOrganizationMembers: Array<che.IUser>;
/**
* Default constructor
* @ngInject for Dependency injection
*/
constructor(cheOrganization: che.api.ICheOrganization, chePermissions: che.api.IChePermissions, cheUser: any, cheNotification: any,
$location: ng.ILocationService, $q: ng.IQService, $log: ng.ILogService, $rootScope: che.IRootScopeService,
initData: any) {
this.cheOrganization = cheOrganization;
this.chePermissions = chePermissions;
this.cheUser = cheUser;
this.cheNotification = cheNotification;
this.$location = $location;
this.$q = $q;
this.$log = $log;
$rootScope.showIDE = false;
this.organizationName = '';
this.isLoading = false;
this.members = [];
// injected by route provider
this.parentQualifiedName = initData.parentQualifiedName;
this.parentOrganizationId = initData.parentOrganizationId;
this.parentOrganizationMembers = initData.parentOrganizationMembers;
}
/**
* Check if the name is unique.
* @param name
* @returns {boolean}
*/
isUniqueName(name: string): boolean {
let organizations = this.cheOrganization.getOrganizations();
let account = this.parentQualifiedName ? this.parentQualifiedName + '/' : '';
if (!organizations.length) {
return true;
} else {
for (let i = 0; i < organizations.length; i++) {
if (organizations[i].qualifiedName === account + name) {
return false;
}
}
return true;
}
}
/**
* Performs new organization creation.
*/
createOrganization(): void {
this.isLoading = true;
this.cheOrganization.createOrganization(this.organizationName, this.parentOrganizationId).then((organization: che.IOrganization) => {
this.addPermissions(organization, this.members);
this.cheOrganization.fetchOrganizations();
}, (error: any) => {
this.isLoading = false;
let message = error.data && error.data.message ? error.data.message : 'Failed to create organization ' + this.organizationName + '.';
this.cheNotification.showError(message);
});
}
/**
* Add permissions for members in pointed organization.
*
* @param organization {che.IOrganization} organization
* @param members members to be added to organization
*/
addPermissions(organization: che.IOrganization, members: Array<any>) {
let promises = [];
members.forEach((member: che.IMember) => {
if (member.id && member.id !== this.cheUser.getUser().id) {
let actions = this.cheOrganization.getActionsFromRoles(member.roles);
let permissions = {
instanceId: organization.id,
userId: member.id,
domainId: 'organization',
actions: actions
};
let promise = this.chePermissions.storePermissions(permissions);
promises.push(promise);
}
});
this.$q.all(promises).then(() => {
this.isLoading = false;
this.$location.path('/organization/' + organization.qualifiedName);
}, (error: any) => {
this.isLoading = false;
let message = error.data && error.data.message ? error.data.message : 'Failed to create organization ' + this.organizationName + '.';
this.cheNotification.showError(message);
});
}
}

View File

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

View File

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

View File

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

View File

@ -0,0 +1,42 @@
/*
* Copyright (c) 2015-2017 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
'use strict';
/**
* @ngdoc directive
* @name organizations.organizations:ListOrganizations
* @restrict E
* @element
*
* @description
* `<list-organizations organizations="ctrl.organizations"></list-organizations>` for displaying list of organizations
*
* @usage
* <list-organizations organizations="ctrl.organizations"></list-organizations>
*
* @author Oleksii Orel
*/
export class ListOrganizations implements ng.IDirective {
restrict: string = 'E';
templateUrl: string = 'app/organizations/list-organizations/list-organizations.html';
controller: string = 'ListOrganizationsController';
controllerAs: string = 'listOrganizationsController';
bindToController: boolean = true;
scope: any = {
isLoading: '=?',
organizations: '=',
hideAddButton: '=?',
onUpdate: '&?onUpdate'
};
}

View File

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

View File

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

View File

@ -0,0 +1,128 @@
/*
* Copyright (c) 2015-2017 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
'use strict';
import {OrganizationsPermissionService} from '../../organizations-permission.service';
/**
* @ngdoc controller
* @name organizations.list.Item.controller:OrganizationsItemController
* @description This class is handling the controller for item of organizations list
* @author Oleksii Orel
*/
export class OrganizationsItemController {
/**
* Service for displaying dialogs.
*/
private confirmDialogService: any;
/**
* Location service.
*/
private $location: ng.ILocationService;
/**
* User service.
*/
private userServices: che.IUserServices;
/**
* Organization permission service.
*/
private organizationsPermissionService: OrganizationsPermissionService;
/**
* Organization API interaction.
*/
private cheOrganization: che.api.ICheOrganization;
/**
* Service for displaying notifications.
*/
private cheNotification: any;
/**
* Organization details (the value is set in directive attributes).
*/
private organization: che.IOrganization;
/**
* Callback needed to react on organizations updation (the value is set in directive attributes).
*/
private onUpdate: Function;
/**
* todo
*/
private organizationActions: che.resource.ICheOrganizationActions;
/**
* Default constructor that is using resource injection
* @ngInject for Dependency injection
*/
constructor($location: ng.ILocationService, cheOrganization: che.api.ICheOrganization, confirmDialogService: any, cheNotification: any, organizationsPermissionService: OrganizationsPermissionService, chePermissions: che.api.IChePermissions, resourcesService: che.service.IResourcesService) {
this.$location = $location;
this.confirmDialogService = confirmDialogService;
this.cheOrganization = cheOrganization;
this.cheNotification = cheNotification;
this.organizationsPermissionService = organizationsPermissionService;
this.organizationActions = resourcesService.getOrganizationActions();
this.userServices = chePermissions.getUserServices();
}
/**
* returns true if current user has Delete permission
* @returns {boolean}
*/
hasDeletePermission(): boolean {
if (!this.organization || (!this.organization.parent && !this.userServices.hasAdminUserService)) {
return false;
}
return this.organizationsPermissionService.isUserAllowedTo(this.organizationActions.DELETE, this.organization.id);
}
/**
* Gets all sub organizations.
*/
getAllSubOrganizations(): Array<che.IOrganization> {
let subOrganizationsTree = this.cheOrganization.getOrganizations().filter((organization: che.IOrganization) => {
if (!organization.parent || this.organization.id === organization.id) {
return false;
}
return organization.qualifiedName.indexOf(this.organization.qualifiedName + '/') === 0;
});
return subOrganizationsTree;
}
/**
* Redirect to factory details.
*/
redirectToOrganizationDetails(tab: string) {
this.$location.path('/organization/' + this.organization.qualifiedName).search(!tab ? {} : {tab: tab});
}
/**
* Removes organization after confirmation.
*/
removeOrganization(): void {
this.confirmRemoval().then(() => {
this.cheOrganization.deleteOrganization(this.organization.id).then(() => {
this.onUpdate();
}, (error: any) => {
let message = 'Failed to delete organization ' + this.organization.name;
this.cheNotification.showError(error && error.data && error.data.message ? error.data.message : message);
});
});
}
/**
* Shows dialog to confirm the current organization removal.
*
* @returns {angular.IPromise<any>}
*/
confirmRemoval(): ng.IPromise<any> {
return this.confirmDialogService.showConfirmDialog('Delete organization',
'Would you like to delete organization \'' + this.organization.name + '\'?', 'Delete');
}
}

View File

@ -0,0 +1,48 @@
/*
* Copyright (c) 2015-2017 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
'use strict';
/**
* @ngdoc directive
* @name organizations.list.Item.controller:WorkspaceItem
* @restrict E
* @element
*
* @description
* `<organizations-item organization="ctrl.organization"></organizations-item>` for displaying list of organizations
*
* @usage
* <organizations-item organization="ctrl.organization"></organizations-item>
*
* @author Oleksii Orel
*/
export class OrganizationsItem implements ng.IDirective {
restrict = 'E';
require = ['ngModel'];
templateUrl = 'app/organizations/list-organizations/organizations-item/organizations-item.html';
controller = 'OrganizationsItemController';
controllerAs = 'organizationsItemController';
bindToController = true;
// scope values
scope = {
organization: '=',
members: '=',
totalRam: '=',
availableRam: '=',
isChecked: '=cdvyChecked',
isSelect: '=?ngModel',
isSelectable: '=?cdvyIsSelectable',
onCheckboxClick: '&?cdvyOnCheckboxClick',
onUpdate: '&?onUpdate'
};
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,41 @@
/*
* Copyright (c) 2015-2017 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
'use strict';
/**
* @ngdoc directive
* @name organization.details.invite-members:ListOrganizationInviteMembers
* @restrict E
* @element
*
* @description
* `<list-organization-members members="ctrl.members"></list-organization-members>` for displaying list of members
*
* @usage
* <list-organization-members members="ctrl.members"></list-organization-members>
*
* @author Oleksii Orel
*/
export class ListOrganizationInviteMembers implements ng.IDirective {
restrict: string = 'E';
templateUrl: string = 'app/organizations/organization-details/organization-invite-members/list-organization-invite-members.html';
controller: string = 'ListOrganizationInviteMembersController';
controllerAs: string = 'listOrganizationInviteMembersController';
bindToController: boolean = true;
scope: any = {
members: '=',
parentOrganizationId: '=',
parentOrganizationMembers: '='
};
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,40 @@
/*
* Copyright (c) 2015-2017 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
'use strict';
/**
* @ngdoc directive
* @name organization.details.members:ListOrganizationMembers
* @restrict E
* @element
*
* @description
* `<list-organization-members editable="ctrl.editable" organization="ctrl.organization"></list-organization-members>` for displaying list of members
*
* @usage
* <list-organization-members editable="ctrl.editable" organization="ctrl.organization"></list-organization-members>
*
* @author Oleksii Orel
*/
export class ListOrganizationMembers implements ng.IDirective {
restrict: string = 'E';
templateUrl: string = 'app/organizations/organization-details/organization-members/list-organization-members.html';
controller: string = 'ListOrganizationMembersController';
controllerAs: string = 'listOrganizationMembersController';
bindToController: boolean = true;
scope: any = {
editable: '=',
organization: '=',
parentOrganizationMembers: '='
};
}

View File

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

View File

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

View File

@ -0,0 +1,36 @@
/*
* Copyright (c) 2015-2017 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
'use strict';
/**
* @ngdoc directive
* @name organization.details:OrganizationNotFound
* @restrict E
* @element
*
* @description
* `<organization-not-found organization-name="myOrganization"></organization-not-found>` for displaying "Organization not found" page.
*
* @usage
* <organization-not-found organization-name="myOrganization"></organization-not-found>
*
* @author Oleksii Kurinnyi
*/
export class OrganizationNotFound implements ng.IDirective {
restrict: string = 'E';
replace: boolean = true;
templateUrl: string = 'app/organizations/organization-details/organization-not-found/organization-not-found.html';
scope: any = {
organizationName: '='
};
}

View File

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

View File

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

View File

@ -0,0 +1,30 @@
/*
* Copyright (c) 2015-2017 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
'use strict';
/**
* Defines a directive for member in list.
*
* @author Oleksii Kurinnyi
*/
export class OrganizationMemberItem {
restrict: string = 'E';
templateUrl: string = 'app/organizations/organization-details/organization-select-members-dialog/organization-member-item/organization-member-item.html';
replace: boolean = false;
scope: {[prop: string]: string} = {
member: '=',
isSelected: '=',
onChange: '&'
};
}

View File

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

View File

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

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