Merging with app management branch

feature/appm-store/pbac
Chathura Ekanayake 7 years ago
commit d0a52bec98

@ -651,7 +651,7 @@ public interface ApplicationManagementAPI {
@GET
@Path("/image-artifacts/{uuid}")
@Produces(MediaType.APPLICATION_OCTET_STREAM)
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@ApiOperation(
consumes = MediaType.APPLICATION_JSON,

@ -29,6 +29,7 @@ import org.wso2.carbon.device.application.mgt.common.Application;
import org.wso2.carbon.device.application.mgt.common.ApplicationList;
import org.wso2.carbon.device.application.mgt.common.ApplicationRelease;
import org.wso2.carbon.device.application.mgt.common.Filter;
import org.wso2.carbon.device.application.mgt.common.ImageArtifact;
import org.wso2.carbon.device.application.mgt.common.exception.ApplicationManagementException;
import org.wso2.carbon.device.application.mgt.common.exception.ApplicationStorageManagementException;
import org.wso2.carbon.device.application.mgt.common.services.ApplicationManager;
@ -452,7 +453,7 @@ public class ApplicationManagementAPIImpl implements ApplicationManagementAPI {
@Override
@GET
@Path("/image-artifacts/{uuid}")
@Produces(MediaType.APPLICATION_OCTET_STREAM)
@Produces(MediaType.APPLICATION_JSON)
public Response getApplicationImageArtifacts(@PathParam("uuid") String applicationUUID,
@QueryParam("name") String name, @QueryParam("count") int count) {
if (name == null || name.isEmpty()) {
@ -461,10 +462,8 @@ public class ApplicationManagementAPIImpl implements ApplicationManagementAPI {
}
ApplicationStorageManager applicationStorageManager = APIUtil.getApplicationStorageManager();
try {
InputStream imageArtifact = applicationStorageManager.getImageArtifact(applicationUUID, name, count);
FileStreamingOutput fileStreamingOutput = new FileStreamingOutput(imageArtifact);
Response.ResponseBuilder response = Response.status(Response.Status.OK).entity(fileStreamingOutput);
response.header("Content-Disposition", "attachment; filename=\"" + name + "\"");
ImageArtifact imageArtifact = applicationStorageManager.getImageArtifact(applicationUUID, name, count);
Response.ResponseBuilder response = Response.status(Response.Status.OK).entity(imageArtifact);
return response.build();
} catch (ApplicationStorageManagementException e) {
log.error("Application Storage Management Exception while getting the image artifact " + name + " of "

@ -32,6 +32,7 @@ import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Response;
import java.util.HashMap;
import java.util.List;
/**
@ -48,24 +49,26 @@ public class SubscriptionManagementAPIImpl implements SubscriptionManagementAPI{
@Path("/install-application")
public Response installApplication(@ApiParam(name = "installationDetails", value = "The application ID and list" +
" the devices/users/roles", required = true) @Valid InstallationDetails installationDetails) {
Object response;
Object result;
SubscriptionManager subscriptionManager = APIUtil.getSubscriptionManager();
try {
String applicationUUTD = installationDetails.getApplicationUUID();
String versionName = installationDetails.getVersionName();
if (!installationDetails.getDeviceIdentifiers().isEmpty()) {
List<DeviceIdentifier> deviceList = installationDetails.getDeviceIdentifiers();
response = subscriptionManager.installApplicationForDevices(applicationUUTD, versionName, deviceList);
result = subscriptionManager.installApplicationForDevices(applicationUUTD, versionName, deviceList);
} else if (!installationDetails.getUserNameList().isEmpty()) {
List<String> userList = installationDetails.getUserNameList();
response = subscriptionManager.installApplicationForUsers(applicationUUTD, userList);
result = subscriptionManager.installApplicationForUsers(applicationUUTD, userList);
} else if (!installationDetails.getRoleNameList().isEmpty()) {
List<String> roleList = installationDetails.getRoleNameList();
response = subscriptionManager.installApplicationForRoles(applicationUUTD, roleList);
result = subscriptionManager.installApplicationForRoles(applicationUUTD, roleList);
} else {
response = "Missing request data!";
return Response.status(Response.Status.BAD_REQUEST).entity(response).build();
result = "Missing request data!";
return Response.status(Response.Status.BAD_REQUEST).entity(result).build();
}
HashMap<String, Object> response = new HashMap<>();
response.put("failedDevices", result);
return Response.status(Response.Status.OK).entity(response).build();
} catch (ApplicationManagementException e) {
String msg = "Error occurred while installing the application";

@ -0,0 +1,192 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
~
~ WSO2 Inc. licenses this file to you under the Apache License,
~ Version 2.0 (the "License"); you may not use this file except
~ in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing,
~ software distributed under the License is distributed on an
~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
~ KIND, either express or implied. See the License for the
~ specific language governing permissions and limitations
~ under the License.
-->
<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>
<groupId>org.wso2.carbon.devicemgt</groupId>
<artifactId>application-mgt</artifactId>
<version>3.0.46-SNAPSHOT</version>
</parent>
<artifactId>org.wso2.carbon.device.application.mgt.authhandler</artifactId>
<version>3.0.46-SNAPSHOT</version>
<packaging>war</packaging>
<name>WSO2 Carbon - Application Management Authentication Handler API</name>
<description>Proxy Service for Authentication Handling in WSO2 App Manager.</description>
<url>http://wso2.org</url>
<build>
<plugins>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<configuration>
<packagingExcludes>WEB-INF/lib/*cxf*.jar</packagingExcludes>
<warName>auth#application-mgt#v1.0</warName>
</configuration>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>deploy</id>
<build>
<defaultGoal>compile</defaultGoal>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<version>1.7</version>
<executions>
<execution>
<phase>compile</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<tasks>
<copy todir="${basedir}/../../../repository/deployment/server/webapps" overwrite="true">
<fileset dir="${basedir}/target">
<include name="auth#application-mgt#v1.0.war" />
</fileset>
</copy>
</tasks>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>client</id>
<build>
<defaultGoal>test</defaultGoal>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.2.1</version>
<executions>
<execution>
<phase>test</phase>
<goals>
<goal>java</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
<dependencies>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-frontend-jaxws</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-frontend-jaxrs</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-transports-http</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-jaxrs</artifactId>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-core-asl</artifactId>
</dependency>
<dependency>
<groupId>javax.ws.rs</groupId>
<artifactId>jsr311-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.wso2.carbon</groupId>
<artifactId>org.wso2.carbon.utils</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.wso2.carbon</groupId>
<artifactId>org.wso2.carbon.logging</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.json.wso2</groupId>
<artifactId>json</artifactId>
</dependency>
<dependency>
<groupId>commons-codec.wso2</groupId>
<artifactId>commons-codec</artifactId>
<scope>provided</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/io.github.openfeign/feign-core -->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-core</artifactId>
<version>9.5.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.github.openfeign/feign-jackson -->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-jackson</artifactId>
<version>9.5.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.github.openfeign/feign-jackson -->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-jaxrs</artifactId>
<version>9.5.0</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.wso2.orbit.com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
</dependency>
<dependency>
<groupId>javax.ws.rs</groupId>
<artifactId>javax.ws.rs-api</artifactId>
</dependency>
</dependencies>
</project>

@ -0,0 +1,52 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.wso2.carbon.device.application.mgt.auth.handler.service;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
@Path("/auth")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public interface AuthHandlerService {
@POST
@Path("/login")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
Response login(@QueryParam("userName") String userName, @QueryParam("password") String password);
@POST
@Path("/refresh")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
Response refresh(@QueryParam("refreshToken") String refreshToken, @QueryParam("clientId") String clientId,
@QueryParam("clientSecret") String clientSecret);
@POST
@Path("/logout")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
Response logout(@QueryParam("token") String token, @QueryParam("clientId") String clientId,
@QueryParam("clientSecret") String clientSecret);
}

@ -0,0 +1,158 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.wso2.carbon.device.application.mgt.auth.handler.service.impl;
import feign.Client;
import feign.Feign;
import feign.auth.BasicAuthRequestInterceptor;
import feign.jackson.JacksonDecoder;
import feign.jackson.JacksonEncoder;
import feign.jaxrs.JAXRSContract;
import org.json.JSONObject;
import org.wso2.carbon.device.application.mgt.auth.handler.service.AuthHandlerService;
import org.wso2.carbon.device.application.mgt.auth.handler.util.Constants;
import org.wso2.carbon.device.application.mgt.auth.handler.util.dto.AccessTokenInfo;
import org.wso2.carbon.device.application.mgt.auth.handler.util.dto.ApiApplicationKey;
import org.wso2.carbon.device.application.mgt.auth.handler.util.dto.ApiApplicationRegistrationService;
import org.wso2.carbon.device.application.mgt.auth.handler.util.dto.ApiRegistrationProfile;
import org.wso2.carbon.device.application.mgt.auth.handler.util.dto.TokenIssuerService;
import org.wso2.carbon.device.application.mgt.auth.handler.util.dto.TokenRevokeService;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
@Path("/auth")
public class AuthHandlerServiceImpl implements AuthHandlerService {
private TrustManager[] trustAllCerts = new TrustManager[]{
new X509TrustManager() {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkClientTrusted(
java.security.cert.X509Certificate[] certs, String authType) {
}
public void checkServerTrusted(
java.security.cert.X509Certificate[] certs, String authType) {
}
}
};
private Client disableHostnameVerification = new Client.Default(getTrustedSSLSocketFactory(), new HostnameVerifier() {
@Override
public boolean verify(String s, SSLSession sslSession) {
return true;
}
});
@POST
@Path("/login")
@Produces(MediaType.APPLICATION_JSON)
@Override
public Response login(@QueryParam("userName") String userName, @QueryParam("password") String password) {
try {
ApiApplicationRegistrationService apiApplicationRegistrationService = Feign.builder()
.client(disableHostnameVerification)
.requestInterceptor(new BasicAuthRequestInterceptor(userName, password))
.contract(new JAXRSContract()).encoder(new JacksonEncoder()).decoder(new JacksonDecoder())
.target(ApiApplicationRegistrationService.class, Constants.API_APPLICATION_ENDPOINT);
ApiRegistrationProfile apiRegistrationProfile = new ApiRegistrationProfile();
apiRegistrationProfile.setApplicationName(Constants.APPLICATION_NAME);
apiRegistrationProfile.setIsAllowedToAllDomains(false);
apiRegistrationProfile.setIsMappingAnExistingOAuthApp(false);
apiRegistrationProfile.setTags(Constants.TAGS);
ApiApplicationKey apiApplicationKey = apiApplicationRegistrationService.register(apiRegistrationProfile);
//PasswordGrantType
TokenIssuerService tokenIssuerService = Feign.builder().client(disableHostnameVerification)
.requestInterceptor(new BasicAuthRequestInterceptor(apiApplicationKey.getConsumerKey(),
apiApplicationKey.getConsumerSecret()))
.contract(new JAXRSContract()).encoder(new JacksonEncoder()).decoder(new JacksonDecoder())
.target(TokenIssuerService.class, Constants.TOKEN_ENDPOINT);
AccessTokenInfo accessTokenInfo = tokenIssuerService.getToken(Constants.PASSWORD_GRANT_TYPE,
userName, password, Constants.SCOPES);
JSONObject loginInfo = new JSONObject(accessTokenInfo);
loginInfo.append(Constants.USER_NAME, userName);
loginInfo.append(Constants.APPLICATION_INFO, new JSONObject(apiApplicationKey));
return Response.status(200).entity(loginInfo.toString()).build();
} catch (Exception e) {
return Response.status(500).build();
}
}
@POST
@Path("/refresh")
@Produces(MediaType.APPLICATION_JSON)
@Override
public Response refresh(@QueryParam("refresh_token") String refresh_token, @QueryParam("clientId") String clientId,
@QueryParam("clientSecret") String clientSecret) {
try {
TokenIssuerService tokenIssuerService = Feign.builder().client(disableHostnameVerification)
.requestInterceptor(new BasicAuthRequestInterceptor(clientId, clientSecret))
.contract(new JAXRSContract()).encoder(new JacksonEncoder()).decoder(new JacksonDecoder())
.target(TokenIssuerService.class, Constants.TOKEN_ENDPOINT);
AccessTokenInfo accessTokenInfo = tokenIssuerService.getRefreshToken(Constants.REFRESH_GRANT_TYPE, refresh_token);
return Response.status(200).entity(new JSONObject(accessTokenInfo)).build();
} catch (Exception e) {
return Response.status(500).build();
}
}
@POST
@Path("/logout")
@Override
public Response logout(@QueryParam("token") String token, @QueryParam("clientId") String clientId,
@QueryParam("clientSecret") String clientSecret) {
try {
TokenRevokeService tokenRevokeService = Feign.builder().client(disableHostnameVerification)
.requestInterceptor(new BasicAuthRequestInterceptor(clientId, clientSecret))
.contract(new JAXRSContract()).encoder(new JacksonEncoder()).decoder(new JacksonDecoder())
.target(TokenRevokeService.class, Constants.TOKEN_ENDPOINT);
tokenRevokeService.revoke(token);
return Response.status(200).build();
} catch (Exception e) {
return Response.status(500).build();
}
}
private SSLSocketFactory getTrustedSSLSocketFactory() {
try {
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, trustAllCerts, new java.security.SecureRandom());
return sc.getSocketFactory();
} catch (KeyManagementException | NoSuchAlgorithmException e) {
return null;
}
}
}

@ -0,0 +1,35 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.wso2.carbon.device.application.mgt.auth.handler.util;
//TODO: Remove hardcoded localhost and ports
public class Constants {
public static String SCOPES = "perm:application:get perm:application:create perm:application:update " +
"perm:application-mgt:login perm:application:delete perm:platform:add perm:platform:remove " +
"perm:roles:view perm:devices:view perm:platform:get";
public static String[] TAGS = {"device_management"};
public static String USER_NAME = "userName";
public static String APPLICATION_NAME = "applicationmgt_publisher";
public static String TOKEN_ENDPOINT = "https://localhost:8243";
public static String PASSWORD_GRANT_TYPE = "password";
public static String REFRESH_GRANT_TYPE = "refresh_token";
public static String API_APPLICATION_ENDPOINT = "https://localhost:9443/api-application-registration/";
public static String APPLICATION_INFO = "application_info";
}

@ -0,0 +1,83 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.wso2.carbon.device.application.mgt.auth.handler.util.dto;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
/**
* This hold access token info that returned from the api call
*/
@XmlRootElement(name = "AccessTokenInfo")
@JsonIgnoreProperties(ignoreUnknown = true)
public class AccessTokenInfo {
@XmlElement(required = true, name = "token_type")
private String token_type;
@XmlElement(required = true, name = "expires_in")
private String expires_in;
@XmlElement(required = true, name = "refresh_token")
private String refresh_token;
@XmlElement(required = true, name = "access_token")
private String access_token;
public AccessTokenInfo() {}
public String getToken_type() {
return token_type;
}
public void setToken_type(String token_type) {
this.token_type = token_type;
}
public String getExpires_in() {
return expires_in;
}
public void setExpires_in(String expires_in) {
this.expires_in = expires_in;
}
public String getRefresh_token() {
return refresh_token;
}
public void setRefresh_token(String refresh_token) {
this.refresh_token = refresh_token;
}
public String getAccess_token() {
return access_token;
}
public void setAccess_token(String access_token) {
this.access_token = access_token;
}
@Override
public String toString() {
return access_token + " " + token_type + " " + refresh_token + " ";
}
}

@ -0,0 +1,49 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.wso2.carbon.device.application.mgt.auth.handler.util.dto;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
/**
* This holds api application consumer key and secret.
*/
@XmlRootElement
public class ApiApplicationKey {
@XmlElement
private String client_id;
@XmlElement
private String client_secret;
public String getConsumerKey() {
return this.client_id;
}
public void setClient_id(String consumerKey) {
this.client_id = consumerKey;
}
public String getConsumerSecret() {
return this.client_secret;
}
public void setClient_secret(String consumerSecret) {
this.client_secret = consumerSecret;
}
}

@ -0,0 +1,43 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.wso2.carbon.device.application.mgt.auth.handler.util.dto;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
/**
* This is the application registration service that exposed for apimApplicationRegistration
*/
@Path("/register")
public interface ApiApplicationRegistrationService {
/**
* This method is used to register api application
*
* @param registrationProfile contains the necessary attributes that are needed in order to register an app.
*/
@POST
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
ApiApplicationKey register(ApiRegistrationProfile registrationProfile);
}

@ -0,0 +1,82 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.wso2.carbon.device.application.mgt.auth.handler.util.dto;
/**
* This class represents the data that are required to register
* the oauth application.
*/
public class ApiRegistrationProfile {
public String applicationName;
public String tags[];
public boolean isAllowedToAllDomains;
public String consumerKey;
public String consumerSecret;
public boolean isMappingAnExistingOAuthApp;
public String getApplicationName() {
return applicationName;
}
public void setApplicationName(String applicationName) {
this.applicationName = applicationName;
}
public String[] getTags() {
return tags;
}
public void setTags(String[] tags) {
this.tags = tags;
}
public boolean isAllowedToAllDomains() {
return isAllowedToAllDomains;
}
public void setIsAllowedToAllDomains(boolean isAllowedToAllDomains) {
this.isAllowedToAllDomains = isAllowedToAllDomains;
}
public boolean isMappingAnExistingOAuthApp() {
return isMappingAnExistingOAuthApp;
}
public void setIsMappingAnExistingOAuthApp(boolean isMappingAnExistingOAuthApp) {
this.isMappingAnExistingOAuthApp = isMappingAnExistingOAuthApp;
}
public String getConsumerKey() {
return consumerKey;
}
public void setConsumerKey(String consumerKey) {
this.consumerKey = consumerKey;
}
public String getConsumerSecret() {
return consumerSecret;
}
public void setConsumerSecret(String consumerSecret) {
this.consumerSecret = consumerSecret;
}
}

@ -0,0 +1,47 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.wso2.carbon.device.application.mgt.auth.handler.util.dto;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import static feign.Util.checkNotNull;
/**
* This is a request interceptor to add oauth token header.
*/
public class OAuthRequestInterceptor implements RequestInterceptor {
private final String headerValue;
/**
* Creates an interceptor that authenticates all requests with the specified OAUTH token
*
* @param token the access token to use for authentication
*/
public OAuthRequestInterceptor(String token) {
checkNotNull(token, "access_token");
headerValue = "Bearer " + token;
}
@Override
public void apply(RequestTemplate template) {
template.header("Authorization", headerValue);
}
}

@ -0,0 +1,83 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.wso2.carbon.device.application.mgt.auth.handler.util.dto;
/**
* This class represents the data that are required to register
* the oauth application.
*/
public class RegistrationProfile {
public String callbackUrl;
public String clientName;
public String tokenScope;
public String owner;
public String grantType;
public String applicationType;
private static final String TAG = RegistrationProfile.class.getSimpleName();
public String getCallbackUrl() {
return callbackUrl;
}
public void setCallbackUrl(String callBackUrl) {
this.callbackUrl = callBackUrl;
}
public String getClientName() {
return clientName;
}
public void setClientName(String clientName) {
this.clientName = clientName;
}
public String getTokenScope() {
return tokenScope;
}
public void setTokenScope(String tokenScope) {
this.tokenScope = tokenScope;
}
public String getOwner() {
return owner;
}
public void setOwner(String owner) {
this.owner = owner;
}
public String getGrantType() {
return grantType;
}
public void setGrantType(String grantType) {
this.grantType = grantType;
}
public String getApplicationType() {
return applicationType;
}
public void setApplicationType(String applicationType) {
this.applicationType = applicationType;
}
}

@ -0,0 +1,42 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.wso2.carbon.device.application.mgt.auth.handler.util.dto;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
/**
* This hold the api definition that is used as a contract with netflix feign.
*/
@Path("/token")
public interface TokenIssuerService {
@POST
@Produces(MediaType.APPLICATION_JSON)
AccessTokenInfo getToken(@QueryParam("grant_type") String grant, @QueryParam("username") String username,
@QueryParam("password") String password, @QueryParam("scope") String scope);
@POST
@Produces(MediaType.APPLICATION_JSON)
AccessTokenInfo getRefreshToken(@QueryParam("grant_type") String grantType,
@QueryParam("refreshToken") String refreshToken);
}

@ -0,0 +1,35 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.wso2.carbon.device.application.mgt.auth.handler.util.dto;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Response;
/**
* Api definition for token revoke that will be used as Feign contract.
* */
@Path("/revoke")
public interface TokenRevokeService {
@POST
Response revoke(@QueryParam("token")String accessToken);
}

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright (c) 2016, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
~
~ WSO2 Inc. licenses this file to you under the Apache License,
~ Version 2.0 (the "License"); you may not use this file except
~ in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing,
~ software distributed under the License is distributed on an
~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
~ KIND, either express or implied. See the License for the
~ specific language governing permissions and limitations
~ under the License.
-->
<!-- This file contains the list of permissions that are associated with URL end points
of the web app. Each permission should contain the name, permission path ,API path
(URL) , HTTP method and OAUTH2 authorization scope (not-required).
When defining dynamic paths for APIs, path variables are denoted by '*' notation.
For ex:
Actual API endpoint: devicemgt_admin/1.0.0/devices/{device-id}
URL to be represented here: /devices/*
NOTE: All the endpoints of the web app should be available in this file. Otherwise
it will result 403 error at the runtime.
-->
<PermissionConfiguration>
<APIVersion></APIVersion>
<!-- Application related permissions -->
</PermissionConfiguration>

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<!--
~ Copyright (c) 2015, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
~
~ WSO2 Inc. licenses this file to you under the Apache License,
~ Version 2.0 (the "License"); you may not use this file except
~ in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing,
~ software distributed under the License is distributed on an
~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
~ KIND, either express or implied. See the License for the
~ specific language governing permissions and limitations
~ under the License.
-->
<!--
This file defines class loading policy of the whole container. But this behaviour can be overridden by individual webapps by putting this file into the META-INF/ directory.
-->
<Classloading xmlns="http://wso2.org/projects/as/classloading">
<!-- Parent-first or child-first. Default behaviour is child-first.-->
<ParentFirst>false</ParentFirst>
<!--
Default environments that contains provides to all the webapps. This can be overridden by individual webapps by specifing required environments
Tomcat environment is the default and every webapps gets it even if they didn't specify it.
e.g. If a webapps requires CXF, they will get both Tomcat and CXF.
-->
<Environments>CXF,Carbon</Environments>
</Classloading>

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
~
~ WSO2 Inc. licenses this file to you under the Apache License,
~ Version 2.0 (the "License"); you may not use this file except
~ in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing,
~ software distributed under the License is distributed on an
~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
~ KIND, either express or implied. See the License for the
~ specific language governing permissions and limitations
~ under the License.
-->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jaxrs="http://cxf.apache.org/jaxrs"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://cxf.apache.org/jaxrs http://cxf.apache.org/schemas/jaxrs.xsd">
<jaxrs:server id="applicationMgtAuthService" address="/">
<jaxrs:serviceBeans>
<ref bean="applicationMgtAuthServiceBean"/>
</jaxrs:serviceBeans>
</jaxrs:server>
<bean id="applicationMgtAuthServiceBean"
class="org.wso2.carbon.device.application.mgt.auth.handler.service.impl.AuthHandlerServiceImpl"/>
</beans>

@ -0,0 +1,115 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright (c) 2016, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
~
~ WSO2 Inc. licenses this file to you under the Apache License,
~ Version 2.0 (the "License"); you may not use this file except
~ in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing,
~ software distributed under the License is distributed on an
~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
~ KIND, either express or implied. See the License for the
~ specific language governing permissions and limitations
~ under the License.
-->
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
version="2.5">
<display-name>Application Management Auth Webapp</display-name>
<servlet>
<description>JAX-WS/JAX-RS Application Management Endpoint</description>
<display-name>JAX-WS/JAX-RS Servlet</display-name>
<servlet-name>CXFServlet</servlet-name>
<servlet-class>
org.apache.cxf.transport.servlet.CXFServlet
</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>CXFServlet</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
<session-config>
<session-timeout>60</session-timeout>
</session-config>
<context-param>
<param-name>doAuthentication</param-name>
<param-value>false</param-value>
</context-param>
<!--publish to apim-->
<context-param>
<param-name>managed-api-enabled</param-name>
<param-value>false</param-value>
</context-param>
<context-param>
<param-name>managed-api-owner</param-name>
<param-value>admin</param-value>
</context-param>
<context-param>
<param-name>isSharedWithAllTenants</param-name>
<param-value>true</param-value>
</context-param>
<filter>
<filter-name>CorsFilter</filter-name>
<filter-class>org.apache.catalina.filters.CorsFilter</filter-class>
<init-param>
<param-name>cors.allowed.origins</param-name>
<param-value>*</param-value>
</init-param>
<init-param>
<param-name>cors.allowed.methods</param-name>
<param-value>GET,POST,DELETE,PUT</param-value>
</init-param>
<init-param>
<param-name>cors.allowed.headers</param-name>
<param-value>Content-Type</param-value>
</init-param>
</filter>
<filter>
<filter-name>HttpHeaderSecurityFilter</filter-name>
<filter-class>org.apache.catalina.filters.HttpHeaderSecurityFilter</filter-class>
<init-param>
<param-name>hstsEnabled</param-name>
<param-value>false</param-value>
</init-param>
</filter>
<filter>
<filter-name>ContentTypeBasedCachePreventionFilter</filter-name>
<filter-class>org.wso2.carbon.ui.filters.cache.ContentTypeBasedCachePreventionFilter</filter-class>
<init-param>
<param-name>patterns</param-name>
<param-value>text/html" ,application/json" ,text/plain</param-value>
</init-param>
<init-param>
<param-name>filterAction</param-name>
<param-value>enforce</param-value>
</init-param>
<init-param>
<param-name>httpHeaders</param-name>
<param-value>Cache-Control: no-store, no-cache, must-revalidate, private</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>HttpHeaderSecurityFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>ContentTypeBasedCachePreventionFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>CorsFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>

@ -0,0 +1,54 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
package org.wso2.carbon.device.application.mgt.common;
/**
* This represents a image artifact of a application. Icon, Screen-shot or Banner.
* It consists of a name, type and base64 encoded string format of the image.
*/
public class ImageArtifact {
private String name;
private String type;
private String encodedImage;
public String getType() {
return type;
}
public String getEncodedImage() {
return encodedImage;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void setType(String type) {
this.type = type;
}
public void setEncodedImage(String encodedImage) {
this.encodedImage = encodedImage;
}
}

@ -19,6 +19,7 @@
package org.wso2.carbon.device.application.mgt.common.services;
import org.wso2.carbon.device.application.mgt.common.ImageArtifact;
import org.wso2.carbon.device.application.mgt.common.exception.ApplicationStorageManagementException;
import java.io.InputStream;
@ -93,6 +94,6 @@ public interface ApplicationStorageManager {
* @return the relevant image artifact.
* @throws ApplicationStorageManagementException Application Storage Management Exception.
*/
public InputStream getImageArtifact(String applicationUUID, String name, int count) throws
public ImageArtifact getImageArtifact(String applicationUUID, String name, int count) throws
ApplicationStorageManagementException;
}

@ -31,7 +31,7 @@ public interface SubscriptionManager {
* To install an application to given list of devices.
* @param applicationUUID Application ID
* @param deviceList Device list
* @return DeviceList which the application has been installed
* @return Failed Device List which the application was unable to install
* @throws ApplicationManagementException Application Management Exception
*/
List<DeviceIdentifier> installApplicationForDevices(String applicationUUID, String versionName,
@ -42,10 +42,10 @@ public interface SubscriptionManager {
* To install an application to given list of users.
* @param applicationUUID Application ID
* @param userList User list
* @return User list which the application has been installed
* @return Failed Device List which the application was unable to install
* @throws ApplicationManagementException Application Management Exception
*/
List<String> installApplicationForUsers(String applicationUUID,
List<DeviceIdentifier> installApplicationForUsers(String applicationUUID,
List<String> userList)
throws ApplicationManagementException;
@ -53,10 +53,10 @@ public interface SubscriptionManager {
* To install an application to given list of users.
* @param applicationUUID Application ID
* @param roleList Role list
* @return Role list which the application has been installed
* @return Failed Device List which the application was unable to install
* @throws ApplicationManagementException Application Management Exception
*/
List<String> installApplicationForRoles(String applicationUUID,
List<DeviceIdentifier> installApplicationForRoles(String applicationUUID,
List<String> roleList)
throws ApplicationManagementException;
@ -64,7 +64,7 @@ public interface SubscriptionManager {
* To uninstall an application from a given list of devices.
* @param applicationUUID Application ID
* @param deviceList Device list
* @return DeviceList which the application has been uninstalled
* @return Failed Device List which the application was unable to uninstall
* @throws ApplicationManagementException Application Management Exception
*/
List<DeviceIdentifier> uninstallApplication(String applicationUUID,

@ -75,7 +75,9 @@
org.wso2.carbon.user.core.*,
org.wso2.carbon.user.api.*,
org.wso2.carbon.ndatasource.core,
org.wso2.carbon
org.wso2.carbon,
org.apache.commons.io,
org.apache.commons.codec.binary;version="${commons-codec.wso2.osgi.version.range}"
</Import-Package>
<Export-Package>
!org.wso2.carbon.device.application.mgt.core.internal.*,

@ -26,6 +26,7 @@ import org.wso2.carbon.device.application.mgt.core.dao.PlatformDAO;
import org.wso2.carbon.device.application.mgt.core.dao.common.Util;
import org.wso2.carbon.device.application.mgt.core.dao.impl.AbstractDAOImpl;
import org.wso2.carbon.device.application.mgt.core.exception.PlatformManagementDAOException;
import org.wso2.carbon.device.application.mgt.core.util.ConnectionManagerUtil;
import org.wso2.carbon.utils.multitenancy.MultitenantConstants;
import java.sql.Connection;
@ -78,6 +79,27 @@ public class GenericPlatformDAOImpl extends AbstractDAOImpl implements PlatformD
preparedStatement.execute();
}
}
//Adding tags to the database.
Boolean isBatchExecutionSupported = ConnectionManagerUtil.isBatchQuerySupported();
String insertTags = "INSERT INTO APPM_PLATFORM_TAG (NAME, PLATFORM_ID) VALUES (?, ?)";
preparedStatement = connection.prepareStatement(insertTags);
if (platform.getTags() != null) {
for (String tag : platform.getTags()) {
preparedStatement.setString(1, tag);
preparedStatement.setInt(2, platformId);
if (isBatchExecutionSupported) {
preparedStatement.addBatch();
}
else {
preparedStatement.execute();
}
}
if (isBatchExecutionSupported) {
preparedStatement.executeBatch();
}
}
} else {
String insertToPlatform =
"INSERT INTO APPM_PLATFORM (IDENTIFIER, TENANT_ID, FILE_BASED, IS_SHARED, "
@ -486,6 +508,18 @@ public class GenericPlatformDAOImpl extends AbstractDAOImpl implements PlatformD
platform.setShared(rs.getBoolean(8));
platform.setDefaultTenantMapping(rs.getBoolean(9));
platform.setId(rs.getInt(4));
//Getting tags
sql = "SELECT * FROM APPM_PLATFORM_TAG WHERE PLATFORM_ID=?";
stmt = conn.prepareStatement(sql);
stmt.setInt(1, rs.getInt(4));
ResultSet rsTags = stmt.executeQuery();
List<String> tags = new ArrayList<>();
while (rsTags.next()) {
tags.add(rsTags.getString("NAME"));
}
platform.setTags(tags);
if (!platform.isFileBased()) {
platform.setName(rs.getString(5));
platform.setDescription(rs.getString(6));

@ -19,11 +19,14 @@
package org.wso2.carbon.device.application.mgt.core.impl;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.IOUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.wso2.carbon.context.PrivilegedCarbonContext;
import org.wso2.carbon.device.application.mgt.common.Application;
import org.wso2.carbon.device.application.mgt.common.ApplicationRelease;
import org.wso2.carbon.device.application.mgt.common.ImageArtifact;
import org.wso2.carbon.device.application.mgt.common.exception.ApplicationManagementException;
import org.wso2.carbon.device.application.mgt.common.exception.ApplicationStorageManagementException;
import org.wso2.carbon.device.application.mgt.common.exception.DBConnectionException;
@ -42,6 +45,7 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.util.Arrays;
import java.util.List;
@ -207,7 +211,7 @@ public class ApplicationStorageManagerImpl implements ApplicationStorageManager
}
@Override
public InputStream getImageArtifact(String applicationUUID, String name, int count) throws
public ImageArtifact getImageArtifact(String applicationUUID, String name, int count) throws
ApplicationStorageManagementException {
Application application = validateApplication(applicationUUID);
validateImageArtifactNames(name);
@ -222,11 +226,19 @@ public class ApplicationStorageManagerImpl implements ApplicationStorageManager
"Image artifact " + name + " does not exist for the " + "application with UUID " + applicationUUID);
} else {
try {
return new FileInputStream(imageArtifactPath);
ImageArtifact imageArtifact = new ImageArtifact();
imageArtifact.setName(imageFile.getName());
imageArtifact.setType(Files.probeContentType(imageFile.toPath()));
byte[] imageBytes = IOUtils.toByteArray(new FileInputStream(imageArtifactPath));
imageArtifact.setEncodedImage(Base64.encodeBase64URLSafeString(imageBytes));
return imageArtifact;
} catch (FileNotFoundException e) {
throw new ApplicationStorageManagementException(
"File not found exception while trying to get the image artifact " + name + " for the "
+ "application " + applicationUUID, e);
} catch (IOException e) {
throw new ApplicationStorageManagementException("IO Exception while trying to detect the image "
+ "artifact " + name + " for the application " + applicationUUID, e);
}
}
}

@ -19,10 +19,12 @@ package org.wso2.carbon.device.application.mgt.core.impl;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.wso2.carbon.context.PrivilegedCarbonContext;
import org.wso2.carbon.device.application.mgt.common.DeviceIdentifier;
import org.wso2.carbon.device.application.mgt.common.exception.ApplicationManagementException;
import org.wso2.carbon.device.application.mgt.common.services.SubscriptionManager;
import org.wso2.carbon.device.application.mgt.core.dao.common.DAOFactory;
import org.wso2.carbon.device.application.mgt.core.internal.DataHolder;
import org.wso2.carbon.device.application.mgt.core.util.HelperUtil;
import org.wso2.carbon.device.mgt.common.Device;
import org.wso2.carbon.device.mgt.common.DeviceManagementConstants;
@ -104,25 +106,42 @@ public class SubscriptionManagerImpl implements SubscriptionManager {
}
@Override
public List<String> installApplicationForUsers(String applicationUUID, List<String> userList)
public List<DeviceIdentifier> installApplicationForUsers(String applicationUUID, List<String> userList)
throws ApplicationManagementException {
log.info("Install application: " + applicationUUID + " to: " + userList.size() + " users.");
List<DeviceIdentifier> deviceList = new ArrayList<>();
int tenantId = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantId();
for (String user : userList) {
//Todo: implementation
//Todo: get the device list and call installApplicationForDevices
try {
List<Device> devicesOfUser = DeviceManagementDAOFactory.getDeviceDAO().getDevicesOfUser(user, tenantId);
for (Device device : devicesOfUser) {
deviceList.add(new DeviceIdentifier(device
.getDeviceIdentifier(), device.getType()));
}
} catch (DeviceManagementDAOException e) {
log.error("Error when extracting the device list from user[" + user + "].", e);
}
}
return userList;
return installApplication(applicationUUID, deviceList);
}
@Override
public List<String> installApplicationForRoles(String applicationUUID, List<String> roleList)
public List<DeviceIdentifier> installApplicationForRoles(String applicationUUID, List<String> roleList)
throws ApplicationManagementException {
log.info("Install application: " + applicationUUID + " to: " + roleList.size() + " users.");
log.info("Install application: " + applicationUUID + " to: " + roleList.size() + " roles.");
List<DeviceIdentifier> deviceList = new ArrayList<>();
for (String role : roleList) {
//Todo: implementation
//Todo: get the device list and call installApplicationForDevices
try {
List<Device> devicesOfRole = DataHolder.getInstance().getDeviceManagementService().getAllDevicesOfRole(role);
for (Device device : devicesOfRole) {
deviceList.add(new DeviceIdentifier(device
.getDeviceIdentifier(), device.getType()));
}
} catch (DeviceManagementException e) {
log.error("Error when extracting the device list from role[" + role + "].", e);
}
}
return roleList;
return installApplication(applicationUUID, deviceList);
}
@Override
@ -131,4 +150,33 @@ public class SubscriptionManagerImpl implements SubscriptionManager {
throws ApplicationManagementException {
return null;
}
private List<DeviceIdentifier> installApplication(String applicationUUID, List<DeviceIdentifier> deviceList)
throws ApplicationManagementException {
List<DeviceIdentifier> failedDeviceList = new ArrayList<>(deviceList);
List<org.wso2.carbon.device.mgt.common.DeviceIdentifier> activeDeviceList = new ArrayList<>();
for (DeviceIdentifier device : deviceList) {
org.wso2.carbon.device.mgt.common.DeviceIdentifier deviceIdentifier = new org.wso2.carbon.device.mgt
.common.DeviceIdentifier(device.getId(), device.getType());
try {
DeviceManagementDAOFactory.openConnection();
if (DeviceManagementDAOFactory.getDeviceDAO().getDevice(deviceIdentifier).isEmpty()) {
log.error("Device with ID: " + device.getId() + " not found to install the application.");
} else {
if (log.isDebugEnabled()) {
log.debug("Prepare application install to : " + device.getId());
}
activeDeviceList.add(deviceIdentifier);
DAOFactory.getSubscriptionDAO().addDeviceApplicationMapping(device.getId(), applicationUUID, false);
failedDeviceList.remove(device);
}
} catch (DeviceManagementDAOException | SQLException e) {
throw new ApplicationManagementException("Error locating device.", e);
} finally {
DeviceManagementDAOFactory.closeConnection();
}
}
//Todo: generating one time download link for the application and put install operation to devices.
return failedDeviceList;
}
}

@ -19,44 +19,21 @@
<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>
<groupId>org.wso2.carbon.devicemgt</groupId>
<artifactId>application-mgt</artifactId>
<version>3.0.46-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>org.wso2.carbon.device.application.mgt.publisher.ui</artifactId>
<version>3.0.46-SNAPSHOT</version>
<name>WSO2 Carbon - Application Management Publisher UI</name>
<description>WSO2 Carbon - Application Management Publisher UI React Application</description>
<url>http://wso2.org</url>
<build>
<plugins>
<plugin>
<groupId>org.wso2.maven</groupId>
<artifactId>carbon-p2-plugin</artifactId>
<version>${carbon.p2.plugin.version}</version>
<executions>
<execution>
<id>4-p2-feature-generation</id>
<phase>package</phase>
<goals>
<goal>p2-feature-gen</goal>
</goals>
<configuration>
<id>org.wso2.carbon.device.application.mgt</id>
<propertiesFile>../../etc/feature.properties</propertiesFile>
<adviceFile>
<properties>
<propertyDef>org.wso2.carbon.p2.category.type:server
</propertyDef>
<propertyDef>org.eclipse.equinox.p2.type.group:false
</propertyDef>
</properties>
</adviceFile>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
@ -96,8 +73,15 @@
<workingDirectory>${npm.working.dir}</workingDirectory>
</configuration>
</plugin>
</plugins>
<resources>
<resource>
<directory>${project.build.directory}/target</directory>
</resource>
<resource>
<directory>src/main/resources</directory>
</resource>
</resources>
</build>
<profiles>
<profile>

@ -10,39 +10,45 @@
"license": "Apache License 2.0",
"dependencies": {
"axios": "^0.16.2",
"bootstrap": "^4.0.0-alpha.6",
"flux": "^3.1.3",
"history": "^4.6.3",
"history": "^4.7.2",
"latest-version": "^3.1.0",
"material-ui": "^0.19.0",
"material-ui": "^0.19.1",
"prop-types": "^15.5.10",
"qs": "^6.5.0",
"react": "^15.6.1",
"react-addons-css-transition-group": "^15.6.0",
"react-addons-transition-group": "^15.6.0",
"react-dom": "^15.6.1",
"react-dropzone": "^4.1.0",
"react-dropzone": "^4.1.2",
"react-images-uploader": "^1.1.0",
"react-material-ui-form-validator": "^0.5.0",
"react-modal": "^2.2.2",
"react-router": "^4.1.2",
"react-router-dom": "^4.1.2",
"react-material-ui-form-validator": "^0.5.1",
"react-modal": "^2.3.2",
"react-router": "^4.2.0",
"react-router-dom": "^4.2.2",
"react-scripts": "1.0.10",
"react-sliding-pane": "^1.2.3",
"react-tap-event-plugin": "^2.0.1"
"react-tap-event-plugin": "^2.0.1",
"reactstrap": "^4.8.0"
},
"devDependencies": {
"babel-core": "^6.24.1",
"babel-loader": "^7.0.0",
"babel-core": "^6.26.0",
"babel-loader": "^7.1.2",
"babel-plugin-transform-class-properties": "^6.24.1",
"babel-preset-es2015": "^6.24.1",
"chai": "^4.0.2",
"babel-preset-react": "^6.24.1",
"babel-register": "^6.24.1",
"css-loader": "^0.28.2",
"babel-register": "^6.26.0",
"chai": "^4.1.2",
"css-loader": "^0.28.7",
"less": "^2.7.2",
"less-loader": "^4.0.4",
"mocha": "^3.4.1",
"mock-local-storage": "^1.0.2",
"node-sass": "^4.5.3",
"sass-loader": "^6.0.6",
"style-loader": "^0.18.1",
"webpack": "^2.5.0"
"webpack": "^2.7.0"
},
"scripts": {
"start": "react-scripts start",

@ -21,12 +21,14 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="theme-color" content="#000000">
<link rel="stylesheet" type="text/css" href="/publisher/css/font-wso2.css">
<link rel="stylesheet" type="text/css" href="/publisher/themes/default/default-theme.css">
<!--
manifest.json provides metadata used when your web app is added to the
homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
-->
<link rel="manifest" href="manifest.json">
<link rel="shortcut icon" href="images/favicon.png">
<link rel="manifest" href="/publisher/manifest.json">
<link rel="shortcut icon" href="/publisher/images/favicon.png">
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
@ -39,11 +41,11 @@
<title>WSO2 IoT App Publisher</title>
</head>
<body>
<script src='./dist/index.js'></script>
<noscript>
You need to enable JavaScript to run this app.
</noscript>
<div id="root"></div>
<script src='/publisher/dist/index.js'></script>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.

@ -0,0 +1,66 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
.image-container {
position: relative;
width: 100%;
height: 100%;
margin: 5px;
}
.image {
opacity: 1;
display: block;
max-width: 100%;
max-height: 100%;
height: auto;
transition: .5s ease;
backface-visibility: hidden;
width: 100%;
}
.btn-content {
transition: .5s ease;
opacity: 0;
position: absolute;
top: 50%;
left: 50%;
background-color: rgba(243, 243, 243, 0.43);
border-radius: 50%;
border: solid 1px #ffffff;
transform: translate(-50%, -50%);
-ms-transform: translate(-50%, -50%)
}
.image-container:hover .image {
opacity: 0.3;
}
.image-container:hover .btn-content {
opacity: 1;
}
.close-btn {
color: #000000;
font-size: 16px;
padding: 20px 30px;
}
.close-btn:hover {
cursor: pointer;
}

@ -16,7 +16,33 @@
* under the License.
*/
import {Dispatcher} from 'flux';
.chip {
display: inline-block;
padding: 0 25px;
height: 50px;
font-size: 16px;
line-height: 50px;
border-radius: 25px;
background-color: #f1f1f1;
}
export default new Dispatcher();
.chip img {
float: left;
margin: 0 10px 0 -25px;
height: 50px;
width: 50px;
border-radius: 50%;
}
.close-btn {
padding-left: 10px;
color: #888;
font-weight: bold;
float: right;
font-size: 20px;
cursor: pointer;
}
.close-btn:hover {
color: #000;
}

@ -0,0 +1,483 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/* Body Styling */
body {
width: 100%;
font-family: Roboto sans-serif;
}
/* Login page styles*/
#userName {
border-radius: 0;
}
#password {
border-radius: 0;
}
#login-btn {
border-radius: 0;
background-color: navy;
color: white;
cursor: pointer;
}
#login-container {
width: 50%;
margin: 0 auto
}
#login-card {
border-radius: 0;
background-color: #BaBaBa;
}
/* Base layout container */
#container {
background-color: #ffffff;
padding: 0;
}
/* Base layout header content*/
#header-content {
height: 125px;
width: 100%;
margin: 0 10px 0 0;
background-color: #3b33bd;
position: fixed; /* Set the navbar to fixed position */
top: 0; /* Position the navbar at the top of the page */
z-index: 2;
box-shadow: -2px 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
}
/* Contains the header styles.*/
#header {
margin: 16px 16px 20px 16px;
height: 100%;
position: relative;
}
#header-text {
color: #ffffff;
font-size: 25px;
top: 10px;
margin-left: 10px;
}
/* The buttons in the header (User and Notification)*/
#header-btn-container {
float: right;
}
.btn-header {
margin-top: 15px;
margin-right: 20px;
color: white;
}
/* Search box styles */
.search-icon {
position: absolute;
top: 5px;
left: 5px;
}
#search-box {
display: flex;
color: #a8a8a8;
position: relative;
float: right;
top: 75px;
left: 80px;
margin-right: 16px;
}
#search {
position: relative;
color: white;
background-color: transparent;
left: 15px;
top: 0px;
height: 25px;
outline: none;
border: none;
border-radius: 0%;
}
/* Application Add button */
#add-btn-container {
position: absolute;
left: 12%;
top: 100px;
}
/* Holds the app publisher pages. */
#application-content {
height: auto;
width: 80%;
margin: 150px auto;
box-shadow: -2px 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
padding: 10px 10px 10px 10px;
}
.stepper-header {
width: 100%;
border-bottom: solid 1px cornflowerblue;
padding-bottom: 5px;
margin-bottom: 10px;
}
.platform-link-placeholder {
color: #888888;
float: right;
margin-right: 20px;
padding-bottom: 10px;
}
#application-list {
margin-top: 20px;
transition: margin-right .5s;
}
#app-image-screenshot {
width: 300px;
height: 300px;
}
#app-image-icon {
width: 300px;
height: 300px;
}
#app-image-banner {
width: 400px;
height: 300px;
}
.application-create-banner-dropzone {
width: 300px;
height: 150px;
border-radius: 5%;
position: relative;
background-color: rgba(157, 159, 157, 0.53);
border: dashed #888888 2px;
}
.application-create-banner-dropzone i {
position: absolute;
top: 65px;
left: 145px;
}
.application-create-screenshot-dropzone {
width: 150px;
height: 150px;
margin: 0 5px 0 5px;
border-radius: 10%;
position: relative;
background-color: rgba(157, 159, 157, 0.53);
border: dashed #888888 2px;
}
.application-create-screenshot-dropzone i {
position: absolute;
top: 65px;
left: 65px;
}
.application-create-icon-dropzone {
width: 150px;
height: 150px;
border-radius: 10%;
position: relative;
background-color: rgba(157, 159, 157, 0.53);
border: dashed #888888 2px;
}
.application-create-icon-dropzone i {
position: absolute;
top: 65px;
left: 65px;
}
#screenshot-container {
display: flex;
overflow-x: auto;
height: 200px;
}
#app-icon-container {
height: 300px;
overflow-x: auto;
}
#modal-body-content {
max-height: 700px;
overflow-y: auto;
}
#img-btn-screenshot {
margin: 0 5px 0 5px;
}
#app-create-modal {
max-width: 700px;
overflow-x: auto;
}
#store {
border: none;
border-bottom: solid #BDBDBD 1px;
border-radius: 0px;
width: 200px;
}
#version {
border: none;
border-bottom: solid #BDBDBD 1px;
border-radius: 0px;
width: 200px;
}
#app-release-switch-content {
display: flex;
}
#app-release-switch-label {
position: absolute;
float: left;
}
#app-release-switch-switch {
position: absolute;
right: 10px;
}
.image-sub-title {
font-style: italic;
font-size: 12px;
color: #818181;
}
/* Application View */
#application-view-content {
width: 100%;
}
#application-view-row {
margin: 10px 10px 20px 20px;
}
#app-icon {
height: 100px;
width: 100px;
border: solid 1px black;
border-radius: 50%;
}
.app-updated-date {
color: #888888;
font-style: italic;
}
.app-install-count {
font-style: italic;
}
.app-details-tbl {
outline: none;
border-color: #2196F3;
}
.app-details-tbl tr {
margin: 20px 0 0 0;
}
.app-details-tbl td {
margin-left: 10px;
max-width: 400px;
}
/* Application Edit Base Layout */
#application-edit-header {
height: 50px;
width: 100%;
margin: 0;
font-size: 20px;
border-bottom: solid 1px #d8d8d8;
}
.application-header-text {
margin: 10px 0px 0px 10px;
}
#save-btn-content {
float: right;
}
#app-save-btn {
border-radius: 0%;
}
.save-btn {
margin: 5px 5px 5px 0px;
height: 70%;
width: 50%;
float: right;
}
.save-btn:hover {
cursor: pointer;
}
/*Tab styling*/
div.tab {
float: left;
border-right: 1px solid #d8d8d8;
height: 100%;
}
/* Style the tab buttons */
div.tab button {
display: block;
background-color: inherit;
color: black;
padding: 15px 16px;
width: 100%;
border: none;
outline: none;
text-align: left;
cursor: pointer;
transition: 0.3s;
}
/* Change background color of buttons on hover */
div.tab button:hover {
background-color: #ddd6d7;
cursor: pointer;
}
/* Create an active/current "tab button" class */
div.tab button.active {
background-color: #1b3bcc;
color: white;
}
#application-edit-main-container {
display: flex;
}
#application-edit-outer-content {
height: auto;
width: 100%;
}
#application-edit-content {
margin: 5px 10px 5px 10px;
width: 90%;
}
#app-edit-content {
height: 100%;
position: relative;
}
.back-to-app {
position: absolute;
height: 40px;
width: 40px;
border-radius: 50%;
}
.back-to-app i {
padding: 10px 10px 10px 10px;
}
.back-to-app:hover {
cursor: pointer;
background-color: #dedede;
transition: .5s;
}
/* Create Release and Release management */
.release-header {
margin-top: 20px;
margin-bottom: 20px;
}
.release-create {
height: 150px;
margin-bottom: 20px;
}
.release-detail-content {
width: 100%;
margin-top: 20%;
height: 300px;
}
.form-btn {
float: right;
margin-bottom: 10px;
}
.release-content {
height: 180px;
width: 95%;
border: dashed 1px #626262;
border-radius: 2%;
position: relative;
background-color: #e8e8e8;
}
.release-content:after {
content: "";
letter-spacing: 4px;
}
.release {
margin: 30px 10px 20px 30px;
}
.no-release-content {
position: absolute;
margin-top: 10px;
left: 40%;
}
.button-add:hover {
cursor: pointer;
}
.release-inner {
margin-top: 5%;
}
/* Application Edit General Info */
.app-edit-general-info {
margin-top: 20px;
max-width: 100%;
}
.save-info {
float: right;
margin-bottom: 10px;
}

@ -0,0 +1,73 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
.app-view-drawer {
height: 100%; /* 100% Full-height */
width: 0; /* 0 width - change this with JavaScript */
position: fixed; /* Stay in place */
z-index: 1; /* Stay on top */
top: 8%;
right: 0%;
background-color: #ffffff;
overflow-x: hidden; /* Disable horizontal scroll */
padding-top: 60px; /* Place content 60px from the top */
transition: 0.5s; /* 0.5 second transition effect to slide in the sidenav */
box-shadow: -2px 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
}
.app-view-drawer a {
padding: 8px 8px 8px 32px;
text-decoration: none;
font-size: 25px;
color: #818181;
display: block;
transition: 0.3s
}
/* Position and style the close button (top right corner) */
.app-view-drawer .closebtn {
position: absolute;
top: 0;
right: 25px;
font-size: 36px;
margin-left: 50px;
}
.drawer-close-btn {
height: 40px;
width: 30px;
}
.drawer-close-btn:hover {
cursor: pointer;
color: #818181;
}
/* Style page content - use this if you want to push the page content to the right when you open the side navigation */
#main {
transition: margin-left .5s;
padding: 20px;
}
/* On smaller screens, where height is less than 450px, change the style of the sidenav (less padding and a smaller font size) */
@media screen and (max-height: 450px) {
.sidenav {padding-top: 15px;}
.sidenav a {font-size: 18px;}
}

@ -0,0 +1,50 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
.btn-circle {
color: white;
position: relative;
background-color: #e65100;
border-radius: 50%;
box-shadow: -2px 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
}
.btn-circle i {
position: absolute;
margin-top: 37%;
margin-left: 37%;
}
.small {
height: 50px;
width: 50px;
}
.medium {
height: 100px;
width: 100px;
}
.btn-circle:hover {
cursor: pointer;
background-color: rgb(255, 93, 2);
}
.primary {
background-color: blue;
}

@ -0,0 +1,49 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
.notification-app-icon {
border-radius: 50%;
border: solid 1px black;
}
.small {
height: 50px;
width: 50px;
}
.medium {
height: 100px;
width: 100px;
}
#notification-view-content {
width: 50%;
border: solid 1px black;
margin: 0 auto;
box-shadow: -2px 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
}
#notification-content {
margin: 20px 10px 20px 10px;
}
#app-reject-msg {
width: 100%;
height: 30px;
background-color: #888888;
}

@ -0,0 +1,76 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/* The switch - the box around the slider */
.switch {
position: relative;
display: inline-block;
width: 40px;
height: 24px;
}
/* Hide default HTML checkbox */
.switch input {display:none;}
/* The slider */
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
-webkit-transition: .4s;
transition: .4s;
}
.slider:before {
position: absolute;
content: "";
height: 16px;
width: 16px;
left: 4px;
bottom: 4px;
background-color: white;
-webkit-transition: .4s;
transition: .4s;
}
input:checked + .slider {
background-color: #2196F3;
}
input:focus + .slider {
box-shadow: 0 0 1px #2196F3;
}
input:checked + .slider:before {
-webkit-transform: translateX(16px);
-ms-transform: translateX(16px);
transform: translateX(16px);
}
/* Rounded sliders */
.slider.round {
border-radius: 34px;
}
.slider.round:before {
border-radius: 50%;
}

@ -16,14 +16,13 @@
* under the License.
*/
import './App.css';
import React, {Component} from 'react';
import AuthHandler from './api/authHandler';
import createHistory from 'history/createBrowserHistory';
import {BrowserRouter as Router, Redirect, Route, Switch} from 'react-router-dom'
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import getMuiTheme from 'material-ui/styles/getMuiTheme';
import {
ApplicationCreate,
ApplicationEdit,
ApplicationListing,
BaseLayout,
Login,
@ -31,20 +30,9 @@ import {
PlatformCreate,
PlatformListing
} from './components';
const history = createHistory({basename: '/publisher'});
/**
* User can define the themes in the config.json. The themes will be loaded based on the user preference.
*/
const theme = require("./config.json").theme;
let muiTheme = null;
if (theme.current === "default") {
let defaultTheme = require("material-ui/styles/baseThemes/" + theme.default);
muiTheme = getMuiTheme(defaultTheme.default);
} else {
let customTheme = require("./themes/" + theme.custom);
muiTheme = getMuiTheme(customTheme.default);
}
const history = createHistory({basename: '/publisher'});
/**
* This component defines the layout and the routes for the app.
@ -63,23 +51,34 @@ class Base extends Component {
constructor() {
super();
this.state = {
user: "admin"
user: null
}
}
componentWillMount() {
let user = AuthHandler.getUser();
if (user) {
if (!AuthHandler.isTokenExpired()) {
this.setState({user: user});
} else {
this.setState({user: null});
}
}
}
render() {
if (this.state.user) {
if (this.state.user !== null) {
return (
<div className="container">
<BaseLayout>
<div>
<BaseLayout user={this.state.user}>
<Switch>
<Redirect exact path={"/"} to={"/assets/apps"}/>
<Route exact path={"/assets/apps"} component={ApplicationListing}/>
<Route exact path={"/assets/apps/create"} component={ApplicationCreate}/>
<Route exact path={"/assets/platforms"} component={PlatformListing}/>
<Route exact path={"/assets/platforms/create"} component={PlatformCreate}/>
<Route exact path={"/assets/apps/:app"} />
<Route exact path={"/assets/apps/:app/edit"} />
{/*<Route exact path={"/assets/apps/:app"}/>*/}
<Route exact path={"/assets/apps/edit/:app"} component={ApplicationEdit}/>
<Route exact path={"/assets/platforms/:platform"}/>
<Route exact path={"/assets/platforms/:platform/edit"}/>
<Route exact path={"/assets/reviews"}/>
@ -89,8 +88,10 @@ class Base extends Component {
</BaseLayout>
</div>
)
} else {
return (<Redirect to={"/login"}/>)
}
return (<Redirect to={"/login"}/>)
}
}
@ -101,10 +102,18 @@ class Base extends Component {
*
* */
class Publisher extends Component {
constructor() {
super();
this.state = {
muiTheme: null,
selectedType: null,
selectedTheme: null
};
}
render() {
return (
<div className="App">
<MuiThemeProvider muiTheme={muiTheme}>
<Router basename="publisher" history={history}>
<Switch>
<Route path="/login" component={Login}/>
@ -112,7 +121,6 @@ class Publisher extends Component {
<Route component={Base}/>
</Switch>
</Router>
</MuiThemeProvider>
</div>
);
}

@ -1,17 +0,0 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

@ -1,17 +0,0 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

@ -1,17 +0,0 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

@ -0,0 +1,138 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
'use strict';
import Axios from 'axios';
import AuthHandler from './authHandler';
import Constants from '../common/constants';
import Helper from './helpers/appMgtApiHelpers';
/**
* Api definitions related to application management.
* TODO: Work to be done on Application release.
* */
export default class ApplicationMgtApi {
/**
* Api for create an application.
* @param: applicationData: The application data object. This contains an object array of each step data from
* application creation wizard.
*
* From applicationData, the proper application object will be created and send it to the api.
* */
static createApplication(applicationData) {
let {application, images} = Helper.buildApplication(applicationData);
const headers = AuthHandler.createAuthenticationHeaders("application/json");
console.log(application);
console.log(images);
Axios.post(Constants.appManagerEndpoints.CREATE_APP, application, {headers: headers});
}
/**
* Upload the image artifacts (banner, icon, screenshots) related to the application.
* @param appId: The application uuid of the application which the images should be uploaded to.
* @param images: The images object. This contains icon, banner and screenshots.
* */
static uploadImageArtifacts(appId, images) {
let formData = new FormData();
formData.append('icon', images.icon);
formData.append('banner', images.banner);
formData.append('screenshot', images.screenshots);
console.log("Image", formData);
const headers = AuthHandler.createAuthenticationHeaders("multipart/form-data");
return Axios.post(Constants.appManagerEndpoints.UPLOAD_IMAGE_ARTIFACTS + appId, formData, {headers: headers});
}
/**
* Method to handle application release process.
* */
static releaseApplication(appId) {
}
/**
* Promote the current life cycle state of the application.
* @param appId: The uuid of the application which the state should be updated.
* @param nextState: The next lifecycle state that the application can be updated to.
*
* URL Pattern : /application/1.0/
* */
static updateLifeCycleState(appId, nextState) {
}
/**
* Get the next possible state, which the application can be promoted to.
* @param appId: The application uuid.
*/
static getNextLifeCycleState(appId) {
}
/**
* Edit created application.
* @param applicationData: The modified application data.
* */
static editApplication(applicationData) {
let app = Helper.buildApplication(applicationData).application;
const headers = AuthHandler.createAuthenticationHeaders("application/json");
return Axios.put(Constants.appManagerEndpoints.CREATE_APP, app, {headers: headers});
}
static getApplicationArtifacts(appId, artifactName) {
const headers = AuthHandler.createAuthenticationHeaders("image/png");
return Axios.get(Constants.appManagerEndpoints.GET_IMAGE_ARTIFACTS + appId + "?name=" + artifactName,
{headers: headers});
}
static editApplicationArtifacts(appId, images) {
let formData = new FormData();
formData.append('icon', images.icon);
formData.append('banner', images.banner);
formData.append('screenshot', images.screenshots);
const headers = AuthHandler.createAuthenticationHeaders("application/json");
return Axios.put(Constants.appManagerEndpoints.UPLOAD_IMAGE_ARTIFACTS + appId, formData, {headers: headers});
}
/**
* Get all the created applications for the user.
* @return Object: The response object from the axios post.
* */
static getApplications() {
const headers = AuthHandler.createAuthenticationHeaders("application/json");
return Axios.get(Constants.appManagerEndpoints.GET_ALL_APPS, {headers: headers});
}
/**
* Get specific application.
* @param appId: The application Id.
* */
static getApplication(appId) {
const headers = AuthHandler.createAuthenticationHeaders("application/json");
return Axios.get(Constants.appManagerEndpoints.GET_ALL_APPS + appId, {headers: headers});
}
/**
* Delete specified application.
* @param appId: The id of the application which is to be deleted.
* */
static deleteApplication(appId) {
const headers = AuthHandler.createAuthenticationHeaders("application/json");
return Axios.delete(Constants.appManagerEndpoints.GET_ALL_APPS + appId, {headers: headers});
}
}

@ -0,0 +1,151 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
'use strict';
import Axios from 'axios';
import User from './data/user';
import Utils from './data/utils';
import Constants from "../common/constants";
/**
* Handles all tasks related to Authentication and Authorization.
* Generate access tokens, verify the user has necessary permissions etc.
* */
class AuthHandler {
/**
* Sends a request to the auth handler endpoint (auth/application-mgt/v1.0/auth/login) and generate token pair.
* @param userName: The user name of the user.
* @param password: The user password.
* @return Object: The response object from the axios post.
* */
static login(userName, password) {
const headers = {"Content-type": "application/json"};
let login_promise =
Axios.post(Constants.userConstants.LOGIN_URL+"?userName=" + userName+ "&password=" + password,
null, {headers: headers});
login_promise.then(response => {
console.log(response);
const userName = response.data.userName;
const validityPeriod = response.data.expires_in; // In seconds
const WSO2_IOT_TOKEN = response.data.access_token;
const refreshToken = response.data.refresh_token;
const clientId = response.data.application_info[0].consumerKey;
const clientSecret = response.data.application_info[0].consumerSecret;
const user = new User(userName, clientId, clientSecret, validityPeriod);
console.log(user);
user.setAuthToken(WSO2_IOT_TOKEN, validityPeriod);
let expiresIn = Date.now() + (validityPeriod * 1000);
localStorage.setItem("expiresIn", expiresIn);
AuthHandler.setUser(user);
}
);
return login_promise;
};
/**
* Persists the user object in browser's local storage.
* @param user: The user object.
* */
static setUser(user) {
if (!user instanceof User) {
throw "Invalid user object";
}
user.created = Date.now();
localStorage.setItem(Constants.userConstants.WSO2_USER, JSON.stringify(user.toJson()));
/* TODO: IMHO it's better to get this key (`wso2_user`) from configs */
}
static unauthorizedErrorHandler(error_response) {
if (error_response.status !== 401) { /* Skip unrelated response code to handle in unauthorizedErrorHandler*/
throw error_response;
/* re throwing the error since we don't handle it here and propagate to downstream error handlers in catch chain*/
}
let message = "The session has expired" + ".<br/> You will be redirect to the login page ...";
if (true) {
alert(message);
} else {
throw error_response;
}
}
/**
* Get the logged in user.
* @return User: The logged in user object.
* */
static getUser() {
const userData = localStorage.getItem(Constants.userConstants.WSO2_USER);
const partialToken = Utils.getCookie(Constants.userConstants.PARTIAL_TOKEN);
if (!(userData && partialToken)) {
return null;
}
return User.fromJson(JSON.parse(userData));
}
isLoggedIn() {
}
static logout() {
const user = AuthHandler.getUser();
const clientId = user.getClientId();
const clientSecret = user.getClientSecret();
const token = user.getAuthToken();
const headers = {"Content-type": "application/json"};
let login_promise = Axios.post(Constants.userConstants.LOGOUT_URL+"?token=" + token + "&clientId=" + clientId
+ "&clientSecret=" + clientSecret,
null, {headers: headers});
login_promise.then(
(response) => {
Utils.delete_cookie(Constants.userConstants.PARTIAL_TOKEN);
localStorage.removeItem(Constants.userConstants.WSO2_USER);
window.location = "/";
}
).catch(
(err) => {
AuthHandler.unauthorizedErrorHandler(err);
}
)
}
/**
* Checks whether the access token is expired.
* @return boolean: True if expired. False otherwise.
* */
static isTokenExpired() {
const expiresIn = localStorage.getItem("expiresIn");
return (expiresIn < Date.now());
}
static createAuthenticationHeaders(contentType) {
if (AuthHandler.getUser().getAuthToken()) {
return {
"Authorization": "Bearer " + AuthHandler.getUser().getAuthToken(),
"Content-Type": contentType,
};
}
return "User not found";
};
}
export default AuthHandler;

@ -0,0 +1,118 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
"use strict";
import Utils from './utils'
import Constants from '../../common/constants';
/**
* Represent an user logged in to the application, There will be allays one user per session and
* this user details will be persist in browser localstorage.
*/
export default class User {
constructor(name, clientId, clientSecret, validityPeriod) {
if (User._instance) {
return User._instance;
}
this._userName = name;
this._clientId = clientId;
this._clientSecret = clientSecret;
this._expires = validityPeriod;
User._instance = this;
}
/**
* OAuth scopes which are available for use by this user
* @returns {Array} : An array of scopes
*/
get scopes() {
return this._scopes;
}
/**
* Set OAuth scopes available to be used by this user
* @param {Array} newScopes : An array of scopes
*/
set scopes(newScopes) {
Object.assign(this.scopes, newScopes);
}
/**
* Get the JS accessible access token fragment from cookie storage.
* @returns {String|null}
*/
getAuthToken() {
return Utils.getCookie(Constants.userConstants.PARTIAL_TOKEN);
}
getClientId() {
return this._clientId;
}
getClientSecret() {
return this._clientSecret;
}
/**
* Store the JavaScript accessible access token segment in cookie storage
* @param {String} newToken : Part of the access token which needs when accessing REST API
* @param {Number} validityPeriod : Validity period of the cookie in seconds
*/
setAuthToken(newToken, validityPeriod) {
Utils.delete_cookie(Constants.userConstants.PARTIAL_TOKEN);
Utils.setCookie(Constants.userConstants.PARTIAL_TOKEN, newToken, validityPeriod);
}
/**
* Get the user name of logged in user.
* @return String: User name
* */
getUserName() {
return this._userName;
}
/**
* Provide user data in JSON structure.
* @returns {JSON} : JSON representation of the user object
*/
toJson() {
return {
name: this._userName,
clientId: this._clientId,
clientSecret: this._clientSecret,
expires: this._expires
};
}
/**
* User utility method to create an user from JSON object.
* @param {JSON} userJson : Need to provide user information in JSON structure to create an user object
* @returns {User} : An instance of User(this) class.
*/
static fromJson(userJson) {
const _user = new User(userJson.name);
_user._clientId = userJson.clientId;
_user._clientSecret = userJson.clientSecret;
_user._expires = userJson.expires;
console.log(_user);
return _user;
}
}
User._instance = null; // A private class variable to preserve the single instance of a swaggerClient

@ -0,0 +1,92 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/**
* Utility class for Publisher application
*/
class PublisherUtils {
/**
* TODO: Remove this method one the initial phase is done, This is used to continue the API class until the login page is create
* @returns {promise}
*/
// static autoLogin() {
// let auth = new AuthManager();
// return auth.authenticateUser('admin', 'admin');
// }
/**
* Get JavaScript accessible cookies saved in browser, by giving the cooke name.
* @param {String} name : Name of the cookie which need to be retrived
* @returns {String|null} : If found a cookie with given name , return its value,Else null value is returned
*/
static getCookie(name) {
let pairs = document.cookie.split(";");
let cookie = null;
for (let pair of pairs) {
pair = pair.split("=");
let cookie_name = pair[0].trim();
let value = encodeURIComponent(pair[1]);
if (cookie_name === name) {
cookie = value;
break;
}
}
return cookie;
}
/**
* Delete a browser cookie given its name
* @param {String} name : Name of the cookie which need to be deleted
*/
static delete_cookie(name) {
document.cookie = name + '=; Path=' + "/" + '; Expires=Thu, 01 Jan 1970 00:00:01 GMT;';
}
/**
* Set a cookie with given name and value assigned to it. Cookies can be only set to the same origin,
* which the script is running
* @param {String} name : Name of the cookie which need to be set
* @param {String} value : Value of the cookie, expect it to be URLEncoded
* @param {number} validityPeriod : (Optional) Validity period of the cookie in seconds
* @param {String} path : Path which needs to set the given cookie
* @param {boolean} secured : secured parameter is set
*/
static setCookie(name, value, validityPeriod, path = "/", secured = true) {
let expires = "";
const securedDirective = secured ? "; Secure" : "";
if (validityPeriod) {
const date = new Date();
date.setTime(date.getTime() + validityPeriod * 1000);
expires = "; expires=" + date.toUTCString();
}
document.cookie = name + "=" + value + expires + "; path=" + path + securedDirective + validityPeriod
}
/**
* Given an object returns whether the object is empty or not
* @param {Object} object : Any JSON object
* @returns {boolean}
*/
static isEmptyObject(object) {
return Object.keys(object).length === 0 && object.constructor === Object
}
}
export default PublisherUtils;

@ -0,0 +1,64 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
'use strict';
/**
* Helper methods for app publisher.
* */
export default class Helper {
/**
* Generate application object from form data passed.
* @param appData: Application data from the application creation form.
* @return {Object, Object}: The application object and the set of images related to the application.
* */
static buildApplication(appData) {
let application = {};
let images = {};
for (let step in appData) {
let tmpData = appData[step].data.step;
for (let prop in tmpData) {
if (prop === 'banner' || prop === 'screenshots' || prop === 'icon') {
images[prop] = tmpData[prop];
} else if(prop === 'tags') {
application[prop] = Helper.stringifyTags(tmpData[prop]);
} else {
application[prop] = tmpData[prop];
}
}
}
return {application, images};
}
/**
* Creates a String array from tags array.
* */
static stringifyTags(tags) {
let tmpTags = [];
for (let tag in tags) {
console.log(tag);
tmpTags.push(tags[tag].value);
}
return tmpTags;
}
}

@ -0,0 +1,68 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
'use strict';
import Axios from 'axios';
import AuthHandler from './authHandler';
import Constants from '../common/constants';
/**
* Api definitions for Platform management.
* */
export default class PlatformMgtApi{
/**
* Create a new Platform
* @param platformData: The platform data object.
* */
static createPlatform(platformData) {
const headers = AuthHandler.createAuthenticationHeaders("application/json");
Axios.post(Constants.platformManagerEndpoints.CREATE_PLATFORM, platformData, {headers: headers}).then(
function (response) {
console.log(response);
}
).catch(function (err) {
console.log(err);
});
}
/**
* Get available platforms
* */
static getPlatforms() {
const headers = AuthHandler.createAuthenticationHeaders("application/json");
return Axios.get(Constants.platformManagerEndpoints.GET_ENABLED_PLATFORMS, {headers: headers});
}
/**
* Get the user specified platform
* @param platformId: The identifier of the platform
* */
static getPlatform(platformId) {
const headers = AuthHandler.createAuthenticationHeaders("application/json");
return Axios.get(Constants.platformManagerEndpoints.GET_PLATFORM + platformId, {headers: headers});
}
/**
* Delete specified platform
* @param platformId: The id of the platform which is to be deleted.
* */
static deletePlatform(platformId) {
const headers = AuthHandler.createAuthenticationHeaders("application/json");
return Axios.delete(Constants.platformManagerEndpoints.GET_PLATFORM + platformId, {headers: headers});
}
}

@ -15,3 +15,33 @@
* specific language governing permissions and limitations
* under the License.
*/
'use strict';
//TODO: Replace the server address with response from auth endpoint and remove hardcoded ids etc.
export default class Constants {
static scopes = 'perm:application:get perm:application:create perm:application:update perm:application-mgt:login' +
' perm:application:delete perm:platform:add perm:platform:remove perm:roles:view perm:devices:view';
static appManagerEndpoints = {
GET_ALL_APPS: 'https://localhost:8243/api/application-mgt/v1.0/applications/1.0.0/',
CREATE_APP: 'https://localhost:8243/api/application-mgt/v1.0/applications/1.0.0/',
UPLOAD_IMAGE_ARTIFACTS: 'https://localhost:8243/api/application-mgt/v1.0/applications/1.0.0/upload-image-artifacts/', //+appId
GET_IMAGE_ARTIFACTS: "https://localhost:8243/api/application-mgt/v1.0/applications/1.0.0/image-artifacts/"
};
static platformManagerEndpoints = {
CREATE_PLATFORM: 'https://localhost:8243/api/application-mgt/v1.0/platforms/1.0.0',
GET_ENABLED_PLATFORMS: 'https://localhost:8243/api/application-mgt/v1.0/platforms/1.0.0?status=ENABLED',
GET_PLATFORM: 'https://localhost:8243/api/application-mgt/v1.0/platforms/1.0.0/'
};
static userConstants = {
LOGIN_URL:"https://localhost:9443/auth/application-mgt/v1.0/auth/login",
LOGOUT_URL: "https://localhost:9443/auth/application-mgt/v1.0/auth/logout",
REFRESH_TOKEN_URL: "",
WSO2_USER: 'wso2_user',
PARTIAL_TOKEN: 'WSO2_IOT_TOKEN'
}
}

@ -0,0 +1,119 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import PropTypes from 'prop-types';
import React, {Component} from 'react';
import {withRouter} from 'react-router-dom';
import AuthHandler from "../../api/authHandler";
import ApplicationCreate from '../Application/Create/ApplicationCreate';
import {Col, Container, Input, Row,} from 'reactstrap';
import FloatingButton from "../UIComponents/FloatingButton/FloatingButton";
/**
* Base Layout:
* App bar
* Left Navigation
* Middle content.
* */
class BaseLayout extends Component {
constructor() {
super();
this.state = {
notifications: 0,
user: 'Admin',
openModal: false
};
this.logout = this.logout.bind(this);
this.closeModal = this.closeModal.bind(this);
}
handleApplicationClick() {
this.handleHistory('/assets/apps');
}
handleApplicationCreateClick(event) {
event.preventDefault();
event.stopPropagation();
this.setState({openModal: true});
}
/**
* The method to update the history.
* to: The URL to route.
* */
handleHistory(to) {
this.props.history.push(to);
}
logout(event, index, value) {
AuthHandler.logout();
}
closeModal() {
this.setState({openModal: false});
}
render() {
return (
<Container noGutters fluid id="container">
<div id="header-content">
<div id="header">
<span id="header-text">
WSO2 IoT App Publisher
</span>
<div id="header-btn-container">
<i className="fw fw-notification btn-header"></i>
<i className="fw fw-user btn-header"></i>
</div>
<div id="search-box">
<i className="fw fw-search search-icon">
</i>
<Input
id="search"
name="search"
placeholder={'Search for Applications'}
onChange={(event) => console.log(event.target.value)} //TODO: Remove this
/>
</div>
</div>
<div id="add-btn-container">
<FloatingButton
className="add-btn small"
onClick={this.handleApplicationCreateClick.bind(this)}
/>
</div>
</div>
<div id="application-content" style={this.state.style}>
<Row>
<Col>
{this.props.children}
</Col>
</Row>
</div>
<ApplicationCreate open={this.state.openModal} close={this.closeModal}/>
</Container>
);
}
}
BaseLayout.propTypes = {
children: PropTypes.element
};
export default withRouter(BaseLayout);

@ -1,234 +0,0 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React, {Component} from 'react';
import Dialog from 'material-ui/Dialog';
import {withRouter} from 'react-router-dom';
import FlatButton from 'material-ui/FlatButton';
import {Step1, Step2, Step3} from './CreateSteps';
import RaisedButton from 'material-ui/RaisedButton';
import {Card, CardActions, CardTitle} from 'material-ui/Card';
import {Step, StepLabel, Stepper,} from 'material-ui/Stepper';
/**
* The App Create Component.
*
* Application creation is handled through a Wizard. (We use Material UI Stepper.)
*
* In each step, data will be set to the state separately.
* When the wizard is completed, data will be arranged and sent to the api.
* */
class ApplicationCreate extends Component {
constructor() {
super();
this.setStepData.bind(this);
this.removeStepData.bind(this);
this.handleSubmit.bind(this);
this.handleCancel.bind(this);
this.handleYes.bind(this);
this.handleNo.bind(this);
this.state = {
finished: false,
stepIndex: 0,
stepData: [],
isDialogOpen: false
};
}
/**
* Handles next button click event.
* */
handleNext = () => {
console.log("Handle Next");
const {stepIndex} = this.state;
this.setState({
stepIndex: stepIndex + 1,
finished: stepIndex >= 2,
});
};
/**
* Handles form submit.
* */
handleSubmit = () => {
console.log(this.state.stepData);
};
/**
* Handles cancel button click event.
* This will show a confirmation dialog to cancel the application creation process.
* */
handleCancel = () => {
this.setState({isDialogOpen: true});
};
/**
* Handled [ < Prev ] button click.
* This clears the data in the current step and returns to the previous step.
* */
handlePrev = () => {
const {stepIndex} = this.state;
if (stepIndex > 0) {
this.removeStepData();
this.setState({stepIndex: stepIndex - 1});
}
};
/**
* Saves form data in each step in to the state.
* */
setStepData = (step, data) => {
console.log(step, data, this.state.stepData);
let tmpStepData = this.state.stepData;
tmpStepData.push({step: step, data: data});
this.setState({stepData: tmpStepData}, this.handleNext())
};
/**
* Remove the last data point
* */
removeStepData = () => {
let tempData = this.state.stepData;
tempData.pop();
this.setState({stepData: tempData});
};
/**
* Handles the Yes button in app creation cancellation dialog.
* Clears all the form data and reset the wizard.
* */
handleYes = () => {
this.setState({finished: false, stepIndex: 0, stepData: [], isDialogOpen: false});
};
/**
* Handles No button in app creation cancellation dialog.
* Returns to the same step.
* */
handleNo = () => {
this.setState({isDialogOpen: false});
};
/**
* Defines all the Steps in the stepper. (Wizard)
*
* Extension Point: If any extra steps needed, follow the instructions below.
* 1. Create the required form ./Forms directory.
* 2. Add defined case statements.
* 3. Define the Step in render function.
*
* */
getStepContent(stepIndex) {
switch (stepIndex) {
case 0:
return <Step1 handleNext={this.handleNext}
setData={this.setStepData}
removeData={this.removeStepData}/>;
case 1:
return <Step2 handleNext={this.handleNext}
handlePrev={this.handlePrev}
setData={this.setStepData}
removeData={this.removeStepData}/>;
case 2:
return <Step3 handleFinish={this.handleNext}
handlePrev={this.handlePrev}
setData={this.setStepData}
removeData={this.removeStepData}/>;
default:
return 'You\'re a long way from home sonny jim!';
}
}
render() {
const {finished, stepIndex} = this.state;
const contentStyle = {margin: '0 16px'};
/**
* Defines the dialog box actions. [Yes][No]
* */
const actions = [
<FlatButton
label="Yes"
primary={true}
onClick={this.handleYes}
/>,
<FlatButton
label="No"
secondary={true}
onClick={this.handleNo}
/>,
];
return (
<div className="middle" style={{width: '95%', height: '100%', marginTop: '1%'}}>
<Card style={{maxHeight: '700px', overflow: 'auto'}}>
<CardTitle title="Create Application"/>
{/**
* The stepper goes here.
*/}
<CardActions>
<div style={{width: '100%', margin: 'auto'}}>
<Stepper activeStep={stepIndex}>
<Step>
<StepLabel>Select Application Platform</StepLabel>
</Step>
<Step>
<StepLabel>Enter Application Details</StepLabel>
</Step>
<Step>
<StepLabel>Release</StepLabel>
</Step>
</Stepper>
<div style={contentStyle}>
{finished ? (
<div>
<p>Create App?</p>
<form>
<RaisedButton primary={true} label="Create" onClick={this.handleSubmit}/>
<FlatButton label="Cancel" onClick={this.handleCancel}/>
</form>
</div>
) : (
<div>
{this.getStepContent(stepIndex)}
</div>
)}
</div>
</div>
</CardActions>
</Card>
<Dialog
actions={actions}
modal={false}
open={this.state.isDialogOpen}
onRequestClose={this.handleNo}
>
Do you really want to cancel?
</Dialog>
</div>);
}
}
ApplicationCreate.propTypes = {};
export default withRouter(ApplicationCreate);

@ -18,9 +18,9 @@
import React, {Component} from 'react';
import {withRouter} from 'react-router-dom';
import TextField from 'material-ui/TextField';
import DataTable from '../UIComponents/DataTable';
import {Card, CardActions, CardTitle} from 'material-ui/Card';
import {Button, Col, Row, Table} from 'reactstrap';
import Drawer from '../UIComponents/Drawer/Drawer';
import ApplicationView from './View/ApplicationView';
/**
* The App Create Component.
@ -33,68 +33,49 @@ import {Card, CardActions, CardTitle} from 'material-ui/Card';
class ApplicationListing extends Component {
constructor() {
super();
this.searchApplications = this.searchApplications.bind(this);
this.onRowClick = this.onRowClick.bind(this);
this.setData = this.setData.bind(this);
this.sortData = this.sortData.bind(this);
this.compare = this.compare.bind(this);
this.handleButtonClick = this.handleButtonClick.bind(this);
this.state = {
data: [],
asc: true
}
searchedApplications: [],
applications: [],
asc: true,
open: false,
application: {},
drawer: {},
appListStyle: {},
//TODO: Remove this declaration.
image: [{id: "1", src: "https://www.greenfoot.org/images/logos/macos.png"},
{
id: "2",
src: "http://dl1.cbsistatic.com/i/r/2016/08/08/0e67e43a-5a45-41ab-b81d-acfba8708044/resize/736x552/0c0ee669677b5060a0fa1bfb0c7873b4/android-logo-promo-470.png"
}]
};
}
data = [
{
id: Math.random(),
applicationName:"Cne",
platform:'Android',
category:"Public",
status: "Created"
},
{
id: Math.random(),
applicationName:"Gone",
platform:'IOS',
category:"Public",
status: "Created"
},
{
id: Math.random(),
applicationName:"Ane",
platform:'Android',
category:"Public",
status: "Created"
},
{
id: Math.random(),
applicationName:"one",
platform:'Android',
category:"Public",
status: "Created"
},
{
id: Math.random(),
applicationName:"one",
platform:'Android',
category:"Public",
status: "Created"
},
];
headers = [
{
data_id: "image",
data_type: "image",
sortable: false,
label: ""},
label: ""
},
{
data_id: "applicationName",
data_type: "string",
sortable: true,
label: "Application Name",
sort: this._sortData.bind(this)
sort: this.sortData
},
{
data_id: "platform",
data_type: "image_array",
sortable: false,
label: "Platform"},
label: "Platform"
},
{
data_id: "category",
data_type: "string",
@ -106,43 +87,105 @@ class ApplicationListing extends Component {
data_type: "string",
sortable: false,
label: "Status"
},
{
data_id: "edit",
data_type: "button",
sortable: false,
label: ""
}
];
applications = [
{
id: "3242342ffww3423",
applicationName: "Facebook",
platform: "android",
category: "Business",
status: "Published"
},
{
icon: "http://dl1.cbsistatic.com/i/r/2016/08/08/0e67e43a-5a45-41ab-b81d-acfba8708044/resize/736x552/0c0ee669677b5060a0fa1bfb0c7873b4/android-logo-promo-470.png",
id: "324234233423423",
applicationName: "Twitter",
platform: "android",
category: "Business",
status: "Created"
},
{
icon: "https://www.greenfoot.org/images/logos/macos.png",
id: "3242d3423423423",
applicationName: "Massenger",
platform: "android",
category: "Business",
status: "In Review"
},
];
componentWillMount() {
//Fetch all the applications from backend and create application objects.
this.setState({data: this.data});
// let getApps = ApplicationMgtApi.getApplications();
// getApps.then(response => {
// let apps = this.setData(response.data.applications);
// console.log(apps); //TODO: Remove this.
// this.setState({searchedApplications: apps});
// // console.log(this.setState({data: response.data}), console.log(this.state));
// }).catch(err => {
// AuthHandler.unauthorizedErrorHandler(err);
// });
}
/**
* Extract application from application list and update the state.
* */
setData(applications) {
let apps = [];
for (let app in applications) {
let application = {};
application.id = applications[app].uuid;
application.applicationName = applications[app].name;
application.platform = applications[app].platform.name;
application.category = applications[app].category.id;
application.status = applications[app].currentLifecycle.lifecycleState.name;
apps.push(application);
}
this.setState({searchedApplications: apps});
}
/**
* Handles the search action.
* When typing in the search bar, this method will be invoked.
* @param event: The event triggered from typing in the search box.
* @param searchText: The text that typed in the search box.
* */
_searchApplications(event, word) {
searchApplications(event, searchText) {
let searchedData;
if (word){
searchedData = this.data.filter((dataItem) => {
return dataItem.applicationName.includes(word);
});
} else {
searchedData = this.data;
}
this.setState({data: searchedData}, console.log("Searched data ", this.state.data));
if (searchText) {
searchedData = this.state.applications.filter((dataItem) => {
return dataItem.applicationName.includes(searchText);
});
} else {
searchedData = this.state.applications;
}
//TODO: Remove the console log.
this.setState({searchedApplications: searchedData}, console.log("Searched data ", this.state.searchedApplications));
}
/**
* Handles sort data function and toggles the asc state.
* asc: true : sort in ascending order.
* */
_sortData() {
sortData() {
console.log(this.state);
let isAsc = this.state.asc;
let datas = isAsc?this.data.sort(this._compare):this.data.reverse();
this.setState({data: datas, asc: !isAsc});
let sortedData = isAsc ? this.state.searchedApplications.sort(this.compare) : this.data.reverse();
this.setState({searchedApplications: sortedData, asc: !isAsc});
}
_compare(a, b) {
compare(a, b) {
if (a.applicationName < b.applicationName)
return -1;
if (a.applicationName > b.applicationName)
@ -150,28 +193,112 @@ class ApplicationListing extends Component {
return 0;
}
_onRowClick(id) {
this.props.history.push("apps/"+id);
onRowClick() {
let style = {
width: '500px',
marginLeft: '500px'
};
let appListStyle = {
marginRight: '500px',
};
this.setState({drawer: style, appListStyle: appListStyle});
}
handleButtonClick() {
console.log("Application Listing");
this.props.history.push("apps/edit/fdsfdsf343");
}
remove(imageId) {
let tmp = this.state.image;
console.log(imageId);
let rem = tmp.filter((image) => {
return image.id !== imageId
});
this.setState({image: rem});
}
closeDrawer() {
let style = {
width: '0',
marginLeft: '0'
};
let appListStyle = {
marginRight: '0',
};
this.setState({drawer: style, appListStyle: appListStyle});
}
render() {
return (
<div className="middle" style={{width: '95%', height: '100%', marginTop: '1%'}}>
<Card style={{display: 'flex', flexWrap: 'wrap'}}>
<TextField hintText="Search"
style={{float:'right', paddingRight: '2px'}}
onChange={this._searchApplications.bind(this)}/>
<CardTitle title="Applications" style={{display: 'flex', flexWrap: 'wrap'}}/>
<CardActions>
</CardActions>
<DataTable headers={this.headers}
data={this.state.data}
handleRowClick={this._onRowClick.bind(this)}
noDataMessage={{type: 'button', text: 'Create Application'}}/>
</Card>
</div>);
<div id="application-list" style={this.state.appListStyle}>
<Row>
<Col xs="3 offset-9">
<div className="platform-link-placeholder">
<Button><i className="fw fw-settings"></i> Platforms</Button>
</div>
</Col>
</Row>
<Row>
<Col>
<Table striped hover>
<thead>
<tr>
<th></th>
{/* TODO: Remove console.log and add sort method. */}
<th onClick={() => {
console.log("sort")
}}>Application Name
</th>
<th>Category</th>
<th>Platform</th>
<th>Status</th>
<th></th>
</tr>
</thead>
<tbody>
{this.applications.map(
(application) => {
return (
<tr key={application.id} onClick={this.onRowClick}>
<td>
{/* TODO: Move this styles to css. */}
<img
src={application.icon}
height='50px'
width='50px'
style={{border: 'solid 1px black', borderRadius: "100%"}}
/>
</td>
<td>{application.applicationName}</td>
<td>{application.category}</td>
<td>{application.platform}</td>
<td>{application.status}</td>
<td>
<Button onClick={this.handleButtonClick}>
<i className="fw fw-edit"></i>
</Button>
</td>
</tr>
)
}
)}
</tbody>
</Table>
</Col>
</Row>
<Drawer onClose={this.closeDrawer.bind(this)} style={this.state.drawer}>
<ApplicationView/>
</Drawer>
</div>
);
}
}

@ -0,0 +1,243 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React, {Component} from 'react';
import {withRouter} from 'react-router-dom';
import AuthHandler from "../../../api/authHandler";
import {Step1, Step2, Step3, Step4} from './CreateSteps/index';
import ApplicationMgtApi from '../../../api/applicationMgtApi';
import {Button, Col, Modal, ModalBody, ModalFooter, ModalHeader, Row} from 'reactstrap';
/**
* The App Create Component.
*
* Application creation is handled through a Wizard. (We use Material UI Stepper.)
*
* In each step, data will be set to the state separately.
* When the wizard is completed, data will be arranged and sent to the api.
* */
class ApplicationCreate extends Component {
constructor() {
super();
this.scriptId = "application-create";
this.setStepData = this.setStepData.bind(this);
this.removeStepData = this.removeStepData.bind(this);
this.onSubmit = this.onSubmit.bind(this);
this.handleCancel = this.handleCancel.bind(this);
this.handleYes = this.handleYes.bind(this);
this.handleNo = this.handleNo.bind(this);
this.onPrevClick = this.onPrevClick.bind(this);
this.onNextClick = this.onNextClick.bind(this);
this.onClose = this.onClose.bind(this);
this.state = {
finished: false,
stepIndex: 0,
stepData: [],
isDialogOpen: false
};
}
componentWillReceiveProps(props, nextprops) {
this.setState({open: props.open})
}
componentWillMount() {
this.setState({open: this.props.open});
}
onClose() {
this.setState({stepIndex: 0}, this.props.close());
}
/**
* Handles next button click event.
* */
onNextClick() {
console.log("Handle Next"); //TODO: Remove this
const {stepIndex} = this.state;
this.setState({
stepIndex: stepIndex + 1,
finished: stepIndex >= 2,
});
};
/**
* Handles form submit.
* */
onSubmit() {
let stepData = this.state.stepData;
let applicationCreationPromise = ApplicationMgtApi.createApplication(stepData);
applicationCreationPromise.then(response => {
this.handleYes();
}
).catch(
function (err) {
AuthHandler.unauthorizedErrorHandler(err);
}
);
};
/**
* Handles cancel button click event.
* This will show a confirmation dialog to cancel the application creation process.
* */
handleCancel() {
this.setState({isDialogOpen: true});
};
/**
* Handled [ < Prev ] button click.
* This clears the data in the current step and returns to the previous step.
* */
onPrevClick() {
const {stepIndex} = this.state;
if (stepIndex > 0) {
this.removeStepData();
this.setState({stepIndex: stepIndex - 1});
}
};
/**
* Saves form data in each step in to the state.
* @param step: The step number of the step data.
* @param data: The form data of the step.
* */
setStepData(step, data) {
console.log(step, data, this.state.stepData); //TODO: Remove this
let tmpStepData = this.state.stepData;
tmpStepData.push({step: step, data: data});
this.setState({stepData: tmpStepData}, this.onNextClick())
};
/**
* Remove the last data point
* */
removeStepData() {
let tempData = this.state.stepData;
tempData.pop();
this.setState({stepData: tempData});
};
/**
* Handles the Yes button in app creation cancellation dialog.
* Clears all the form data and reset the wizard.
* */
handleYes() {
this.setState({finished: false, stepIndex: 0, stepData: [], isDialogOpen: false});
};
/**
* Handles No button in app creation cancellation dialog.
* Returns to the same step.
* */
handleNo() {
this.setState({isDialogOpen: false});
};
/**
* Defines all the Steps in the stepper. (Wizard)
*
* Extension Point: If any extra steps needed, follow the instructions below.
* 1. Create the required form ./Forms directory.
* 2. Add defined case statements.
* 3. Define the Step in render function.
*
* */
getStepContent(stepIndex) {
switch (stepIndex) {
case 0:
return (
<Step1
handleNext={this.onNextClick}
setData={this.setStepData}
removeData={this.removeStepData}
/>
);
case 1:
return (
<Step2
handleNext={this.onNextClick}
handlePrev={this.onPrevClick}
setData={this.setStepData}
removeData={this.removeStepData}
/>
);
case 2:
return (
<Step3
handleFinish={this.onNextClick}
handlePrev={this.onPrevClick}
setData={this.setStepData}
removeData={this.removeStepData}
/>
);
case 3: {
return (
<Step4
handleNext={this.onNextClick}
setData={this.setStepData}
removeData={this.removeStepData}
/>
)
}
default:
return <div/>;
}
}
render() {
const {finished, stepIndex} = this.state;
return (
<div id="create-application-modal">
<Modal isOpen={this.state.open} toggle={this.toggle} id="app-create-modal"
backdrop={'static'}>
<ModalHeader toggle={this.toggle}>Create Application</ModalHeader>
<ModalBody id="modal-body-content">
<Row>
<Col>
<div className="stepper-header">
</div>
</Col>
</Row>
<Row>
<Col>
{this.getStepContent(stepIndex)}
</Col>
</Row>
</ModalBody>
<ModalFooter>
{stepIndex === 0 ? <div/> :
<Button color="primary" onClick={this.onPrevClick}>Back</Button>}
<Button color="secondary" onClick={this.onClose}>Cancel</Button>
{finished ?
<Button color="primary" onClick={this.onSubmit}>Finish</Button> :
<Button color="primary" onClick={this.onNextClick}>Continue</Button>}
</ModalFooter>
</Modal>
</div>);
}
}
ApplicationCreate.propTypes = {};
export default withRouter(ApplicationCreate);

@ -0,0 +1,213 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import PropTypes from 'prop-types';
import React, {Component} from 'react';
import {Badge, FormGroup, Input, Label} from 'reactstrap';
/**
* The Second step of application create wizard.
* This contains following components.
* * App Title
* * Short Description
* * Application Description
* * Application Visibility
* * Application Tags : {Used Material UI Chip component}
* * Application Category.
* * Platform Specific properties.
*
* Parent Component: Create
* Props:
* * onNextClick : {type: function, Invokes onNextClick function in Parent.}
* * onPrevClick : {type: function, Invokes onPrevClick function in Parent}
* * setData : {type: function, Invokes setStepData function in Parent}
* * removeData : {type: Invokes removeStepData function in Parent}
* */
class Step1 extends Component {
constructor() {
super();
this.state = {
tags: [],
icon: [],
title: "",
errors: {},
banner: [],
defValue: "",
category: 0,
visibility: 0,
description: "",
screenshots: [],
identifier: "",
shortDescription: ""
};
}
/**
* Create a tag on Enter key press and set it to the state.
* Clears the tags text field.
* Chip gets two parameters: Key and value.
* */
addTags(event) {
let tags = this.state.tags;
if (event.charCode === 13) {
event.preventDefault();
tags.push({key: Math.floor(Math.random() * 1000), value: event.target.value});
this.setState({tags, defValue: ""}, console.log(tags));
}
}
/**
* Set the value for tag.
* */
handleTagChange(event) {
let defaultValue = this.state.defValue;
defaultValue = event.target.value;
this.setState({defValue: defaultValue})
}
/**
* Handles Chip delete function.
* Removes the tag from state.tags
* */
handleRequestDelete(event) {
this.chipData = this.state.tags;
console.log(event.target);
const chipToDelete = this.chipData.map((chip) => chip.value).indexOf(event.target.value);
this.chipData.splice(chipToDelete, 1);
this.setState({tags: this.chipData});
};
/**
* Creates an object with the current step data and persist in the parent.
* */
setStepData() {
let stepData = {};
this.props.setData("step1", {step: stepData});
};
/**
* Set text field values to state.
* */
onTextFieldChange(event, value) {
let field = event.target.id;
switch (field) {
case "name": {
this.setState({name: value});
break;
}
case "shortDescription": {
this.setState({shortDescription: value});
break;
}
case "description": {
this.setState({description: value});
break;
}
case "identifier": {
this.setState({identifier: value});
break;
}
}
};
render() {
return (
<div className="createStep2Content">
<div>
<div>
<FormGroup>
<Label for="app-title">Title*</Label>
<Input
required
type="text"
name="appName"
id="app-title"
/>
</FormGroup>
<FormGroup>
<Label for="app-description">Description*</Label>
<Input
required
type="textarea"
name="appDescription"
id="app-description"
/>
</FormGroup>
<FormGroup>
<Label for="app-category">Category</Label>
<Input
type="select"
name="category"
id="app-category"
>
<option>Business</option>
</Input>
</FormGroup>
<FormGroup>
<Label for="app-visibility">Visibility</Label>
<Input
type="select"
name="visibility"
id="app-visibility"
>
<option>Devices</option>
<option>Roles</option>
<option>Groups</option>
</Input>
</FormGroup>
<FormGroup>
<Label for="app-tags">Tags*</Label>
<Input
required
type="text"
value={this.state.defValue}
name="app-tags"
id="app-tags"
onChange={this.handleTagChange.bind(this)}
onKeyPress={this.addTags.bind(this)}
/>
<div id="batch-content">
{this.state.tags.map(tag => {
return (
<Badge
style={{margin: '0 2px 0 2px'}}
value={tag.value}
onClick={this.handleRequestDelete.bind(this)}
>
{tag.value}
</Badge>
)
}
)}
</div>
</FormGroup>
</div>
</div>
</div>
);
}
}
Step1.prototypes = {
handleNext: PropTypes.func,
handlePrev: PropTypes.func,
setData: PropTypes.func,
removeData: PropTypes.func
};
export default Step1;

@ -0,0 +1,154 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import PropTypes from 'prop-types';
import React, {Component} from 'react';
import AuthHandler from "../../../../api/authHandler";
import PlatformMgtApi from "../../../../api/platformMgtApi";
import {FormGroup, Input, Label} from 'reactstrap';
/**
* The first step of the application creation wizard.
* This contains following components:
* * Application Title
* * Store Type
* * Application Platform
*
* Parent Component: Create
* Props:
* 1. onNextClick: {type: function, Invokes onNextClick function of parent component}
* 2. setData : {type: function, Sets current form data to the state of the parent component}
* 3. removeData: {type: function, Invokes the removeStepData function click of parent}
* */
class Step2 extends Component {
constructor() {
super();
this.setPlatforms = this.setPlatforms.bind(this);
this.setStepData = this.setStepData.bind(this);
this.platforms = [];
this.state = {
finished: false,
stepIndex: 0,
store: 1,
platformSelectedIndex: 0,
platform: "",
platforms: [],
stepData: [],
title: "",
titleError: ""
};
}
componentDidMount() {
//Get the list of available platforms and set to the state.
PlatformMgtApi.getPlatforms().then(response => {
console.log(response);
this.setPlatforms(response.data);
}).catch(err => {
AuthHandler.unauthorizedErrorHandler(err);
})
}
/**
* Extract the platforms from the response data and populate the state.
* @param platforms: The array returned as the response.
* */
setPlatforms(platforms) {
let tmpPlatforms = [];
for (let index in platforms) {
let platform = {};
platform = platforms[index];
tmpPlatforms.push(platform);
}
this.setState({platforms: tmpPlatforms, platformSelectedIndex: 0, platform: tmpPlatforms[0].name})
}
/**
* Persist the current form data to the state.
* */
setStepData() {
let step = {
store: this.state.store,
platform: this.state.platforms[this.state.platformSelectedIndex]
};
this.props.setData("step2", {step: step});
}
/**
* Triggers when changing the Platform selection.
* */
onChangePlatform(event) {
console.log(event.target.value, this.state.platforms);
let id = event.target.value;
let selectedPlatform = this.state.platforms.filter((platform) => {
return platform.identifier === id;
});
this.setState({platform: selectedPlatform});
};
/**
* Triggers when changing the Store selection.
* */
onChangeStore(event) {
this.setState({store: event.target.value});
};
render() {
return (
<div>
<FormGroup>
<Label for="store">Store Type</Label>
<Input
type="select"
name="store"
className="input-custom"
onChange={this.onChangeStore.bind(this)}
>
<option>Enterprise</option>
<option>Public</option>
</Input>
</FormGroup>
<FormGroup>
<Label for="store">Platform</Label>
<Input
type="select"
name="store"
onChange={this.onChangePlatform.bind(this)}
>
{this.state.platforms.length > 0 ? this.state.platforms.map(platform => {
return (
<option value={platform.identifier}>
{platform.name}
</option>
)
}) : <option>No Platforms</option>}
</Input>
</FormGroup>
</div>
);
}
}
Step2.propTypes = {
handleNext: PropTypes.func,
setData: PropTypes.func,
removeData: PropTypes.func
};
export default Step2;

@ -0,0 +1,202 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import PropTypes from 'prop-types';
import Chip from 'material-ui/Chip';
import Dropzone from 'react-dropzone';
import React, {Component} from 'react';
import MenuItem from 'material-ui/MenuItem';
import SelectField from 'material-ui/SelectField';
import {FormGroup, Label} from 'reactstrap';
import AppImage from "../../../UIComponents/AppImage/AppImage";
/**
* The Third step of application create wizard.
* This contains following components.
* * Screenshots
* * Banner
* * Icon
*
* Parent Component: Create
* Props:
* * onNextClick : {type: function, Invokes onNextClick function in Parent.}
* * onPrevClick : {type: function, Invokes onPrevClick function in Parent}
* * setData : {type: function, Invokes setStepData function in Parent}
* * removeData : {type: Invokes removeStepData function in Parent}
* */
class Step3 extends Component {
constructor() {
super();
this.state = {
tags: [],
icon: [],
title: "",
errors: {},
banner: [],
defValue: "",
category: 0,
visibility: 0,
description: "",
screenshots: [],
identifier: "",
shortDescription: ""
};
}
/**
* Handles Chip delete function.
* Removes the tag from state.tags
* */
handleRequestDelete(event) {
this.chipData = this.state.tags;
console.log(event.target); //TODO: Remove Console log.
const chipToDelete = this.chipData.map((chip) => chip.value).indexOf(event.target.value);
this.chipData.splice(chipToDelete, 1);
this.setState({tags: this.chipData});
};
/**
* Creates an object with the current step data and persist in the parent.
* */
setStepData() {
let stepData = {
icon: this.state.icon,
banner: this.state.banner,
screenshots: this.state.screenshots
};
this.props.setData("step2", {step: stepData});
};
/**
* Removed user uploaded banner.
* */
removeBanner(event, d) {
console.log(event, d); //TODO: Remove this
this.setState({banner: []});
};
/**
* Removes uploaded icon.
* */
removeIcon(event) {
this.setState({icon: []});
};
/**
* Removes selected screenshot.
* */
removeScreenshot(event) {
console.log(event.target) //TODO: Remove this.
};
//TODO: Remove inline css.
render() {
return (
<div className="createStep2Content">
<div>
<FormGroup>
<Label for="app-screenshots">Screenshots*</Label>
<span className="image-sub-title"> (600 X 800 32 bit PNG)</span>
<div id="screenshot-container">
{this.state.screenshots.map((tile) => (
<div id="app-image-screenshot">
<AppImage image={tile[0].preview}/>
</div>
))}
{this.state.screenshots.length < 3 ?
<Dropzone
className="application-create-screenshot-dropzone"
accept="image/jpeg, image/png"
onDrop={(screenshots, rejected) => {
let tmpScreenshots = this.state.screenshots;
tmpScreenshots.push(screenshots);
console.log(screenshots); //TODO: Remove this
this.setState({
screenshots: tmpScreenshots
});
}}
>
<i className="fw fw-add"></i>
</Dropzone> : <div/>}
</div>
</FormGroup>
</div>
<div style={{display: 'flex'}}>
<div style={{float: 'left', marginRight: '15px'}}>
<FormGroup>
<Label for="app-icon">Icon*</Label>
<span className="image-sub-title"> (512 X 512 32 bit PNG)</span>
<div id="app-icon-container">
{this.state.icon.map((tile) => (
<div id="app-image-icon">
<AppImage image={tile.preview}/>
</div>
))}
{this.state.icon.length === 0 ?
<Dropzone
className="application-create-icon-dropzone"
accept="image/jpeg, image/png"
onDrop={(icon, rejected) => {
this.setState({icon, rejected});
}}
>
<i className="fw fw-add"></i>
</Dropzone> : <div/>}
</div>
</FormGroup>
</div>
<div style={{marginLeft: '15px'}}>
<FormGroup>
<Label for="app-banner">Banner*</Label>
<span className="image-sub-title"> (1000 X 400 32 bit PNG)</span>
<div id="app-banner-container">
{this.state.banner.map((tile) => (
<div id="app-image-banner">
<AppImage image={tile.preview}/>
</div>
))}
{this.state.banner.length === 0 ?
<Dropzone
className="application-create-banner-dropzone"
accept="image/jpeg, image/png"
onDrop={(banner, rejected) => {
this.setState({banner, rejected});
}}
>
<i className="fw fw-add"></i>
</Dropzone> : <div/>
}
</div>
</FormGroup>
</div>
</div>
</div>
);
}
}
Step3.prototypes = {
handleNext: PropTypes.func,
handlePrev: PropTypes.func,
setData: PropTypes.func,
removeData: PropTypes.func
};
export default Step3;

@ -0,0 +1,155 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import PropTypes from 'prop-types';
import React, {Component} from 'react';
import {Collapse, FormGroup, Input, Label, FormText} from 'reactstrap';
import Switch from '../../../UIComponents/Switch/Switch'
/**
* The Third step of application create wizard. {Application Release Step}
* This step is not compulsory.
*
* When click finish, user will prompt to confirm the application creation.
* User can go ahead and create the app or cancel.
*
* This contains following components:
* * Toggle to select application release. Un-hides the Application Release form.
*
* Application Release Form.
* * Release Channel
* * Application Version
* * Upload component for application.
*
* Parent Component: Create
* Props:
* * handleFinish : {type: function, Invokes onNextClick function in Parent.}
* * onPrevClick : {type: function, Invokes onPrevClick function in Parent}
* * setData : {type: function, Invokes setStepData function in Parent}
* * removeData : {type: Invokes removeStepData function in Parent}
* */
class Step4 extends Component {
constructor() {
super();
this.handleToggle = this.handleToggle.bind(this);
this.handlePrev = this.handlePrev.bind(this);
this.handleToggle = this.handleToggle.bind(this);
this.handleFinish = this.handleFinish.bind(this);
this.state = {
showForm: false,
releaseChannel: 1,
errors: {}
};
this.scriptId = "application-create-step3";
}
/**
* Handles finish button click.
* This invokes onNextClick function in parent component.
* */
handleFinish() {
this.props.handleFinish();
}
/**
* Invokes Prev button click.
* */
handlePrev() {
this.props.handlePrev();
}
/**
* Handles release application selection.
* */
handleToggle() {
let hide = this.state.showForm;
this.setState({showForm: !hide});
}
render() {
return (
<div className="applicationCreateStepMiddle">
<div>
<FormGroup>
<div id="app-release-switch-content">
<div id="app-release-switch-label">
<Label for="app-release-switch">
<strong>
Add Release to Application
</strong>
</Label>
</div>
<div id="app-release-switch-switch">
<Switch
id="app-release-switch"
onChange={this.handleToggle.bind(this)}
/>
</div>
</div>
</FormGroup>
<br/>
<div>
<FormText color="muted">
<i>Info: </i>
Enabling this will create a release for the current Application.
To upload the Application, please visit to the Release management section of
Application Edit View.
</FormText>
</div>
{/*If toggle is true, the release form will be shown.*/}
<Collapse isOpen={this.state.showForm}>
<FormGroup>
<Label for="release-channel">Release Channel</Label>
<Input
type="select"
id="release-channel"
style={{
width: '200px',
border: 'none',
borderRadius: '0',
borderBottom: 'solid 1px #BDBDBD'
}}>
<option>GA</option>
<option>Alpha</option>
<option>Beta</option>
</Input>
</FormGroup>
<FormGroup>
<Label for="version">Version*</Label>
<Input
type="text"
id="version input-custom"
placeholder="v1.0"
required
/>
</FormGroup>
</Collapse>
</div>
</div>
);
}
}
Step4.propTypes = {
handleFinish: PropTypes.func,
handlePrev: PropTypes.func,
setData: PropTypes.func,
removeData: PropTypes.func
};
export default Step4;

@ -1,157 +0,0 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import PropTypes from 'prop-types';
import React, {Component} from 'react';
import MenuItem from 'material-ui/MenuItem';
import TextField from 'material-ui/TextField';
import SelectField from 'material-ui/SelectField';
import RaisedButton from 'material-ui/RaisedButton';
/**
* The first step of the application creation wizard.
* This contains following components:
* * Application Title
* * Store Type
* * Application Platform
*
* Parent Component: Create
* Props:
* 1. handleNext: {type: function, Invokes handleNext function of parent component}
* 2. setData : {type: function, Sets current form data to the state of the parent component}
* 3. removeData: {type: function, Invokes the removeStepData function click of parent}
* */
class Step1 extends Component {
constructor() {
super();
this.state = {
finished: false,
stepIndex: 0,
store: 1,
platform: 1,
stepData: [],
title: "",
titleError: ""
};
}
componentWillMount() {
//Get the list of available platforms and set to the state.
}
/**
* Invokes the handleNext function in Create component.
* */
_handleNext = () => {
this.props.handleNext();
};
/**
* Persist the current form data to the state.
* */
_setStepData() {
var step = {
store: this.state.store,
platform: this.state.platform
};
this.props.setData("step1", {step: step});
}
/**
* Handles Next button click.
* Validates the form.
* Sets the data to the state.
* Invokes the handleNext method of Create component.
* */
_handleClick() {
this._setStepData();
}
/**
* Triggers when changing the Platform selection.
* */
_onChangePlatform = (event, index, value) => {
console.log(value);
this.setState({platform: value});
};
/**
* Triggers when changing the Store selection.
* */
_onChangeStore = (event, index, value) => {
this.setState({store: value});
};
/**
* Triggers when user types on Title text field.
* */
_onChangeTitle = (event, value) => {
this.setState({title: value});
};
render() {
const contentStyle = {margin: '0 16px'};
return (
<div>
<div style={contentStyle}>
<div>
<div>
<SelectField
floatingLabelText="Store Type*"
value={this.state.store}
floatingLabelFixed={true}
onChange={this._onChangeStore.bind(this)}
>
<MenuItem value={1} primaryText="Enterprise"/>
<MenuItem value={2} primaryText="Public"/>
</SelectField> <br/>
<SelectField
floatingLabelText="Platform*"
value={this.state.platform}
floatingLabelFixed={true}
onChange={this._onChangePlatform.bind(this)}
>
<MenuItem value={1} primaryText="Android"/>
<MenuItem value={2} primaryText="iOS"/>
<MenuItem value={{name: "Web", id:3}} primaryText="Web"/>
</SelectField>
</div>
<br/>
<br/>
<div style={{marginTop: 12}}>
<RaisedButton
label="Next >"
primary={true}
onClick={this._handleClick.bind(this)}
/>
</div>
</div>
</div>
</div>
);
}
}
Step1.propTypes = {
handleNext: PropTypes.func,
setData: PropTypes.func,
removeData: PropTypes.func
};
export default Step1;

@ -1,489 +0,0 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import PropTypes from 'prop-types';
import Chip from 'material-ui/Chip';
import Dropzone from 'react-dropzone';
import React, {Component} from 'react';
import MenuItem from 'material-ui/MenuItem';
import TextField from 'material-ui/TextField';
import FlatButton from 'material-ui/FlatButton';
import IconButton from 'material-ui/IconButton';
import SelectField from 'material-ui/SelectField';
import RaisedButton from 'material-ui/RaisedButton';
import Clear from 'material-ui/svg-icons/content/clear';
import {GridList, GridTile} from 'material-ui/GridList';
/**
* The Second step of application create wizard.
* This contains following components.
* * App Title
* * Short Description
* * Application Description
* * Application Visibility
* * Application Tags : {Used Material UI Chip component}
* * Application Category.
* * Platform Specific properties.
* * Screenshots
* * Banner
* * Icon
*
* Parent Component: Create
* Props:
* * handleNext : {type: function, Invokes handleNext function in Parent.}
* * handlePrev : {type: function, Invokes handlePrev function in Parent}
* * setData : {type: function, Invokes setStepData function in Parent}
* * removeData : {type: Invokes removeStepData function in Parent}
* */
class Step2 extends Component {
constructor() {
super();
this.state = {
tags: [],
defValue: "",
category: 0,
visibility: 0,
errors: {},
title: "",
shortDescription: "",
description: "",
banner: [],
screenshots: [],
icon: []
};
this.styles = {
chip: {
margin: 4,
},
wrapper: {
display: 'flex',
flexWrap: 'wrap',
},
};
}
/**
* Create a tag on Enter key press and set it to the state.
* Clears the tags text field.
* Chip gets two parameters: Key and value.
* */
_addTags(event) {
let tags = this.state.tags;
if (event.charCode === 13) {
event.preventDefault();
tags.push({key: Math.floor(Math.random() * 1000), value: event.target.value});
this.setState({tags, defValue: ""});
}
}
/**
* Set the value for tag.
* */
_handleTagChange(event) {
let defaultValue = this.state.defValue;
defaultValue = event.target.value;
this.setState({defValue: defaultValue})
}
/**
* Invokes the handleNext function in Create component.
* */
_handleNext() {
let fields = [{name: "Title", value: this.state.title},
{name: "Short Description", value: this.state.shortDescription},
{name: "Description", value: this.state.description},
{name: "Banner", value: this.state.banner},
{name: "Screenshots", value: this.state.screenshots},
{name: "Icon", value: this.state.icon}];
this._validate(fields);
// this.props.handleNext();
}
/**
* Invokes the handlePrev function in Create component.
* */
_handlePrev() {
this.props.handlePrev();
}
/**
* Handles Chip delete function.
* Removes the tag from state.tags
* */
_handleRequestDelete = (key) => {
this.chipData = this.state.tags;
const chipToDelete = this.chipData.map((chip) => chip.key).indexOf(key);
this.chipData.splice(chipToDelete, 1);
this.setState({tags: this.chipData});
};
/**
* Creates Chip array from state.tags.
* */
_renderChip(data) {
return (
<Chip
key={data.key}
onRequestDelete={() => this._handleRequestDelete(data.key)}
style={this.styles.chip}
>
{data.value}
</Chip>
);
}
_onVisibilitySelect = (event, index, value) => {
console.log(value);
let comp = <SelectField> <MenuItem value={0} primaryText="Public"/>
<MenuItem value={1} primaryText="Roles"/>
<MenuItem value={2} primaryText="Devices"/> </SelectField>;
if (value === 1) {
this.setState({visibilityComponent: comp});
} else if (value === 2) {
} else {
}
};
/**
* Validate the form.
* */
_validate(fields) {
let errors = {};
let errorsPresent = false;
fields.forEach(function (field) {
switch (field.name) {
case 'Title': {
if (field.value === "") {
errors[field.name] = field.name + " is required!";
errorsPresent = true;
} else {
errorsPresent = false;
}
break;
}
case 'Short Description': {
if (field.value === "") {
errors[field.name] = field.name + " is required!";
errorsPresent = true;
} else {
errorsPresent = false;
}
break;
}
case 'Description': {
if (field.value === "") {
errors[field.name] = field.name + " is required!";
errorsPresent = true;
} else {
errorsPresent = false;
}
break;
}
case 'Banner': {
if (field.value.length === 0) {
errors[field.name] = field.name + " is required!";
errorsPresent = true;
} else {
errorsPresent = false;
}
break;
}
case 'Icon': {
if (field.value.length === 0) {
errors[field.name] = field.name + " is required!";
errorsPresent = true;
} else {
errorsPresent = false;
}
break;
}
case 'Screenshots': {
if (field.value.length < 3) {
errors[field.name] = "3 " +field.name + " are required!";
errorsPresent = true;
} else {
errorsPresent = false;
}
break;
}
}
});
console.log(errorsPresent);
if (!errorsPresent) {
this._setStepData();
} else {
this.setState({errors: errors}, console.log(errors));
}
}
/**
* Creates an object with the current step data and persist in the parent.
* */
_setStepData() {
let stepData = {
title: this.state.title,
description: this.state.description,
shortDescription: this.state.shortDescription,
tags: this.state.tags,
banner: this.state.banner,
screenshots: this.state.screenshots,
icon: this.state.icon
};
this.props.setData("step2", {step: stepData});
}
/**
* Set text field values to state.
* */
_onTextFieldChange(event, value) {
let field = event.target.id;
switch (field) {
case "title": {
this.setState({title: value});
break;
}
case "shortDescription": {
this.setState({shortDescription: value});
break;
}
case "description": {
this.setState({description: value});
break;
}
}
}
/**
* Removed user uploaded banner.
* */
_removeBanner(event, d) {
console.log(event, d);
this.setState({banner: []});
}
/**
* Removes uploaded icon.
* */
_removeIcon(event) {
this.setState({icon: []});
}
/**
* Removes selected screenshot.
* */
_removeScreenshot(event) {
console.log(event.target)
}
render() {
console.log(this.state.visibilityComponent);
const contentStyle = {margin: '0 16px'};
return (
<div style={contentStyle}>
<div>
<div>
<TextField
id="title"
hintText="Enter a title for your application."
errorText={this.state.errors["Title"]}
floatingLabelText="Title*"
floatingLabelFixed={true}
onChange={this._onTextFieldChange.bind(this)}
/><br/>
<TextField
id="shortDescription"
hintText="Enter a short description for your application."
errorText={this.state.errors["Short Description"]}
floatingLabelText="Short Description*"
floatingLabelFixed={true}
multiLine={true}
rows={2}
onChange={this._onTextFieldChange.bind(this)}
/><br/>
<TextField
id="description"
errorText={this.state.errors["Description"]}
hintText="Enter the description."
floatingLabelText="Description*"
floatingLabelFixed={true}
multiLine={true}
rows={4}
onChange={this._onTextFieldChange.bind(this)}
/><br/>
<SelectField
floatingLabelText="Visibility*"
value={this.state.visibility}
floatingLabelFixed={true}
onChange={this._onVisibilitySelect.bind(this)}
>
<MenuItem value={0} primaryText="Public"/>
<MenuItem value={1} primaryText="Roles"/>
<MenuItem value={2} primaryText="Devices"/>
</SelectField><br/>
<TextField
id="tags"
errorText={this.state.errors["tags"]}
hintText="Enter application tags.."
floatingLabelText="Tags*"
floatingLabelFixed={true}
value={this.state.defValue}
onChange={this._handleTagChange.bind(this)}
onKeyPress={this._addTags.bind(this)}
/><br/>
<div style={this.styles.wrapper}>
{this.state.tags.map(this._renderChip, this)}
</div>
<br/>
<SelectField
floatingLabelText="Category*"
value={this.state.category}
floatingLabelFixed={true}
>
<MenuItem value={0} primaryText="Business"/>
</SelectField> <br/>
{/*Platform Specific Properties.*/}
<div style={{border: 'solid #BDBDBD 1px'}}>
<p style={{color: '#BDBDBD'}}>Platform Specific Properties</p>
</div>
<br/>
<div>
<p style={{color: '#f44336'}}>{this.state.errors["Banner"]}</p>
<p style={{color: '#BDBDBD'}}>Banner*:</p>
<GridList style={{
display: 'flex',
flexWrap: 'nowrap',
overflowX: 'auto',
}} cols={1.1}>
{this.state.banner.map((tile) => (
<GridTile key={Math.floor(Math.random() * 1000)}
title={tile.name}
actionIcon={
<IconButton onClick={this._removeBanner.bind(this)}>
<Clear />
</IconButton>}>
<img src={tile.preview}/></GridTile>
))}
{this.state.banner.length === 0 ?
<Dropzone style={{width: '300px', height: '150px', border: 'dashed #BDBDBD 1px'}}
accept="image/jpeg, image/png"
onDrop={(banner, rejected) => {
this.setState({banner, rejected});
}}>
<p style={{margin: '70px 40px 40px 150px'}}>+</p>
</Dropzone> : <div />}
</GridList>
</div>
<br/>
<div>
<p style={{color: '#f44336'}}>{this.state.errors["Screenshots"]}</p>
<p style={{color: '#BDBDBD'}}>Screenshots*:</p>
<GridList style={{
display: 'flex',
flexWrap: 'nowrap',
overflowX: 'auto',
}} cols={1.1}>
{this.state.screenshots.map((file) => (
<GridTile key={Math.floor(Math.random() * 1000)}
title={file[0].name}
actionIcon={
<IconButton onClick={this._removeScreenshot.bind(this)}>
<Clear/>
</IconButton>}>
<img src={file[0].preview}/></GridTile>
))}
{this.state.screenshots.length < 3 ?
<Dropzone style={{width: '150px', height: '150px', border: 'dashed #BDBDBD 1px'}}
accept="image/jpeg, image/png"
onDrop={(screenshots, rejected) => {
let tmpScreenshots = this.state.screenshots;
tmpScreenshots.push(screenshots);
this.setState({
screenshots: tmpScreenshots});
}}>
<p style={{margin: '70px 40px 70px 70px'}}>+</p>
</Dropzone> : <div />}
</GridList>
</div>
<br/>
<div>
<p style={{color: '#f44336'}}>{this.state.errors["Icon"]}</p>
<p style={{color: '#BDBDBD'}}>Icon*:</p>
<GridList style={{
display: 'flex',
flexWrap: 'nowrap',
overflowX: 'auto',
}} cols={1.1}>
{this.state.icon.map((tile) => (
<GridTile key={Math.floor(Math.random() * 1000)}
title={tile.name}
actionIcon={
<IconButton onClick={this._removeIcon.bind(this)}>
<Clear />
</IconButton>}>
<img src={tile.preview}/></GridTile>
))}
{this.state.icon.length === 0 ?
<Dropzone style={{width: '150px', height: '150px', border: 'dashed #BDBDBD 1px'}}
accept="image/jpeg, image/png"
onDrop={(icon, rejected) => {this.setState({icon, rejected});}}>
<p style={{margin: '70px 40px 70px 70px'}}>+</p>
</Dropzone> : <div />}
</GridList>
</div>
<br/>
</div>
<br/>
<br/>
<div style={{marginTop: 12}}>
<FlatButton
label="< Back"
disabled={false}
onClick={this._handlePrev.bind(this)}
style={{marginRight: 12}}
/>
<RaisedButton
label="Next >"
primary={true}
onClick={this._handleNext.bind(this)}
/>
</div>
</div>
</div>
);
}
}
Step2.prototypes = {
handleNext: PropTypes.func,
handlePrev: PropTypes.func,
setData: PropTypes.func,
removeData: PropTypes.func
};
export default Step2;

@ -1,139 +0,0 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import PropTypes from 'prop-types';
import React, {Component} from 'react';
import Toggle from 'material-ui/Toggle';
import MenuItem from 'material-ui/MenuItem';
import TextField from 'material-ui/TextField';
import FlatButton from 'material-ui/FlatButton';
import SelectField from 'material-ui/SelectField';
import RaisedButton from 'material-ui/RaisedButton';
/**
* The Third step of application create wizard. {Application Release Step}
* This step is not compulsory.
*
* When click finish, user will prompt to confirm the application creation.
* User can go ahead and create the app or cancel.
*
* This contains following components:
* * Toggle to select application release. Un-hides the Application Release form.
*
* Application Release Form.
* * Release Channel
* * Application Version
* * Upload component for application.
*
* Parent Component: Create
* Props:
* * handleFinish : {type: function, Invokes handleNext function in Parent.}
* * handlePrev : {type: function, Invokes handlePrev function in Parent}
* * setData : {type: function, Invokes setStepData function in Parent}
* * removeData : {type: Invokes removeStepData function in Parent}
* */
class Step3 extends Component {
constructor() {
super();
this.state = {
showForm: false,
releaseChannel: 1,
errors: {}
}
}
/**
* Handles finish button click.
* This invokes handleNext function in parent component.
* */
_handleFinish() {
this.props.handleFinish();
}
/**
* Invokes Prev button click.
* */
_handlePrev() {
this.props.handlePrev();
}
/**
* Handles release application selection.
* */
_handleToggle() {
let hide = this.state.showForm;
this.setState({showForm: !hide});
}
render() {
const contentStyle = {margin: '0 16px'};
return (
<div style={contentStyle}>
<div>
<Toggle
label="Release the Application"
labelPosition="right"
onToggle={this._handleToggle.bind(this)}
defaultToggled={this.state.showForm}
/>
{/*If toggle is true, the release form will be shown.*/}
{!this.state.showForm ? <div/> : <div>
<SelectField
floatingLabelText="Select Release Channel*"
value={this.state.releaseChannel}
floatingLabelFixed={true}
>
<MenuItem value={1} primaryText="Alpha"/>
<MenuItem value={2} primaryText="Beta"/>
<MenuItem value={3} primaryText="GA"/>
</SelectField> <br/>
<TextField
hintText="1.0.0"
floatingLabelText="Version*"
errorText={this.state.errors["title"]}
floatingLabelFixed={true}
/><br/>
</div>}
<div style={{marginTop: 12}}>
<FlatButton
label="< Back"
disabled={false}
onClick={this._handlePrev.bind(this)}
style={{marginRight: 12}}
/>
<RaisedButton
label="Finish"
primary={true}
onClick={this._handleFinish.bind(this)}
/>
</div>
</div>
</div>
);
}
}
Step3.propTypes = {
handleFinish: PropTypes.func,
handlePrev: PropTypes.func,
setData: PropTypes.func,
removeData: PropTypes.func
};
export default Step3;

@ -0,0 +1,126 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import {Col, Row} from "reactstrap";
import React, {Component} from 'react';
import GeneralInfo from "../GenenralInfo/GeneralInfo";
import ReleaseManager from '../../Release/ReleaseMgtBase/ReleaseManager';
class ApplicationEdit extends Component {
constructor() {
super();
this.getTabContent = this.getTabContent.bind(this);
this.state = {
general: "active",
release: "",
pkgmgt: "",
activeTab: 1
}
}
handleTabClick(event) {
event.stopPropagation();
const key = event.target.value;
switch (key) {
case "1": {
this.setState({activeTab: 1, general: "active", release: "", pkgmgt: ""});
break;
}
case "2": {
this.setState({activeTab: 2, general: "", release: "active", pkgmgt: ""});
break;
}
case "3": {
this.setState({activeTab: 3, general: "", release: "", pkgmgt: "active"});
break;
}
default: {
return "No Content";
}
}
}
getTabContent(tab) {
switch (tab) {
case 1: {
return <GeneralInfo/>
}
case 2: {
return <ReleaseManager/>
}
case 3: {
return ("Step3")
}
}
}
handleOnBackClick() {
window.location.href = "/publisher/assets/apps"
}
render() {
return (
<div id="application-edit-base">
<Row id="application-edit-header">
<Col xs="3">
<a className="back-to-app" onClick={this.handleOnBackClick.bind(this)}>
<i className="fw fw-left-arrow"></i>
</a>
</Col>
<Col>
Application Name
</Col>
</Row>
<Row id="application-edit-main-container">
<Col xs="3">
<div className="tab">
<button className={this.state.general} value={1} onClick={this.handleTabClick.bind(this)}>
General
</button>
<button className={this.state.release} value={2} onClick={this.handleTabClick.bind(this)}>
App
Releases
</button>
<button className={this.state.pkgmgt} value={3} onClick={this.handleTabClick.bind(this)}>
Package Manager
</button>
</div>
</Col>
<Col xs="9">
<div id="app-edit-content">
<Row>
<Col xs="12">
<div id="application-edit-outer-content">
{/* Application edit content */}
<div id="application-edit-content">
{this.getTabContent(this.state.activeTab)}
</div>
</div>
</Col>
</Row>
</div>
</Col>
</Row>
</div>
)
}
}
export default ApplicationEdit;

@ -0,0 +1,202 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React, {Component} from 'react';
import {Badge, Button, FormGroup, Input, Label, Row} from 'reactstrap';
import Dropzone from 'react-dropzone';
class GeneralInfo extends Component {
constructor() {
super();
this.state = {
defValue: "",
tags: [],
screenshots: [],
icon: [],
banner: []
}
}
//TODO: Remove Console logs.
render() {
return (
<div className="app-edit-general-info">
<Row>
<form>
<FormGroup>
<Label for="app-title">Title*</Label>
<Input
required
type="text"
name="appName"
id="app-title"
/>
</FormGroup>
<FormGroup>
<Label for="app-title">Description*</Label>
<Input
required
type="textarea"
multiline
name="appName"
id="app-title"
/>
</FormGroup>
<FormGroup>
<Label for="app-category">Category</Label>
<Input
type="select"
name="category"
id="app-category"
>
<option>Business</option>
</Input>
</FormGroup>
<FormGroup>
<Label for="app-visibility">Visibility</Label>
<Input
type="select"
name="visibility"
id="app-visibility"
>
<option>Devices</option>
<option>Roles</option>
<option>Groups</option>
</Input>
</FormGroup>
<FormGroup>
<Label for="app-tags">Tags*</Label>
<Input
required
type="text"
value={this.state.defValue}
name="app-tags"
id="app-tags"
/>
<div id="batch-content">
{this.state.tags.map(tag => {
return (
<Badge
style={{margin: '0 2px 0 2px'}}
value={tag.value}
>
{tag.value}
</Badge>
)
}
)}
</div>
</FormGroup>
<div>
<FormGroup>
<Label for="app-screenshots">Screenshots*</Label>
<span className="image-sub-title"> (600 X 800 32 bit PNG)</span>
<div id="screenshot-container">
{this.state.screenshots.map((tile) => (
<button id="img-btn-screenshot" style={{height: '210px', width: '410px'}}
onMouseEnter={() => {
console.log("Mouse Entered")
}}>
{console.log(tile[0].preview)}
<img style={{height: '200px', width: '400px'}} src={tile[0].preview}/>
</button>
))}
{this.state.screenshots.length < 3 ?
<Dropzone
className="application-create-screenshot-dropzone"
accept="image/jpeg, image/png"
onDrop={(screenshots, rejected) => {
let tmpScreenshots = this.state.screenshots;
tmpScreenshots.push(screenshots);
console.log(screenshots);
this.setState({
screenshots: tmpScreenshots
});
}}
>
<i className="fw fw-add"></i>
</Dropzone> : <div/>}
</div>
</FormGroup>
</div>
<div style={{display: 'flex'}}>
<div style={{float: 'left', marginRight: '15px'}}>
<FormGroup>
<Label for="app-icon">Icon*</Label>
<span className="image-sub-title"> (512 X 512 32 bit PNG)</span>
<div id="app-icon-container">
{this.state.icon.map((tile) => (
<button onMouseEnter={() => {
console.log("Mouse Entered")
}}>
<img style={{height: '200px', width: '200px'}} src={tile.preview}/>
</button>
))}
{this.state.icon.length === 0 ?
<Dropzone
className="application-create-icon-dropzone"
accept="image/jpeg, image/png"
onDrop={(icon, rejected) => {
this.setState({icon, rejected});
}}
>
<i className="fw fw-add"></i>
</Dropzone> : <div/>}
</div>
</FormGroup>
</div>
<div style={{marginLeft: '15px'}}>
<FormGroup>
<Label for="app-banner">Banner*</Label>
<span className="image-sub-title"> (1000 X 400 32 bit PNG)</span>
<div id="app-banner-container">
{this.state.banner.map((tile) => (
<button onMouseEnter={() => {
console.log("Mouse Entered")
}}>
<img style={{height: '200px', width: '400px'}} src={tile.preview}/>
</button>
))}
{this.state.banner.length === 0 ?
<Dropzone
className="application-create-banner-dropzone"
accept="image/jpeg, image/png"
onDrop={(banner, rejected) => {
this.setState({banner, rejected});
}}
>
<i className="fw fw-add"></i>
</Dropzone> : <div/>
}
</div>
</FormGroup>
</div>
</div>
<div className="save-info">
<Button>Save</Button>
</div>
</form>
</Row>
</div>
)
}
}
export default GeneralInfo;

@ -16,9 +16,22 @@
* under the License.
*/
import ReviewStore from './ReviewStore';
import PlatformStore from './PlatformStore';
import ApplicationStore from './ApplicationStore';
import PropTypes from 'prop-types';
import React, {Component} from 'react';
class PackageManager extends Component {
export {ApplicationStore, PlatformStore, ReviewStore};
constructor() {
super();
}
render() {
return(
<div id="package-mgt-content">
</div>
)
}
}
export default PackageManager;

@ -0,0 +1,172 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import PropTypes from 'prop-types';
import React, {Component} from 'react';
import {Button, FormGroup, FormText, Input, Label, Row} from "reactstrap";
import UploadPackage from "./UploadPackage";
class CreateRelease extends Component {
constructor() {
super();
this.onTestMethodChange = this.onTestMethodChange.bind(this);
this.showUploadArtifacts = this.showUploadArtifacts.bind(this);
this.onBackClick = this.onBackClick.bind(this);
this.onBackToRelease = this.onBackToRelease.bind(this);
this.state = {
open: true,
hiddenMain: false
}
}
onTestMethodChange(event) {
let type = event.target.value;
if (type !== 'open') {
this.setState({open: false})
} else {
this.setState({open: true})
}
}
showUploadArtifacts() {
this.setState({hiddenMain: true})
}
onBackClick() {
this.props.handleBack();
}
onBackToRelease() {
this.setState({hiddenMain: false});
}
render() {
const {channel} = this.props;
console.log(channel);
return (
<div>
{this.state.hiddenMain ?
<div>
<UploadPackage
backToRelease={this.onBackToRelease}
selectedChannel={channel}
/>
</div> :
<div>
<Row>
<div className="release-header">
<a onClick={this.onBackClick}>{"<-"}</a>
<span id="create-release-header">
<strong>{channel} Release</strong>
</span>
</div>
</Row>
<Row>
<div className="release-create">
<div>
<span>
<strong>Create Release</strong>
</span>
<p>
{channel === 'Production' ? "" :
"You could create " + channel + " release for your application and let " +
"the test users to test the application for it's stability."}
</p>
</div>
<div>
<Button id="create-release-btn" onClick={this.showUploadArtifacts}>Create a {channel} Release</Button>
</div>
</div>
</Row>
{channel !== 'Production' ?
<Row>
<div>
<span>
<strong>Manage Test Method</strong>
</span>
<p>
This section allows you to change the test method and the users who would be
able to test your application.
</p>
<div>
<form>
<FormGroup>
<Label for="test-method">Test Method*</Label>
<Input
required
type="select"
name="testMethod"
id="test-method"
onChange={this.onTestMethodChange}
>
<option value="open">Open {channel}</option>
<option value="closed">Closed {channel}</option>
</Input>
</FormGroup>
{!this.state.open ? (
<FormGroup>
<Label for="user-list">Users List*</Label>
<Input
required
name="userList"
id="user-list"
type="text"
/>
<FormText color="muted">
Provide a comma separated list of email
addresses.
</FormText>
</FormGroup>
) : <div/>}
<FormGroup>
<Label for="app-title">Feedback Method*</Label>
<Input
required
name="appName"
id="app-title"
/>
<FormText color="muted">
Provide an Email address or a URL for your users to provide
feedback on the application.
</FormText>
</FormGroup>
<div>
<Button className="form-btn">Save</Button>
</div>
</form>
</div>
</div>
</Row> :
<div/>
}
</div>
}
</div>
);
}
}
CreateRelease.propTypes = {
channel: PropTypes.string,
handleBack: PropTypes.func
};
export default CreateRelease;

@ -0,0 +1,94 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import PropTypes from 'prop-types';
import React, {Component} from 'react';
import {Button, Col, FormGroup, Input, Label, Row} from "reactstrap";
class UploadPackage extends Component {
constructor() {
super();
this.handleBack = this.handleBack.bind(this)
}
handleBack() {
this.props.onBackToRelease();
}
render() {
const {selectedChannel} = this.props;
return (
<div>
<Row>
<div className="release-header">
<a onClick={this.handleBack}>{"<-"}</a>
<span id="create-release-header">
<strong>New Release for {selectedChannel}</strong>
</span>
</div>
</Row>
<Row>
<div className="release-header">
<span id="create-release-header">
<strong>Upload Package File</strong>
</span>
</div>
</Row>
<Row>
<Col xs="3">
<Button>Upload</Button>
</Col>
<Col xs="3">
<Button>Select from package library</Button>
</Col>
</Row>
<Row>
<div className="release-detail-content">
<form>
<FormGroup>
<Label>Release Name *</Label>
<Input
required
type="text"
/>
</FormGroup>
<FormGroup>
<Label>Release Notes *</Label>
<Input
required
type="textarea"
/>
</FormGroup>
<div className="form-btn">
<Button>Send for Review</Button>
</div>
</form>
</div>
</Row>
</div>
);
}
}
UploadPackage.protoTypes = {
backToRelease: PropTypes.func,
channel: PropTypes.string
};
export default UploadPackage;

@ -0,0 +1,125 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import PropTypes from 'prop-types';
import React, {Component} from 'react';
import {Button, Col, Row} from "reactstrap";
import CreateRelease from "../Create/CreateRelease";
class ReleaseManager extends Component {
constructor() {
super();
this.getNoReleaseContent = this.getNoReleaseContent.bind(this);
this.onCreateRelease = this.onCreateRelease.bind(this);
this.onBackClick = this.onBackClick.bind(this);
this.state = {
createRelease: false,
onGoing: ""
}
}
onCreateRelease(event) {
event.preventDefault();
this.setState({createRelease: true, onGoing: event.target.value})
}
onBackClick() {
this.setState({createRelease: false});
}
/**
* Holds a generic message saying there are no current release in the specified release channel.
* */
getNoReleaseContent(release) {
return (
<div>
<Row>
<Col sm="12" md={{size: 8, offset: 4}}>
<p>You have no on-going {release} Releases!</p>
</Col>
</Row>
<Row>
<Col sm="12" md={{size: 8, offset: 5}}>
<Button
className="button-add"
id={release.toLowerCase()}
value={release}
onClick={this.onCreateRelease}
>
Create a Release
</Button>
</Col>
</Row>
</div>
);
}
render() {
return (
<div>
{this.state.createRelease ?
<CreateRelease
channel={this.state.onGoing}
handleBack={this.onBackClick}
/> :
<div id="release-mgt-content">
<Row>
<Col sm="12">
<div className="release" id="production">
<span>Production Releases</span>
<div className="release-content">
<div className="release-inner">
{this.getNoReleaseContent("Production")}
</div>
</div>
</div>
</Col>
</Row>
<Row>
<Col sm="12">
<div className="release" id="beta">
<span>Beta Releases</span>
<div className="release-content">
<div className="release-inner">
{this.getNoReleaseContent("Beta")}
</div>
</div>
</div>
</Col>
</Row>
<Row>
<Col sm="12">
<div className="release" id="alpha">
<span>Alpha Releases</span>
<div className="release-content">
<div className="release-inner">
{this.getNoReleaseContent("Alpha")}
</div>
</div>
</div>
</Col>
</Row>
</div>
}
</div>
)
}
}
export default ReleaseManager;

@ -0,0 +1,129 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React, {Component} from 'react';
import {withRouter} from 'react-router-dom';
import {Col, Row} from "reactstrap";
/**
* Application view component.
* Shows the details of the application.
* */
class ApplicationView extends Component {
constructor() {
super();
this.state = {
application: {}
}
}
componentWillReceiveProps(props, nextProps) {
this.setState({application: props.application});
console.log(props.application, nextProps)
}
componentDidMount() {
//TODO: Download image artifacts.
}
handleEdit() {
this.props.history.push("/assets/apps/edit/" + this.state.application.uuid);
}
render() {
const platform = this.state.application;
console.log(platform);
return (
<div id="application-view-content">
<div id="application-view-row">
<Row>
<Col>
<div id="app-icon">
</div>
</Col>
<Col>
<Row>
<span><strong>Facebook</strong></span>
</Row>
<Row>
<span className="app-updated-date">Last updated on 2017-09-23</span>
</Row>
</Col>
</Row>
</div>
<div id="application-view-row">
<Row>
<Col>
<span className="app-install-count">2k Installs</span>
</Col>
</Row>
<Row>
<Col>
<i className="fw fw-star"></i>
<i className="fw fw-star"></i>
<i className="fw fw-star"></i>
<i className="fw fw-star"></i>
</Col>
<Col>
<a href="#">View in Store</a>
</Col>
</Row>
</div>
<hr/>
<div id="application-view-row">
<Row>
<Col>
<span><strong>Description: </strong></span>
</Col>
<Col>
<p>sdfjlkdsjfsjdfjsdf sfjdslkjfdsflkjdsfslkdjfl j</p>
</Col>
</Row>
<Row>
<Col>
<span><strong>Tags: </strong></span>
</Col>
<Col>
<p>[list of tags...]</p>
</Col>
</Row>
<Row>
<Col>
<span><strong>Release: </strong></span>
</Col>
<Col>
<p>Production</p>
</Col>
</Row>
<Row>
<Col>
<span><strong>Version: </strong></span>
</Col>
<Col>
<p>v1.0</p>
</Col>
</Row>
</div>
</div>
);
}
}
export default withRouter(ApplicationView);

@ -17,6 +17,7 @@
*/
import PropTypes from 'prop-types';
import Chip from 'material-ui/Chip';
import Dropzone from 'react-dropzone';
import React, {Component} from 'react';
import Toggle from 'material-ui/Toggle';
@ -26,6 +27,7 @@ import FlatButton from 'material-ui/FlatButton';
import IconButton from 'material-ui/IconButton';
import SelectField from 'material-ui/SelectField';
import RaisedButton from 'material-ui/RaisedButton';
import PlatformMgtApi from '../../api/platformMgtApi';
import Clear from 'material-ui/svg-icons/content/clear';
import {GridList, GridTile} from 'material-ui/GridList';
import Close from 'material-ui/svg-icons/navigation/close';
@ -46,7 +48,20 @@ class PlatformCreate extends Component {
constructor() {
super();
this.onCreatePlatform = this.onCreatePlatform.bind(this);
this.handleToggle = this.handleToggle.bind(this);
this.addProperty = this.addProperty.bind(this);
this.addTags = this.addTags.bind(this);
this.clearForm = this.clearForm.bind(this);
this.onPropertySelect = this.onPropertySelect.bind(this);
this.handleTagChange = this.handleTagChange.bind(this);
this.removeIcon = this.removeIcon.bind(this);
this.onTextChange = this.onTextChange.bind(this);
this.renderChip = this.renderChip.bind(this);
this.removeProperty = this.removeProperty.bind(this);
this.state = {
tags: [],
defValue: "",
enabled: true,
allTenants: false,
files: [],
@ -56,19 +71,20 @@ class PlatformCreate extends Component {
description: "",
property: "",
icon: [],
identifier: "",
propertyTypes: [
{key: 0, value: 'String'},
{key: 1, value: 'Number'},
{key: 2, value: 'Boolean'},
{key: 3, value: 'File'}]
}
};
}
/**
* Handles toggle button actions.
* One method is used for all the toggle buttons and, each toggle is identified by the id.
* */
_handleToggle(event) {
handleToggle(event) {
switch (event.target.id) {
case "enabled" : {
let enabled = this.state.enabled;
@ -86,15 +102,64 @@ class PlatformCreate extends Component {
/**
* Triggers the onChange action on property type selection.
* */
_onPropertySelect = (event, index, value) => {
onPropertySelect(event, index, value) {
console.log(this.state.propertyTypes[value]);
this.setState({selectedProperty: value});
};
}
/**
* Handles Chip delete function.
* Removes the tag from state.tags
* */
handleTagDelete(key) {
this.chipData = this.state.tags;
const chipToDelete = this.chipData.map((chip) => chip.key).indexOf(key);
this.chipData.splice(chipToDelete, 1);
this.setState({tags: this.chipData});
}
/**
* Create a tag on Enter key press and set it to the state.
* Clears the tags text field.
* Chip gets two parameters: Key and value.
* */
addTags(event) {
let tags = this.state.tags;
if (event.charCode === 13) {
event.preventDefault();
tags.push({key: Math.floor(Math.random() * 1000), value: event.target.value});
this.setState({tags, defValue: ""});
}
}
/**
* Creates Chip array from state.tags.
* */
renderChip(data) {
return (
<Chip
key={data.key}
onRequestDelete={() => this.handleTagDelete(data.key)}
style={this.styles.chip}
>
{data.value}
</Chip>
);
}
/**
* Set the value for tag.
* */
handleTagChange(event) {
let defaultValue = this.state.defValue;
defaultValue = event.target.value;
this.setState({defValue: defaultValue})
}
/**
* Remove the selected property from the property list.
* */
_removeProperty(property) {
removeProperty(property) {
let properties = this.state.platformProperties;
properties.splice(properties.indexOf(property), 1);
this.setState({platformProperties: properties});
@ -103,28 +168,31 @@ class PlatformCreate extends Component {
/**
* Add a new platform property.
* */
_addProperty() {
addProperty() {
let property = this.state.property;
let selected = this.state.selectedProperty;
this.setState({platformProperties:
this.state.platformProperties.concat([
{
key: property,
value: this.state.propertyTypes[selected].value
}]),
this.setState({
platformProperties:
this.state.platformProperties.concat([
{
key: property,
value: this.state.propertyTypes[selected].value
}]),
property: "",
selectedProperty: 0});
selectedProperty: 0
});
}
/**
* Triggers in onChange event of text fields.
* Text fields are identified by their ids and the value will be persisted in the component state.
* */
_onTextChange = (event, value) => {
onTextChange(event, value) {
let property = this.state.property;
let name = this.state.name;
let description = this.state.description;
let identifier = this.state.identifier;
switch (event.target.id) {
case "name": {
@ -144,32 +212,57 @@ class PlatformCreate extends Component {
this.setState({property: property});
break;
}
case "identifier": {
identifier = value;
this.setState({identifier: identifier});
}
}
};
_onCreatePlatform() {
/**
* Create platform object and call the create platform api.
* */
onCreatePlatform(event) {
//Call the platform create api.
event.preventDefault();
let platform = {};
platform.identifier = this.state.identifier;
platform.name = this.state.name;
platform.description = this.state.description;
platform.tags = this.state.tags;
platform.properties = this.state.platformProperties;
platform.icon = this.state.icon;
platform.enabled = this.state.enabled;
platform.allTenants = this.state.allTenants;
platform.defaultTenantMapping = true;
PlatformMgtApi.createPlatform(platform);
}
/**
* Remove the uploaded icon.
* */
_removeIcon(event) {
removeIcon(event) {
event.preventDefault();
this.setState({icon: []});
}
/**
* Clears the user entered values in the form.
* */
_clearForm() {
this.setState({enabled: true,
clearForm(event) {
event.preventDefault();
this.setState({
enabled: true,
allTenants: false,
files: [],
platformProperties: [],
selectedProperty: 0,
name: "",
description: "",
property: "",})
property: "",
})
}
render() {
@ -180,25 +273,38 @@ class PlatformCreate extends Component {
selectedProperty,
propertyTypes,
name,
tags,
defValue,
description,
property} = this.state;
identifier,
property
} = this.state;
return (
<div className="middle" style={{width: '95%', height: '100%', marginTop: '1%'}}>
<div className="middle createplatformmiddle">
<Card>
<CardTitle title="Create Platform"/>
<CardActions>
<div style={{width: '100%', margin: 'auto', paddingLeft: '10px'}}>
<div className="createplatformcardaction">
<form>
<TextField
hintText="Unique Identifier for Platform."
id="identifier"
floatingLabelText="Identifier*"
floatingLabelFixed={true}
value={identifier}
onChange={this.onTextChange}
/>
<br/>
<TextField
hintText="Enter the Platform Name."
id="name"
floatingLabelText="Name*"
floatingLabelFixed={true}
value={name}
onChange={this._onTextChange.bind(this)}
/><br/>
onChange={this.onTextChange}
/>
<br/>
<TextField
id="description"
hintText="Enter the Platform Description."
@ -207,92 +313,109 @@ class PlatformCreate extends Component {
multiLine={true}
rows={2}
value={description}
onChange={this._onTextChange.bind(this)}
/><br/><br/>
onChange={this.onTextChange}
/>
<br/>
<br/>
<Toggle
id="tenant"
label="Shared with all Tenants"
labelPosition="right"
onToggle={this._handleToggle.bind(this)}
onToggle={this.handleToggle}
toggled={allTenants}
/> <br/>
/>
<br/>
<Toggle
id="enabled"
label="Enabled"
labelPosition="right"
onToggle={this._handleToggle.bind(this)}
onToggle={this.handleToggle}
toggled={enabled}
/> <br/>
/>
<br/>
<TextField
id="tags"
hintText="Enter Platform tags.."
floatingLabelText="Tags*"
floatingLabelFixed={true}
value={defValue}
onChange={this.handleTagChange}
onKeyPress={this.addTags}
/>
<br/>
<div className="createPlatformTagWrapper">
{tags.map(this.renderChip, this)}
</div>
<br/>
<div>
<p style={{color: '#BaBaBa'}}>Platform Properties</p>
<p className="createplatformproperties">Platform Properties</p>
<div id="property-container">
{platformProperties.map((p) => {
return <div key={p.key}>{p.key} : {p.value}
<IconButton onClick={this._removeProperty.bind(this, p)}>
<Close style={{height: '10px', width: '10px'}}/>
<IconButton onClick={this.removeProperty.bind(this, p)}>
<Close className="createplatformpropertyclose"/>
</IconButton>
</div>
})}
</div>
<div style={{display: 'flex'}}>
<div className="createplatformproperty">
<TextField
id="property"
hintText="Property Name"
floatingLabelText="Platform Property*"
floatingLabelFixed={true}
value={this.state.property}
onChange={this._onTextChange.bind(this)}
onChange={this.onTextChange}
/> <em/>
<SelectField
style={{flex: '1 1 23% 1', margin: '0 1%'}}
className="createplatformpropertyselect"
floatingLabelText="Property Type"
value={selectedProperty}
floatingLabelFixed={true}
onChange={this._onPropertySelect.bind(this)}>
onChange={this.onPropertySelect}>
{propertyTypes.map((type) => {
return <MenuItem key={type.key}
value={type.key}
primaryText={type.value}/>
return <MenuItem key={type.key}
value={type.key}
primaryText={type.value}/>
})}
</SelectField>
<IconButton onClick={this._addProperty.bind(this)}>
<IconButton onClick={this.addProperty}>
<AddCircleOutline/>
</IconButton>
<br/>
</div>
</div>
<div>
{/*<p style={{color: '#f44336'}}>{this.state.errors["Icon"]}</p>*/}
<p style={{color: '#BDBDBD'}}>Platform Icon*:</p>
<GridList style={{
display: 'flex',
flexWrap: 'nowrap',
overflowX: 'auto',
}} cols={1.1}>
<p className="createplatformiconp">Platform Icon*:</p>
<GridList className="createplatformicon" cols={1.1}>
{this.state.icon.map((tile) => (
<GridTile key={Math.floor(Math.random() * 1000)}
title={tile.name}
actionIcon={
<IconButton onClick={this._removeIcon.bind(this)}>
<Clear />
</IconButton>}>
<GridTile
key={Math.floor(Math.random() * 1000)}
title={tile.name}
actionIcon={
<IconButton onClick={this.removeIcon}>
<Clear/>
</IconButton>}>
<img src={tile.preview}/>
</GridTile>
))}
{this.state.icon.length === 0 ?
<Dropzone style={
{width: '150px', height: '150px', border: 'dashed #BDBDBD 1px'}
}
accept="image/jpeg, image/png"
onDrop={(icon, rejected) => {this.setState({icon, rejected})}}>
<p style={{margin: '70px 40px 70px 70px'}}>+</p>
</Dropzone> : <div />}
<Dropzone
className="createplatformdropzone"
accept="image/jpeg, image/png"
onDrop={(icon, rejected) => {
this.setState({icon, rejected})
}}
>
<p className="createplatformdropzonep">+</p>
</Dropzone> : <div/>}
</GridList>
</div>
<br/>
<RaisedButton primary={true} label="Create"
onClick={this._onCreatePlatform.bind(this)}/>
<FlatButton label="Cancel" onClick={this._clearForm.bind(this)}/>
<RaisedButton
primary={true} label="Create"
onClick={this.onCreatePlatform}/>
<FlatButton label="Cancel" onClick={this.clearForm}/>
</form>
</div>
</CardActions>
@ -302,7 +425,6 @@ class PlatformCreate extends Component {
}
}
PlatformCreate.prototypes = {
};
PlatformCreate.prototypes = {};
export default PlatformCreate;

@ -19,7 +19,9 @@
import React, {Component} from 'react';
import {withRouter} from 'react-router-dom';
import TextField from 'material-ui/TextField';
import DataTable from '../UIComponents/DataTable';
import AuthHandler from "../../api/authHandler";
import DataTable from '../UIComponents/DataTable/DataTable';
import PlatformMgtApi from "../../api/platformMgtApi";
import {Card, CardActions, CardTitle} from 'material-ui/Card';
/**
@ -33,21 +35,78 @@ import {Card, CardActions, CardTitle} from 'material-ui/Card';
class PlatformListing extends Component {
constructor() {
super();
this.setPlatforms = this.setPlatforms.bind(this);
this.state = {
data: [],
platforms: [],
asc: true
};
}
headers = [
{
data_id: "image",
data_type: "image",
sortable: false,
label: ""
},
{
data_id: "platformName",
data_type: String,
sortable: true,
label: "Platform Name",
sort: this.sortData
},
{
data_id: "enabled",
data_type: String,
sortable: false,
label: "Enabled"
},
{
data_id: "fileBased",
data_type: String,
sortable: false,
label: "File Based"
}
];
componentDidMount() {
let platformsPromise = PlatformMgtApi.getPlatforms();
platformsPromise.then(
response => {
let platforms = this.setPlatforms(response.data);
this.setState({platforms: platforms});
}
).catch(
err => {
AuthHandler.unauthorizedErrorHandler(err);
}
)
}
componentWillMount() {
//Fetch all the applications from backend and create application objects.
/**
* Create platform objects from the response which can be displayed in the table.
* */
setPlatforms(platforms) {
let tmpPlatforms = [];
for (let index in platforms) {
let platform = {};
platform.id = platforms[index].identifier;
platform.platformName = platforms[index].name;
platform.enabled = platforms[index].enabled.toString();
platform.fileBased = platforms[index].fileBased.toString();
tmpPlatforms.push(platform)
}
return tmpPlatforms;
}
/**
* Handles the search action.
* When typing in the search bar, this method will be invoked.
* */
_searchApplications(word) {
searchApplications(word) {
let searchedData = [];
}
@ -55,13 +114,13 @@ class PlatformListing extends Component {
* Handles sort data function and toggles the asc state.
* asc: true : sort in ascending order.
* */
_sortData() {
sortData() {
let isAsc = this.state.asc;
let datas = isAsc?this.data.sort(this._compare):this.data.reverse();
let datas = isAsc ? this.data.sort(this.compare) : this.data.reverse();
this.setState({data: datas, asc: !isAsc});
}
_compare(a, b) {
compare(a, b) {
if (a.applicationName < b.applicationName)
return -1;
if (a.applicationName > b.applicationName)
@ -69,28 +128,29 @@ class PlatformListing extends Component {
return 0;
}
_onRowClick(id) {
onRowClick(id) {
//TODO: Remove this
console.log(id)
}
render() {
return (
<div className="middle" style={{width: '95%', height: '100%', marginTop: '1%'}}>
<Card style={{display: 'flex', flexWrap: 'wrap'}}>
<TextField hintText="Search"
style={{float:'right', paddingRight: '2px'}}
onChange={this._searchApplications.bind(this)}/>
<CardTitle title="Platforms" style={{display: 'flex', flexWrap: 'wrap'}}/>
<div className='middle listingplatformmiddle'>
<Card className='listingplatformcard'>
<TextField hintText="Search" onChange={this.searchApplications.bind(this)}
className='listingplatformsearch'/>
<CardTitle title="Platforms" className='listingplatformTitle'/>
<CardActions>
</CardActions>
<DataTable headers={this.headers}
data={this.data}
handleRowClick={this._onRowClick.bind(this)}
noDataMessage={{type: 'button', text: 'Create Platform'}}/>
<DataTable
headers={this.headers}
data={this.state.platforms}
handleRowClick={this.onRowClick.bind(this)}
noDataMessage={{type: 'button', text: 'Create Platform'}}/>
</Card>
</div>);
</div>
);
}
}

@ -0,0 +1,75 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import PropTypes from 'prop-types';
import React, {Component} from 'react';
import Theme from '../../../theme';
/**
* Component for holding uploaded image.
* This component has the feature to remove selected image from the array.
* */
class AppImage extends Component {
constructor() {
super();
this.removeImage = this.removeImage.bind(this);
this.scriptId = "appImage";
}
componentWillMount() {
/**
*Loading the theme files based on the the user-preference.
*/
Theme.insertThemingScripts(this.scriptId);
}
componentWillUnmount() {
Theme.removeThemingScripts(this.scriptId);
}
/**
* Triggers the parent method to remove the selected image.
* @param event: The click event of the component.
* */
removeImage(event) {
event.preventDefault();
this.props.onRemove(event.target.id);
}
render() {
const {image, imageId} = this.props;
return (
<div className="image-container" style={this.props.imageStyles}>
<img src={image} className="image" id={imageId}/>
<div className="btn-content">
<i className="close-btn" id={imageId} onClick={this.removeImage}>X</i>
</div>
</div>
)
}
}
AppImage.propTypes = {
image: PropTypes.string,
imageId: PropTypes.string,
onRemove: PropTypes.func,
imageStyles: PropTypes.object
};
export default AppImage;

@ -15,35 +15,36 @@
* specific language governing permissions and limitations
* under the License.
*/
import React, {Component} from 'react';
import DataTable from '../UIComponents/DataTable';
/**
*
* ***NEW***
* The Publisher overview component.
* This component could be used to view app analytics.
* i.e number of overall downloads, ratings ect.
* */
class PublisherOverview extends Component {
import React, {Component} from 'react';
import Theme from '../../../theme';
class Chip extends Component {
constructor() {
super();
this.scriptId = "chip";
}
componentWillMount() {
/**
*Loading the theme files based on the the user-preference.
*/
Theme.insertThemingScripts(this.scriptId);
}
render() {
componentWillUnmount() {
Theme.removeThemingScripts(this.scriptId);
}
render() {
return (
<div>
Overview
<div className="chip">
{this.props.image?<img src={this.props.image} alt="Person" width="96" height="96" />:<div/>}
{this.props.text}
<span className="close-btn" >&times;</span>
</div>
);
)
}
}
export default PublisherOverview;
export default Chip;

@ -16,6 +16,7 @@
* under the License.
*/
import Theme from '../../../theme';
import PropTypes from 'prop-types';
import React, {Component} from 'react';
import DataTableRow from './DataTableRow';
@ -52,19 +53,33 @@ class DataTable extends Component {
constructor() {
super();
this.handleRowClick = this.handleRowClick.bind(this);
this.handleBtnClick = this.handleBtnClick.bind(this);
this.state = {
data: [],
headers: [],
}
};
this.scriptId = "data-table"
};
componentWillMount() {
this.setState({data: this.props.data, headers: this.props.headers})
console.log("Will mount", this.props.data); //TODO: Remove this
this.setState({data: this.props.data, headers: this.props.headers}, Theme.insertThemingScripts(this.scriptId));
/**
*Loading the theme files based on the the user-preference.
*/
}
componentWillUnmount() {
Theme.removeThemingScripts(this.scriptId);
}
shouldComponentUpdate(nextProps, nextState) {
console.log("next Props", nextProps.data);
if (!nextProps.data) {
this.setState({data: nextState.data});
return true;
}
this.setState({data: nextProps.data});
return true;
}
@ -73,13 +88,18 @@ class DataTable extends Component {
* Triggers when user click on table row.
* This method invokes the parent method handleRowClick, which is passed via props.
* */
_handleRowClick(id) {
handleRowClick(id) {
this.props.handleRowClick(id);
}
handleBtnClick(id) {
this.props.handleButtonClick(id);
}
render() {
const {data, headers} = this.state;
//TODO: Remove this
console.log(data);
let noDataContent = null;
@ -90,22 +110,30 @@ class DataTable extends Component {
if (data) {
return (<Table
selectable={ false }>
<TableHeader displaySelectAll={ false }
adjustForCheckbox={ false }>
selectable={false}>
<TableHeader displaySelectAll={false} adjustForCheckbox={false}>
<TableRow>
{headers.map((header) => {
return (<DataTableHeader key={header.data_id}
style={{display: 'flex'}} header={header}/>)
}
return (
<DataTableHeader
key={header.data_id}
className="datatableRowColumn"
header={header}
/>
)}
)}
</TableRow>
</TableHeader>
<TableBody>
{data.map((dataItem) =>{
return (<DataTableRow key={dataItem.id}
dataItem={dataItem}
handleClick={this._handleRowClick.bind(this)}/>)
{data.map((dataItem) => {
return (
<DataTableRow
key={dataItem.id}
dataItem={dataItem}
handleButtonClick={this.handleBtnClick}
handleClick={this.handleRowClick}
/>
)
})}
</TableBody>
</Table>)

@ -16,6 +16,7 @@
* under the License.
*/
import Theme from '../../../theme';
import PropTypes from 'prop-types';
import React, {Component} from 'react';
import FlatButton from 'material-ui/FlatButton';
@ -29,13 +30,27 @@ class DataTableHeader extends Component {
constructor() {
super();
this.tableHeaderClick = this.tableHeaderClick.bind(this);
this.scriptId = "data-table";
}
componentWillMount() {
/**
*Loading the theme files based on the the user-preference.
*/
Theme.insertThemingScripts(this.scriptId);
}
componentWillUnmount() {
Theme.removeThemingScripts(this.scriptId);
}
/**
* The onClick function of the table header.
* Invokes the function passed in the header object.
* */
_tableHeaderClick() {
tableHeaderClick() {
this.props.header.sort();
}
@ -47,21 +62,18 @@ class DataTableHeader extends Component {
* else create a span element with label as the table header.
* */
if (this.props.header.sortable) {
headerCell = <FlatButton label={this.props.header.label}
onClick={this._tableHeaderClick.bind(this)}
style={{color: '#bdbdbd'}}/>;
headerCell =
<FlatButton
label={this.props.header.label}
onClick={this.tableHeaderClick}
className="sortableHeaderCell"
/>
} else {
headerCell = <span style={{position: 'relative',
paddingLeft: '16px',
paddingRight: '16px',
textTransform: 'uppercase',
fontWeight: 'normal',
color: '#bdbdbd',
fontSize: '14px'}}>{this.props.header.label}</span>;
headerCell = <span className="notsortableHeaderCell">{this.props.header.label}</span>;
}
return (
<TableHeaderColumn key={this.props.header.id} style={{paddingLeft: '0px'}} >
<TableHeaderColumn key={this.props.header.id} className="datatableHeaderColumn">
{headerCell}
</TableHeaderColumn>
);

@ -0,0 +1,112 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import Theme from '../../../theme';
import PropTypes from 'prop-types';
import React, {Component} from 'react';
import IconButton from 'material-ui/IconButton';
import Create from 'material-ui/svg-icons/content/create'
import {TableRow, TableRowColumn} from 'material-ui/Table';
import Avatar from 'material-ui/Avatar';
/**
* Data table row component.
* This component created a row in the data table according to the props.
* */
class DataTableRow extends Component {
constructor() {
super();
this.state = {
dataItem: {}
};
this.scriptId = "data-table";
}
componentWillMount() {
this.setState({dataItem: this.props.dataItem});
/**
*Loading the theme files based on the the user-preference.
*/
Theme.insertThemingScripts(this.scriptId);
}
componentWillUnmount() {
Theme.removeThemingScripts(this.scriptId);
}
/**
* Triggers the click event on the data table row.
* */
handleClick(event) {
event.stopPropagation();
this.props.handleClick(this.state.dataItem.id);
}
handleBtnClick(event) {
event.stopPropagation();
console.log(event.target['id'])
this.props.handleButtonClick(event.target['id']);
}
render() {
const {dataItem} = this.state;
return (
<TableRow
key={this.props.key}
onClick={this.handleClick.bind(this)}
>
<TableRowColumn
className="datatableRowColumn"
key={Math.random()}
>
<Avatar>{dataItem.name}</Avatar>
</TableRowColumn>
{Object.keys(dataItem).map((key) => {
if (key !== 'id') {
return (
<TableRowColumn
className="datatableRowColumn"
key={key}
>
{dataItem[key]}
</TableRowColumn>)
}
})}
<TableRowColumn
className="datatableRowColumn"
key={dataItem.id}
>
<IconButton id={dataItem.id} onClick={this.handleBtnClick.bind(this)}>
<Create id={dataItem.id}/>
</IconButton>
</TableRowColumn>
</TableRow>
);
}
}
DataTableRow.propTypes = {
onClick: PropTypes.func,
data: PropTypes.object
};
export default DataTableRow;

@ -1,70 +0,0 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import PropTypes from 'prop-types';
import React, {Component} from 'react';
import {TableRow, TableRowColumn} from 'material-ui/Table';
/**
* Data table row component.
* This component created a row in the data table according to the props.
* */
class DataTableRow extends Component {
constructor() {
super();
this.state = {
dataItem: {}
}
}
componentWillMount() {
this.setState({dataItem: this.props.dataItem})
}
/**
* Triggers the click event on the data table row.
* */
_handleClick() {
this.props.handleClick(this.state.dataItem.id);
}
render() {
const {dataItem} = this.state;
return (
<TableRow key={this.props.key} onClick={this._handleClick.bind(this)} >
{Object.keys(dataItem).map((key) => {
if (key !== 'id') {
return <TableRowColumn style={{alignItems: 'center'}}
key={key}>{dataItem[key]}</TableRowColumn>
} else {
return <TableRowColumn key={key}/>
}
} )}
</TableRow>
);
}
}
DataTableRow.propTypes = {
onClick: PropTypes.func,
data: PropTypes.object
};
export default DataTableRow;

@ -0,0 +1,70 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import PropTypes from 'prop-types';
import React, {Component} from 'react';
import Theme from '../../../theme'
/**
* Custom React component for Application View.
* */
class Drawer extends Component {
constructor() {
super();
this.closeDrawer = this.closeDrawer.bind(this);
this.scriptId = "drawer";
}
componentWillMount() {
/**
*Loading the theme files based on the the user-preference.
*/
Theme.insertThemingScripts(this.scriptId);
}
componentWillUnmount() {
Theme.removeThemingScripts(this.scriptId);
}
/**
* Closes the drawer.
* */
closeDrawer() {
this.props.onClose();
}
render() {
return (
<div>
<div id="app-view" className="app-view-drawer" style={this.props.style}>
<a onClick={this.closeDrawer} className="drawer-close-btn"><i className="fw fw-uncheck"></i></a>
{this.props.children}
</div>
</div>
);
}
}
Drawer.propTypes = {
style: PropTypes.object,
children: PropTypes.node,
onClose: PropTypes.func
};
export default Drawer;

@ -0,0 +1,63 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import PropTypes from 'prop-types';
import React, {Component} from 'react';
import Theme from '../../../theme';
/**
* Floating Action button.
* */
class FloatingButton extends Component {
constructor() {
super();
this.scriptId = "floatingButton";
}
componentWillMount() {
/**
*Loading the theme files based on the the user-preference.
*/
Theme.insertThemingScripts(this.scriptId);
}
componentWillUnmount() {
Theme.removeThemingScripts(this.scriptId);
}
handleClick(event) {
this.props.onClick(event);
}
render() {
let classes = 'btn-circle ' + this.props.className;
return (
<div className={classes} onClick={this.handleClick.bind(this)}>
<i className="fw fw-add"></i>
</div>
)
}
}
FloatingButton.propTypes = {
classNames: PropTypes.string,
onClick: PropTypes.func
};
export default FloatingButton;

@ -0,0 +1,88 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import PropTypes from 'prop-types';
import React, {Component} from 'react';
import Dropzone from "react-dropzone";
import {Row} from "reactstrap";
import Theme from '../../../theme';
class ImageUploader extends Component {
constructor() {
super();
this.setImages = this.setImages.bind(this);
this.state = {
images: []
};
this.scriptId = "imageUploader";
}
componentWillMount() {
/**
*Loading the theme files based on the the user-preference.
*/
Theme.insertThemingScripts(this.scriptId);
}
componentWillUnmount() {
Theme.removeThemingScripts(this.scriptId);
}
setImages(images) {
this.props.setImages(images);
}
render() {
let {images, height, width, accepted, multiple, maxAmount} = this.props;
return (
<div id="screenshot-container">
<Row>
{images.map((tile) => (
<input type="image" src={tile[0].preview} onClick=""/>
)
)}
</Row>
{this.state.screenshots.length < maxAmount ?
<Dropzone
className="add-image"
accept="image/jpeg, image/png"
onDrop={(accepted, rejected) => {
this.setImages(accepted);
}}
>
<p className="add-image-symbol">+</p>
</Dropzone> : <div/>}
</div>
);
}
}
ImageUploader.prototypes = {
height: PropTypes.string,
width: PropTypes.string,
accepted: PropTypes.array,
multiple: PropTypes.bool,
maxAmount: PropTypes.number,
setImages: PropTypes.func
};
export default ImageUploader;

@ -0,0 +1,58 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React, {Component} from 'react';
import {Col, Row} from "reactstrap";
import Theme from '../../../theme'
class NotificationItem extends Component {
constructor() {
super();
this.scriptId = "notification";
}
componentWillMount() {
/**
*Loading the theme files based on the the user-preference.
*/
Theme.insertThemingScripts(this.scriptId);
}
componentWillUnmount() {
Theme.removeThemingScripts(this.scriptId);
}
render() {
return (
<div>
<Row>
<Col>
<div className="notification-app-icon small">
<img/>
</div>
</Col>
<Col>
<p>Your application, Facebook has been published.</p>
</Col>
</Row>
</div>
);
}
}
export default NotificationItem;

@ -0,0 +1,84 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React, {Component} from 'react';
import {Col, Row} from "reactstrap";
import './notification.css';
import Theme from '../../../theme'
class NotificationView extends Component {
constructor() {
super();
this.scriptId = "notification";
}
componentWillMount() {
/**
*Loading the theme files based on the the user-preference.
*/
Theme.insertThemingScripts(this.scriptId);
}
componentWillUnmount() {
Theme.removeThemingScripts(this.scriptId);
}
render() {
return (
<div id="notification-view-content">
<div>
<Row id="notification-content">
<Col xs="3">
<div className="notification-app-icon medium">
</div>
</Col>
<Col xs="9">
<Row>
<span><strong>Application Name</strong></span>
</Row>
<Row>
<span>Version 1.0</span>
</Row>
<Row>
<p id="app-reject-msg">Your Application was rejected</p>
</Row>
</Col>
</Row>
<hr/>
<Row id="notification-content">
<Col xs="12">
<p>Following validations were detected in your review submission.
Please attend to them and re-submit</p>
<ul>
<li>sdjjfsdfsdfkjs shdfjhlkds hflkhfdslkd </li>
<li>sdfkds jfdsljfklsdfjksdjlksdjdlkf</li>
<li>sfksdf slkjskd jfjds lkfjdsfdsfdslkf sjf lkdsf</li>
<li>skfjslkdjfsdjfjksldjf sdkl jflkds jfkslfjs</li>
<li>ksdf jks;kshflk hlkjhds lkjhdsklhsd lkf</li>
<li> jsdljflksd jfklsdfskljfkjshf;ks ldj</li>
</ul>
</Col>
</Row>
</div>
</div>
);
}
}
export default NotificationView;

@ -0,0 +1,51 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React, {Component} from 'react';
import Theme from '../../../theme';
class Switch extends Component {
constructor() {
super();
this.scriptId = "switch";
}
componentWillMount() {
/**
*Loading the theme files based on the the user-preference.
*/
Theme.insertThemingScripts(this.scriptId);
}
componentWillUnmount() {
Theme.removeThemingScripts(this.scriptId);
}
render() {
const {height, width} = this.props;
return (
<label className="switch">
<input type="checkbox" onChange={this.props.onChange}/>
<span className="slider round"></span>
</label>
)
}
}
export default Switch;

@ -17,16 +17,10 @@
*/
import qs from 'qs';
import PropTypes from 'prop-types';
import React, {Component} from 'react';
import Checkbox from 'material-ui/Checkbox';
import {Redirect, Switch} from 'react-router-dom';
import RaisedButton from 'material-ui/RaisedButton';
import {Card, CardActions, CardTitle} from 'material-ui/Card';
import {TextValidator, ValidatorForm} from 'react-material-ui-form-validator';
//todo: remove the {TextValidator, ValidatorForm} and implement it manually.
import AuthHandler from '../../../api/authHandler';
import {Button, Card, CardBlock, CardTitle, Col, Form, FormGroup, Input, Label} from 'reactstrap';
/**
* The Login Component.
@ -40,11 +34,12 @@ class Login extends Component {
constructor() {
super();
this.state = {
isLoggedIn: true,
isLoggedIn: false,
referrer: "/",
userName: "",
password: "",
rememberMe: true
rememberMe: true,
errors: {}
}
}
@ -61,12 +56,14 @@ class Login extends Component {
handleLogin(event) {
event.preventDefault();
this.validateForm();
}
/**
* Handles the username field change event.
* */
onUserNameChange(event) {
onUserNameChange(event, value) {
console.log(event.target.value);
this.setState(
{
userName: event.target.value
@ -77,7 +74,7 @@ class Login extends Component {
/**
* Handles the password field change event.
* */
onPasswordChange(event) {
onPasswordChange(event, value) {
this.setState(
{
password: event.target.value
@ -96,49 +93,69 @@ class Login extends Component {
);
}
/**
* Validate the login form.
* */
validateForm() {
let errors = {};
let validationFailed = true;
if (!this.state.password) {
errors["passwordError"] = "Password is Required";
validationFailed = true;
} else {
validationFailed = false;
}
if (!this.state.userName) {
errors["userNameError"] = "User Name is Required";
validationFailed = true;
} else {
validationFailed = false;
}
if (validationFailed) {
this.setState({errors: errors}, console.log(errors));
} else {
let loginPromis = AuthHandler.login(this.state.userName, this.state.password);
loginPromis.then(response => {
console.log(AuthHandler.getUser());
this.setState({isLoggedIn: AuthHandler.getUser()});
})
}
}
render() {
if (!this.state.isLoggedIn) {
return (
<div>
<div id="login-container">
{/*TODO: Style the components.*/}
<Card id="login-card">
<CardBlock>
<CardTitle>WSO2 IoT APP Publisher</CardTitle>
<Form onSubmit={this.handleLogin.bind(this)}>
<FormGroup row>
<Label for="userName" sm={2}>User Name:</Label>
<Col sm={10}>
<Input type="text" name="userName" id="userName" placeholder="User Name"
onChange={this.onUserNameChange.bind(this)}/>
</Col>
<Card>
<CardTitle title="WSO2 IoT App Publisher"/>
<CardActions>
<ValidatorForm
ref="form"
onSubmit={this.handleLogin.bind(this)}
onError={errors => console.log(errors)}>
<TextValidator
floatingLabelText="User Name"
floatingLabelFixed={true}
onChange={this.onUserNameChange.bind(this)}
name="userName"
validators={['required']}
errorMessages={['User Name is required']}
value={this.state.userName}
/>
<br/>
<TextValidator
floatingLabelText="Password"
floatingLabelFixed={true}
onChange={this.onPasswordChange.bind(this)}
name="password"
type="password"
value={this.state.password}
validators={['required']}
errorMessages={['Password is required']}
/>
<br/>
<Checkbox label="Remember me."
onCheck={this.handleRememberMe.bind(this)}
checked={this.state.rememberMe}/>
<br/>
<RaisedButton type="submit" label="Login"/>
</ValidatorForm>
</CardActions>
</FormGroup>
<FormGroup row>
<Label for="password" sm={2}>Password:</Label>
<Col sm={10}>
<Input type="password" name="text" id="password" placeholder="Password"
onChange={this.onPasswordChange.bind(this)}/>
</Col>
</FormGroup>
<FormGroup check row>
<Col sm={{size: 10, offset: 2}}>
<Button type="submit" id="login-btn">Login</Button>
</Col>
</FormGroup>
</Form>
</CardBlock>
</Card>
</div>);
} else {

@ -18,16 +18,15 @@
import Login from './User/Login/Login';
import NotFound from './Error/NotFound';
import BaseLayout from './Base/BaseLayout';
import BaseLayout from './AppPublisherBase/BaseLayout';
import PlatformCreate from './Platform/PlatformCreate';
import PlatformListing from './Platform/PlatformListing';
import PublisherOverview from './Overview/PublisherOverview';
import ApplicationCreate from './Application/ApplicationCreate';
import ApplicationCreate from './Application/Create/ApplicationCreate';
import ApplicationListing from './Application/ApplicationListing';
import ApplicationEdit from './Application/Edit/Base/ApplicationEditBaseLayout';
/**
* Contains all UI components related to Application, Login and Platform
*/
export {Login, BaseLayout, ApplicationCreate, ApplicationListing, PlatformListing, NotFound, PublisherOverview,
PlatformCreate};
export {Login, BaseLayout, ApplicationCreate, ApplicationListing, PlatformListing, NotFound, PlatformCreate, ApplicationEdit};

@ -1,7 +1,6 @@
{
"theme" : {
"current" : "default",
"default" : "lightBaseTheme",
"custom" : "custom-theme"
"theme": {
"type": "default",
"value": "lightBaseTheme"
}
}

@ -16,10 +16,10 @@
* under the License.
*/
import './index.css';
import React from 'react';
import Publisher from './App';
import ReactDOM from 'react-dom';
import 'bootstrap/dist/css/bootstrap.css';
import registerServiceWorker from './registerServiceWorker';
/**

@ -1,19 +0,0 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
export class ApplicationStore {};

@ -1,18 +0,0 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
export class PlatformStore{};

@ -1,19 +0,0 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
export class ReviewStore{};

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

Loading…
Cancel
Save