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.java6.19.x
commit
108b3d832b
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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;
|
||||
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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
|
||||
+ '\''
|
||||
+ '}';
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
+ '}';
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
"angular-charts": "0.2.6",
|
||||
"angular-cookies": "1.4.8",
|
||||
"angular-dropdowns": "1.0.0",
|
||||
"angular-gravatar": "0.2.4",
|
||||
"angular-filter": "0.5.4",
|
||||
"angular-material": "1.0.1",
|
||||
"angular-moment": "0.9.0",
|
||||
|
|
|
|||
|
|
@ -22,10 +22,9 @@ var serverOptions = {
|
|||
|
||||
var options = minimist(process.argv.slice(2), serverOptions);
|
||||
|
||||
var patterns = ['/api', '/ext', '/ws', '/datasource', '/java-ca', '/im', '/che', '/admin'];
|
||||
|
||||
var proxies = []
|
||||
var patterns = ['/api', '/ext', '/ws', '/datasource', '/java-ca', '/im', '/che', '/admin', '/wsmaster'];
|
||||
|
||||
var proxies = [];
|
||||
|
||||
patterns.forEach(function(pattern) {
|
||||
var proxyOptions = url.parse(options.server + pattern);
|
||||
|
|
@ -37,6 +36,8 @@ patterns.forEach(function(pattern) {
|
|||
proxyOptions.route = '/admin';
|
||||
} else if (pattern === '/ext') {
|
||||
proxyOptions.route = '/ext';
|
||||
} else if (pattern === '/wsmaster') {
|
||||
proxyOptions.route = '/wsmaster';
|
||||
} else {
|
||||
proxyOptions.route = '/api';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,15 +11,16 @@
|
|||
'use strict';
|
||||
|
||||
import {AdminsPluginsConfig} from './plugins/plugins-config';
|
||||
import {AdminsUserManagementConfig} from './user-management/user-management-config';
|
||||
|
||||
/**
|
||||
* @author Florent Benoit
|
||||
*/
|
||||
export class AdminsConfig {
|
||||
|
||||
constructor(register) {
|
||||
constructor(register: che.IRegisterService) {
|
||||
new AdminsPluginsConfig(register);
|
||||
|
||||
new AdminsUserManagementConfig(register);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* Copyright (c) 2015-2017 Red Hat, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Red Hat, Inc. - initial API and implementation
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
interface IAccountProfileScope extends ng.IScope {
|
||||
profileAttributes: {
|
||||
phone?: string;
|
||||
country?: string;
|
||||
employer?: string;
|
||||
jobtitle?: string;
|
||||
lastName?: string;
|
||||
firstName?: string;
|
||||
};
|
||||
profileInformationForm: ng.IFormController;
|
||||
countries?: Array<{ 'name': string, 'code': string }>;
|
||||
jobs?: Array<{ 'name': string }>;
|
||||
}
|
||||
|
||||
/**
|
||||
* @ngdoc directive
|
||||
* @name account.profile.directive:accountProfile
|
||||
* @restrict E
|
||||
* @element
|
||||
*
|
||||
* @description
|
||||
* <account-profile profile-attributes="ctrl.profileAttributes"></account-profile>` for displaying account profile.
|
||||
*
|
||||
* @usage
|
||||
* <account-profile profile-attributes="ctrl.profileAttributes"></account-profile>
|
||||
*
|
||||
* @author Florent Benoit
|
||||
*/
|
||||
export class AccountProfile implements ng.IDirective {
|
||||
restrict = 'E';
|
||||
templateUrl = 'app/account/account-profile/account-profile.html';
|
||||
replace = true;
|
||||
scope = {
|
||||
profileAttributes: '=profileAttributes',
|
||||
profileInformationForm: '=?profileInformationForm'
|
||||
};
|
||||
|
||||
jsonCountries: string;
|
||||
jsonJobs: string;
|
||||
|
||||
/**
|
||||
* Default constructor that is using resource
|
||||
* @ngInject for Dependency injection
|
||||
*/
|
||||
constructor(jsonCountries: string, jsonJobs: string) {
|
||||
this.jsonCountries = jsonCountries;
|
||||
this.jsonJobs = jsonJobs;
|
||||
}
|
||||
|
||||
link($scope: IAccountProfileScope) {
|
||||
$scope.countries = angular.fromJson(this.jsonCountries);
|
||||
$scope.jobs = angular.fromJson(this.jsonJobs);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
<div class="account-profile">
|
||||
<ng-form name="profileInformationForm">
|
||||
<che-label-container che-label-name="First Name">
|
||||
<che-input che-form="profileInformationForm"
|
||||
che-name="firstName"
|
||||
che-place-holder="First Name"
|
||||
ng-model="profileAttributes.firstName"
|
||||
required
|
||||
ng-maxlength="128">
|
||||
<div ng-message="required">A name is required.</div>
|
||||
<div ng-message="maxlength">The name has to be less than 128 characters long.</div>
|
||||
</che-input>
|
||||
</che-label-container>
|
||||
|
||||
<che-label-container che-label-name="Last Name">
|
||||
<che-input che-form="profileInformationForm"
|
||||
che-name="lastName"
|
||||
che-place-holder="Last Name"
|
||||
ng-model="profileAttributes.lastName"
|
||||
required
|
||||
ng-maxlength="128">
|
||||
<div ng-message="required">A last name is required.</div>
|
||||
<div ng-message="maxlength">The name has to be less than 128 characters long.</div>
|
||||
</che-input>
|
||||
</che-label-container>
|
||||
|
||||
<che-label-container che-label-name="Phone">
|
||||
<che-input che-form="profileInformationForm"
|
||||
che-name="phone"
|
||||
che-place-holder="phone"
|
||||
che-pattern="(^[+]{0,1}[0-9-]{0,}$)"
|
||||
ng-model="profileAttributes.phone"
|
||||
ng-maxlength="15"
|
||||
ng-minlength="7">
|
||||
<div ng-message="pattern">Should be numbers and may start with a '+'.</div>
|
||||
<div ng-message="minlength">The phone number has to be more than 7 characters long.</div>
|
||||
<div ng-message="maxlength">The phone number has to be less than 15 characters long.</div>
|
||||
</che-input>
|
||||
</che-label-container>
|
||||
|
||||
<che-label-container che-label-name="Country">
|
||||
<che-select che-form="profileInformationForm"
|
||||
che-name="country"
|
||||
che-option-values="countries"
|
||||
che-place-holder="Select Your Country"
|
||||
che-value="profileAttributes.country">
|
||||
</che-select>
|
||||
</che-label-container>
|
||||
|
||||
<che-label-container che-label-name="Company">
|
||||
<che-input che-form="profileInformationForm"
|
||||
che-name="department"
|
||||
che-place-holder="Company"
|
||||
ng-model="profileAttributes.employer"
|
||||
ng-maxlength="128">
|
||||
<div ng-message="maxlength">The name has to be less than 128 characters long.</div>
|
||||
</che-input>
|
||||
</che-label-container>
|
||||
|
||||
<che-label-container che-label-name="Role">
|
||||
<che-select che-option-values="jobs"
|
||||
che-place-holder="Select Your Role"
|
||||
che-value="profileAttributes.jobtitle">
|
||||
</che-select>
|
||||
</che-label-container>
|
||||
</ng-form>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
.account-profile
|
||||
padding 0 14px
|
||||
|
||||
.che-input-desktop
|
||||
margin-top -1px
|
||||
|
||||
.che-select
|
||||
margin-top -2px
|
||||
|
|
@ -0,0 +1,123 @@
|
|||
/*
|
||||
* Copyright (c) 2015-2017 Red Hat, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Red Hat, Inc. - initial API and implementation
|
||||
*/
|
||||
'use strict';
|
||||
import {AdminsUserManagementCtrl} from '../user-management.controller';
|
||||
|
||||
/**
|
||||
* This class is handling the controller for the add user
|
||||
* @author Oleksii Orel
|
||||
*/
|
||||
export class AdminsAddUserController {
|
||||
private $mdDialog: ng.material.IDialogService;
|
||||
private lodash: any;
|
||||
private cheNotification: any;
|
||||
private cheUser: any;
|
||||
private callbackController: AdminsUserManagementCtrl;
|
||||
private newUserName: string;
|
||||
private newUserEmail: string;
|
||||
private newUserPassword: string;
|
||||
private organizations: Array<string>;
|
||||
private organization: string;
|
||||
private cheOrganization: che.api.ICheOrganization;
|
||||
private chePermissions: che.api.IChePermissions;
|
||||
private organizationRoles: che.resource.ICheOrganizationRoles;
|
||||
|
||||
/**
|
||||
* Default constructor.
|
||||
* @ngInject for Dependency injection
|
||||
*/
|
||||
constructor($mdDialog: ng.material.IDialogService,
|
||||
cheUser: any,
|
||||
cheNotification: any,
|
||||
lodash: any,
|
||||
cheOrganization: che.api.ICheOrganization,
|
||||
chePermissions: che.api.IChePermissions,
|
||||
resourcesService: che.service.IResourcesService) {
|
||||
this.$mdDialog = $mdDialog;
|
||||
this.lodash = lodash;
|
||||
this.cheUser = cheUser;
|
||||
this.cheNotification = cheNotification;
|
||||
this.cheOrganization = cheOrganization;
|
||||
this.chePermissions = chePermissions;
|
||||
this.organizationRoles = resourcesService.getOrganizationRoles();
|
||||
|
||||
this.organizations = [];
|
||||
|
||||
this.cheOrganization.fetchOrganizations().then(() => {
|
||||
let organizations = this.cheOrganization.getOrganizations();
|
||||
let rootOrganizations = organizations.filter((organization: any) => {
|
||||
return !organization.parent;
|
||||
});
|
||||
this.organizations = lodash.pluck(rootOrganizations, 'name');
|
||||
if (this.organizations.length > 0) {
|
||||
this.organization = this.organizations[0];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback of the cancel button of the dialog.
|
||||
*/
|
||||
abort(): void {
|
||||
this.$mdDialog.hide();
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback of the add button of the dialog(create new user).
|
||||
*/
|
||||
createUser(): void {
|
||||
let promise = this.cheUser.createUser(this.newUserName, this.newUserEmail, this.newUserPassword);
|
||||
|
||||
promise.then((data: any) => {
|
||||
if (this.organization) {
|
||||
this.addUserToOrganization(data.id);
|
||||
} else {
|
||||
this.finish();
|
||||
}
|
||||
}, (error: any) => {
|
||||
this.cheNotification.showError(error.data.message ? error.data.message : 'Failed to create user.');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Finish user creation.
|
||||
*/
|
||||
private finish(): void {
|
||||
this.$mdDialog.hide();
|
||||
this.callbackController.updateUsers();
|
||||
this.cheNotification.showInfo('User successfully created.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds user to chosen organization.
|
||||
*
|
||||
* @param userId
|
||||
*/
|
||||
private addUserToOrganization(userId: string): void {
|
||||
let organizations = this.cheOrganization.getOrganizations();
|
||||
let organization = this.lodash.find(organizations, (organization: any) => {
|
||||
return organization.name === this.organization;
|
||||
});
|
||||
|
||||
let actions = this.organizationRoles.MEMBER.actions;
|
||||
let permissions = {
|
||||
instanceId: organization.id,
|
||||
userId: userId,
|
||||
domainId: 'organization',
|
||||
actions: actions
|
||||
};
|
||||
this.chePermissions.storePermissions(permissions).then(() => {
|
||||
this.finish();
|
||||
}, (error: any) => {
|
||||
this.cheNotification.showError(error.data.message ? error.data.message : 'Failed to add user to organization' + this.organization + '.');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
<!--
|
||||
|
||||
Copyright (c) 2015 Red Hat, Inc.
|
||||
All rights reserved. This program and the accompanying materials
|
||||
are made available under the terms of the Eclipse Public License v1.0
|
||||
which accompanies this distribution, and is available at
|
||||
http://www.eclipse.org/legal/epl-v10.html
|
||||
|
||||
Contributors:
|
||||
Red Hat, Inc. - initial API and implementation
|
||||
|
||||
-->
|
||||
<che-popup title="Add user" on-close="adminsAddUserController.abort()">
|
||||
<ng-form name="createUserForm" class="admins-add-user-form" layout="column" align="center center">
|
||||
<div class="form-input-fields">
|
||||
<che-input-box che-form="createUserForm"
|
||||
che-name="login"
|
||||
che-label-name="User Login"
|
||||
che-place-holder="login"
|
||||
ng-model="adminsAddUserController.newUserName"
|
||||
required
|
||||
focusable
|
||||
ng-maxlength="100">
|
||||
<div ng-message="maxlength">User login to be less than 100 characters long.</div>
|
||||
</che-input-box>
|
||||
<che-input-box che-form="createUserForm"
|
||||
che-name="email"
|
||||
che-label-name="User Email"
|
||||
che-place-holder="e-mail"
|
||||
ng-maxlength="100"
|
||||
type="email"
|
||||
required
|
||||
ng-model="adminsAddUserController.newUserEmail">
|
||||
<div ng-message="email">Enter a valid email address.</div>
|
||||
<div ng-message="maxlength">User email has to be less than 100 characters long</div>
|
||||
</che-input-box>
|
||||
<div layout="row" layout-align="start start" ng-if="adminsAddUserController.organizations.length > 0">
|
||||
<label>Organization:</label>
|
||||
<che-filter-selector che-values="adminsAddUserController.organizations"
|
||||
che-width="400px"
|
||||
ng-model="adminsAddUserController.organization"></che-filter-selector>
|
||||
</div>
|
||||
|
||||
<che-input-box che-form="createUserForm"
|
||||
che-name="password"
|
||||
che-label-name="User Password"
|
||||
che-place-holder="password"
|
||||
che-pattern="^(?=.*[0-9]+.*)(?=.*[a-zA-Z]+.*).{0,}$"
|
||||
ng-maxlength="100"
|
||||
ng-minlength="8"
|
||||
type="password"
|
||||
required
|
||||
ng-model="adminsAddUserController.newUserPassword">
|
||||
<div ng-message="pattern">User password should contain both letters and digits</div>
|
||||
<div ng-message="minlength">User password should contain at least 8 characters.</div>
|
||||
<div ng-message="maxlength">User password has to be less than 100 characters long.</div>
|
||||
</che-input-box>
|
||||
<div class="password-prompt"
|
||||
layout-xs="column" layout-sm="column" layout-md="column"
|
||||
layout-gt-md="row" flex="100">
|
||||
<div flex="50" flex-gt-md="25" class="password-prompt-text">
|
||||
<span>Minimum 8 characters, both letters and digits.</span>
|
||||
</div>
|
||||
<div flex="50" flex-gt-md="50" layout="column" layout-align="center center">
|
||||
<div class="pass-strength" ng-password-strength="adminsAddUserController.newUserPassword"
|
||||
strength="passStrength" inner-class="password-meter" class="ng-isolate-scope"></div>
|
||||
</div>
|
||||
</div>
|
||||
<che-input-box che-form="createUserForm"
|
||||
che-name="confirmPassword"
|
||||
che-label-name="Confirm Password"
|
||||
che-place-holder="confirm password"
|
||||
ng-model="confirmNewUserPassword"
|
||||
ng-maxlength="100"
|
||||
type="password"
|
||||
required>
|
||||
<div ng-message="maxlength">Confirm password has to be less than 100 characters long.</div>
|
||||
<div
|
||||
ng-show="confirmNewUserPassword && (confirmNewUserPassword !== adminsAddUserController.newUserPassword)">
|
||||
Passwords do not match.
|
||||
</div>
|
||||
</che-input-box>
|
||||
|
||||
</div>
|
||||
<div layout="row" layout-align="end center">
|
||||
<che-button-notice che-button-title="Cancel"
|
||||
ng-click="adminsAddUserController.abort()">
|
||||
</che-button-notice>
|
||||
<che-button-primary che-button-title="Create"
|
||||
ng-click="adminsAddUserController.createUser()"
|
||||
ng-disabled="createUserForm.$invalid || confirmNewUserPassword !== adminsAddUserController.newUserPassword">
|
||||
</che-button-primary>
|
||||
</div>
|
||||
</ng-form>
|
||||
</che-popup>
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
.admins-add-user-form
|
||||
min-width 520px
|
||||
|
||||
.form-input-fields
|
||||
margin 20px 0px
|
||||
|
||||
che-filter-selector
|
||||
margin-bottom 20px
|
||||
line-height 33px
|
||||
|
||||
.che-input-box-desktop
|
||||
width 520px
|
||||
max-width 520px
|
||||
|
||||
label
|
||||
max-width 23% !important
|
||||
width 23% !important
|
||||
margin-top 10px
|
||||
|
||||
input
|
||||
width 400px
|
||||
float right
|
||||
|
||||
.admins-add-user-form .che-input-desktop-label
|
||||
margin-top 1px
|
||||
|
||||
.admins-add-user-form .password-prompt,
|
||||
.admins-add-user-form .che-input-desktop-label
|
||||
min-width 125px
|
||||
|
||||
.admins-add-user-form .password-prompt
|
||||
font-size 12px
|
||||
color $disabled-color
|
||||
margin-bottom 20px
|
||||
|
||||
.admins-add-user-form .password-prompt p
|
||||
margin-top 5px
|
||||
margin-bottom 5px
|
||||
|
||||
.admins-add-user-form .password-prompt
|
||||
.password-prompt-text
|
||||
min-width 125px
|
||||
|
||||
.pass-strength
|
||||
width 100%
|
||||
|
||||
& > div
|
||||
margin-bottom 10px
|
||||
|
|
@ -0,0 +1,243 @@
|
|||
/*
|
||||
* Copyright (c) 2015-2017 Red Hat, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Red Hat, Inc. - initial API and implementation
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
enum Tab {Profile, Organization}
|
||||
|
||||
interface IScope extends ng.IScope {
|
||||
profileInformationForm: ng.IFormController;
|
||||
}
|
||||
|
||||
interface IProfileAttributes {
|
||||
firstName?: string;
|
||||
lastName?: string;
|
||||
phone?: string;
|
||||
country?: string;
|
||||
employer?: string;
|
||||
jobtitle?: string;
|
||||
}
|
||||
|
||||
const MAX_ITEMS = 12;
|
||||
|
||||
/**
|
||||
* Controller for user details.
|
||||
*
|
||||
* @author Oleksii Orel
|
||||
*/
|
||||
export class AdminUserDetailsController {
|
||||
tab: Object = Tab;
|
||||
|
||||
/**
|
||||
* Angular Location service.
|
||||
*/
|
||||
private $location: ng.ILocationService;
|
||||
/**
|
||||
* User profile service.
|
||||
*/
|
||||
private cheProfile: any;
|
||||
/**
|
||||
* Notification service.
|
||||
*/
|
||||
private cheNotification: any;
|
||||
/**
|
||||
* Index of the selected tab.
|
||||
*/
|
||||
private selectedTabIndex: number = 0;
|
||||
/**
|
||||
* User profile.
|
||||
*/
|
||||
private profile: che.IProfile;
|
||||
/**
|
||||
* Profile attributes.
|
||||
*/
|
||||
private profileAttributes: IProfileAttributes;
|
||||
/**
|
||||
* Loading state of the page.
|
||||
*/
|
||||
private isLoading: boolean;
|
||||
/**
|
||||
* User ID.
|
||||
*/
|
||||
private userId: string;
|
||||
/**
|
||||
* User Name.
|
||||
*/
|
||||
private userName: string;
|
||||
|
||||
private cheOrganization: che.api.ICheOrganization;
|
||||
|
||||
private userOrganizations: Array<che.IOrganization>;
|
||||
/**
|
||||
* User's page info.
|
||||
*/
|
||||
private pageInfo: che.IPageInfo;
|
||||
|
||||
/**
|
||||
* Default constructor that is using resource injection
|
||||
* @ngInject for Dependency injection
|
||||
*/
|
||||
constructor(cheProfile: any, $location: ng.ILocationService, $timeout: ng.ITimeoutService, $scope: ng.IScope, cheNotification: any, cheOrganization: che.api.ICheOrganization, initData: {userId; userName}) {
|
||||
this.cheOrganization = cheOrganization;
|
||||
this.$location = $location;
|
||||
this.cheProfile = cheProfile;
|
||||
this.cheNotification = cheNotification;
|
||||
this.userId = initData.userId;
|
||||
this.userName = initData.userName;
|
||||
|
||||
this.updateSelectedTab(this.$location.search().tab);
|
||||
let deRegistrationFn = $scope.$watch(() => {
|
||||
return $location.search().tab;
|
||||
}, (tab: string) => {
|
||||
if (!angular.isUndefined(tab)) {
|
||||
this.updateSelectedTab(tab);
|
||||
}
|
||||
}, true);
|
||||
|
||||
let timeoutPromise: ng.IPromise<any>;
|
||||
$scope.$watch(() => {
|
||||
return angular.isUndefined(this.profileAttributes) || this.profileAttributes;
|
||||
}, () => {
|
||||
if (!this.profileAttributes || !(<IScope>$scope).profileInformationForm || (<IScope>$scope).profileInformationForm.$invalid) {
|
||||
return;
|
||||
}
|
||||
if (timeoutPromise) {
|
||||
$timeout.cancel(timeoutPromise);
|
||||
}
|
||||
timeoutPromise = $timeout(() => {
|
||||
this.setProfileAttributes();
|
||||
}, 500);
|
||||
}, true);
|
||||
|
||||
$scope.$on('$destroy', () => {
|
||||
deRegistrationFn();
|
||||
if (timeoutPromise) {
|
||||
$timeout.cancel(timeoutPromise);
|
||||
}
|
||||
});
|
||||
|
||||
this.updateData();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update user's data.
|
||||
*/
|
||||
updateData(): void {
|
||||
this.isLoading = true;
|
||||
this.cheProfile.fetchProfileById(this.userId).then(() => {
|
||||
this.profile = this.cheProfile.getProfileById(this.userId);
|
||||
this.profileAttributes = angular.copy(this.profile.attributes);
|
||||
this.fetchOrganizations();
|
||||
}, (error: any) => {
|
||||
this.isLoading = false;
|
||||
this.cheNotification.showError(error && error.data && error.data.message !== null ? error.data.message : 'Failed to retrieve user\'s profile.');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Request the list of the user's organizations (first page).
|
||||
*
|
||||
* @returns {ng.IPromise<any>}
|
||||
*/
|
||||
fetchOrganizations(): void {
|
||||
this.isLoading = true;
|
||||
this.cheOrganization.fetchUserOrganizations(this.userId, MAX_ITEMS).then((userOrganizations: Array<che.IOrganization>) => {
|
||||
this.userOrganizations = userOrganizations;
|
||||
}, (error: any) => {
|
||||
this.cheNotification.showError(error && error.data && error.data.message !== null ? error.data.message : 'Failed to retrieve organizations.');
|
||||
}).finally(() => {
|
||||
this.isLoading = false;
|
||||
this.pageInfo = this.cheOrganization.getUserOrganizationPageInfo(this.userId);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the array of user's organizations.
|
||||
*
|
||||
* @returns {Array<any>}
|
||||
*/
|
||||
getUserOrganizations(): Array<che.IOrganization> {
|
||||
return this.userOrganizations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the the user's page info.
|
||||
*
|
||||
* @returns {che.IPageInfo}
|
||||
*/
|
||||
getPagesInfo(): che.IPageInfo {
|
||||
return this.pageInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Request the list of the user's organizations for a page depends on page key('first', 'prev', 'next', 'last').
|
||||
* @param key {string}
|
||||
*/
|
||||
fetchOrganizationPageObjects(key: string): void {
|
||||
this.isLoading = true;
|
||||
this.cheOrganization.fetchUserOrganizationPageObjects(this.userId, key).then((userOrganizations: Array<che.IOrganization>) => {
|
||||
this.userOrganizations = userOrganizations;
|
||||
}).finally(() => {
|
||||
this.isLoading = false;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if profile attributes have changed
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isAttributesChanged(): boolean {
|
||||
return !angular.equals(this.profile.attributes, this.profileAttributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set profile attributes
|
||||
*/
|
||||
setProfileAttributes(): void {
|
||||
if (angular.equals(this.profile.attributes, this.profileAttributes)) {
|
||||
return;
|
||||
}
|
||||
let promise = this.cheProfile.setAttributes(this.profileAttributes, this.userId);
|
||||
|
||||
promise.then(() => {
|
||||
this.cheNotification.showInfo('Profile successfully updated.');
|
||||
this.updateData();
|
||||
}, (error: any) => {
|
||||
this.profileAttributes = angular.copy(this.profile.attributes);
|
||||
this.cheNotification.showError(error.data.message ? error.data.message : 'Profile update failed.');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update selected tab index by search part of URL.
|
||||
*
|
||||
* @param {string} tab
|
||||
*/
|
||||
updateSelectedTab(tab: string): void {
|
||||
this.selectedTabIndex = parseInt(this.tab[tab], 10);
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes search part of URL.
|
||||
*
|
||||
* @param {number} tabIndex
|
||||
*/
|
||||
onSelectTab(tabIndex?: number): void {
|
||||
let param: { tab?: string } = {};
|
||||
if (!angular.isUndefined(tabIndex)) {
|
||||
param.tab = Tab[tabIndex];
|
||||
}
|
||||
if (angular.isUndefined(this.$location.search().tab)) {
|
||||
this.$location.replace();
|
||||
}
|
||||
this.$location.search(param);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
<che-toolbar che-title="{{adminUserDetailsController.userName}}"
|
||||
che-breadcrumb-title="Users"
|
||||
che-breadcrumb-href="#/admin/usermanagement">
|
||||
</che-toolbar>
|
||||
|
||||
<md-content md-scroll-y flex md-theme="default">
|
||||
<md-tabs md-dynamic-height md-stretch-tabs="auto"
|
||||
md-selected="adminUserDetailsController.selectedTabIndex"
|
||||
md-center-tabs="">
|
||||
|
||||
<!-- User Profile Tab -->
|
||||
<md-tab md-on-select="adminUserDetailsController.onSelectTab(adminUserDetailsController.tab.Profile);">
|
||||
<md-icon md-font-icon="fa-user" class="fa che-tab-label-icon"></md-icon>
|
||||
<span class="che-tab-label-title">Profile</span>
|
||||
</md-tab-label>
|
||||
<md-tab-body>
|
||||
<div class="progress-line">
|
||||
<md-progress-linear ng-show="adminUserDetailsController.isLoading" md-mode="indeterminate"></md-progress-linear>
|
||||
</div>
|
||||
<account-profile profile-attributes="adminUserDetailsController.profileAttributes"
|
||||
profile-information-form="profileInformationForm"></account-profile>
|
||||
</md-tab-body>
|
||||
</md-tab>
|
||||
|
||||
<!-- User Organizations Tab -->
|
||||
<md-tab md-on-select="adminUserDetailsController.onSelectTab(adminUserDetailsController.tab.Organization);">
|
||||
<md-icon md-font-icon="md-font fa fa-sitemap material-icons" class="che-tab-label-icon"></md-icon>
|
||||
<span class="che-tab-label-title">Organizations</span>
|
||||
</md-tab-label>
|
||||
<md-tab-body>
|
||||
<div class="progress-line">
|
||||
<md-progress-linear ng-show="adminUserDetailsController.isLoading" md-mode="indeterminate"></md-progress-linear>
|
||||
</div>
|
||||
<list-organizations hide-add-button="true"
|
||||
is-loading="adminUserDetailsController.isInfoLoading"
|
||||
on-update="adminUserDetailsController.fetchOrganizations()"
|
||||
organizations="adminUserDetailsController.getUserOrganizations()"></list-organizations>
|
||||
<che-paging-buttons ng-hide="adminUserDetailsController.isLoading"
|
||||
pages-info="adminUserDetailsController.getPagesInfo()"
|
||||
fetch-page="adminUserDetailsController.fetchOrganizationPageObjects(key)"></che-paging-buttons>
|
||||
</md-tab-body>
|
||||
</md-tab>
|
||||
|
||||
</md-tabs>
|
||||
</md-content>
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* Copyright (c) 2015-2017 Red Hat, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Red Hat, Inc. - initial API and implementation
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
import {AdminsAddUserController} from './add-user/add-user.controller';
|
||||
import {AdminsUserManagementCtrl} from './user-management.controller';
|
||||
import {AdminUserDetailsController} from './user-details/user-details.controller';
|
||||
import {AccountProfile} from './account-profile/account-profile.directive';
|
||||
|
||||
export class AdminsUserManagementConfig {
|
||||
|
||||
constructor(register: che.IRegisterService) {
|
||||
register.controller('AdminUserDetailsController', AdminUserDetailsController);
|
||||
register.controller('AdminsAddUserController', AdminsAddUserController);
|
||||
register.controller('AdminsUserManagementCtrl', AdminsUserManagementCtrl);
|
||||
register.directive('accountProfile', AccountProfile);
|
||||
|
||||
const userDetailLocationProvider = {
|
||||
title: 'User Details',
|
||||
reloadOnSearch: false,
|
||||
templateUrl: 'app/admin/user-management/user-details/user-details.html',
|
||||
controller: 'AdminUserDetailsController',
|
||||
controllerAs: 'adminUserDetailsController',
|
||||
resolve: {
|
||||
initData: ['$q', 'cheUser', '$route', 'chePermissions', ($q: ng.IQService, cheUser: any, $route: any, chePermissions: che.api.IChePermissions) => {
|
||||
const userId = $route.current.params.userId;
|
||||
let defer = $q.defer();
|
||||
chePermissions.fetchSystemPermissions().finally(() => {
|
||||
cheUser.fetchUserId(userId).then((user: che.IUser) => {
|
||||
if (!chePermissions.getUserServices().hasAdminUserService) {
|
||||
defer.reject();
|
||||
}
|
||||
defer.resolve({userId: userId, userName: user.name});
|
||||
}, (error: any) => {
|
||||
defer.reject(error);
|
||||
});
|
||||
});
|
||||
return defer.promise;
|
||||
}]
|
||||
}
|
||||
};
|
||||
|
||||
// configure routes
|
||||
register.app.config(($routeProvider: che.route.IRouteProvider) => {
|
||||
$routeProvider.accessWhen('/admin/usermanagement', {
|
||||
title: 'Users',
|
||||
templateUrl: 'app/admin/user-management/user-management.html',
|
||||
controller: 'AdminsUserManagementCtrl',
|
||||
controllerAs: 'adminsUserManagementCtrl',
|
||||
resolve: {
|
||||
check: ['$q', 'chePermissions', ($q: ng.IQService, chePermissions: che.api.IChePermissions) => {
|
||||
let defer = $q.defer();
|
||||
chePermissions.fetchSystemPermissions().finally(() => {
|
||||
if (chePermissions.getUserServices().hasUserService) {
|
||||
defer.resolve();
|
||||
} else {
|
||||
defer.reject();
|
||||
}
|
||||
});
|
||||
return defer.promise;
|
||||
}]
|
||||
}
|
||||
})
|
||||
.accessWhen('/admin/userdetails/:userId', userDetailLocationProvider);
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,298 @@
|
|||
/*
|
||||
* Copyright (c) 2015-2017 Red Hat, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Red Hat, Inc. - initial API and implementation
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const MAX_ITEMS = 12;
|
||||
|
||||
/**
|
||||
* This class is handling the controller for the admins user management
|
||||
* @author Oleksii Orel
|
||||
*/
|
||||
export class AdminsUserManagementCtrl {
|
||||
$q: ng.IQService;
|
||||
$log: ng.ILogService;
|
||||
$mdDialog: ng.material.IDialogService;
|
||||
$location: ng.ILocationService;
|
||||
cheUser: any;
|
||||
cheNotification: any;
|
||||
pagesInfo: any;
|
||||
users: Array<any>;
|
||||
usersMap: Map<string, any>;
|
||||
userFilter: {name: string};
|
||||
userOrderBy: string;
|
||||
isLoading: boolean;
|
||||
|
||||
private confirmDialogService: any;
|
||||
private cheOrganization: che.api.ICheOrganization;
|
||||
private userOrganizationCount: {[userId: string]: number} = {};
|
||||
private cheListHelper: che.widget.ICheListHelper;
|
||||
|
||||
/**
|
||||
* Default constructor.
|
||||
* @ngInject for Dependency injection
|
||||
*/
|
||||
constructor($q: ng.IQService,
|
||||
$rootScope: che.IRootScopeService,
|
||||
$log: ng.ILogService,
|
||||
$mdDialog: ng.material.IDialogService,
|
||||
cheUser: any,
|
||||
$location: ng.ILocationService,
|
||||
cheNotification: any,
|
||||
confirmDialogService: any,
|
||||
cheOrganization: che.api.ICheOrganization,
|
||||
$scope: ng.IScope,
|
||||
cheListHelperFactory: che.widget.ICheListHelperFactory) {
|
||||
this.$q = $q;
|
||||
this.$log = $log;
|
||||
this.$mdDialog = $mdDialog;
|
||||
this.$location = $location;
|
||||
this.cheUser = cheUser;
|
||||
this.cheOrganization = cheOrganization;
|
||||
this.cheNotification = cheNotification;
|
||||
this.confirmDialogService = confirmDialogService;
|
||||
|
||||
$rootScope.showIDE = false;
|
||||
|
||||
this.isLoading = false;
|
||||
|
||||
this.users = [];
|
||||
this.usersMap = this.cheUser.getUsersMap();
|
||||
|
||||
this.userOrderBy = 'name';
|
||||
this.userFilter = {name: ''};
|
||||
|
||||
const helperId = 'user-management';
|
||||
this.cheListHelper = cheListHelperFactory.getHelper(helperId);
|
||||
$scope.$on('$destroy', () => {
|
||||
cheListHelperFactory.removeHelper(helperId);
|
||||
});
|
||||
|
||||
if (this.usersMap && this.usersMap.size > 1) {
|
||||
this.updateUsers();
|
||||
} else {
|
||||
this.isLoading = true;
|
||||
this.cheUser.fetchUsers(MAX_ITEMS, 0).then(() => {
|
||||
this.isLoading = false;
|
||||
this.updateUsers();
|
||||
}, (error: any) => {
|
||||
this.isLoading = false;
|
||||
if (error && error.status !== 304) {
|
||||
this.cheNotification.showError(error.data && error.data.message ? error.data.message : 'Failed to retrieve the list of users.');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.pagesInfo = this.cheUser.getPagesInfo();
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback when name is changed.
|
||||
*
|
||||
* @param str {string} a string to filter user names.
|
||||
*/
|
||||
onSearchChanged(str: string): void {
|
||||
this.userFilter.name = str;
|
||||
this.cheListHelper.applyFilter('name', this.userFilter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirect to user details
|
||||
* @param userId {string}
|
||||
* @param tab {string}
|
||||
*/
|
||||
redirectToUserDetails(userId: string, tab?: string): void {
|
||||
this.$location.path('/admin/userdetails/' + userId).search(!tab ? {} : {tab: tab});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update user's organizations count
|
||||
* @param userId {string}
|
||||
*/
|
||||
updateUserOrganizationsCount(userId: string): void {
|
||||
this.cheOrganization.fetchUserOrganizations(userId, 1).then((userOrganizations: Array<any>) => {
|
||||
if (!angular.isArray(userOrganizations) || userOrganizations.length === 0) {
|
||||
return;
|
||||
}
|
||||
this.userOrganizationCount[userId] = this.cheOrganization.getUserOrganizationPageInfo(userId).countPages;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* User clicked on the - action to remove the user. Show the dialog
|
||||
* @param event {MouseEvent} - the $event
|
||||
* @param user {any} - the selected user
|
||||
*/
|
||||
removeUser(event: MouseEvent, user: any): void {
|
||||
let content = 'Are you sure you want to remove \'' + user.email + '\'?';
|
||||
let promise = this.confirmDialogService.showConfirmDialog('Remove user', content, 'Delete', 'Cancel');
|
||||
|
||||
promise.then(() => {
|
||||
this.isLoading = true;
|
||||
let promise = this.cheUser.deleteUserById(user.id);
|
||||
promise.then(() => {
|
||||
this.isLoading = false;
|
||||
this.updateUsers();
|
||||
}, (error: any) => {
|
||||
this.isLoading = false;
|
||||
this.cheNotification.showError(error.data && error.data.message ? error.data.message : 'Delete user failed.');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all selected users
|
||||
*/
|
||||
deleteSelectedUsers(): void {
|
||||
const selectedUsers = this.cheListHelper.getSelectedItems(),
|
||||
selectedUserIds = selectedUsers.map((user: che.IUser) => {
|
||||
return user.id;
|
||||
});
|
||||
|
||||
const queueLength = selectedUserIds.length;
|
||||
if (!queueLength) {
|
||||
this.cheNotification.showError('No such user.');
|
||||
return;
|
||||
}
|
||||
|
||||
const confirmationPromise = this.showDeleteUsersConfirmation(queueLength);
|
||||
confirmationPromise.then(() => {
|
||||
const numberToDelete = queueLength;
|
||||
const deleteUserPromises = [];
|
||||
let isError = false;
|
||||
let currentUserId;
|
||||
|
||||
selectedUserIds.forEach((userId: string) => {
|
||||
currentUserId = userId;
|
||||
this.cheListHelper.itemsSelectionStatus[userId] = false;
|
||||
|
||||
let promise = this.cheUser.deleteUserById(userId);
|
||||
promise.catch((error: any) => {
|
||||
isError = true;
|
||||
this.$log.error('Cannot delete user: ', error);
|
||||
});
|
||||
deleteUserPromises.push(promise);
|
||||
});
|
||||
|
||||
this.$q.all(deleteUserPromises).finally(() => {
|
||||
this.isLoading = true;
|
||||
|
||||
const promise = this.cheUser.fetchUsersPage(this.pagesInfo.currentPageNumber);
|
||||
promise.then(() => {
|
||||
this.isLoading = false;
|
||||
this.updateUsers();
|
||||
}, (error: any) => {
|
||||
this.isLoading = false;
|
||||
this.$log.error(error);
|
||||
});
|
||||
|
||||
if (isError) {
|
||||
this.cheNotification.showError('Delete failed.');
|
||||
} else {
|
||||
if (numberToDelete === 1) {
|
||||
this.cheNotification.showInfo('Selected user has been removed.');
|
||||
} else {
|
||||
this.cheNotification.showInfo('Selected users have been removed.');
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Show confirmation popup before delete
|
||||
* @param numberToDelete {number}
|
||||
* @returns {angular.IPromise<any>}
|
||||
*/
|
||||
showDeleteUsersConfirmation(numberToDelete: number): angular.IPromise<any> {
|
||||
let content = 'Are you sure you want to remove ' + numberToDelete + ' selected ';
|
||||
if (numberToDelete > 1) {
|
||||
content += 'users?';
|
||||
} else {
|
||||
content += 'user?';
|
||||
}
|
||||
|
||||
return this.confirmDialogService.showConfirmDialog('Remove users', content, 'Delete', 'Cancel');
|
||||
}
|
||||
|
||||
/**
|
||||
* Update users array
|
||||
*/
|
||||
updateUsers(): void {
|
||||
// update users array
|
||||
this.users.length = 0;
|
||||
this.usersMap.forEach((user: any) => {
|
||||
this.users.push(user);
|
||||
});
|
||||
|
||||
this.cheListHelper.setList(this.users, 'id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Ask for loading the users page in asynchronous way
|
||||
* @param pageKey {string} - the key of page
|
||||
*/
|
||||
fetchUsersPage(pageKey: string): void {
|
||||
this.isLoading = true;
|
||||
let promise = this.cheUser.fetchUsersPage(pageKey);
|
||||
|
||||
promise.then(() => {
|
||||
this.isLoading = false;
|
||||
this.updateUsers();
|
||||
}, (error: any) => {
|
||||
this.isLoading = false;
|
||||
if (error.status === 304) {
|
||||
this.updateUsers();
|
||||
} else {
|
||||
this.cheNotification.showError(error.data && error.data.message ? error.data.message : 'Update information failed.');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the next page is exist.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
hasNextPage(): boolean {
|
||||
return this.pagesInfo.currentPageNumber < this.pagesInfo.countOfPages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the previous page is exist.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
hasPreviousPage(): boolean {
|
||||
return this.pagesInfo.currentPageNumber > 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if we have more then one page.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isPagination(): boolean {
|
||||
return this.pagesInfo.countOfPages > 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new user. Show the dialog
|
||||
* @param event {MouseEvent} - the $event
|
||||
*/
|
||||
showAddUserDialog(event: MouseEvent): void {
|
||||
this.$mdDialog.show({
|
||||
targetEvent: event,
|
||||
bindToController: true,
|
||||
clickOutsideToClose: true,
|
||||
controller: 'AdminsAddUserController',
|
||||
controllerAs: 'adminsAddUserController',
|
||||
locals: {callbackController: this},
|
||||
templateUrl: 'app/admin/user-management/add-user/add-user.html'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,147 @@
|
|||
<!--
|
||||
|
||||
Copyright (c) 2015 Red Hat, Inc.
|
||||
All rights reserved. This program and the accompanying materials
|
||||
are made available under the terms of the Eclipse Public License v1.0
|
||||
which accompanies this distribution, and is available at
|
||||
http://www.eclipse.org/legal/epl-v10.html
|
||||
|
||||
Contributors:
|
||||
Red Hat, Inc. - initial API and implementation
|
||||
|
||||
-->
|
||||
<che-toolbar che-title="User Management"></che-toolbar>
|
||||
<md-content flex md-scroll-y md-theme="maincontent-theme" class="admins-user-management">
|
||||
<div class="progress-line">
|
||||
<md-progress-linear ng-show="adminsUserManagementCtrl.isLoading" md-mode="indeterminate"></md-progress-linear>
|
||||
</div>
|
||||
<div>
|
||||
<che-list-header flex="100"
|
||||
che-input-placeholder="Search"
|
||||
che-search-model="adminsUserManagementCtrl.userFilter.name"
|
||||
che-on-search-change="adminsUserManagementCtrl.onSearchChanged(str)"
|
||||
che-hide-search="adminsUserManagementCtrl.users.length === 0"
|
||||
che-add-button-title="Add User"
|
||||
che-on-add="adminsUserManagementCtrl.showAddUserDialog($event)"
|
||||
che-delete-button-title="Delete"
|
||||
che-on-delete="adminsUserManagementCtrl.deleteSelectedUsers()"
|
||||
che-hide-delete="adminsUserManagementCtrl.cheListHelper.isNoItemSelected"
|
||||
che-hide-header="adminsUserManagementCtrl.cheListHelper.visibleItemsNumber === 0">
|
||||
<div flex="100"
|
||||
layout="row"
|
||||
layout-align="start stretch"
|
||||
class="che-list-item-row">
|
||||
<div layout="column" layout-gt-xs="row"
|
||||
layout-align="start center"
|
||||
class="che-checkbox-area">
|
||||
<div layout="row" layout-align="start center" class="che-list-item-checkbox-main">
|
||||
<md-checkbox class="che-list-item-checkbox"
|
||||
aria-label="Bulk check on users"
|
||||
ng-checked="adminsUserManagementCtrl.cheListHelper.areAllItemsSelected"
|
||||
ng-click="adminsUserManagementCtrl.cheListHelper.changeBulkSelection()"></md-checkbox>
|
||||
</div>
|
||||
</div>
|
||||
<div flex hide-xs layout-gt-xs="row"
|
||||
layout-align="start center"
|
||||
class="che-list-item-details">
|
||||
<che-list-header-column flex-gt-xs="40"
|
||||
che-sort-value='adminsUserManagementCtrl.userOrderBy'
|
||||
che-sort-item='email'
|
||||
che-column-title='Email'></che-list-header-column>
|
||||
<che-list-header-column flex-gt-xs="20"
|
||||
che-sort-value='adminsUserManagementCtrl.userOrderBy'
|
||||
che-sort-item='name'
|
||||
che-column-title='Login'></che-list-header-column>
|
||||
<che-list-header-column flex-gt-xs="20"
|
||||
che-column-title='Organizations'></che-list-header-column>
|
||||
<che-list-header-column flex-gt-xs="20"
|
||||
che-column-title='Actions'></che-list-header-column>
|
||||
</div>
|
||||
</div>
|
||||
</che-list-header>
|
||||
<che-list flex ng-if="adminsUserManagementCtrl.users && adminsUserManagementCtrl.users.length > 0">
|
||||
<che-list-item
|
||||
ng-repeat="user in adminsUserManagementCtrl.cheListHelper.getVisibleItems() | orderBy:adminsUserManagementCtrl.userOrderBy"
|
||||
ng-init="adminsUserManagementCtrl.updateUserOrganizationsCount(user.id)"
|
||||
flex-gt-sm="100" flex="33"
|
||||
ng-mouseover="hover=true"
|
||||
ng-mouseout="hover=false">
|
||||
<div flex="100"
|
||||
layout="row"
|
||||
layout-align="start stretch"
|
||||
class="che-list-item-row">
|
||||
<div layout="row"
|
||||
layout-align="start center"
|
||||
class="che-checkbox-area">
|
||||
<che-list-item-checked ng-model="adminsUserManagementCtrl.cheListHelper.itemsSelectionStatus[user.id]"
|
||||
ng-click="adminsUserManagementCtrl.cheListHelper.updateBulkSelectionStatus()"
|
||||
che-aria-label-checkbox="User {{user.id}}"></che-list-item-checked>
|
||||
</div>
|
||||
<div flex
|
||||
layout-xs="column" layout-gt-xs="row"
|
||||
layout-align-gt-xs="start center"
|
||||
layout-align-xs="start start"
|
||||
class="che-list-item-details">
|
||||
<div flex-gt-xs="40"
|
||||
class="che-list-item-name"
|
||||
ng-click="adminsUserManagementCtrl.redirectToUserDetails(user.id)">
|
||||
<span class="che-xs-header noselect" hide-gt-xs>Email</span>
|
||||
<span><img class="user-face" gravatar-src="user.email"></span>
|
||||
<span class="user-email">{{user.email}}</span>
|
||||
</div>
|
||||
<div flex-gt-xs="20"
|
||||
ng-click="adminsUserManagementCtrl.redirectToUserDetails(user.id)">
|
||||
<span class="che-xs-header noselect" hide-gt-xs>Login</span>
|
||||
<span class="user-description">{{user.name}}</span>
|
||||
</div>
|
||||
<div flex-gt-xs="20"
|
||||
ng-click="adminsUserManagementCtrl.redirectToUserDetails(user.id, 'Organization')">
|
||||
<span class="che-xs-header noselect" hide-gt-xs>Organizations</span>
|
||||
<span class="user-description">{{adminsUserManagementCtrl.userOrganizationCount[user.id] ? adminsUserManagementCtrl.userOrganizationCount[user.id] : '-'}}</span>
|
||||
</div>
|
||||
<div flex-gt-xs="20"
|
||||
ng-click="adminsUserManagementCtrl.redirectToUserDetails(user.id)">
|
||||
<span class="che-xs-header noselect" hide-gt-xs>Actions</span>
|
||||
<span class="che-list-actions">
|
||||
<div ng-click="adminsUserManagementCtrl.removeUser($event, user);" uib-tooltip="Remove user">
|
||||
<span class="material-design icon-ic_remove_circle_outline_24px"></span>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</che-list-item>
|
||||
<div class="paging-buttons-area" ng-if="adminsUserManagementCtrl.isPagination()">
|
||||
<md-button
|
||||
ng-disabled="!adminsUserManagementCtrl.hasPreviousPage()"
|
||||
ng-click="adminsUserManagementCtrl.fetchUsersPage('first');">
|
||||
<span><<</span>
|
||||
</md-button>
|
||||
<md-button
|
||||
ng-disabled="!adminsUserManagementCtrl.hasPreviousPage()"
|
||||
ng-click="adminsUserManagementCtrl.fetchUsersPage('prev');">
|
||||
<span><</span>
|
||||
</md-button>
|
||||
<md-button disabled>
|
||||
<span>{{adminsUserManagementCtrl.pagesInfo.currentPageNumber}}</span>
|
||||
</md-button>
|
||||
<md-button
|
||||
ng-disabled="!adminsUserManagementCtrl.hasNextPage()"
|
||||
ng-click="adminsUserManagementCtrl.fetchUsersPage('next');">
|
||||
<span>></span>
|
||||
</md-button>
|
||||
<md-button
|
||||
ng-disabled="!adminsUserManagementCtrl.hasNextPage()"
|
||||
ng-click="adminsUserManagementCtrl.fetchUsersPage('last');">
|
||||
<span>>></span>
|
||||
</md-button>
|
||||
</div>
|
||||
</che-list>
|
||||
<div class="che-list-empty">
|
||||
<span ng-show="adminsUserManagementCtrl.users.length > 0 && adminsUserManagementCtrl.cheListHelper.visibleItemsNumber === 0">
|
||||
No users found.
|
||||
</span>
|
||||
<span ng-show="adminsUserManagementCtrl.users.length === 0">There are no users.</span>
|
||||
</div>
|
||||
</div>
|
||||
</md-content>
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
.admins-user-management
|
||||
.user-email,
|
||||
.user-description
|
||||
max-width 100%
|
||||
.user-description
|
||||
color $label-info-color
|
||||
.user-face
|
||||
che-developers-face()
|
||||
height 16px
|
||||
width 16px
|
||||
margin-right 5px
|
||||
|
||||
.che-list-item
|
||||
outline none
|
||||
user-select none
|
||||
*
|
||||
outline inherit
|
||||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -10,9 +10,10 @@
|
|||
*/
|
||||
'use strict';
|
||||
import {CheAPI} from '../../components/api/che-api.factory';
|
||||
import {CheKeycloak, keycloakUserInfo} from '../../components/api/che-keycloak.factory';
|
||||
|
||||
export class CheNavBarController {
|
||||
menuItemUrl = {
|
||||
private menuItemUrl = {
|
||||
dashboard: '#/',
|
||||
workspaces: '#/workspaces',
|
||||
administration: '#/administration',
|
||||
|
|
@ -20,9 +21,26 @@ export class CheNavBarController {
|
|||
plugins: '#/admin/plugins',
|
||||
factories: '#/factories',
|
||||
account: '#/account',
|
||||
stacks: '#/stacks'
|
||||
stacks: '#/stacks',
|
||||
organizations: '#/organizations',
|
||||
usermanagement: '#/admin/usermanagement'
|
||||
};
|
||||
|
||||
accountItems = [
|
||||
{
|
||||
name: 'Go to Profile',
|
||||
onclick: () => {
|
||||
this.gotoProfile();
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Logout',
|
||||
onclick: () => {
|
||||
this.logout();
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
private $mdSidenav: ng.material.ISidenavService;
|
||||
private $scope: ng.IScope;
|
||||
private $window: ng.IWindowService;
|
||||
|
|
@ -30,21 +48,39 @@ export class CheNavBarController {
|
|||
private $route: ng.route.IRouteService;
|
||||
private cheAPI: CheAPI;
|
||||
private profile: che.IProfile;
|
||||
private chePermissions: che.api.IChePermissions;
|
||||
private userServices: che.IUserServices;
|
||||
private hasPersonalAccount: boolean;
|
||||
private organizations: Array<che.IOrganization>;
|
||||
private cheKeycloak: CheKeycloak;
|
||||
private userInfo: keycloakUserInfo;
|
||||
|
||||
/**
|
||||
* Default constructor
|
||||
* @ngInject for Dependency injection
|
||||
*/
|
||||
constructor($mdSidenav: ng.material.ISidenavService, $scope: ng.IScope, $location: ng.ILocationService, $route: ng.route.IRouteService, cheAPI: CheAPI, $window: ng.IWindowService) {
|
||||
constructor($mdSidenav: ng.material.ISidenavService,
|
||||
$scope: ng.IScope,
|
||||
$location: ng.ILocationService,
|
||||
$route: ng.route.IRouteService,
|
||||
cheAPI: CheAPI,
|
||||
$window: ng.IWindowService,
|
||||
chePermissions: che.api.IChePermissions,
|
||||
cheKeycloak: CheKeycloak) {
|
||||
this.$mdSidenav = $mdSidenav;
|
||||
this.$scope = $scope;
|
||||
this.$location = $location;
|
||||
this.$route = $route;
|
||||
this.cheAPI = cheAPI;
|
||||
this.$window = $window;
|
||||
this.chePermissions = chePermissions;
|
||||
this.cheKeycloak = cheKeycloak;
|
||||
this.userInfo = null;
|
||||
|
||||
this.profile = cheAPI.getProfile().getProfile();
|
||||
|
||||
this.userServices = this.chePermissions.getUserServices();
|
||||
|
||||
// highlight navbar menu item
|
||||
$scope.$on('$locationChangeStart', () => {
|
||||
let path = '#' + $location.path();
|
||||
|
|
@ -53,6 +89,34 @@ export class CheNavBarController {
|
|||
|
||||
cheAPI.getWorkspace().fetchWorkspaces();
|
||||
cheAPI.getFactory().fetchFactories();
|
||||
|
||||
if (this.cheKeycloak.isPresent()) {
|
||||
this.cheKeycloak.fetchUserInfo().then((userInfo: keycloakUserInfo) => {
|
||||
this.userInfo = userInfo;
|
||||
});
|
||||
}
|
||||
|
||||
if (this.chePermissions.getSystemPermissions()) {
|
||||
this.updateData();
|
||||
} else {
|
||||
this.chePermissions.fetchSystemPermissions().finally(() => {
|
||||
this.updateData();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update data.
|
||||
*/
|
||||
updateData(): void {
|
||||
const organization = this.cheAPI.getOrganization();
|
||||
organization.fetchOrganizations().then(() => {
|
||||
this.organizations = organization.getOrganizations();
|
||||
const user = this.cheAPI.getUser().getUser();
|
||||
organization.fetchOrganizationByName(user.name).finally(() => {
|
||||
this.hasPersonalAccount = angular.isDefined(organization.getOrganizationByName(user.name));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
reload(): void {
|
||||
|
|
@ -66,15 +130,63 @@ export class CheNavBarController {
|
|||
this.$mdSidenav('left').toggle();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns number of workspaces.
|
||||
*
|
||||
* @return {number}
|
||||
*/
|
||||
getWorkspacesNumber(): number {
|
||||
return this.cheAPI.getWorkspace().getWorkspaces().length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns number of factories.
|
||||
*
|
||||
* @return {number}
|
||||
*/
|
||||
getFactoriesNumber(): number {
|
||||
return this.cheAPI.getFactory().getPageFactories().length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns number of all organizations.
|
||||
*
|
||||
* @return {number}
|
||||
*/
|
||||
getOrganizationsNumber(): number {
|
||||
if (!this.organizations) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return this.organizations.length;
|
||||
}
|
||||
|
||||
openLinkInNewTab(url: string): void {
|
||||
this.$window.open(url, '_blank');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns <code>true</code> if Keycloak is present.
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isKeycloakPresent(): boolean {
|
||||
return this.cheKeycloak.isPresent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens user profile in new browser page.
|
||||
*/
|
||||
gotoProfile(): void {
|
||||
const url = this.cheKeycloak.getProfileUrl();
|
||||
this.$window.open(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logout.
|
||||
*/
|
||||
logout(): void {
|
||||
this.cheKeycloak.logout();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ export class CheNavBar {
|
|||
this.replace = false;
|
||||
this.templateUrl = 'app/navbar/navbar.html';
|
||||
this.controller = 'CheNavBarController';
|
||||
this.controllerAs = 'navbarCtrl';
|
||||
this.controllerAs = 'navbarController';
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,8 +12,9 @@
|
|||
-->
|
||||
<div class="left-sidebar-container">
|
||||
<md-toolbar class="flex-shrink-none">
|
||||
<md-progress-linear md-mode="indeterminate" ng-hide="navbarCtrl.profile && navbarCtrl.profile.userId"></md-progress-linear>
|
||||
<div ng-show="navbarCtrl.profile && navbarCtrl.profile.userId">
|
||||
<md-progress-linear md-mode="indeterminate" ng-hide="navbarController.profile && navbarController.profile.userId"></md-progress-linear>
|
||||
<div ng-show="navbarController.profile && navbarController.profile.userId"
|
||||
layout="column" flex>
|
||||
<section class="navbar-top-logo logo-color-white" layout="column" layout-align="center left" ng-include="branding.logoText">
|
||||
</section>
|
||||
|
||||
|
|
@ -23,7 +24,7 @@
|
|||
<section class="left-sidebar-menu" layout="column" layout-align="center center">
|
||||
<md-list layout="column">
|
||||
<md-list-item flex class="navbar-subsection-item">
|
||||
<md-button nav-bar-selected flex che-reload-href href="{{navbarCtrl.menuItemUrl.dashboard}}" layout-align="left"
|
||||
<md-button nav-bar-selected flex che-reload-href href="{{navbarController.menuItemUrl.dashboard}}" layout-align="left"
|
||||
target="_self">
|
||||
<div class="navbar-item" layout="row" layout-align="start center">
|
||||
<md-icon md-font-icon="navbar-icon chefont cheico-dashboard" aria-label="Dashboard"></md-icon>
|
||||
|
|
@ -32,17 +33,17 @@
|
|||
</md-button>
|
||||
</md-list-item>
|
||||
<md-list-item flex class="navbar-subsection-item">
|
||||
<md-button nav-bar-selected flex che-reload-href href="{{navbarCtrl.menuItemUrl.workspaces}}" layout-align="left">
|
||||
<md-button nav-bar-selected flex che-reload-href href="{{navbarController.menuItemUrl.workspaces}}" layout-align="left">
|
||||
<div class="navbar-item" layout="row" layout-align="start center">
|
||||
<md-icon md-font-icon="navbar-icon chefont cheico-workspace"></md-icon>
|
||||
<span>Workspaces</span>
|
||||
<span class="navbar-number" ng-show="navbarCtrl.getWorkspacesNumber()"> ({{navbarCtrl.getWorkspacesNumber()}})</span>
|
||||
<span class="navbar-number" ng-show="navbarController.getWorkspacesNumber()"> ({{navbarController.getWorkspacesNumber()}})</span>
|
||||
</div>
|
||||
</md-button>
|
||||
</md-list-item>
|
||||
<md-list-item flex class="navbar-subsection-item">
|
||||
<md-button nav-bar-selected flex che-reload-href
|
||||
href="{{navbarCtrl.menuItemUrl.stacks}}" layout-align="left">
|
||||
href="{{navbarController.menuItemUrl.stacks}}" layout-align="left">
|
||||
<div class="navbar-item" layout="row" layout-align="start center">
|
||||
<md-icon md-font-icon="navbar-icon material-design icon-ic_inbox_24px"></md-icon>
|
||||
<span>Stacks</span>
|
||||
|
|
@ -51,29 +52,102 @@
|
|||
</md-list-item>
|
||||
<md-list-item flex class="navbar-subsection-item">
|
||||
<md-button nav-bar-selected flex che-reload-href
|
||||
href="{{navbarCtrl.menuItemUrl.factories}}" layout-align="left">
|
||||
href="{{navbarController.menuItemUrl.factories}}" layout-align="left">
|
||||
<div class="navbar-item" layout="row" layout-align="start center">
|
||||
<md-icon md-font-icon="navbar-icon chefont cheico-factory"></md-icon>
|
||||
<span>Factories</span>
|
||||
<span class="navbar-number" ng-show="navbarCtrl.getFactoriesNumber()"> ({{navbarCtrl.getFactoriesNumber()}})</span>
|
||||
<span class="navbar-number" ng-show="navbarController.getFactoriesNumber()"> ({{navbarController.getFactoriesNumber()}})</span>
|
||||
</div>
|
||||
</md-button>
|
||||
</md-list-item>
|
||||
<md-list-item flex class="navbar-subsection-item">
|
||||
<md-button nav-bar-selected flex che-reload-href
|
||||
href="{{navbarCtrl.menuItemUrl.administration}}" layout-align="left">
|
||||
href="{{navbarController.menuItemUrl.administration}}" layout-align="left">
|
||||
<div class="navbar-item" layout="row" layout-align="start center">
|
||||
<md-icon md-font-icon="navbar-icon material-design icon-ic_settings_24px"></md-icon>
|
||||
<span>Administration</span>
|
||||
</div>
|
||||
</md-button>
|
||||
</md-list-item>
|
||||
<md-list-item flex class="navbar-subsection-item" ng-if="!navbarController.userServices.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()"> ({{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()"> ({{navbarController.getRootOrganizationsNumber()}})</span>
|
||||
</div>
|
||||
</md-button>
|
||||
</md-list-item>
|
||||
</md-list>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<navbar-recent-workspaces></navbar-recent-workspaces>
|
||||
|
||||
<navbar-teams flex layout="column" layout-aling="start strength"></navbar-teams>
|
||||
|
||||
<div class="admin-navbar-menu"
|
||||
layout="column" layout-align="end stretch" flex
|
||||
ng-if="navbarController.isKeycloakPresent() && navbarController.userInfo">
|
||||
<section class="left-sidebar-menu navbar-account-section">
|
||||
<md-list layout="column" flex>
|
||||
|
||||
<md-list-item class="navbar-subsection-item">
|
||||
<navbar-dropdown-menu flex
|
||||
navbar-dropdown-items="navbarController.accountItems"
|
||||
navbar-dropdown-offset="15 -45">
|
||||
<md-button ng-href="" layout-align="left">
|
||||
<div class="navbar-item navbar-identity" layout="row" layout-align="start center">
|
||||
|
||||
<i class="navbar-icon" flex="none">
|
||||
<img class="developers-face" gravatar-src="navbarController.userInfo.email"/>
|
||||
</i>
|
||||
<span flex style="text-align: left;">{{navbarController.userInfo.name}}</span>
|
||||
<i class="fa fa-angle-up navbar-icon" aria-hidden="true"></i>
|
||||
</div>
|
||||
</md-button>
|
||||
</navbar-dropdown-menu>
|
||||
</md-list-item>
|
||||
</md-list>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</md-toolbar>
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,166 @@
|
|||
/*
|
||||
* Copyright (c) 2015-2017 Red Hat, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Red Hat, Inc. - initial API and implementation
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* @ngdoc controller
|
||||
* @name organizations.create.controller:CreateOrganizationController
|
||||
* @description This class is handling the controller for the new organization creation.
|
||||
* @author Oleksii Orel
|
||||
*/
|
||||
export class CreateOrganizationController {
|
||||
/**
|
||||
* Organization API interaction.
|
||||
*/
|
||||
private cheOrganization: che.api.ICheOrganization;
|
||||
/**
|
||||
* User API interaction.
|
||||
*/
|
||||
private cheUser: any;
|
||||
/**
|
||||
* Permissions API interaction.
|
||||
*/
|
||||
private chePermissions: che.api.IChePermissions;
|
||||
/**
|
||||
* Notifications service.
|
||||
*/
|
||||
private cheNotification: any;
|
||||
/**
|
||||
* Location service.
|
||||
*/
|
||||
private $location: ng.ILocationService;
|
||||
/**
|
||||
* Log service.
|
||||
*/
|
||||
private $log: ng.ILogService;
|
||||
/**
|
||||
* Promises service.
|
||||
*/
|
||||
private $q: ng.IQService;
|
||||
/**
|
||||
* Current organization's name.
|
||||
*/
|
||||
private organizationName: string;
|
||||
/**
|
||||
* Loading state of the page.
|
||||
*/
|
||||
private isLoading: boolean;
|
||||
/**
|
||||
* The list of users to invite.
|
||||
*/
|
||||
private members: Array<che.IMember>;
|
||||
/**
|
||||
* Parent organization name.
|
||||
*/
|
||||
private parentQualifiedName: string;
|
||||
/**
|
||||
* Parent organization id.
|
||||
*/
|
||||
private parentOrganizationId: string;
|
||||
/**
|
||||
* List of members of parent organization.
|
||||
*/
|
||||
private parentOrganizationMembers: Array<che.IUser>;
|
||||
|
||||
/**
|
||||
* Default constructor
|
||||
* @ngInject for Dependency injection
|
||||
*/
|
||||
constructor(cheOrganization: che.api.ICheOrganization, chePermissions: che.api.IChePermissions, cheUser: any, cheNotification: any,
|
||||
$location: ng.ILocationService, $q: ng.IQService, $log: ng.ILogService, $rootScope: che.IRootScopeService,
|
||||
initData: any) {
|
||||
this.cheOrganization = cheOrganization;
|
||||
this.chePermissions = chePermissions;
|
||||
this.cheUser = cheUser;
|
||||
this.cheNotification = cheNotification;
|
||||
this.$location = $location;
|
||||
this.$q = $q;
|
||||
this.$log = $log;
|
||||
$rootScope.showIDE = false;
|
||||
|
||||
this.organizationName = '';
|
||||
this.isLoading = false;
|
||||
this.members = [];
|
||||
|
||||
// injected by route provider
|
||||
this.parentQualifiedName = initData.parentQualifiedName;
|
||||
this.parentOrganizationId = initData.parentOrganizationId;
|
||||
this.parentOrganizationMembers = initData.parentOrganizationMembers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the name is unique.
|
||||
* @param name
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isUniqueName(name: string): boolean {
|
||||
let organizations = this.cheOrganization.getOrganizations();
|
||||
let account = this.parentQualifiedName ? this.parentQualifiedName + '/' : '';
|
||||
if (!organizations.length) {
|
||||
return true;
|
||||
} else {
|
||||
for (let i = 0; i < organizations.length; i++) {
|
||||
if (organizations[i].qualifiedName === account + name) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs new organization creation.
|
||||
*/
|
||||
createOrganization(): void {
|
||||
this.isLoading = true;
|
||||
this.cheOrganization.createOrganization(this.organizationName, this.parentOrganizationId).then((organization: che.IOrganization) => {
|
||||
this.addPermissions(organization, this.members);
|
||||
this.cheOrganization.fetchOrganizations();
|
||||
}, (error: any) => {
|
||||
this.isLoading = false;
|
||||
let message = error.data && error.data.message ? error.data.message : 'Failed to create organization ' + this.organizationName + '.';
|
||||
this.cheNotification.showError(message);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add permissions for members in pointed organization.
|
||||
*
|
||||
* @param organization {che.IOrganization} organization
|
||||
* @param members members to be added to organization
|
||||
*/
|
||||
addPermissions(organization: che.IOrganization, members: Array<any>) {
|
||||
let promises = [];
|
||||
members.forEach((member: che.IMember) => {
|
||||
if (member.id && member.id !== this.cheUser.getUser().id) {
|
||||
let actions = this.cheOrganization.getActionsFromRoles(member.roles);
|
||||
let permissions = {
|
||||
instanceId: organization.id,
|
||||
userId: member.id,
|
||||
domainId: 'organization',
|
||||
actions: actions
|
||||
};
|
||||
|
||||
let promise = this.chePermissions.storePermissions(permissions);
|
||||
promises.push(promise);
|
||||
}
|
||||
});
|
||||
|
||||
this.$q.all(promises).then(() => {
|
||||
this.isLoading = false;
|
||||
this.$location.path('/organization/' + organization.qualifiedName);
|
||||
}, (error: any) => {
|
||||
this.isLoading = false;
|
||||
let message = error.data && error.data.message ? error.data.message : 'Failed to create organization ' + this.organizationName + '.';
|
||||
this.cheNotification.showError(message);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
<organization-not-found ng-if="createOrganizationController.parentQualifiedName && !createOrganizationController.parentOrganizationId"
|
||||
organization-name="createOrganizationController.parentQualifiedName"></organization-not-found>
|
||||
|
||||
<div ng-if="!createOrganizationController.parentQualifiedName || createOrganizationController.parentOrganizationId"
|
||||
flex layout="column">
|
||||
<che-toolbar che-title="{{createOrganizationController.parentOrganizationId ? 'Create Sub-Organization' : 'Create New Organization'}}" border-none></che-toolbar>
|
||||
<che-description>
|
||||
Create {{createOrganizationController.parentOrganizationId ? 'a sub-organization' : 'an organization'}} to share resources.
|
||||
</che-description>
|
||||
<div class="organization-progress-line">
|
||||
<md-progress-linear md-mode="indeterminate"
|
||||
ng-show="createOrganizationController.isLoading"></md-progress-linear>
|
||||
</div>
|
||||
<md-content md-scroll-y flex md-theme="default" class="create-organization">
|
||||
<ng-form name="createOrganizationForm">
|
||||
<!-- Name -->
|
||||
<che-label-container che-label-name="Name"
|
||||
che-label-description="Name will be displayed in menus and prefix workspaces.">
|
||||
|
||||
<div layout="column" class="create-organization-input">
|
||||
<che-input-box che-form="createOrganizationForm"
|
||||
che-name="name"
|
||||
che-place-holder="Enter organization name"
|
||||
aria-label="Name of the organization"
|
||||
ng-model="createOrganizationController.organizationName"
|
||||
ng-trim
|
||||
ng-minlength="1"
|
||||
ng-maxlength="20"
|
||||
ng-model-options="{allowInvalid: true, updateOn: 'default blur', debounce: { 'default': 200, 'blur': 0 } }"
|
||||
custom-validator="createOrganizationController.isUniqueName($value)"
|
||||
parent-account="createOrganizationController.accountName"
|
||||
ng-keypress="createOrganizationForm.$valid && $event.which === 13 && createOrganizationController.createOrganization()"
|
||||
ng-pattern="/^[a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,38}$/i"
|
||||
required focusable>
|
||||
<div ng-message="pattern">The name can contain alphanumeric characters or single '-' inside.
|
||||
</div>
|
||||
<div ng-message="minlength">The name has to be more than 1 character long.</div>
|
||||
<div ng-message="maxlength">The name has to be less than 20 characters long.</div>
|
||||
<div ng-message="customValidator">This organization name is already used.</div>
|
||||
</che-input-box>
|
||||
</div>
|
||||
</che-label-container>
|
||||
|
||||
<che-label-container che-label-name="Members" che-label-description="Invite others to collaborate in the organization."
|
||||
che-alignment="{{createOrganizationController.members.length > 0 ? 'column' : 'row'}}">
|
||||
<list-organization-invite-members members="createOrganizationController.members"
|
||||
parent-organization-id="createOrganizationController.parentOrganizationId"
|
||||
parent-organization-members="createOrganizationController.parentOrganizationMembers"
|
||||
class="create-organization-list"></list-organization-invite-members>
|
||||
</che-label-container>
|
||||
</ng-form>
|
||||
|
||||
<div layout="row" layout-align="center center">
|
||||
<che-button-primary id="create-organization-button"
|
||||
che-button-title="Create {{createOrganizationController.parentOrganizationId ? 'Sub-Organization' : 'Organization'}}"
|
||||
ng-click="createOrganizationController.createOrganization()"
|
||||
ng-disabled="!createOrganizationForm.$valid || createOrganizationController.isLoading">
|
||||
</che-button-primary>
|
||||
</div>
|
||||
|
||||
</md-content>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
.create-organization
|
||||
padding 0 52px
|
||||
|
||||
.che-label-container .che-label-container-content
|
||||
width 100%
|
||||
|
||||
input.ng-invalid.ng-pristine:focus
|
||||
border-color $primary-color
|
||||
|
|
@ -0,0 +1,322 @@
|
|||
/*
|
||||
* Copyright (c) 2015-2017 Red Hat, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Red Hat, Inc. - initial API and implementation
|
||||
*/
|
||||
'use strict';
|
||||
import {OrganizationsPermissionService} from '../organizations-permission.service';
|
||||
|
||||
/**
|
||||
* @ngdoc controller
|
||||
* @name organizations.list.controller:ListOrganizationsController
|
||||
* @description This class is handling the controller for listing the organizations
|
||||
* @author Oleksii Orel
|
||||
*/
|
||||
export class ListOrganizationsController {
|
||||
/**
|
||||
* Organization API interaction.
|
||||
*/
|
||||
private cheOrganization: che.api.ICheOrganization;
|
||||
/**
|
||||
* Service for displaying notifications.
|
||||
*/
|
||||
private cheNotification: any;
|
||||
/**
|
||||
* Service for displaying dialogs.
|
||||
*/
|
||||
private confirmDialogService: any;
|
||||
/**
|
||||
* Promises service.
|
||||
*/
|
||||
private $q: ng.IQService;
|
||||
/**
|
||||
* Permissions service.
|
||||
*/
|
||||
private chePermissions: che.api.IChePermissions;
|
||||
/**
|
||||
* Resources distribution service.
|
||||
*/
|
||||
private cheResourcesDistribution: che.api.ICheResourcesDistribution;
|
||||
/**
|
||||
* Organization permission service.
|
||||
*/
|
||||
private organizationsPermissionService: OrganizationsPermissionService;
|
||||
/**
|
||||
* List of organizations.
|
||||
*/
|
||||
private organizations: Array<any>;
|
||||
/**
|
||||
* Map of organization members.
|
||||
*/
|
||||
private organizationMembers: Map<string, number>;
|
||||
/**
|
||||
* Map of organization total resources.
|
||||
*/
|
||||
private organizationTotalResources: Map<string, any>;
|
||||
/**
|
||||
* Map of organization available resources.
|
||||
*/
|
||||
private organizationAvailableResources: Map<string, any>;
|
||||
/**
|
||||
* Loading state of the page.
|
||||
*/
|
||||
private isLoading: boolean;
|
||||
/**
|
||||
* On update function.
|
||||
*/
|
||||
private onUpdate: Function;
|
||||
|
||||
/**
|
||||
* Parent organization name.
|
||||
*/
|
||||
private parentName: string;
|
||||
/**
|
||||
* Parent organization id.
|
||||
*/
|
||||
private parentId: string;
|
||||
/**
|
||||
* Organization order by.
|
||||
*/
|
||||
private organizationOrderBy: string;
|
||||
/**
|
||||
* Organization filter.
|
||||
*/
|
||||
private organizationFilter: {name: string};
|
||||
/**
|
||||
* User services.
|
||||
*/
|
||||
private userServices: che.IUserServices;
|
||||
/**
|
||||
* Selection and filtration helper.
|
||||
*/
|
||||
private cheListHelper: che.widget.ICheListHelper;
|
||||
/**
|
||||
* todo
|
||||
*/
|
||||
private resourceLimits: che.resource.ICheResourceLimits;
|
||||
/**
|
||||
* todo
|
||||
*/
|
||||
private organizationActions: che.resource.ICheOrganizationActions;
|
||||
|
||||
/**
|
||||
* Default constructor that is using resource
|
||||
* @ngInject for Dependency injection
|
||||
*/
|
||||
constructor($q: ng.IQService, $scope: ng.IScope, chePermissions: che.api.IChePermissions, cheResourcesDistribution: che.api.ICheResourcesDistribution, cheOrganization: che.api.ICheOrganization, cheNotification: any, confirmDialogService: any, $route: ng.route.IRouteService, organizationsPermissionService: OrganizationsPermissionService, cheListHelperFactory: che.widget.ICheListHelperFactory, resourcesService: che.service.IResourcesService) {
|
||||
this.$q = $q;
|
||||
this.cheNotification = cheNotification;
|
||||
this.chePermissions = chePermissions;
|
||||
this.cheOrganization = cheOrganization;
|
||||
this.confirmDialogService = confirmDialogService;
|
||||
this.cheResourcesDistribution = cheResourcesDistribution;
|
||||
this.resourceLimits = resourcesService.getResourceLimits();
|
||||
this.organizationActions = resourcesService.getOrganizationActions();
|
||||
|
||||
this.parentName = $route.current.params.organizationName;
|
||||
this.organizationOrderBy = 'name';
|
||||
this.organizationFilter = {name: ''};
|
||||
|
||||
const helperId = 'list-organizations';
|
||||
this.cheListHelper = cheListHelperFactory.getHelper(helperId);
|
||||
$scope.$on('$destroy', () => {
|
||||
cheListHelperFactory.removeHelper(helperId);
|
||||
});
|
||||
|
||||
this.userServices = this.chePermissions.getUserServices();
|
||||
this.organizationsPermissionService = organizationsPermissionService;
|
||||
|
||||
$scope.$watch(() => {
|
||||
return this.organizations;
|
||||
}, (newValue: Array<any>, oldValue: Array<any>) => {
|
||||
if (newValue && !angular.equals(newValue, oldValue)) {
|
||||
this.processOrganizations();
|
||||
}
|
||||
});
|
||||
this.processOrganizations();
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback when name is changed.
|
||||
*
|
||||
* @param str {string} a string to filter organizations.
|
||||
*/
|
||||
onSearchChanged(str: string): void {
|
||||
this.organizationFilter.name = str;
|
||||
this.cheListHelper.applyFilter('name', this.organizationFilter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if user has manage permission.
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
hasManagePermission(): boolean {
|
||||
if (this.parentId) {
|
||||
return this.organizationsPermissionService.isUserAllowedTo(this.organizationActions.MANAGE_SUB_ORGANIZATION, this.parentId);
|
||||
}
|
||||
return this.userServices.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');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Copyright (c) 2015-2017 Red Hat, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Red Hat, Inc. - initial API and implementation
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* @ngdoc directive
|
||||
* @name organizations.organizations:ListOrganizations
|
||||
* @restrict E
|
||||
* @element
|
||||
*
|
||||
* @description
|
||||
* `<list-organizations organizations="ctrl.organizations"></list-organizations>` for displaying list of organizations
|
||||
*
|
||||
* @usage
|
||||
* <list-organizations organizations="ctrl.organizations"></list-organizations>
|
||||
*
|
||||
* @author Oleksii Orel
|
||||
*/
|
||||
export class ListOrganizations implements ng.IDirective {
|
||||
|
||||
restrict: string = 'E';
|
||||
templateUrl: string = 'app/organizations/list-organizations/list-organizations.html';
|
||||
|
||||
controller: string = 'ListOrganizationsController';
|
||||
controllerAs: string = 'listOrganizationsController';
|
||||
bindToController: boolean = true;
|
||||
|
||||
scope: any = {
|
||||
isLoading: '=?',
|
||||
organizations: '=',
|
||||
hideAddButton: '=?',
|
||||
onUpdate: '&?onUpdate'
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
<md-content flex class="list-organizations-content">
|
||||
<div ng-class="{'list-empty': !(listOrganizationsController.organizations | filter:listOrganizationsController.organizationFilter).length}">
|
||||
<che-list-header che-hide-header="!listOrganizationsController.organizations || listOrganizationsController.cheListHelper.visibleItemsNumber === 0"
|
||||
che-input-placeholder="Search"
|
||||
che-search-model="listOrganizationsController.organizationFilter.name"
|
||||
che-on-search-change="listOrganizationsController.onSearchChanged(str)"
|
||||
che-hide-search="!listOrganizationsController.organizations || listOrganizationsController.organizations.length === 0"
|
||||
che-add-button-title="{{listOrganizationsController.parentName ? 'Add Sub-Organization' : 'Add Organization'}}"
|
||||
che-add-button-href="#/admin/create-organization{{listOrganizationsController.parentName ? '/' + listOrganizationsController.parentName : ''}}"
|
||||
che-hide-add="!listOrganizationsController.hasManagePermission() || listOrganizationsController.hideAddButton"
|
||||
che-delete-button-title="Delete"
|
||||
che-on-delete="listOrganizationsController.deleteSelectedOrganizations()"
|
||||
che-hide-delete="!listOrganizationsController.hasManagePermission() || listOrganizationsController.cheListHelper.isNoItemSelected"
|
||||
che-filter-values="listOrganizationsController.namespaceLabels"
|
||||
che-on-filter-changed="listOrganizationsController.onFilterChanged">
|
||||
<div flex="100"
|
||||
layout="row"
|
||||
layout-align="start stretch"
|
||||
class="che-list-item-row">
|
||||
<div ng-if="listOrganizationsController.hasManagePermission()"
|
||||
layout="column" layout-gt-xs="row" layout-align="start center" class="che-checkbox-area">
|
||||
<div layout="row" layout-align="center center" class="che-list-item-checkbox-main">
|
||||
<md-checkbox class="che-list-item-checkbox"
|
||||
aria-label="organization list"
|
||||
ng-checked="listOrganizationsController.cheListHelper.areAllItemsSelected"
|
||||
ng-click="listOrganizationsController.cheListHelper.changeBulkSelection()"></md-checkbox>
|
||||
</div>
|
||||
</div>
|
||||
<div flex hide-xs layout-gt-xs="row"
|
||||
layout-align="start center"
|
||||
class="che-list-item-details">
|
||||
<che-list-header-column flex-gt-xs="30"
|
||||
che-sort-value='listOrganizationsController.organizationOrderBy'
|
||||
che-sort-item='name'
|
||||
che-column-title='Name'></che-list-header-column>
|
||||
<che-list-header-column flex-gt-xs="20"
|
||||
che-column-title='Members'></che-list-header-column>
|
||||
<che-list-header-column flex-gt-xs="15"
|
||||
che-column-title='Total RAM'></che-list-header-column>
|
||||
<che-list-header-column flex-gt-xs="15"
|
||||
che-column-title='Available RAM'></che-list-header-column>
|
||||
<che-list-header-column flex-gt-xs="20"
|
||||
che-column-title='Sub-Organizations'></che-list-header-column>
|
||||
<che-list-header-column flex-gt-xs="15"
|
||||
che-column-title='Actions'></che-list-header-column>
|
||||
</div>
|
||||
</div>
|
||||
</che-list-header>
|
||||
<che-list ng-show="listOrganizationsController.organizations">
|
||||
<organizations-item
|
||||
ng-repeat="organization in listOrganizationsController.cheListHelper.getVisibleItems() | orderBy:listOrganizationsController.organizationOrderBy"
|
||||
organization="organization"
|
||||
members="listOrganizationsController.getMembersCount(organization.id)"
|
||||
total-ram="listOrganizationsController.getTotalRAM(organization.id)"
|
||||
available-ram="listOrganizationsController.getAvailableRAM(organization.id)"
|
||||
cdvy-is-selectable="listOrganizationsController.hasManagePermission()"
|
||||
ng-model="listOrganizationsController.cheListHelper.itemsSelectionStatus[organization.id]"
|
||||
on-update="listOrganizationsController.onUpdate()"
|
||||
cdvy-on-checkbox-click="listOrganizationsController.cheListHelper.updateBulkSelectionStatus()">
|
||||
</organizations-item>
|
||||
</che-list>
|
||||
</div>
|
||||
<div class="che-list-empty">
|
||||
<span ng-show="listOrganizationsController.cheListHelper.visibleItemsNumber === 0">There are no {{listOrganizationsController.parentName ? 'sub-organizations' : 'organizations'}}.</span>
|
||||
</div>
|
||||
</md-content>
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
.list-organizations-content
|
||||
margin 0
|
||||
|
||||
.che-list-item
|
||||
display block !important
|
||||
|
||||
.list-empty .che-list-header md-item
|
||||
border-top none
|
||||
|
|
@ -0,0 +1,128 @@
|
|||
/*
|
||||
* Copyright (c) 2015-2017 Red Hat, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Red Hat, Inc. - initial API and implementation
|
||||
*/
|
||||
'use strict';
|
||||
import {OrganizationsPermissionService} from '../../organizations-permission.service';
|
||||
|
||||
/**
|
||||
* @ngdoc controller
|
||||
* @name organizations.list.Item.controller:OrganizationsItemController
|
||||
* @description This class is handling the controller for item of organizations list
|
||||
* @author Oleksii Orel
|
||||
*/
|
||||
export class OrganizationsItemController {
|
||||
/**
|
||||
* Service for displaying dialogs.
|
||||
*/
|
||||
private confirmDialogService: any;
|
||||
/**
|
||||
* Location service.
|
||||
*/
|
||||
private $location: ng.ILocationService;
|
||||
/**
|
||||
* User service.
|
||||
*/
|
||||
private userServices: che.IUserServices;
|
||||
/**
|
||||
* Organization permission service.
|
||||
*/
|
||||
private organizationsPermissionService: OrganizationsPermissionService;
|
||||
/**
|
||||
* Organization API interaction.
|
||||
*/
|
||||
private cheOrganization: che.api.ICheOrganization;
|
||||
/**
|
||||
* Service for displaying notifications.
|
||||
*/
|
||||
private cheNotification: any;
|
||||
/**
|
||||
* Organization details (the value is set in directive attributes).
|
||||
*/
|
||||
private organization: che.IOrganization;
|
||||
/**
|
||||
* Callback needed to react on organizations updation (the value is set in directive attributes).
|
||||
*/
|
||||
private onUpdate: Function;
|
||||
/**
|
||||
* todo
|
||||
*/
|
||||
private organizationActions: che.resource.ICheOrganizationActions;
|
||||
|
||||
/**
|
||||
* Default constructor that is using resource injection
|
||||
* @ngInject for Dependency injection
|
||||
*/
|
||||
constructor($location: ng.ILocationService, cheOrganization: che.api.ICheOrganization, confirmDialogService: any, cheNotification: any, organizationsPermissionService: OrganizationsPermissionService, chePermissions: che.api.IChePermissions, resourcesService: che.service.IResourcesService) {
|
||||
this.$location = $location;
|
||||
this.confirmDialogService = confirmDialogService;
|
||||
this.cheOrganization = cheOrganization;
|
||||
this.cheNotification = cheNotification;
|
||||
this.organizationsPermissionService = organizationsPermissionService;
|
||||
this.organizationActions = resourcesService.getOrganizationActions();
|
||||
|
||||
this.userServices = chePermissions.getUserServices();
|
||||
}
|
||||
|
||||
/**
|
||||
* returns true if current user has Delete permission
|
||||
* @returns {boolean}
|
||||
*/
|
||||
hasDeletePermission(): boolean {
|
||||
if (!this.organization || (!this.organization.parent && !this.userServices.hasAdminUserService)) {
|
||||
return false;
|
||||
}
|
||||
return this.organizationsPermissionService.isUserAllowedTo(this.organizationActions.DELETE, this.organization.id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all sub organizations.
|
||||
*/
|
||||
getAllSubOrganizations(): Array<che.IOrganization> {
|
||||
let subOrganizationsTree = this.cheOrganization.getOrganizations().filter((organization: che.IOrganization) => {
|
||||
if (!organization.parent || this.organization.id === organization.id) {
|
||||
return false;
|
||||
}
|
||||
return organization.qualifiedName.indexOf(this.organization.qualifiedName + '/') === 0;
|
||||
});
|
||||
|
||||
return subOrganizationsTree;
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirect to factory details.
|
||||
*/
|
||||
redirectToOrganizationDetails(tab: string) {
|
||||
this.$location.path('/organization/' + this.organization.qualifiedName).search(!tab ? {} : {tab: tab});
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes organization after confirmation.
|
||||
*/
|
||||
removeOrganization(): void {
|
||||
this.confirmRemoval().then(() => {
|
||||
this.cheOrganization.deleteOrganization(this.organization.id).then(() => {
|
||||
this.onUpdate();
|
||||
}, (error: any) => {
|
||||
let message = 'Failed to delete organization ' + this.organization.name;
|
||||
this.cheNotification.showError(error && error.data && error.data.message ? error.data.message : message);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows dialog to confirm the current organization removal.
|
||||
*
|
||||
* @returns {angular.IPromise<any>}
|
||||
*/
|
||||
confirmRemoval(): ng.IPromise<any> {
|
||||
return this.confirmDialogService.showConfirmDialog('Delete organization',
|
||||
'Would you like to delete organization \'' + this.organization.name + '\'?', 'Delete');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* Copyright (c) 2015-2017 Red Hat, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Red Hat, Inc. - initial API and implementation
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* @ngdoc directive
|
||||
* @name organizations.list.Item.controller:WorkspaceItem
|
||||
* @restrict E
|
||||
* @element
|
||||
*
|
||||
* @description
|
||||
* `<organizations-item organization="ctrl.organization"></organizations-item>` for displaying list of organizations
|
||||
*
|
||||
* @usage
|
||||
* <organizations-item organization="ctrl.organization"></organizations-item>
|
||||
*
|
||||
* @author Oleksii Orel
|
||||
*/
|
||||
export class OrganizationsItem implements ng.IDirective {
|
||||
|
||||
restrict = 'E';
|
||||
require = ['ngModel'];
|
||||
templateUrl = 'app/organizations/list-organizations/organizations-item/organizations-item.html';
|
||||
controller = 'OrganizationsItemController';
|
||||
controllerAs = 'organizationsItemController';
|
||||
bindToController = true;
|
||||
|
||||
// scope values
|
||||
scope = {
|
||||
organization: '=',
|
||||
members: '=',
|
||||
totalRam: '=',
|
||||
availableRam: '=',
|
||||
isChecked: '=cdvyChecked',
|
||||
isSelect: '=?ngModel',
|
||||
isSelectable: '=?cdvyIsSelectable',
|
||||
onCheckboxClick: '&?cdvyOnCheckboxClick',
|
||||
onUpdate: '&?onUpdate'
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
<che-list-item flex ng-mouseover="hover=true" ng-mouseout="hover=false">
|
||||
<div flex="100"
|
||||
layout="row"
|
||||
layout-align="start stretch"
|
||||
class="che-list-item-row">
|
||||
<div ng-if="organizationsItemController.isSelectable"
|
||||
layout="row" layout-align="start center" class="che-checkbox-area">
|
||||
<che-list-item-checked ng-model="organizationsItemController.isSelect"
|
||||
che-aria-label-checkbox="Organization {{organizationsItemController.organization.name}}"
|
||||
ng-click="organizationsItemController.onCheckboxClick()"></che-list-item-checked>
|
||||
</div>
|
||||
<div flex
|
||||
layout-xs="column" layout-gt-xs="row"
|
||||
layout-align-gt-xs="start center"
|
||||
layout-align-xs="start start"
|
||||
class="che-list-item-details">
|
||||
<div flex-gt-xs="30"
|
||||
class="che-list-item-name"
|
||||
ng-click="organizationsItemController.redirectToOrganizationDetails();">
|
||||
<span class="che-xs-header noselect" hide-gt-xs>Name</span>
|
||||
<span class="che-hover">{{organizationsItemController.organization.qualifiedName}}</span>
|
||||
</div>
|
||||
<div flex-gt-xs="20"
|
||||
ng-click="organizationsItemController.redirectToOrganizationDetails('Members')">
|
||||
<span class="che-xs-header noselect" hide-gt-xs>Members</span>
|
||||
<span>{{organizationsItemController.members}}</span>
|
||||
</div>
|
||||
<div flex-gt-xs="15"
|
||||
ng-click="organizationsItemController.redirectToOrganizationDetails()"
|
||||
class="cap-value">
|
||||
<span class="che-xs-header noselect" hide-gt-xs>Total RAM</span>
|
||||
<span>{{organizationsItemController.totalRam ? organizationsItemController.totalRam + 'GB' : 'not limited'}}</span>
|
||||
</div>
|
||||
<div flex-gt-xs="15"
|
||||
ng-click="organizationsItemController.redirectToOrganizationDetails()"
|
||||
class="cap-value">
|
||||
<span class="che-xs-header noselect" hide-gt-xs>Available RAM</span>
|
||||
<span>{{organizationsItemController.availableRam ? organizationsItemController.availableRam + 'GB' : 'not limited'}}</span>
|
||||
</div>
|
||||
<div flex-gt-xs="20"
|
||||
ng-click="organizationsItemController.redirectToOrganizationDetails('Organization')">
|
||||
<span class="che-xs-header noselect" hide-gt-xs>Sub-Organizations</span>
|
||||
<span>
|
||||
{{organizationsItemController.getAllSubOrganizations().length ? organizationsItemController.getAllSubOrganizations().length : 'none'}}
|
||||
</span>
|
||||
</div>
|
||||
<div flex-gt-xs="15">
|
||||
<span class="che-xs-header noselect" hide-gt-xs>Actions</span>
|
||||
<span class="che-list-actions" ng-if="organizationsItemController.hasDeletePermission() || organizationsItemController.isSelectable">
|
||||
<a uib-tooltip="Remove organization" ng-click="organizationsItemController.removeOrganization();">
|
||||
<span class="fa fa-trash-o"></span>
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</che-list-item>
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
.cap-value
|
||||
color $primary-color
|
||||
|
|
@ -0,0 +1,556 @@
|
|||
/*
|
||||
* Copyright (c) 2015-2017 Red Hat, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Red Hat, Inc. - initial API and implementation
|
||||
*/
|
||||
'use strict';
|
||||
import {OrganizationsPermissionService} from '../organizations-permission.service';
|
||||
|
||||
enum Tab {Settings, Members, Organization}
|
||||
|
||||
/**
|
||||
* Controller for a managing organization details.
|
||||
*
|
||||
* @author Oleksii Orel
|
||||
*/
|
||||
export class OrganizationDetailsController {
|
||||
tab: Object = Tab;
|
||||
|
||||
/**
|
||||
* Organization API interaction.
|
||||
*/
|
||||
private cheOrganization: che.api.ICheOrganization;
|
||||
/**
|
||||
* Organization resources API interaction.
|
||||
*/
|
||||
private cheResourcesDistribution: che.api.ICheResourcesDistribution;
|
||||
/**
|
||||
* Permissions API interaction.
|
||||
*/
|
||||
private chePermissions: che.api.IChePermissions;
|
||||
/**
|
||||
* User API interaction.
|
||||
*/
|
||||
private cheUser: any;
|
||||
/**
|
||||
* Notifications service.
|
||||
*/
|
||||
private cheNotification: any;
|
||||
/**
|
||||
* Location service.
|
||||
*/
|
||||
private $location: ng.ILocationService;
|
||||
/**
|
||||
* Route service.
|
||||
*/
|
||||
private $route: ng.route.IRouteService;
|
||||
/**
|
||||
* Service for displaying dialogs.
|
||||
*/
|
||||
private confirmDialogService: any;
|
||||
/**
|
||||
* Lodash library.
|
||||
*/
|
||||
private lodash: any;
|
||||
/**
|
||||
* Current organization's name. Comes from route path params.
|
||||
*/
|
||||
private organizationName: string;
|
||||
/**
|
||||
* Current organization's data (injected by $routeProvider)
|
||||
*/
|
||||
private organization: che.IOrganization;
|
||||
/**
|
||||
* Parent organization member's list (injected by $routeProvider)
|
||||
*/
|
||||
private parentOrganizationMembers: Array<che.IUser>;
|
||||
/**
|
||||
* The list of allowed user actions.
|
||||
*/
|
||||
private allowedUserActions: Array<string>;
|
||||
/**
|
||||
* New organization's name (for renaming widget).
|
||||
*/
|
||||
private newName: string;
|
||||
/**
|
||||
* Index of the selected tab.
|
||||
*/
|
||||
private selectedTabIndex: number;
|
||||
/**
|
||||
* Organization limits.
|
||||
*/
|
||||
private limits: any;
|
||||
/**
|
||||
* Copy of limits before letting to modify, to be able to compare.
|
||||
*/
|
||||
private limitsCopy: any;
|
||||
/**
|
||||
* Organization total resources.
|
||||
*/
|
||||
private totalResources: any;
|
||||
/**
|
||||
* Copy of organization total resources before letting to modify, to be able to compare.
|
||||
*/
|
||||
private totalResourcesCopy: any;
|
||||
/**
|
||||
* Page loading state.
|
||||
*/
|
||||
private isLoading: boolean;
|
||||
|
||||
private organizationForm: ng.IFormController;
|
||||
|
||||
private subOrganizations: Array<any> = [];
|
||||
|
||||
private organizationsPermissionService: OrganizationsPermissionService;
|
||||
|
||||
private resourceLimits: che.resource.ICheResourceLimits;
|
||||
|
||||
private organizationActions: che.resource.ICheOrganizationActions;
|
||||
|
||||
/**
|
||||
* Default constructor that is using resource injection
|
||||
* @ngInject for Dependency injection
|
||||
*/
|
||||
constructor(cheResourcesDistribution: che.api.ICheResourcesDistribution, chePermissions: che.api.IChePermissions,
|
||||
cheUser: any, $route: ng.route.IRouteService, $location: ng.ILocationService, $rootScope: che.IRootScopeService,
|
||||
$scope: ng.IScope, confirmDialogService: any, cheNotification: any,
|
||||
lodash: any, cheOrganization: che.api.ICheOrganization, organizationsPermissionService: OrganizationsPermissionService, resourcesService: che.service.IResourcesService,
|
||||
initData: any) {
|
||||
this.cheResourcesDistribution = cheResourcesDistribution;
|
||||
this.confirmDialogService = confirmDialogService;
|
||||
this.cheOrganization = cheOrganization;
|
||||
this.organizationsPermissionService = organizationsPermissionService;
|
||||
this.chePermissions = chePermissions;
|
||||
this.cheNotification = cheNotification;
|
||||
this.cheUser = cheUser;
|
||||
this.$location = $location;
|
||||
this.$route = $route;
|
||||
this.lodash = lodash;
|
||||
this.resourceLimits = resourcesService.getResourceLimits();
|
||||
this.organizationActions = resourcesService.getOrganizationActions();
|
||||
|
||||
// injected by router
|
||||
this.organization = initData.organization as che.IOrganization;
|
||||
this.parentOrganizationMembers = initData.parentOrganizationMembers as Array<che.IUser>;
|
||||
|
||||
$rootScope.showIDE = false;
|
||||
|
||||
this.allowedUserActions = [];
|
||||
|
||||
this.updateData();
|
||||
|
||||
this.updateSelectedTab(this.$location.search().tab);
|
||||
let deRegistrationFn = $scope.$watch(() => {
|
||||
return $location.search().tab;
|
||||
}, (tab: string) => {
|
||||
if (angular.isDefined(tab)) {
|
||||
this.updateSelectedTab(tab);
|
||||
}
|
||||
}, true);
|
||||
$scope.$on('$destroy', () => {
|
||||
deRegistrationFn();
|
||||
});
|
||||
}
|
||||
|
||||
get SET_PERMISSIONS(): string {
|
||||
return this.organizationActions.SET_PERMISSIONS;
|
||||
}
|
||||
|
||||
get DELETE(): string {
|
||||
return this.organizationActions.DELETE;
|
||||
}
|
||||
|
||||
get UPDATE(): string {
|
||||
return this.organizationActions.UPDATE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch sub-organizations.
|
||||
*/
|
||||
fetchSubOrganizations() {
|
||||
let manageSubOrganizations = this.isUserAllowedTo(this.organizationActions.MANAGE_SUB_ORGANIZATION);
|
||||
|
||||
if (manageSubOrganizations) {
|
||||
this.cheOrganization.fetchSubOrganizationsById(this.organization.id).then((data: any) => {
|
||||
this.subOrganizations = data;
|
||||
});
|
||||
} else {
|
||||
this.cheOrganization.fetchOrganizations().then(() => {
|
||||
this.subOrganizations = this.lodash.filter(this.cheOrganization.getOrganizations(), (organization: che.IOrganization) => {
|
||||
return organization.parent === this.organization.id;
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update data.
|
||||
*/
|
||||
updateData(): void {
|
||||
this.organizationName = this.$route.current.params.organizationName;
|
||||
if (!this.organization) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.newName = angular.copy(this.organization.name);
|
||||
|
||||
if (this.isRootOrganization()) {
|
||||
this.processTotalResources();
|
||||
} else {
|
||||
this.processResources();
|
||||
}
|
||||
|
||||
this.allowedUserActions = this.processUserPermissions();
|
||||
this.fetchSubOrganizations();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update selected tab index by search part of URL.
|
||||
*
|
||||
* @param {string} tab
|
||||
*/
|
||||
updateSelectedTab(tab: string): void {
|
||||
this.selectedTabIndex = parseInt(this.tab[tab], 10);
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes search part of URL.
|
||||
*
|
||||
* @param {number} tabIndex
|
||||
*/
|
||||
onSelectTab(tabIndex?: number): void {
|
||||
let param: { tab?: string } = {};
|
||||
if (angular.isDefined(tabIndex)) {
|
||||
param.tab = Tab[tabIndex];
|
||||
}
|
||||
if (angular.isUndefined(this.$location.search().tab)) {
|
||||
this.$location.replace().search(param);
|
||||
} else {
|
||||
this.$location.search(param);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets sub-organizations for current organization.
|
||||
*
|
||||
* @returns {Array<any>}
|
||||
*/
|
||||
getSubOrganizations(): Array<any> {
|
||||
return this.subOrganizations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process permissions to retrieve current user actions.
|
||||
*
|
||||
* @returns {Array} current user allowed actions
|
||||
*/
|
||||
processUserPermissions(): Array<string> {
|
||||
let userId = this.cheUser.getUser().id;
|
||||
let permissions = this.chePermissions.getOrganizationPermissions(this.organization.id);
|
||||
let userPermissions = this.lodash.find(permissions, (permission: any) => {
|
||||
return permission.userId === userId;
|
||||
});
|
||||
return userPermissions ? userPermissions.actions : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether user is allowed to perform pointed action.
|
||||
*
|
||||
* @param value action
|
||||
* @returns {boolean} <code>true</code> if allowed
|
||||
*/
|
||||
isUserAllowedTo(value: string): boolean {
|
||||
if (value === this.organizationActions.UPDATE && this.isPersonalOrganization()) {
|
||||
return false;
|
||||
}
|
||||
return this.allowedUserActions ? this.allowedUserActions.indexOf(value) >= 0 : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for personal.
|
||||
*
|
||||
* @returns {boolean} <code>true</code> if personal
|
||||
*/
|
||||
isPersonalOrganization(): boolean {
|
||||
let user = this.cheUser.getUser();
|
||||
return this.organization && user && this.organization.qualifiedName === user.name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for root.
|
||||
*
|
||||
* @returns {boolean} <code>true</code> if root
|
||||
*/
|
||||
isRootOrganization(): boolean {
|
||||
return this.organization && !this.organization.parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether current user can change organization resource limits.
|
||||
*
|
||||
* @returns {boolean} <code>true</code> if can change resource limits
|
||||
*/
|
||||
canChangeResourceLimits(): boolean {
|
||||
if (this.isRootOrganization()) {
|
||||
return this.chePermissions.getUserServices().hasAdminUserService;
|
||||
}
|
||||
return this.organizationsPermissionService.isUserAllowedTo(this.organizationActions.MANAGE_RESOURCES, this.organization.parent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the name is unique.
|
||||
* @param name
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isUniqueName(name: string): boolean {
|
||||
let currentOrganizationName = this.organization.name;
|
||||
let organizations = this.cheOrganization.getOrganizations();
|
||||
let account = '';
|
||||
let parentId = this.organization.parent;
|
||||
if (parentId) {
|
||||
let parent = this.cheOrganization.getOrganizationById(parentId);
|
||||
if (parent && parent.qualifiedName) {
|
||||
account = parent.qualifiedName + '/';
|
||||
}
|
||||
}
|
||||
if (organizations.length && currentOrganizationName !== name) {
|
||||
for (let i = 0; i < organizations.length; i++) {
|
||||
if (organizations[i].qualifiedName === account + name) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches defined organization's limits (workspace, runtime, RAM caps, etc).
|
||||
*/
|
||||
fetchLimits(): void {
|
||||
this.isLoading = true;
|
||||
this.cheResourcesDistribution.fetchOrganizationResources(this.organization.id).then(() => {
|
||||
this.isLoading = false;
|
||||
this.processResources();
|
||||
}, (error: any) => {
|
||||
this.isLoading = false;
|
||||
this.limits = {};
|
||||
this.limitsCopy = angular.copy(this.limits);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Process resources limits.
|
||||
*/
|
||||
processResources(): void {
|
||||
let ramLimit = this.cheResourcesDistribution.getOrganizationResourceByType(this.organization.id, this.resourceLimits.RAM);
|
||||
let workspaceLimit = this.cheResourcesDistribution.getOrganizationResourceByType(this.organization.id, this.resourceLimits.WORKSPACE);
|
||||
let runtimeLimit = this.cheResourcesDistribution.getOrganizationResourceByType(this.organization.id, this.resourceLimits.RUNTIME);
|
||||
|
||||
this.limits = {};
|
||||
this.limits.workspaceCap = workspaceLimit ? workspaceLimit.amount : undefined;
|
||||
this.limits.runtimeCap = runtimeLimit ? runtimeLimit.amount : undefined;
|
||||
this.limits.ramCap = ramLimit ? ramLimit.amount / 1024 : undefined;
|
||||
this.limitsCopy = angular.copy(this.limits);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Fetches total resources of the organization (workspace, runtime, RAM caps, etc).
|
||||
*/
|
||||
fetchTotalResources(): void {
|
||||
this.isLoading = true;
|
||||
this.cheResourcesDistribution.fetchTotalOrganizationResources(this.organization.id).then(() => {
|
||||
this.isLoading = false;
|
||||
this.processTotalResources();
|
||||
}, (error: any) => {
|
||||
this.isLoading = false;
|
||||
this.limits = {};
|
||||
this.limitsCopy = angular.copy(this.limits);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Process organization's total resources.
|
||||
*/
|
||||
processTotalResources(): void {
|
||||
let ram = this.cheResourcesDistribution.getOrganizationTotalResourceByType(this.organization.id, this.resourceLimits.RAM);
|
||||
let workspace = this.cheResourcesDistribution.getOrganizationTotalResourceByType(this.organization.id, this.resourceLimits.WORKSPACE);
|
||||
let runtime = this.cheResourcesDistribution.getOrganizationTotalResourceByType(this.organization.id, this.resourceLimits.RUNTIME);
|
||||
|
||||
this.totalResources = {};
|
||||
this.totalResources.workspaceCap = (workspace && workspace.amount !== -1) ? workspace.amount : undefined;
|
||||
this.totalResources.runtimeCap = (runtime && runtime.amount !== -1) ? runtime.amount : undefined;
|
||||
this.totalResources.ramCap = (ram && ram.amount !== -1) ? ram.amount / 1024 : undefined;
|
||||
this.totalResourcesCopy = angular.copy(this.totalResources);
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirms and performs organization's deletion.
|
||||
*/
|
||||
deleteOrganization(): void {
|
||||
let promise = this.confirmDialogService.showConfirmDialog('Delete organization',
|
||||
'Would you like to delete organization \'' + this.organization.name + '\'?', 'Delete');
|
||||
|
||||
promise.then(() => {
|
||||
let promise = this.cheOrganization.deleteOrganization(this.organization.id);
|
||||
promise.then(() => {
|
||||
this.$location.path('/organizations');
|
||||
|
||||
}, (error: any) => {
|
||||
this.cheNotification.showError(error.data.message !== null ? error.data.message : 'Team deletion failed.');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update organization's details.
|
||||
*
|
||||
*/
|
||||
updateOrganizationName(): void {
|
||||
if (this.newName && this.organization && this.newName !== this.organization.name) {
|
||||
this.organization.name = this.newName;
|
||||
this.cheOrganization.updateOrganization(this.organization).then((organization: che.IOrganization) => {
|
||||
this.cheOrganization.fetchOrganizations().then(() => {
|
||||
this.$location.path('/organization/' + organization.qualifiedName);
|
||||
});
|
||||
}, (error: any) => {
|
||||
this.cheNotification.showError((error.data && error.data.message !== null) ? error.data.message : 'Rename organization failed.');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update resource limits.
|
||||
*/
|
||||
updateLimits(): void {
|
||||
if (!this.organization || !this.limits || angular.equals(this.limits, this.limitsCopy)) {
|
||||
return;
|
||||
}
|
||||
let resources = angular.copy(this.cheResourcesDistribution.getOrganizationResources(this.organization.id));
|
||||
|
||||
let resourcesToRemove = [this.resourceLimits.TIMEOUT];
|
||||
if (this.limits.ramCap !== null && this.limits.ramCap !== undefined) {
|
||||
resources = this.cheResourcesDistribution.setOrganizationResourceLimitByType(resources, this.resourceLimits.RAM, (this.limits.ramCap * 1024).toString());
|
||||
} else {
|
||||
resourcesToRemove.push(this.resourceLimits.RAM);
|
||||
}
|
||||
if (this.limits.workspaceCap !== null && this.limits.workspaceCap !== undefined) {
|
||||
resources = this.cheResourcesDistribution.setOrganizationResourceLimitByType(resources, this.resourceLimits.WORKSPACE, this.limits.workspaceCap);
|
||||
} else {
|
||||
resourcesToRemove.push(this.resourceLimits.WORKSPACE);
|
||||
}
|
||||
if (this.limits.runtimeCap !== null && this.limits.runtimeCap !== undefined) {
|
||||
resources = this.cheResourcesDistribution.setOrganizationResourceLimitByType(resources, this.resourceLimits.RUNTIME, this.limits.runtimeCap);
|
||||
} else {
|
||||
resourcesToRemove.push(this.resourceLimits.RUNTIME);
|
||||
}
|
||||
// if the timeout resource will be send in this case - it will set the timeout for the current organization, and the updating timeout of
|
||||
// parent organization will not affect the current organization, so to avoid this - remove timeout resource if present:
|
||||
this.lodash.remove(resources, (resource: any) => {
|
||||
return resourcesToRemove.indexOf(resource.type) >= 0;
|
||||
});
|
||||
|
||||
this.isLoading = true;
|
||||
this.cheResourcesDistribution.distributeResources(this.organization.id, resources).then(() => {
|
||||
this.fetchLimits();
|
||||
}, (error: any) => {
|
||||
let errorMessage = 'Failed to set update organization CAPs.';
|
||||
this.cheNotification.showError((error.data && error.data.message !== null) ? errorMessage + '</br>Reason: ' + error.data.message : errorMessage);
|
||||
this.fetchLimits();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update resource limits.
|
||||
*/
|
||||
updateTotalResources(): void {
|
||||
if (!this.organization || !this.totalResources || angular.equals(this.totalResources, this.totalResourcesCopy)) {
|
||||
return;
|
||||
}
|
||||
let resources = angular.copy(this.cheResourcesDistribution.getTotalOrganizationResources(this.organization.id));
|
||||
|
||||
let resourcesToRemove = [this.resourceLimits.TIMEOUT];
|
||||
if (this.totalResources.ramCap !== null && this.totalResources.ramCap !== undefined) {
|
||||
resources = this.cheResourcesDistribution.setOrganizationResourceLimitByType(resources, this.resourceLimits.RAM, (this.totalResources.ramCap * 1024).toString());
|
||||
} else {
|
||||
resources = this.cheResourcesDistribution.setOrganizationResourceLimitByType(resources, this.resourceLimits.RAM, '-1');
|
||||
}
|
||||
if (this.totalResources.workspaceCap !== null && this.totalResources.workspaceCap !== undefined) {
|
||||
resources = this.cheResourcesDistribution.setOrganizationResourceLimitByType(resources, this.resourceLimits.WORKSPACE, this.totalResources.workspaceCap);
|
||||
} else {
|
||||
resources = this.cheResourcesDistribution.setOrganizationResourceLimitByType(resources, this.resourceLimits.WORKSPACE, '-1');
|
||||
}
|
||||
if (this.totalResources.runtimeCap !== null && this.totalResources.runtimeCap !== undefined) {
|
||||
resources = this.cheResourcesDistribution.setOrganizationResourceLimitByType(resources, this.resourceLimits.RUNTIME, this.totalResources.runtimeCap);
|
||||
} else {
|
||||
resources = this.cheResourcesDistribution.setOrganizationResourceLimitByType(resources, this.resourceLimits.RUNTIME, '-1');
|
||||
}
|
||||
// if the timeout resource will be send in this case - it will set the timeout for the current organization, and the updating timeout of
|
||||
// parent organization will not affect the current organization, so to avoid this - remove timeout resource if present:
|
||||
this.lodash.remove(resources, (resource: any) => {
|
||||
return resourcesToRemove.indexOf(resource.type) >= 0;
|
||||
});
|
||||
|
||||
this.isLoading = true;
|
||||
|
||||
this.cheResourcesDistribution.updateTotalResources(this.organization.id, resources).then(() => {
|
||||
this.fetchTotalResources();
|
||||
}, (error: any) => {
|
||||
let errorMessage = 'Failed to update organization CAPs.';
|
||||
this.cheNotification.showError((error.data && error.data.message !== null) ? errorMessage + '</br>Reason: ' + error.data.message : errorMessage);
|
||||
this.fetchTotalResources();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether save button is disabled.
|
||||
*
|
||||
* @return {boolean}
|
||||
*/
|
||||
isSaveButtonDisabled(): boolean {
|
||||
return !this.organizationForm || this.organizationForm.$invalid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if "Save" button should be visible
|
||||
*
|
||||
* @return {boolean}
|
||||
*/
|
||||
isSaveButtonVisible(): boolean {
|
||||
return (this.selectedTabIndex === Tab.Settings && !this.isLoading) && (!angular.equals(this.organization.name, this.newName) ||
|
||||
!angular.equals(this.limits, this.limitsCopy) || !angular.equals(this.totalResources, this.totalResourcesCopy));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns back button link and title.
|
||||
*
|
||||
* @returns {any} back button link
|
||||
*/
|
||||
getBackButtonLink(): any {
|
||||
if (this.organization && this.organization.parent) {
|
||||
let parent = this.organization.qualifiedName.replace(/\/[^\/]+$/, '');
|
||||
return {link: '#/organization/' + parent, title: parent};
|
||||
} else {
|
||||
return {link: '#/organizations', title: 'Organizations'};
|
||||
}
|
||||
}
|
||||
|
||||
updateOrganization(): void {
|
||||
this.updateOrganizationName();
|
||||
this.updateLimits();
|
||||
this.updateTotalResources();
|
||||
}
|
||||
|
||||
cancelChanges(): void {
|
||||
this.newName = angular.copy(this.organization.name);
|
||||
this.limits = angular.copy(this.limitsCopy);
|
||||
this.totalResources = angular.copy(this.totalResourcesCopy);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,201 @@
|
|||
<che-toolbar che-title="{{organizationDetailsController.organizationName}}"
|
||||
che-breadcrumb-title="{{organizationDetailsController.getBackButtonLink().title}}"
|
||||
che-breadcrumb-href="{{organizationDetailsController.getBackButtonLink().link}}"
|
||||
ng-if="organizationDetailsController.organization">
|
||||
<div class="save-button-placeholder">
|
||||
<che-button-save-flat ng-show="organizationDetailsController.isSaveButtonVisible()"
|
||||
ng-disabled="organizationDetailsController.isSaveButtonDisabled()"
|
||||
che-button-title="Save" name="saveButton"
|
||||
ng-click="organizationDetailsController.updateOrganization()"></che-button-save-flat>
|
||||
</div>
|
||||
</che-toolbar>
|
||||
|
||||
<md-content md-scroll-y flex md-theme="default"
|
||||
ng-if="organizationDetailsController.organization">
|
||||
<md-tabs md-dynamic-height md-stretch-tabs="auto"
|
||||
md-selected="organizationDetailsController.selectedTabIndex"
|
||||
md-center-tabs="">
|
||||
|
||||
<!-- Settings Tab -->
|
||||
<md-tab md-on-select="organizationDetailsController.onSelectTab(organizationDetailsController.tab.Settings);">
|
||||
<md-tab-label>
|
||||
<md-icon md-font-icon="material-design icon-ic_settings_24px" class="che-tab-label-icon"></md-icon>
|
||||
<span class="che-tab-label-title">Settings</span>
|
||||
</md-tab-label>
|
||||
<md-tab-body>
|
||||
<div class="organization-progress-line">
|
||||
<md-progress-linear md-mode="indeterminate"
|
||||
ng-show="organizationDetailsController.isLoading"></md-progress-linear>
|
||||
</div>
|
||||
<div flex layout="column" class="organization-details-content">
|
||||
<ng-form name="organizationDetailsController.organizationForm">
|
||||
<!-- Name -->
|
||||
<che-label-container che-label-name="Name">
|
||||
<che-input-box che-form="organizationDetailsController.organizationForm"
|
||||
che-name="name"
|
||||
aria-label="Name of the organization"
|
||||
che-place-holder="Name of the organization"
|
||||
ng-model="organizationDetailsController.newName"
|
||||
che-readonly="!organizationDetailsController.isUserAllowedTo(organizationDetailsController.UPDATE)"
|
||||
custom-validator="organizationDetailsController.isUniqueName($value)"
|
||||
required
|
||||
ng-maxlength="20"
|
||||
ng-pattern="/^[a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,38}$/i">
|
||||
<div ng-message="pattern">The name can contain alphanumeric characters or single '-' inside.
|
||||
</div>
|
||||
<div ng-message="maxlength">The name has to be less than 20 characters long.</div>
|
||||
<div ng-message="md-maxlength">The name has to be less than 20 characters long.</div>
|
||||
<div ng-message="customValidator">This organization name is already used.</div>
|
||||
</che-input-box>
|
||||
|
||||
</che-label-container>
|
||||
<div ng-if="!organizationDetailsController.isRootOrganization()">
|
||||
<!-- Workspace cap -->
|
||||
<che-label-container che-label-name="Workspace Cap"
|
||||
che-label-description="Maximum number of workspaces for the organization.">
|
||||
<che-input-box che-name="workspaceCap" che-form="organizationDetailsController.organizationForm"
|
||||
aria-label="workspace cap"
|
||||
che-place-holder="Total number of workspaces has not been limited."
|
||||
ng-model="organizationDetailsController.limits.workspaceCap"
|
||||
che-readonly="!organizationDetailsController.canChangeResourceLimits()"
|
||||
type="number"
|
||||
che-type-number
|
||||
min="0"
|
||||
max="1000">
|
||||
<div ng-message="min">A workspace cap should be greater than 0.</div>
|
||||
</che-input-box>
|
||||
</che-label-container>
|
||||
|
||||
<!-- Running workspace cap -->
|
||||
<che-label-container che-label-name="Running Workspace Cap"
|
||||
che-label-description="Maximum number of running workspaces for each organization.">
|
||||
<che-input-box che-name="runtimeCap" che-form="organizationDetailsController.organizationForm"
|
||||
aria-label="runtime cap"
|
||||
che-place-holder="Number of running workspaces has not been limited."
|
||||
ng-model="organizationDetailsController.limits.runtimeCap"
|
||||
che-readonly="!organizationDetailsController.canChangeResourceLimits()"
|
||||
type="number"
|
||||
che-type-number
|
||||
min="0"
|
||||
max="1000">
|
||||
<div ng-message="min">A running workspace cap should be greater than 0.</div>
|
||||
</che-input-box>
|
||||
</che-label-container>
|
||||
<!-- Workspace RAM cap -->
|
||||
<che-label-container che-label-name="Workspace RAM Cap" class="organization-ram-cap"
|
||||
che-label-description="Maximum RAM organization workspaces can use.">
|
||||
<che-input-box che-name="workspaceRamCap" che-form="organizationDetailsController.organizationForm"
|
||||
aria-label="runtime cap"
|
||||
che-place-holder="Workspace RAM has not been limited."
|
||||
ng-model="organizationDetailsController.limits.ramCap"
|
||||
che-readonly="!organizationDetailsController.canChangeResourceLimits()"
|
||||
type="number"
|
||||
che-type-number
|
||||
min="0"
|
||||
max="1000">
|
||||
<div ng-message="min">A workspace RAM cap should be greater than 0.</div>
|
||||
</che-input-box>
|
||||
</che-label-container>
|
||||
</div>
|
||||
<div ng-if="organizationDetailsController.isRootOrganization()">
|
||||
<!-- Workspace total resources -->
|
||||
<che-label-container che-label-name="Workspace Cap"
|
||||
che-label-description="Maximum number of workspaces for the organization.">
|
||||
<che-input-box che-name="workspaceCap" che-form="organizationDetailsController.organizationForm"
|
||||
aria-label="workspace cap"
|
||||
che-place-holder="Total number of workspaces has not been limited."
|
||||
ng-model="organizationDetailsController.totalResources.workspaceCap"
|
||||
che-readonly="!organizationDetailsController.canChangeResourceLimits()"
|
||||
type="number"
|
||||
che-type-number
|
||||
min="0"
|
||||
max="1000">
|
||||
<div ng-message="min">A workspace cap should be greater than 0.</div>
|
||||
</che-input-box>
|
||||
</che-label-container>
|
||||
|
||||
<!-- Running workspace cap -->
|
||||
<che-label-container che-label-name="Running Workspace Cap"
|
||||
che-label-description="Maximum number of running workspaces for each organization.">
|
||||
<che-input-box che-name="runtimeCap" che-form="organizationDetailsController.organizationForm"
|
||||
aria-label="runtime cap"
|
||||
che-place-holder="Number of running workspaces has not been limited."
|
||||
ng-model="organizationDetailsController.totalResources.runtimeCap"
|
||||
che-readonly="!organizationDetailsController.canChangeResourceLimits()"
|
||||
type="number"
|
||||
che-type-number
|
||||
min="0"
|
||||
max="1000">
|
||||
<div ng-message="min">A running workspace cap should be greater than 0.</div>
|
||||
</che-input-box>
|
||||
</che-label-container>
|
||||
<!-- Workspace RAM cap -->
|
||||
<che-label-container che-label-name="Workspace RAM Cap" class="organization-ram-cap"
|
||||
che-label-description="Maximum RAM organization workspaces can use.">
|
||||
<che-input-box che-name="workspaceRamCap" che-form="organizationDetailsController.organizationForm"
|
||||
aria-label="runtime cap"
|
||||
che-place-holder="Workspace RAM has not been limited."
|
||||
ng-model="organizationDetailsController.totalResources.ramCap"
|
||||
che-readonly="!organizationDetailsController.canChangeResourceLimits()"
|
||||
type="number"
|
||||
che-type-number
|
||||
min="0"
|
||||
max="1000">
|
||||
<div ng-message="min">A workspace RAM cap should be greater than 0.</div>
|
||||
</che-input-box>
|
||||
</che-label-container>
|
||||
</div>
|
||||
<che-label-container class="organization-details-delete-label"
|
||||
ng-if="organizationDetailsController.isUserAllowedTo(organizationDetailsController.DELETE)"
|
||||
che-label-name="Delete Organization"
|
||||
che-label-description="This is irreversible. Deleting your organization will also destroy organization workspaces and stacks.">
|
||||
<che-button-danger che-button-title="Delete"
|
||||
ng-click="organizationDetailsController.deleteOrganization(t)"></che-button-danger>
|
||||
</che-label-container>
|
||||
</ng-form>
|
||||
</div>
|
||||
</md-tab-body>
|
||||
</md-tab>
|
||||
|
||||
<!-- Members Tab -->
|
||||
<md-tab md-on-select="organizationDetailsController.onSelectTab(organizationDetailsController.tab.Members);">
|
||||
<md-tab-label>
|
||||
<md-icon md-font-icon="fa-group" class="fa che-tab-label-icon"></md-icon>
|
||||
<span class="che-tab-label-title">Members</span>
|
||||
</md-tab-label>
|
||||
<md-tab-body>
|
||||
<list-organization-members
|
||||
ng-if="organizationDetailsController.organization && organizationDetailsController.organization.id"
|
||||
organization="organizationDetailsController.organization"
|
||||
parent-organization-members="organizationDetailsController.parentOrganizationMembers"
|
||||
editable="organizationDetailsController.isUserAllowedTo(organizationDetailsController.SET_PERMISSIONS)"></list-organization-members>
|
||||
</md-tab-body>
|
||||
</md-tab>
|
||||
|
||||
<!-- Sub Organizations Tab -->
|
||||
<md-tab md-on-select="organizationDetailsController.onSelectTab(organizationDetailsController.tab.Organization);">
|
||||
<md-tab-label>
|
||||
<md-icon md-font-icon="md-font fa fa-sitemap material-icons" class="che-tab-label-icon"></md-icon>
|
||||
<span class="che-tab-label-title">Sub-Organizations</span>
|
||||
</md-tab-label>
|
||||
<md-tab-body>
|
||||
<div class="organization-progress-line">
|
||||
<md-progress-linear md-mode="indeterminate"
|
||||
ng-show="organizationDetailsController.isLoading"></md-progress-linear>
|
||||
</div>
|
||||
<list-organizations is-loading="organizationsController.isInfoLoading"
|
||||
on-update="organizationDetailsController.fetchSubOrganizations()"
|
||||
organizations="organizationDetailsController.getSubOrganizations()"></list-organizations>
|
||||
</md-tab-body>
|
||||
</md-tab>
|
||||
|
||||
</md-tabs>
|
||||
</md-content>
|
||||
|
||||
<organization-not-found ng-if="!organizationDetailsController.organization"
|
||||
organization-name="organizationDetailsController.organizationName"></organization-not-found>
|
||||
|
||||
<workspace-edit-mode-overlay ng-if="organizationDetailsController.isSaveButtonVisible()"
|
||||
workspace-edit-disable-save-button="organizationDetailsController.isSaveButtonDisabled()"
|
||||
workspace-edit-mode-on-save="organizationDetailsController.updateOrganization()"
|
||||
workspace-edit-mode-on-cancel="organizationDetailsController.cancelChanges()"></workspace-edit-mode-overlay>
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
.organization-details-content
|
||||
padding 0 14px
|
||||
|
||||
.organization-ram-cap .che-input-box-desktop-value-column:after
|
||||
color $label-secondary-color
|
||||
content "GB"
|
||||
position absolute
|
||||
right 7px
|
||||
top 0
|
||||
line-height 40px
|
||||
|
||||
.organization-details-delete-label label
|
||||
color $che-delete-label-color !important
|
||||
|
||||
.che-label-container-content button
|
||||
margin-left 0
|
||||
margin-top 0
|
||||
|
||||
.save-button-placeholder
|
||||
width 89px
|
||||
|
||||
.organization-progress-line
|
||||
top 0
|
||||
left 0
|
||||
right 0
|
||||
height 5px
|
||||
position absolute
|
||||
z-index 2147483647
|
||||
|
|
@ -0,0 +1,259 @@
|
|||
/*
|
||||
* Copyright (c) 2015-2017 Red Hat, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Red Hat, Inc. - initial API and implementation
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* @ngdoc controller
|
||||
* @name organization.details.invite-members:ListOrganizationInviteMembersController
|
||||
* @description This class is handling the controller for the list of invited organization members.
|
||||
* @author Oleksii Orel
|
||||
*/
|
||||
export class ListOrganizationInviteMembersController {
|
||||
/**
|
||||
* Lodash library.
|
||||
*/
|
||||
private lodash: any;
|
||||
/**
|
||||
* Service for displaying dialogs.
|
||||
*/
|
||||
private $mdDialog: ng.material.IDialogService;
|
||||
/**
|
||||
* No members selected.
|
||||
*/
|
||||
private isNoSelected: boolean;
|
||||
/**
|
||||
* Bulk operation checked state.
|
||||
*/
|
||||
private isBulkChecked: boolean;
|
||||
/**
|
||||
* Status of selected members.
|
||||
*/
|
||||
private membersSelectedStatus: any;
|
||||
/**
|
||||
* Number of selected members.
|
||||
*/
|
||||
private membersSelectedNumber: number;
|
||||
/**
|
||||
* Members order by value.
|
||||
*/
|
||||
private membersOrderBy: string;
|
||||
/**
|
||||
* List of members to be invited.
|
||||
*/
|
||||
private members: Array<che.IMember>;
|
||||
/**
|
||||
* Parent organization ID
|
||||
*/
|
||||
private parentOrganizationId: string;
|
||||
/**
|
||||
* Members list of parent organization.
|
||||
*/
|
||||
private parentOrganizationMembers: string[];
|
||||
/**
|
||||
* ID of user which is owner of the team
|
||||
*/
|
||||
private ownerId: string;
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private organizationRoles: che.resource.ICheOrganizationRoles;
|
||||
|
||||
/**
|
||||
* Default constructor that is using resource
|
||||
* @ngInject for Dependency injection
|
||||
*/
|
||||
constructor($mdDialog: angular.material.IDialogService, lodash: any, cheUser: any, resourcesService: che.service.IResourcesService) {
|
||||
this.$mdDialog = $mdDialog;
|
||||
this.lodash = lodash;
|
||||
this.organizationRoles = resourcesService.getOrganizationRoles();
|
||||
|
||||
this.isNoSelected = true;
|
||||
this.isBulkChecked = false;
|
||||
this.membersSelectedStatus = {};
|
||||
this.membersSelectedNumber = 0;
|
||||
this.membersOrderBy = 'email';
|
||||
|
||||
// add current user to members list
|
||||
const user = cheUser.getUser();
|
||||
const member = user as che.IMember;
|
||||
member.role = this.organizationRoles.ADMIN.name;
|
||||
this.members = [member];
|
||||
this.buildMembersList();
|
||||
|
||||
this.ownerId = user.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Forms the list of members.
|
||||
*/
|
||||
buildMembersList(): void {
|
||||
this.members.forEach((member: che.IMember) => {
|
||||
member.roles = [this.organizationRoles[member.role]];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns developer role value.
|
||||
*
|
||||
* @returns {string} string of the developer role value
|
||||
*/
|
||||
getDeveloperRoleValue(): string {
|
||||
return this.organizationRoles.MEMBER.name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns admin role value.
|
||||
*
|
||||
* @returns {string} string of the admin role value
|
||||
*/
|
||||
getAdminRoleValue(): string {
|
||||
return this.organizationRoles.ADMIN.name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for member role changed in the list.
|
||||
* @param {che.IMember} member
|
||||
*/
|
||||
onChangeMemberRole(member: che.IMember): void {
|
||||
member.roles[0] = this.organizationRoles[member.role];
|
||||
}
|
||||
|
||||
/**
|
||||
* Update members selected status
|
||||
*/
|
||||
updateSelectedStatus(): void {
|
||||
this.membersSelectedNumber = 0;
|
||||
this.isBulkChecked = !!this.members.length;
|
||||
this.members.forEach((member: che.IMember) => {
|
||||
if (this.membersSelectedStatus[member.email]) {
|
||||
this.membersSelectedNumber++;
|
||||
} else {
|
||||
this.isBulkChecked = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Change bulk selection value.
|
||||
*/
|
||||
changeBulkSelection(): void {
|
||||
if (this.isBulkChecked) {
|
||||
this.deselectAllMembers();
|
||||
this.isBulkChecked = false;
|
||||
return;
|
||||
}
|
||||
this.selectAllMembers();
|
||||
this.isBulkChecked = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check all members in list.
|
||||
*/
|
||||
selectAllMembers(): void {
|
||||
this.membersSelectedNumber = this.members.length;
|
||||
this.members.forEach((member: che.IMember) => {
|
||||
if (member.id === this.ownerId) {
|
||||
return;
|
||||
}
|
||||
this.membersSelectedStatus[member.email] = true;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Uncheck all members in list
|
||||
*/
|
||||
deselectAllMembers(): void {
|
||||
this.membersSelectedStatus = {};
|
||||
this.membersSelectedNumber = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds member to the list.
|
||||
*
|
||||
* @param members {Array<che.IMember>}
|
||||
* @param role {string} member role's name in organization
|
||||
*/
|
||||
addMembers(members: Array<che.IMember>, role: string): void {
|
||||
members.forEach((member: any) => {
|
||||
member.role = role;
|
||||
this.members.push(member);
|
||||
});
|
||||
this.buildMembersList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects which dialog should be shown.
|
||||
*
|
||||
* @param $event
|
||||
*/
|
||||
selectAddMemberDialog($event: MouseEvent) {
|
||||
if (this.parentOrganizationId) {
|
||||
this.showMembersListDialog($event);
|
||||
} else {
|
||||
this.showMemberDialog($event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows dialog to add new member to a root organization.
|
||||
*
|
||||
* @param $event
|
||||
*/
|
||||
showMemberDialog($event: MouseEvent): void {
|
||||
this.$mdDialog.show({
|
||||
targetEvent: $event,
|
||||
controller: 'OrganizationMemberDialogController',
|
||||
controllerAs: 'organizationMemberDialogController',
|
||||
bindToController: true,
|
||||
clickOutsideToClose: true,
|
||||
locals: {
|
||||
members: this.members,
|
||||
member: null,
|
||||
parentOrganizationId: this.parentOrganizationId,
|
||||
parentOrganizationMembers: this.parentOrganizationMembers,
|
||||
callbackController: this
|
||||
},
|
||||
templateUrl: 'app/organizations/organization-details/organization-member-dialog/organization-member-dialog.html'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows dialog to select members from list to a sub-organization.
|
||||
*
|
||||
* @param $event
|
||||
*/
|
||||
showMembersListDialog($event: MouseEvent): void {
|
||||
this.$mdDialog.show({
|
||||
targetEvent: $event,
|
||||
bindToController: true,
|
||||
clickOutsideToClose: true,
|
||||
controller: 'OrganizationSelectMembersDialogController',
|
||||
controllerAs: 'organizationSelectMembersDialogController',
|
||||
locals: {
|
||||
callbackController: this,
|
||||
parentOrganizationMembers: this.parentOrganizationMembers,
|
||||
members: this.members
|
||||
},
|
||||
templateUrl: 'app/organizations/organization-details/organization-select-members-dialog/organization-select-members-dialog.html'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes selected members.
|
||||
*/
|
||||
removeSelectedMembers(): void {
|
||||
this.lodash.remove(this.members, (member: che.IMember) => {
|
||||
return this.membersSelectedStatus[member.email];
|
||||
});
|
||||
this.deselectAllMembers();
|
||||
this.isBulkChecked = false;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* Copyright (c) 2015-2017 Red Hat, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Red Hat, Inc. - initial API and implementation
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* @ngdoc directive
|
||||
* @name organization.details.invite-members:ListOrganizationInviteMembers
|
||||
* @restrict E
|
||||
* @element
|
||||
*
|
||||
* @description
|
||||
* `<list-organization-members members="ctrl.members"></list-organization-members>` for displaying list of members
|
||||
*
|
||||
* @usage
|
||||
* <list-organization-members members="ctrl.members"></list-organization-members>
|
||||
*
|
||||
* @author Oleksii Orel
|
||||
*/
|
||||
export class ListOrganizationInviteMembers implements ng.IDirective {
|
||||
|
||||
restrict: string = 'E';
|
||||
templateUrl: string = 'app/organizations/organization-details/organization-invite-members/list-organization-invite-members.html';
|
||||
|
||||
controller: string = 'ListOrganizationInviteMembersController';
|
||||
controllerAs: string = 'listOrganizationInviteMembersController';
|
||||
bindToController: boolean = true;
|
||||
|
||||
scope: any = {
|
||||
members: '=',
|
||||
parentOrganizationId: '=',
|
||||
parentOrganizationMembers: '='
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
<div class="list-organization-invite-members" layout="column">
|
||||
<div ng-if="listOrganizationInviteMembersController.members.length > 0"
|
||||
class="list-organization-invite-members-spacing">
|
||||
<che-list-header>
|
||||
<div flex="100"
|
||||
layout="row"
|
||||
layout-align="start stretch"
|
||||
class="che-list-item-row">
|
||||
<div layout="row"
|
||||
layout-align="start center"
|
||||
class="che-checkbox-area">
|
||||
<div layout="row" layout-align="start center" class="che-list-item-checkbox-main">
|
||||
<md-checkbox class="che-list-item-checkbox"
|
||||
aria-label="All members"
|
||||
md-theme="default"
|
||||
ng-checked="listOrganizationInviteMembersController.isBulkChecked"
|
||||
ng-click="listOrganizationInviteMembersController.changeBulkSelection()"></md-checkbox>
|
||||
</div>
|
||||
</div>
|
||||
<div flex hide-xs layout-gt-xs="row"
|
||||
layout-align="start center"
|
||||
class="che-list-item-details">
|
||||
<che-list-header-column flex="30"
|
||||
che-sort-value="listOrganizationInviteMembersController.membersOrderBy"
|
||||
che-sort-item="name"
|
||||
che-column-title='Invited Members'></che-list-header-column>
|
||||
<che-list-header-column flex="35"
|
||||
che-column-title='Organization Member'></che-list-header-column>
|
||||
<che-list-header-column flex="35"
|
||||
che-column-title='Organization Admin'></che-list-header-column>
|
||||
</div>
|
||||
</div>
|
||||
</che-list-header>
|
||||
<che-list flex>
|
||||
<che-list-item ng-mouseover="hover=true" ng-mouseout="hover=false"
|
||||
ng-repeat="member in listOrganizationInviteMembersController.members | orderBy:listOrganizationInviteMembersController.membersOrderBy">
|
||||
<div flex="100"
|
||||
layout="row"
|
||||
layout-align="start stretch"
|
||||
class="member-item-row">
|
||||
<div layout="row"
|
||||
layout-align="start center"
|
||||
class="che-checkbox-area">
|
||||
<che-list-item-checked
|
||||
ng-if="member.id !== listOrganizationInviteMembersController.ownerId"
|
||||
ng-model="listOrganizationInviteMembersController.membersSelectedStatus[member.email]"
|
||||
che-aria-label-checkbox="Member {{member.email}}"
|
||||
ng-click="listOrganizationInviteMembersController.updateSelectedStatus()"></che-list-item-checked>
|
||||
</div>
|
||||
<div flex
|
||||
layout-xs="column" layout-gt-xs="row"
|
||||
layout-align-gt-xs="start center"
|
||||
layout-align-xs="start start"
|
||||
class="che-list-item-details">
|
||||
<div flex="30"
|
||||
class="che-list-item-name">
|
||||
<span class="material-design icon-ic_done_24px user-exists-checked" ng-if="member.id"></span>
|
||||
<span class="che-hover">{{member.email}}</span>
|
||||
</div>
|
||||
<div flex="35">
|
||||
<md-radio-group ng-model="member.role"
|
||||
ng-change="listOrganizationInviteMembersController.onChangeMemberRole(member)">
|
||||
<md-radio-button ng-disabled="member.id === listOrganizationInviteMembersController.ownerId"
|
||||
value="{{listOrganizationInviteMembersController.getDeveloperRoleValue()}}"></md-radio-button>
|
||||
</md-radio-group>
|
||||
</div>
|
||||
<div flex="35">
|
||||
<md-radio-group ng-model="member.role"
|
||||
ng-change="listOrganizationInviteMembersController.onChangeMemberRole(member)">
|
||||
<md-radio-button ng-disabled="member.id === listOrganizationInviteMembersController.ownerId"
|
||||
value="{{listOrganizationInviteMembersController.getAdminRoleValue()}}"></md-radio-button>
|
||||
</md-radio-group>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</che-list-item>
|
||||
</che-list>
|
||||
</div>
|
||||
<!-- buttons -->
|
||||
<div layout="row">
|
||||
<div flex layout-align="center start">
|
||||
<che-button-default class="che-list-add-button"
|
||||
che-button-title="Add" name="addButton"
|
||||
ng-click="listOrganizationInviteMembersController.selectAddMemberDialog($event)"></che-button-default>
|
||||
</div>
|
||||
<div flex-offset="5" ng-if="listOrganizationInviteMembersController.members.length > 0">
|
||||
<che-button-primary-flat ng-disabled="(listOrganizationInviteMembersController.membersSelectedNumber === 0)"
|
||||
che-button-title="Remove" name="removeButton"
|
||||
ng-click="listOrganizationInviteMembersController.removeSelectedMembers()"></che-button-primary-flat>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
.list-organization-invite-members
|
||||
*
|
||||
outline none !important
|
||||
|
||||
md-radio-button
|
||||
margin-bottom 0 !important
|
||||
|
||||
.che-list
|
||||
margin-bottom 12px
|
||||
background-color inherit
|
||||
|
||||
.che-list-item-checkbox-main
|
||||
padding-left 0
|
||||
|
||||
.md-button
|
||||
margin 0
|
||||
filter none !important
|
||||
|
||||
.member-item-row
|
||||
min-height 33px
|
||||
|
||||
.che-list-header-content > *, .che-list
|
||||
margin 0
|
||||
|
||||
.che-list-header md-item
|
||||
border-top none
|
||||
|
||||
.list-organization-invite-members .permission-switcher
|
||||
overflow visible
|
||||
margin 0
|
||||
|
||||
.list-organization-invite-members-spacing
|
||||
margin-bottom 20px
|
||||
|
|
@ -0,0 +1,284 @@
|
|||
/*
|
||||
* Copyright (c) 2015-2017 Red Hat, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Red Hat, Inc. - initial API and implementation
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* @ngdoc controller
|
||||
* @name organization.details.member:MemberDialogController
|
||||
* @description This class is handling the controller for adding/editing organization member dialog.
|
||||
* @author Oleksii Orel
|
||||
*/
|
||||
export class OrganizationMemberDialogController {
|
||||
/**
|
||||
* User API interaction.
|
||||
*/
|
||||
private cheUser: any;
|
||||
/**
|
||||
* Organization API interaction.
|
||||
*/
|
||||
private cheOrganization: che.api.ICheOrganization;
|
||||
/**
|
||||
* Service for displaying dialogs.
|
||||
*/
|
||||
private $mdDialog: angular.material.IDialogService;
|
||||
/**
|
||||
* Promises service.
|
||||
*/
|
||||
private $q: ng.IQService;
|
||||
/**
|
||||
* Lodash library.
|
||||
*/
|
||||
private lodash: any;
|
||||
/**
|
||||
* Processing state of adding member.
|
||||
*/
|
||||
private isProcessing: boolean;
|
||||
/**
|
||||
* Set of user roles info.
|
||||
*/
|
||||
private roles: Array<any>;
|
||||
/**
|
||||
* Already added emails.
|
||||
*/
|
||||
private emails: Array<string>;
|
||||
/**
|
||||
* Existing members.
|
||||
*/
|
||||
private members: Array<any>;
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private parentOrganizationId: string;
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private parentOrganizationMembers: string;
|
||||
/**
|
||||
* Entered email address.
|
||||
*/
|
||||
private email: string;
|
||||
/**
|
||||
* Controller that will handle callbacks.
|
||||
*/
|
||||
private callbackController: any;
|
||||
/**
|
||||
* Member to be displayed, may be <code>null</code> if add new member is needed. (Comes from outside)
|
||||
*/
|
||||
private member: che.IMember;
|
||||
/**
|
||||
* Role to be used, may be <code>null</code> if role is needed to be set. (Comes from outside)
|
||||
*/
|
||||
private role: string;
|
||||
/**
|
||||
* Choosen role for user.
|
||||
*/
|
||||
private newRole: string;
|
||||
/**
|
||||
* Dialog window title.
|
||||
*/
|
||||
private title: string;
|
||||
/**
|
||||
* Title of operation button (Save or Add)
|
||||
*/
|
||||
private buttonTitle: string;
|
||||
/**
|
||||
* Email validation error message.
|
||||
*/
|
||||
private emailError: string;
|
||||
/**
|
||||
* todo
|
||||
*/
|
||||
private organizationRoles: che.resource.ICheOrganizationRoles;
|
||||
|
||||
/**
|
||||
* Default constructor that is using resource
|
||||
* @ngInject for Dependency injection
|
||||
*/
|
||||
constructor($q: ng.IQService, $mdDialog: angular.material.IDialogService, cheUser: any, cheOrganization: che.api.ICheOrganization, lodash: any, resourcesService: che.service.IResourcesService) {
|
||||
this.$mdDialog = $mdDialog;
|
||||
this.cheUser = cheUser;
|
||||
this.cheOrganization = cheOrganization;
|
||||
this.$q = $q;
|
||||
this.lodash = lodash;
|
||||
this.organizationRoles = resourcesService.getOrganizationRoles();
|
||||
|
||||
this.isProcessing = false;
|
||||
|
||||
this.emails = [];
|
||||
this.members.forEach((member: che.IMember) => {
|
||||
this.emails.push(member.email);
|
||||
});
|
||||
|
||||
// role is set, need to add only user with this role:
|
||||
if (this.role) {
|
||||
this.email = '';
|
||||
this.title = 'Add new ' + this.organizationRoles[this.role].title.toLowerCase();
|
||||
this.buttonTitle = 'Add';
|
||||
return;
|
||||
}
|
||||
|
||||
this.roles = this.organizationRoles.getRoles();
|
||||
if (this.member) {
|
||||
this.title = 'Edit ' + this.member.name + ' roles';
|
||||
this.buttonTitle = 'Save';
|
||||
this.email = this.member.email;
|
||||
let roles = cheOrganization.getRolesFromActions(this.member.permissions.actions);
|
||||
this.newRole = (roles && roles.length > 0) ? roles[0].name : this.organizationRoles.MEMBER.name;
|
||||
} else {
|
||||
this.email = '';
|
||||
this.title = 'Invite member to collaborate';
|
||||
this.buttonTitle = 'Add';
|
||||
this.newRole = this.organizationRoles.MEMBER.name;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns title of specified role.
|
||||
*
|
||||
* @param {string} roleName
|
||||
* @returns {string}
|
||||
*/
|
||||
getRoleTitle(roleName: string): string {
|
||||
return this.organizationRoles[roleName].title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns description of specified role.
|
||||
*
|
||||
* @param {string} roleName
|
||||
* @returns {string}
|
||||
*/
|
||||
getRoleDescription(roleName: string): string {
|
||||
return this.organizationRoles[roleName].description;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides the add member dialog.
|
||||
*/
|
||||
hide(): void {
|
||||
this.$mdDialog.hide();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether entered email valid and is unique.
|
||||
*
|
||||
* @param value value with email(s) to check
|
||||
* @returns {boolean} true if pointed email(s) are valid and not in the list yet
|
||||
*/
|
||||
isValidEmail(value: string): boolean {
|
||||
let emails = value.replace(/\s*,?\s+/g, ',').split(',');
|
||||
for (let i = 0; i < emails.length; i++) {
|
||||
// email is valid
|
||||
let email = emails[i];
|
||||
let emailRe = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i;
|
||||
if (!emailRe.test(email)) {
|
||||
this.emailError = `"${email}" is invalid email address.`;
|
||||
return false;
|
||||
}
|
||||
|
||||
// user has not been invited yet
|
||||
if (this.emails.indexOf(email) >= 0) {
|
||||
this.emailError = `User with email ${email} is already invited.`;
|
||||
return false;
|
||||
}
|
||||
|
||||
// user is a member of parent organization
|
||||
if (this.parentOrganizationId && this.parentOrganizationMembers.indexOf(email) === -1) {
|
||||
this.emailError = 'User with this email is not a member of parent organization.';
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds new member.
|
||||
*/
|
||||
addMembers(): void {
|
||||
let userRoleName = this.role ? this.role : this.newRole;
|
||||
let emails = this.email.replace(/\s*,?\s+/g, ',').split(',');
|
||||
// form the list of emails without duplicates and empty values:
|
||||
let resultEmails = emails.reduce((array: Array<string>, element: string) => {
|
||||
if (array.indexOf(element) < 0 && element.length > 0) {
|
||||
array.push(element);
|
||||
}
|
||||
return array;
|
||||
}, []);
|
||||
|
||||
let promises = [];
|
||||
let users = [];
|
||||
resultEmails.forEach((email: string) => {
|
||||
promises.push(this.processUser(email, users));
|
||||
});
|
||||
|
||||
this.$q.all(promises).then(() => {
|
||||
this.finishAdding(users, userRoleName);
|
||||
});
|
||||
}
|
||||
|
||||
processUser(email: string, users: Array<any>): ng.IPromise<any> {
|
||||
let deferred = this.$q.defer();
|
||||
let user = this.cheUser.getUserByAlias(email);
|
||||
if (user) {
|
||||
users.push(user);
|
||||
deferred.resolve();
|
||||
} else {
|
||||
this.isProcessing = true;
|
||||
this.cheUser.fetchUserByAlias(email).then(() => {
|
||||
users.push(this.cheUser.getUserByAlias(email));
|
||||
deferred.resolve();
|
||||
}, (error: any) => {
|
||||
users.push({email: email});
|
||||
deferred.resolve();
|
||||
});
|
||||
}
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle edit member user's action.
|
||||
*/
|
||||
editMember(): void {
|
||||
this.member.permissions.actions = this.getCurrentActions();
|
||||
this.callbackController.updateMember(this.member);
|
||||
this.hide();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the actions of current chosen roles.
|
||||
*/
|
||||
getCurrentActions(): Array<string> {
|
||||
let userRoleName = this.role ? this.role : this.newRole;
|
||||
let processedActions = [];
|
||||
this.roles.forEach((roleName: string) => {
|
||||
const role = this.organizationRoles[roleName];
|
||||
processedActions = processedActions.concat(role.actions);
|
||||
});
|
||||
|
||||
let actions = this.member ? this.member.permissions.actions : [];
|
||||
let otherActions = this.lodash.difference(actions, processedActions);
|
||||
|
||||
return this.lodash.uniq(this.organizationRoles[userRoleName].actions.concat(otherActions));
|
||||
}
|
||||
|
||||
/**
|
||||
* Finish adding user state.
|
||||
*
|
||||
* @param {Array<any>} users users to be added
|
||||
* @param {sring} role user's role
|
||||
*/
|
||||
finishAdding(users: Array<any>, role: string): void {
|
||||
this.isProcessing = false;
|
||||
this.callbackController.addMembers(users, role);
|
||||
this.hide();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
<che-popup title="{{organizationMemberDialogController.title}}" on-close="organizationMemberDialogController.hide()">
|
||||
<div class="organization-member-dialog-content" md-theme="default">
|
||||
<ng-form flex layout="column" name="memberForm">
|
||||
<che-label-container che-label-name="Email" ng-show="!organizationMemberDialogController.member"
|
||||
che-label-description="User email address.">
|
||||
<che-input-box che-form="memberForm"
|
||||
che-name="email"
|
||||
che-place-holder="Enter email"
|
||||
ng-model="organizationMemberDialogController.email"
|
||||
ng-model-options="{allowInvalid: true}"
|
||||
ng-disabled="organizationMemberDialogController.isProcessing"
|
||||
custom-validator="organizationMemberDialogController.isValidEmail($value)"
|
||||
type="text"
|
||||
aria-label="New member"
|
||||
ng-keypress="memberForm.$valid && $event.which === 13 && organizationMemberDialogController.addMembers()"
|
||||
required
|
||||
focusable>
|
||||
<div ng-message="customValidator" ng-if="memberForm.$dirty">{{organizationMemberDialogController.emailError}}</div>
|
||||
</che-input-box>
|
||||
</che-label-container>
|
||||
<che-label-container che-label-name="Role" che-label-description="Allowed actions of member."
|
||||
ng-if="!organizationMemberDialogController.role">
|
||||
<div layout="column">
|
||||
<md-radio-group ng-model="organizationMemberDialogController.newRole">
|
||||
<div ng-repeat="roleName in organizationMemberDialogController.roles" layout="row">
|
||||
<md-radio-button value="{{roleName}}">{{organizationMemberDialogController.getRoleTitle(roleName)}}</md-radio-button>
|
||||
<span class="member-role-description">({{organizationMemberDialogController.getRoleDescription(roleName)}})</span>
|
||||
</div>
|
||||
</md-radio-group>
|
||||
</div>
|
||||
</che-label-container>
|
||||
</ng-form>
|
||||
|
||||
<div layout="row" layout-align="end center">
|
||||
<che-button-primary che-button-title="{{organizationMemberDialogController.buttonTitle}}"
|
||||
ng-if="!organizationMemberDialogController.member"
|
||||
ng-disabled="memberForm.$invalid || organizationMemberDialogController.isProcessing"
|
||||
ng-click="organizationMemberDialogController.addMembers()"></che-button-primary>
|
||||
<che-button-primary che-button-title="{{organizationMemberDialogController.buttonTitle}}"
|
||||
ng-disabled="organizationMemberDialogController.isProcessing"
|
||||
ng-if="organizationMemberDialogController.member"
|
||||
ng-click="organizationMemberDialogController.editMember()"></che-button-primary>
|
||||
<che-button-cancel-flat che-button-title="Cancel"
|
||||
ng-click="organizationMemberDialogController.hide()"
|
||||
tabindex="0"></che-button-cancel-flat>
|
||||
</div>
|
||||
</div>
|
||||
</che-popup>
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
.organization-member-dialog-content
|
||||
width 630px
|
||||
|
||||
button
|
||||
margin 0 0 0 20px
|
||||
|
||||
input.ng-invalid.ng-pristine:focus
|
||||
border-color $primary-color
|
||||
|
||||
.che-label-container div.che-label-container-label
|
||||
width 150px
|
||||
min-width 150px
|
||||
|
||||
.member-role-title
|
||||
margin-bottom 10px
|
||||
|
||||
.member-role-description
|
||||
color $disabled-color
|
||||
font-size 10px
|
||||
padding 0 5px
|
||||
line-height 18pt
|
||||
|
||||
.member-role-warning-label
|
||||
color $warning-color
|
||||
height 20px
|
||||
|
||||
.member-role-button
|
||||
margin-right 10px
|
||||
width 150px
|
||||
|
||||
.member-role-button button
|
||||
width 150px
|
||||
|
||||
|
|
@ -0,0 +1,489 @@
|
|||
/*
|
||||
* Copyright (c) 2015-2017 Red Hat, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Red Hat, Inc. - initial API and implementation
|
||||
*/
|
||||
'use strict';
|
||||
import {OrganizationsPermissionService} from '../../organizations-permission.service';
|
||||
|
||||
/**
|
||||
* @ngdoc controller
|
||||
* @name organization.details.members:ListOrganizationMembersController
|
||||
* @description This class is handling the controller for the list of organization's members.
|
||||
* @author Oleksii Orel
|
||||
*/
|
||||
export class ListOrganizationMembersController {
|
||||
/**
|
||||
* Location service.
|
||||
*/
|
||||
private $location: ng.ILocationService;
|
||||
/**
|
||||
* User API interaction.
|
||||
*/
|
||||
private cheUser: any;
|
||||
/**
|
||||
* Organization API interaction.
|
||||
*/
|
||||
private cheOrganization: che.api.ICheOrganization;
|
||||
/**
|
||||
* User profile API interaction.
|
||||
*/
|
||||
private cheProfile: any;
|
||||
/**
|
||||
* Permissions API interaction.
|
||||
*/
|
||||
private chePermissions: che.api.IChePermissions;
|
||||
/**
|
||||
* Service for displaying dialogs.
|
||||
*/
|
||||
private $mdDialog: angular.material.IDialogService;
|
||||
/**
|
||||
* Notifications service.
|
||||
*/
|
||||
private cheNotification: any;
|
||||
/**
|
||||
* Confirm dialog service.
|
||||
*/
|
||||
private confirmDialogService: any;
|
||||
/**
|
||||
* Promises service.
|
||||
*/
|
||||
private $q: ng.IQService;
|
||||
/**
|
||||
* Lodash library.
|
||||
*/
|
||||
private lodash: any;
|
||||
/**
|
||||
* Organization's members list.
|
||||
*/
|
||||
private members: Array<che.IMember>;
|
||||
/**
|
||||
* Members list of parent organization (comes from directive's scope)
|
||||
*/
|
||||
private parentOrganizationMembers: Array<che.IUser>;
|
||||
/**
|
||||
* Loading state of the page.
|
||||
*/
|
||||
private isLoading: boolean;
|
||||
/**
|
||||
* Filter for members list.
|
||||
*/
|
||||
private memberFilter: any;
|
||||
/**
|
||||
* Current organization (comes from directive's scope).
|
||||
*/
|
||||
private organization: che.IOrganization;
|
||||
/**
|
||||
* Organization permission service.
|
||||
*/
|
||||
private organizationsPermissionService: OrganizationsPermissionService;
|
||||
/**
|
||||
* Has update permission.
|
||||
*/
|
||||
private hasUpdatePermission;
|
||||
/**
|
||||
* Selection and filtration helper
|
||||
*/
|
||||
private cheListHelper: che.widget.ICheListHelper;
|
||||
/**
|
||||
* todo
|
||||
*/
|
||||
private organizationActions: che.resource.ICheOrganizationActions;
|
||||
/**
|
||||
* todo
|
||||
*/
|
||||
private organizationRoles: che.resource.ICheOrganizationRoles;
|
||||
|
||||
/**
|
||||
* Default constructor that is using resource
|
||||
* @ngInject for Dependency injection
|
||||
*/
|
||||
constructor(chePermissions: che.api.IChePermissions, cheUser: any, cheProfile: any, cheOrganization: che.api.ICheOrganization,
|
||||
confirmDialogService: any, $mdDialog: angular.material.IDialogService, $q: ng.IQService, cheNotification: any,
|
||||
lodash: any, $location: ng.ILocationService, organizationsPermissionService: OrganizationsPermissionService,
|
||||
$scope: ng.IScope, cheListHelperFactory: che.widget.ICheListHelperFactory, resourcesService: che.service.IResourcesService) {
|
||||
this.chePermissions = chePermissions;
|
||||
this.cheProfile = cheProfile;
|
||||
this.cheUser = cheUser;
|
||||
this.cheOrganization = cheOrganization;
|
||||
this.$mdDialog = $mdDialog;
|
||||
this.$q = $q;
|
||||
this.$location = $location;
|
||||
this.lodash = lodash;
|
||||
this.cheNotification = cheNotification;
|
||||
this.confirmDialogService = confirmDialogService;
|
||||
this.organizationsPermissionService = organizationsPermissionService;
|
||||
this.organizationActions = resourcesService.getOrganizationActions();
|
||||
this.organizationRoles = resourcesService.getOrganizationRoles();
|
||||
|
||||
this.members = [];
|
||||
|
||||
this.memberFilter = {name: ''};
|
||||
const helperId = 'list-organization-members';
|
||||
this.cheListHelper = cheListHelperFactory.getHelper(helperId);
|
||||
$scope.$on('$destroy', () => {
|
||||
cheListHelperFactory.removeHelper(helperId);
|
||||
});
|
||||
|
||||
this.formUsersList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback when name is changed.
|
||||
*
|
||||
* @param str {string} a string to filter organization members.
|
||||
*/
|
||||
onSearchChanged(str: string): void {
|
||||
this.memberFilter.name = str;
|
||||
this.cheListHelper.applyFilter('name', this.memberFilter);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Fetches the list of organization members.
|
||||
*/
|
||||
fetchMembers(): void {
|
||||
if (!this.organization || !this.organization.id) {
|
||||
return;
|
||||
}
|
||||
this.isLoading = true;
|
||||
this.chePermissions.fetchOrganizationPermissions(this.organization.id).then(() => {
|
||||
this.formUsersList();
|
||||
}, (error: any) => {
|
||||
let errorMessage = error && error.data && error.data.message ? error.data.message : 'Failed to retrieve organization permissions.';
|
||||
this.cheNotification.showError(errorMessage);
|
||||
}).finally(() => {
|
||||
this.isLoading = false;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Combines permissions and users data in one list.
|
||||
*/
|
||||
formUsersList(): void {
|
||||
const permissions = this.chePermissions.getOrganizationPermissions(this.organization.id);
|
||||
this.members = [];
|
||||
|
||||
const promises: Array<ng.IPromise<any>> = [];
|
||||
|
||||
permissions.forEach((permission: any) => {
|
||||
let userId = permission.userId;
|
||||
let userProfile = this.cheProfile.getProfileById(userId);
|
||||
|
||||
if (userProfile) {
|
||||
this.formUserItem(userProfile, permission);
|
||||
} else {
|
||||
const promise = this.cheProfile.fetchProfileById(userId).then(() => {
|
||||
this.formUserItem(this.cheProfile.getProfileById(userId), permission);
|
||||
});
|
||||
promises.push(promise);
|
||||
}
|
||||
});
|
||||
|
||||
this.$q.all(promises).finally(() => {
|
||||
this.cheListHelper.setList(this.members, 'id');
|
||||
});
|
||||
|
||||
this.hasUpdatePermission = this.organizationsPermissionService.isUserAllowedTo(this.organizationActions.UPDATE.toString(), this.organization.id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Forms item to display with permissions and user data.
|
||||
*
|
||||
* @param userProfile {che.IProfile} user's profile
|
||||
* @param permissions {che.IPermissions} data
|
||||
*/
|
||||
formUserItem(userProfile: che.IProfile, permissions: che.IPermissions): void {
|
||||
const member = <che.IMember>angular.copy(userProfile);
|
||||
member.id = userProfile.userId;
|
||||
member.name = this.cheProfile.getFullName(userProfile.attributes);
|
||||
member.permissions = permissions;
|
||||
this.members.push(member);
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects which dialog should be shown.
|
||||
*/
|
||||
selectAddMemberDialog() {
|
||||
if (this.organization.parent) {
|
||||
this.showMembersListDialog();
|
||||
} else {
|
||||
this.showMemberDialog(null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows dialog for adding new member to the organization.
|
||||
*/
|
||||
showMemberDialog(member: che.IMember): void {
|
||||
this.$mdDialog.show({
|
||||
controller: 'OrganizationMemberDialogController',
|
||||
controllerAs: 'organizationMemberDialogController',
|
||||
bindToController: true,
|
||||
clickOutsideToClose: true,
|
||||
locals: {
|
||||
members: this.members,
|
||||
callbackController: this,
|
||||
member: angular.copy(member),
|
||||
parentOrganizationId: this.organization.parent,
|
||||
parentOrganizationMembers: this.parentOrganizationMembers
|
||||
},
|
||||
templateUrl: 'app/organizations/organization-details/organization-member-dialog/organization-member-dialog.html'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows dialog to select members from list to a sub-organization.
|
||||
*
|
||||
*/
|
||||
showMembersListDialog(): void {
|
||||
this.$mdDialog.show({
|
||||
bindToController: true,
|
||||
clickOutsideToClose: true,
|
||||
controller: 'OrganizationSelectMembersDialogController',
|
||||
controllerAs: 'organizationSelectMembersDialogController',
|
||||
locals: {
|
||||
callbackController: this,
|
||||
parentOrganizationMembers: this.parentOrganizationMembers,
|
||||
members: this.members
|
||||
},
|
||||
templateUrl: 'app/organizations/organization-details/organization-select-members-dialog/organization-select-members-dialog.html'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add new members to the organization.
|
||||
*
|
||||
* @param {Array<che.IMember>} members members to be added
|
||||
* @param {string} role member role
|
||||
*/
|
||||
addMembers(members: Array<che.IMember>, role: string): void {
|
||||
let promises = [];
|
||||
let unregistered = [];
|
||||
|
||||
members.forEach((member: che.IMember) => {
|
||||
if (member.id) {
|
||||
let actions = this.organizationRoles[role].actions;
|
||||
let permissions = {
|
||||
instanceId: this.organization.id,
|
||||
userId: member.id,
|
||||
domainId: 'organization',
|
||||
actions: actions
|
||||
};
|
||||
let promise = this.chePermissions.storePermissions(permissions);
|
||||
promises.push(promise);
|
||||
} else {
|
||||
unregistered.push(member.email);
|
||||
}
|
||||
});
|
||||
|
||||
this.isLoading = true;
|
||||
this.$q.all(promises).then(() => {
|
||||
this.fetchMembers();
|
||||
}).finally(() => {
|
||||
this.isLoading = false;
|
||||
if (unregistered.length > 0) {
|
||||
this.cheNotification.showError('User' + (unregistered.length > 1 ? 's ' : ' ') + unregistered.join(', ') + (unregistered.length > 1 ? ' are' : ' is') + ' not registered in the system.');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform edit member permissions.
|
||||
*
|
||||
* @param member
|
||||
*/
|
||||
editMember(member: che.IMember): void {
|
||||
this.showMemberDialog(member);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs member's permissions update.
|
||||
*
|
||||
* @param member member to update permissions
|
||||
*/
|
||||
updateMember(member: che.IMember): void {
|
||||
if (member.permissions.actions.length > 0) {
|
||||
this.storePermissions(member.permissions);
|
||||
} else {
|
||||
this.removePermissions(member);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores provided permissions.
|
||||
*
|
||||
* @param permissions {che.IPermissions}
|
||||
*/
|
||||
storePermissions(permissions: che.IPermissions): void {
|
||||
this.isLoading = true;
|
||||
this.chePermissions.storePermissions(permissions).then(() => {
|
||||
this.fetchMembers();
|
||||
}, (error: any) => {
|
||||
this.isLoading = false;
|
||||
this.cheNotification.showError(error.data && error.data.message ? error.data.message : 'Set user permissions failed.');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all selected members.
|
||||
*/
|
||||
removeSelectedMembers(): void {
|
||||
const selectedMembers = this.cheListHelper.getSelectedItems(),
|
||||
selectedMemberIds = selectedMembers.map((member: che.IMember) => {
|
||||
return member.id;
|
||||
});
|
||||
|
||||
if (!selectedMemberIds.length) {
|
||||
this.cheNotification.showError('No such developers.');
|
||||
return;
|
||||
}
|
||||
|
||||
const confirmationPromise = this.showDeleteMembersConfirmation(selectedMemberIds.length);
|
||||
confirmationPromise.then(() => {
|
||||
const removeMembersPromises = [];
|
||||
let removalError;
|
||||
let isCurrentUser = false;
|
||||
for (let i = 0; i < selectedMemberIds.length; i++) {
|
||||
const id = selectedMemberIds[i];
|
||||
this.cheListHelper.itemsSelectionStatus[id] = false;
|
||||
if (id === this.cheUser.getUser().id) {
|
||||
isCurrentUser = true;
|
||||
}
|
||||
const promise = this.chePermissions.removeOrganizationPermissions(this.organization.id, id);
|
||||
promise.catch((error: any) => {
|
||||
removalError = error;
|
||||
});
|
||||
removeMembersPromises.push(promise);
|
||||
}
|
||||
|
||||
this.$q.all(removeMembersPromises).finally(() => {
|
||||
if (isCurrentUser) {
|
||||
this.processCurrentUserRemoval();
|
||||
} else {
|
||||
this.fetchMembers();
|
||||
}
|
||||
|
||||
if (removalError) {
|
||||
this.cheNotification.showError(removalError.data && removalError.data.message ? removalError.data.message : 'User removal failed.');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Call user permissions removal. Show the dialog
|
||||
* @param member
|
||||
*/
|
||||
removeMember(member: che.IMember): void {
|
||||
let promise = this.confirmDialogService.showConfirmDialog('Remove member', 'Would you like to remove member ' + member.email + ' ?', 'Delete');
|
||||
|
||||
promise.then(() => {
|
||||
this.removePermissions(member);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the member is owner for current organization.
|
||||
* @param member
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isOwner(member: che.IMember): boolean {
|
||||
if (!this.organization || !member) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.organization.qualifiedName.split('/')[0] === member.name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns string with member roles.
|
||||
* @param member
|
||||
*
|
||||
* @returns {string} string format of roles array
|
||||
*/
|
||||
getMemberRoles(member: che.IMember): string {
|
||||
if (!member) {
|
||||
return '';
|
||||
}
|
||||
if (this.isOwner(member)) {
|
||||
return 'Organization Owner';
|
||||
}
|
||||
let roles = this.cheOrganization.getRolesFromActions(member.permissions.actions);
|
||||
let titles = [];
|
||||
let processedActions = [];
|
||||
roles.forEach((role: any) => {
|
||||
titles.push(role.title);
|
||||
processedActions = processedActions.concat(role.actions);
|
||||
});
|
||||
|
||||
return titles.join(', ');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns string with member other actions.
|
||||
* @param member
|
||||
*
|
||||
* @returns {string} string format of roles array
|
||||
*/
|
||||
getOtherActions(member: che.IMember): string {
|
||||
if (!member) {
|
||||
return '';
|
||||
}
|
||||
let roles = this.cheOrganization.getRolesFromActions(member.permissions.actions);
|
||||
let processedActions = [];
|
||||
roles.forEach((role: any) => {
|
||||
processedActions = processedActions.concat(role.actions);
|
||||
});
|
||||
|
||||
return this.lodash.difference(member.permissions.actions, processedActions).join(', ');
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the removal of current user from organization.
|
||||
*/
|
||||
processCurrentUserRemoval(): void {
|
||||
this.$location.path('/organizations');
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes user permissions for current organization
|
||||
*
|
||||
* @param member {che.IMember}
|
||||
*/
|
||||
removePermissions(member: che.IMember): void {
|
||||
this.isLoading = true;
|
||||
this.chePermissions.removeOrganizationPermissions(member.permissions.instanceId, member.id).then(() => {
|
||||
if (member.id === this.cheUser.getUser().id) {
|
||||
this.processCurrentUserRemoval();
|
||||
} else {
|
||||
this.fetchMembers();
|
||||
}
|
||||
}, (error: any) => {
|
||||
this.isLoading = false;
|
||||
this.cheNotification.showError(error.data && error.data.message ? error.data.message : 'Failed to remove user ' + member.email + ' permissions.');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Show confirmation popup before members removal
|
||||
* @param numberToDelete {number}
|
||||
* @returns {ng.IPromise<any>}
|
||||
*/
|
||||
showDeleteMembersConfirmation(numberToDelete: number): ng.IPromise<any> {
|
||||
let confirmTitle = 'Would you like to remove ';
|
||||
if (numberToDelete > 1) {
|
||||
confirmTitle += 'these ' + numberToDelete + ' members?';
|
||||
} else {
|
||||
confirmTitle += 'the selected member?';
|
||||
}
|
||||
|
||||
return this.confirmDialogService.showConfirmDialog('Remove members', confirmTitle, 'Delete');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Copyright (c) 2015-2017 Red Hat, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Red Hat, Inc. - initial API and implementation
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* @ngdoc directive
|
||||
* @name organization.details.members:ListOrganizationMembers
|
||||
* @restrict E
|
||||
* @element
|
||||
*
|
||||
* @description
|
||||
* `<list-organization-members editable="ctrl.editable" organization="ctrl.organization"></list-organization-members>` for displaying list of members
|
||||
*
|
||||
* @usage
|
||||
* <list-organization-members editable="ctrl.editable" organization="ctrl.organization"></list-organization-members>
|
||||
*
|
||||
* @author Oleksii Orel
|
||||
*/
|
||||
export class ListOrganizationMembers implements ng.IDirective {
|
||||
|
||||
restrict: string = 'E';
|
||||
templateUrl: string = 'app/organizations/organization-details/organization-members/list-organization-members.html';
|
||||
controller: string = 'ListOrganizationMembersController';
|
||||
controllerAs: string = 'listOrganizationMembersController';
|
||||
bindToController: boolean = true;
|
||||
|
||||
scope: any = {
|
||||
editable: '=',
|
||||
organization: '=',
|
||||
parentOrganizationMembers: '='
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,118 @@
|
|||
<div class="organization-progress-line">
|
||||
<md-progress-linear md-mode="indeterminate"
|
||||
ng-show="listOrganizationMembersController.isLoading"></md-progress-linear>
|
||||
</div>
|
||||
<md-content flex class="organization-member-list">
|
||||
<che-list-header che-input-placeholder="Search"
|
||||
che-search-model="listOrganizationMembersController.memberFilter.name"
|
||||
che-on-search-change="listOrganizationMembersController.onSearchChanged(str)"
|
||||
che-hide-search="listOrganizationMembersController.members.length === 0"
|
||||
che-add-button-title="Add Member"
|
||||
che-on-add="listOrganizationMembersController.selectAddMemberDialog(null)"
|
||||
che-hide-add="!listOrganizationMembersController.hasUpdatePermission"
|
||||
che-delete-button-title="Delete"
|
||||
che-on-delete="listOrganizationMembersController.removeSelectedMembers()"
|
||||
che-hide-delete="listOrganizationMembersController.cheListHelper.isNoItemSelected || !listOrganizationMembersController.hasUpdatePermission"
|
||||
che-hide-header="listOrganizationMembersController.cheListHelper.visibleItemsNumber === 0">
|
||||
<div flex="100"
|
||||
layout="row"
|
||||
layout-align="start stretch"
|
||||
class="che-list-item-row">
|
||||
<div layout="column" layout-gt-xs="row" ng-if="listOrganizationMembersController.hasUpdatePermission"
|
||||
layout-align="start center"
|
||||
class="che-checkbox-area">
|
||||
<div layout="row" layout-align="center center" class="che-list-item-checkbox-main">
|
||||
<md-checkbox class="che-list-item-checkbox"
|
||||
aria-label="Member list"
|
||||
ng-checked="listOrganizationMembersController.cheListHelper.areAllItemsSelected"
|
||||
ng-click="listOrganizationMembersController.cheListHelper.changeBulkSelection()"></md-checkbox>
|
||||
</div>
|
||||
</div>
|
||||
<div flex hide-xs layout-gt-xs="row"
|
||||
layout-align="start center"
|
||||
class="che-list-item-details">
|
||||
<che-list-header-column flex-gt-xs="25"
|
||||
che-sort-value='listOrganizationMembersController.memberOrderBy'
|
||||
che-sort-item='name'
|
||||
che-column-title='Name'></che-list-header-column>
|
||||
<che-list-header-column flex-gt-xs="25"
|
||||
che-sort-value='listOrganizationMembersController.memberOrderBy'
|
||||
che-sort-item='email'
|
||||
che-column-title='Email'></che-list-header-column>
|
||||
<che-list-header-column flex-gt-xs="35"
|
||||
che-sort-value='listOrganizationMembersController.memberOrderBy'
|
||||
che-column-title='Roles'></che-list-header-column>
|
||||
<che-list-header-column flex-gt-xs="15"
|
||||
che-column-title='Actions'></che-list-header-column>
|
||||
</div>
|
||||
</div>
|
||||
</che-list-header>
|
||||
<che-list
|
||||
ng-show="listOrganizationMembersController.cheListHelper.visibleItemsNumber > 0">
|
||||
<che-list-item
|
||||
ng-repeat="member in listOrganizationMembersController.cheListHelper.getVisibleItems() | orderBy:[listOrganizationMembersController.memberOrderBy, 'config.name']"
|
||||
lex-gt-sm="100" flex="33" ng-mouseover="hover=true" ng-mouseout="hover=false">
|
||||
<div flex="100"
|
||||
layout="row"
|
||||
layout-align="start stretch"
|
||||
ng-class="{'member-bold': listOrganizationMembersController.isOwner(member)}"
|
||||
class="che-list-item-row member-list-row">
|
||||
<div ng-if="listOrganizationMembersController.hasUpdatePermission"
|
||||
layout="row" layout-align="start center" class="che-checkbox-area">
|
||||
<che-list-item-checked ng-model="listOrganizationMembersController.cheListHelper.itemsSelectionStatus[member.id]"
|
||||
ng-click="listOrganizationMembersController.cheListHelper.updateBulkSelectionStatus()"
|
||||
ng-show="!listOrganizationMembersController.isOwner(member)"
|
||||
che-aria-label-checkbox="member {{member.id}}"></che-list-item-checked>
|
||||
</div>
|
||||
<div flex
|
||||
layout-xs="column" layout-gt-xs="row"
|
||||
layout-align-gt-xs="start center"
|
||||
layout-align-xs="start start"
|
||||
class="che-list-item-details">
|
||||
<div flex-gt-xs="25"
|
||||
class="che-list-item-name">
|
||||
<span class="che-xs-header noselect" hide-gt-xs>Name</span>
|
||||
<span class="member-email che-hover">{{member.name}}</span>
|
||||
</div>
|
||||
<div flex-gt-xs="25"
|
||||
class="che-list-item-secondary">
|
||||
<span class="che-xs-header noselect" hide-gt-xs>Email</span>
|
||||
<span><img class="user-face" gravatar-src="member.email"></span>
|
||||
<span class="member-email che-hover ">{{member.email}}</span>
|
||||
</div>
|
||||
<div flex-gt-xs="35" class="che-list-item-secondary">
|
||||
<span class="che-xs-header noselect" hide-gt-xs>Roles</span>
|
||||
<span class="member-list-permissions">{{listOrganizationMembersController.getMemberRoles(member)}} </span>
|
||||
<span ng-if="listOrganizationMembersController.getOtherActions(member)"
|
||||
uib-tooltip="{{listOrganizationMembersController.getOtherActions(member)}}"> Other...</span>
|
||||
</div>
|
||||
<div flex-gt-xs="15">
|
||||
<span class="che-xs-header noselect" hide-gt-xs>Actions</span>
|
||||
<span class="che-list-actions">
|
||||
|
||||
<a uib-tooltip="Edit developer permissions"
|
||||
ng-click="listOrganizationMembersController.editMember(member)"
|
||||
ng-if="!listOrganizationMembersController.isOwner(member) && listOrganizationMembersController.hasUpdatePermission"
|
||||
ng-disabled="!listOrganizationMembersController.editable">
|
||||
<span class="fa fa-pencil"></span>
|
||||
</a>
|
||||
<a uib-tooltip="Remove developer"
|
||||
ng-click="listOrganizationMembersController.removeMember(member)"
|
||||
ng-if="!listOrganizationMembersController.isOwner(member) && listOrganizationMembersController.hasUpdatePermission"
|
||||
ng-disabled="!listOrganizationMembersController.editable">
|
||||
<span class="fa fa-trash-o"></span>
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</che-list-item>
|
||||
</che-list>
|
||||
<div class="che-list-empty">
|
||||
<span
|
||||
ng-show="listOrganizationMembersController.members.length > 0 && listOrganizationMembersController.cheListHelper.visibleItemsNumber === 0">
|
||||
No members found.
|
||||
</span>
|
||||
<span ng-show="listOrganizationMembersController.members.length === 0">There are no members.</span>
|
||||
</div>
|
||||
</md-content>
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
.organization-member-list *.che-list-item
|
||||
display block
|
||||
|
||||
.user-face
|
||||
che-developers-face()
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* Copyright (c) 2015-2017 Red Hat, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Red Hat, Inc. - initial API and implementation
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* @ngdoc directive
|
||||
* @name organization.details:OrganizationNotFound
|
||||
* @restrict E
|
||||
* @element
|
||||
*
|
||||
* @description
|
||||
* `<organization-not-found organization-name="myOrganization"></organization-not-found>` for displaying "Organization not found" page.
|
||||
*
|
||||
* @usage
|
||||
* <organization-not-found organization-name="myOrganization"></organization-not-found>
|
||||
*
|
||||
* @author Oleksii Kurinnyi
|
||||
*/
|
||||
export class OrganizationNotFound implements ng.IDirective {
|
||||
restrict: string = 'E';
|
||||
replace: boolean = true;
|
||||
templateUrl: string = 'app/organizations/organization-details/organization-not-found/organization-not-found.html';
|
||||
|
||||
scope: any = {
|
||||
organizationName: '='
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
<md-content flex class="organization-error-content" layout="column"
|
||||
layout-align="center center">
|
||||
<div class="error-title">Organization <b>{{organizationName}}</b> not found.</div>
|
||||
<span class="fa fa-group error-image"></span>
|
||||
<div class="error-description">
|
||||
The organization could be deleted by its owner or you do not have permissions to access the organization.
|
||||
</div>
|
||||
</md-content>
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
.organization-error-content
|
||||
height 100%
|
||||
width 100%
|
||||
color $label-secondary-color
|
||||
|
||||
.error-title
|
||||
font-size 12pt
|
||||
font-weight bold
|
||||
|
||||
.error-image
|
||||
font-size 56pt
|
||||
margin 25px 0
|
||||
color $label-info-color
|
||||
|
||||
.error-description
|
||||
font-size 11pt
|
||||
padding 0 20px
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* Copyright (c) 2015-2017 Red Hat, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Red Hat, Inc. - initial API and implementation
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Defines a directive for member in list.
|
||||
*
|
||||
* @author Oleksii Kurinnyi
|
||||
*/
|
||||
export class OrganizationMemberItem {
|
||||
restrict: string = 'E';
|
||||
|
||||
templateUrl: string = 'app/organizations/organization-details/organization-select-members-dialog/organization-member-item/organization-member-item.html';
|
||||
replace: boolean = false;
|
||||
|
||||
scope: {[prop: string]: string} = {
|
||||
member: '=',
|
||||
isSelected: '=',
|
||||
onChange: '&'
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
<che-list-item flex-gt-sm="100" flex="33"
|
||||
ng-mouseover="hover=true" ng-mouseout="hover=false"
|
||||
class="organization-member-item">
|
||||
<div flex="100"
|
||||
layout="row"
|
||||
layout-align="start stretch"
|
||||
class="che-list-item-row member-list-row">
|
||||
<div layout="row"
|
||||
layout-align="start center"
|
||||
class="che-checkbox-area">
|
||||
<che-list-item-checked ng-model="isSelected"
|
||||
ng-click="onChange({'memberId': member.id, 'isSelected': isSelected})"></che-list-item-checked>
|
||||
</div>
|
||||
<div flex
|
||||
layout-xs="column" layout-gt-xs="row"
|
||||
layout-align-gt-xs="start center"
|
||||
layout-align-xs="start start"
|
||||
class="che-list-item-details">
|
||||
<div flex-gt-xs="50"
|
||||
class="che-list-item-name">
|
||||
<span class="che-xs-header noselect" hide-gt-xs>Name</span>
|
||||
<span class="member-email che-hover ">{{member.fullName}}</span>
|
||||
</div>
|
||||
<div flex-gt-xs="50"
|
||||
class="che-list-item-name">
|
||||
<span class="che-xs-header noselect" hide-gt-xs>Email</span>
|
||||
<span class="member-email che-hover ">{{member.email}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</che-list-item>
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
.organization-member-item .member-list-row
|
||||
margin 0
|
||||
|
||||
& > div
|
||||
outline none
|
||||
|
||||
.member-bold
|
||||
font-weight bold
|
||||
|
||||
.member-email
|
||||
max-width 100%
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue