forked from community/device-mgt-core
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;
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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,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);
|
@ -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,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
@ -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));
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
%>
|
Loading…
Reference in new issue