mirror of
https://repository.entgra.net/community/device-mgt-core.git
synced 2025-10-06 02:01:45 +00:00
Add android enterprise support for IoTS.
This commit is contained in:
parent
4e4b2fb5c8
commit
5cb706f24b
@ -0,0 +1,59 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2019, Entgra (Pvt) Ltd. (http://www.entgra.io) All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Entgra (Pvt) Ltd. 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.dto;
|
||||||
|
|
||||||
|
public class ApiRegistrationProfile {
|
||||||
|
|
||||||
|
private String applicationName;
|
||||||
|
private String tags[];
|
||||||
|
private boolean isAllowedToAllDomains;
|
||||||
|
private 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 setAllowedToAllDomains(boolean allowedToAllDomains) {
|
||||||
|
isAllowedToAllDomains = allowedToAllDomains;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isMappingAnExistingOAuthApp() {
|
||||||
|
return isMappingAnExistingOAuthApp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMappingAnExistingOAuthApp(boolean mappingAnExistingOAuthApp) {
|
||||||
|
isMappingAnExistingOAuthApp = mappingAnExistingOAuthApp;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -94,6 +94,18 @@ public class ApplicationDTO {
|
|||||||
required = true)
|
required = true)
|
||||||
private List<ApplicationReleaseDTO> applicationReleaseDTOs;
|
private List<ApplicationReleaseDTO> applicationReleaseDTOs;
|
||||||
|
|
||||||
|
@ApiModelProperty(name = "packageName",
|
||||||
|
value = "package name of the application")
|
||||||
|
private String packageName;
|
||||||
|
|
||||||
|
public String getPackageName() {
|
||||||
|
return packageName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPackageName(String packageName) {
|
||||||
|
this.packageName = packageName;
|
||||||
|
}
|
||||||
|
|
||||||
public int getId() {
|
public int getId() {
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,44 @@
|
|||||||
|
package org.wso2.carbon.device.application.mgt.common.dto;
|
||||||
|
|
||||||
|
import org.wso2.carbon.device.mgt.common.DeviceIdentifier;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class ApplicationPolicyDTO {
|
||||||
|
ApplicationDTO applicationDTO;
|
||||||
|
String policy;
|
||||||
|
List<DeviceIdentifier> deviceIdentifierList;
|
||||||
|
String action;
|
||||||
|
|
||||||
|
public List<DeviceIdentifier> getDeviceIdentifierList() {
|
||||||
|
return deviceIdentifierList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDeviceIdentifierList(List<DeviceIdentifier> deviceIdentifierList) {
|
||||||
|
this.deviceIdentifierList = deviceIdentifierList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAction() {
|
||||||
|
return action;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAction(String action) {
|
||||||
|
this.action = action;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ApplicationDTO getApplicationDTO() {
|
||||||
|
return applicationDTO;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setApplicationDTO(ApplicationDTO applicationDTO) {
|
||||||
|
this.applicationDTO = applicationDTO;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPolicy() {
|
||||||
|
return policy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPolicy(String policy) {
|
||||||
|
this.policy = policy;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -82,6 +82,18 @@ public class Application {
|
|||||||
required = true)
|
required = true)
|
||||||
private List<ApplicationRelease> applicationReleases;
|
private List<ApplicationRelease> applicationReleases;
|
||||||
|
|
||||||
|
@ApiModelProperty(name = "packageName",
|
||||||
|
value = "package name of the application")
|
||||||
|
private String packageName;
|
||||||
|
|
||||||
|
public String getPackageName() {
|
||||||
|
return packageName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPackageName(String packageName) {
|
||||||
|
this.packageName = packageName;
|
||||||
|
}
|
||||||
|
|
||||||
public int getId() { return id; }
|
public int getId() { return id; }
|
||||||
|
|
||||||
public void setId(int id) { this.id = id; }
|
public void setId(int id) { this.id = id; }
|
||||||
|
|||||||
@ -273,4 +273,6 @@ public interface ApplicationManager {
|
|||||||
*/
|
*/
|
||||||
String getPlistArtifact(String uuid) throws ApplicationManagementException;
|
String getPlistArtifact(String uuid) throws ApplicationManagementException;
|
||||||
|
|
||||||
|
List<ApplicationReleaseDTO> getReleaseByPackageNames(List<String> packageIds) throws ApplicationManagementException;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -80,6 +80,10 @@
|
|||||||
org.apache.commons.codec.digest;version="${commons-codec.wso2.osgi.version.range}",
|
org.apache.commons.codec.digest;version="${commons-codec.wso2.osgi.version.range}",
|
||||||
org.wso2.carbon.base,
|
org.wso2.carbon.base,
|
||||||
com.dd.*,
|
com.dd.*,
|
||||||
|
org.wso2.carbon.identity.jwt.client.extension.*,
|
||||||
|
org.wso2.carbon.apimgt.application.extension.*,
|
||||||
|
org.apache.commons.httpclient,
|
||||||
|
org.apache.commons.httpclient.methods,
|
||||||
org.apache.commons.validator.routines
|
org.apache.commons.validator.routines
|
||||||
</Import-Package>
|
</Import-Package>
|
||||||
<Embed-Dependency>apk-parser;scope=compile|runtime;inline=false</Embed-Dependency>
|
<Embed-Dependency>apk-parser;scope=compile|runtime;inline=false</Embed-Dependency>
|
||||||
@ -240,6 +244,11 @@
|
|||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.wso2.carbon.devicemgt</groupId>
|
||||||
|
<artifactId>org.wso2.carbon.apimgt.application.extension</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
||||||
|
|
||||||
|
|||||||
@ -117,4 +117,7 @@ public interface ApplicationReleaseDAO {
|
|||||||
boolean hasExistInstallableAppRelease(String releaseUuid, String installableStateName, int tenantId)
|
boolean hasExistInstallableAppRelease(String releaseUuid, String installableStateName, int tenantId)
|
||||||
throws ApplicationManagementDAOException;
|
throws ApplicationManagementDAOException;
|
||||||
|
|
||||||
}
|
List<ApplicationReleaseDTO> getReleaseByPackages(List<String> packages, int tenantId)
|
||||||
|
throws ApplicationManagementDAOException;
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
|
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
|
||||||
*
|
*
|
||||||
* WSO2 Inc. licenses this file to you under the Apache License,
|
* WSO2 Inc. licenses this file to you under the Apache License,
|
||||||
@ -36,8 +36,9 @@ import java.sql.SQLException;
|
|||||||
import java.sql.Statement;
|
import java.sql.Statement;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.StringJoiner;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GenericApplicationReleaseDAOImpl holds the implementation of ApplicationRelease related DAO operations.
|
* GenericApplicationReleaseDAOImpl holds the implementation of ApplicationRelease related DAO operations.
|
||||||
*/
|
*/
|
||||||
public class GenericApplicationReleaseDAOImpl extends AbstractDAOImpl implements ApplicationReleaseDAO {
|
public class GenericApplicationReleaseDAOImpl extends AbstractDAOImpl implements ApplicationReleaseDAO {
|
||||||
@ -278,9 +279,15 @@ public class GenericApplicationReleaseDAOImpl extends AbstractDAOImpl implements
|
|||||||
+ "APP_HASH_VALUE = ?, "
|
+ "APP_HASH_VALUE = ?, "
|
||||||
+ "SHARED_WITH_ALL_TENANTS = ?, "
|
+ "SHARED_WITH_ALL_TENANTS = ?, "
|
||||||
+ "APP_META_INFO = ?, "
|
+ "APP_META_INFO = ?, "
|
||||||
+ "SUPPORTED_OS_VERSIONS = ?, "
|
+ "SUPPORTED_OS_VERSIONS = ?";
|
||||||
+ "CURRENT_STATE = ? "
|
|
||||||
+ "WHERE ID = ? AND TENANT_ID = ? ";
|
if (applicationReleaseDTO.getCurrentState() != null) {
|
||||||
|
sql += ", CURRENT_STATE = ? ";
|
||||||
|
}
|
||||||
|
|
||||||
|
sql += " WHERE ID = ? AND TENANT_ID = ? ";
|
||||||
|
|
||||||
|
int x = 17;
|
||||||
try {
|
try {
|
||||||
Connection connection = this.getDBConnection();
|
Connection connection = this.getDBConnection();
|
||||||
try (PreparedStatement statement = connection.prepareStatement(sql)) {
|
try (PreparedStatement statement = connection.prepareStatement(sql)) {
|
||||||
@ -300,9 +307,12 @@ public class GenericApplicationReleaseDAOImpl extends AbstractDAOImpl implements
|
|||||||
statement.setBoolean(14, applicationReleaseDTO.getIsSharedWithAllTenants());
|
statement.setBoolean(14, applicationReleaseDTO.getIsSharedWithAllTenants());
|
||||||
statement.setString(15, applicationReleaseDTO.getMetaData());
|
statement.setString(15, applicationReleaseDTO.getMetaData());
|
||||||
statement.setString(16, applicationReleaseDTO.getSupportedOsVersions());
|
statement.setString(16, applicationReleaseDTO.getSupportedOsVersions());
|
||||||
statement.setString(17, applicationReleaseDTO.getCurrentState().toUpperCase());
|
|
||||||
statement.setInt(18, applicationReleaseDTO.getId());
|
if (applicationReleaseDTO.getCurrentState() != null) {
|
||||||
statement.setInt(19, tenantId);
|
statement.setString(x++, applicationReleaseDTO.getCurrentState().toUpperCase());
|
||||||
|
}
|
||||||
|
statement.setInt(x++, applicationReleaseDTO.getId());
|
||||||
|
statement.setInt(x++, tenantId);
|
||||||
if (statement.executeUpdate() == 0) {
|
if (statement.executeUpdate() == 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -550,4 +560,65 @@ public class GenericApplicationReleaseDAOImpl extends AbstractDAOImpl implements
|
|||||||
throw new ApplicationManagementDAOException(msg, e);
|
throw new ApplicationManagementDAOException(msg, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ApplicationReleaseDTO> getReleaseByPackages(List<String> packages, int tenantId) throws
|
||||||
|
ApplicationManagementDAOException {
|
||||||
|
|
||||||
|
String sql = "SELECT "
|
||||||
|
+ "AR.ID AS RELEASE_ID, "
|
||||||
|
+ "AR.DESCRIPTION AS RELEASE_DESCRIPTION, "
|
||||||
|
+ "AR.VERSION AS RELEASE_VERSION, "
|
||||||
|
+ "AR.UUID AS RELEASE_UUID, "
|
||||||
|
+ "AR.RELEASE_TYPE AS RELEASE_TYPE, "
|
||||||
|
+ "AR.INSTALLER_LOCATION AS AP_RELEASE_STORED_LOC, "
|
||||||
|
+ "AR.ICON_LOCATION AS AP_RELEASE_ICON_LOC, "
|
||||||
|
+ "AR.BANNER_LOCATION AS AP_RELEASE_BANNER_LOC, "
|
||||||
|
+ "AR.SC_1_LOCATION AS AP_RELEASE_SC1, "
|
||||||
|
+ "AR.SC_2_LOCATION AS AP_RELEASE_SC2, "
|
||||||
|
+ "AR.SC_3_LOCATION AS AP_RELEASE_SC3, "
|
||||||
|
+ "AR.APP_HASH_VALUE AS RELEASE_HASH_VALUE, "
|
||||||
|
+ "AR.APP_PRICE AS RELEASE_PRICE, "
|
||||||
|
+ "AR.APP_META_INFO AS RELEASE_META_INFO, "
|
||||||
|
+ "AR.PACKAGE_NAME AS PACKAGE_NAME, "
|
||||||
|
+ "AR.SUPPORTED_OS_VERSIONS AS RELEASE_SUP_OS_VERSIONS, "
|
||||||
|
+ "AR.RATING AS RELEASE_RATING, "
|
||||||
|
+ "AR.CURRENT_STATE AS RELEASE_CURRENT_STATE, "
|
||||||
|
+ "AR.RATED_USERS AS RATED_USER_COUNT "
|
||||||
|
+ "FROM AP_APP_RELEASE AS AR "
|
||||||
|
+ "WHERE AR.TENANT_ID = ? AND AR.PACKAGE_NAME IN (";
|
||||||
|
|
||||||
|
StringJoiner joiner = new StringJoiner(",", sql, ")");
|
||||||
|
packages.stream().map(ignored -> "?").forEach(joiner::add);
|
||||||
|
sql = joiner.toString();
|
||||||
|
|
||||||
|
try {
|
||||||
|
Connection connection = this.getDBConnection();
|
||||||
|
try (PreparedStatement statement = connection.prepareStatement(sql)) {
|
||||||
|
statement.setInt(1, tenantId);
|
||||||
|
for (int y = 0; y < packages.size(); y++) {
|
||||||
|
// y +2 because tenantId parameter is 1 and the counter is starting at o for y
|
||||||
|
statement.setString(y+2, packages.get(y));
|
||||||
|
}
|
||||||
|
try (ResultSet resultSet = statement.executeQuery()) {
|
||||||
|
List<ApplicationReleaseDTO> releaseDTOs = new ArrayList<>();
|
||||||
|
while (resultSet.next()) {
|
||||||
|
releaseDTOs.add(DAOUtil.constructAppReleaseDTO(resultSet));
|
||||||
|
}
|
||||||
|
return releaseDTOs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (DBConnectionException e) {
|
||||||
|
String msg = "Database connection error occurred while trying to get application release details which has "
|
||||||
|
+ "packages: " + String.join(", ", packages);
|
||||||
|
log.error(msg, e);
|
||||||
|
throw new ApplicationManagementDAOException(msg, e);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
String msg =
|
||||||
|
"Error while getting application release details which has packages: " + String.join(", ", packages)
|
||||||
|
+ " , while executing the query " + sql;
|
||||||
|
log.error(msg, e);
|
||||||
|
throw new ApplicationManagementDAOException(msg, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -176,13 +176,15 @@ public class ApplicationManagerImpl implements ApplicationManager {
|
|||||||
@Override
|
@Override
|
||||||
public Application createPublicApp(PublicAppWrapper publicAppWrapper, ApplicationArtifact applicationArtifact)
|
public Application createPublicApp(PublicAppWrapper publicAppWrapper, ApplicationArtifact applicationArtifact)
|
||||||
throws ApplicationManagementException {
|
throws ApplicationManagementException {
|
||||||
|
int tenantId = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantId(true);
|
||||||
|
|
||||||
if (log.isDebugEnabled()) {
|
if (log.isDebugEnabled()) {
|
||||||
log.debug("Public app creating request is received. App name: " + publicAppWrapper.getName()
|
log.debug("Public app creating request is received. App name: " + publicAppWrapper.getName()
|
||||||
+ " Device Type: " + publicAppWrapper.getDeviceType());
|
+ " Device Type: " + publicAppWrapper.getDeviceType());
|
||||||
}
|
}
|
||||||
|
|
||||||
String publicAppStorePath = "";
|
String publicAppStorePath = "";
|
||||||
if (DeviceTypes.ANDROID.toString().equals(publicAppWrapper.getDeviceType())) {
|
if (DeviceTypes.ANDROID.toString().toLowerCase().equals(publicAppWrapper.getDeviceType())) {
|
||||||
publicAppStorePath = Constants.GOOGLE_PLAY_STORE_URL;
|
publicAppStorePath = Constants.GOOGLE_PLAY_STORE_URL;
|
||||||
} else if (DeviceTypes.IOS.toString().equals(publicAppWrapper.getDeviceType())) {
|
} else if (DeviceTypes.IOS.toString().equals(publicAppWrapper.getDeviceType())) {
|
||||||
publicAppStorePath = Constants.APPLE_STORE_URL;
|
publicAppStorePath = Constants.APPLE_STORE_URL;
|
||||||
@ -194,18 +196,59 @@ public class ApplicationManagerImpl implements ApplicationManager {
|
|||||||
applicationReleaseDTO.setInstallerName(appInstallerUrl);
|
applicationReleaseDTO.setInstallerName(appInstallerUrl);
|
||||||
applicationReleaseDTO.setUuid(UUID.randomUUID().toString());
|
applicationReleaseDTO.setUuid(UUID.randomUUID().toString());
|
||||||
applicationReleaseDTO.setAppHashValue(DigestUtils.md5Hex(appInstallerUrl));
|
applicationReleaseDTO.setAppHashValue(DigestUtils.md5Hex(appInstallerUrl));
|
||||||
//uploading application artifacts
|
|
||||||
|
ConnectionManagerUtil.openDBConnection();
|
||||||
|
List<ApplicationReleaseDTO> exitingRelease;
|
||||||
try {
|
try {
|
||||||
applicationDTO.getApplicationReleaseDTOs().clear();
|
exitingRelease = applicationReleaseDAO.getReleaseByPackages(Arrays.asList(applicationReleaseDTO.getPackageName())
|
||||||
applicationDTO.getApplicationReleaseDTOs()
|
, tenantId);
|
||||||
.add(addImageArtifacts(applicationReleaseDTO, applicationArtifact));
|
} catch (ApplicationManagementDAOException e) {
|
||||||
} catch (ResourceManagementException e) {
|
String msg = "Error Occured when fetching release: " + publicAppWrapper.getName();
|
||||||
String msg = "Error Occured when uploading artifacts of the public app: " + publicAppWrapper.getName();
|
log.error(msg);
|
||||||
log.error(msg, e);
|
|
||||||
throw new ApplicationManagementException(msg, e);
|
throw new ApplicationManagementException(msg, e);
|
||||||
|
} finally {
|
||||||
|
ConnectionManagerUtil.closeDBConnection();
|
||||||
}
|
}
|
||||||
//insert application data into database
|
|
||||||
return addAppDataIntoDB(applicationDTO);
|
if (exitingRelease != null && exitingRelease.size() > 0) {
|
||||||
|
applicationDTO.getApplicationReleaseDTOs().clear();
|
||||||
|
applicationReleaseDTO.setUuid(exitingRelease.get(0).getUuid());
|
||||||
|
applicationReleaseDTO.setCurrentState(exitingRelease.get(0).getCurrentState());
|
||||||
|
|
||||||
|
try {
|
||||||
|
applicationReleaseDTO = addImageArtifacts(applicationReleaseDTO, applicationArtifact);
|
||||||
|
applicationDTO.getApplicationReleaseDTOs().add(applicationReleaseDTO);
|
||||||
|
ConnectionManagerUtil.beginDBTransaction();
|
||||||
|
applicationReleaseDAO.updateRelease(applicationReleaseDTO, tenantId);
|
||||||
|
ConnectionManagerUtil.commitDBTransaction();
|
||||||
|
return APIUtil.appDtoToAppResponse(applicationDTO);
|
||||||
|
} catch (ApplicationManagementDAOException e) {
|
||||||
|
ConnectionManagerUtil.rollbackDBTransaction();
|
||||||
|
String msg = "Error occurred when updating public app: " + publicAppWrapper.getName();
|
||||||
|
log.error(msg, e);
|
||||||
|
throw new ApplicationManagementException(msg, e);
|
||||||
|
} catch (ResourceManagementException e) {
|
||||||
|
String msg = "Error occurred when adding artifacts of release: " + publicAppWrapper.getName();
|
||||||
|
log.error(msg, e);
|
||||||
|
throw new ApplicationManagementException(msg, e);
|
||||||
|
} finally {
|
||||||
|
ConnectionManagerUtil.closeDBConnection();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//uploading application artifacts
|
||||||
|
try {
|
||||||
|
applicationReleaseDTO = addImageArtifacts(applicationReleaseDTO, applicationArtifact);
|
||||||
|
applicationDTO.getApplicationReleaseDTOs().clear();
|
||||||
|
applicationDTO.getApplicationReleaseDTOs().add(applicationReleaseDTO);
|
||||||
|
} catch (ResourceManagementException e) {
|
||||||
|
String msg = "Error Occured when uploading artifacts of the public app: " + publicAppWrapper.getName();
|
||||||
|
log.error(msg, e);
|
||||||
|
throw new ApplicationManagementException(msg, e);
|
||||||
|
}
|
||||||
|
//insert application data into database
|
||||||
|
return addAppDataIntoDB(applicationDTO);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -3115,4 +3158,24 @@ public class ApplicationManagerImpl implements ApplicationManager {
|
|||||||
ConnectionManagerUtil.closeDBConnection();
|
ConnectionManagerUtil.closeDBConnection();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ApplicationReleaseDTO> getReleaseByPackageNames(List<String> packageIds) throws ApplicationManagementException {
|
||||||
|
int tenantId = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantId(true);
|
||||||
|
try {
|
||||||
|
ConnectionManagerUtil.openDBConnection();
|
||||||
|
return this.applicationReleaseDAO.getReleaseByPackages(packageIds, tenantId);
|
||||||
|
} catch (DBConnectionException e) {
|
||||||
|
String msg = "Error occurred while obtaining the database connection for getting application for the " +
|
||||||
|
"packages";
|
||||||
|
log.error(msg, e);
|
||||||
|
throw new ApplicationManagementException(msg, e);
|
||||||
|
} catch (ApplicationManagementDAOException e) {
|
||||||
|
String msg = "Error occurred while getting application data for packages";
|
||||||
|
log.error(msg, e);
|
||||||
|
throw new ApplicationManagementException(msg, e);
|
||||||
|
} finally {
|
||||||
|
ConnectionManagerUtil.closeDBConnection();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,9 +17,19 @@
|
|||||||
|
|
||||||
package org.wso2.carbon.device.application.mgt.core.impl;
|
package org.wso2.carbon.device.application.mgt.core.impl;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.JsonArray;
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonParser;
|
||||||
|
import org.apache.commons.httpclient.HttpClient;
|
||||||
|
import org.apache.commons.httpclient.HttpException;
|
||||||
|
import org.apache.commons.httpclient.methods.PostMethod;
|
||||||
|
import org.apache.commons.httpclient.methods.StringRequestEntity;
|
||||||
import org.apache.commons.lang.StringUtils;
|
import org.apache.commons.lang.StringUtils;
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
import org.wso2.carbon.apimgt.application.extension.dto.ApiApplicationKey;
|
||||||
|
import org.wso2.carbon.apimgt.application.extension.exception.APIManagerException;
|
||||||
import org.wso2.carbon.context.PrivilegedCarbonContext;
|
import org.wso2.carbon.context.PrivilegedCarbonContext;
|
||||||
import org.wso2.carbon.device.application.mgt.common.ApplicationInstallResponse;
|
import org.wso2.carbon.device.application.mgt.common.ApplicationInstallResponse;
|
||||||
import org.wso2.carbon.device.application.mgt.common.ApplicationType;
|
import org.wso2.carbon.device.application.mgt.common.ApplicationType;
|
||||||
@ -29,6 +39,7 @@ import org.wso2.carbon.device.application.mgt.common.SubAction;
|
|||||||
import org.wso2.carbon.device.application.mgt.common.SubscriptionType;
|
import org.wso2.carbon.device.application.mgt.common.SubscriptionType;
|
||||||
import org.wso2.carbon.device.application.mgt.common.SubscribingDeviceIdHolder;
|
import org.wso2.carbon.device.application.mgt.common.SubscribingDeviceIdHolder;
|
||||||
import org.wso2.carbon.device.application.mgt.common.dto.ApplicationDTO;
|
import org.wso2.carbon.device.application.mgt.common.dto.ApplicationDTO;
|
||||||
|
import org.wso2.carbon.device.application.mgt.common.dto.ApplicationPolicyDTO;
|
||||||
import org.wso2.carbon.device.application.mgt.common.dto.DeviceSubscriptionDTO;
|
import org.wso2.carbon.device.application.mgt.common.dto.DeviceSubscriptionDTO;
|
||||||
import org.wso2.carbon.device.application.mgt.common.dto.ScheduledSubscriptionDTO;
|
import org.wso2.carbon.device.application.mgt.common.dto.ScheduledSubscriptionDTO;
|
||||||
import org.wso2.carbon.device.application.mgt.common.exception.ApplicationManagementException;
|
import org.wso2.carbon.device.application.mgt.common.exception.ApplicationManagementException;
|
||||||
@ -51,6 +62,7 @@ import org.wso2.carbon.device.application.mgt.core.util.APIUtil;
|
|||||||
import org.wso2.carbon.device.application.mgt.core.util.ConnectionManagerUtil;
|
import org.wso2.carbon.device.application.mgt.core.util.ConnectionManagerUtil;
|
||||||
import org.wso2.carbon.device.application.mgt.core.util.Constants;
|
import org.wso2.carbon.device.application.mgt.core.util.Constants;
|
||||||
import org.wso2.carbon.device.application.mgt.core.util.HelperUtil;
|
import org.wso2.carbon.device.application.mgt.core.util.HelperUtil;
|
||||||
|
import org.wso2.carbon.device.application.mgt.core.util.OAuthUtils;
|
||||||
import org.wso2.carbon.device.mgt.common.Device;
|
import org.wso2.carbon.device.mgt.common.Device;
|
||||||
import org.wso2.carbon.device.mgt.common.DeviceIdentifier;
|
import org.wso2.carbon.device.mgt.common.DeviceIdentifier;
|
||||||
import org.wso2.carbon.device.mgt.common.MDMAppConstants;
|
import org.wso2.carbon.device.mgt.common.MDMAppConstants;
|
||||||
@ -65,13 +77,18 @@ import org.wso2.carbon.device.mgt.common.operation.mgt.Activity;
|
|||||||
import org.wso2.carbon.device.mgt.common.operation.mgt.ActivityStatus;
|
import org.wso2.carbon.device.mgt.common.operation.mgt.ActivityStatus;
|
||||||
import org.wso2.carbon.device.mgt.common.operation.mgt.Operation;
|
import org.wso2.carbon.device.mgt.common.operation.mgt.Operation;
|
||||||
import org.wso2.carbon.device.mgt.common.operation.mgt.OperationManagementException;
|
import org.wso2.carbon.device.mgt.common.operation.mgt.OperationManagementException;
|
||||||
|
import org.wso2.carbon.device.mgt.common.policy.mgt.ProfileFeature;
|
||||||
import org.wso2.carbon.device.mgt.core.dto.DeviceType;
|
import org.wso2.carbon.device.mgt.core.dto.DeviceType;
|
||||||
import org.wso2.carbon.device.mgt.core.operation.mgt.ProfileOperation;
|
import org.wso2.carbon.device.mgt.core.operation.mgt.ProfileOperation;
|
||||||
import org.wso2.carbon.device.mgt.core.service.DeviceManagementProviderService;
|
import org.wso2.carbon.device.mgt.core.service.DeviceManagementProviderService;
|
||||||
import org.wso2.carbon.device.mgt.core.service.GroupManagementProviderService;
|
import org.wso2.carbon.device.mgt.core.service.GroupManagementProviderService;
|
||||||
import org.wso2.carbon.device.mgt.core.util.MDMAndroidOperationUtil;
|
import org.wso2.carbon.device.mgt.core.util.MDMAndroidOperationUtil;
|
||||||
import org.wso2.carbon.device.mgt.core.util.MDMIOSOperationUtil;
|
import org.wso2.carbon.device.mgt.core.util.MDMIOSOperationUtil;
|
||||||
|
import org.wso2.carbon.identity.jwt.client.extension.dto.AccessTokenInfo;
|
||||||
|
import org.wso2.carbon.user.api.UserStoreException;
|
||||||
|
|
||||||
|
import javax.ws.rs.core.MediaType;
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@ -377,8 +394,19 @@ public class SubscriptionManagerImpl implements SubscriptionManager {
|
|||||||
activityList.add(activity);
|
activityList.add(activity);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Activity activity = addAppOperationOnDevices(applicationDTO, deviceIdentifiers, deviceType, action);
|
if (applicationDTO.getType().equals(ApplicationType.PUBLIC.toString())) {
|
||||||
activityList.add(activity);
|
List<String> categories = getApplicationCategories(applicationDTO.getId());
|
||||||
|
if (categories.contains("GooglePlaySyncedApp")) {
|
||||||
|
ApplicationPolicyDTO applicationPolicyDTO = new ApplicationPolicyDTO();
|
||||||
|
applicationPolicyDTO.setApplicationDTO(applicationDTO);
|
||||||
|
applicationPolicyDTO.setDeviceIdentifierList(deviceIdentifiers);
|
||||||
|
applicationPolicyDTO.setAction(action);
|
||||||
|
installEnrollmentApplications(applicationPolicyDTO);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Activity activity = addAppOperationOnDevices(applicationDTO, deviceIdentifiers, deviceType, action);
|
||||||
|
activityList.add(activity);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ApplicationInstallResponse applicationInstallResponse = new ApplicationInstallResponse();
|
ApplicationInstallResponse applicationInstallResponse = new ApplicationInstallResponse();
|
||||||
applicationInstallResponse.setActivities(activityList);
|
applicationInstallResponse.setActivities(activityList);
|
||||||
@ -414,6 +442,22 @@ public class SubscriptionManagerImpl implements SubscriptionManager {
|
|||||||
return subscribingDeviceIdHolder;
|
return subscribingDeviceIdHolder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<String> getApplicationCategories(int id) throws ApplicationManagementException {
|
||||||
|
List<String> categories;
|
||||||
|
int tenantId = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantId(true);
|
||||||
|
try {
|
||||||
|
ConnectionManagerUtil.openDBConnection();
|
||||||
|
categories = this.applicationDAO.getAppCategories(id, tenantId);
|
||||||
|
return categories;
|
||||||
|
} catch (ApplicationManagementDAOException e) {
|
||||||
|
String msg = "Error occurred while getting categories for application : " + id;
|
||||||
|
log.error(msg, e);
|
||||||
|
throw new ApplicationManagementException(msg);
|
||||||
|
} finally {
|
||||||
|
ConnectionManagerUtil.closeDBConnection();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private ApplicationDTO getApplicationDTO(String uuid) throws ApplicationManagementException {
|
private ApplicationDTO getApplicationDTO(String uuid) throws ApplicationManagementException {
|
||||||
ApplicationDTO applicationDTO;
|
ApplicationDTO applicationDTO;
|
||||||
int tenantId = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantId(true);
|
int tenantId = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantId(true);
|
||||||
@ -681,4 +725,55 @@ public class SubscriptionManagerImpl implements SubscriptionManager {
|
|||||||
throw new ApplicationManagementException(msg, e);
|
throw new ApplicationManagementException(msg, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int installEnrollmentApplications(ApplicationPolicyDTO applicationPolicyDTO)
|
||||||
|
throws ApplicationManagementException {
|
||||||
|
|
||||||
|
HttpClient httpClient;
|
||||||
|
PostMethod request;
|
||||||
|
try {
|
||||||
|
String tenantDomain = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantDomain();
|
||||||
|
ApiApplicationKey apiApplicationKey = OAuthUtils.getClientCredentials(tenantDomain);
|
||||||
|
String username = PrivilegedCarbonContext.getThreadLocalCarbonContext().getUserRealm()
|
||||||
|
.getRealmConfiguration().getAdminUserName() + Constants.ApplicationInstall.AT + tenantDomain;
|
||||||
|
AccessTokenInfo tokenInfo = OAuthUtils.getOAuthCredentials(apiApplicationKey, username);
|
||||||
|
String requestUrl = Constants.ApplicationInstall.ENROLLMENT_APP_INSTALL_PROTOCOL +
|
||||||
|
System.getProperty(Constants.ApplicationInstall.IOT_CORE_HOST) +
|
||||||
|
Constants.ApplicationInstall.COLON +
|
||||||
|
System.getProperty(Constants.ApplicationInstall.IOT_CORE_PORT) +
|
||||||
|
Constants.ApplicationInstall.GOOGLE_APP_INSTALL_URL;
|
||||||
|
Gson gson = new Gson();
|
||||||
|
String payload = gson.toJson(applicationPolicyDTO);
|
||||||
|
|
||||||
|
StringRequestEntity requestEntity = new StringRequestEntity(payload, MediaType.APPLICATION_JSON
|
||||||
|
, Constants.ApplicationInstall.ENCODING);;
|
||||||
|
httpClient = new HttpClient();
|
||||||
|
request = new PostMethod(requestUrl);
|
||||||
|
request.addRequestHeader(Constants.ApplicationInstall.AUTHORIZATION
|
||||||
|
, Constants.ApplicationInstall.AUTHORIZATION_HEADER_VALUE + tokenInfo.getAccessToken());
|
||||||
|
request.setRequestEntity(requestEntity);
|
||||||
|
httpClient.executeMethod(request);
|
||||||
|
return request.getStatusCode();
|
||||||
|
|
||||||
|
} catch (UserStoreException e) {
|
||||||
|
String msg = "Error while accessing user store for user with Android device.";
|
||||||
|
log.error(msg, e);
|
||||||
|
throw new ApplicationManagementException(msg, e);
|
||||||
|
} catch (APIManagerException e) {
|
||||||
|
String msg = "Error while retrieving access token for Android device" ;
|
||||||
|
log.error(msg, e);
|
||||||
|
throw new ApplicationManagementException(msg, e);
|
||||||
|
} catch (HttpException e) {
|
||||||
|
String msg = "Error while calling the app store to install enrollment app with id: " +
|
||||||
|
applicationPolicyDTO.getApplicationDTO().getId() +
|
||||||
|
" on device";
|
||||||
|
log.error(msg, e);
|
||||||
|
throw new ApplicationManagementException(msg, e);
|
||||||
|
} catch (IOException e) {
|
||||||
|
String msg = "Error while installing the enrollment with id: " + applicationPolicyDTO.getApplicationDTO()
|
||||||
|
.getId() + " on device";
|
||||||
|
log.error(msg, e);
|
||||||
|
throw new ApplicationManagementException(msg, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -356,6 +356,7 @@ public class APIUtil {
|
|||||||
applicationReleases.add(releaseDtoToRelease(applicationReleaseDTO));
|
applicationReleases.add(releaseDtoToRelease(applicationReleaseDTO));
|
||||||
}
|
}
|
||||||
application.setApplicationReleases(applicationReleases);
|
application.setApplicationReleases(applicationReleases);
|
||||||
|
application.setPackageName(applicationDTO.getPackageName());
|
||||||
return application;
|
return application;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -115,4 +115,30 @@ public class Constants {
|
|||||||
public static final int REVIEW_PARENT_ID = -1;
|
public static final int REVIEW_PARENT_ID = -1;
|
||||||
|
|
||||||
public static final int MAX_RATING = 5;
|
public static final int MAX_RATING = 5;
|
||||||
|
|
||||||
|
public final class ApplicationInstall {
|
||||||
|
private ApplicationInstall() {
|
||||||
|
throw new AssertionError();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final String APPLICATION_NAME = "device_type_android";
|
||||||
|
public static final String ENROLLMENT_APP_INSTALL_FEATURE_CODE = "ENROLLMENT_APP_INSTALL";
|
||||||
|
public static final String DEFAULT_TOKEN_TYPE = "PRODUCTION";
|
||||||
|
public static final String DEFAULT_VALIDITY_PERIOD = "3600";
|
||||||
|
public static final String SUBSCRIPTION_SCOPE = "appm:subscribe";
|
||||||
|
public static final String ENROLLMENT_APP_INSTALL_UUID = "uuid";
|
||||||
|
public static final String GOOGLE_POLICY_PAYLOAD = "installGooglePolicyPayload";
|
||||||
|
public static final String ENROLLMENT_APP_INSTALL_CODE = "enrollmentAppInstall";
|
||||||
|
public static final String ENCODING = "UTF-8";
|
||||||
|
public static final String AT = "@";
|
||||||
|
public static final String DEVICE_TYPE_ANDROID = "android";
|
||||||
|
public static final String COLON = ":";
|
||||||
|
public static final String IOT_CORE_HOST = "iot.core.host";
|
||||||
|
public static final String IOT_CORE_PORT = "iot.core.https.port";
|
||||||
|
public static final String ENROLLMENT_APP_INSTALL_PROTOCOL = "https://";
|
||||||
|
public static final String GOOGLE_APP_INSTALL_URL = "/api/device-mgt/android/v1.0/enterprise/change-app";
|
||||||
|
|
||||||
|
public static final String AUTHORIZATION = "Authorization";
|
||||||
|
public static final String AUTHORIZATION_HEADER_VALUE = "Bearer ";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -105,6 +105,7 @@ public class DAOUtil {
|
|||||||
application.setStatus(rs.getString("APP_STATUS"));
|
application.setStatus(rs.getString("APP_STATUS"));
|
||||||
application.setAppRating(rs.getDouble("APP_RATING"));
|
application.setAppRating(rs.getDouble("APP_RATING"));
|
||||||
application.setDeviceTypeId(rs.getInt("APP_DEVICE_TYPE_ID"));
|
application.setDeviceTypeId(rs.getInt("APP_DEVICE_TYPE_ID"));
|
||||||
|
application.setPackageName(rs.getString("PACKAGE_NAME"));
|
||||||
application.getApplicationReleaseDTOs().add(constructAppReleaseDTO(rs));
|
application.getApplicationReleaseDTOs().add(constructAppReleaseDTO(rs));
|
||||||
} else {
|
} else {
|
||||||
if (application != null && application.getApplicationReleaseDTOs() != null) {
|
if (application != null && application.getApplicationReleaseDTOs() != null) {
|
||||||
|
|||||||
@ -0,0 +1,90 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2019, Entgra (Pvt) Ltd. (http://www.entgra.io) All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Entgra (Pvt) Ltd. 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.core.util;
|
||||||
|
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
import org.wso2.carbon.apimgt.application.extension.APIManagementProviderService;
|
||||||
|
import org.wso2.carbon.apimgt.application.extension.dto.ApiApplicationKey;
|
||||||
|
import org.wso2.carbon.apimgt.application.extension.exception.APIManagerException;
|
||||||
|
import org.wso2.carbon.context.PrivilegedCarbonContext;
|
||||||
|
import org.wso2.carbon.device.application.mgt.common.dto.ApiRegistrationProfile;
|
||||||
|
import org.wso2.carbon.identity.jwt.client.extension.JWTClient;
|
||||||
|
import org.wso2.carbon.identity.jwt.client.extension.dto.AccessTokenInfo;
|
||||||
|
import org.wso2.carbon.identity.jwt.client.extension.exception.JWTClientException;
|
||||||
|
import org.wso2.carbon.identity.jwt.client.extension.service.JWTClientManagerService;
|
||||||
|
import org.wso2.carbon.user.api.UserStoreException;
|
||||||
|
import org.wso2.carbon.utils.multitenancy.MultitenantConstants;
|
||||||
|
|
||||||
|
public class OAuthUtils {
|
||||||
|
|
||||||
|
private static final Log log = LogFactory.getLog(OAuthUtils.class);
|
||||||
|
|
||||||
|
public static ApiApplicationKey getClientCredentials(String tenantDomain)
|
||||||
|
throws UserStoreException, APIManagerException {
|
||||||
|
ApiRegistrationProfile registrationProfile = new ApiRegistrationProfile();
|
||||||
|
registrationProfile.setApplicationName(Constants.ApplicationInstall.APPLICATION_NAME);
|
||||||
|
registrationProfile.setTags(new String[]{Constants.ApplicationInstall.DEVICE_TYPE_ANDROID});
|
||||||
|
registrationProfile.setAllowedToAllDomains(false);
|
||||||
|
registrationProfile.setMappingAnExistingOAuthApp(false);
|
||||||
|
return getCredentials(registrationProfile, tenantDomain);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ApiApplicationKey getCredentials(ApiRegistrationProfile registrationProfile, String tenantDomain)
|
||||||
|
throws UserStoreException, APIManagerException {
|
||||||
|
ApiApplicationKey apiApplicationKeyInfo;
|
||||||
|
if (tenantDomain == null || tenantDomain.isEmpty()) {
|
||||||
|
tenantDomain = MultitenantConstants.SUPER_TENANT_DOMAIN_NAME;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
PrivilegedCarbonContext.startTenantFlow();
|
||||||
|
PrivilegedCarbonContext.getThreadLocalCarbonContext().setTenantDomain(tenantDomain, true);
|
||||||
|
PrivilegedCarbonContext.getThreadLocalCarbonContext().setUsername(PrivilegedCarbonContext.
|
||||||
|
getThreadLocalCarbonContext().getUserRealm().getRealmConfiguration().getAdminUserName());
|
||||||
|
PrivilegedCarbonContext ctx = PrivilegedCarbonContext.getThreadLocalCarbonContext();
|
||||||
|
APIManagementProviderService apiManagementProviderService = (APIManagementProviderService) ctx.
|
||||||
|
getOSGiService(APIManagementProviderService.class, null);
|
||||||
|
apiApplicationKeyInfo = apiManagementProviderService.
|
||||||
|
generateAndRetrieveApplicationKeys(registrationProfile.getApplicationName(),
|
||||||
|
registrationProfile.getTags(), Constants.ApplicationInstall.DEFAULT_TOKEN_TYPE,
|
||||||
|
registrationProfile.getApplicationName(), registrationProfile.isAllowedToAllDomains(),
|
||||||
|
Constants.ApplicationInstall.DEFAULT_VALIDITY_PERIOD);
|
||||||
|
} finally {
|
||||||
|
PrivilegedCarbonContext.endTenantFlow();
|
||||||
|
}
|
||||||
|
return apiApplicationKeyInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AccessTokenInfo getOAuthCredentials(ApiApplicationKey apiApplicationKey, String username)
|
||||||
|
throws APIManagerException {
|
||||||
|
try {
|
||||||
|
PrivilegedCarbonContext ctx = PrivilegedCarbonContext.getThreadLocalCarbonContext();
|
||||||
|
JWTClientManagerService jwtClientManagerService = (JWTClientManagerService) ctx.
|
||||||
|
getOSGiService(JWTClientManagerService.class, null);
|
||||||
|
JWTClient jwtClient = jwtClientManagerService.getJWTClient();
|
||||||
|
return jwtClient.getAccessToken(apiApplicationKey.getConsumerKey(), apiApplicationKey.getConsumerSecret(),
|
||||||
|
username, Constants.ApplicationInstall.SUBSCRIPTION_SCOPE);
|
||||||
|
} catch (JWTClientException e) {
|
||||||
|
String errorMsg = "Error while generating an OAuth token for user " + username;
|
||||||
|
log.error(errorMsg, e);
|
||||||
|
throw new APIManagerException(errorMsg, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -10,13 +10,16 @@
|
|||||||
},
|
},
|
||||||
"license": "Apache License 2.0",
|
"license": "Apache License 2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@ant-design/dark-theme": "^0.2.2",
|
||||||
"@babel/polyfill": "^7.6.0",
|
"@babel/polyfill": "^7.6.0",
|
||||||
"acorn": "^6.2.0",
|
"acorn": "^6.2.0",
|
||||||
"antd": "^3.20.1",
|
"antd": "^3.22.2",
|
||||||
"axios": "^0.19.0",
|
"axios": "^0.19.0",
|
||||||
"d3": "^5.9.7",
|
"d3": "^5.9.7",
|
||||||
"dagre": "^0.8.4",
|
"dagre": "^0.8.4",
|
||||||
"fetch": "^1.1.0",
|
"fetch": "^1.1.0",
|
||||||
|
"gapi": "0.0.3",
|
||||||
|
"gapi-client": "0.0.3",
|
||||||
"keymirror": "^0.1.1",
|
"keymirror": "^0.1.1",
|
||||||
"rc-tween-one": "^2.4.1",
|
"rc-tween-one": "^2.4.1",
|
||||||
"react-d3-graph": "^2.1.0",
|
"react-d3-graph": "^2.1.0",
|
||||||
@ -54,7 +57,7 @@
|
|||||||
"html-webpack-plugin": "^3.2.0",
|
"html-webpack-plugin": "^3.2.0",
|
||||||
"img-loader": "^3.0.1",
|
"img-loader": "^3.0.1",
|
||||||
"json-loader": "^0.5.7",
|
"json-loader": "^0.5.7",
|
||||||
"less": "^3.9.0",
|
"less": "^3.10.3",
|
||||||
"less-loader": "^4.1.0",
|
"less-loader": "^4.1.0",
|
||||||
"mini-css-extract-plugin": "^0.5.0",
|
"mini-css-extract-plugin": "^0.5.0",
|
||||||
"mocha": "^5.2.0",
|
"mocha": "^5.2.0",
|
||||||
@ -73,8 +76,8 @@
|
|||||||
"sass-loader": "^6.0.7",
|
"sass-loader": "^6.0.7",
|
||||||
"style-loader": "^0.18.2",
|
"style-loader": "^0.18.2",
|
||||||
"url-loader": "^1.1.2",
|
"url-loader": "^1.1.2",
|
||||||
"webpack": "^4.35.2",
|
"webpack": "^4.39.3",
|
||||||
"webpack-cli": "^3.3.5",
|
"webpack-cli": "^3.3.7",
|
||||||
"webpack-dev-server": "^3.7.2"
|
"webpack-dev-server": "^3.7.2"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@ -1,14 +1,9 @@
|
|||||||
{
|
{
|
||||||
"theme": {
|
"theme": {
|
||||||
"type": "default",
|
|
||||||
"value": "lightBaseTheme",
|
|
||||||
"logo": "https://entgra.io/assets/images/svg/logo.svg",
|
"logo": "https://entgra.io/assets/images/svg/logo.svg",
|
||||||
"primaryColor": "rgb(24, 144, 255)"
|
"primaryColor": "#badc58"
|
||||||
},
|
},
|
||||||
"serverConfig": {
|
"serverConfig": {
|
||||||
"protocol": "https",
|
|
||||||
"hostname": "localhost",
|
|
||||||
"httpsPort": "9443",
|
|
||||||
"invoker": {
|
"invoker": {
|
||||||
"uri": "/publisher-ui-request-handler/invoke",
|
"uri": "/publisher-ui-request-handler/invoke",
|
||||||
"publisher": "/application-mgt-publisher/v1.0",
|
"publisher": "/application-mgt-publisher/v1.0",
|
||||||
|
|||||||
@ -67,11 +67,10 @@ class App extends React.Component {
|
|||||||
axios.get(
|
axios.get(
|
||||||
window.location.origin + "/publisher/public/conf/config.json",
|
window.location.origin + "/publisher/public/conf/config.json",
|
||||||
).then(res => {
|
).then(res => {
|
||||||
console.log(res);
|
const config = res.data;
|
||||||
this.setState({
|
|
||||||
loading: false,
|
this.getAndroidEnterpriseToken(config);
|
||||||
config: res.data
|
|
||||||
})
|
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
loading: false,
|
loading: false,
|
||||||
@ -80,6 +79,26 @@ class App extends React.Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getAndroidEnterpriseToken = (config) => {
|
||||||
|
axios.get(
|
||||||
|
window.location.origin + config.serverConfig.invoker.uri + "/device-mgt/android/v1.0/enterprise/store-url?approveApps=true" +
|
||||||
|
"&searchEnabled=true&isPrivateAppsEnabled=true&isWebAppEnabled=true&isOrganizeAppPageVisible=true&isManagedConfigEnabled=true" +
|
||||||
|
"&host=https://localhost:9443",
|
||||||
|
).then(res => {
|
||||||
|
config.androidEnterpriseToken = res.data.data.token;
|
||||||
|
this.setState({
|
||||||
|
loading: false,
|
||||||
|
config: config
|
||||||
|
});
|
||||||
|
}).catch((error) => {
|
||||||
|
config.androidEnterpriseToken = null;
|
||||||
|
this.setState({
|
||||||
|
loading: false,
|
||||||
|
config: config
|
||||||
|
})
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {loading, error} = this.state;
|
const {loading, error} = this.state;
|
||||||
|
|
||||||
|
|||||||
@ -40,6 +40,8 @@ import ReactHtmlParser from 'react-html-parser';
|
|||||||
import "./AppDetailsDrawer.css";
|
import "./AppDetailsDrawer.css";
|
||||||
import pSBC from "shade-blend-color";
|
import pSBC from "shade-blend-color";
|
||||||
import {withConfigContext} from "../../../../context/ConfigContext";
|
import {withConfigContext} from "../../../../context/ConfigContext";
|
||||||
|
import ManagedConfigurationsIframe
|
||||||
|
from "../../../manage/android-enterprise/ManagedConfigurationsIframe/ManagedConfigurationsIframe";
|
||||||
|
|
||||||
const {Meta} = Card;
|
const {Meta} = Card;
|
||||||
const {Text, Title} = Typography;
|
const {Text, Title} = Typography;
|
||||||
@ -474,6 +476,31 @@ class AppDetailsDrawer extends React.Component {
|
|||||||
|
|
||||||
<Divider/>
|
<Divider/>
|
||||||
|
|
||||||
|
{/*display manage config button only if the app is public android app*/}
|
||||||
|
{(app.type === "PUBLIC") && (app.deviceType === "android") && (
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<Text strong={true}>Set up managed configurations</Text>
|
||||||
|
</div>
|
||||||
|
<div style={{paddingTop: 16}}>
|
||||||
|
<Text>
|
||||||
|
If you are developing apps for the enterprise market, you may need to satisfy
|
||||||
|
particular requirements set by a organization's policies. Managed
|
||||||
|
configurations,
|
||||||
|
previously known as application restrictions, allow the organization's IT admin
|
||||||
|
to
|
||||||
|
remotely specify settings for apps. This capability is particularly useful for
|
||||||
|
organization-approved apps deployed to a work profile.
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
<br/>
|
||||||
|
<ManagedConfigurationsIframe
|
||||||
|
style={{paddingTop: 16}}
|
||||||
|
packageName={app.packageName}/>
|
||||||
|
<Divider dashed={true}/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<Text strong={true}>Releases </Text>
|
<Text strong={true}>Releases </Text>
|
||||||
{/*display add new release only if app type is enterprise*/}
|
{/*display add new release only if app type is enterprise*/}
|
||||||
|
|
||||||
|
|||||||
@ -224,7 +224,7 @@ class EditReleaseModal extends React.Component {
|
|||||||
isSharedWithAllTenants,
|
isSharedWithAllTenants,
|
||||||
metaData: JSON.stringify(this.state.metaData),
|
metaData: JSON.stringify(this.state.metaData),
|
||||||
releaseType: releaseType,
|
releaseType: releaseType,
|
||||||
supportedOsVersions: "4.0-10.0"
|
supportedOsVersions: "4-30"
|
||||||
};
|
};
|
||||||
|
|
||||||
if (specificElements.hasOwnProperty("binaryFile") && binaryFiles.length === 1) {
|
if (specificElements.hasOwnProperty("binaryFile") && binaryFiles.length === 1) {
|
||||||
|
|||||||
@ -0,0 +1,125 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2019, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Entgra (pvt) Ltd. 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 from "react";
|
||||||
|
import {Button, Divider, Form, Input, message, Modal, notification, Spin} from "antd";
|
||||||
|
import axios from "axios";
|
||||||
|
import {withConfigContext} from "../../../../context/ConfigContext";
|
||||||
|
import {withRouter} from "react-router";
|
||||||
|
import {handleApiError} from "../../../../js/Utils";
|
||||||
|
|
||||||
|
class AddNewPage extends React.Component {
|
||||||
|
|
||||||
|
state = {
|
||||||
|
visible: false,
|
||||||
|
pageName: ''
|
||||||
|
};
|
||||||
|
|
||||||
|
showModal = () => {
|
||||||
|
this.setState({
|
||||||
|
visible: true,
|
||||||
|
loading: false
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
handleCancel = e => {
|
||||||
|
console.log(e);
|
||||||
|
this.setState({
|
||||||
|
visible: false,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
handlePageName = (e) => {
|
||||||
|
this.setState({
|
||||||
|
pageName: e.target.value,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
createNewPage = () => {
|
||||||
|
const config = this.props.context;
|
||||||
|
this.setState({loading: true});
|
||||||
|
|
||||||
|
axios.post(
|
||||||
|
window.location.origin + config.serverConfig.invoker.uri +
|
||||||
|
"/device-mgt/android/v1.0/enterprise/store-layout/page",
|
||||||
|
{
|
||||||
|
"locale": "en",
|
||||||
|
"pageName": this.state.pageName
|
||||||
|
}
|
||||||
|
).then(res => {
|
||||||
|
if (res.status === 200) {
|
||||||
|
|
||||||
|
const {pageId, pageName} = res.data.data;
|
||||||
|
|
||||||
|
notification["success"]({
|
||||||
|
message: 'Saved!',
|
||||||
|
description: 'Page created successfully!'
|
||||||
|
});
|
||||||
|
|
||||||
|
this.setState({loading: false});
|
||||||
|
|
||||||
|
this.props.history.push(`/publisher/manage/android-enterprise/pages/${pageName}/${pageId}`);
|
||||||
|
}
|
||||||
|
}).catch((error) => {
|
||||||
|
handleApiError(error, "Error occurred while trying to update the cluster.");
|
||||||
|
this.setState({loading: false});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div style={{marginTop: 24, marginBottom: 24}}>
|
||||||
|
<Button
|
||||||
|
type="dashed"
|
||||||
|
onClick={this.showModal}>
|
||||||
|
Add new page
|
||||||
|
</Button>
|
||||||
|
<Modal
|
||||||
|
title="Add new page"
|
||||||
|
visible={this.state.visible}
|
||||||
|
onOk={this.createNewPage}
|
||||||
|
onCancel={this.handleCancel}
|
||||||
|
okText="Create Page"
|
||||||
|
footer={null}
|
||||||
|
>
|
||||||
|
<Spin spinning={this.state.loading}>
|
||||||
|
<p>Choose a name for the page</p>
|
||||||
|
<Input onChange={this.handlePageName}/>
|
||||||
|
<Divider/>
|
||||||
|
<div>
|
||||||
|
<Button
|
||||||
|
onClick={this.handleCancel}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Divider type="vertical"/>
|
||||||
|
<Button
|
||||||
|
onClick={this.createNewPage}
|
||||||
|
htmlType="button" type="primary"
|
||||||
|
disabled={this.state.pageName.length === 0}>
|
||||||
|
Create Page
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Spin>
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withConfigContext(withRouter(AddNewPage));
|
||||||
@ -0,0 +1,81 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2019, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Entgra (pvt) Ltd. 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 from "react";
|
||||||
|
import {Modal, Button} from "antd";
|
||||||
|
import {withConfigContext} from "../../../context/ConfigContext";
|
||||||
|
|
||||||
|
class GooglePlayIframe extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.config = this.props.context;
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
visible: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
showModal = () => {
|
||||||
|
this.setState({
|
||||||
|
visible: true,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
handleOk = e => {
|
||||||
|
console.log(e);
|
||||||
|
this.setState({
|
||||||
|
visible: false,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
handleCancel = e => {
|
||||||
|
console.log(e);
|
||||||
|
this.setState({
|
||||||
|
visible: false,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div style={{display: "inline-block", padding: 4}}>
|
||||||
|
<Button type="primary" onClick={this.showModal}>
|
||||||
|
Approve Applications
|
||||||
|
</Button>
|
||||||
|
<Modal
|
||||||
|
title={null}
|
||||||
|
visible={this.state.visible}
|
||||||
|
onOk={this.handleOk}
|
||||||
|
onCancel={this.handleCancel}
|
||||||
|
width = {740}
|
||||||
|
footer={null}>
|
||||||
|
<iframe
|
||||||
|
style={{
|
||||||
|
height: 720,
|
||||||
|
border: 0,
|
||||||
|
width: "100%"
|
||||||
|
}}
|
||||||
|
src={"https://play.google.com/work/embedded/search?token=" + this.config.androidEnterpriseToken +
|
||||||
|
"&mode=APPROVE&showsearchbox=TRUE"}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withConfigContext(GooglePlayIframe);
|
||||||
@ -0,0 +1,224 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2019, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Entgra (pvt) Ltd. 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 from "react";
|
||||||
|
import {Button, message, Modal, notification, Spin} from "antd";
|
||||||
|
import axios from "axios";
|
||||||
|
import {withConfigContext} from "../../../../context/ConfigContext";
|
||||||
|
import {handleApiError} from "../../../../js/Utils";
|
||||||
|
|
||||||
|
// import gapi from 'gapi-client';
|
||||||
|
|
||||||
|
class ManagedConfigurationsIframe extends React.Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.config = this.props.context;
|
||||||
|
this.state = {
|
||||||
|
visible: false,
|
||||||
|
loading: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
showModal = () => {
|
||||||
|
this.getMcm();
|
||||||
|
this.setState({
|
||||||
|
visible: true,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
handleOk = e => {
|
||||||
|
console.log(e);
|
||||||
|
this.setState({
|
||||||
|
visible: false,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
handleCancel = e => {
|
||||||
|
console.log(e);
|
||||||
|
this.setState({
|
||||||
|
visible: false,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
getMcm = () => {
|
||||||
|
const {packageName} = this.props;
|
||||||
|
this.setState({loading: true});
|
||||||
|
|
||||||
|
//send request to the invoker
|
||||||
|
axios.get(
|
||||||
|
window.location.origin + this.config.serverConfig.invoker.uri +
|
||||||
|
"/device-mgt/android/v1.0/enterprise/managed-configs/package/" + packageName,
|
||||||
|
).then(res => {
|
||||||
|
if (res.status === 200) {
|
||||||
|
let mcmId = null;
|
||||||
|
if (res.data.hasOwnProperty("data")) {
|
||||||
|
mcmId = res.data.data.mcmId;
|
||||||
|
}
|
||||||
|
this.loadIframe(mcmId);
|
||||||
|
this.setState({loading: false});
|
||||||
|
}
|
||||||
|
|
||||||
|
}).catch((error) => {
|
||||||
|
if (error.hasOwnProperty("response") && error.response.status === 401) {
|
||||||
|
message.error('You are not logged in');
|
||||||
|
window.location.href = window.location.origin + '/publisher/login';
|
||||||
|
} else {
|
||||||
|
notification["error"]({
|
||||||
|
message: "There was a problem",
|
||||||
|
duration: 0,
|
||||||
|
description:
|
||||||
|
"Error occurred while trying to load configurations.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({loading: false, visible: false});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
loadIframe = (mcmId) => {
|
||||||
|
const {packageName} = this.props;
|
||||||
|
let method = "post";
|
||||||
|
gapi.load('gapi.iframes', () => {
|
||||||
|
const parameters = {
|
||||||
|
token: this.config.androidEnterpriseToken,
|
||||||
|
packageName: packageName
|
||||||
|
};
|
||||||
|
if (mcmId != null) {
|
||||||
|
parameters.mcmId = mcmId;
|
||||||
|
parameters.canDelete = true;
|
||||||
|
method = "put";
|
||||||
|
}
|
||||||
|
|
||||||
|
const queryString = Object.keys(parameters).map(key => key + '=' + parameters[key]).join('&');
|
||||||
|
|
||||||
|
var options = {
|
||||||
|
'url': "https://play.google.com/managed/mcm?" + queryString,
|
||||||
|
'where': document.getElementById('manage-config-iframe-container'),
|
||||||
|
'attributes': {style: 'height:720px', scrolling: 'yes'}
|
||||||
|
};
|
||||||
|
|
||||||
|
var iframe = gapi.iframes.getContext().openChild(options);
|
||||||
|
iframe.register('onconfigupdated', (event) => {
|
||||||
|
this.updateConfig(method, event);
|
||||||
|
}, gapi.iframes.CROSS_ORIGIN_IFRAMES_FILTER);
|
||||||
|
|
||||||
|
iframe.register('onconfigdeleted', (event) => {
|
||||||
|
this.deleteConfig(event);
|
||||||
|
}, gapi.iframes.CROSS_ORIGIN_IFRAMES_FILTER);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
updateConfig = (method, event) => {
|
||||||
|
const {packageName} = this.props;
|
||||||
|
this.setState({loading: true});
|
||||||
|
console.log(event);
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
mcmId: event.mcmId,
|
||||||
|
profileName: event.name,
|
||||||
|
packageName
|
||||||
|
};
|
||||||
|
|
||||||
|
//send request to the invoker
|
||||||
|
axios({
|
||||||
|
method,
|
||||||
|
url: window.location.origin + this.config.serverConfig.invoker.uri +
|
||||||
|
"/device-mgt/android/v1.0/enterprise/managed-configs",
|
||||||
|
data
|
||||||
|
}).then(res => {
|
||||||
|
if (res.status === 200 || res.status === 201) {
|
||||||
|
notification["success"]({
|
||||||
|
message: 'Saved!',
|
||||||
|
description: 'Configuration Profile updated Successfully',
|
||||||
|
});
|
||||||
|
this.setState({
|
||||||
|
loading: false,
|
||||||
|
visible: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}).catch((error) => {
|
||||||
|
if (error.hasOwnProperty("response") && error.response.status === 401) {
|
||||||
|
message.error('You are not logged in');
|
||||||
|
window.location.href = window.location.origin + '/publisher/login';
|
||||||
|
} else {
|
||||||
|
notification["error"]({
|
||||||
|
message: "There was a problem",
|
||||||
|
duration: 0,
|
||||||
|
description:
|
||||||
|
"Error occurred while trying to update configurations.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({loading: false});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
deleteConfig = (event) => {
|
||||||
|
const {packageName} = this.props;
|
||||||
|
this.setState({loading: true});
|
||||||
|
console.log(event);
|
||||||
|
|
||||||
|
//send request to the invoker
|
||||||
|
axios.delete(
|
||||||
|
window.location.origin + this.config.serverConfig.invoker.uri +
|
||||||
|
"/device-mgt/android/v1.0/enterprise/managed-configs/mcm/" + event.mcmId
|
||||||
|
).then(res => {
|
||||||
|
if (res.status === 200 || res.status === 201) {
|
||||||
|
notification["success"]({
|
||||||
|
message: 'Saved!',
|
||||||
|
description: 'Configuration Profile removed Successfully',
|
||||||
|
});
|
||||||
|
this.setState({
|
||||||
|
loading: false,
|
||||||
|
visible: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).catch((error) => {
|
||||||
|
handleApiError(error, "Error occurred while trying to remove configurations.");
|
||||||
|
this.setState({loading: false});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
type="primary"
|
||||||
|
icon="setting"
|
||||||
|
onClick={this.showModal}>
|
||||||
|
Manage
|
||||||
|
</Button>
|
||||||
|
<Modal
|
||||||
|
visible={this.state.visible}
|
||||||
|
onOk={this.handleOk}
|
||||||
|
onCancel={this.handleCancel}
|
||||||
|
footer={null}>
|
||||||
|
<Spin spinning={this.state.loading}>
|
||||||
|
<div id="manage-config-iframe-container">
|
||||||
|
</div>
|
||||||
|
</Spin>
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withConfigContext(ManagedConfigurationsIframe);
|
||||||
@ -0,0 +1,122 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2019, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Entgra (pvt) Ltd. 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 from "react";
|
||||||
|
import {Modal, Icon, Table, Avatar} from 'antd';
|
||||||
|
import "../Cluster.css";
|
||||||
|
import {withConfigContext} from "../../../../../../context/ConfigContext";
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: '',
|
||||||
|
dataIndex: 'iconUrl',
|
||||||
|
key: 'iconUrl',
|
||||||
|
render: (iconUrl) => (<Avatar shape="square" src={iconUrl}/>)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Name',
|
||||||
|
dataIndex: 'name',
|
||||||
|
key: 'name'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Page',
|
||||||
|
dataIndex: 'packageId',
|
||||||
|
key: 'packageId'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
class AddAppsToClusterModal extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
visible: false,
|
||||||
|
loading: false,
|
||||||
|
selectedProducts: [],
|
||||||
|
homePageId: null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
showModal = () => {
|
||||||
|
this.setState({
|
||||||
|
visible: true,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
handleOk = () => {
|
||||||
|
this.props.addSelectedProducts(this.state.selectedProducts);
|
||||||
|
this.handleCancel();
|
||||||
|
};
|
||||||
|
|
||||||
|
handleCancel = () => {
|
||||||
|
this.setState({
|
||||||
|
visible: false,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
rowSelection = {
|
||||||
|
onChange: (selectedRowKeys, selectedRows) => {
|
||||||
|
this.setState({
|
||||||
|
selectedProducts: selectedRows
|
||||||
|
})
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {pagination, loading} = this.state;
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="btn-add-new-wrapper">
|
||||||
|
<div className="btn-add-new">
|
||||||
|
<button className="btn"
|
||||||
|
onClick={this.showModal}>
|
||||||
|
<Icon style={{position: "relative"}} type="plus"/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="title">
|
||||||
|
Add app
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Modal
|
||||||
|
title="Select Apps"
|
||||||
|
width={640}
|
||||||
|
visible={this.state.visible}
|
||||||
|
onOk={this.handleOk}
|
||||||
|
onCancel={this.handleCancel}>
|
||||||
|
<Table
|
||||||
|
columns={columns}
|
||||||
|
rowKey={record => record.packageId}
|
||||||
|
dataSource={this.props.unselectedProducts}
|
||||||
|
scroll={{ x: 300 }}
|
||||||
|
pagination={{
|
||||||
|
...pagination,
|
||||||
|
size: "small",
|
||||||
|
// position: "top",
|
||||||
|
showTotal: (total, range) => `showing ${range[0]}-${range[1]} of ${total} pages`,
|
||||||
|
showQuickJumper: true
|
||||||
|
}}
|
||||||
|
loading={loading}
|
||||||
|
onChange={this.handleTableChange}
|
||||||
|
rowSelection={this.rowSelection}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withConfigContext(AddAppsToClusterModal);
|
||||||
@ -0,0 +1,168 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2019, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Entgra (pvt) Ltd. 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.cluster{
|
||||||
|
border-radius: 5px;
|
||||||
|
background-color: white;
|
||||||
|
padding: 24px;
|
||||||
|
margin: 14px 0 14px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cluster .products-row{
|
||||||
|
-webkit-align-items: center;
|
||||||
|
align-items: center;
|
||||||
|
display: -webkit-box;
|
||||||
|
display: -webkit-flex;
|
||||||
|
display: flex;
|
||||||
|
-webkit-flex-wrap: wrap;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin-top: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cluster .product{
|
||||||
|
display: -webkit-box;
|
||||||
|
display: -webkit-flex;
|
||||||
|
display: flex;
|
||||||
|
padding: 20px 0;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cluster .product .product-icon{
|
||||||
|
display: -webkit-box;
|
||||||
|
display: -webkit-flex;
|
||||||
|
display: flex;
|
||||||
|
-webkit-flex-direction: column;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 180px;
|
||||||
|
width: 90px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cluster .product .title, .cluster .btn-add-new-wrapper .title {
|
||||||
|
color: #202124;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cluster .product .product-icon img{
|
||||||
|
-webkit-align-self: center;
|
||||||
|
align-self: center;
|
||||||
|
height: 90px;
|
||||||
|
/*padding-bottom: 16px;*/
|
||||||
|
width: 90px;
|
||||||
|
border-radius: 28%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cluster .product .arrow {
|
||||||
|
color: #80868b;
|
||||||
|
font-size: 20px;
|
||||||
|
position: relative;
|
||||||
|
top: 20px;
|
||||||
|
width: 36px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cluster .product .arrow .btn {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
padding: 12px;
|
||||||
|
font-size: 24px;
|
||||||
|
display: inline-block;
|
||||||
|
position: relative;
|
||||||
|
-webkit-box-sizing: border-box;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
background-color: transparent;
|
||||||
|
fill: currentColor;
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
cursor: pointer;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cluster .product .arrow .btn-right {
|
||||||
|
left: -12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cluster .product .delete-btn {
|
||||||
|
color: #80868b;
|
||||||
|
font-size: 20px;
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: -10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cluster .product .delete-btn .btn {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
padding: 16px 28px 0 0;
|
||||||
|
font-size: 24px;
|
||||||
|
display: inline-block;
|
||||||
|
position: relative;
|
||||||
|
-webkit-box-sizing: border-box;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
background-color: transparent;
|
||||||
|
fill: currentColor;
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
cursor: pointer;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cluster .btn-add-new-wrapper{
|
||||||
|
display: -webkit-box;
|
||||||
|
display: -webkit-flex;
|
||||||
|
display: flex;
|
||||||
|
-webkit-flex-direction: column;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 180px;
|
||||||
|
width: 90px;
|
||||||
|
margin: 0 33px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cluster .btn-add-new{
|
||||||
|
-webkit-align-self: center;
|
||||||
|
align-self: center;
|
||||||
|
height: 90px;
|
||||||
|
padding-bottom: 16px;
|
||||||
|
width: 90px;
|
||||||
|
border-radius: 28%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cluster .btn-add-new .btn{
|
||||||
|
height: 36px;
|
||||||
|
padding: 0 23px 0 23px;
|
||||||
|
border-width: 1px;
|
||||||
|
min-height: 90px;
|
||||||
|
width: 90px;
|
||||||
|
background-color: transparent;
|
||||||
|
border-radius: 28%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cluster .btn-add-new :hover{
|
||||||
|
background-color: rgba(250, 159, 0, 0.2);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cluster .ant-typography-edit-content{
|
||||||
|
width: 200px;
|
||||||
|
}
|
||||||
@ -0,0 +1,410 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2019, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Entgra (pvt) Ltd. 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 from "react";
|
||||||
|
import {Button, Col, Divider, Icon, message, notification, Popconfirm, Row, Spin, Tooltip, Typography} from "antd";
|
||||||
|
|
||||||
|
import "./Cluster.css";
|
||||||
|
import axios from "axios";
|
||||||
|
import {withConfigContext} from "../../../../../context/ConfigContext";
|
||||||
|
import AddAppsToClusterModal from "./AddAppsToClusterModal/AddAppsToClusterModal";
|
||||||
|
import {handleApiError} from "../../../../../js/Utils";
|
||||||
|
|
||||||
|
const {Title} = Typography;
|
||||||
|
|
||||||
|
class Cluster extends React.Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
const {cluster, pageId} = this.props;
|
||||||
|
this.originalCluster = Object.assign({}, cluster);
|
||||||
|
const {name, products, clusterId} = cluster;
|
||||||
|
this.clusterId = clusterId;
|
||||||
|
this.pageId = pageId;
|
||||||
|
this.state = {
|
||||||
|
name,
|
||||||
|
products,
|
||||||
|
isSaveable: false,
|
||||||
|
loading: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
handleNameChange = (name) => {
|
||||||
|
this.setState({
|
||||||
|
name
|
||||||
|
});
|
||||||
|
if (name !== this.originalCluster.name) {
|
||||||
|
this.setState({
|
||||||
|
isSaveable: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
isProductsChanged = (currentProducts) => {
|
||||||
|
let isChanged = false;
|
||||||
|
const originalProducts = this.originalCluster.products;
|
||||||
|
if (currentProducts.length === originalProducts.length) {
|
||||||
|
for (let i = 0; i < currentProducts.length; i++) {
|
||||||
|
if (currentProducts[i].packageId !== originalProducts[i].packageId) {
|
||||||
|
isChanged = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
isChanged = true;
|
||||||
|
}
|
||||||
|
return isChanged;
|
||||||
|
};
|
||||||
|
|
||||||
|
swapProduct = (index, swapIndex) => {
|
||||||
|
const products = [...this.state.products];
|
||||||
|
if (swapIndex !== -1 && index < products.length) {
|
||||||
|
// swap elements
|
||||||
|
[products[index], products[swapIndex]] = [products[swapIndex], products[index]];
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
products,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
isSaveable: this.isProductsChanged(products)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
removeProduct = (index) => {
|
||||||
|
const products = [...this.state.products];
|
||||||
|
products.splice(index, 1);
|
||||||
|
this.setState({
|
||||||
|
products,
|
||||||
|
isSaveable: true
|
||||||
|
});
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
getCurrentCluster = () => {
|
||||||
|
const {products, name} = this.state;
|
||||||
|
return {
|
||||||
|
pageId: this.pageId,
|
||||||
|
clusterId: this.clusterId,
|
||||||
|
name: name,
|
||||||
|
products: products,
|
||||||
|
orderInPage: this.props.orderInPage
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
resetChanges = () => {
|
||||||
|
const cluster = this.originalCluster;
|
||||||
|
const {name, products} = cluster;
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
loading: false,
|
||||||
|
name,
|
||||||
|
products,
|
||||||
|
isSaveable: false
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
updateCluster = () => {
|
||||||
|
const config = this.props.context;
|
||||||
|
|
||||||
|
const cluster = this.getCurrentCluster();
|
||||||
|
this.setState({loading: true});
|
||||||
|
|
||||||
|
axios.put(
|
||||||
|
window.location.origin + config.serverConfig.invoker.uri +
|
||||||
|
"/device-mgt/android/v1.0/enterprise/store-layout/cluster",
|
||||||
|
cluster
|
||||||
|
).then(res => {
|
||||||
|
if (res.status === 200) {
|
||||||
|
notification["success"]({
|
||||||
|
message: 'Saved!',
|
||||||
|
description: 'Cluster updated successfully!'
|
||||||
|
});
|
||||||
|
const cluster = res.data.data;
|
||||||
|
const {name, products} = cluster;
|
||||||
|
|
||||||
|
this.originalCluster = Object.assign({}, cluster);
|
||||||
|
|
||||||
|
this.resetChanges();
|
||||||
|
if (this.props.toggleAddNewClusterVisibility !== undefined) {
|
||||||
|
this.props.toggleAddNewClusterVisibility(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).catch((error) => {
|
||||||
|
handleApiError(error, "Error occurred while trying to update the cluster.");
|
||||||
|
this.setState({loading: false});
|
||||||
|
});
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
deleteCluster = () => {
|
||||||
|
const config = this.props.context;
|
||||||
|
this.setState({loading: true});
|
||||||
|
|
||||||
|
axios.delete(
|
||||||
|
window.location.origin + config.serverConfig.invoker.uri +
|
||||||
|
`/device-mgt/android/v1.0/enterprise/store-layout/cluster/${this.clusterId}/page/${this.pageId}`
|
||||||
|
).then(res => {
|
||||||
|
if (res.status === 200) {
|
||||||
|
notification["success"]({
|
||||||
|
message: 'Done!',
|
||||||
|
description: 'Cluster deleted successfully!'
|
||||||
|
});
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
loading: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.props.removeLoadedCluster(this.clusterId);
|
||||||
|
|
||||||
|
}
|
||||||
|
}).catch((error) => {
|
||||||
|
handleApiError(error, "Error occurred while trying to update the cluster.");
|
||||||
|
this.setState({loading: false});
|
||||||
|
});
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
getUnselectedProducts = () => {
|
||||||
|
const {applications} = this.props;
|
||||||
|
const selectedProducts = this.state.products;
|
||||||
|
|
||||||
|
// get a copy from all products
|
||||||
|
const unSelectedProducts = [...applications];
|
||||||
|
|
||||||
|
// remove selected products from unselected products
|
||||||
|
selectedProducts.forEach((selectedProduct) => {
|
||||||
|
for (let i = 0; i < unSelectedProducts.length; i++) {
|
||||||
|
if (selectedProduct.packageId === unSelectedProducts[i].packageId) {
|
||||||
|
// remove item from array
|
||||||
|
unSelectedProducts.splice(i, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return unSelectedProducts;
|
||||||
|
};
|
||||||
|
|
||||||
|
addSelectedProducts = (products) => {
|
||||||
|
this.setState({
|
||||||
|
products: [...this.state.products, ...products],
|
||||||
|
isSaveable: products.length > 0
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
cancelAddingNewCluster = () => {
|
||||||
|
this.resetChanges();
|
||||||
|
this.props.toggleAddNewClusterVisibility(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
saveNewCluster = () => {
|
||||||
|
const config = this.props.context;
|
||||||
|
|
||||||
|
const cluster = this.getCurrentCluster();
|
||||||
|
this.setState({loading: true});
|
||||||
|
|
||||||
|
axios.post(
|
||||||
|
window.location.origin + config.serverConfig.invoker.uri +
|
||||||
|
"/device-mgt/android/v1.0/enterprise/store-layout/cluster",
|
||||||
|
cluster
|
||||||
|
).then(res => {
|
||||||
|
if (res.status === 200) {
|
||||||
|
notification["success"]({
|
||||||
|
message: 'Saved!',
|
||||||
|
description: 'Cluster updated successfully!'
|
||||||
|
});
|
||||||
|
|
||||||
|
const cluster = res.data.data;
|
||||||
|
|
||||||
|
this.resetChanges();
|
||||||
|
this.props.addSavedClusterToThePage(cluster);
|
||||||
|
}
|
||||||
|
}).catch((error) => {
|
||||||
|
if (error.hasOwnProperty("response") && error.response.status === 401) {
|
||||||
|
message.error('You are not logged in');
|
||||||
|
window.location.href = window.location.origin + '/publisher/login';
|
||||||
|
} else {
|
||||||
|
notification["error"]({
|
||||||
|
message: "There was a problem",
|
||||||
|
duration: 0,
|
||||||
|
description:
|
||||||
|
"Error occurred while trying to update the cluster.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({loading: false});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {name, products, loading} = this.state;
|
||||||
|
const unselectedProducts = this.getUnselectedProducts();
|
||||||
|
const {isTemporary, index} = this.props;
|
||||||
|
const Product = ({product, index}) => {
|
||||||
|
const {packageId} = product;
|
||||||
|
let imageSrc = "";
|
||||||
|
const iconUrl = product.iconUrl;
|
||||||
|
// check if the icon url is an url or google image id
|
||||||
|
if (iconUrl.startsWith("http")) {
|
||||||
|
imageSrc = iconUrl;
|
||||||
|
} else {
|
||||||
|
imageSrc = `https://lh3.googleusercontent.com/${iconUrl}=s240-rw`;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div className="product">
|
||||||
|
<div className="arrow">
|
||||||
|
<button disabled={index === 0} className="btn"
|
||||||
|
onClick={() => {
|
||||||
|
this.swapProduct(index, index - 1);
|
||||||
|
}}>
|
||||||
|
<Icon type="caret-left" theme="filled"/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="product-icon">
|
||||||
|
<img src={imageSrc}/>
|
||||||
|
<Tooltip title={packageId}>
|
||||||
|
<div className="title">
|
||||||
|
{packageId}
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
<div className="arrow">
|
||||||
|
<button
|
||||||
|
disabled={index === products.length - 1}
|
||||||
|
onClick={() => {
|
||||||
|
this.swapProduct(index, index + 1);
|
||||||
|
}} className="btn btn-right"><Icon type="caret-right" theme="filled"/></button>
|
||||||
|
</div>
|
||||||
|
<div className="delete-btn">
|
||||||
|
<button className="btn"
|
||||||
|
onClick={() => {
|
||||||
|
this.removeProduct(index)
|
||||||
|
}}>
|
||||||
|
<Icon type="close-circle" theme="filled"/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="cluster" id={this.props.orderInPage}>
|
||||||
|
<Spin spinning={loading}>
|
||||||
|
<Row>
|
||||||
|
<Col span={16}>
|
||||||
|
<Title editable={{onChange: this.handleNameChange}} level={4}>{name}</Title>
|
||||||
|
</Col>
|
||||||
|
<Col span={8}>
|
||||||
|
{!isTemporary && (
|
||||||
|
<div style={{float: "right"}}>
|
||||||
|
<Tooltip title="Move Up">
|
||||||
|
<Button
|
||||||
|
type="link"
|
||||||
|
icon="caret-up"
|
||||||
|
size="large"
|
||||||
|
onClick={() => {
|
||||||
|
this.props.swapClusters(index, index - 1)
|
||||||
|
}} htmlType="button"/>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip title="Move Down">
|
||||||
|
<Button
|
||||||
|
type="link"
|
||||||
|
icon="caret-down"
|
||||||
|
size="large"
|
||||||
|
onClick={() => {
|
||||||
|
this.props.swapClusters(index, index + 1)
|
||||||
|
}} htmlType="button"/>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip title="Delete Cluster">
|
||||||
|
<Popconfirm
|
||||||
|
title="Are you sure?"
|
||||||
|
okText="Yes"
|
||||||
|
cancelText="No"
|
||||||
|
onConfirm={this.deleteCluster}>
|
||||||
|
<Button
|
||||||
|
type="danger"
|
||||||
|
icon="delete"
|
||||||
|
shape="circle"
|
||||||
|
htmlType="button"/>
|
||||||
|
</Popconfirm>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<div className="products-row">
|
||||||
|
<AddAppsToClusterModal
|
||||||
|
addSelectedProducts={this.addSelectedProducts}
|
||||||
|
unselectedProducts={unselectedProducts}/>
|
||||||
|
{
|
||||||
|
products.map((product, index) => {
|
||||||
|
return (
|
||||||
|
<Product
|
||||||
|
key={product.packageId}
|
||||||
|
product={product}
|
||||||
|
index={index}/>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<Row>
|
||||||
|
<Col>
|
||||||
|
{isTemporary && (
|
||||||
|
<div>
|
||||||
|
<Button
|
||||||
|
onClick={this.cancelAddingNewCluster}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Divider type="vertical"/>
|
||||||
|
<Tooltip
|
||||||
|
title={(products.length === 0) ? "You must add applications to the cluster before saving" : ""}>
|
||||||
|
<Button
|
||||||
|
disabled={products.length === 0}
|
||||||
|
onClick={this.saveNewCluster}
|
||||||
|
htmlType="button" type="primary">
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{!isTemporary && (
|
||||||
|
<div>
|
||||||
|
<Button
|
||||||
|
onClick={this.resetChanges}
|
||||||
|
disabled={!this.state.isSaveable}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Divider type="vertical"/>
|
||||||
|
<Button
|
||||||
|
onClick={this.updateCluster}
|
||||||
|
htmlType="button" type="primary"
|
||||||
|
disabled={!this.state.isSaveable}>
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Spin>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withConfigContext(Cluster);
|
||||||
@ -0,0 +1,118 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2019, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Entgra (pvt) Ltd. 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 from "react";
|
||||||
|
import {Button, message, Modal, notification, Select, Spin} from "antd";
|
||||||
|
import axios from "axios";
|
||||||
|
import {withConfigContext} from "../../../../../context/ConfigContext";
|
||||||
|
import {handleApiError} from "../../../../../js/Utils";
|
||||||
|
|
||||||
|
const {Option} = Select;
|
||||||
|
|
||||||
|
class EditLinks extends React.Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.selectedLinks = [];
|
||||||
|
this.state = {
|
||||||
|
visible: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
showModal = () => {
|
||||||
|
this.setState({
|
||||||
|
visible: true,
|
||||||
|
loading: false
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
handleCancel = e => {
|
||||||
|
this.setState({
|
||||||
|
visible: false,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
updateLinks = () => {
|
||||||
|
const config = this.props.context;
|
||||||
|
this.setState({loading: true});
|
||||||
|
|
||||||
|
axios.put(
|
||||||
|
window.location.origin + config.serverConfig.invoker.uri +
|
||||||
|
"/device-mgt/android/v1.0/enterprise/store-layout/page-link",
|
||||||
|
{
|
||||||
|
pageId: this.props.pageId,
|
||||||
|
links: this.selectedLinks
|
||||||
|
}
|
||||||
|
).then(res => {
|
||||||
|
if (res.status === 200) {
|
||||||
|
|
||||||
|
notification["success"]({
|
||||||
|
message: 'Saved!',
|
||||||
|
description: 'Links updated successfully!'
|
||||||
|
});
|
||||||
|
|
||||||
|
this.props.updateLinks(this.selectedLinks);
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
loading: false,
|
||||||
|
visible: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).catch((error) => {
|
||||||
|
handleApiError(error, "Error occurred while trying to update the cluster.");
|
||||||
|
this.setState({loading: false});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
handleChange= (selectedLinks) =>{
|
||||||
|
this.selectedLinks = selectedLinks;
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Button onClick={this.showModal} type="link">[add / remove links]</Button>
|
||||||
|
<Modal
|
||||||
|
title="Add / Remove Links"
|
||||||
|
visible={this.state.visible}
|
||||||
|
onOk={this.updateLinks}
|
||||||
|
onCancel={this.handleCancel}
|
||||||
|
okText="Update">
|
||||||
|
<Spin spinning={this.state.loading}>
|
||||||
|
<Select
|
||||||
|
mode="multiple"
|
||||||
|
style={{width: '100%'}}
|
||||||
|
placeholder="Please select links"
|
||||||
|
defaultValue={this.props.selectedLinks}
|
||||||
|
onChange={this.handleChange}>
|
||||||
|
{
|
||||||
|
this.props.pages.map((page) => (
|
||||||
|
<Option disabled={page.id===this.props.pageId} key={page.id}>
|
||||||
|
{page.name[0]["text"]}
|
||||||
|
</Option>))
|
||||||
|
}
|
||||||
|
</Select>
|
||||||
|
</Spin>
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withConfigContext(EditLinks);
|
||||||
@ -0,0 +1,33 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2019, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Entgra (pvt) Ltd. 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.layout-pages .action {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-pages .edit {
|
||||||
|
color: #008dff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-pages .btn-warning {
|
||||||
|
color: #faad14;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-pages .btn-warning:hover {
|
||||||
|
color: #fa8905;
|
||||||
|
}
|
||||||
@ -0,0 +1,263 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2019, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Entgra (pvt) Ltd. 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 from "react";
|
||||||
|
import axios from "axios";
|
||||||
|
import {Tag, message, notification, Table, Typography, Divider, Icon, Popconfirm, Button} from "antd";
|
||||||
|
|
||||||
|
import {withConfigContext} from "../../../../context/ConfigContext";
|
||||||
|
import "./Pages.css";
|
||||||
|
import {Link} from "react-router-dom";
|
||||||
|
import AddNewPage from "../AddNewPage/AddNewPage";
|
||||||
|
import {handleApiError} from "../../../../js/Utils";
|
||||||
|
|
||||||
|
const {Text, Title} = Typography;
|
||||||
|
|
||||||
|
let config = null;
|
||||||
|
|
||||||
|
|
||||||
|
class Pages extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
config = this.props.context;
|
||||||
|
// TimeAgo.addLocale(en);
|
||||||
|
this.state = {
|
||||||
|
data: [],
|
||||||
|
pagination: {},
|
||||||
|
loading: false,
|
||||||
|
selectedRows: [],
|
||||||
|
homePageId: null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
rowSelection = {
|
||||||
|
onChange: (selectedRowKeys, selectedRows) => {
|
||||||
|
// console.lohhhg(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows);
|
||||||
|
this.setState({
|
||||||
|
selectedRows: selectedRows
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.setHomePage();
|
||||||
|
this.fetch();
|
||||||
|
}
|
||||||
|
|
||||||
|
//fetch data from api
|
||||||
|
fetch = (params = {}) => {
|
||||||
|
const config = this.props.context;
|
||||||
|
this.setState({loading: true});
|
||||||
|
// get current page
|
||||||
|
const currentPage = (params.hasOwnProperty("page")) ? params.page : 1;
|
||||||
|
|
||||||
|
const extraParams = {
|
||||||
|
offset: 10 * (currentPage - 1), //calculate the offset
|
||||||
|
limit: 10,
|
||||||
|
};
|
||||||
|
|
||||||
|
//send request to the invoker
|
||||||
|
axios.get(
|
||||||
|
window.location.origin + config.serverConfig.invoker.uri +
|
||||||
|
"/device-mgt/android/v1.0/enterprise/store-layout/page",
|
||||||
|
).then(res => {
|
||||||
|
if (res.status === 200) {
|
||||||
|
const pagination = {...this.state.pagination};
|
||||||
|
this.setState({
|
||||||
|
loading: false,
|
||||||
|
data: res.data.data.page,
|
||||||
|
pagination,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}).catch((error) => {
|
||||||
|
handleApiError(error, "Error occurred while trying to load pages.");
|
||||||
|
this.setState({loading: false});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
setHomePage = () => {
|
||||||
|
const config = this.props.context;
|
||||||
|
//send request to the invoker
|
||||||
|
axios.get(
|
||||||
|
window.location.origin + config.serverConfig.invoker.uri +
|
||||||
|
"/device-mgt/android/v1.0/enterprise/store-layout/home-page",
|
||||||
|
).then(res => {
|
||||||
|
if (res.status === 200) {
|
||||||
|
this.setState({
|
||||||
|
homePageId: res.data.data.homepageId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).catch((error) => {
|
||||||
|
handleApiError(error, "Error occurred while trying to get home page.");
|
||||||
|
this.setState({loading: false});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
updateHomePage = (pageId) => {
|
||||||
|
const config = this.props.context;
|
||||||
|
this.setState({
|
||||||
|
loading: true
|
||||||
|
});
|
||||||
|
//send request to the invoker
|
||||||
|
axios.put(
|
||||||
|
window.location.origin + config.serverConfig.invoker.uri +
|
||||||
|
"/device-mgt/android/v1.0/enterprise/store-layout/home-page/" + pageId,
|
||||||
|
{}
|
||||||
|
).then(res => {
|
||||||
|
if (res.status === 200) {
|
||||||
|
notification["success"]({
|
||||||
|
message: "Done!",
|
||||||
|
description:
|
||||||
|
"Home page was updated successfully!",
|
||||||
|
});
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
homePageId: res.data.data.homepageId,
|
||||||
|
loading: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}).catch((error) => {
|
||||||
|
handleApiError(error, "Error occurred while trying to update the home page.");
|
||||||
|
this.setState({loading: false});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
deletePage = (pageId) => {
|
||||||
|
const {data} = this.state;
|
||||||
|
const config = this.props.context;
|
||||||
|
this.setState({
|
||||||
|
loading: true
|
||||||
|
});
|
||||||
|
//send request to the invoker
|
||||||
|
axios.delete(
|
||||||
|
window.location.origin + config.serverConfig.invoker.uri +
|
||||||
|
"/device-mgt/android/v1.0/enterprise/store-layout/page/" + pageId
|
||||||
|
).then(res => {
|
||||||
|
if (res.status === 200) {
|
||||||
|
notification["success"]({
|
||||||
|
message: "Done!",
|
||||||
|
description:
|
||||||
|
"Home page was updated successfully!",
|
||||||
|
});
|
||||||
|
|
||||||
|
for( let i = 0; i < data.length; i++){
|
||||||
|
if ( data[i].id === pageId) {
|
||||||
|
data.splice(i, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
loading: false,
|
||||||
|
data: data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}).catch((error) => {
|
||||||
|
handleApiError(error, "Error occurred while trying to delete the page.");
|
||||||
|
this.setState({loading: false});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
handleTableChange = (pagination, filters, sorter) => {
|
||||||
|
const pager = {...this.state.pagination};
|
||||||
|
pager.current = pagination.current;
|
||||||
|
this.setState({
|
||||||
|
pagination: pager,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
columns = [
|
||||||
|
{
|
||||||
|
title: 'Page',
|
||||||
|
dataIndex: 'name',
|
||||||
|
key: 'name',
|
||||||
|
width: 300,
|
||||||
|
render: (name, page) => {
|
||||||
|
const pageName = name[0].text;
|
||||||
|
return (<div>
|
||||||
|
<Link to={`/publisher/manage/android-enterprise/pages/${pageName}/${page.id}`}> {pageName + " "}</Link>
|
||||||
|
{(page.id === this.state.homePageId) && (<Tag color="#badc58">Home Page</Tag>)}
|
||||||
|
</div>)
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Actions',
|
||||||
|
key: 'actions',
|
||||||
|
render: (name, page) => (
|
||||||
|
<div>
|
||||||
|
<span className="action">
|
||||||
|
<Button disabled={page.id === this.state.homePageId}
|
||||||
|
className="btn-warning"
|
||||||
|
icon="home"
|
||||||
|
type="link"
|
||||||
|
onClick={() => {
|
||||||
|
this.updateHomePage(page.id);
|
||||||
|
}}>
|
||||||
|
set as homepage
|
||||||
|
</Button>
|
||||||
|
</span>
|
||||||
|
<Divider type="vertical"/>
|
||||||
|
<Popconfirm
|
||||||
|
title="Are you sure?"
|
||||||
|
okText="Yes"
|
||||||
|
cancelText="No"
|
||||||
|
onConfirm={() => {
|
||||||
|
this.deletePage(page.id);
|
||||||
|
}}>
|
||||||
|
<span className="action">
|
||||||
|
<Text type="danger"><Icon type="delete"/> delete</Text>
|
||||||
|
</span>
|
||||||
|
</Popconfirm>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {data, pagination, loading, selectedRows} = this.state;
|
||||||
|
return (
|
||||||
|
<div className="layout-pages">
|
||||||
|
<Title level={4}>Pages</Title>
|
||||||
|
<AddNewPage/>
|
||||||
|
<div style={{backgroundColor: "#ffffff", borderRadius: 5}}>
|
||||||
|
<Table
|
||||||
|
columns={this.columns}
|
||||||
|
rowKey={record => record.id}
|
||||||
|
dataSource={data}
|
||||||
|
pagination={{
|
||||||
|
...pagination,
|
||||||
|
size: "small",
|
||||||
|
// position: "top",
|
||||||
|
showTotal: (total, range) => `showing ${range[0]}-${range[1]} of ${total} pages`,
|
||||||
|
showQuickJumper: true
|
||||||
|
}}
|
||||||
|
loading={loading}
|
||||||
|
onChange={this.handleTableChange}
|
||||||
|
// rowSelection={this.rowSelection}
|
||||||
|
scroll={{x: 1000}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withConfigContext(Pages);
|
||||||
@ -0,0 +1,79 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2019, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Entgra (pvt) Ltd. 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 from "react";
|
||||||
|
import {Button, notification} from "antd";
|
||||||
|
import axios from "axios";
|
||||||
|
import {withConfigContext} from "../../../context/ConfigContext";
|
||||||
|
import {handleApiError} from "../../../js/Utils";
|
||||||
|
|
||||||
|
class SyncAndroidApps extends React.Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
loading: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
syncApps = () => {
|
||||||
|
const config = this.props.context;
|
||||||
|
this.setState({
|
||||||
|
loading: true
|
||||||
|
});
|
||||||
|
|
||||||
|
axios.get(
|
||||||
|
window.location.origin + config.serverConfig.invoker.uri + "/device-mgt/android/v1.0/enterprise/products/sync",
|
||||||
|
).then(res => {
|
||||||
|
|
||||||
|
notification["success"]({
|
||||||
|
message: "Done!",
|
||||||
|
description:
|
||||||
|
"Apps synced successfully!",
|
||||||
|
});
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
loading: false
|
||||||
|
});
|
||||||
|
}).catch((error) => {
|
||||||
|
handleApiError(error, "Error occurred while syncing the apps.");
|
||||||
|
this.setState({
|
||||||
|
loading: false
|
||||||
|
})
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {loading} = this.state;
|
||||||
|
return (
|
||||||
|
<div style={{display: "inline-block", padding: 4}}>
|
||||||
|
<Button
|
||||||
|
onClick={this.syncApps}
|
||||||
|
loading={loading}
|
||||||
|
style={{marginTop: 16}}
|
||||||
|
type="primary"
|
||||||
|
icon="sync"
|
||||||
|
>
|
||||||
|
Sync{loading && "ing..."}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withConfigContext(SyncAndroidApps);
|
||||||
@ -87,12 +87,7 @@ class AddNewAppFormComponent extends React.Component {
|
|||||||
|
|
||||||
axios.post(
|
axios.post(
|
||||||
url,
|
url,
|
||||||
data,
|
data
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
'X-Platform': config.serverConfig.platform
|
|
||||||
},
|
|
||||||
}
|
|
||||||
).then(res => {
|
).then(res => {
|
||||||
if (res.status === 201) {
|
if (res.status === 201) {
|
||||||
this.setState({
|
this.setState({
|
||||||
|
|||||||
@ -95,7 +95,7 @@ class NewAppUploadForm extends React.Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (formConfig.installationType !== "WEB_CLIP" && formConfig.installationType !== "CUSTOM") {
|
if (formConfig.installationType !== "WEB_CLIP" && formConfig.installationType !== "CUSTOM") {
|
||||||
release.supportedOsVersions = "4.0-10.0";
|
release.supportedOsVersions = "4-30";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (specificElements.hasOwnProperty("version")) {
|
if (specificElements.hasOwnProperty("version")) {
|
||||||
|
|||||||
@ -88,7 +88,7 @@ class AddNewReleaseFormComponent extends React.Component {
|
|||||||
isSharedWithAllTenants,
|
isSharedWithAllTenants,
|
||||||
metaData: "string",
|
metaData: "string",
|
||||||
releaseType: releaseType,
|
releaseType: releaseType,
|
||||||
supportedOsVersions: "4.0-10.0"
|
supportedOsVersions: "4-30"
|
||||||
};
|
};
|
||||||
|
|
||||||
data.append('binaryFile', binaryFile[0].originFileObj);
|
data.append('binaryFile', binaryFile[0].originFileObj);
|
||||||
@ -108,12 +108,7 @@ class AddNewReleaseFormComponent extends React.Component {
|
|||||||
|
|
||||||
axios.post(
|
axios.post(
|
||||||
url,
|
url,
|
||||||
data,
|
data
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
'X-Platform': config.serverConfig.platform
|
|
||||||
},
|
|
||||||
}
|
|
||||||
).then(res => {
|
).then(res => {
|
||||||
if (res.status === 201) {
|
if (res.status === 201) {
|
||||||
this.setState({
|
this.setState({
|
||||||
|
|||||||
@ -21,6 +21,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=0"/>
|
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=0"/>
|
||||||
<title>Entgra App Publisher</title>
|
<title>Entgra App Publisher</title>
|
||||||
|
<script src="https://apis.google.com/js/client.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
</html>
|
</html>
|
||||||
@ -31,6 +31,8 @@ import AddNewPublicApp from "./pages/dashboard/add-new-app/AddNewPublicApp";
|
|||||||
import AddNewWebClip from "./pages/dashboard/add-new-app/AddNewWebClip";
|
import AddNewWebClip from "./pages/dashboard/add-new-app/AddNewWebClip";
|
||||||
import AddNewRelease from "./pages/dashboard/add-new-release/AddNewRelease";
|
import AddNewRelease from "./pages/dashboard/add-new-release/AddNewRelease";
|
||||||
import AddNewCustomApp from "./pages/dashboard/add-new-app/AddNewCustomApp";
|
import AddNewCustomApp from "./pages/dashboard/add-new-app/AddNewCustomApp";
|
||||||
|
import ManageAndroidEnterprise from "./pages/dashboard/manage/android-enterprise/ManageAndroidEnterprise";
|
||||||
|
import Page from "./pages/dashboard/manage/android-enterprise/page/Page";
|
||||||
|
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
@ -83,6 +85,16 @@ const routes = [
|
|||||||
path: '/publisher/manage',
|
path: '/publisher/manage',
|
||||||
component: Mange,
|
component: Mange,
|
||||||
exact: true
|
exact: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/publisher/manage/android-enterprise',
|
||||||
|
component: ManageAndroidEnterprise,
|
||||||
|
exact: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/publisher/manage/android-enterprise/pages/:pageName/:pageId',
|
||||||
|
component: Page,
|
||||||
|
exact: true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2019, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Entgra (pvt) Ltd. 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 {message, notification} from "antd";
|
||||||
|
|
||||||
|
export const handleApiError = (error, message) => {
|
||||||
|
if (error.hasOwnProperty("response") && error.response.status === 401) {
|
||||||
|
message.error('You are not logged in');
|
||||||
|
window.location.href = window.location.origin + '/publisher/login';
|
||||||
|
} else {
|
||||||
|
notification["error"]({
|
||||||
|
message: "There was a problem",
|
||||||
|
duration: 0,
|
||||||
|
description: message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -77,18 +77,22 @@ class Dashboard extends React.Component {
|
|||||||
<Icon type="plus"/>
|
<Icon type="plus"/>
|
||||||
Add New App
|
Add New App
|
||||||
</span>
|
</span>
|
||||||
}>
|
}
|
||||||
<Menu.Item key="setting:1">
|
>
|
||||||
<Link to="/publisher/add-new-app/public">Public APP</Link>
|
<Menu.Item key="add-new-public-app">
|
||||||
|
<Link to="/publisher/add-new-app/public">
|
||||||
|
Public APP
|
||||||
|
</Link>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
<Menu.Item key="setting:2">
|
<Menu.Item key="add-new-enterprise-app">
|
||||||
<Link to="/publisher/add-new-app/enterprise">Enterprise APP</Link>
|
<Link to="/publisher/add-new-app/enterprise">
|
||||||
|
Enterprise APP
|
||||||
|
</Link>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
<Menu.Item key="setting:3">
|
<Menu.Item key="add-new-web-clip">
|
||||||
<Link to="/publisher/add-new-app/web-clip">Web Clip</Link>
|
<Link to="/publisher/add-new-app/web-clip">
|
||||||
</Menu.Item>
|
Web Clip
|
||||||
<Menu.Item key="setting:4">
|
</Link>
|
||||||
<Link to="/publisher/add-new-app/custom-app">Custom App</Link>
|
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
</SubMenu>
|
</SubMenu>
|
||||||
|
|
||||||
|
|||||||
@ -24,22 +24,6 @@ import {Link} from "react-router-dom";
|
|||||||
|
|
||||||
const {Paragraph} = Typography;
|
const {Paragraph} = Typography;
|
||||||
|
|
||||||
const routes = [
|
|
||||||
{
|
|
||||||
path: 'index',
|
|
||||||
breadcrumbName: 'Publisher',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'first',
|
|
||||||
breadcrumbName: 'Dashboard',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'second',
|
|
||||||
breadcrumbName: 'Manage',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
|
|
||||||
class Manage extends React.Component {
|
class Manage extends React.Component {
|
||||||
routes;
|
routes;
|
||||||
|
|
||||||
@ -57,10 +41,13 @@ class Manage extends React.Component {
|
|||||||
<Breadcrumb.Item>
|
<Breadcrumb.Item>
|
||||||
<Link to="/publisher/apps"><Icon type="home"/> Home</Link>
|
<Link to="/publisher/apps"><Icon type="home"/> Home</Link>
|
||||||
</Breadcrumb.Item>
|
</Breadcrumb.Item>
|
||||||
<Breadcrumb.Item>Manage</Breadcrumb.Item>
|
<Breadcrumb.Item>
|
||||||
|
Manage
|
||||||
|
</Breadcrumb.Item>
|
||||||
|
<Breadcrumb.Item>General</Breadcrumb.Item>
|
||||||
</Breadcrumb>
|
</Breadcrumb>
|
||||||
<div className="wrap">
|
<div className="wrap">
|
||||||
<h3>Manage</h3>
|
<h3>Manage General Settings</h3>
|
||||||
<Paragraph>Maintain and manage categories and tags here..</Paragraph>
|
<Paragraph>Maintain and manage categories and tags here..</Paragraph>
|
||||||
</div>
|
</div>
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
|
|||||||
@ -0,0 +1,68 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2019, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Entgra (pvt) Ltd. 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 from "react";
|
||||||
|
import {PageHeader, Typography, Breadcrumb, Divider, Button, Icon} from "antd";
|
||||||
|
import {Link} from "react-router-dom";
|
||||||
|
import SyncAndroidApps from "../../../../components/manage/android-enterprise/SyncAndroidApps";
|
||||||
|
import {withConfigContext} from "../../../../context/ConfigContext";
|
||||||
|
import GooglePlayIframe from "../../../../components/manage/android-enterprise/GooglePlayIframe";
|
||||||
|
import Pages from "../../../../components/manage/android-enterprise/Pages/Pages";
|
||||||
|
|
||||||
|
const {Paragraph} = Typography;
|
||||||
|
|
||||||
|
class ManageAndroidEnterprise extends React.Component {
|
||||||
|
routes;
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.routes = props.routes;
|
||||||
|
this.config = this.props.context;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<PageHeader style={{paddingTop: 0}}>
|
||||||
|
<Breadcrumb style={{paddingBottom: 16}}>
|
||||||
|
<Breadcrumb.Item>
|
||||||
|
<Link to="/publisher/apps"><Icon type="home"/> Home</Link>
|
||||||
|
</Breadcrumb.Item>
|
||||||
|
<Breadcrumb.Item>
|
||||||
|
Manage
|
||||||
|
</Breadcrumb.Item>
|
||||||
|
<Breadcrumb.Item>Android Enterprise</Breadcrumb.Item>
|
||||||
|
</Breadcrumb>
|
||||||
|
<div className="wrap">
|
||||||
|
<h3>Manage Android Enterprise</h3>
|
||||||
|
{/*<Paragraph>Lorem ipsum</Paragraph>*/}
|
||||||
|
</div>
|
||||||
|
</PageHeader>
|
||||||
|
<div style={{background: '#f0f2f5', padding: 24, minHeight: 720}}>
|
||||||
|
<SyncAndroidApps/>
|
||||||
|
<GooglePlayIframe/>
|
||||||
|
<Divider/>
|
||||||
|
<Pages/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withConfigContext(ManageAndroidEnterprise);
|
||||||
@ -0,0 +1,396 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2019, Entgra (pvt) Ltd. (http://entgra.io) All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Entgra (pvt) Ltd. 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 from "react";
|
||||||
|
import {
|
||||||
|
PageHeader,
|
||||||
|
Typography,
|
||||||
|
Breadcrumb,
|
||||||
|
Button,
|
||||||
|
Icon,
|
||||||
|
Col,
|
||||||
|
Row,
|
||||||
|
notification,
|
||||||
|
message,
|
||||||
|
Spin,
|
||||||
|
Select,
|
||||||
|
Tag,
|
||||||
|
Divider
|
||||||
|
} from "antd";
|
||||||
|
import {Link, withRouter} from "react-router-dom";
|
||||||
|
import {withConfigContext} from "../../../../../context/ConfigContext";
|
||||||
|
import axios from "axios";
|
||||||
|
import Cluster from "../../../../../components/manage/android-enterprise/Pages/Cluster/Cluster";
|
||||||
|
import EditLinks from "../../../../../components/manage/android-enterprise/Pages/EditLinks/EditLinks";
|
||||||
|
|
||||||
|
const {Option} = Select;
|
||||||
|
const {Title, Text} = Typography;
|
||||||
|
|
||||||
|
class Page extends React.Component {
|
||||||
|
routes;
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
const {pageName, pageId} = this.props.match.params;
|
||||||
|
this.pageId = pageId;
|
||||||
|
this.routes = props.routes;
|
||||||
|
this.config = this.props.context;
|
||||||
|
this.pages = [];
|
||||||
|
this.pageNames = {};
|
||||||
|
this.state = {
|
||||||
|
pageName,
|
||||||
|
clusters: [],
|
||||||
|
loading: false,
|
||||||
|
applications: [],
|
||||||
|
isAddNewClusterVisible: false,
|
||||||
|
links: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.fetchClusters();
|
||||||
|
this.fetchApplications();
|
||||||
|
this.fetchPages();
|
||||||
|
}
|
||||||
|
|
||||||
|
removeLoadedCluster = (clusterId) => {
|
||||||
|
const clusters = [...this.state.clusters];
|
||||||
|
let index = -1;
|
||||||
|
for (let i = 0; i < clusters.length; i++) {
|
||||||
|
if (clusters[i].clusterId === clusterId) {
|
||||||
|
index = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
clusters.splice(index, 1);
|
||||||
|
this.setState({
|
||||||
|
clusters
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
updatePageName = pageName => {
|
||||||
|
const config = this.props.context;
|
||||||
|
if (pageName !== this.state.pageName && pageName !== "") {
|
||||||
|
const data = {
|
||||||
|
locale: "en",
|
||||||
|
pageName: pageName,
|
||||||
|
pageId: this.pageId
|
||||||
|
};
|
||||||
|
axios.put(
|
||||||
|
window.location.origin + config.serverConfig.invoker.uri +
|
||||||
|
"/device-mgt/android/v1.0/enterprise/store-layout/page",
|
||||||
|
data
|
||||||
|
).then(res => {
|
||||||
|
if (res.status === 200) {
|
||||||
|
notification["success"]({
|
||||||
|
message: 'Saved!',
|
||||||
|
description: 'Page name updated successfully!'
|
||||||
|
});
|
||||||
|
this.setState({
|
||||||
|
loading: false,
|
||||||
|
pageName: res.data.data.pageName,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.props.history.push(`/publisher/manage/android-enterprise/pages/${pageName}/${this.pageId}`);
|
||||||
|
|
||||||
|
}
|
||||||
|
}).catch((error) => {
|
||||||
|
if (error.hasOwnProperty("response") && error.response.status === 401) {
|
||||||
|
message.error('You are not logged in');
|
||||||
|
window.location.href = window.location.origin + '/publisher/login';
|
||||||
|
} else {
|
||||||
|
notification["error"]({
|
||||||
|
message: "There was a problem",
|
||||||
|
duration: 0,
|
||||||
|
description:
|
||||||
|
"Error occurred while trying to save the page name.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({loading: false});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
swapClusters = (index, swapIndex) => {
|
||||||
|
const clusters = [...this.state.clusters];
|
||||||
|
|
||||||
|
if (swapIndex !== -1 && index < clusters.length) {
|
||||||
|
// swap elements
|
||||||
|
[clusters[index], clusters[swapIndex]] = [clusters[swapIndex], clusters[index]];
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
clusters,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchPages = () => {
|
||||||
|
const config = this.props.context;
|
||||||
|
this.setState({loading: true});
|
||||||
|
|
||||||
|
//send request to the invoker
|
||||||
|
axios.get(
|
||||||
|
window.location.origin + config.serverConfig.invoker.uri +
|
||||||
|
"/device-mgt/android/v1.0/enterprise/store-layout/page",
|
||||||
|
).then(res => {
|
||||||
|
if (res.status === 200) {
|
||||||
|
this.pages = res.data.data.page;
|
||||||
|
|
||||||
|
let links = [];
|
||||||
|
|
||||||
|
this.pages.forEach((page) => {
|
||||||
|
this.pageNames[page.id.toString()] = page.name[0]["text"];
|
||||||
|
if (page.id === this.pageId && page.hasOwnProperty("link")) {
|
||||||
|
links = page["link"];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
loading: false,
|
||||||
|
links
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}).catch((error) => {
|
||||||
|
if (error.hasOwnProperty("response") && error.response.status === 401) {
|
||||||
|
message.error('You are not logged in');
|
||||||
|
window.location.href = window.location.origin + '/publisher/login';
|
||||||
|
} else {
|
||||||
|
notification["error"]({
|
||||||
|
message: "There was a problem",
|
||||||
|
duration: 0,
|
||||||
|
description:
|
||||||
|
"Error occurred while trying to load pages.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({loading: false});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchClusters = () => {
|
||||||
|
const config = this.props.context;
|
||||||
|
axios.get(
|
||||||
|
window.location.origin + config.serverConfig.invoker.uri +
|
||||||
|
`/device-mgt/android/v1.0/enterprise/store-layout/page/${this.pageId}/clusters`
|
||||||
|
).then(res => {
|
||||||
|
if (res.status === 200) {
|
||||||
|
let clusters = JSON.parse(res.data.data);
|
||||||
|
|
||||||
|
// sort according to the orderInPage value
|
||||||
|
clusters.sort((a, b) => (a.orderInPage > b.orderInPage) ? 1 : -1);
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
clusters,
|
||||||
|
loading: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).catch((error) => {
|
||||||
|
if (error.hasOwnProperty("response") && error.response.status === 401) {
|
||||||
|
window.location.href = window.location.origin + '/publisher/login';
|
||||||
|
} else if (!(error.hasOwnProperty("response") && error.response.status === 404)) {
|
||||||
|
// API sends 404 when no apps
|
||||||
|
notification["error"]({
|
||||||
|
message: "There was a problem",
|
||||||
|
duration: 0,
|
||||||
|
description:
|
||||||
|
"Error occurred while trying to load clusters.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.setState({
|
||||||
|
loading: false
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
//fetch applications
|
||||||
|
fetchApplications = () => {
|
||||||
|
const config = this.props.context;
|
||||||
|
this.setState({loading: true});
|
||||||
|
|
||||||
|
const filters = {
|
||||||
|
appType: "PUBLIC",
|
||||||
|
deviceType: "android"
|
||||||
|
};
|
||||||
|
|
||||||
|
//send request to the invoker
|
||||||
|
axios.post(
|
||||||
|
window.location.origin + config.serverConfig.invoker.uri + config.serverConfig.invoker.publisher + "/applications",
|
||||||
|
filters
|
||||||
|
).then(res => {
|
||||||
|
if (res.status === 200) {
|
||||||
|
const applications = res.data.data.applications.map(application => {
|
||||||
|
const release = application.applicationReleases[0];
|
||||||
|
return {
|
||||||
|
packageId: `app:${application.packageName}`,
|
||||||
|
iconUrl: release.iconPath,
|
||||||
|
name: application.name
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
loading: false,
|
||||||
|
applications,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}).catch((error) => {
|
||||||
|
if (error.hasOwnProperty("response") && error.response.status === 401) {
|
||||||
|
message.error('You are not logged in');
|
||||||
|
window.location.href = window.location.origin + '/publisher/login';
|
||||||
|
} else {
|
||||||
|
notification["error"]({
|
||||||
|
message: "There was a problem",
|
||||||
|
duration: 0,
|
||||||
|
description:
|
||||||
|
"Error occurred while trying to load pages.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({loading: false});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
toggleAddNewClusterVisibility = (isAddNewClusterVisible) => {
|
||||||
|
this.setState({
|
||||||
|
isAddNewClusterVisible
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
addSavedClusterToThePage = (cluster) => {
|
||||||
|
this.setState({
|
||||||
|
clusters: [...this.state.clusters, cluster],
|
||||||
|
isAddNewClusterVisible: false
|
||||||
|
});
|
||||||
|
window.scrollTo(0, document.body.scrollHeight);
|
||||||
|
};
|
||||||
|
|
||||||
|
updateLinks = (links) =>{
|
||||||
|
this.setState({
|
||||||
|
links
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {pageName, loading, clusters, applications, isAddNewClusterVisible, links} = this.state;
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<PageHeader style={{paddingTop: 0}}>
|
||||||
|
<Breadcrumb style={{paddingBottom: 16}}>
|
||||||
|
<Breadcrumb.Item>
|
||||||
|
<Link to="/publisher/apps"><Icon type="home"/> Home</Link>
|
||||||
|
</Breadcrumb.Item>
|
||||||
|
<Breadcrumb.Item>
|
||||||
|
Manage
|
||||||
|
</Breadcrumb.Item>
|
||||||
|
<Breadcrumb.Item>
|
||||||
|
<Link to="/publisher/manage/android-enterprise">Android Enterprise</Link>
|
||||||
|
</Breadcrumb.Item>
|
||||||
|
<Breadcrumb.Item>Manage Page</Breadcrumb.Item>
|
||||||
|
</Breadcrumb>
|
||||||
|
<div className="wrap">
|
||||||
|
<h3>Manage Android Enterprise</h3>
|
||||||
|
{/*<Paragraph>Lorem ipsum</Paragraph>*/}
|
||||||
|
</div>
|
||||||
|
</PageHeader>
|
||||||
|
<Spin spinning={loading}>
|
||||||
|
<div style={{background: '#f0f2f5', padding: 24, minHeight: 720}}>
|
||||||
|
<Row>
|
||||||
|
<Col md={8} sm={18} xs={24}>
|
||||||
|
<Title editable={{onChange: this.updatePageName}} level={2}>{pageName}</Title>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row>
|
||||||
|
<Col>
|
||||||
|
<Title level={4}>Links</Title>
|
||||||
|
{
|
||||||
|
links.map(link => {
|
||||||
|
if (this.pageNames.hasOwnProperty(link.toString())) {
|
||||||
|
return <Tag key={link}
|
||||||
|
color="#87d068">{this.pageNames[link.toString()]}</Tag>
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
<EditLinks
|
||||||
|
updateLinks={this.updateLinks}
|
||||||
|
pageId={this.pageId}
|
||||||
|
selectedLinks={links}
|
||||||
|
pages={this.pages}/>
|
||||||
|
</Col>
|
||||||
|
{/*<Col>*/}
|
||||||
|
|
||||||
|
{/*</Col>*/}
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
<Divider dashed={true}/>
|
||||||
|
<Title level={4}>Clusters</Title>
|
||||||
|
|
||||||
|
<div hidden={isAddNewClusterVisible} style={{textAlign: "center"}}>
|
||||||
|
<Button
|
||||||
|
type="dashed"
|
||||||
|
shape="round"
|
||||||
|
icon="plus"
|
||||||
|
size="large"
|
||||||
|
onClick={() => {
|
||||||
|
this.toggleAddNewClusterVisibility(true);
|
||||||
|
}}
|
||||||
|
>Add new cluster</Button>
|
||||||
|
</div>
|
||||||
|
<div hidden={!isAddNewClusterVisible}>
|
||||||
|
<Cluster
|
||||||
|
cluster={{
|
||||||
|
clusterId: 0,
|
||||||
|
name: "New Cluster",
|
||||||
|
products: []
|
||||||
|
}}
|
||||||
|
orderInPage={clusters.length}
|
||||||
|
isTemporary={true}
|
||||||
|
pageId={this.pageId}
|
||||||
|
applications={applications}
|
||||||
|
addSavedClusterToThePage={this.addSavedClusterToThePage}
|
||||||
|
toggleAddNewClusterVisibility={this.toggleAddNewClusterVisibility}/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{
|
||||||
|
clusters.map((cluster, index) => {
|
||||||
|
return (
|
||||||
|
<Cluster
|
||||||
|
key={cluster.clusterId}
|
||||||
|
index={index}
|
||||||
|
orderInPage={cluster.orderInPage}
|
||||||
|
isTemporary={false}
|
||||||
|
cluster={cluster}
|
||||||
|
pageId={this.pageId}
|
||||||
|
applications={applications}
|
||||||
|
swapClusters={this.swapClusters}
|
||||||
|
removeLoadedCluster={this.removeLoadedCluster}/>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</Spin>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withConfigContext(withRouter(Page));
|
||||||
@ -78,7 +78,7 @@ const config = {
|
|||||||
loader: "style-loader"
|
loader: "style-loader"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
loader: "css-loader",
|
loader: "css-loader"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
loader: "less-loader",
|
loader: "less-loader",
|
||||||
|
|||||||
@ -0,0 +1,132 @@
|
|||||||
|
<%
|
||||||
|
var log = new Log("api/enterprise.jag");
|
||||||
|
|
||||||
|
var uri = request.getRequestURI();
|
||||||
|
var uriMatcher = new URIMatcher(String(uri));
|
||||||
|
|
||||||
|
var constants = require("/app/modules/constants.js");
|
||||||
|
var devicemgtProps = require("/app/modules/conf-reader/main.js")["conf"];
|
||||||
|
var userModule = require("/app/modules/business-controllers/user.js")["userModule"];
|
||||||
|
var restAPIRequestDetails = request.getContent();
|
||||||
|
var result;
|
||||||
|
var user = session.get(constants.USER_SESSION_KEY);
|
||||||
|
|
||||||
|
// This checks if the session is valid
|
||||||
|
getAccessToken = function() {
|
||||||
|
if (session) {
|
||||||
|
var tokenPair = session.get(constants["TOKEN_PAIR"]);
|
||||||
|
if (tokenPair) {
|
||||||
|
return parse(tokenPair)["accessToken"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
callBackend = function(url, token, method, payload) {
|
||||||
|
var xmlHttpRequest = new XMLHttpRequest();
|
||||||
|
xmlHttpRequest.open(method, url);
|
||||||
|
xmlHttpRequest.setRequestHeader(constants["AUTHORIZATION_HEADER"], constants["BEARER_PREFIX"] + token);
|
||||||
|
xmlHttpRequest.setRequestHeader(constants["CONTENT_TYPE_IDENTIFIER"], constants["APPLICATION_JSON"]);
|
||||||
|
xmlHttpRequest.setRequestHeader(constants["ACCEPT_IDENTIFIER"], constants["APPLICATION_JSON"]);
|
||||||
|
if (payload) {
|
||||||
|
xmlHttpRequest.send(payload);
|
||||||
|
} else {
|
||||||
|
xmlHttpRequest.send();
|
||||||
|
}
|
||||||
|
response["status"] = xmlHttpRequest["status"];
|
||||||
|
if (xmlHttpRequest["responseText"]) {
|
||||||
|
result = xmlHttpRequest["responseText"];
|
||||||
|
response["content"] = xmlHttpRequest["responseText"];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var accessToken = getAccessToken();
|
||||||
|
if (!user || accessToken == null) {
|
||||||
|
response.sendRedirect("/devicemgt/login?#login-required");
|
||||||
|
exit();
|
||||||
|
} else {
|
||||||
|
response.contentType = 'application/json';
|
||||||
|
if (uriMatcher.match("/{context}/api/enterprise/token")) {
|
||||||
|
session.put("externalEndpoint", restAPIRequestDetails["endpoint"]);
|
||||||
|
session.put("externalToken", restAPIRequestDetails["externalToken"]);
|
||||||
|
log.info("Calling get token");
|
||||||
|
callBackend(restAPIRequestDetails["endpoint"], session.get("externalToken"), "POST", restAPIRequestDetails);
|
||||||
|
if (response["status"] && response["status"] == 200) {
|
||||||
|
var completionToken = parse(result)["completionToken"];
|
||||||
|
if (completionToken) {
|
||||||
|
log.info("Token received");
|
||||||
|
session.put("completionToken", completionToken)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (uriMatcher.match("/{context}/api/enterprise/enroll-complete")) {
|
||||||
|
var tokenEndpoint = session.get("externalEndpoint")
|
||||||
|
var enterpriseEndpoint = tokenEndpoint.replace("signup-url", "complete-signup");
|
||||||
|
|
||||||
|
var completionToken = session.get("completionToken");
|
||||||
|
var enterpriseToken = request.getParameter("enterpriseToken");
|
||||||
|
|
||||||
|
log.debug("completionToken" + completionToken + ", enterpriseEndpoint" + enterpriseEndpoint +
|
||||||
|
", enterpriseToken" + enterpriseToken);
|
||||||
|
|
||||||
|
var requestPayload = {}
|
||||||
|
requestPayload.completionToken = completionToken;
|
||||||
|
requestPayload.enterpriseToken = enterpriseToken;
|
||||||
|
log.info("Calling complete-signup");
|
||||||
|
callBackend(enterpriseEndpoint, session.get("externalToken"), "POST", requestPayload);
|
||||||
|
|
||||||
|
var enterpriseId = parse(result)["id"];
|
||||||
|
if (enterpriseId) {
|
||||||
|
log.info("Calling complete-signup success");
|
||||||
|
var serviceAccountRequest = {};
|
||||||
|
serviceAccountRequest.enterpriseId = enterpriseId;
|
||||||
|
serviceAccountRequest.keyType = "googleCredentials"
|
||||||
|
|
||||||
|
var enterpriseEndpoint = tokenEndpoint.replace("signup-url", "create-esa");
|
||||||
|
|
||||||
|
log.info("Calling create-esa");
|
||||||
|
callBackend(enterpriseEndpoint, session.get("externalToken"), "POST", serviceAccountRequest);
|
||||||
|
var data = parse(result)["data"];
|
||||||
|
log.info("Calling create-esa success" + data);
|
||||||
|
|
||||||
|
var androidConfigAPI = devicemgtProps["httpsURL"] + "/api/device-mgt/android/v1.0/configuration";
|
||||||
|
log.info("fetching platform configs");
|
||||||
|
callBackend(androidConfigAPI, accessToken, "GET");
|
||||||
|
|
||||||
|
var configurationsList = parse(result);
|
||||||
|
|
||||||
|
for (var x = 0; x < configurationsList.configuration.length; x++) {
|
||||||
|
if (configurationsList.configuration[x].name == "esa" || configurationsList.configuration[x].name == "enterpriseId") {
|
||||||
|
configurationsList.configuration.splice(x, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.info("fetching platform configs success");
|
||||||
|
var payloadToAdd = {};
|
||||||
|
payloadToAdd.contentType = "text";
|
||||||
|
payloadToAdd.name = "esa";
|
||||||
|
payloadToAdd.value = data;
|
||||||
|
|
||||||
|
var enterpriseIdPayload = {};
|
||||||
|
enterpriseIdPayload.contentType = "text";
|
||||||
|
enterpriseIdPayload.name = "enterpriseId";
|
||||||
|
enterpriseIdPayload.value = enterpriseId;
|
||||||
|
|
||||||
|
configurationsList.configuration[configurationsList.configuration.length] = payloadToAdd;
|
||||||
|
configurationsList.configuration[configurationsList.configuration.length] = enterpriseIdPayload;
|
||||||
|
|
||||||
|
log.info("saving platform configs");
|
||||||
|
callBackend(androidConfigAPI, accessToken, "PUT", configurationsList);
|
||||||
|
log.info("saving platform configs success");
|
||||||
|
if (response["status"] == 200) {
|
||||||
|
log.info("Process successful!! Redirecting...");
|
||||||
|
response.sendRedirect("/devicemgt/platform-configuration?enterprise-success=true");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (uriMatcher.match("/{context}/api/enterprise/asdsad/unenroll")) {
|
||||||
|
session.put("externalEndpoint", restAPIRequestDetails["endpoint"]);
|
||||||
|
session.put("externalToken", restAPIRequestDetails["externalToken"]);
|
||||||
|
callBackend(restAPIRequestDetails["endpoint"], session.get("externalToken"), "POST", restAPIRequestDetails);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
%>
|
||||||
@ -224,6 +224,7 @@ policyModule = function () {
|
|||||||
appObjectFromRestEndpoint = appListFromRestEndpoint[i];
|
appObjectFromRestEndpoint = appListFromRestEndpoint[i];
|
||||||
appObjectToView = {};
|
appObjectToView = {};
|
||||||
appObjectToView["appName"] = appObjectFromRestEndpoint["name"];
|
appObjectToView["appName"] = appObjectFromRestEndpoint["name"];
|
||||||
|
appObjectToView["packageName"] = appObjectFromRestEndpoint["packageName"];
|
||||||
appObjectToView["appId"] = appObjectFromRestEndpoint["id"];
|
appObjectToView["appId"] = appObjectFromRestEndpoint["id"];
|
||||||
if ("WEB_CLIP" === appObjectFromRestEndpoint["type"]) {
|
if ("WEB_CLIP" === appObjectFromRestEndpoint["type"]) {
|
||||||
appObjectToView["type"] = "Web Clip"
|
appObjectToView["type"] = "Web Clip"
|
||||||
|
|||||||
@ -3,6 +3,10 @@
|
|||||||
"logLevel": "info",
|
"logLevel": "info",
|
||||||
"initScripts": ["/app/modules/init.js"],
|
"initScripts": ["/app/modules/init.js"],
|
||||||
"urlMappings": [
|
"urlMappings": [
|
||||||
|
{
|
||||||
|
"url": "/api/enterprise/*",
|
||||||
|
"path": "/api/enterprise.jag"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"url": "/api/devices/*",
|
"url": "/api/devices/*",
|
||||||
"path": "/api/device-api.jag"
|
"path": "/api/device-api.jag"
|
||||||
|
|||||||
@ -271,7 +271,7 @@ public class InvokerHandler extends HttpServlet {
|
|||||||
*/
|
*/
|
||||||
private boolean validateRequest(HttpServletRequest req, HttpServletResponse resp)
|
private boolean validateRequest(HttpServletRequest req, HttpServletResponse resp)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
serverUrl = req.getScheme() + "://" + req.getServerName() + ":" + req.getServerPort();
|
serverUrl = req.getScheme() + "://" + req.getServerName() + ":" + System.getProperty("iot.gateway.https.port");
|
||||||
apiEndpoint = req.getPathInfo();
|
apiEndpoint = req.getPathInfo();
|
||||||
HttpSession session = req.getSession(false);
|
HttpSession session = req.getSession(false);
|
||||||
|
|
||||||
|
|||||||
@ -225,8 +225,9 @@ public class LoginHandler extends HttpServlet {
|
|||||||
password = req.getParameter("password");
|
password = req.getParameter("password");
|
||||||
platform = req.getParameter(HandlerConstants.PLATFORM);
|
platform = req.getParameter(HandlerConstants.PLATFORM);
|
||||||
serverUrl = req.getScheme() + HandlerConstants.SCHEME_SEPARATOR + req.getServerName() + HandlerConstants.COLON
|
serverUrl = req.getScheme() + HandlerConstants.SCHEME_SEPARATOR + req.getServerName() + HandlerConstants.COLON
|
||||||
+ req.getServerPort();
|
+ System.getProperty("iot.gateway.https.port");
|
||||||
uiConfigUrl = serverUrl + HandlerConstants.UI_CONFIG_ENDPOINT;
|
uiConfigUrl = req.getScheme() + HandlerConstants.SCHEME_SEPARATOR + req.getServerName() + HandlerConstants.COLON
|
||||||
|
+ System.getProperty("iot.gateway.carbon.https.port") + HandlerConstants.UI_CONFIG_ENDPOINT;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (platform == null) {
|
if (platform == null) {
|
||||||
|
|||||||
@ -22,7 +22,7 @@ public class HandlerConstants {
|
|||||||
public static final String PUBLISHER_APPLICATION_NAME = "application-mgt-publisher";
|
public static final String PUBLISHER_APPLICATION_NAME = "application-mgt-publisher";
|
||||||
public static final String APP_REG_ENDPOINT = "/api-application-registration/register";
|
public static final String APP_REG_ENDPOINT = "/api-application-registration/register";
|
||||||
public static final String UI_CONFIG_ENDPOINT = "/api/application-mgt/v1.0/config/ui-config";
|
public static final String UI_CONFIG_ENDPOINT = "/api/application-mgt/v1.0/config/ui-config";
|
||||||
public static final String TOKEN_ENDPOINT = "/oauth2/token";
|
public static final String TOKEN_ENDPOINT = "/token";
|
||||||
public static final String LOGIN_PAGE = "/login";
|
public static final String LOGIN_PAGE = "/login";
|
||||||
public static final String BASIC = "Basic ";
|
public static final String BASIC = "Basic ";
|
||||||
public static final String BEARER = "Bearer ";
|
public static final String BEARER = "Bearer ";
|
||||||
|
|||||||
@ -156,6 +156,12 @@
|
|||||||
<Scope>perm:admin:app:review:view</Scope>
|
<Scope>perm:admin:app:review:view</Scope>
|
||||||
<Scope>perm:admin:app:publisher:update</Scope>
|
<Scope>perm:admin:app:publisher:update</Scope>
|
||||||
<Scope>perm:admin:app:review:update</Scope>
|
<Scope>perm:admin:app:review:update</Scope>
|
||||||
|
<Scope>perm:device-types:types</Scope>
|
||||||
|
<Scope>perm:enterprise:modify</Scope>
|
||||||
|
<Scope>perm:enterprise:view</Scope>
|
||||||
|
<Scope>perm:android-work:customer</Scope>
|
||||||
|
<Scope>perm:android-work:admin</Scope>
|
||||||
|
<Scope>perm:application-command:modify</Scope>
|
||||||
</Scopes>
|
</Scopes>
|
||||||
<SSOConfiguration>
|
<SSOConfiguration>
|
||||||
<Issuer>app-mgt</Issuer>
|
<Issuer>app-mgt</Issuer>
|
||||||
@ -176,6 +182,39 @@
|
|||||||
<AppCategories>
|
<AppCategories>
|
||||||
<Category>EMM</Category>
|
<Category>EMM</Category>
|
||||||
<Category>IoT</Category>
|
<Category>IoT</Category>
|
||||||
|
<Category>Art & Design</Category>
|
||||||
|
<Category>Auto & Vehicles</Category>
|
||||||
|
<Category>Beauty</Category>
|
||||||
|
<Category>Books & Reference</Category>
|
||||||
|
<Category>Business</Category>
|
||||||
|
<Category>Comics</Category>
|
||||||
|
<Category>Communications</Category>
|
||||||
|
<Category>Dating</Category>
|
||||||
|
<Category>Education</Category>
|
||||||
|
<Category>Entertainment</Category>
|
||||||
|
<Category>Events</Category>
|
||||||
|
<Category>Finance</Category>
|
||||||
|
<Category>Food & Drink</Category>
|
||||||
|
<Category>Health & Fitness</Category>
|
||||||
|
<Category>House & Home</Category>
|
||||||
|
<Category>Libraries & Demo</Category>
|
||||||
|
<Category>Lifestyle</Category>
|
||||||
|
<Category>Maps & Navigation</Category>
|
||||||
|
<Category>Medical</Category>
|
||||||
|
<Category>Music & Audio</Category>
|
||||||
|
<Category>News & Magazines</Category>
|
||||||
|
<Category>Parenting</Category>
|
||||||
|
<Category>Personalization</Category>
|
||||||
|
<Category>Photography</Category>
|
||||||
|
<Category>Productivity</Category>
|
||||||
|
<Category>Shopping</Category>
|
||||||
|
<Category>Social</Category>
|
||||||
|
<Category>Sports</Category>
|
||||||
|
<Category>Tools</Category>
|
||||||
|
<Category>Travel & Local</Category>
|
||||||
|
<Category>Video Players & Editors</Category>
|
||||||
|
<Category>Weather</Category>
|
||||||
|
<Category>GooglePlaySyncedApp</Category>
|
||||||
</AppCategories>
|
</AppCategories>
|
||||||
|
|
||||||
<RatingConfig>
|
<RatingConfig>
|
||||||
|
|||||||
@ -21,7 +21,7 @@ PRIMARY KEY (ID)
|
|||||||
CREATE TABLE IF NOT EXISTS AP_APP_RELEASE(
|
CREATE TABLE IF NOT EXISTS AP_APP_RELEASE(
|
||||||
ID INTEGER NOT NULL AUTO_INCREMENT,
|
ID INTEGER NOT NULL AUTO_INCREMENT,
|
||||||
DESCRIPTION CLOB NOT NULL,
|
DESCRIPTION CLOB NOT NULL,
|
||||||
VERSION VARCHAR(20) NOT NULL,
|
VERSION VARCHAR(70) NOT NULL,
|
||||||
TENANT_ID INTEGER NOT NULL,
|
TENANT_ID INTEGER NOT NULL,
|
||||||
UUID VARCHAR(200) NOT NULL,
|
UUID VARCHAR(200) NOT NULL,
|
||||||
RELEASE_TYPE VARCHAR(45) NOT NULL,
|
RELEASE_TYPE VARCHAR(45) NOT NULL,
|
||||||
|
|||||||
@ -26,7 +26,7 @@ CREATE TABLE IF NOT EXISTS `APP_MANAGER`.`AP_APP_RELEASE`
|
|||||||
(
|
(
|
||||||
`ID` INT(11) NOT NULL,
|
`ID` INT(11) NOT NULL,
|
||||||
`DESCRIPTION` TEXT NOT NULL,
|
`DESCRIPTION` TEXT NOT NULL,
|
||||||
`VERSION` VARCHAR(20) NOT NULL,
|
`VERSION` VARCHAR(70) NOT NULL,
|
||||||
`TENANT_ID` INT(11) NOT NULL,
|
`TENANT_ID` INT(11) NOT NULL,
|
||||||
`UUID` VARCHAR(200) NOT NULL,
|
`UUID` VARCHAR(200) NOT NULL,
|
||||||
`RELEASE_TYPE` VARCHAR(45) NOT NULL,
|
`RELEASE_TYPE` VARCHAR(45) NOT NULL,
|
||||||
|
|||||||
@ -298,7 +298,7 @@ CREATE INDEX FK_APP_PROPERTY_APP ON APPM_APPLICATION_PROPERTY(APPLICATION_ID ASC
|
|||||||
-- -----------------------------------------------------
|
-- -----------------------------------------------------
|
||||||
CREATE TABLE APPM_APPLICATION_RELEASE (
|
CREATE TABLE APPM_APPLICATION_RELEASE (
|
||||||
ID INT UNIQUE ,
|
ID INT UNIQUE ,
|
||||||
VERSION_NAME VARCHAR(100) NOT NULL,
|
VERSION_NAME VARCHAR(70) NOT NULL,
|
||||||
RELEASE_RESOURCE VARCHAR(2048) NULL,
|
RELEASE_RESOURCE VARCHAR(2048) NULL,
|
||||||
RELEASE_CHANNEL VARCHAR(50) DEFAULT 'ALPHA',
|
RELEASE_CHANNEL VARCHAR(50) DEFAULT 'ALPHA',
|
||||||
RELEASE_DETAILS VARCHAR(2048) NULL,
|
RELEASE_DETAILS VARCHAR(2048) NULL,
|
||||||
|
|||||||
@ -221,7 +221,7 @@ CREATE SEQUENCE APPM_APPLICATION_RELEASE_PK_SEQ;
|
|||||||
-- -----------------------------------------------------
|
-- -----------------------------------------------------
|
||||||
CREATE TABLE IF NOT EXISTS APPM_APPLICATION_RELEASE (
|
CREATE TABLE IF NOT EXISTS APPM_APPLICATION_RELEASE (
|
||||||
ID INT DEFAULT NEXTVAL('APPM_APPLICATION_RELEASE_PK_SEQ') UNIQUE,
|
ID INT DEFAULT NEXTVAL('APPM_APPLICATION_RELEASE_PK_SEQ') UNIQUE,
|
||||||
VERSION_NAME VARCHAR(100) NOT NULL,
|
VERSION_NAME VARCHAR(70) NOT NULL,
|
||||||
RELEASE_RESOURCE TEXT NULL,
|
RELEASE_RESOURCE TEXT NULL,
|
||||||
RELEASE_CHANNEL VARCHAR(50) DEFAULT 'ALPHA',
|
RELEASE_CHANNEL VARCHAR(50) DEFAULT 'ALPHA',
|
||||||
RELEASE_DETAILS TEXT NULL,
|
RELEASE_DETAILS TEXT NULL,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user