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:
commit
11610d3635
@ -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)
|
||||
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() {
|
||||
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)
|
||||
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 void setId(int id) { this.id = id; }
|
||||
|
||||
@ -273,4 +273,6 @@ public interface ApplicationManager {
|
||||
*/
|
||||
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.wso2.carbon.base,
|
||||
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
|
||||
</Import-Package>
|
||||
<Embed-Dependency>apk-parser;scope=compile|runtime;inline=false</Embed-Dependency>
|
||||
@ -240,6 +244,11 @@
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.wso2.carbon.devicemgt</groupId>
|
||||
<artifactId>org.wso2.carbon.apimgt.application.extension</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
</project>
|
||||
|
||||
|
||||
@ -117,4 +117,7 @@ public interface ApplicationReleaseDAO {
|
||||
boolean hasExistInstallableAppRelease(String releaseUuid, String installableStateName, int tenantId)
|
||||
throws ApplicationManagementDAOException;
|
||||
|
||||
List<ApplicationReleaseDTO> getReleaseByPackages(List<String> packages, int tenantId)
|
||||
throws ApplicationManagementDAOException;
|
||||
|
||||
}
|
||||
|
||||
@ -36,6 +36,7 @@ import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.StringJoiner;
|
||||
|
||||
/**
|
||||
* GenericApplicationReleaseDAOImpl holds the implementation of ApplicationRelease related DAO operations.
|
||||
@ -278,9 +279,15 @@ public class GenericApplicationReleaseDAOImpl extends AbstractDAOImpl implements
|
||||
+ "APP_HASH_VALUE = ?, "
|
||||
+ "SHARED_WITH_ALL_TENANTS = ?, "
|
||||
+ "APP_META_INFO = ?, "
|
||||
+ "SUPPORTED_OS_VERSIONS = ?, "
|
||||
+ "CURRENT_STATE = ? "
|
||||
+ "WHERE ID = ? AND TENANT_ID = ? ";
|
||||
+ "SUPPORTED_OS_VERSIONS = ?";
|
||||
|
||||
if (applicationReleaseDTO.getCurrentState() != null) {
|
||||
sql += ", CURRENT_STATE = ? ";
|
||||
}
|
||||
|
||||
sql += " WHERE ID = ? AND TENANT_ID = ? ";
|
||||
|
||||
int x = 17;
|
||||
try {
|
||||
Connection connection = this.getDBConnection();
|
||||
try (PreparedStatement statement = connection.prepareStatement(sql)) {
|
||||
@ -300,9 +307,12 @@ public class GenericApplicationReleaseDAOImpl extends AbstractDAOImpl implements
|
||||
statement.setBoolean(14, applicationReleaseDTO.getIsSharedWithAllTenants());
|
||||
statement.setString(15, applicationReleaseDTO.getMetaData());
|
||||
statement.setString(16, applicationReleaseDTO.getSupportedOsVersions());
|
||||
statement.setString(17, applicationReleaseDTO.getCurrentState().toUpperCase());
|
||||
statement.setInt(18, applicationReleaseDTO.getId());
|
||||
statement.setInt(19, tenantId);
|
||||
|
||||
if (applicationReleaseDTO.getCurrentState() != null) {
|
||||
statement.setString(x++, applicationReleaseDTO.getCurrentState().toUpperCase());
|
||||
}
|
||||
statement.setInt(x++, applicationReleaseDTO.getId());
|
||||
statement.setInt(x++, tenantId);
|
||||
if (statement.executeUpdate() == 0) {
|
||||
return null;
|
||||
}
|
||||
@ -550,4 +560,65 @@ public class GenericApplicationReleaseDAOImpl extends AbstractDAOImpl implements
|
||||
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
|
||||
public Application createPublicApp(PublicAppWrapper publicAppWrapper, ApplicationArtifact applicationArtifact)
|
||||
throws ApplicationManagementException {
|
||||
int tenantId = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantId(true);
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Public app creating request is received. App name: " + publicAppWrapper.getName()
|
||||
+ " Device Type: " + publicAppWrapper.getDeviceType());
|
||||
}
|
||||
|
||||
String publicAppStorePath = "";
|
||||
if (DeviceTypes.ANDROID.toString().equals(publicAppWrapper.getDeviceType())) {
|
||||
if (DeviceTypes.ANDROID.toString().toLowerCase().equals(publicAppWrapper.getDeviceType())) {
|
||||
publicAppStorePath = Constants.GOOGLE_PLAY_STORE_URL;
|
||||
} else if (DeviceTypes.IOS.toString().equals(publicAppWrapper.getDeviceType())) {
|
||||
publicAppStorePath = Constants.APPLE_STORE_URL;
|
||||
@ -194,11 +196,50 @@ public class ApplicationManagerImpl implements ApplicationManager {
|
||||
applicationReleaseDTO.setInstallerName(appInstallerUrl);
|
||||
applicationReleaseDTO.setUuid(UUID.randomUUID().toString());
|
||||
applicationReleaseDTO.setAppHashValue(DigestUtils.md5Hex(appInstallerUrl));
|
||||
|
||||
ConnectionManagerUtil.openDBConnection();
|
||||
List<ApplicationReleaseDTO> exitingRelease;
|
||||
try {
|
||||
exitingRelease = applicationReleaseDAO.getReleaseByPackages(Arrays.asList(applicationReleaseDTO.getPackageName())
|
||||
, tenantId);
|
||||
} catch (ApplicationManagementDAOException e) {
|
||||
String msg = "Error Occured when fetching release: " + publicAppWrapper.getName();
|
||||
log.error(msg);
|
||||
throw new ApplicationManagementException(msg, e);
|
||||
} finally {
|
||||
ConnectionManagerUtil.closeDBConnection();
|
||||
}
|
||||
|
||||
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(addImageArtifacts(applicationReleaseDTO, applicationArtifact));
|
||||
applicationDTO.getApplicationReleaseDTOs().add(applicationReleaseDTO);
|
||||
} catch (ResourceManagementException e) {
|
||||
String msg = "Error Occured when uploading artifacts of the public app: " + publicAppWrapper.getName();
|
||||
log.error(msg, e);
|
||||
@ -208,6 +249,8 @@ public class ApplicationManagerImpl implements ApplicationManager {
|
||||
return addAppDataIntoDB(applicationDTO);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Application createCustomApp(CustomAppWrapper customAppWrapper, ApplicationArtifact applicationArtifact)
|
||||
throws ApplicationManagementException {
|
||||
@ -3115,4 +3158,24 @@ public class ApplicationManagerImpl implements ApplicationManager {
|
||||
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;
|
||||
|
||||
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.logging.Log;
|
||||
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.device.application.mgt.common.ApplicationInstallResponse;
|
||||
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.SubscribingDeviceIdHolder;
|
||||
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.ScheduledSubscriptionDTO;
|
||||
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.Constants;
|
||||
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.DeviceIdentifier;
|
||||
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.Operation;
|
||||
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.operation.mgt.ProfileOperation;
|
||||
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.util.MDMAndroidOperationUtil;
|
||||
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.Arrays;
|
||||
import java.util.HashMap;
|
||||
@ -376,10 +393,21 @@ public class SubscriptionManagerImpl implements SubscriptionManager {
|
||||
entry.getKey(), action);
|
||||
activityList.add(activity);
|
||||
}
|
||||
} else {
|
||||
if (applicationDTO.getType().equals(ApplicationType.PUBLIC.toString())) {
|
||||
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.setActivities(activityList);
|
||||
applicationInstallResponse.setIgnoredDeviceIdentifiers(ignoredDeviceIdentifiers);
|
||||
@ -414,6 +442,22 @@ public class SubscriptionManagerImpl implements SubscriptionManager {
|
||||
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 {
|
||||
ApplicationDTO applicationDTO;
|
||||
int tenantId = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantId(true);
|
||||
@ -681,4 +725,55 @@ public class SubscriptionManagerImpl implements SubscriptionManager {
|
||||
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));
|
||||
}
|
||||
application.setApplicationReleases(applicationReleases);
|
||||
application.setPackageName(applicationDTO.getPackageName());
|
||||
return application;
|
||||
}
|
||||
|
||||
|
||||
@ -115,4 +115,30 @@ public class Constants {
|
||||
public static final int REVIEW_PARENT_ID = -1;
|
||||
|
||||
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.setAppRating(rs.getDouble("APP_RATING"));
|
||||
application.setDeviceTypeId(rs.getInt("APP_DEVICE_TYPE_ID"));
|
||||
application.setPackageName(rs.getString("PACKAGE_NAME"));
|
||||
application.getApplicationReleaseDTOs().add(constructAppReleaseDTO(rs));
|
||||
} else {
|
||||
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",
|
||||
"dependencies": {
|
||||
"@ant-design/dark-theme": "^0.2.2",
|
||||
"@babel/polyfill": "^7.6.0",
|
||||
"acorn": "^6.2.0",
|
||||
"antd": "^3.20.1",
|
||||
"antd": "^3.22.2",
|
||||
"axios": "^0.19.0",
|
||||
"d3": "^5.9.7",
|
||||
"dagre": "^0.8.4",
|
||||
"fetch": "^1.1.0",
|
||||
"gapi": "0.0.3",
|
||||
"gapi-client": "0.0.3",
|
||||
"keymirror": "^0.1.1",
|
||||
"rc-tween-one": "^2.4.1",
|
||||
"react-d3-graph": "^2.1.0",
|
||||
@ -54,7 +57,7 @@
|
||||
"html-webpack-plugin": "^3.2.0",
|
||||
"img-loader": "^3.0.1",
|
||||
"json-loader": "^0.5.7",
|
||||
"less": "^3.9.0",
|
||||
"less": "^3.10.3",
|
||||
"less-loader": "^4.1.0",
|
||||
"mini-css-extract-plugin": "^0.5.0",
|
||||
"mocha": "^5.2.0",
|
||||
@ -73,8 +76,8 @@
|
||||
"sass-loader": "^6.0.7",
|
||||
"style-loader": "^0.18.2",
|
||||
"url-loader": "^1.1.2",
|
||||
"webpack": "^4.35.2",
|
||||
"webpack-cli": "^3.3.5",
|
||||
"webpack": "^4.39.3",
|
||||
"webpack-cli": "^3.3.7",
|
||||
"webpack-dev-server": "^3.7.2"
|
||||
},
|
||||
"scripts": {
|
||||
|
||||
@ -1,14 +1,9 @@
|
||||
{
|
||||
"theme": {
|
||||
"type": "default",
|
||||
"value": "lightBaseTheme",
|
||||
"logo": "https://entgra.io/assets/images/svg/logo.svg",
|
||||
"primaryColor": "rgb(24, 144, 255)"
|
||||
"primaryColor": "#badc58"
|
||||
},
|
||||
"serverConfig": {
|
||||
"protocol": "https",
|
||||
"hostname": "localhost",
|
||||
"httpsPort": "9443",
|
||||
"invoker": {
|
||||
"uri": "/publisher-ui-request-handler/invoke",
|
||||
"publisher": "/application-mgt-publisher/v1.0",
|
||||
|
||||
@ -67,11 +67,10 @@ class App extends React.Component {
|
||||
axios.get(
|
||||
window.location.origin + "/publisher/public/conf/config.json",
|
||||
).then(res => {
|
||||
console.log(res);
|
||||
this.setState({
|
||||
loading: false,
|
||||
config: res.data
|
||||
})
|
||||
const config = res.data;
|
||||
|
||||
this.getAndroidEnterpriseToken(config);
|
||||
|
||||
}).catch((error) => {
|
||||
this.setState({
|
||||
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() {
|
||||
const {loading, error} = this.state;
|
||||
|
||||
|
||||
@ -40,6 +40,8 @@ import ReactHtmlParser from 'react-html-parser';
|
||||
import "./AppDetailsDrawer.css";
|
||||
import pSBC from "shade-blend-color";
|
||||
import {withConfigContext} from "../../../../context/ConfigContext";
|
||||
import ManagedConfigurationsIframe
|
||||
from "../../../manage/android-enterprise/ManagedConfigurationsIframe/ManagedConfigurationsIframe";
|
||||
|
||||
const {Meta} = Card;
|
||||
const {Text, Title} = Typography;
|
||||
@ -474,6 +476,31 @@ class AppDetailsDrawer extends React.Component {
|
||||
|
||||
<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>
|
||||
{/*display add new release only if app type is enterprise*/}
|
||||
|
||||
|
||||
@ -224,7 +224,7 @@ class EditReleaseModal extends React.Component {
|
||||
isSharedWithAllTenants,
|
||||
metaData: JSON.stringify(this.state.metaData),
|
||||
releaseType: releaseType,
|
||||
supportedOsVersions: "4.0-10.0"
|
||||
supportedOsVersions: "4-30"
|
||||
};
|
||||
|
||||
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(
|
||||
url,
|
||||
data,
|
||||
{
|
||||
headers: {
|
||||
'X-Platform': config.serverConfig.platform
|
||||
},
|
||||
}
|
||||
data
|
||||
).then(res => {
|
||||
if (res.status === 201) {
|
||||
this.setState({
|
||||
|
||||
@ -95,7 +95,7 @@ class NewAppUploadForm extends React.Component {
|
||||
};
|
||||
|
||||
if (formConfig.installationType !== "WEB_CLIP" && formConfig.installationType !== "CUSTOM") {
|
||||
release.supportedOsVersions = "4.0-10.0";
|
||||
release.supportedOsVersions = "4-30";
|
||||
}
|
||||
|
||||
if (specificElements.hasOwnProperty("version")) {
|
||||
|
||||
@ -88,7 +88,7 @@ class AddNewReleaseFormComponent extends React.Component {
|
||||
isSharedWithAllTenants,
|
||||
metaData: "string",
|
||||
releaseType: releaseType,
|
||||
supportedOsVersions: "4.0-10.0"
|
||||
supportedOsVersions: "4-30"
|
||||
};
|
||||
|
||||
data.append('binaryFile', binaryFile[0].originFileObj);
|
||||
@ -108,12 +108,7 @@ class AddNewReleaseFormComponent extends React.Component {
|
||||
|
||||
axios.post(
|
||||
url,
|
||||
data,
|
||||
{
|
||||
headers: {
|
||||
'X-Platform': config.serverConfig.platform
|
||||
},
|
||||
}
|
||||
data
|
||||
).then(res => {
|
||||
if (res.status === 201) {
|
||||
this.setState({
|
||||
|
||||
@ -21,6 +21,7 @@
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=0"/>
|
||||
<title>Entgra App Publisher</title>
|
||||
<script src="https://apis.google.com/js/client.js"></script>
|
||||
</head>
|
||||
<div id="root"></div>
|
||||
</html>
|
||||
@ -31,6 +31,8 @@ import AddNewPublicApp from "./pages/dashboard/add-new-app/AddNewPublicApp";
|
||||
import AddNewWebClip from "./pages/dashboard/add-new-app/AddNewWebClip";
|
||||
import AddNewRelease from "./pages/dashboard/add-new-release/AddNewRelease";
|
||||
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 = [
|
||||
@ -83,6 +85,16 @@ const routes = [
|
||||
path: '/publisher/manage',
|
||||
component: Mange,
|
||||
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"/>
|
||||
Add New App
|
||||
</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 key="setting:2">
|
||||
<Link to="/publisher/add-new-app/enterprise">Enterprise APP</Link>
|
||||
<Menu.Item key="add-new-enterprise-app">
|
||||
<Link to="/publisher/add-new-app/enterprise">
|
||||
Enterprise APP
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
<Menu.Item key="setting:3">
|
||||
<Link to="/publisher/add-new-app/web-clip">Web Clip</Link>
|
||||
</Menu.Item>
|
||||
<Menu.Item key="setting:4">
|
||||
<Link to="/publisher/add-new-app/custom-app">Custom App</Link>
|
||||
<Menu.Item key="add-new-web-clip">
|
||||
<Link to="/publisher/add-new-app/web-clip">
|
||||
Web Clip
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
</SubMenu>
|
||||
|
||||
|
||||
@ -24,22 +24,6 @@ import {Link} from "react-router-dom";
|
||||
|
||||
const {Paragraph} = Typography;
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: 'index',
|
||||
breadcrumbName: 'Publisher',
|
||||
},
|
||||
{
|
||||
path: 'first',
|
||||
breadcrumbName: 'Dashboard',
|
||||
},
|
||||
{
|
||||
path: 'second',
|
||||
breadcrumbName: 'Manage',
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
class Manage extends React.Component {
|
||||
routes;
|
||||
|
||||
@ -57,10 +41,13 @@ class Manage extends React.Component {
|
||||
<Breadcrumb.Item>
|
||||
<Link to="/publisher/apps"><Icon type="home"/> Home</Link>
|
||||
</Breadcrumb.Item>
|
||||
<Breadcrumb.Item>Manage</Breadcrumb.Item>
|
||||
<Breadcrumb.Item>
|
||||
Manage
|
||||
</Breadcrumb.Item>
|
||||
<Breadcrumb.Item>General</Breadcrumb.Item>
|
||||
</Breadcrumb>
|
||||
<div className="wrap">
|
||||
<h3>Manage</h3>
|
||||
<h3>Manage General Settings</h3>
|
||||
<Paragraph>Maintain and manage categories and tags here..</Paragraph>
|
||||
</div>
|
||||
</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: "css-loader",
|
||||
loader: "css-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];
|
||||
appObjectToView = {};
|
||||
appObjectToView["appName"] = appObjectFromRestEndpoint["name"];
|
||||
appObjectToView["packageName"] = appObjectFromRestEndpoint["packageName"];
|
||||
appObjectToView["appId"] = appObjectFromRestEndpoint["id"];
|
||||
if ("WEB_CLIP" === appObjectFromRestEndpoint["type"]) {
|
||||
appObjectToView["type"] = "Web Clip"
|
||||
|
||||
@ -3,6 +3,10 @@
|
||||
"logLevel": "info",
|
||||
"initScripts": ["/app/modules/init.js"],
|
||||
"urlMappings": [
|
||||
{
|
||||
"url": "/api/enterprise/*",
|
||||
"path": "/api/enterprise.jag"
|
||||
},
|
||||
{
|
||||
"url": "/api/devices/*",
|
||||
"path": "/api/device-api.jag"
|
||||
|
||||
@ -271,7 +271,7 @@ public class InvokerHandler extends HttpServlet {
|
||||
*/
|
||||
private boolean validateRequest(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws IOException {
|
||||
serverUrl = req.getScheme() + "://" + req.getServerName() + ":" + req.getServerPort();
|
||||
serverUrl = req.getScheme() + "://" + req.getServerName() + ":" + System.getProperty("iot.gateway.https.port");
|
||||
apiEndpoint = req.getPathInfo();
|
||||
HttpSession session = req.getSession(false);
|
||||
|
||||
|
||||
@ -225,8 +225,9 @@ public class LoginHandler extends HttpServlet {
|
||||
password = req.getParameter("password");
|
||||
platform = req.getParameter(HandlerConstants.PLATFORM);
|
||||
serverUrl = req.getScheme() + HandlerConstants.SCHEME_SEPARATOR + req.getServerName() + HandlerConstants.COLON
|
||||
+ req.getServerPort();
|
||||
uiConfigUrl = serverUrl + HandlerConstants.UI_CONFIG_ENDPOINT;
|
||||
+ System.getProperty("iot.gateway.https.port");
|
||||
uiConfigUrl = req.getScheme() + HandlerConstants.SCHEME_SEPARATOR + req.getServerName() + HandlerConstants.COLON
|
||||
+ System.getProperty("iot.gateway.carbon.https.port") + HandlerConstants.UI_CONFIG_ENDPOINT;
|
||||
|
||||
try {
|
||||
if (platform == null) {
|
||||
|
||||
@ -22,7 +22,7 @@ public class HandlerConstants {
|
||||
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 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 BASIC = "Basic ";
|
||||
public static final String BEARER = "Bearer ";
|
||||
|
||||
@ -156,6 +156,12 @@
|
||||
<Scope>perm:admin:app:review:view</Scope>
|
||||
<Scope>perm:admin:app:publisher: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>
|
||||
<SSOConfiguration>
|
||||
<Issuer>app-mgt</Issuer>
|
||||
@ -176,6 +182,39 @@
|
||||
<AppCategories>
|
||||
<Category>EMM</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>
|
||||
|
||||
<RatingConfig>
|
||||
|
||||
@ -21,7 +21,7 @@ PRIMARY KEY (ID)
|
||||
CREATE TABLE IF NOT EXISTS AP_APP_RELEASE(
|
||||
ID INTEGER NOT NULL AUTO_INCREMENT,
|
||||
DESCRIPTION CLOB NOT NULL,
|
||||
VERSION VARCHAR(20) NOT NULL,
|
||||
VERSION VARCHAR(70) NOT NULL,
|
||||
TENANT_ID INTEGER NOT NULL,
|
||||
UUID VARCHAR(200) 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,
|
||||
`DESCRIPTION` TEXT NOT NULL,
|
||||
`VERSION` VARCHAR(20) NOT NULL,
|
||||
`VERSION` VARCHAR(70) NOT NULL,
|
||||
`TENANT_ID` INT(11) NOT NULL,
|
||||
`UUID` VARCHAR(200) 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 (
|
||||
ID INT UNIQUE ,
|
||||
VERSION_NAME VARCHAR(100) NOT NULL,
|
||||
VERSION_NAME VARCHAR(70) NOT NULL,
|
||||
RELEASE_RESOURCE VARCHAR(2048) NULL,
|
||||
RELEASE_CHANNEL VARCHAR(50) DEFAULT 'ALPHA',
|
||||
RELEASE_DETAILS VARCHAR(2048) NULL,
|
||||
|
||||
@ -221,7 +221,7 @@ CREATE SEQUENCE APPM_APPLICATION_RELEASE_PK_SEQ;
|
||||
-- -----------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS APPM_APPLICATION_RELEASE (
|
||||
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_CHANNEL VARCHAR(50) DEFAULT 'ALPHA',
|
||||
RELEASE_DETAILS TEXT NULL,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user