mirror of
https://repository.entgra.net/community/device-mgt-core.git
synced 2025-10-06 02:01:45 +00:00
Fix merge conflicts
This commit is contained in:
commit
a79b600922
@ -67,7 +67,7 @@ import org.wso2.carbon.device.application.mgt.core.exception.UnexpectedServerErr
|
|||||||
import org.wso2.carbon.device.application.mgt.core.exception.ValidationException;
|
import org.wso2.carbon.device.application.mgt.core.exception.ValidationException;
|
||||||
import org.wso2.carbon.device.application.mgt.core.exception.VisibilityManagementDAOException;
|
import org.wso2.carbon.device.application.mgt.core.exception.VisibilityManagementDAOException;
|
||||||
import org.wso2.carbon.device.application.mgt.core.internal.DataHolder;
|
import org.wso2.carbon.device.application.mgt.core.internal.DataHolder;
|
||||||
import org.wso2.carbon.device.application.mgt.core.lifecycle.LifecycleStateManger;
|
import org.wso2.carbon.device.application.mgt.core.lifecycle.LifecycleStateManager;
|
||||||
import org.wso2.carbon.device.application.mgt.core.util.ConnectionManagerUtil;
|
import org.wso2.carbon.device.application.mgt.core.util.ConnectionManagerUtil;
|
||||||
import org.wso2.carbon.device.application.mgt.core.util.Constants;
|
import org.wso2.carbon.device.application.mgt.core.util.Constants;
|
||||||
import org.wso2.carbon.device.mgt.common.DeviceManagementException;
|
import org.wso2.carbon.device.mgt.common.DeviceManagementException;
|
||||||
@ -104,11 +104,11 @@ public class ApplicationManagerImpl implements ApplicationManager {
|
|||||||
private ApplicationDAO applicationDAO;
|
private ApplicationDAO applicationDAO;
|
||||||
private ApplicationReleaseDAO applicationReleaseDAO;
|
private ApplicationReleaseDAO applicationReleaseDAO;
|
||||||
private LifecycleStateDAO lifecycleStateDAO;
|
private LifecycleStateDAO lifecycleStateDAO;
|
||||||
private LifecycleStateManger lifecycleStateManger;
|
private LifecycleStateManager lifecycleStateManager;
|
||||||
|
|
||||||
public ApplicationManagerImpl() {
|
public ApplicationManagerImpl() {
|
||||||
initDataAccessObjects();
|
initDataAccessObjects();
|
||||||
lifecycleStateManger = DataHolder.getInstance().getLifecycleStateManager();
|
lifecycleStateManager = DataHolder.getInstance().getLifecycleStateManager();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initDataAccessObjects() {
|
private void initDataAccessObjects() {
|
||||||
@ -122,8 +122,8 @@ public class ApplicationManagerImpl implements ApplicationManager {
|
|||||||
* The responsbility of this method is the creating an application.
|
* The responsbility of this method is the creating an application.
|
||||||
* @param applicationWrapper ApplicationDTO that need to be created.
|
* @param applicationWrapper ApplicationDTO that need to be created.
|
||||||
* @return {@link ApplicationDTO}
|
* @return {@link ApplicationDTO}
|
||||||
* @throws RequestValidatingException if application creating request is invalid, returns {@link RequestValidatingException}
|
* @throws RequestValidatingException if application creating request is invalid,
|
||||||
* @throws ApplicationManagementException Catch all other throwing exceptions and returns {@link ApplicationManagementException}
|
* @throws ApplicationManagementException Catch all other throwing exceptions and throw {@link ApplicationManagementException}
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Application createApplication(ApplicationWrapper applicationWrapper,
|
public Application createApplication(ApplicationWrapper applicationWrapper,
|
||||||
@ -504,7 +504,8 @@ public class ApplicationManagerImpl implements ApplicationManager {
|
|||||||
String packageName = this.applicationReleaseDAO.getPackageName(applicationId, tenantId);
|
String packageName = this.applicationReleaseDAO.getPackageName(applicationId, tenantId);
|
||||||
if (packageName != null && !packageName.equals(applicationRelease.getPackageName())) {
|
if (packageName != null && !packageName.equals(applicationRelease.getPackageName())) {
|
||||||
throw new BadRequestException(
|
throw new BadRequestException(
|
||||||
"Package name in the payload is different from the existing package name of other application releases.");
|
"Package name in the payload is different from the existing package name of other application" +
|
||||||
|
" releases.");
|
||||||
}
|
}
|
||||||
applicationRelease = this.applicationReleaseDAO
|
applicationRelease = this.applicationReleaseDAO
|
||||||
.createRelease(applicationRelease, existingApplication.getId(), tenantId);
|
.createRelease(applicationRelease, existingApplication.getId(), tenantId);
|
||||||
@ -810,8 +811,8 @@ public class ApplicationManagerImpl implements ApplicationManager {
|
|||||||
.getLatestLifeCycleState(applicationId, applicationRelease.getUuid());
|
.getLatestLifeCycleState(applicationId, applicationRelease.getUuid());
|
||||||
LifecycleStateDTO newAppLifecycleState = getLifecycleStateInstance(AppLifecycleState.REMOVED.toString(),
|
LifecycleStateDTO newAppLifecycleState = getLifecycleStateInstance(AppLifecycleState.REMOVED.toString(),
|
||||||
appLifecycleState.getCurrentState());
|
appLifecycleState.getCurrentState());
|
||||||
if (lifecycleStateManger.isValidStateChange(newAppLifecycleState.getPreviousState(),
|
if (lifecycleStateManager.isValidStateChange(newAppLifecycleState.getPreviousState(),
|
||||||
newAppLifecycleState.getCurrentState())) {
|
newAppLifecycleState.getCurrentState(), userName, tenantId)) {
|
||||||
this.lifecycleStateDAO
|
this.lifecycleStateDAO
|
||||||
.addLifecycleState(newAppLifecycleState, applicationId, applicationRelease.getUuid(),
|
.addLifecycleState(newAppLifecycleState, applicationId, applicationRelease.getUuid(),
|
||||||
tenantId);
|
tenantId);
|
||||||
@ -821,7 +822,7 @@ public class ApplicationManagerImpl implements ApplicationManager {
|
|||||||
AppLifecycleState.REMOVED.toString());
|
AppLifecycleState.REMOVED.toString());
|
||||||
for (String nextState : lifecycleFlow) {
|
for (String nextState : lifecycleFlow) {
|
||||||
LifecycleStateDTO lifecycleState = getLifecycleStateInstance(nextState, currentState);
|
LifecycleStateDTO lifecycleState = getLifecycleStateInstance(nextState, currentState);
|
||||||
if (lifecycleStateManger.isValidStateChange(currentState, nextState)) {
|
if (lifecycleStateManager.isValidStateChange(currentState, nextState, userName, tenantId)) {
|
||||||
this.lifecycleStateDAO
|
this.lifecycleStateDAO
|
||||||
.addLifecycleState(lifecycleState, applicationId, applicationRelease.getUuid(),
|
.addLifecycleState(lifecycleState, applicationId, applicationRelease.getUuid(),
|
||||||
tenantId);
|
tenantId);
|
||||||
@ -871,7 +872,7 @@ public class ApplicationManagerImpl implements ApplicationManager {
|
|||||||
if (currentNode.equals(finish)) {
|
if (currentNode.equals(finish)) {
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
Set<String> nextStates = lifecycleStateManger.getNextLifecycleStates(currentNode);
|
Set<String> nextStates = lifecycleStateManager.getNextLifecycleStates(currentNode);
|
||||||
if (nextStates.contains(finish)) {
|
if (nextStates.contains(finish)) {
|
||||||
queue = new LinkedList<>();
|
queue = new LinkedList<>();
|
||||||
queue.add(finish);
|
queue.add(finish);
|
||||||
@ -940,8 +941,8 @@ public class ApplicationManagerImpl implements ApplicationManager {
|
|||||||
.equals(currentState) || AppLifecycleState.UNPUBLISHED.toString().equals(currentState)) {
|
.equals(currentState) || AppLifecycleState.UNPUBLISHED.toString().equals(currentState)) {
|
||||||
LifecycleStateDTO newAppLifecycleState = getLifecycleStateInstance(AppLifecycleState.REMOVED.toString(),
|
LifecycleStateDTO newAppLifecycleState = getLifecycleStateInstance(AppLifecycleState.REMOVED.toString(),
|
||||||
appLifecycleState.getCurrentState());
|
appLifecycleState.getCurrentState());
|
||||||
if (lifecycleStateManger.isValidStateChange(newAppLifecycleState.getPreviousState(),
|
if (lifecycleStateManager.isValidStateChange(newAppLifecycleState.getPreviousState(),
|
||||||
newAppLifecycleState.getCurrentState())) {
|
newAppLifecycleState.getCurrentState(), userName, tenantId)) {
|
||||||
this.lifecycleStateDAO
|
this.lifecycleStateDAO
|
||||||
.addLifecycleState(newAppLifecycleState, applicationId, applicationRelease.getUuid(),
|
.addLifecycleState(newAppLifecycleState, applicationId, applicationRelease.getUuid(),
|
||||||
tenantId);
|
tenantId);
|
||||||
@ -951,7 +952,7 @@ public class ApplicationManagerImpl implements ApplicationManager {
|
|||||||
AppLifecycleState.REMOVED.toString());
|
AppLifecycleState.REMOVED.toString());
|
||||||
for (String nextState : lifecycleFlow) {
|
for (String nextState : lifecycleFlow) {
|
||||||
LifecycleStateDTO lifecycleState = getLifecycleStateInstance(nextState, currentState);
|
LifecycleStateDTO lifecycleState = getLifecycleStateInstance(nextState, currentState);
|
||||||
if (lifecycleStateManger.isValidStateChange(currentState, nextState)) {
|
if (lifecycleStateManager.isValidStateChange(currentState, nextState, userName, tenantId)) {
|
||||||
this.lifecycleStateDAO
|
this.lifecycleStateDAO
|
||||||
.addLifecycleState(lifecycleState, applicationId, applicationRelease.getUuid(),
|
.addLifecycleState(lifecycleState, applicationId, applicationRelease.getUuid(),
|
||||||
tenantId);
|
tenantId);
|
||||||
@ -1187,7 +1188,8 @@ public class ApplicationManagerImpl implements ApplicationManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* To validate a app release creating request and app updating request to make sure all the pre-conditions satisfied.
|
* To validate a app release creating request and app updating request to make sure all the pre-conditions
|
||||||
|
* satisfied.
|
||||||
*
|
*
|
||||||
* @param applicationRelease ApplicationReleaseDTO that need to be created.
|
* @param applicationRelease ApplicationReleaseDTO that need to be created.
|
||||||
* @throws ApplicationManagementException ApplicationDTO Management Exception.
|
* @throws ApplicationManagementException ApplicationDTO Management Exception.
|
||||||
@ -1210,8 +1212,7 @@ public class ApplicationManagerImpl implements ApplicationManager {
|
|||||||
if (lifecycleState == null) {
|
if (lifecycleState == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
lifecycleState.setNextStates(
|
lifecycleState.setNextStates(new ArrayList<>(lifecycleStateManager.getNextLifecycleStates(lifecycleState.getCurrentState())));
|
||||||
new ArrayList<>(lifecycleStateManger.getNextLifecycleStates(lifecycleState.getCurrentState())));
|
|
||||||
|
|
||||||
} catch (LifeCycleManagementDAOException e) {
|
} catch (LifeCycleManagementDAOException e) {
|
||||||
throw new ApplicationManagementException("Failed to get lifecycle state from database", e);
|
throw new ApplicationManagementException("Failed to get lifecycle state from database", e);
|
||||||
@ -1241,12 +1242,12 @@ public class ApplicationManagerImpl implements ApplicationManager {
|
|||||||
+ " and application release UUID: " + releaseUuid);
|
+ " and application release UUID: " + releaseUuid);
|
||||||
}
|
}
|
||||||
state.setPreviousState(currentState.getCurrentState());
|
state.setPreviousState(currentState.getCurrentState());
|
||||||
|
|
||||||
String userName = PrivilegedCarbonContext.getThreadLocalCarbonContext().getUsername();
|
String userName = PrivilegedCarbonContext.getThreadLocalCarbonContext().getUsername();
|
||||||
state.setUpdatedBy(userName);
|
state.setUpdatedBy(userName);
|
||||||
|
|
||||||
if (state.getCurrentState() != null && state.getPreviousState() != null) {
|
if (state.getCurrentState() != null && state.getPreviousState() != null) {
|
||||||
if (lifecycleStateManger.isValidStateChange(state.getPreviousState(), state.getCurrentState())) {
|
if (lifecycleStateManager.isValidStateChange(state.getPreviousState(), state.getCurrentState(),
|
||||||
|
userName, tenantId)) {
|
||||||
//todo if current state of the adding lifecycle state is PUBLISHED, need to check whether is there
|
//todo if current state of the adding lifecycle state is PUBLISHED, need to check whether is there
|
||||||
//todo any other application release in PUBLISHED state for the application( i.e for the appid)
|
//todo any other application release in PUBLISHED state for the application( i.e for the appid)
|
||||||
this.lifecycleStateDAO.addLifecycleState(state, applicationId, releaseUuid, tenantId);
|
this.lifecycleStateDAO.addLifecycleState(state, applicationId, releaseUuid, tenantId);
|
||||||
|
|||||||
@ -31,7 +31,7 @@ import org.wso2.carbon.device.application.mgt.core.config.ConfigurationManager;
|
|||||||
import org.wso2.carbon.device.application.mgt.common.config.UIConfiguration;
|
import org.wso2.carbon.device.application.mgt.common.config.UIConfiguration;
|
||||||
import org.wso2.carbon.device.application.mgt.core.dao.common.ApplicationManagementDAOFactory;
|
import org.wso2.carbon.device.application.mgt.core.dao.common.ApplicationManagementDAOFactory;
|
||||||
import org.wso2.carbon.device.application.mgt.core.impl.AppmDataHandlerImpl;
|
import org.wso2.carbon.device.application.mgt.core.impl.AppmDataHandlerImpl;
|
||||||
import org.wso2.carbon.device.application.mgt.core.lifecycle.LifecycleStateManger;
|
import org.wso2.carbon.device.application.mgt.core.lifecycle.LifecycleStateManager;
|
||||||
import org.wso2.carbon.device.application.mgt.core.lifecycle.config.LifecycleState;
|
import org.wso2.carbon.device.application.mgt.core.lifecycle.config.LifecycleState;
|
||||||
import org.wso2.carbon.device.application.mgt.core.util.ApplicationManagementUtil;
|
import org.wso2.carbon.device.application.mgt.core.util.ApplicationManagementUtil;
|
||||||
import org.wso2.carbon.device.mgt.core.service.DeviceManagementProviderService;
|
import org.wso2.carbon.device.mgt.core.service.DeviceManagementProviderService;
|
||||||
@ -78,10 +78,10 @@ public class ApplicationManagementServiceComponent {
|
|||||||
|
|
||||||
List<LifecycleState> lifecycleStates = ConfigurationManager.getInstance().
|
List<LifecycleState> lifecycleStates = ConfigurationManager.getInstance().
|
||||||
getConfiguration().getLifecycleStates();
|
getConfiguration().getLifecycleStates();
|
||||||
LifecycleStateManger lifecycleStateManger = ApplicationManagementUtil.getLifecycleStateMangerInstance();
|
LifecycleStateManager lifecycleStateManager = ApplicationManagementUtil.getLifecycleStateMangerInstance();
|
||||||
lifecycleStateManger.init(lifecycleStates);
|
lifecycleStateManager.init(lifecycleStates);
|
||||||
DataHolder.getInstance().setLifecycleStateManger(lifecycleStateManger);
|
DataHolder.getInstance().setLifecycleStateManger(lifecycleStateManager);
|
||||||
bundleContext.registerService(LifecycleStateManger.class.getName(), lifecycleStateManger, null);
|
bundleContext.registerService(LifecycleStateManager.class.getName(), lifecycleStateManager, null);
|
||||||
|
|
||||||
ApplicationManager applicationManager = ApplicationManagementUtil.getApplicationManagerInstance();
|
ApplicationManager applicationManager = ApplicationManagementUtil.getApplicationManagerInstance();
|
||||||
applicationManager
|
applicationManager
|
||||||
|
|||||||
@ -23,7 +23,7 @@ import org.wso2.carbon.device.application.mgt.common.services.ApplicationStorage
|
|||||||
import org.wso2.carbon.device.application.mgt.common.services.AppmDataHandler;
|
import org.wso2.carbon.device.application.mgt.common.services.AppmDataHandler;
|
||||||
import org.wso2.carbon.device.application.mgt.common.services.ReviewManager;
|
import org.wso2.carbon.device.application.mgt.common.services.ReviewManager;
|
||||||
import org.wso2.carbon.device.application.mgt.common.services.SubscriptionManager;
|
import org.wso2.carbon.device.application.mgt.common.services.SubscriptionManager;
|
||||||
import org.wso2.carbon.device.application.mgt.core.lifecycle.LifecycleStateManger;
|
import org.wso2.carbon.device.application.mgt.core.lifecycle.LifecycleStateManager;
|
||||||
import org.wso2.carbon.device.mgt.core.service.DeviceManagementProviderService;
|
import org.wso2.carbon.device.mgt.core.service.DeviceManagementProviderService;
|
||||||
import org.wso2.carbon.user.core.service.RealmService;
|
import org.wso2.carbon.user.core.service.RealmService;
|
||||||
|
|
||||||
@ -44,7 +44,7 @@ public class DataHolder {
|
|||||||
|
|
||||||
private ApplicationStorageManager applicationStorageManager;
|
private ApplicationStorageManager applicationStorageManager;
|
||||||
|
|
||||||
private LifecycleStateManger lifecycleStateManger;
|
private LifecycleStateManager lifecycleStateManager;
|
||||||
|
|
||||||
private AppmDataHandler configManager;
|
private AppmDataHandler configManager;
|
||||||
|
|
||||||
@ -106,12 +106,12 @@ public class DataHolder {
|
|||||||
return applicationStorageManager;
|
return applicationStorageManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
public LifecycleStateManger getLifecycleStateManager() {
|
public LifecycleStateManager getLifecycleStateManager() {
|
||||||
return lifecycleStateManger;
|
return lifecycleStateManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setLifecycleStateManger(LifecycleStateManger lifecycleStateManger) {
|
public void setLifecycleStateManger(LifecycleStateManager lifecycleStateManager) {
|
||||||
this.lifecycleStateManger = lifecycleStateManger;
|
this.lifecycleStateManager = lifecycleStateManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
public AppmDataHandler getConfigManager() {
|
public AppmDataHandler getConfigManager() {
|
||||||
|
|||||||
@ -0,0 +1,157 @@
|
|||||||
|
/* 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.lifecycle;
|
||||||
|
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
import org.wso2.carbon.device.application.mgt.common.exception.LifecycleManagementException;
|
||||||
|
import org.wso2.carbon.device.application.mgt.core.internal.DataHolder;
|
||||||
|
import org.wso2.carbon.device.application.mgt.core.lifecycle.config.LifecycleState;
|
||||||
|
import org.wso2.carbon.device.mgt.common.permission.mgt.PermissionManagementException;
|
||||||
|
import org.wso2.carbon.device.mgt.core.permission.mgt.PermissionUtils;
|
||||||
|
import org.wso2.carbon.device.mgt.core.search.mgt.Constants;
|
||||||
|
import org.wso2.carbon.user.api.UserRealm;
|
||||||
|
import org.wso2.carbon.user.api.UserStoreException;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class represents the activities related to lifecycle management
|
||||||
|
*/
|
||||||
|
public class LifecycleStateManager {
|
||||||
|
|
||||||
|
private Map<String, State> lifecycleStates;
|
||||||
|
private static Log log = LogFactory.getLog(LifecycleStateManager.class);
|
||||||
|
|
||||||
|
public void init(List<LifecycleState> states) throws LifecycleManagementException {
|
||||||
|
lifecycleStates = new HashMap<>();
|
||||||
|
for (LifecycleState s : states) {
|
||||||
|
if (s.getProceedingStates() != null) {
|
||||||
|
s.getProceedingStates().replaceAll(String::toUpperCase);
|
||||||
|
}
|
||||||
|
lifecycleStates.put(s.getName().toUpperCase(), new State(s.getName().toUpperCase(),
|
||||||
|
s.getProceedingStates(), s.getPermission(), s.isAppUpdatable(), s.isAppInstallable(),
|
||||||
|
s.isInitialState(), s.isEndState()));
|
||||||
|
try {
|
||||||
|
PermissionUtils.putPermission(s.getPermission());
|
||||||
|
} catch (PermissionManagementException e) {
|
||||||
|
String msg = "Error when adding permission " + s.getPermission() + " related to the state: "
|
||||||
|
+ s.getName();
|
||||||
|
log.error(msg, e);
|
||||||
|
throw new LifecycleManagementException(msg, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public Set<String> getNextLifecycleStates(String currentLifecycleState) {
|
||||||
|
return lifecycleStates.get(currentLifecycleState.toUpperCase()).getProceedingStates();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isValidStateChange(String currentState, String nextState, String username, int tenantId) throws
|
||||||
|
LifecycleManagementException {
|
||||||
|
|
||||||
|
UserRealm userRealm;
|
||||||
|
String permission = getPermissionForStateChange(nextState);
|
||||||
|
if (permission != null) {
|
||||||
|
try {
|
||||||
|
userRealm = DataHolder.getInstance().getRealmService().getTenantUserRealm(tenantId);
|
||||||
|
if (userRealm != null && userRealm.getAuthorizationManager() != null &&
|
||||||
|
userRealm.getAuthorizationManager().isUserAuthorized(username,
|
||||||
|
PermissionUtils.getAbsolutePermissionPath(permission),
|
||||||
|
Constants.UI_EXECUTE)) {
|
||||||
|
if (currentState.equalsIgnoreCase(nextState)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
State state = getMatchingState(currentState);
|
||||||
|
if (state != null) {
|
||||||
|
return getMatchingNextState(state.getProceedingStates(), nextState);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
} catch (UserStoreException e) {
|
||||||
|
throw new LifecycleManagementException(
|
||||||
|
"UserStoreException exception from changing the state from : " + currentState + " to: "
|
||||||
|
+ nextState + " with username : " + username + " and tenant Id : " + tenantId, e);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new LifecycleManagementException(
|
||||||
|
"Required permissions cannot be found for the state : " + nextState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private State getMatchingState(String currentState) {
|
||||||
|
Iterator it = lifecycleStates.entrySet().iterator();
|
||||||
|
while (it.hasNext()) {
|
||||||
|
Map.Entry pair = (Map.Entry) it.next();
|
||||||
|
if (pair.getKey().toString().equalsIgnoreCase(currentState)) {
|
||||||
|
return lifecycleStates.get(pair.getKey().toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private boolean getMatchingNextState(Set<String> proceedingStates, String nextState) {
|
||||||
|
for (String state : proceedingStates) {
|
||||||
|
if (state.equalsIgnoreCase(nextState)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getPermissionForStateChange(String nextState) {
|
||||||
|
Iterator it = lifecycleStates.entrySet().iterator();
|
||||||
|
State nextLifecycleState;
|
||||||
|
while (it.hasNext()) {
|
||||||
|
Map.Entry pair = (Map.Entry) it.next();
|
||||||
|
if (pair.getKey().toString().equalsIgnoreCase(nextState)) {
|
||||||
|
nextLifecycleState = lifecycleStates.get(nextState);
|
||||||
|
return nextLifecycleState.getPermission();
|
||||||
|
}
|
||||||
|
it.remove();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isUpdatable(String state) {
|
||||||
|
State currentState = getMatchingState(state);
|
||||||
|
if (currentState.getIsAppUpdatable()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isInstallable(String state) {
|
||||||
|
State currentState = getMatchingState(state);
|
||||||
|
if (currentState.getIsAppInstallable()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLifecycleStates(Map<String, State> lifecycleStates) {
|
||||||
|
this.lifecycleStates = lifecycleStates;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,66 +0,0 @@
|
|||||||
package org.wso2.carbon.device.application.mgt.core.lifecycle;
|
|
||||||
|
|
||||||
import org.wso2.carbon.device.application.mgt.core.lifecycle.config.LifecycleState;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This class represents the activities related to lifecycle management
|
|
||||||
*/
|
|
||||||
public class LifecycleStateManger {
|
|
||||||
|
|
||||||
private Map<String, State> lifecycleStates;
|
|
||||||
|
|
||||||
public void init(List<LifecycleState> states){
|
|
||||||
lifecycleStates = new HashMap<>();
|
|
||||||
for (LifecycleState s : states) {
|
|
||||||
if (s.getProceedingStates() != null) {
|
|
||||||
s.getProceedingStates().replaceAll(String::toUpperCase);
|
|
||||||
}
|
|
||||||
lifecycleStates.put(s.getName().toUpperCase(), new State(s.getName().toUpperCase(), s.getProceedingStates()));
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Set<String> getNextLifecycleStates(String currentLifecycleState) {
|
|
||||||
return lifecycleStates.get(currentLifecycleState.toUpperCase()).getProceedingStates();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isValidStateChange(String currentState, String nextState) {
|
|
||||||
if (currentState.equalsIgnoreCase(nextState)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
State state = getMatchingState(currentState);
|
|
||||||
if (state != null) {
|
|
||||||
return getMatchingNextState(state.getProceedingStates(), nextState);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private State getMatchingState(String currentState) {
|
|
||||||
Iterator it = lifecycleStates.entrySet().iterator();
|
|
||||||
while (it.hasNext()) {
|
|
||||||
Map.Entry pair = (Map.Entry)it.next();
|
|
||||||
if(pair.getKey().toString().equalsIgnoreCase(currentState)) {
|
|
||||||
return lifecycleStates.get(pair.getKey().toString());
|
|
||||||
}
|
|
||||||
it.remove();
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean getMatchingNextState(Set<String> proceedingStates, String nextState) {
|
|
||||||
|
|
||||||
for (String state: proceedingStates) {
|
|
||||||
if (state.equalsIgnoreCase(nextState)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -11,9 +11,21 @@ public class State {
|
|||||||
|
|
||||||
private Set<String> proceedingStates;
|
private Set<String> proceedingStates;
|
||||||
private String stateName;
|
private String stateName;
|
||||||
|
private String permission;
|
||||||
|
private List<String> allowedActions;
|
||||||
|
private boolean isAppUpdatable;
|
||||||
|
private boolean isAppInstallable;
|
||||||
|
private boolean isInitialState;
|
||||||
|
private boolean isEndState;
|
||||||
|
|
||||||
public State(String stateName, List<String> states) {
|
public State(String stateName, List<String> states, String permission, boolean isAppUpdatable,
|
||||||
|
boolean isAppInstallable, boolean isInitialState, boolean isEndState) {
|
||||||
this.stateName = stateName;
|
this.stateName = stateName;
|
||||||
|
this.permission = permission;
|
||||||
|
this.isAppUpdatable=isAppUpdatable;
|
||||||
|
this.isAppInstallable=isAppInstallable;
|
||||||
|
this.isInitialState=isInitialState;
|
||||||
|
this.isEndState=isEndState;
|
||||||
if (states != null && !states.isEmpty()) {
|
if (states != null && !states.isEmpty()) {
|
||||||
proceedingStates = new HashSet<>(states);
|
proceedingStates = new HashSet<>(states);
|
||||||
}
|
}
|
||||||
@ -27,4 +39,14 @@ public class State {
|
|||||||
return proceedingStates;
|
return proceedingStates;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getPermission(){ return permission;}
|
||||||
|
|
||||||
|
public boolean getIsAppUpdatable(){ return isAppUpdatable;}
|
||||||
|
|
||||||
|
public boolean getIsAppInstallable(){ return isAppInstallable;}
|
||||||
|
|
||||||
|
public boolean getIsInitialState(){ return isInitialState;}
|
||||||
|
|
||||||
|
public boolean getIsEndState(){ return isEndState;}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,16 +3,22 @@ package org.wso2.carbon.device.application.mgt.core.lifecycle.config;
|
|||||||
import javax.xml.bind.annotation.XmlAttribute;
|
import javax.xml.bind.annotation.XmlAttribute;
|
||||||
import javax.xml.bind.annotation.XmlElement;
|
import javax.xml.bind.annotation.XmlElement;
|
||||||
import javax.xml.bind.annotation.XmlElementWrapper;
|
import javax.xml.bind.annotation.XmlElementWrapper;
|
||||||
|
import javax.xml.bind.annotation.XmlRootElement;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class represents the lifecycle state config
|
* This class represents the lifecycle state config
|
||||||
*/
|
*/
|
||||||
|
@XmlRootElement(name = "LifecycleState")
|
||||||
public class LifecycleState {
|
public class LifecycleState {
|
||||||
|
|
||||||
private String name;
|
private String name;
|
||||||
|
private String permission;
|
||||||
private List<String> proceedingStates;
|
private List<String> proceedingStates;
|
||||||
|
private boolean isAppInstallable;
|
||||||
|
private boolean isAppUpdatable;
|
||||||
|
private boolean isInitialState;
|
||||||
|
private boolean isEndState;
|
||||||
|
|
||||||
@XmlAttribute(name = "name")
|
@XmlAttribute(name = "name")
|
||||||
public String getName() {
|
public String getName() {
|
||||||
@ -32,4 +38,50 @@ public class LifecycleState {
|
|||||||
public void setProceedingStates(List<String> proceedingStates) {
|
public void setProceedingStates(List<String> proceedingStates) {
|
||||||
this.proceedingStates = proceedingStates;
|
this.proceedingStates = proceedingStates;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@XmlElement(name = "Permission")
|
||||||
|
public String getPermission() {
|
||||||
|
return permission;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPermission(String permission) {
|
||||||
|
this.permission = permission;
|
||||||
|
}
|
||||||
|
|
||||||
|
@XmlElement(name = "IsAppInstallable")
|
||||||
|
public boolean isAppInstallable() {
|
||||||
|
return isAppInstallable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAppInstallable(boolean isAppInstallable) {
|
||||||
|
this.isAppInstallable = isAppInstallable;
|
||||||
|
}
|
||||||
|
|
||||||
|
@XmlElement(name = "IsAppUpdatable")
|
||||||
|
public boolean isAppUpdatable() {
|
||||||
|
return isAppUpdatable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAppUpdatable(boolean isAppUpdatable) {
|
||||||
|
this.isAppUpdatable = isAppUpdatable;
|
||||||
|
}
|
||||||
|
|
||||||
|
@XmlElement(name = "IsInitialState")
|
||||||
|
public boolean isInitialState() {
|
||||||
|
return isInitialState;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setInitialState(boolean isInitialState) {
|
||||||
|
this.isInitialState = isInitialState;
|
||||||
|
}
|
||||||
|
|
||||||
|
@XmlElement(name = "IsEndState")
|
||||||
|
public boolean isEndState() {
|
||||||
|
return isEndState;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEndState(boolean isEndState) {
|
||||||
|
this.isEndState = isEndState;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -27,7 +27,7 @@ import org.wso2.carbon.device.application.mgt.common.services.ReviewManager;
|
|||||||
import org.wso2.carbon.device.application.mgt.common.services.SubscriptionManager;
|
import org.wso2.carbon.device.application.mgt.common.services.SubscriptionManager;
|
||||||
import org.wso2.carbon.device.application.mgt.core.config.ConfigurationManager;
|
import org.wso2.carbon.device.application.mgt.core.config.ConfigurationManager;
|
||||||
import org.wso2.carbon.device.application.mgt.core.config.Extension;
|
import org.wso2.carbon.device.application.mgt.core.config.Extension;
|
||||||
import org.wso2.carbon.device.application.mgt.core.lifecycle.LifecycleStateManger;
|
import org.wso2.carbon.device.application.mgt.core.lifecycle.LifecycleStateManager;
|
||||||
|
|
||||||
import java.lang.reflect.Constructor;
|
import java.lang.reflect.Constructor;
|
||||||
|
|
||||||
@ -64,10 +64,10 @@ public class ApplicationManagementUtil {
|
|||||||
return getInstance(extension, ApplicationStorageManager.class);
|
return getInstance(extension, ApplicationStorageManager.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static LifecycleStateManger getLifecycleStateMangerInstance() throws InvalidConfigurationException {
|
public static LifecycleStateManager getLifecycleStateMangerInstance() throws InvalidConfigurationException {
|
||||||
ConfigurationManager configurationManager = ConfigurationManager.getInstance();
|
ConfigurationManager configurationManager = ConfigurationManager.getInstance();
|
||||||
Extension extension = configurationManager.getExtension(Extension.Name.LifecycleStateManager);
|
Extension extension = configurationManager.getExtension(Extension.Name.LifecycleStateManager);
|
||||||
return getInstance(extension, LifecycleStateManger.class);
|
return getInstance(extension, LifecycleStateManager.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static <T> T getInstance(Extension extension, Class<T> cls) throws InvalidConfigurationException {
|
private static <T> T getInstance(Extension extension, Class<T> cls) throws InvalidConfigurationException {
|
||||||
|
|||||||
@ -0,0 +1,25 @@
|
|||||||
|
package org.wso2.carbon.device.application.mgt.core;
|
||||||
|
|
||||||
|
import org.wso2.carbon.device.application.mgt.core.lifecycle.LifecycleStateManager;
|
||||||
|
import org.wso2.carbon.device.application.mgt.core.lifecycle.State;
|
||||||
|
import org.wso2.carbon.device.application.mgt.core.lifecycle.config.LifecycleState;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
|
||||||
|
public class LifeCycleStateManagerTest extends LifecycleStateManager {
|
||||||
|
|
||||||
|
public void initializeLifeCycleDetails(List<LifecycleState> states) {
|
||||||
|
HashMap<String, State> lifecycleStates = new HashMap<>();
|
||||||
|
for (LifecycleState s : states) {
|
||||||
|
if (s.getProceedingStates() != null) {
|
||||||
|
s.getProceedingStates().replaceAll(String::toUpperCase);
|
||||||
|
}
|
||||||
|
lifecycleStates.put(s.getName().toUpperCase(), new State(s.getName().toUpperCase(),
|
||||||
|
s.getProceedingStates(), s.getPermission(), s.isAppUpdatable(), s.isAppInstallable(),
|
||||||
|
s.isInitialState(), s.isEndState()));
|
||||||
|
}
|
||||||
|
setLifecycleStates(lifecycleStates);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -3,9 +3,10 @@ package org.wso2.carbon.device.application.mgt.core;
|
|||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.testng.annotations.BeforeClass;
|
import org.testng.annotations.BeforeClass;
|
||||||
import org.testng.annotations.Test;
|
import org.testng.annotations.Test;
|
||||||
|
import org.wso2.carbon.device.application.mgt.common.exception.LifecycleManagementException;
|
||||||
import org.wso2.carbon.device.application.mgt.core.config.Configuration;
|
import org.wso2.carbon.device.application.mgt.core.config.Configuration;
|
||||||
import org.wso2.carbon.device.application.mgt.core.config.ConfigurationManager;
|
import org.wso2.carbon.device.application.mgt.core.config.ConfigurationManager;
|
||||||
import org.wso2.carbon.device.application.mgt.core.lifecycle.LifecycleStateManger;
|
import org.wso2.carbon.device.application.mgt.core.lifecycle.LifecycleStateManager;
|
||||||
import org.wso2.carbon.device.application.mgt.core.lifecycle.config.LifecycleState;
|
import org.wso2.carbon.device.application.mgt.core.lifecycle.config.LifecycleState;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -14,45 +15,70 @@ import java.util.Set;
|
|||||||
public class LifecycleManagementTest {
|
public class LifecycleManagementTest {
|
||||||
|
|
||||||
private List<LifecycleState> lifecycleStates;
|
private List<LifecycleState> lifecycleStates;
|
||||||
private LifecycleStateManger lifecycleStateManger;
|
private LifecycleStateManager lifecycleStateManager;
|
||||||
|
|
||||||
private final String CURRENT_STATE = "Approved";
|
private final String CURRENT_STATE = "Approved";
|
||||||
private final String NEXT_STATE = "Published";
|
private final String NEXT_STATE = "Published";
|
||||||
private final String BOGUS_STATE = "Removed";
|
private final String BOGUS_STATE = "Removed";
|
||||||
|
private final String UPDATABLE_STATE = "Created";
|
||||||
|
private final String NON_UPDATABLE_STATE = "Removed";
|
||||||
|
private final String INSTALLABLE_STATE = "Published";
|
||||||
|
private final String UNINSTALlABLE_STATE = "Removed";
|
||||||
|
|
||||||
|
|
||||||
@BeforeClass
|
@BeforeClass
|
||||||
public void init() {
|
public void init() throws LifecycleManagementException {
|
||||||
ConfigurationManager configurationManager = ConfigurationManager.getInstance();
|
ConfigurationManager configurationManager = ConfigurationManager.getInstance();
|
||||||
Configuration configuration = configurationManager.getConfiguration();
|
Configuration configuration = configurationManager.getConfiguration();
|
||||||
lifecycleStates = configuration.getLifecycleStates();
|
lifecycleStates = configuration.getLifecycleStates();
|
||||||
lifecycleStateManger = new LifecycleStateManger();
|
lifecycleStateManager = new LifeCycleStateManagerTest();
|
||||||
lifecycleStateManger.init(lifecycleStates);
|
((LifeCycleStateManagerTest) lifecycleStateManager).initializeLifeCycleDetails(lifecycleStates);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void checkValidNextLifecycleState() {
|
public void checkValidNextLifecycleState() {
|
||||||
Set<String> proceedingStates = lifecycleStateManger.getNextLifecycleStates(CURRENT_STATE);
|
Set<String> proceedingStates = lifecycleStateManager.getNextLifecycleStates(CURRENT_STATE);
|
||||||
Assert.assertTrue("Invalid proceeding state of: " + CURRENT_STATE,
|
Assert.assertTrue("Invalid proceeding state of: " + CURRENT_STATE,
|
||||||
proceedingStates.contains(NEXT_STATE.toUpperCase()));
|
proceedingStates.contains(NEXT_STATE.toUpperCase()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void checkInvalidNextLifecycleState() {
|
public void checkInvalidNextLifecycleState() {
|
||||||
Set<String> proceedingStates = lifecycleStateManger.getNextLifecycleStates(CURRENT_STATE);
|
Set<String> proceedingStates = lifecycleStateManager.getNextLifecycleStates(CURRENT_STATE);
|
||||||
Assert.assertFalse("Invalid proceeding state of: " + CURRENT_STATE,
|
Assert.assertFalse("Invalid proceeding state of: " + CURRENT_STATE,
|
||||||
proceedingStates.contains(BOGUS_STATE.toUpperCase()));
|
proceedingStates.contains(BOGUS_STATE.toUpperCase()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void checkValidStateChange() {
|
public void CheckUpdatableState() {
|
||||||
Assert.assertTrue("Invalid state transition from: " + CURRENT_STATE + " to: " + NEXT_STATE,
|
Boolean isUpdatable = lifecycleStateManager.isUpdatable(UPDATABLE_STATE);
|
||||||
lifecycleStateManger.isValidStateChange(CURRENT_STATE, NEXT_STATE));
|
System.out.println(isUpdatable);
|
||||||
|
Assert.assertTrue("Updatable state: " + UPDATABLE_STATE, isUpdatable);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void checkInvalidStateChange() {
|
public void CheckNonUpdatableState() {
|
||||||
Assert.assertFalse("Invalid state transition from: " + CURRENT_STATE + " to: " + BOGUS_STATE,
|
Boolean isUpdatable = lifecycleStateManager.isUpdatable(NON_UPDATABLE_STATE);
|
||||||
lifecycleStateManger.isValidStateChange(CURRENT_STATE, BOGUS_STATE));
|
Assert.assertFalse("Non Updatable state: " + NON_UPDATABLE_STATE, isUpdatable);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void CheckInstallableState() {
|
||||||
|
Boolean isInstallable = lifecycleStateManager.isInstallable(INSTALLABLE_STATE);
|
||||||
|
Assert.assertTrue("Installable state: " + INSTALLABLE_STATE, isInstallable);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void CheckUnInstallableState() {
|
||||||
|
Boolean isInstallable = lifecycleStateManager.isInstallable(UNINSTALlABLE_STATE);
|
||||||
|
Assert.assertFalse("UnInstallable state: " + UNINSTALlABLE_STATE, isInstallable);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void check() {
|
||||||
|
Set<String> proceedingStates = lifecycleStateManager.getNextLifecycleStates(CURRENT_STATE);
|
||||||
|
Assert.assertFalse("Invalid proceeding state of: " + CURRENT_STATE,
|
||||||
|
proceedingStates.contains(BOGUS_STATE.toUpperCase()));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,7 +16,6 @@
|
|||||||
~ specific language governing permissions and limitations
|
~ specific language governing permissions and limitations
|
||||||
~ under the License.
|
~ under the License.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<ApplicationManagementConfiguration>
|
<ApplicationManagementConfiguration>
|
||||||
|
|
||||||
<!-- ApplicationDTO Mgt DB schema -->
|
<!-- ApplicationDTO Mgt DB schema -->
|
||||||
@ -30,54 +29,143 @@
|
|||||||
<ClassName>org.wso2.carbon.device.application.mgt.core.impl.ReviewManagerImpl</ClassName>
|
<ClassName>org.wso2.carbon.device.application.mgt.core.impl.ReviewManagerImpl</ClassName>
|
||||||
</Extension>
|
</Extension>
|
||||||
<Extension name="LifecycleStateManager">
|
<Extension name="LifecycleStateManager">
|
||||||
<ClassName>org.wso2.carbon.device.application.mgt.core.lifecycle.LifecycleStateManger</ClassName>
|
<ClassName>org.wso2.carbon.device.application.mgt.core.lifecycle.LifecycleStateManager</ClassName>
|
||||||
</Extension>
|
</Extension>
|
||||||
<Extension name="SubscriptionManager">
|
<Extension name="SubscriptionManager">
|
||||||
<ClassName>org.wso2.carbon.device.application.mgt.core.impl.SubscriptionManagerImpl</ClassName>
|
<ClassName>org.wso2.carbon.device.application.mgt.core.impl.SubscriptionManagerImpl</ClassName>
|
||||||
</Extension>
|
</Extension>
|
||||||
|
<Extension name="ApplicationStorageManager">
|
||||||
|
<ClassName>org.wso2.carbon.device.application.mgt.core.impl.ApplicationStorageManagerImpl</ClassName>
|
||||||
|
<Parameters>
|
||||||
|
<Parameter name="StoragePath">repository/resources/apps/</Parameter>
|
||||||
|
<Parameter name="MaxScreenShotCount">6</Parameter>
|
||||||
|
</Parameters>
|
||||||
|
</Extension>
|
||||||
</Extensions>
|
</Extensions>
|
||||||
|
|
||||||
|
<!-- This is for publisher lifecycle -->
|
||||||
|
<!-- The current lifecycle as follows
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
[Created] -> [In-Review] -> [Approved] -> [Published] -> [Unpublished] -> [Removed]
|
||||||
|
^ | ^
|
||||||
|
| | |
|
||||||
|
| |-> [Deprecated] - - - - - - - -|
|
||||||
|
| |
|
||||||
|
|-> [Rejected] - - - - - - - - - - - - - - - - - - - - - - - - |
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
If there is a requirement to introduce a new state to the lifecycle, please refer above
|
||||||
|
diagram and add relevant state to the below configuration appropriately.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<!-- a lifecyclestate can have following properties
|
||||||
|
<LifecycleState name="In-Review">
|
||||||
|
<IsAppInstallable>false</IsAppInstallable>
|
||||||
|
<IsAppUpdatable>true</IsAppUpdatable>
|
||||||
|
<IsInitialState>false</IsInitialState>
|
||||||
|
<IsEndState>false</IsEndState>
|
||||||
|
<Permission>
|
||||||
|
/device-mgt/applications/life-cycle/in-review
|
||||||
|
</Permission>
|
||||||
|
<ProceedingStates>
|
||||||
|
<State>Rejected</State>
|
||||||
|
<State>Approved</State>
|
||||||
|
</ProceedingStates>
|
||||||
|
</LifecycleState>
|
||||||
|
-->
|
||||||
<LifecycleStates>
|
<LifecycleStates>
|
||||||
<LifecycleState name="Created">
|
<LifecycleState name="Created">
|
||||||
|
<IsAppUpdatable>true</IsAppUpdatable>
|
||||||
|
<IsInitialState>true</IsInitialState>
|
||||||
|
<Permission>/device-mgt/applications/life-cycle/create</Permission>
|
||||||
<ProceedingStates>
|
<ProceedingStates>
|
||||||
<State>In-Review</State>
|
<State>In-Review</State>
|
||||||
</ProceedingStates>
|
</ProceedingStates>
|
||||||
</LifecycleState>
|
</LifecycleState>
|
||||||
<LifecycleState name="In-Review">
|
<LifecycleState name="In-Review">
|
||||||
|
<Permission>/device-mgt/applications/life-cycle/in-review</Permission>
|
||||||
<ProceedingStates>
|
<ProceedingStates>
|
||||||
<State>Rejected</State>
|
<State>Rejected</State>
|
||||||
<State>Approved</State>
|
<State>Approved</State>
|
||||||
</ProceedingStates>
|
</ProceedingStates>
|
||||||
</LifecycleState>
|
</LifecycleState>
|
||||||
<LifecycleState name="Approved">
|
<LifecycleState name="Approved">
|
||||||
|
<Permission>/device-mgt/applications/life-cycle/approve</Permission>
|
||||||
<ProceedingStates>
|
<ProceedingStates>
|
||||||
<State>Published</State>
|
<State>Published</State>
|
||||||
|
<State>Created</State>
|
||||||
</ProceedingStates>
|
</ProceedingStates>
|
||||||
</LifecycleState>
|
</LifecycleState>
|
||||||
<LifecycleState name="Rejected">
|
<LifecycleState name="Rejected">
|
||||||
|
<Permission>/device-mgt/applications/life-cycle/reject</Permission>
|
||||||
<ProceedingStates>
|
<ProceedingStates>
|
||||||
<State>In-Review</State>
|
<State>Created</State>
|
||||||
<State>Removed</State>
|
<State>Removed</State>
|
||||||
</ProceedingStates>
|
</ProceedingStates>
|
||||||
</LifecycleState>
|
</LifecycleState>
|
||||||
<LifecycleState name="Published">
|
<LifecycleState name="Published">
|
||||||
|
<IsAppInstallable>true</IsAppInstallable>
|
||||||
|
<Permission>/device-mgt/applications/life-cycle/publish</Permission>
|
||||||
<ProceedingStates>
|
<ProceedingStates>
|
||||||
<State>Unpublished</State>
|
<State>Unpublished</State>
|
||||||
<State>Deprecated</State>
|
<State>Deprecated</State>
|
||||||
</ProceedingStates>
|
</ProceedingStates>
|
||||||
</LifecycleState>
|
</LifecycleState>
|
||||||
<LifecycleState name="Unpublished">
|
<LifecycleState name="Unpublished">
|
||||||
|
<Permission>/device-mgt/applications/life-cycle/unpublish</Permission>
|
||||||
<ProceedingStates>
|
<ProceedingStates>
|
||||||
|
<State>Published</State>
|
||||||
|
<State>In-Review</State>
|
||||||
<State>Removed</State>
|
<State>Removed</State>
|
||||||
</ProceedingStates>
|
</ProceedingStates>
|
||||||
</LifecycleState>
|
</LifecycleState>
|
||||||
<LifecycleState name="Deprecated">
|
<LifecycleState name="Deprecated">
|
||||||
|
<Permission>/device-mgt/applications/life-cycle/deprecate</Permission>
|
||||||
<ProceedingStates>
|
<ProceedingStates>
|
||||||
<State>Removed</State>
|
<State>Removed</State>
|
||||||
|
<State>In-Review</State>
|
||||||
</ProceedingStates>
|
</ProceedingStates>
|
||||||
</LifecycleState>
|
</LifecycleState>
|
||||||
<LifecycleState name="Removed">
|
<LifecycleState name="Removed">
|
||||||
|
<IsEndState>true</IsEndState>
|
||||||
|
<Permission>/device-mgt/applications/life-cycle/remove</Permission>
|
||||||
</LifecycleState>
|
</LifecycleState>
|
||||||
</LifecycleStates>
|
</LifecycleStates>
|
||||||
|
|
||||||
|
<UIConfigs>
|
||||||
|
<EnableOAuth>true</EnableOAuth>
|
||||||
|
<EnableSSO>false</EnableSSO>
|
||||||
|
<EnableSSO>false</EnableSSO>
|
||||||
|
<AppRegistration>
|
||||||
|
<Tags>
|
||||||
|
<Tag>application_management</Tag>
|
||||||
|
<Tag>device_management</Tag>
|
||||||
|
<Tag>subscription_management</Tag>
|
||||||
|
<Tag>review_management</Tag>
|
||||||
|
</Tags>
|
||||||
|
<AllowToAllDomains>true</AllowToAllDomains>
|
||||||
|
</AppRegistration>
|
||||||
|
<Scopes>
|
||||||
|
<Scope>perm:app:review:view</Scope>
|
||||||
|
<Scope>perm:app:review:update</Scope>
|
||||||
|
<Scope>perm:app:publisher:view</Scope>
|
||||||
|
<Scope>perm:app:publisher:update</Scope>
|
||||||
|
</Scopes>
|
||||||
|
<SSOConfiguration>
|
||||||
|
<Issuer>app-mgt</Issuer>
|
||||||
|
</SSOConfiguration>
|
||||||
|
<LoginResponse>
|
||||||
|
<SuccessCallback>/application-mgt</SuccessCallback>
|
||||||
|
<FailureCallback>
|
||||||
|
<BadRequest>/pages/error/client-errors/400</BadRequest>
|
||||||
|
<Unauthorized>/pages/error/client-errors/401</Unauthorized>
|
||||||
|
<Forbidden>/pages/error/client-errors/403</Forbidden>
|
||||||
|
<NotFound>/pages/error/client-errors/404</NotFound>
|
||||||
|
<MethodNotAllowed>/pages/error/client-errors/405</MethodNotAllowed>
|
||||||
|
<NotAcceptable>/pages/error/client-errors/406</NotAcceptable>
|
||||||
|
<UnsupportedMediaType>/pages/error/client-errors/415</UnsupportedMediaType>
|
||||||
|
<InternalServerError>/pages/error/server-errors/500</InternalServerError>
|
||||||
|
<DefaultPage>/pages/error/default</DefaultPage>
|
||||||
|
</FailureCallback>
|
||||||
|
</LoginResponse>
|
||||||
|
</UIConfigs>
|
||||||
</ApplicationManagementConfiguration>
|
</ApplicationManagementConfiguration>
|
||||||
|
|||||||
@ -14,6 +14,7 @@
|
|||||||
"antd": "^3.15.0",
|
"antd": "^3.15.0",
|
||||||
"react": "^16.8.4",
|
"react": "^16.8.4",
|
||||||
"react-dom": "^16.8.4",
|
"react-dom": "^16.8.4",
|
||||||
|
"react-highlight-words": "^0.16.0",
|
||||||
"react-router-config": "^5.0.0",
|
"react-router-config": "^5.0.0",
|
||||||
"react-router-dom": "latest",
|
"react-router-dom": "latest",
|
||||||
"react-scripts": "2.1.8"
|
"react-scripts": "2.1.8"
|
||||||
|
|||||||
@ -1,19 +1,26 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import "antd/dist/antd.css";
|
import "antd/dist/antd.css";
|
||||||
import { renderRoutes } from "react-router-config";
|
import RouteWithSubRoutes from "./components/RouteWithSubRoutes";
|
||||||
|
import {
|
||||||
|
BrowserRouter as Router,
|
||||||
|
Link,
|
||||||
|
} from 'react-router-dom';
|
||||||
|
|
||||||
class App extends React.Component {
|
class App extends React.Component {
|
||||||
|
routes;
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.routes = props.routes;
|
||||||
route : props.route
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<Router>
|
||||||
{renderRoutes(this.state.route.routes)}
|
<div>
|
||||||
</div>
|
{this.routes.map((route) => (
|
||||||
|
<RouteWithSubRoutes key={route.path} {...route} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Router>
|
||||||
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,19 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import {Route} from 'react-router-dom';
|
||||||
|
class RouteWithSubRoutes extends React.Component{
|
||||||
|
props;
|
||||||
|
constructor(props){
|
||||||
|
super(props);
|
||||||
|
this.props = props;
|
||||||
|
}
|
||||||
|
render() {
|
||||||
|
return(
|
||||||
|
<Route path={this.props.path} render={(props) => (
|
||||||
|
<this.props.component {...props} routes={this.props.routes}/>
|
||||||
|
)}/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RouteWithSubRoutes;
|
||||||
@ -1,3 +1,27 @@
|
|||||||
.App {
|
.App {
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ant-layout-header{
|
||||||
|
padding: 0;
|
||||||
|
height: auto;
|
||||||
|
box-shadow: 0 2px 8px #f0f1f2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.steps-content {
|
||||||
|
margin-top: 16px;
|
||||||
|
border: 1px dashed #e9e9e9;
|
||||||
|
border-radius: 6px;
|
||||||
|
background-color: #fafafa;
|
||||||
|
min-height: 200px;
|
||||||
|
text-align: center;
|
||||||
|
padding-top: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.steps-action {
|
||||||
|
margin-top: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-input-affix-wrapper .ant-input{
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
@ -1,41 +1,39 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import './index.css';
|
|
||||||
import App from './App';
|
|
||||||
import * as serviceWorker from './serviceWorker';
|
import * as serviceWorker from './serviceWorker';
|
||||||
import { renderRoutes } from "react-router-config";
|
import App from "./App";
|
||||||
import Dashboard from "./pages/dashboard/Dashboard"
|
|
||||||
import Login from "./pages/Login";
|
import Login from "./pages/Login";
|
||||||
import {BrowserRouter} from "react-router-dom";
|
import Dashboard from "./pages/dashboard/Dashboard";
|
||||||
|
import Apps from "./pages/dashboard/apps/Apps";
|
||||||
|
import AddNewApp from "./pages/dashboard/add-new-app/AddNewApp";
|
||||||
|
import './index.css';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
{
|
{
|
||||||
component: App,
|
path: '/publisher/Login',
|
||||||
|
component: Login
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/publisher/dashboard',
|
||||||
|
component: Dashboard,
|
||||||
routes: [
|
routes: [
|
||||||
{
|
{
|
||||||
path: "/publisher",
|
path: '/publisher/dashboard/apps',
|
||||||
exact: true,
|
component: Apps
|
||||||
component: Dashboard,
|
|
||||||
routes: [
|
|
||||||
{
|
|
||||||
path: "/publisher/a",
|
|
||||||
component: Login
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/publisher/login",
|
path: '/publisher/dashboard/new-app',
|
||||||
component: Login
|
component: AddNewApp
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
];
|
]
|
||||||
|
|
||||||
ReactDOM.render( <BrowserRouter>
|
|
||||||
{/* kick it all off with the root route */}
|
|
||||||
{renderRoutes(routes)}
|
ReactDOM.render( <App routes={routes}/>, document.getElementById('root'));
|
||||||
</BrowserRouter>, document.getElementById('root'));
|
|
||||||
|
|
||||||
// If you want your app to work offline and load faster, you can change
|
// If you want your app to work offline and load faster, you can change
|
||||||
// unregister() to register() below. Note this comes with some pitfalls.
|
// unregister() to register() below. Note this comes with some pitfalls.
|
||||||
|
|||||||
@ -1,53 +1,45 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { Layout, Menu, Breadcrumb } from 'antd';
|
import { Layout, Menu, Icon } from 'antd';
|
||||||
|
|
||||||
const { Header, Content, Footer } = Layout;
|
const { Header, Content, Footer } = Layout;
|
||||||
|
|
||||||
import styles from './Dashboard.less';
|
import styles from './Dashboard.less';
|
||||||
import Logo from "../../../public/images/logo.svg";
|
import Logo from "../../../public/images/logo.svg";
|
||||||
import Login from "../Login";
|
import {Link, NavLink} from "react-router-dom";
|
||||||
import {renderRoutes} from "react-router-config";
|
import RouteWithSubRoutes from "../../components/RouteWithSubRoutes"
|
||||||
import {NavLink} from "react-router-dom";
|
|
||||||
|
|
||||||
|
|
||||||
class Dashboard extends React.Component {
|
class Dashboard extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
route : props.route
|
routes : props.routes
|
||||||
}
|
}
|
||||||
console.log(props);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<Layout className="layout">
|
<Layout className="layout">
|
||||||
<Header>
|
<Header>
|
||||||
<div style={{backgroundImage: "url(" + { Logo} + ")"}} className={styles.logo}/>
|
<div className={styles.logo}>
|
||||||
|
<img src={Logo}/>
|
||||||
|
</div>
|
||||||
<Menu
|
<Menu
|
||||||
theme="light"
|
theme="light"
|
||||||
mode="horizontal"
|
mode="horizontal"
|
||||||
defaultSelectedKeys={['2']}
|
defaultSelectedKeys={['2']}
|
||||||
style={{ lineHeight: '64px' }}
|
style={{ lineHeight: '64px' }}
|
||||||
>
|
>
|
||||||
<Menu.Item key="1">nav 1</Menu.Item>
|
<Menu.Item key="1"><Link to="apps"><Icon type="appstore" />Apps</Link></Menu.Item>
|
||||||
<Menu.Item key="2">nav 2</Menu.Item>
|
<Menu.Item key="2"><Link to="apps"><Icon type="line-chart" />Apps</Link></Menu.Item>
|
||||||
<Menu.Item key="3">nav 3</Menu.Item>
|
<Menu.Item key="3"><Link to="new-app"><Icon type="upload" />Add New App</Link></Menu.Item>
|
||||||
</Menu>
|
</Menu>
|
||||||
</Header>
|
</Header>
|
||||||
<Content style={{ padding: '0 50px' }}>
|
<Content style={{ padding: '0 0' }}>
|
||||||
<Breadcrumb style={{ margin: '16px 0' }}>
|
{this.state.routes.map((route) => (
|
||||||
<Breadcrumb.Item>Home</Breadcrumb.Item>
|
<RouteWithSubRoutes key={route.path} {...route} />
|
||||||
<Breadcrumb.Item>List</Breadcrumb.Item>
|
))}
|
||||||
<Breadcrumb.Item>App</Breadcrumb.Item>
|
|
||||||
</Breadcrumb>
|
|
||||||
<NavLink exact to="/publisher/a" className="nav-link" >
|
|
||||||
Items
|
|
||||||
</NavLink>
|
|
||||||
|
|
||||||
{/* child routes won't render without this */}
|
|
||||||
{renderRoutes(this.state.route.routes, { someProp: "these extra props are optional" })}
|
|
||||||
<div style={{ background: '#fff', padding: 24, minHeight: 280 }}>Content</div>
|
|
||||||
</Content>
|
</Content>
|
||||||
<Footer style={{ textAlign: 'center' }}>
|
<Footer style={{ textAlign: 'center' }}>
|
||||||
©2019 entgra.io
|
©2019 entgra.io
|
||||||
|
|||||||
@ -1,7 +1,15 @@
|
|||||||
.logo {
|
.logo {
|
||||||
width: 120px;
|
width: 120px;
|
||||||
height: 31px;
|
height: 31px;
|
||||||
background: rgba(0,0,0,.2);
|
margin: 16px 0 16px 20px;
|
||||||
margin: 16px 24px 16px 0;
|
|
||||||
float: left;
|
float: left;
|
||||||
|
|
||||||
|
img{
|
||||||
|
height: 35px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
input{
|
||||||
|
min-height: 0;
|
||||||
}
|
}
|
||||||
@ -0,0 +1,117 @@
|
|||||||
|
import React from "react";
|
||||||
|
import "antd/dist/antd.css";
|
||||||
|
import {PageHeader, Typography, Card, Steps, Button, message, Row, Col} from "antd";
|
||||||
|
import Step1 from "./Step1"
|
||||||
|
import Step2 from "./Step2"
|
||||||
|
import Step3 from "./Step3"
|
||||||
|
|
||||||
|
const Paragraph = Typography;
|
||||||
|
|
||||||
|
const routes = [
|
||||||
|
{
|
||||||
|
path: 'index',
|
||||||
|
breadcrumbName: 'publisher',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'first',
|
||||||
|
breadcrumbName: 'dashboard',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'second',
|
||||||
|
breadcrumbName: 'add new app',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const Step = Steps.Step;
|
||||||
|
|
||||||
|
const steps = [{
|
||||||
|
title: 'First',
|
||||||
|
content: Step1
|
||||||
|
}, {
|
||||||
|
title: 'Second',
|
||||||
|
content: Step2,
|
||||||
|
}, {
|
||||||
|
title: 'Last',
|
||||||
|
content: Step3,
|
||||||
|
}];
|
||||||
|
|
||||||
|
|
||||||
|
class AddNewApp extends React.Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
current: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
next() {
|
||||||
|
const current = this.state.current + 1;
|
||||||
|
this.setState({current});
|
||||||
|
}
|
||||||
|
|
||||||
|
prev() {
|
||||||
|
const current = this.state.current - 1;
|
||||||
|
this.setState({current});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {current} = this.state;
|
||||||
|
const Content = steps[current].content;
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<PageHeader
|
||||||
|
title="Add New App"
|
||||||
|
breadcrumb={{routes}}
|
||||||
|
>
|
||||||
|
<div className="wrap">
|
||||||
|
<div className="content">
|
||||||
|
<Paragraph>
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempo.
|
||||||
|
</Paragraph>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</PageHeader>
|
||||||
|
<div style={{background: '#f0f2f5', padding: 24, minHeight: 720}}>
|
||||||
|
<Row>
|
||||||
|
<Col span={16} offset={4}>
|
||||||
|
<Card>
|
||||||
|
<div>
|
||||||
|
<Steps current={current}>
|
||||||
|
{steps.map(item => <Step key={item.title} title={item.title}/>)}
|
||||||
|
</Steps>
|
||||||
|
<Content/>
|
||||||
|
<div className="steps-action">
|
||||||
|
{
|
||||||
|
current < steps.length - 1
|
||||||
|
&& <Button type="primary" onClick={() => this.next()}>Next</Button>
|
||||||
|
}
|
||||||
|
{
|
||||||
|
current === steps.length - 1
|
||||||
|
&& <Button type="primary"
|
||||||
|
onClick={() => message.success('Processing complete!')}>Done</Button>
|
||||||
|
}
|
||||||
|
{
|
||||||
|
current > 0
|
||||||
|
&& (
|
||||||
|
<Button style={{marginLeft: 8}} onClick={() => this.prev()}>
|
||||||
|
Previous
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AddNewApp;
|
||||||
@ -0,0 +1,153 @@
|
|||||||
|
import React from "react";
|
||||||
|
import {Form, Input, Button, Select, Divider, Tag, Tooltip, Icon, Checkbox, Row, Col} from "antd";
|
||||||
|
import styles from './Style.less';
|
||||||
|
|
||||||
|
const { Option } = Select;
|
||||||
|
const { TextArea } = Input;
|
||||||
|
const InputGroup = Input.Group;
|
||||||
|
|
||||||
|
const formItemLayout = {
|
||||||
|
labelCol: {
|
||||||
|
span: 8,
|
||||||
|
},
|
||||||
|
wrapperCol: {
|
||||||
|
span: 16,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
class EditableTagGroup extends React.Component {
|
||||||
|
state = {
|
||||||
|
tags: [],
|
||||||
|
inputVisible: false,
|
||||||
|
inputValue: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
handleClose = (removedTag) => {
|
||||||
|
const tags = this.state.tags.filter(tag => tag !== removedTag);
|
||||||
|
console.log(tags);
|
||||||
|
this.setState({ tags });
|
||||||
|
}
|
||||||
|
|
||||||
|
showInput = () => {
|
||||||
|
this.setState({ inputVisible: true }, () => this.input.focus());
|
||||||
|
}
|
||||||
|
|
||||||
|
handleInputChange = (e) => {
|
||||||
|
this.setState({ inputValue: e.target.value });
|
||||||
|
}
|
||||||
|
|
||||||
|
handleInputConfirm = () => {
|
||||||
|
const { inputValue } = this.state;
|
||||||
|
let { tags } = this.state;
|
||||||
|
if (inputValue && tags.indexOf(inputValue) === -1) {
|
||||||
|
tags = [...tags, inputValue];
|
||||||
|
}
|
||||||
|
console.log(tags);
|
||||||
|
this.setState({
|
||||||
|
tags,
|
||||||
|
inputVisible: false,
|
||||||
|
inputValue: '',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
saveInputRef = input => this.input = input
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { tags, inputVisible, inputValue } = this.state;
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{tags.map((tag, index) => {
|
||||||
|
const isLongTag = tag.length > 20;
|
||||||
|
const tagElem = (
|
||||||
|
<Tag key={tag} closable={index !== 0} onClose={() => this.handleClose(tag)}>
|
||||||
|
{isLongTag ? `${tag.slice(0, 20)}...` : tag}
|
||||||
|
</Tag>
|
||||||
|
);
|
||||||
|
return isLongTag ? <Tooltip title={tag} key={tag}>{tagElem}</Tooltip> : tagElem;
|
||||||
|
})}
|
||||||
|
{inputVisible && (
|
||||||
|
<Input
|
||||||
|
ref={this.saveInputRef}
|
||||||
|
type="text"
|
||||||
|
size="small"
|
||||||
|
style={{ width: 78 }}
|
||||||
|
value={inputValue}
|
||||||
|
onChange={this.handleInputChange}
|
||||||
|
onBlur={this.handleInputConfirm}
|
||||||
|
onPressEnter={this.handleInputConfirm}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{!inputVisible && (
|
||||||
|
<Tag
|
||||||
|
onClick={this.showInput}
|
||||||
|
style={{ background: '#fff', borderStyle: 'dashed' }}
|
||||||
|
>
|
||||||
|
<Icon type="plus" /> New Tag
|
||||||
|
</Tag>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Step1 extends React.Component {
|
||||||
|
render() {
|
||||||
|
console.log("hhhoohh");
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Form layout="horizontal" className={styles.stepForm} hideRequiredMark>
|
||||||
|
|
||||||
|
<Form.Item {...formItemLayout} label="Platform">
|
||||||
|
<Select placeholder="ex: android">
|
||||||
|
<Option value="Android">Android</Option>
|
||||||
|
<Option value="iOS">iOS</Option>
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item {...formItemLayout} label="Type">
|
||||||
|
<Select value="Enterprise">
|
||||||
|
<Option value="Enterprise" selected>Enterprise</Option>
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item {...formItemLayout} label="Name">
|
||||||
|
<Input placeholder="App Name" />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item {...formItemLayout} label="Description">
|
||||||
|
<TextArea placeholder="Enter the description" rows={4} />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item {...formItemLayout} label="Category">
|
||||||
|
<Select placeholder="Select a category">
|
||||||
|
<Option value="travel">Travel</Option>
|
||||||
|
<Option value="entertainment">Entertainment</Option>
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item {...formItemLayout} label="Tags">
|
||||||
|
<EditableTagGroup/>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item {...formItemLayout} label="Price">
|
||||||
|
<Input prefix="$" placeholder="00.00" />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item {...formItemLayout} label="Share with all tenents?">
|
||||||
|
<Checkbox > </Checkbox>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item {...formItemLayout} label="Meta Daa">
|
||||||
|
<InputGroup>
|
||||||
|
<Row gutter={8}>
|
||||||
|
<Col span={5}>
|
||||||
|
<Input placeholder="Key" />
|
||||||
|
</Col>
|
||||||
|
<Col span={10}>
|
||||||
|
<Input placeholder="value" />
|
||||||
|
</Col>
|
||||||
|
<Col span={4}>
|
||||||
|
<Button type="dashed" shape="circle" icon="plus" />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</InputGroup>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Step1;
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
import React from "react"
|
||||||
|
|
||||||
|
class Step2 extends React.Component {
|
||||||
|
render() {
|
||||||
|
console.log("hhhoohh");
|
||||||
|
return (
|
||||||
|
<p>tttoooeeee</p>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Step2;
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
import React from "react"
|
||||||
|
|
||||||
|
class Step3 extends React.Component {
|
||||||
|
render() {
|
||||||
|
console.log("hhhoohh");
|
||||||
|
return (
|
||||||
|
<p>tttoooeeee</p>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Step3;
|
||||||
@ -0,0 +1,4 @@
|
|||||||
|
.stepForm {
|
||||||
|
max-width: 500px;
|
||||||
|
margin: 40px auto 0;
|
||||||
|
}
|
||||||
@ -0,0 +1,215 @@
|
|||||||
|
import React from "react";
|
||||||
|
import "antd/dist/antd.css";
|
||||||
|
import {Table, Divider, Tag, Card, PageHeader, Typography, Avatar,Input, Button, Icon, Row, Col} from "antd";
|
||||||
|
import Highlighter from 'react-highlight-words';
|
||||||
|
|
||||||
|
const Paragraph = Typography;
|
||||||
|
const Search = Input.Search;
|
||||||
|
|
||||||
|
const routes = [
|
||||||
|
{
|
||||||
|
path: 'index',
|
||||||
|
breadcrumbName: 'Publisher',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'first',
|
||||||
|
breadcrumbName: 'Dashboard',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'second',
|
||||||
|
breadcrumbName: 'Apps',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const data = [{
|
||||||
|
key: '1',
|
||||||
|
icon: 'https://gw.alipayobjects.com/zos/rmsportal/zOsKZmFRdUtvpqCImOVY.png',
|
||||||
|
name: 'John Brown',
|
||||||
|
platform: 'android',
|
||||||
|
type: 'Enterprise',
|
||||||
|
status: 'published',
|
||||||
|
version: '13.0.0.1',
|
||||||
|
updated_at: '27-03-2019 08:27'
|
||||||
|
},{
|
||||||
|
key: '2',
|
||||||
|
icon: 'http://aztechbeat.com/wp-content/uploads/2014/04/confide-app-icon.png',
|
||||||
|
name: 'Lorem Ipsum',
|
||||||
|
platform: 'ios',
|
||||||
|
type: 'Enterprise',
|
||||||
|
status: 'published',
|
||||||
|
version: '2.3.1.2',
|
||||||
|
updated_at: '27-03-2019 09:45'
|
||||||
|
},{
|
||||||
|
key: '3',
|
||||||
|
icon: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRx2Xx1-hnH16EGZHUlT06nOcfGODPoboA2TXKaBVtODto4lJtK',
|
||||||
|
name: 'Lorem Ipsum',
|
||||||
|
platform: 'ios',
|
||||||
|
type: 'Enterprise',
|
||||||
|
status: 'removed',
|
||||||
|
version: '4.1.1.0',
|
||||||
|
updated_at: '27-03-2019 09:46'
|
||||||
|
}];
|
||||||
|
|
||||||
|
class Apps extends React.Component {
|
||||||
|
routes;
|
||||||
|
|
||||||
|
|
||||||
|
state = {
|
||||||
|
searchText: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.routes = props.routes;
|
||||||
|
}
|
||||||
|
|
||||||
|
getColumnSearchProps = (dataIndex) => ({
|
||||||
|
filterDropdown: ({
|
||||||
|
setSelectedKeys, selectedKeys, confirm, clearFilters,
|
||||||
|
}) => (
|
||||||
|
<div style={{ padding: 8 }}>
|
||||||
|
<Input
|
||||||
|
ref={node => { this.searchInput = node; }}
|
||||||
|
placeholder={`Search ${dataIndex}`}
|
||||||
|
value={selectedKeys[0]}
|
||||||
|
onChange={e => setSelectedKeys(e.target.value ? [e.target.value] : [])}
|
||||||
|
onPressEnter={() => this.handleSearch(selectedKeys, confirm)}
|
||||||
|
style={{ width: 188, marginBottom: 8, display: 'block' }}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
onClick={() => this.handleSearch(selectedKeys, confirm)}
|
||||||
|
icon="search"
|
||||||
|
size="small"
|
||||||
|
style={{ width: 90, marginRight: 8 }}
|
||||||
|
>
|
||||||
|
Search
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => this.handleReset(clearFilters)}
|
||||||
|
size="small"
|
||||||
|
style={{ width: 90 }}
|
||||||
|
>
|
||||||
|
Reset
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
filterIcon: filtered => <Icon type="search" style={{ color: filtered ? '#1890ff' : undefined }} />,
|
||||||
|
onFilter: (value, record) => record[dataIndex].toString().toLowerCase().includes(value.toLowerCase()),
|
||||||
|
onFilterDropdownVisibleChange: (visible) => {
|
||||||
|
if (visible) {
|
||||||
|
setTimeout(() => this.searchInput.select());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
render: (text) => (
|
||||||
|
<Highlighter
|
||||||
|
highlightStyle={{ backgroundColor: '#ffc069', padding: 0 }}
|
||||||
|
searchWords={[this.state.searchText]}
|
||||||
|
autoEscape
|
||||||
|
textToHighlight={text.toString()}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
})
|
||||||
|
|
||||||
|
handleSearch = (selectedKeys, confirm) => {
|
||||||
|
confirm();
|
||||||
|
this.setState({ searchText: selectedKeys[0] });
|
||||||
|
}
|
||||||
|
|
||||||
|
handleReset = (clearFilters) => {
|
||||||
|
clearFilters();
|
||||||
|
this.setState({ searchText: '' });
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const columns = [{
|
||||||
|
title: '',
|
||||||
|
dataIndex: 'icon',
|
||||||
|
key: 'icon',
|
||||||
|
render: text => <Avatar size="large" src={text}/>,
|
||||||
|
}, {
|
||||||
|
title: 'Name',
|
||||||
|
dataIndex: 'name',
|
||||||
|
key: 'name',
|
||||||
|
render: text => <a href="javascript:;">{text}</a>,
|
||||||
|
...this.getColumnSearchProps('name'),
|
||||||
|
}, {
|
||||||
|
title: 'Platform',
|
||||||
|
dataIndex: 'platform',
|
||||||
|
key: 'platform',
|
||||||
|
}, {
|
||||||
|
title: 'Type',
|
||||||
|
dataIndex: 'type',
|
||||||
|
key: 'type',
|
||||||
|
}, {
|
||||||
|
title: 'Status',
|
||||||
|
key: 'status',
|
||||||
|
dataIndex: 'status',
|
||||||
|
render: tag => {
|
||||||
|
let color;
|
||||||
|
switch (tag) {
|
||||||
|
case 'published':
|
||||||
|
color = 'green';
|
||||||
|
break;
|
||||||
|
case 'removed':
|
||||||
|
color = 'red'
|
||||||
|
break;
|
||||||
|
case 'default':
|
||||||
|
color = 'blue'
|
||||||
|
}
|
||||||
|
return <Tag color={color} key={tag}>{tag.toUpperCase()}</Tag>;
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
title: 'Published Version',
|
||||||
|
dataIndex: 'version',
|
||||||
|
key: 'version',
|
||||||
|
}, {
|
||||||
|
title: 'Last Updated',
|
||||||
|
dataIndex: 'updated_at',
|
||||||
|
key: 'updated_at',
|
||||||
|
},{
|
||||||
|
title: 'Action',
|
||||||
|
key: 'action',
|
||||||
|
render: () => (
|
||||||
|
<span>
|
||||||
|
<a href="javascript:;">Edit</a>
|
||||||
|
<Divider type="vertical" />
|
||||||
|
<a href="javascript:;">Manage</a>
|
||||||
|
</span>
|
||||||
|
),
|
||||||
|
}];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<PageHeader
|
||||||
|
breadcrumb={{routes}}
|
||||||
|
/>
|
||||||
|
<div style={{background: '#f0f2f5', padding: 24, minHeight: 780}}>
|
||||||
|
|
||||||
|
<Card>
|
||||||
|
<Row style={{padding:10}}>
|
||||||
|
<Col span={6} offset={18}>
|
||||||
|
<Search
|
||||||
|
placeholder="search"
|
||||||
|
onSearch={value => console.log(value)}
|
||||||
|
style={{ width: 200}}
|
||||||
|
/>
|
||||||
|
<Button style={{margin:5}}>Advanced Search</Button>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Table columns={columns} dataSource={data}/>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Apps;
|
||||||
@ -21,6 +21,9 @@ const MiniCssExtractPlugin = require("mini-css-extract-plugin");
|
|||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
devtool: "source-map",
|
devtool: "source-map",
|
||||||
|
output: {
|
||||||
|
publicPath: '/publisher/' // <---- this
|
||||||
|
},
|
||||||
watch: false,
|
watch: false,
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
|
|||||||
@ -80,9 +80,13 @@ public class PermissionUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static boolean putPermission(Permission permission) throws PermissionManagementException {
|
public static boolean putPermission(Permission permission) throws PermissionManagementException {
|
||||||
|
return putPermission(permission.getPath());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean putPermission(String permissionPath) throws PermissionManagementException {
|
||||||
boolean status;
|
boolean status;
|
||||||
try {
|
try {
|
||||||
StringTokenizer tokenizer = new StringTokenizer(permission.getPath(), "/");
|
StringTokenizer tokenizer = new StringTokenizer(permissionPath, "/");
|
||||||
String lastToken = "", currentToken, tempPath;
|
String lastToken = "", currentToken, tempPath;
|
||||||
while (tokenizer.hasMoreTokens()) {
|
while (tokenizer.hasMoreTokens()) {
|
||||||
currentToken = tokenizer.nextToken();
|
currentToken = tokenizer.nextToken();
|
||||||
@ -95,7 +99,7 @@ public class PermissionUtils {
|
|||||||
status = true;
|
status = true;
|
||||||
} catch (RegistryException e) {
|
} catch (RegistryException e) {
|
||||||
throw new PermissionManagementException("Error occurred while persisting permission : " +
|
throw new PermissionManagementException("Error occurred while persisting permission : " +
|
||||||
permission.getName(), e);
|
permissionPath, e);
|
||||||
}
|
}
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -29,7 +29,7 @@
|
|||||||
<ClassName>org.wso2.carbon.device.application.mgt.core.impl.ReviewManagerImpl</ClassName>
|
<ClassName>org.wso2.carbon.device.application.mgt.core.impl.ReviewManagerImpl</ClassName>
|
||||||
</Extension>
|
</Extension>
|
||||||
<Extension name="LifecycleStateManager">
|
<Extension name="LifecycleStateManager">
|
||||||
<ClassName>org.wso2.carbon.device.application.mgt.core.lifecycle.LifecycleStateManger</ClassName>
|
<ClassName>org.wso2.carbon.device.application.mgt.core.lifecycle.LifecycleStateManager</ClassName>
|
||||||
</Extension>
|
</Extension>
|
||||||
<Extension name="SubscriptionManager">
|
<Extension name="SubscriptionManager">
|
||||||
<ClassName>org.wso2.carbon.device.application.mgt.core.impl.SubscriptionManagerImpl</ClassName>
|
<ClassName>org.wso2.carbon.device.application.mgt.core.impl.SubscriptionManagerImpl</ClassName>
|
||||||
@ -56,46 +56,78 @@
|
|||||||
If there is a requirement to introduce a new state to the lifecycle, please refer above
|
If there is a requirement to introduce a new state to the lifecycle, please refer above
|
||||||
diagram and add relevant state to the below configuration appropriately.
|
diagram and add relevant state to the below configuration appropriately.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
|
<!-- a lifecyclestate can have following properties
|
||||||
|
<LifecycleState name="In-Review">
|
||||||
|
<IsAppInstallable>false</IsAppInstallable>
|
||||||
|
<IsAppUpdatable>true</IsAppUpdatable>
|
||||||
|
<IsInitialState>false</IsInitialState>
|
||||||
|
<IsEndState>false</IsEndState>
|
||||||
|
<Permission>
|
||||||
|
/device-mgt/applications/life-cycle/in-review
|
||||||
|
</Permission>
|
||||||
|
<ProceedingStates>
|
||||||
|
<State>Rejected</State>
|
||||||
|
<State>Approved</State>
|
||||||
|
</ProceedingStates>
|
||||||
|
</LifecycleState>
|
||||||
|
-->
|
||||||
<LifecycleStates>
|
<LifecycleStates>
|
||||||
<LifecycleState name="Created">
|
<LifecycleState name="Created">
|
||||||
|
<IsAppUpdatable>true</IsAppUpdatable>
|
||||||
|
<IsInitialState>true</IsInitialState>
|
||||||
|
<Permission>/device-mgt/applications/life-cycle/create</Permission>
|
||||||
<ProceedingStates>
|
<ProceedingStates>
|
||||||
<State>In-Review</State>
|
<State>In-Review</State>
|
||||||
</ProceedingStates>
|
</ProceedingStates>
|
||||||
</LifecycleState>
|
</LifecycleState>
|
||||||
<LifecycleState name="In-Review">
|
<LifecycleState name="In-Review">
|
||||||
|
<Permission>/device-mgt/applications/life-cycle/in-review</Permission>
|
||||||
<ProceedingStates>
|
<ProceedingStates>
|
||||||
<State>Rejected</State>
|
<State>Rejected</State>
|
||||||
<State>Approved</State>
|
<State>Approved</State>
|
||||||
</ProceedingStates>
|
</ProceedingStates>
|
||||||
</LifecycleState>
|
</LifecycleState>
|
||||||
<LifecycleState name="Approved">
|
<LifecycleState name="Approved">
|
||||||
|
<Permission>/device-mgt/applications/life-cycle/approve</Permission>
|
||||||
<ProceedingStates>
|
<ProceedingStates>
|
||||||
<State>Published</State>
|
<State>Published</State>
|
||||||
|
<State>Created</State>
|
||||||
</ProceedingStates>
|
</ProceedingStates>
|
||||||
</LifecycleState>
|
</LifecycleState>
|
||||||
<LifecycleState name="Rejected">
|
<LifecycleState name="Rejected">
|
||||||
|
<Permission>/device-mgt/applications/life-cycle/reject</Permission>
|
||||||
<ProceedingStates>
|
<ProceedingStates>
|
||||||
<State>In-Review</State>
|
<State>Created</State>
|
||||||
<State>Removed</State>
|
<State>Removed</State>
|
||||||
</ProceedingStates>
|
</ProceedingStates>
|
||||||
</LifecycleState>
|
</LifecycleState>
|
||||||
<LifecycleState name="Published">
|
<LifecycleState name="Published">
|
||||||
|
<IsAppInstallable>true</IsAppInstallable>
|
||||||
|
<Permission>/device-mgt/applications/life-cycle/publish</Permission>
|
||||||
<ProceedingStates>
|
<ProceedingStates>
|
||||||
<State>Unpublished</State>
|
<State>Unpublished</State>
|
||||||
<State>Deprecated</State>
|
<State>Deprecated</State>
|
||||||
</ProceedingStates>
|
</ProceedingStates>
|
||||||
</LifecycleState>
|
</LifecycleState>
|
||||||
<LifecycleState name="Unpublished">
|
<LifecycleState name="Unpublished">
|
||||||
|
<Permission>/device-mgt/applications/life-cycle/unpublish</Permission>
|
||||||
<ProceedingStates>
|
<ProceedingStates>
|
||||||
|
<State>Published</State>
|
||||||
|
<State>In-Review</State>
|
||||||
<State>Removed</State>
|
<State>Removed</State>
|
||||||
</ProceedingStates>
|
</ProceedingStates>
|
||||||
</LifecycleState>
|
</LifecycleState>
|
||||||
<LifecycleState name="Deprecated">
|
<LifecycleState name="Deprecated">
|
||||||
|
<Permission>/device-mgt/applications/life-cycle/deprecate</Permission>
|
||||||
<ProceedingStates>
|
<ProceedingStates>
|
||||||
<State>Removed</State>
|
<State>Removed</State>
|
||||||
|
<State>In-Review</State>
|
||||||
</ProceedingStates>
|
</ProceedingStates>
|
||||||
</LifecycleState>
|
</LifecycleState>
|
||||||
<LifecycleState name="Removed">
|
<LifecycleState name="Removed">
|
||||||
|
<IsEndState>true</IsEndState>
|
||||||
|
<Permission>/device-mgt/applications/life-cycle/remove</Permission>
|
||||||
</LifecycleState>
|
</LifecycleState>
|
||||||
</LifecycleStates>
|
</LifecycleStates>
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user