mirror of https://github.com/apache/cloudstack.git
Merge remote-tracking branch 'origin/4.15'
Signed-off-by: Rohit Yadav <rohit.yadav@shapeblue.com>
This commit is contained in:
commit
cb167072a1
|
|
@ -374,6 +374,7 @@ public class EventTypes {
|
|||
// Primary storage pool
|
||||
public static final String EVENT_ENABLE_PRIMARY_STORAGE = "ENABLE.PS";
|
||||
public static final String EVENT_DISABLE_PRIMARY_STORAGE = "DISABLE.PS";
|
||||
public static final String EVENT_SYNC_STORAGE_POOL = "SYNC.STORAGE.POOL";
|
||||
|
||||
// VPN
|
||||
public static final String EVENT_REMOTE_ACCESS_VPN_CREATE = "VPN.REMOTE.ACCESS.CREATE";
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import org.apache.cloudstack.api.command.admin.storage.DeleteImageStoreCmd;
|
|||
import org.apache.cloudstack.api.command.admin.storage.DeletePoolCmd;
|
||||
import org.apache.cloudstack.api.command.admin.storage.DeleteSecondaryStagingStoreCmd;
|
||||
import org.apache.cloudstack.api.command.admin.storage.UpdateStoragePoolCmd;
|
||||
import org.apache.cloudstack.api.command.admin.storage.SyncStoragePoolCmd;
|
||||
|
||||
import com.cloud.exception.DiscoveryException;
|
||||
import com.cloud.exception.InsufficientCapacityException;
|
||||
|
|
@ -104,4 +105,6 @@ public interface StorageService {
|
|||
|
||||
ImageStore updateImageStoreStatus(Long id, Boolean readonly);
|
||||
|
||||
StoragePool syncStoragePool(SyncStoragePoolCmd cmd);
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,97 @@
|
|||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF 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.apache.cloudstack.api.command.admin.storage;
|
||||
|
||||
import com.cloud.event.EventTypes;
|
||||
import com.cloud.exception.ResourceUnavailableException;
|
||||
import com.cloud.exception.InsufficientCapacityException;
|
||||
import com.cloud.exception.ConcurrentOperationException;
|
||||
import com.cloud.exception.ResourceAllocationException;
|
||||
import com.cloud.exception.NetworkRuleConflictException;
|
||||
import com.cloud.storage.StoragePool;
|
||||
import org.apache.cloudstack.acl.RoleType;
|
||||
import org.apache.cloudstack.api.APICommand;
|
||||
import org.apache.cloudstack.api.ApiConstants;
|
||||
import org.apache.cloudstack.api.BaseAsyncCmd;
|
||||
import org.apache.cloudstack.api.Parameter;
|
||||
import org.apache.cloudstack.api.ServerApiException;
|
||||
import org.apache.cloudstack.api.response.StoragePoolResponse;
|
||||
import org.apache.cloudstack.api.ApiErrorCode;
|
||||
import org.apache.cloudstack.context.CallContext;
|
||||
|
||||
import java.util.logging.Logger;
|
||||
|
||||
@APICommand(name = SyncStoragePoolCmd.APINAME,
|
||||
description = "Sync storage pool with management server (currently supported for Datastore Cluster in VMware and syncs the datastores in it)",
|
||||
responseObject = StoragePoolResponse.class,
|
||||
requestHasSensitiveInfo = false,
|
||||
responseHasSensitiveInfo = false,
|
||||
since = "4.15.1",
|
||||
authorized = {RoleType.Admin}
|
||||
)
|
||||
public class SyncStoragePoolCmd extends BaseAsyncCmd {
|
||||
|
||||
public static final String APINAME = "syncStoragePool";
|
||||
public static final Logger LOGGER = Logger.getLogger(SyncStoragePoolCmd.class.getName());
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
//////////////// API parameters /////////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
|
||||
@Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = StoragePoolResponse.class, required = true, description = "Storage pool id")
|
||||
private Long poolId;
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
/////////////////// Accessors ///////////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
|
||||
public Long getPoolId() {
|
||||
return poolId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEventType() {
|
||||
return EventTypes.EVENT_SYNC_STORAGE_POOL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEventDescription() {
|
||||
return "Attempting to synchronise storage pool with management server";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException {
|
||||
StoragePool result = _storageService.syncStoragePool(this);
|
||||
if (result != null) {
|
||||
StoragePoolResponse response = _responseGenerator.createStoragePoolResponse(result);
|
||||
response.setResponseName("storagepool");
|
||||
this.setResponseObject(response);
|
||||
} else {
|
||||
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to synchronise storage pool");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCommandName() {
|
||||
return APINAME.toLowerCase() + BaseAsyncCmd.RESPONSE_SUFFIX;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getEntityOwnerId() {
|
||||
return CallContext.current().getCallingAccountId();
|
||||
}
|
||||
}
|
||||
|
|
@ -30,6 +30,7 @@ import org.apache.cloudstack.storage.command.ForgetObjectCmd;
|
|||
import org.apache.cloudstack.storage.command.IntroduceObjectCmd;
|
||||
import org.apache.cloudstack.storage.command.ResignatureCommand;
|
||||
import org.apache.cloudstack.storage.command.SnapshotAndCopyCommand;
|
||||
import org.apache.cloudstack.storage.command.SyncVolumePathCommand;
|
||||
|
||||
import com.cloud.agent.api.Answer;
|
||||
|
||||
|
|
@ -81,5 +82,7 @@ public interface StorageProcessor {
|
|||
|
||||
Answer copyVolumeFromPrimaryToPrimary(CopyCommand cmd);
|
||||
|
||||
public Answer CheckDataStoreStoragePolicyComplaince(CheckDataStoreStoragePolicyComplainceCommand cmd);
|
||||
public Answer checkDataStoreStoragePolicyCompliance(CheckDataStoreStoragePolicyComplainceCommand cmd);
|
||||
|
||||
public Answer syncVolumePath(SyncVolumePathCommand cmd);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ import org.apache.cloudstack.storage.command.IntroduceObjectCmd;
|
|||
import org.apache.cloudstack.storage.command.ResignatureCommand;
|
||||
import org.apache.cloudstack.storage.command.SnapshotAndCopyCommand;
|
||||
import org.apache.cloudstack.storage.command.StorageSubSystemCommand;
|
||||
import org.apache.cloudstack.storage.command.SyncVolumePathCommand;
|
||||
|
||||
import com.cloud.agent.api.Answer;
|
||||
import com.cloud.agent.api.Command;
|
||||
|
|
@ -73,7 +74,9 @@ public class StorageSubsystemCommandHandlerBase implements StorageSubsystemComma
|
|||
} else if (command instanceof DirectDownloadCommand) {
|
||||
return processor.handleDownloadTemplateToPrimaryStorage((DirectDownloadCommand) command);
|
||||
} else if (command instanceof CheckDataStoreStoragePolicyComplainceCommand) {
|
||||
return processor.CheckDataStoreStoragePolicyComplaince((CheckDataStoreStoragePolicyComplainceCommand) command);
|
||||
return processor.checkDataStoreStoragePolicyCompliance((CheckDataStoreStoragePolicyComplainceCommand) command);
|
||||
} else if (command instanceof SyncVolumePathCommand) {
|
||||
return processor.syncVolumePath((SyncVolumePathCommand) command);
|
||||
}
|
||||
|
||||
return new Answer((Command)command, false, "not implemented yet");
|
||||
|
|
|
|||
|
|
@ -0,0 +1,49 @@
|
|||
//
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF 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.apache.cloudstack.storage.command;
|
||||
|
||||
import com.cloud.agent.api.Answer;
|
||||
import com.cloud.agent.api.to.DiskTO;
|
||||
|
||||
public class SyncVolumePathAnswer extends Answer {
|
||||
private DiskTO disk;
|
||||
|
||||
public SyncVolumePathAnswer() {
|
||||
super(null);
|
||||
}
|
||||
|
||||
public SyncVolumePathAnswer(DiskTO disk) {
|
||||
super(null);
|
||||
setDisk(disk);
|
||||
}
|
||||
|
||||
public SyncVolumePathAnswer(String errMsg) {
|
||||
super(null, false, errMsg);
|
||||
}
|
||||
|
||||
public DiskTO getDisk() {
|
||||
return disk;
|
||||
}
|
||||
|
||||
public void setDisk(DiskTO disk) {
|
||||
this.disk = disk;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
//
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF 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.apache.cloudstack.storage.command;
|
||||
|
||||
import com.cloud.agent.api.to.DiskTO;
|
||||
|
||||
public class SyncVolumePathCommand extends StorageSubSystemCommand {
|
||||
|
||||
private DiskTO disk;
|
||||
|
||||
public SyncVolumePathCommand(final DiskTO disk) {
|
||||
super();
|
||||
this.disk = disk;
|
||||
}
|
||||
|
||||
public DiskTO getDisk() {
|
||||
return disk;
|
||||
}
|
||||
|
||||
public void setDisk(final DiskTO disk) {
|
||||
this.disk = disk;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean executeInSequence() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setExecuteInSequence(boolean inSeq) {
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -19,6 +19,7 @@ package com.cloud.storage;
|
|||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
import com.cloud.agent.api.ModifyStoragePoolAnswer;
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.HypervisorHostListener;
|
||||
import org.apache.cloudstack.framework.config.ConfigKey;
|
||||
|
|
@ -272,4 +273,6 @@ public interface StorageManager extends StorageService {
|
|||
|
||||
boolean isStoragePoolDatastoreClusterParent(StoragePool pool);
|
||||
|
||||
void syncDatastoreClusterStoragePool(long datastoreClusterPoolId, List<ModifyStoragePoolAnswer> childDatastoreAnswerList, long hostId);
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ import java.util.ArrayList;
|
|||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
||||
import org.apache.log4j.Logger;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
|
|
|
|||
|
|
@ -130,4 +130,6 @@ public interface PrimaryDataStoreDao extends GenericDao<StoragePoolVO, Long> {
|
|||
|
||||
Integer countAll();
|
||||
|
||||
List<StoragePoolVO> findPoolsByStorageType(String storageType);
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -93,6 +93,7 @@ public class PrimaryDataStoreDaoImpl extends GenericDaoBase<StoragePoolVO, Long>
|
|||
AllFieldSearch.and("podId", AllFieldSearch.entity().getPodId(), Op.EQ);
|
||||
AllFieldSearch.and("clusterId", AllFieldSearch.entity().getClusterId(), Op.EQ);
|
||||
AllFieldSearch.and("storage_provider_name", AllFieldSearch.entity().getStorageProviderName(), Op.EQ);
|
||||
AllFieldSearch.and("poolType", AllFieldSearch.entity().getPoolType(), Op.EQ);
|
||||
AllFieldSearch.done();
|
||||
|
||||
DcPodSearch = createSearchBuilder();
|
||||
|
|
@ -581,4 +582,11 @@ public class PrimaryDataStoreDaoImpl extends GenericDaoBase<StoragePoolVO, Long>
|
|||
sc.setParameters("status", StoragePoolStatus.Up);
|
||||
return listBy(sc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<StoragePoolVO> findPoolsByStorageType(String storageType) {
|
||||
SearchCriteria<StoragePoolVO> sc = AllFieldSearch.create();
|
||||
sc.setParameters("poolType", storageType);
|
||||
return listBy(sc);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,16 +22,14 @@ import com.cloud.agent.AgentManager;
|
|||
import com.cloud.agent.api.Answer;
|
||||
import com.cloud.agent.api.ModifyStoragePoolAnswer;
|
||||
import com.cloud.agent.api.ModifyStoragePoolCommand;
|
||||
import com.cloud.agent.api.StoragePoolInfo;
|
||||
import com.cloud.alert.AlertManager;
|
||||
import com.cloud.exception.StorageConflictException;
|
||||
import com.cloud.storage.DataStoreRole;
|
||||
import com.cloud.storage.Storage;
|
||||
import com.cloud.storage.StoragePool;
|
||||
import com.cloud.storage.StoragePoolHostVO;
|
||||
import com.cloud.storage.StoragePoolStatus;
|
||||
import com.cloud.storage.dao.StoragePoolHostDao;
|
||||
import com.cloud.storage.dao.StoragePoolTagsDao;
|
||||
import com.cloud.storage.StorageManager;
|
||||
import com.cloud.utils.exception.CloudRuntimeException;
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.HypervisorHostListener;
|
||||
|
|
@ -43,9 +41,7 @@ import org.apache.commons.lang.StringUtils;
|
|||
import org.apache.log4j.Logger;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class DefaultHostListener implements HypervisorHostListener {
|
||||
private static final Logger s_logger = Logger.getLogger(DefaultHostListener.class);
|
||||
|
|
@ -62,7 +58,7 @@ public class DefaultHostListener implements HypervisorHostListener {
|
|||
@Inject
|
||||
StoragePoolDetailsDao storagePoolDetailsDao;
|
||||
@Inject
|
||||
StoragePoolTagsDao storagePoolTagsDao;
|
||||
StorageManager storageManager;
|
||||
|
||||
@Override
|
||||
public boolean hostAdded(long hostId) {
|
||||
|
|
@ -104,43 +100,7 @@ public class DefaultHostListener implements HypervisorHostListener {
|
|||
updateStoragePoolHostVOAndDetails(poolVO, hostId, mspAnswer);
|
||||
|
||||
if (pool.getPoolType() == Storage.StoragePoolType.DatastoreCluster) {
|
||||
for (ModifyStoragePoolAnswer childDataStoreAnswer : ((ModifyStoragePoolAnswer) answer).getDatastoreClusterChildren()) {
|
||||
StoragePoolInfo childStoragePoolInfo = childDataStoreAnswer.getPoolInfo();
|
||||
StoragePoolVO dataStoreVO = primaryStoreDao.findPoolByUUID(childStoragePoolInfo.getUuid());
|
||||
if (dataStoreVO != null) {
|
||||
continue;
|
||||
}
|
||||
dataStoreVO = new StoragePoolVO();
|
||||
dataStoreVO.setStorageProviderName(poolVO.getStorageProviderName());
|
||||
dataStoreVO.setHostAddress(childStoragePoolInfo.getHost());
|
||||
dataStoreVO.setPoolType(Storage.StoragePoolType.PreSetup);
|
||||
dataStoreVO.setPath(childStoragePoolInfo.getHostPath());
|
||||
dataStoreVO.setPort(poolVO.getPort());
|
||||
dataStoreVO.setName(childStoragePoolInfo.getName());
|
||||
dataStoreVO.setUuid(childStoragePoolInfo.getUuid());
|
||||
dataStoreVO.setDataCenterId(poolVO.getDataCenterId());
|
||||
dataStoreVO.setPodId(poolVO.getPodId());
|
||||
dataStoreVO.setClusterId(poolVO.getClusterId());
|
||||
dataStoreVO.setStatus(StoragePoolStatus.Up);
|
||||
dataStoreVO.setUserInfo(poolVO.getUserInfo());
|
||||
dataStoreVO.setManaged(poolVO.isManaged());
|
||||
dataStoreVO.setCapacityIops(poolVO.getCapacityIops());
|
||||
dataStoreVO.setCapacityBytes(childDataStoreAnswer.getPoolInfo().getCapacityBytes());
|
||||
dataStoreVO.setUsedBytes(childDataStoreAnswer.getPoolInfo().getCapacityBytes() - childDataStoreAnswer.getPoolInfo().getAvailableBytes());
|
||||
dataStoreVO.setHypervisor(poolVO.getHypervisor());
|
||||
dataStoreVO.setScope(poolVO.getScope());
|
||||
dataStoreVO.setParent(poolVO.getId());
|
||||
|
||||
Map<String, String> details = new HashMap<>();
|
||||
if(StringUtils.isNotEmpty(childDataStoreAnswer.getPoolType())) {
|
||||
details.put("pool_type", childDataStoreAnswer.getPoolType());
|
||||
}
|
||||
|
||||
List<String> storageTags = storagePoolTagsDao.getStoragePoolTags(poolId);
|
||||
primaryStoreDao.persist(dataStoreVO, details, storageTags);
|
||||
|
||||
updateStoragePoolHostVOAndDetails(dataStoreVO, hostId, childDataStoreAnswer);
|
||||
}
|
||||
storageManager.syncDatastoreClusterStoragePool(poolId, ((ModifyStoragePoolAnswer) answer).getDatastoreClusterChildren(), hostId);
|
||||
}
|
||||
|
||||
s_logger.info("Connection established between storage pool " + pool + " and host " + hostId);
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ import org.apache.cloudstack.storage.command.ResignatureAnswer;
|
|||
import org.apache.cloudstack.storage.command.ResignatureCommand;
|
||||
import org.apache.cloudstack.storage.command.SnapshotAndCopyAnswer;
|
||||
import org.apache.cloudstack.storage.command.SnapshotAndCopyCommand;
|
||||
import org.apache.cloudstack.storage.command.SyncVolumePathCommand;
|
||||
import org.apache.cloudstack.storage.to.PrimaryDataStoreTO;
|
||||
import org.apache.cloudstack.storage.to.SnapshotObjectTO;
|
||||
import org.apache.cloudstack.storage.to.TemplateObjectTO;
|
||||
|
|
@ -1922,8 +1923,14 @@ public class KVMStorageProcessor implements StorageProcessor {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Answer CheckDataStoreStoragePolicyComplaince(CheckDataStoreStoragePolicyComplainceCommand cmd) {
|
||||
public Answer checkDataStoreStoragePolicyCompliance(CheckDataStoreStoragePolicyComplainceCommand cmd) {
|
||||
s_logger.info("'CheckDataStoreStoragePolicyComplainceCommand' not currently applicable for KVMStorageProcessor");
|
||||
return new Answer(cmd,false,"Not currently applicable for KVMStorageProcessor");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Answer syncVolumePath(SyncVolumePathCommand cmd) {
|
||||
s_logger.info("SyncVolumePathCommand not currently applicable for KVMStorageProcessor");
|
||||
return new Answer(cmd, false, "Not currently applicable for KVMStorageProcessor");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,8 +35,9 @@ import org.apache.cloudstack.storage.command.ForgetObjectCmd;
|
|||
import org.apache.cloudstack.storage.command.IntroduceObjectCmd;
|
||||
import org.apache.cloudstack.storage.command.ResignatureAnswer;
|
||||
import org.apache.cloudstack.storage.command.ResignatureCommand;
|
||||
import org.apache.cloudstack.storage.command.SnapshotAndCopyAnswer;
|
||||
import org.apache.cloudstack.storage.command.SnapshotAndCopyCommand;
|
||||
import org.apache.cloudstack.storage.command.SnapshotAndCopyAnswer;
|
||||
import org.apache.cloudstack.storage.command.SyncVolumePathCommand;
|
||||
import org.apache.cloudstack.storage.to.SnapshotObjectTO;
|
||||
import org.apache.cloudstack.storage.to.TemplateObjectTO;
|
||||
import org.apache.cloudstack.storage.to.VolumeObjectTO;
|
||||
|
|
@ -828,11 +829,17 @@ public class Ovm3StorageProcessor implements StorageProcessor {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Answer CheckDataStoreStoragePolicyComplaince(CheckDataStoreStoragePolicyComplainceCommand cmd) {
|
||||
public Answer checkDataStoreStoragePolicyCompliance(CheckDataStoreStoragePolicyComplainceCommand cmd) {
|
||||
LOGGER.info("'CheckDataStoreStoragePolicyComplainceCommand' not applicable used for Ovm3StorageProcessor");
|
||||
return new Answer(cmd,false,"Not applicable used for Ovm3StorageProcessor");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Answer syncVolumePath(SyncVolumePathCommand cmd) {
|
||||
LOGGER.info("SyncVolumePathCommand not currently applicable for Ovm3StorageProcessor");
|
||||
return new Answer(cmd, false, "Not currently applicable for Ovm3StorageProcessor");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Answer copyVolumeFromPrimaryToPrimary(CopyCommand cmd) {
|
||||
return null;
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ import org.apache.cloudstack.storage.command.ResignatureAnswer;
|
|||
import org.apache.cloudstack.storage.command.ResignatureCommand;
|
||||
import org.apache.cloudstack.storage.command.SnapshotAndCopyAnswer;
|
||||
import org.apache.cloudstack.storage.command.SnapshotAndCopyCommand;
|
||||
import org.apache.cloudstack.storage.command.SyncVolumePathCommand;
|
||||
import org.apache.cloudstack.storage.to.SnapshotObjectTO;
|
||||
import org.apache.cloudstack.storage.to.TemplateObjectTO;
|
||||
import org.apache.cloudstack.storage.to.VolumeObjectTO;
|
||||
|
|
@ -272,7 +273,12 @@ public class SimulatorStorageProcessor implements StorageProcessor {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Answer CheckDataStoreStoragePolicyComplaince(CheckDataStoreStoragePolicyComplainceCommand cmd) {
|
||||
public Answer checkDataStoreStoragePolicyCompliance(CheckDataStoreStoragePolicyComplainceCommand cmd) {
|
||||
return new Answer(cmd, true, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Answer syncVolumePath(SyncVolumePathCommand cmd) {
|
||||
return new Answer(cmd, true, null);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5037,10 +5037,12 @@ public class VmwareResource implements StoragePoolResource, ServerResource, Vmwa
|
|||
String datacenterName = datastoreClusterPath.substring(0, pathstartPosition+1);
|
||||
String childPath = datacenterName + summary.getName();
|
||||
poolInfo.setHostPath(childPath);
|
||||
String uuid = UUID.nameUUIDFromBytes(((pool.getHost() + childPath)).getBytes()).toString();
|
||||
String uuid = childDsMo.getCustomFieldValue(CustomFieldConstants.CLOUD_UUID);
|
||||
if (uuid == null) {
|
||||
uuid = UUID.nameUUIDFromBytes(((pool.getHost() + childPath)).getBytes()).toString();
|
||||
}
|
||||
poolInfo.setUuid(uuid);
|
||||
poolInfo.setLocalPath(cmd.LOCAL_PATH_PREFIX + File.separator + uuid);
|
||||
|
||||
answer.setPoolInfo(poolInfo);
|
||||
answer.setPoolType(summary.getType());
|
||||
answer.setLocalDatastoreName(morDatastore.getValue());
|
||||
|
|
|
|||
|
|
@ -50,6 +50,8 @@ import org.apache.cloudstack.storage.command.ResignatureAnswer;
|
|||
import org.apache.cloudstack.storage.command.ResignatureCommand;
|
||||
import org.apache.cloudstack.storage.command.SnapshotAndCopyAnswer;
|
||||
import org.apache.cloudstack.storage.command.SnapshotAndCopyCommand;
|
||||
import org.apache.cloudstack.storage.command.SyncVolumePathCommand;
|
||||
import org.apache.cloudstack.storage.command.SyncVolumePathAnswer;
|
||||
import org.apache.cloudstack.storage.to.PrimaryDataStoreTO;
|
||||
import org.apache.cloudstack.storage.to.SnapshotObjectTO;
|
||||
import org.apache.cloudstack.storage.to.TemplateObjectTO;
|
||||
|
|
@ -3892,7 +3894,7 @@ public class VmwareStorageProcessor implements StorageProcessor {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Answer CheckDataStoreStoragePolicyComplaince(CheckDataStoreStoragePolicyComplainceCommand cmd) {
|
||||
public Answer checkDataStoreStoragePolicyCompliance(CheckDataStoreStoragePolicyComplainceCommand cmd) {
|
||||
String primaryStorageNameLabel = cmd.getStoragePool().getUuid();
|
||||
String storagePolicyId = cmd.getStoragePolicyId();
|
||||
VmwareContext context = hostService.getServiceContext(cmd);
|
||||
|
|
@ -3962,4 +3964,81 @@ public class VmwareStorageProcessor implements StorageProcessor {
|
|||
throw new CloudRuntimeException(msg, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Answer syncVolumePath(SyncVolumePathCommand cmd) {
|
||||
DiskTO disk = cmd.getDisk();
|
||||
VolumeObjectTO volumeTO = (VolumeObjectTO)disk.getData();
|
||||
DataStoreTO primaryStore = volumeTO.getDataStore();
|
||||
String volumePath = volumeTO.getPath();
|
||||
String vmName = volumeTO.getVmName();
|
||||
|
||||
boolean datastoreChangeObserved = false;
|
||||
boolean volumePathChangeObserved = false;
|
||||
String chainInfo = null;
|
||||
try {
|
||||
VmwareContext context = hostService.getServiceContext(null);
|
||||
VmwareHypervisorHost hyperHost = hostService.getHyperHost(context, null);
|
||||
VirtualMachineMO vmMo = hyperHost.findVmOnHyperHost(vmName);
|
||||
if (vmMo == null) {
|
||||
vmMo = hyperHost.findVmOnPeerHyperHost(vmName);
|
||||
if (vmMo == null) {
|
||||
String msg = "Unable to find the VM to execute SyncVolumePathCommand, vmName: " + vmName;
|
||||
s_logger.error(msg);
|
||||
throw new Exception(msg);
|
||||
}
|
||||
}
|
||||
|
||||
String datastoreUUID = primaryStore.getUuid();
|
||||
if (disk.getDetails().get(DiskTO.PROTOCOL_TYPE) != null && disk.getDetails().get(DiskTO.PROTOCOL_TYPE).equalsIgnoreCase("DatastoreCluster")) {
|
||||
VirtualMachineDiskInfo matchingExistingDisk = getMatchingExistingDisk(hyperHost, context, vmMo, disk);
|
||||
VirtualMachineDiskInfoBuilder diskInfoBuilder = vmMo.getDiskInfoBuilder();
|
||||
if (diskInfoBuilder != null && matchingExistingDisk != null) {
|
||||
String[] diskChain = matchingExistingDisk.getDiskChain();
|
||||
assert (diskChain.length > 0);
|
||||
DatastoreFile file = new DatastoreFile(diskChain[0]);
|
||||
if (!file.getFileBaseName().equalsIgnoreCase(volumePath)) {
|
||||
if (s_logger.isInfoEnabled())
|
||||
s_logger.info("Detected disk-chain top file change on volume: " + volumeTO.getId() + " " + volumePath + " -> " + file.getFileBaseName());
|
||||
volumePathChangeObserved = true;
|
||||
volumePath = file.getFileBaseName();
|
||||
volumeTO.setPath(volumePath);
|
||||
chainInfo = _gson.toJson(matchingExistingDisk);
|
||||
}
|
||||
|
||||
DatastoreMO diskDatastoreMofromVM = getDiskDatastoreMofromVM(hyperHost, context, vmMo, disk, diskInfoBuilder);
|
||||
if (diskDatastoreMofromVM != null) {
|
||||
String actualPoolUuid = diskDatastoreMofromVM.getCustomFieldValue(CustomFieldConstants.CLOUD_UUID);
|
||||
if (!actualPoolUuid.equalsIgnoreCase(primaryStore.getUuid())) {
|
||||
s_logger.warn(String.format("Volume %s found to be in a different storage pool %s", volumePath, actualPoolUuid));
|
||||
datastoreChangeObserved = true;
|
||||
datastoreUUID = actualPoolUuid;
|
||||
chainInfo = _gson.toJson(matchingExistingDisk);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SyncVolumePathAnswer answer = new SyncVolumePathAnswer(disk);
|
||||
if (datastoreChangeObserved) {
|
||||
answer.setContextParam("datastoreName", datastoreUUID);
|
||||
}
|
||||
if (volumePathChangeObserved) {
|
||||
answer.setContextParam("volumePath", volumePath);
|
||||
}
|
||||
if (chainInfo != null && !chainInfo.isEmpty()) {
|
||||
answer.setContextParam("chainInfo", chainInfo);
|
||||
}
|
||||
|
||||
return answer;
|
||||
} catch (Throwable e) {
|
||||
if (e instanceof RemoteException) {
|
||||
s_logger.warn("Encounter remote exception to vCenter, invalidate VMware session context");
|
||||
|
||||
hostService.invalidateServiceContext(null);
|
||||
}
|
||||
|
||||
return new SyncVolumePathAnswer("Failed to process SyncVolumePathCommand due to " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ import org.apache.cloudstack.storage.command.ResignatureAnswer;
|
|||
import org.apache.cloudstack.storage.command.ResignatureCommand;
|
||||
import org.apache.cloudstack.storage.command.SnapshotAndCopyAnswer;
|
||||
import org.apache.cloudstack.storage.command.SnapshotAndCopyCommand;
|
||||
import org.apache.cloudstack.storage.command.SyncVolumePathCommand;
|
||||
import org.apache.cloudstack.storage.to.PrimaryDataStoreTO;
|
||||
import org.apache.cloudstack.storage.to.SnapshotObjectTO;
|
||||
import org.apache.cloudstack.storage.to.TemplateObjectTO;
|
||||
|
|
@ -217,11 +218,17 @@ public class XenServerStorageProcessor implements StorageProcessor {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Answer CheckDataStoreStoragePolicyComplaince(CheckDataStoreStoragePolicyComplainceCommand cmd) {
|
||||
public Answer checkDataStoreStoragePolicyCompliance(CheckDataStoreStoragePolicyComplainceCommand cmd) {
|
||||
s_logger.info("'CheckDataStoreStoragePolicyComplainceCommand' not applicable used for XenServerStorageProcessor");
|
||||
return new Answer(cmd,false,"Not applicable used for XenServerStorageProcessor");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Answer syncVolumePath(SyncVolumePathCommand cmd) {
|
||||
s_logger.info("SyncVolumePathCommand not currently applicable for XenServerStorageProcessor");
|
||||
return new Answer(cmd, false, "Not currently applicable for XenServerStorageProcessor");
|
||||
}
|
||||
|
||||
@Override
|
||||
public AttachAnswer attachIso(final AttachCommand cmd) {
|
||||
final DiskTO disk = cmd.getDisk();
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ import java.util.UUID;
|
|||
import org.apache.cloudstack.storage.command.CheckDataStoreStoragePolicyComplainceCommand;
|
||||
import org.apache.cloudstack.storage.command.CopyCmdAnswer;
|
||||
import org.apache.cloudstack.storage.command.CopyCommand;
|
||||
import org.apache.cloudstack.storage.command.SyncVolumePathCommand;
|
||||
import org.apache.cloudstack.storage.to.PrimaryDataStoreTO;
|
||||
import org.apache.cloudstack.storage.to.SnapshotObjectTO;
|
||||
import org.apache.cloudstack.storage.to.TemplateObjectTO;
|
||||
|
|
@ -915,11 +916,17 @@ public class Xenserver625StorageProcessor extends XenServerStorageProcessor {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Answer CheckDataStoreStoragePolicyComplaince(CheckDataStoreStoragePolicyComplainceCommand cmd) {
|
||||
public Answer checkDataStoreStoragePolicyCompliance(CheckDataStoreStoragePolicyComplainceCommand cmd) {
|
||||
s_logger.info("'CheckDataStoreStoragePolicyComplainceCommand' not applicable used for XenServerStorageProcessor");
|
||||
return new Answer(cmd,false,"Not applicable used for XenServerStorageProcessor");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Answer syncVolumePath(SyncVolumePathCommand cmd) {
|
||||
s_logger.info("SyncVolumePathCommand not currently applicable for XenServerStorageProcessor");
|
||||
return new Answer(cmd, false, "Not currently applicable for XenServerStorageProcessor");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Answer copyVolumeFromPrimaryToSecondary(final CopyCommand cmd) {
|
||||
final Connection conn = hypervisorResource.getConnection();
|
||||
|
|
|
|||
|
|
@ -207,6 +207,7 @@ import org.apache.cloudstack.api.command.admin.storage.PreparePrimaryStorageForM
|
|||
import org.apache.cloudstack.api.command.admin.storage.UpdateCloudToUseObjectStoreCmd;
|
||||
import org.apache.cloudstack.api.command.admin.storage.UpdateImageStoreCmd;
|
||||
import org.apache.cloudstack.api.command.admin.storage.UpdateStoragePoolCmd;
|
||||
import org.apache.cloudstack.api.command.admin.storage.SyncStoragePoolCmd;
|
||||
import org.apache.cloudstack.api.command.admin.swift.AddSwiftCmd;
|
||||
import org.apache.cloudstack.api.command.admin.swift.ListSwiftsCmd;
|
||||
import org.apache.cloudstack.api.command.admin.systemvm.DestroySystemVmCmd;
|
||||
|
|
@ -3039,6 +3040,7 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
|
|||
cmdList.add(FindStoragePoolsForMigrationCmd.class);
|
||||
cmdList.add(PreparePrimaryStorageForMaintenanceCmd.class);
|
||||
cmdList.add(UpdateStoragePoolCmd.class);
|
||||
cmdList.add(SyncStoragePoolCmd.class);
|
||||
cmdList.add(UpdateImageStoreCmd.class);
|
||||
cmdList.add(DestroySystemVmCmd.class);
|
||||
cmdList.add(ListSystemVMsCmd.class);
|
||||
|
|
|
|||
|
|
@ -32,6 +32,8 @@ import java.util.Iterator;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
|
|
@ -52,6 +54,7 @@ import org.apache.cloudstack.api.command.admin.storage.DeleteImageStoreCmd;
|
|||
import org.apache.cloudstack.api.command.admin.storage.DeletePoolCmd;
|
||||
import org.apache.cloudstack.api.command.admin.storage.DeleteSecondaryStagingStoreCmd;
|
||||
import org.apache.cloudstack.api.command.admin.storage.UpdateStoragePoolCmd;
|
||||
import org.apache.cloudstack.api.command.admin.storage.SyncStoragePoolCmd;
|
||||
import org.apache.cloudstack.context.CallContext;
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.ClusterScope;
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
|
||||
|
|
@ -90,6 +93,8 @@ import org.apache.cloudstack.management.ManagementServerHost;
|
|||
import org.apache.cloudstack.resourcedetail.dao.DiskOfferingDetailsDao;
|
||||
import org.apache.cloudstack.storage.command.CheckDataStoreStoragePolicyComplainceCommand;
|
||||
import org.apache.cloudstack.storage.command.DettachCommand;
|
||||
import org.apache.cloudstack.storage.command.SyncVolumePathAnswer;
|
||||
import org.apache.cloudstack.storage.command.SyncVolumePathCommand;
|
||||
import org.apache.cloudstack.storage.datastore.db.ImageStoreDao;
|
||||
import org.apache.cloudstack.storage.datastore.db.ImageStoreDetailsDao;
|
||||
import org.apache.cloudstack.storage.datastore.db.ImageStoreVO;
|
||||
|
|
@ -104,6 +109,7 @@ import org.apache.cloudstack.storage.datastore.db.VolumeDataStoreDao;
|
|||
import org.apache.cloudstack.storage.datastore.db.VolumeDataStoreVO;
|
||||
import org.apache.cloudstack.storage.image.datastore.ImageStoreEntity;
|
||||
import org.apache.cloudstack.storage.to.VolumeObjectTO;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
|
|
@ -119,6 +125,8 @@ import com.cloud.agent.api.StoragePoolInfo;
|
|||
import com.cloud.agent.api.VolumeStatsEntry;
|
||||
import com.cloud.agent.api.to.DataTO;
|
||||
import com.cloud.agent.api.to.DiskTO;
|
||||
import com.cloud.agent.api.ModifyStoragePoolCommand;
|
||||
import com.cloud.agent.api.ModifyStoragePoolAnswer;
|
||||
import com.cloud.agent.manager.Commands;
|
||||
import com.cloud.api.ApiDBUtils;
|
||||
import com.cloud.api.query.dao.TemplateJoinDao;
|
||||
|
|
@ -1624,14 +1632,14 @@ public class StorageManagerImpl extends ManagerBase implements StorageManager, C
|
|||
if (primaryStorage.getStatus() == StoragePoolStatus.PrepareForMaintenance) {
|
||||
throw new CloudRuntimeException(String.format("There is already a job running for preparation for maintenance of the storage pool %s", primaryStorage.getUuid()));
|
||||
}
|
||||
handlePrepareDatastoreCluserMaintenance(lifeCycle, primaryStorageId);
|
||||
handlePrepareDatastoreClusterMaintenance(lifeCycle, primaryStorageId);
|
||||
}
|
||||
lifeCycle.maintain(store);
|
||||
|
||||
return (PrimaryDataStoreInfo)_dataStoreMgr.getDataStore(primaryStorage.getId(), DataStoreRole.Primary);
|
||||
}
|
||||
|
||||
private void handlePrepareDatastoreCluserMaintenance(DataStoreLifeCycle lifeCycle, Long primaryStorageId) {
|
||||
private void handlePrepareDatastoreClusterMaintenance(DataStoreLifeCycle lifeCycle, Long primaryStorageId) {
|
||||
StoragePoolVO datastoreCluster = _storagePoolDao.findById(primaryStorageId);
|
||||
datastoreCluster.setStatus(StoragePoolStatus.PrepareForMaintenance);
|
||||
_storagePoolDao.update(datastoreCluster.getId(), datastoreCluster);
|
||||
|
|
@ -1705,6 +1713,261 @@ public class StorageManagerImpl extends ManagerBase implements StorageManager, C
|
|||
return (PrimaryDataStoreInfo)_dataStoreMgr.getDataStore(primaryStorage.getId(), DataStoreRole.Primary);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ActionEvent(eventType = EventTypes.EVENT_SYNC_STORAGE_POOL, eventDescription = "synchronising storage pool with management server", async = true)
|
||||
public StoragePool syncStoragePool(SyncStoragePoolCmd cmd) {
|
||||
Long poolId = cmd.getPoolId();
|
||||
StoragePoolVO pool = _storagePoolDao.findById(poolId);
|
||||
|
||||
if (pool == null) {
|
||||
String msg = String.format("Unable to obtain lock on the storage pool record while syncing storage pool [%s] with management server", pool.getUuid());
|
||||
s_logger.error(msg);
|
||||
throw new InvalidParameterValueException(msg);
|
||||
}
|
||||
|
||||
if (!pool.getPoolType().equals(StoragePoolType.DatastoreCluster)) {
|
||||
throw new InvalidParameterValueException("SyncStoragePool API is currently supported only for storage type of datastore cluster");
|
||||
}
|
||||
|
||||
if (!pool.getStatus().equals(StoragePoolStatus.Up)) {
|
||||
throw new InvalidParameterValueException(String.format("Primary storage with id %s is not ready for syncing, as the status is %s", pool.getUuid(), pool.getStatus().toString()));
|
||||
}
|
||||
|
||||
// find the host
|
||||
List<Long> poolIds = new ArrayList<>();
|
||||
poolIds.add(poolId);
|
||||
List<Long> hosts = _storagePoolHostDao.findHostsConnectedToPools(poolIds);
|
||||
if (hosts.size() > 0) {
|
||||
Long hostId = hosts.get(0);
|
||||
ModifyStoragePoolCommand modifyStoragePoolCommand = new ModifyStoragePoolCommand(true, pool);
|
||||
final Answer answer = _agentMgr.easySend(hostId, modifyStoragePoolCommand);
|
||||
|
||||
if (answer == null) {
|
||||
throw new CloudRuntimeException(String.format("Unable to get an answer to the modify storage pool command %s", pool.getUuid()));
|
||||
}
|
||||
|
||||
if (!answer.getResult()) {
|
||||
throw new CloudRuntimeException(String.format("Unable to process ModifyStoragePoolCommand for pool %s on the host %s due to ", pool.getUuid(), hostId, answer.getDetails()));
|
||||
}
|
||||
|
||||
assert (answer instanceof ModifyStoragePoolAnswer) : "Well, now why won't you actually return the ModifyStoragePoolAnswer when it's ModifyStoragePoolCommand? Pool=" +
|
||||
pool.getId() + "Host=" + hostId;
|
||||
ModifyStoragePoolAnswer mspAnswer = (ModifyStoragePoolAnswer) answer;
|
||||
StoragePoolVO poolVO = _storagePoolDao.findById(poolId);
|
||||
updateStoragePoolHostVOAndBytes(poolVO, hostId, mspAnswer);
|
||||
validateChildDatastoresToBeAddedInUpState(poolVO, mspAnswer.getDatastoreClusterChildren());
|
||||
syncDatastoreClusterStoragePool(poolId, mspAnswer.getDatastoreClusterChildren(), hostId);
|
||||
for (ModifyStoragePoolAnswer childDataStoreAnswer : mspAnswer.getDatastoreClusterChildren()) {
|
||||
StoragePoolInfo childStoragePoolInfo = childDataStoreAnswer.getPoolInfo();
|
||||
StoragePoolVO dataStoreVO = _storagePoolDao.findPoolByUUID(childStoragePoolInfo.getUuid());
|
||||
for (Long host : hosts) {
|
||||
updateStoragePoolHostVOAndBytes(dataStoreVO, host, childDataStoreAnswer);
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
throw new CloudRuntimeException(String.format("Unable to sync storage pool [%s] as there no connected hosts to the storage pool", pool.getUuid()));
|
||||
}
|
||||
return (PrimaryDataStoreInfo) _dataStoreMgr.getDataStore(pool.getId(), DataStoreRole.Primary);
|
||||
}
|
||||
|
||||
public void syncDatastoreClusterStoragePool(long datastoreClusterPoolId, List<ModifyStoragePoolAnswer> childDatastoreAnswerList, long hostId) {
|
||||
StoragePoolVO datastoreClusterPool = _storagePoolDao.findById(datastoreClusterPoolId);
|
||||
List<String> storageTags = _storagePoolTagsDao.getStoragePoolTags(datastoreClusterPoolId);
|
||||
List<StoragePoolVO> childDatastores = _storagePoolDao.listChildStoragePoolsInDatastoreCluster(datastoreClusterPoolId);
|
||||
Set<String> childDatastoreUUIDs = new HashSet<>();
|
||||
for (StoragePoolVO childDatastore : childDatastores) {
|
||||
childDatastoreUUIDs.add(childDatastore.getUuid());
|
||||
}
|
||||
|
||||
for (ModifyStoragePoolAnswer childDataStoreAnswer : childDatastoreAnswerList) {
|
||||
StoragePoolInfo childStoragePoolInfo = childDataStoreAnswer.getPoolInfo();
|
||||
StoragePoolVO dataStoreVO = _storagePoolDao.findPoolByUUID(childStoragePoolInfo.getUuid());
|
||||
if (dataStoreVO == null && childDataStoreAnswer.getPoolType().equalsIgnoreCase("NFS")) {
|
||||
List<StoragePoolVO> nfsStoragePools = _storagePoolDao.findPoolsByStorageType(StoragePoolType.NetworkFilesystem.toString());
|
||||
for (StoragePoolVO storagePool : nfsStoragePools) {
|
||||
String storagePoolUUID = storagePool.getUuid();
|
||||
if (childStoragePoolInfo.getName().equalsIgnoreCase(storagePoolUUID.replaceAll("-", ""))) {
|
||||
dataStoreVO = storagePool;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (dataStoreVO != null) {
|
||||
if (dataStoreVO.getParent() != datastoreClusterPoolId) {
|
||||
s_logger.debug(String.format("Storage pool %s with uuid %s is found to be under datastore cluster %s at vCenter, " +
|
||||
"so moving the storage pool to be a child storage pool under the datastore cluster in CloudStack management server",
|
||||
childStoragePoolInfo.getName(), childStoragePoolInfo.getUuid(), datastoreClusterPool.getName()));
|
||||
dataStoreVO.setParent(datastoreClusterPoolId);
|
||||
_storagePoolDao.update(dataStoreVO.getId(), dataStoreVO);
|
||||
if (CollectionUtils.isNotEmpty(storageTags)) {
|
||||
storageTags.addAll(_storagePoolTagsDao.getStoragePoolTags(dataStoreVO.getId()));
|
||||
} else {
|
||||
storageTags = _storagePoolTagsDao.getStoragePoolTags(dataStoreVO.getId());
|
||||
}
|
||||
if (CollectionUtils.isNotEmpty(storageTags)) {
|
||||
Set<String> set = new LinkedHashSet<>(storageTags);
|
||||
storageTags.clear();
|
||||
storageTags.addAll(set);
|
||||
if (s_logger.isDebugEnabled()) {
|
||||
s_logger.debug("Updating Storage Pool Tags to :" + storageTags);
|
||||
}
|
||||
_storagePoolTagsDao.persist(dataStoreVO.getId(), storageTags);
|
||||
}
|
||||
} else {
|
||||
// This is to find datastores which are removed from datastore cluster.
|
||||
// The final set childDatastoreUUIDs contains the UUIDs of child datastores which needs to be removed from datastore cluster
|
||||
childDatastoreUUIDs.remove(childStoragePoolInfo.getUuid());
|
||||
}
|
||||
} else {
|
||||
dataStoreVO = createChildDatastoreVO(datastoreClusterPool, childDataStoreAnswer);
|
||||
}
|
||||
updateStoragePoolHostVOAndBytes(dataStoreVO, hostId, childDataStoreAnswer);
|
||||
}
|
||||
|
||||
handleRemoveChildStoragePoolFromDatastoreCluster(childDatastoreUUIDs);
|
||||
}
|
||||
|
||||
private void validateChildDatastoresToBeAddedInUpState(StoragePoolVO datastoreClusterPool, List<ModifyStoragePoolAnswer> childDatastoreAnswerList) {
|
||||
for (ModifyStoragePoolAnswer childDataStoreAnswer : childDatastoreAnswerList) {
|
||||
StoragePoolInfo childStoragePoolInfo = childDataStoreAnswer.getPoolInfo();
|
||||
StoragePoolVO dataStoreVO = _storagePoolDao.findPoolByUUID(childStoragePoolInfo.getUuid());
|
||||
if (dataStoreVO == null && childDataStoreAnswer.getPoolType().equalsIgnoreCase("NFS")) {
|
||||
List<StoragePoolVO> nfsStoragePools = _storagePoolDao.findPoolsByStorageType(StoragePoolType.NetworkFilesystem.toString());
|
||||
for (StoragePoolVO storagePool : nfsStoragePools) {
|
||||
String storagePoolUUID = storagePool.getUuid();
|
||||
if (childStoragePoolInfo.getName().equalsIgnoreCase(storagePoolUUID.replaceAll("-", ""))) {
|
||||
dataStoreVO = storagePool;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (dataStoreVO != null && !dataStoreVO.getStatus().equals(StoragePoolStatus.Up)) {
|
||||
String msg = String.format("Cannot synchronise datastore cluster %s because primary storage with id %s is not ready for syncing, " +
|
||||
"as the status is %s", datastoreClusterPool.getUuid(), dataStoreVO.getUuid(), dataStoreVO.getStatus().toString());
|
||||
throw new CloudRuntimeException(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private StoragePoolVO createChildDatastoreVO(StoragePoolVO datastoreClusterPool, ModifyStoragePoolAnswer childDataStoreAnswer) {
|
||||
StoragePoolInfo childStoragePoolInfo = childDataStoreAnswer.getPoolInfo();
|
||||
List<String> storageTags = _storagePoolTagsDao.getStoragePoolTags(datastoreClusterPool.getId());
|
||||
|
||||
StoragePoolVO dataStoreVO = new StoragePoolVO();
|
||||
dataStoreVO.setStorageProviderName(datastoreClusterPool.getStorageProviderName());
|
||||
dataStoreVO.setHostAddress(childStoragePoolInfo.getHost());
|
||||
dataStoreVO.setPoolType(Storage.StoragePoolType.PreSetup);
|
||||
dataStoreVO.setPath(childStoragePoolInfo.getHostPath());
|
||||
dataStoreVO.setPort(datastoreClusterPool.getPort());
|
||||
dataStoreVO.setName(childStoragePoolInfo.getName());
|
||||
dataStoreVO.setUuid(childStoragePoolInfo.getUuid());
|
||||
dataStoreVO.setDataCenterId(datastoreClusterPool.getDataCenterId());
|
||||
dataStoreVO.setPodId(datastoreClusterPool.getPodId());
|
||||
dataStoreVO.setClusterId(datastoreClusterPool.getClusterId());
|
||||
dataStoreVO.setStatus(StoragePoolStatus.Up);
|
||||
dataStoreVO.setUserInfo(datastoreClusterPool.getUserInfo());
|
||||
dataStoreVO.setManaged(datastoreClusterPool.isManaged());
|
||||
dataStoreVO.setCapacityIops(datastoreClusterPool.getCapacityIops());
|
||||
dataStoreVO.setCapacityBytes(childDataStoreAnswer.getPoolInfo().getCapacityBytes());
|
||||
dataStoreVO.setUsedBytes(childDataStoreAnswer.getPoolInfo().getCapacityBytes() - childDataStoreAnswer.getPoolInfo().getAvailableBytes());
|
||||
dataStoreVO.setHypervisor(datastoreClusterPool.getHypervisor());
|
||||
dataStoreVO.setScope(datastoreClusterPool.getScope());
|
||||
dataStoreVO.setParent(datastoreClusterPool.getId());
|
||||
|
||||
Map<String, String> details = new HashMap<>();
|
||||
if(org.apache.commons.lang.StringUtils.isNotEmpty(childDataStoreAnswer.getPoolType())) {
|
||||
details.put("pool_type", childDataStoreAnswer.getPoolType());
|
||||
}
|
||||
_storagePoolDao.persist(dataStoreVO, details, storageTags);
|
||||
return dataStoreVO;
|
||||
}
|
||||
|
||||
private void handleRemoveChildStoragePoolFromDatastoreCluster(Set<String> childDatastoreUUIDs) {
|
||||
|
||||
for (String childDatastoreUUID : childDatastoreUUIDs) {
|
||||
StoragePoolVO dataStoreVO = _storagePoolDao.findPoolByUUID(childDatastoreUUID);
|
||||
List<VolumeVO> allVolumes = _volumeDao.findByPoolId(dataStoreVO.getId());
|
||||
allVolumes.removeIf(volumeVO -> volumeVO.getInstanceId() == null);
|
||||
allVolumes.removeIf(volumeVO -> volumeVO.getState() != Volume.State.Ready);
|
||||
for (VolumeVO volume : allVolumes) {
|
||||
VMInstanceVO vmInstance = _vmInstanceDao.findById(volume.getInstanceId());
|
||||
if (vmInstance == null) {
|
||||
continue;
|
||||
}
|
||||
long volumeId = volume.getId();
|
||||
Long hostId = vmInstance.getHostId();
|
||||
if (hostId == null) {
|
||||
hostId = vmInstance.getLastHostId();
|
||||
}
|
||||
HostVO hostVO = _hostDao.findById(hostId);
|
||||
|
||||
// Prepare for the syncvolumepath command
|
||||
DataTO volTO = volFactory.getVolume(volume.getId()).getTO();
|
||||
DiskTO disk = new DiskTO(volTO, volume.getDeviceId(), volume.getPath(), volume.getVolumeType());
|
||||
Map<String, String> details = new HashMap<String, String>();
|
||||
details.put(DiskTO.PROTOCOL_TYPE, Storage.StoragePoolType.DatastoreCluster.toString());
|
||||
disk.setDetails(details);
|
||||
|
||||
s_logger.debug(String.format("Attempting to process SyncVolumePathCommand for the volume %d on the host %d with state %s", volumeId, hostId, hostVO.getResourceState()));
|
||||
SyncVolumePathCommand cmd = new SyncVolumePathCommand(disk);
|
||||
final Answer answer = _agentMgr.easySend(hostId, cmd);
|
||||
// validate answer
|
||||
if (answer == null) {
|
||||
throw new CloudRuntimeException("Unable to get an answer to the SyncVolumePath command for volume " + volumeId);
|
||||
}
|
||||
if (!answer.getResult()) {
|
||||
throw new CloudRuntimeException("Unable to process SyncVolumePathCommand for the volume" + volumeId + " to the host " + hostId + " due to " + answer.getDetails());
|
||||
}
|
||||
assert (answer instanceof SyncVolumePathAnswer) : "Well, now why won't you actually return the SyncVolumePathAnswer when it's SyncVolumePathCommand? volume=" +
|
||||
volume.getUuid() + "Host=" + hostId;
|
||||
|
||||
// check for the changed details of volume and update database
|
||||
VolumeVO volumeVO = _volumeDao.findById(volumeId);
|
||||
String datastoreName = answer.getContextParam("datastoreName");
|
||||
if (datastoreName != null) {
|
||||
StoragePoolVO storagePoolVO = _storagePoolDao.findByUuid(datastoreName);
|
||||
if (storagePoolVO != null) {
|
||||
volumeVO.setPoolId(storagePoolVO.getId());
|
||||
} else {
|
||||
s_logger.warn(String.format("Unable to find datastore %s while updating the new datastore of the volume %d", datastoreName, volumeId));
|
||||
}
|
||||
}
|
||||
|
||||
String volumePath = answer.getContextParam("volumePath");
|
||||
if (volumePath != null) {
|
||||
volumeVO.setPath(volumePath);
|
||||
}
|
||||
|
||||
String chainInfo = answer.getContextParam("chainInfo");
|
||||
if (chainInfo != null) {
|
||||
volumeVO.setChainInfo(chainInfo);
|
||||
}
|
||||
|
||||
_volumeDao.update(volumeVO.getId(), volumeVO);
|
||||
}
|
||||
dataStoreVO.setParent(0L);
|
||||
_storagePoolDao.update(dataStoreVO.getId(), dataStoreVO);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void updateStoragePoolHostVOAndBytes(StoragePool pool, long hostId, ModifyStoragePoolAnswer mspAnswer) {
|
||||
StoragePoolHostVO poolHost = _storagePoolHostDao.findByPoolHost(pool.getId(), hostId);
|
||||
if (poolHost == null) {
|
||||
poolHost = new StoragePoolHostVO(pool.getId(), hostId, mspAnswer.getPoolInfo().getLocalPath().replaceAll("//", "/"));
|
||||
_storagePoolHostDao.persist(poolHost);
|
||||
} else {
|
||||
poolHost.setLocalPath(mspAnswer.getPoolInfo().getLocalPath().replaceAll("//", "/"));
|
||||
}
|
||||
|
||||
StoragePoolVO poolVO = _storagePoolDao.findById(pool.getId());
|
||||
poolVO.setUsedBytes(mspAnswer.getPoolInfo().getCapacityBytes() - mspAnswer.getPoolInfo().getAvailableBytes());
|
||||
poolVO.setCapacityBytes(mspAnswer.getPoolInfo().getCapacityBytes());
|
||||
|
||||
_storagePoolDao.update(pool.getId(), poolVO);
|
||||
}
|
||||
|
||||
protected class StorageGarbageCollector extends ManagedContextRunnable {
|
||||
|
||||
public StorageGarbageCollector() {
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ import org.springframework.stereotype.Component;
|
|||
import com.cloud.agent.AgentManager;
|
||||
import com.cloud.agent.api.Answer;
|
||||
import com.cloud.agent.api.ModifyStoragePoolCommand;
|
||||
import com.cloud.agent.api.ModifyStoragePoolAnswer;
|
||||
import com.cloud.alert.AlertManager;
|
||||
import com.cloud.host.HostVO;
|
||||
import com.cloud.host.Status;
|
||||
|
|
@ -100,6 +101,8 @@ public class StoragePoolAutomationImpl implements StoragePoolAutomation {
|
|||
ManagementServer server;
|
||||
@Inject
|
||||
DataStoreProviderManager providerMgr;
|
||||
@Inject
|
||||
StorageManager storageManager;
|
||||
|
||||
@Override
|
||||
public boolean maintain(DataStore store) {
|
||||
|
|
@ -162,6 +165,10 @@ public class StoragePoolAutomationImpl implements StoragePoolAutomation {
|
|||
if (s_logger.isDebugEnabled()) {
|
||||
s_logger.debug("ModifyStoragePool false succeeded");
|
||||
}
|
||||
if (pool.getPoolType() == Storage.StoragePoolType.DatastoreCluster) {
|
||||
s_logger.debug(String.format("Started synchronising datastore cluster storage pool %s with vCenter", pool.getUuid()));
|
||||
storageManager.syncDatastoreClusterStoragePool(pool.getId(), ((ModifyStoragePoolAnswer) answer).getDatastoreClusterChildren(), host.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
// check to see if other ps exist
|
||||
|
|
@ -323,6 +330,10 @@ public class StoragePoolAutomationImpl implements StoragePoolAutomation {
|
|||
if (s_logger.isDebugEnabled()) {
|
||||
s_logger.debug("ModifyStoragePool add succeeded");
|
||||
}
|
||||
if (pool.getPoolType() == Storage.StoragePoolType.DatastoreCluster) {
|
||||
s_logger.debug(String.format("Started synchronising datastore cluster storage pool %s with vCenter", pool.getUuid()));
|
||||
storageManager.syncDatastoreClusterStoragePool(pool.getId(), ((ModifyStoragePoolAnswer) answer).getDatastoreClusterChildren(), host.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2265,7 +2265,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
|
|||
VMInstanceVO vmInstance = _vmInstanceDao.findById(vmId);
|
||||
|
||||
// only load running vms. For stopped vms get loaded on starting
|
||||
if (vmInstance.getState() == State.Running) {
|
||||
if (vmInstance != null && vmInstance.getState() == State.Running) {
|
||||
VmAndCountDetails vmAndCount = new VmAndCountDetails(vmId, VmIpFetchTrialMax.value());
|
||||
vmIdCountMap.put(nicId, vmAndCount);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -95,6 +95,7 @@ known_categories = {
|
|||
'StorageMaintenance': 'Storage Pool',
|
||||
'StoragePool': 'Storage Pool',
|
||||
'StorageProvider': 'Storage Pool',
|
||||
'syncStoragePool': 'Storage Pool',
|
||||
'SecurityGroup': 'Security Group',
|
||||
'SSH': 'SSH',
|
||||
'register': 'Registration',
|
||||
|
|
|
|||
|
|
@ -52,18 +52,8 @@
|
|||
:dataSource="detailOptions[newKey]"
|
||||
:placeholder="$t('label.value')"
|
||||
@change="e => onAddInputChange(e, 'newValue')" />
|
||||
<a-tooltip arrowPointAtCenter placement="topRight">
|
||||
<template slot="title">
|
||||
{{ $t('label.add.setting') }}
|
||||
</template>
|
||||
<a-button icon="check" @click="addDetail" class="detail-button"></a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip arrowPointAtCenter placement="topRight">
|
||||
<template slot="title">
|
||||
{{ $t('label.cancel') }}
|
||||
</template>
|
||||
<a-button icon="close" @click="closeDetail" class="detail-button"></a-button>
|
||||
</a-tooltip>
|
||||
<tooltip-button :tooltip="$t('label.add.setting')" icon="check" @click="addDetail" buttonClass="detail-button" />
|
||||
<tooltip-button :tooltip="$t('label.cancel')" icon="close" @click="closeDetail" buttonClass="detail-button" />
|
||||
</a-input-group>
|
||||
<p v-if="error" style="color: red"> {{ $t(error) }} </p>
|
||||
</div>
|
||||
|
|
@ -90,14 +80,10 @@
|
|||
slot="actions"
|
||||
v-if="!disableSettings && 'updateTemplate' in $store.getters.apis &&
|
||||
'updateVirtualMachine' in $store.getters.apis && isAdminOrOwner() && allowEditOfDetail(item.name)">
|
||||
<a-button shape="circle" size="default" @click="updateDetail(index)" v-if="item.edit">
|
||||
<a-icon type="check-circle" theme="twoTone" twoToneColor="#52c41a" />
|
||||
</a-button>
|
||||
<a-button shape="circle" size="default" @click="hideEditDetail(index)" v-if="item.edit">
|
||||
<a-icon type="close-circle" theme="twoTone" twoToneColor="#f5222d" />
|
||||
</a-button>
|
||||
<a-button
|
||||
shape="circle"
|
||||
<tootip-button :tooltip="$t('label.cancel')" @click="hideEditDetail(index)" v-if="item.edit" iconType="close-circle" iconTwoToneColor="#f5222d" />
|
||||
<tootip-button :tooltip="$t('label.ok')" @click="updateDetail(index)" v-if="item.edit" iconType="check-circle" iconTwoToneColor="#52c41a" />
|
||||
<tooltip-button
|
||||
:tooltip="$t('label.edit')"
|
||||
icon="edit"
|
||||
v-if="!item.edit"
|
||||
@click="showEditDetail(index)" />
|
||||
|
|
@ -113,7 +99,7 @@
|
|||
:cancelText="$t('label.no')"
|
||||
placement="left"
|
||||
>
|
||||
<a-button shape="circle" type="danger" icon="delete" />
|
||||
<tooltip-button :tooltip="$t('label.delete')" type="danger" icon="delete" />
|
||||
</a-popconfirm>
|
||||
</div>
|
||||
</a-list-item>
|
||||
|
|
@ -123,8 +109,10 @@
|
|||
|
||||
<script>
|
||||
import { api } from '@/api'
|
||||
import TooltipButton from './TooltipButton.vue'
|
||||
|
||||
export default {
|
||||
components: { TooltipButton },
|
||||
name: 'DetailSettings',
|
||||
props: {
|
||||
resource: {
|
||||
|
|
|
|||
|
|
@ -106,19 +106,15 @@
|
|||
<div class="resource-detail-item" v-if="resource.id">
|
||||
<div class="resource-detail-item__label">{{ $t('label.id') }}</div>
|
||||
<div class="resource-detail-item__details">
|
||||
<a-tooltip placement="right" >
|
||||
<template slot="title">
|
||||
<span>{{ $t('label.copyid') }}</span>
|
||||
</template>
|
||||
<a-button
|
||||
style="margin-left: -5px"
|
||||
shape="circle"
|
||||
icon="barcode"
|
||||
type="dashed"
|
||||
size="small"
|
||||
@click="$message.success($t('label.copied.clipboard'))"
|
||||
v-clipboard:copy="resource.id" />
|
||||
</a-tooltip>
|
||||
<tooltip-button
|
||||
tooltipPlacement="right"
|
||||
:tooltip="$t('label.copyid')"
|
||||
style="margin-left: -5px"
|
||||
icon="barcode"
|
||||
type="dashed"
|
||||
size="small"
|
||||
@click="$message.success($t('label.copied.clipboard'))"
|
||||
v-clipboard:copy="resource.id" />
|
||||
<span style="margin-left: 10px;">{{ resource.id }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -573,14 +569,14 @@
|
|||
<a-icon type="key" />
|
||||
<strong>
|
||||
{{ $t('label.apikey') }}
|
||||
<a-tooltip placement="right" >
|
||||
<template slot="title">
|
||||
<span>{{ $t('label.copy') + ' ' + $t('label.apikey') }}</span>
|
||||
</template>
|
||||
<a-button shape="circle" type="dashed" size="small" @click="$message.success($t('label.copied.clipboard'))" v-clipboard:copy="resource.apikey">
|
||||
<a-icon type="copy"/>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<tooltip-button
|
||||
tooltipPlacement="right"
|
||||
:tooltip="$t('label.copy') + ' ' + $t('label.apikey')"
|
||||
icon="copy"
|
||||
type="dashed"
|
||||
size="small"
|
||||
@click="$message.success($t('label.copied.clipboard'))"
|
||||
v-clipboard:copy="resource.apikey" />
|
||||
</strong>
|
||||
<div>
|
||||
{{ resource.apikey.substring(0, 20) }}...
|
||||
|
|
@ -590,14 +586,14 @@
|
|||
<a-icon type="lock" />
|
||||
<strong>
|
||||
{{ $t('label.secretkey') }}
|
||||
<a-tooltip placement="right" >
|
||||
<template slot="title">
|
||||
<span>{{ $t('label.copy') + ' ' + $t('label.secretkey') }}</span>
|
||||
</template>
|
||||
<a-button shape="circle" type="dashed" size="small" @click="$message.success($t('label.copied.clipboard'))" v-clipboard:copy="resource.secretkey">
|
||||
<a-icon type="copy"/>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<tooltip-button
|
||||
tooltipPlacement="right"
|
||||
:tooltip="$t('label.copy') + ' ' + $t('label.secretkey')"
|
||||
icon="copy"
|
||||
type="dashed"
|
||||
size="small"
|
||||
@click="$message.success($t('label.copied.clipboard'))"
|
||||
v-clipboard:copy="resource.secretkey" />
|
||||
</strong>
|
||||
<div>
|
||||
{{ resource.secretkey.substring(0, 20) }}...
|
||||
|
|
@ -626,12 +622,8 @@
|
|||
<a-input ref="input" :value="inputKey" @change="handleKeyChange" style="width: 30%; text-align: center" :placeholder="$t('label.key')" />
|
||||
<a-input style=" width: 30px; border-left: 0; pointer-events: none; backgroundColor: #fff" placeholder="=" disabled />
|
||||
<a-input :value="inputValue" @change="handleValueChange" style="width: 30%; text-align: center; border-left: 0" :placeholder="$t('label.value')" />
|
||||
<a-button shape="circle" size="small" @click="handleInputConfirm">
|
||||
<a-icon type="check"/>
|
||||
</a-button>
|
||||
<a-button shape="circle" size="small" @click="inputVisible=false">
|
||||
<a-icon type="close"/>
|
||||
</a-button>
|
||||
<tooltip-button :tooltip="$t('label.ok')" icon="check" size="small" @click="handleInputConfirm" />
|
||||
<tooltip-button :tooltip="$t('label.cancel')" icon="close" size="small" @click="inputVisible=false" />
|
||||
</a-input-group>
|
||||
</div>
|
||||
<a-tag @click="showInput" style="background: #fff; borderStyle: dashed;" v-else-if="isAdminOrOwner() && 'createTags' in $store.getters.apis">
|
||||
|
|
@ -701,13 +693,15 @@ import { api } from '@/api'
|
|||
import Console from '@/components/widgets/Console'
|
||||
import OsLogo from '@/components/widgets/OsLogo'
|
||||
import Status from '@/components/widgets/Status'
|
||||
import TooltipButton from '@/components/view/TooltipButton'
|
||||
|
||||
export default {
|
||||
name: 'InfoCard',
|
||||
components: {
|
||||
Console,
|
||||
OsLogo,
|
||||
Status
|
||||
Status,
|
||||
TooltipButton
|
||||
},
|
||||
props: {
|
||||
resource: {
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@
|
|||
:enabled="quickViewEnabled() && actions.length > 0 && columns && columns[0].dataIndex === 'name' "
|
||||
@exec-action="$parent.execAction"/>
|
||||
<span v-if="$route.path.startsWith('/project')" style="margin-right: 5px">
|
||||
<a-button type="dashed" size="small" shape="circle" icon="login" @click="changeProject(record)" />
|
||||
<tooltip-button type="dashed" size="small" icon="login" @click="changeProject(record)" />
|
||||
</span>
|
||||
<os-logo v-if="record.ostypename" :osName="record.ostypename" size="1x" style="margin-right: 5px" />
|
||||
|
||||
|
|
@ -287,30 +287,29 @@
|
|||
</div>
|
||||
</template>
|
||||
<template slot="actions" slot-scope="text, record">
|
||||
<a-button
|
||||
shape="circle"
|
||||
<tooltip-button
|
||||
:tooltip="$t('label.edit')"
|
||||
:disabled="!('updateConfiguration' in $store.getters.apis)"
|
||||
v-if="editableValueKey !== record.key"
|
||||
icon="edit"
|
||||
@click="editValue(record)" />
|
||||
<a-button
|
||||
shape="circle"
|
||||
<tooltip-button
|
||||
:tooltip="$t('label.cancel')"
|
||||
@click="editableValueKey = null"
|
||||
v-if="editableValueKey === record.key"
|
||||
iconType="close-circle"
|
||||
iconTwoToneColor="#f5222d" />
|
||||
<tooltip-button
|
||||
:tooltip="$t('label.ok')"
|
||||
:disabled="!('updateConfiguration' in $store.getters.apis)"
|
||||
@click="saveValue(record)"
|
||||
v-if="editableValueKey === record.key" >
|
||||
<a-icon type="check-circle" theme="twoTone" twoToneColor="#52c41a" />
|
||||
</a-button>
|
||||
<a-button
|
||||
shape="circle"
|
||||
size="default"
|
||||
@click="editableValueKey = null"
|
||||
v-if="editableValueKey === record.key" >
|
||||
<a-icon type="close-circle" theme="twoTone" twoToneColor="#f5222d" />
|
||||
</a-button>
|
||||
v-if="editableValueKey === record.key"
|
||||
iconType="check-circle"
|
||||
iconTwoToneColor="#52c41a" />
|
||||
</template>
|
||||
<template slot="tariffActions" slot-scope="text, record">
|
||||
<a-button
|
||||
shape="circle"
|
||||
<tooltip-button
|
||||
:tooltip="$t('label.edit')"
|
||||
v-if="editableValueKey !== record.key"
|
||||
:disabled="!('quotaTariffUpdate' in $store.getters.apis)"
|
||||
icon="edit"
|
||||
|
|
@ -327,6 +326,7 @@ import OsLogo from '@/components/widgets/OsLogo'
|
|||
import Status from '@/components/widgets/Status'
|
||||
import InfoCard from '@/components/view/InfoCard'
|
||||
import QuickView from '@/components/view/QuickView'
|
||||
import TooltipButton from '@/components/view/TooltipButton'
|
||||
|
||||
export default {
|
||||
name: 'ListView',
|
||||
|
|
@ -335,7 +335,8 @@ export default {
|
|||
OsLogo,
|
||||
Status,
|
||||
InfoCard,
|
||||
QuickView
|
||||
QuickView,
|
||||
TooltipButton
|
||||
},
|
||||
props: {
|
||||
columns: {
|
||||
|
|
|
|||
|
|
@ -76,9 +76,7 @@
|
|||
<a-input ref="input" :value="inputKey" @change="e => inputKey = e.target.value" style="width: 50px; text-align: center" :placeholder="$t('label.key')" />
|
||||
<a-input style=" width: 20px; border-left: 0; pointer-events: none; backgroundColor: #fff" placeholder="=" disabled />
|
||||
<a-input :value="inputValue" @change="handleValueChange" style="width: 50px; text-align: center; border-left: 0" :placeholder="$t('label.value')" />
|
||||
<a-button shape="circle" size="small" @click="inputKey = inputValue = ''">
|
||||
<a-icon type="close"/>
|
||||
</a-button>
|
||||
<tooltip-button icon="close" size="small" @click="inputKey = inputValue = ''" />
|
||||
</a-input-group>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -46,25 +46,24 @@
|
|||
</div>
|
||||
|
||||
<div slot="actions" class="action">
|
||||
<a-button
|
||||
shape="circle"
|
||||
<tooltip-button
|
||||
:tooltip="$t('label.edit')"
|
||||
:disabled="!('updateConfiguration' in $store.getters.apis)"
|
||||
v-if="editableValueKey !== index"
|
||||
icon="edit"
|
||||
@click="setEditableSetting(item, index)" />
|
||||
<a-button
|
||||
shape="circle"
|
||||
size="default"
|
||||
<tooltip-button
|
||||
:tooltip="$t('label.cancel')"
|
||||
@click="editableValueKey = null"
|
||||
v-if="editableValueKey === index" >
|
||||
<a-icon type="close-circle" theme="twoTone" twoToneColor="#f5222d" />
|
||||
</a-button>
|
||||
<a-button
|
||||
shape="circle"
|
||||
v-if="editableValueKey === index"
|
||||
iconType="close-circle"
|
||||
iconTwoToneColor="#f5222d" />
|
||||
<tooltip-button
|
||||
:tooltip="$t('label.ok')"
|
||||
@click="updateData(item)"
|
||||
v-if="editableValueKey === index" >
|
||||
<a-icon type="check-circle" theme="twoTone" twoToneColor="#52c41a" />
|
||||
</a-button>
|
||||
v-if="editableValueKey === index"
|
||||
iconType="check-circle"
|
||||
iconTwoToneColor="#52c41a" />
|
||||
</div>
|
||||
</a-list-item>
|
||||
</a-list>
|
||||
|
|
@ -73,8 +72,12 @@
|
|||
|
||||
<script>
|
||||
import { api } from '@/api'
|
||||
import TooltipButton from './TooltipButton.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
TooltipButton
|
||||
},
|
||||
name: 'SettingsTab',
|
||||
props: {
|
||||
resource: {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,100 @@
|
|||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF 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.
|
||||
|
||||
<template>
|
||||
<a-tooltip arrowPointAtCenter :placement="tooltipPlacement">
|
||||
<template slot="title" v-if="tooltip">
|
||||
{{ tooltip }}
|
||||
</template>
|
||||
<a-button
|
||||
shape="circle"
|
||||
:size="size"
|
||||
:type="type"
|
||||
:disabled="disabled"
|
||||
:icon="icon"
|
||||
:class="buttonClass"
|
||||
:loading="loading"
|
||||
@click="handleClicked()" >
|
||||
<a-icon
|
||||
v-if="iconType && iconTwoToneColor"
|
||||
:type="iconType"
|
||||
theme="twoTone"
|
||||
:twoToneColor="iconTwoToneColor" />
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'TooltipButton',
|
||||
props: {
|
||||
tooltip: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
tooltipPlacement: {
|
||||
type: String,
|
||||
default: 'bottomRight'
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: 'default'
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
default: 'default'
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
iconType: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
iconTwoToneColor: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
buttonClass: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleClicked () {
|
||||
this.$emit('click')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
</style>
|
||||
|
|
@ -67,61 +67,47 @@
|
|||
</a-button>
|
||||
<NicsTable :resource="vm" :loading="loading">
|
||||
<span slot="actions" slot-scope="record">
|
||||
<a-tooltip placement="bottom">
|
||||
<template slot="title">
|
||||
{{ $t('label.set.default.nic') }}
|
||||
</template>
|
||||
<a-popconfirm
|
||||
:title="$t('label.set.default.nic')"
|
||||
@confirm="setAsDefault(record.nic)"
|
||||
:okText="$t('label.yes')"
|
||||
:cancelText="$t('label.no')"
|
||||
v-if="!record.nic.isdefault"
|
||||
>
|
||||
<a-button
|
||||
:disabled="!('updateDefaultNicForVirtualMachine' in $store.getters.apis)"
|
||||
icon="check-square"
|
||||
shape="circle" />
|
||||
</a-popconfirm>
|
||||
</a-tooltip>
|
||||
<a-tooltip placement="bottom" v-if="record.nic.type !== 'L2'">
|
||||
<template slot="title">
|
||||
{{ $t('label.change.ip.addess') }}
|
||||
</template>
|
||||
<a-button
|
||||
icon="swap"
|
||||
shape="circle"
|
||||
:disabled="!('updateVmNicIp' in $store.getters.apis)"
|
||||
@click="onChangeIPAddress(record)" />
|
||||
</a-tooltip>
|
||||
<a-tooltip placement="bottom" v-if="record.nic.type !== 'L2'">
|
||||
<template slot="title">
|
||||
{{ $t('label.edit.secondary.ips') }}
|
||||
</template>
|
||||
<a-button
|
||||
icon="environment"
|
||||
shape="circle"
|
||||
:disabled="(!('addIpToNic' in $store.getters.apis) && !('addIpToNic' in $store.getters.apis))"
|
||||
@click="onAcquireSecondaryIPAddress(record)" />
|
||||
</a-tooltip>
|
||||
<a-tooltip placement="bottom">
|
||||
<template slot="title">
|
||||
{{ $t('label.action.delete.nic') }}
|
||||
</template>
|
||||
<a-popconfirm
|
||||
:title="$t('message.network.removenic')"
|
||||
@confirm="removeNIC(record.nic)"
|
||||
:okText="$t('label.yes')"
|
||||
:cancelText="$t('label.no')"
|
||||
v-if="!record.nic.isdefault"
|
||||
>
|
||||
<a-button
|
||||
:disabled="!('removeNicFromVirtualMachine' in $store.getters.apis)"
|
||||
type="danger"
|
||||
icon="delete"
|
||||
shape="circle" />
|
||||
</a-popconfirm>
|
||||
</a-tooltip>
|
||||
<a-popconfirm
|
||||
:title="$t('label.set.default.nic')"
|
||||
@confirm="setAsDefault(record.nic)"
|
||||
:okText="$t('label.yes')"
|
||||
:cancelText="$t('label.no')"
|
||||
v-if="!record.nic.isdefault"
|
||||
>
|
||||
<tooltip-button
|
||||
tooltipPlacement="bottom"
|
||||
:tooltip="$t('label.set.default.nic')"
|
||||
:disabled="!('updateDefaultNicForVirtualMachine' in $store.getters.apis)"
|
||||
icon="check-square" />
|
||||
</a-popconfirm>
|
||||
<tooltip-button
|
||||
v-if="record.nic.type !== 'L2'"
|
||||
tooltipPlacement="bottom"
|
||||
:tooltip="$t('label.change.ip.addess')"
|
||||
icon="swap"
|
||||
:disabled="!('updateVmNicIp' in $store.getters.apis)"
|
||||
@click="onChangeIPAddress(record)" />
|
||||
<tooltip-button
|
||||
v-if="record.nic.type !== 'L2'"
|
||||
tooltipPlacement="bottom"
|
||||
:tooltip="$t('label.edit.secondary.ips')"
|
||||
icon="environment"
|
||||
:disabled="(!('addIpToNic' in $store.getters.apis) && !('addIpToNic' in $store.getters.apis))"
|
||||
@click="onAcquireSecondaryIPAddress(record)" />
|
||||
<a-popconfirm
|
||||
:title="$t('message.network.removenic')"
|
||||
@confirm="removeNIC(record.nic)"
|
||||
:okText="$t('label.yes')"
|
||||
:cancelText="$t('label.no')"
|
||||
v-if="!record.nic.isdefault"
|
||||
>
|
||||
<tooltip-button
|
||||
tooltipPlacement="bottom"
|
||||
:tooltip="$t('label.action.delete.nic')"
|
||||
:disabled="!('removeNicFromVirtualMachine' in $store.getters.apis)"
|
||||
type="danger"
|
||||
icon="delete" />
|
||||
</a-popconfirm>
|
||||
</span>
|
||||
</NicsTable>
|
||||
</a-tab-pane>
|
||||
|
|
@ -251,9 +237,10 @@
|
|||
:okText="$t('label.yes')"
|
||||
:cancelText="$t('label.no')"
|
||||
>
|
||||
<a-button
|
||||
<tooltip-button
|
||||
tooltipPlacement="top"
|
||||
:tooltip="$t('label.action.release.ip')"
|
||||
type="danger"
|
||||
shape="circle"
|
||||
icon="delete" />
|
||||
{{ ip.ipaddress }}
|
||||
</a-popconfirm>
|
||||
|
|
@ -274,6 +261,7 @@ import DetailsTab from '@/components/view/DetailsTab'
|
|||
import DetailSettings from '@/components/view/DetailSettings'
|
||||
import NicsTable from '@/views/network/NicsTable'
|
||||
import ListResourceTable from '@/components/view/ListResourceTable'
|
||||
import TooltipButton from '@/components/view/TooltipButton'
|
||||
|
||||
export default {
|
||||
name: 'InstanceTab',
|
||||
|
|
@ -283,7 +271,8 @@ export default {
|
|||
DetailSettings,
|
||||
NicsTable,
|
||||
Status,
|
||||
ListResourceTable
|
||||
ListResourceTable,
|
||||
TooltipButton
|
||||
},
|
||||
mixins: [mixinDevice],
|
||||
props: {
|
||||
|
|
|
|||
|
|
@ -58,18 +58,14 @@
|
|||
<label>{{ getTimeZone(record.timezone) }}</label>
|
||||
</div>
|
||||
<div slot="action" class="account-button-action" slot-scope="text, record">
|
||||
<a-tooltip placement="top">
|
||||
<template slot="title">
|
||||
{{ $t('label.delete') }}
|
||||
</template>
|
||||
<a-button
|
||||
type="danger"
|
||||
shape="circle"
|
||||
icon="close"
|
||||
size="small"
|
||||
:loading="actionLoading"
|
||||
@click="handleClickDelete(record)"/>
|
||||
</a-tooltip>
|
||||
<tooltip-button
|
||||
tooltipPlacement="top"
|
||||
:tooltip="$t('label.delete')"
|
||||
type="danger"
|
||||
icon="close"
|
||||
size="small"
|
||||
:loading="actionLoading"
|
||||
@click="handleClickDelete(record)"/>
|
||||
</div>
|
||||
</a-table>
|
||||
</div>
|
||||
|
|
@ -78,9 +74,13 @@
|
|||
<script>
|
||||
import { api } from '@/api'
|
||||
import { timeZoneName } from '@/utils/timezone'
|
||||
import TooltipButton from '@/components/view/TooltipButton'
|
||||
|
||||
export default {
|
||||
name: 'BackupSchedule',
|
||||
components: {
|
||||
TooltipButton
|
||||
},
|
||||
props: {
|
||||
loading: {
|
||||
type: Boolean,
|
||||
|
|
|
|||
|
|
@ -51,20 +51,13 @@
|
|||
<a-input v-model="newRuleDescription" :placeholder="$t('label.description')"></a-input>
|
||||
</div>
|
||||
<div class="rules-table__col rules-table__col--actions">
|
||||
<a-tooltip
|
||||
placement="bottom">
|
||||
<template slot="title">
|
||||
{{ $t('label.save.new.rule') }}
|
||||
</template>
|
||||
<a-button
|
||||
:disabled="!('createRolePermission' in $store.getters.apis)"
|
||||
icon="plus"
|
||||
type="primary"
|
||||
shape="circle"
|
||||
@click="onRuleSave"
|
||||
>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<tooltip-button
|
||||
tooltipPlacement="bottom"
|
||||
:tooltip="$t('label.save.new.rule')"
|
||||
:disabled="!('createRolePermission' in $store.getters.apis)"
|
||||
icon="plus"
|
||||
type="primary"
|
||||
@click="onRuleSave" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -117,13 +110,15 @@ import { api } from '@/api'
|
|||
import draggable from 'vuedraggable'
|
||||
import PermissionEditable from './PermissionEditable'
|
||||
import RuleDelete from './RuleDelete'
|
||||
import TooltipButton from '@/components/view/TooltipButton'
|
||||
|
||||
export default {
|
||||
name: 'RolePermissionTab',
|
||||
components: {
|
||||
RuleDelete,
|
||||
PermissionEditable,
|
||||
draggable
|
||||
draggable,
|
||||
TooltipButton
|
||||
},
|
||||
props: {
|
||||
resource: {
|
||||
|
|
|
|||
|
|
@ -16,24 +16,22 @@
|
|||
// under the License.
|
||||
|
||||
<template>
|
||||
<a-tooltip placement="bottom">
|
||||
<template slot="title">
|
||||
{{ $t('label.delete.rule') }}
|
||||
</template>
|
||||
<a-popconfirm
|
||||
:title="`${$t('label.delete.rule')}?`"
|
||||
@confirm="handleDelete"
|
||||
:disabled="disabled">
|
||||
<a-button type="danger" shape="circle" :disabled="disabled">
|
||||
<a-icon type="delete" />
|
||||
</a-button>
|
||||
</a-popconfirm>
|
||||
</a-tooltip>
|
||||
<a-popconfirm
|
||||
:title="`${$t('label.delete.rule')}?`"
|
||||
@confirm="handleDelete"
|
||||
:disabled="disabled">
|
||||
<tooltip-button :tooltip="$t('label.delete.rule')" tooltipPlacement="bottom" type="danger" icon="delete" :disabled="disabled" />
|
||||
</a-popconfirm>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import TooltipButton from '@/components/view/TooltipButton'
|
||||
|
||||
export default {
|
||||
name: 'RuleDelete',
|
||||
components: {
|
||||
TooltipButton
|
||||
},
|
||||
props: {
|
||||
record: {
|
||||
type: Object,
|
||||
|
|
|
|||
|
|
@ -29,24 +29,21 @@
|
|||
v-if="!quickview"
|
||||
>
|
||||
<span slot="action" slot-scope="text, record" class="cert-button-action">
|
||||
<a-tooltip placement="top">
|
||||
<template slot="title">
|
||||
{{ $t('label.quickview') }}
|
||||
</template>
|
||||
<a-button type="primary" shape="circle" icon="eye" size="small" @click="onQuickView(record.id)" />
|
||||
</a-tooltip>
|
||||
<a-tooltip placement="top">
|
||||
<template slot="title">
|
||||
{{ $t('label.delete.sslcertificate') }}
|
||||
</template>
|
||||
<a-button
|
||||
:disabled="!('deleteSslCert' in $store.getters.apis)"
|
||||
type="danger"
|
||||
shape="circle"
|
||||
icon="delete"
|
||||
size="small"
|
||||
@click="onShowConfirm(record)"/>
|
||||
</a-tooltip>
|
||||
<tooltip-button
|
||||
tooltipPlacement="top"
|
||||
:tooltip="$t('label.quickview')"
|
||||
type="primary"
|
||||
icon="eye"
|
||||
size="small"
|
||||
@click="onQuickView(record.id)" />
|
||||
<tooltip-button
|
||||
tooltipPlacement="top"
|
||||
:tooltip="$t('label.delete.sslcertificate')"
|
||||
:disabled="!('deleteSslCert' in $store.getters.apis)"
|
||||
type="danger"
|
||||
icon="delete"
|
||||
size="small"
|
||||
@click="onShowConfirm(record)" />
|
||||
</span>
|
||||
</a-table>
|
||||
|
||||
|
|
@ -71,9 +68,13 @@
|
|||
|
||||
<script>
|
||||
import { api } from '@/api'
|
||||
import TooltipButton from '@/components/view/TooltipButton'
|
||||
|
||||
export default {
|
||||
name: 'SSLCertificate',
|
||||
components: {
|
||||
TooltipButton
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
columns: [],
|
||||
|
|
|
|||
|
|
@ -31,10 +31,10 @@
|
|||
</div>
|
||||
<template slot="action" slot-scope="text, record">
|
||||
<span style="margin-right: 5px">
|
||||
<a-button
|
||||
<tooltip-button
|
||||
:tooltip="$t('label.action.copy.iso')"
|
||||
:disabled="!('copyIso' in $store.getters.apis && record.isready)"
|
||||
icon="copy"
|
||||
shape="circle"
|
||||
:loading="copyLoading"
|
||||
@click="showCopyIso(record)" />
|
||||
</span>
|
||||
|
|
@ -48,10 +48,10 @@
|
|||
:loading="deleteLoading"
|
||||
@confirm="deleteIso(record)"
|
||||
>
|
||||
<a-button
|
||||
<tooltip-button
|
||||
:tooltip="$t('label.action.delete.iso')"
|
||||
type="danger"
|
||||
icon="delete"
|
||||
shape="circle" />
|
||||
icon="delete" />
|
||||
</a-popconfirm>
|
||||
</span>
|
||||
</template>
|
||||
|
|
@ -123,9 +123,13 @@
|
|||
|
||||
<script>
|
||||
import { api } from '@/api'
|
||||
import TooltipButton from '@/components/view/TooltipButton'
|
||||
|
||||
export default {
|
||||
name: 'IsoZones',
|
||||
components: {
|
||||
TooltipButton
|
||||
},
|
||||
props: {
|
||||
resource: {
|
||||
type: Object,
|
||||
|
|
|
|||
|
|
@ -30,22 +30,20 @@
|
|||
<span v-else>{{ $t('label.no') }}</span>
|
||||
</div>
|
||||
<template slot="action" slot-scope="text, record">
|
||||
<span style="margin-right: 5px">
|
||||
<a-button
|
||||
:disabled="!('copyTemplate' in $store.getters.apis && record.isready)"
|
||||
icon="copy"
|
||||
shape="circle"
|
||||
:loading="copyLoading"
|
||||
@click="showCopyTemplate(record)" />
|
||||
</span>
|
||||
<span style="margin-right: 5px">
|
||||
<a-button
|
||||
:disabled="!('deleteTemplate' in $store.getters.apis)"
|
||||
type="danger"
|
||||
icon="delete"
|
||||
shape="circle"
|
||||
@click="onShowDeleteModal(record)"/>
|
||||
</span>
|
||||
<tooltip-button
|
||||
style="margin-right: 5px"
|
||||
:disabled="!('copyTemplate' in $store.getters.apis && record.isready)"
|
||||
:title="$t('label.action.copy.template')"
|
||||
icon="copy"
|
||||
:loading="copyLoading"
|
||||
@click="showCopyTemplate(record)" />
|
||||
<tooltip-button
|
||||
style="margin-right: 5px"
|
||||
:disabled="!('deleteTemplate' in $store.getters.apis)"
|
||||
:title="$t('label.action.delete.template')"
|
||||
type="danger"
|
||||
icon="delete"
|
||||
@click="onShowDeleteModal(record)"/>
|
||||
</template>
|
||||
</a-table>
|
||||
<a-pagination
|
||||
|
|
@ -134,9 +132,13 @@
|
|||
|
||||
<script>
|
||||
import { api } from '@/api'
|
||||
import TooltipButton from '@/components/view/TooltipButton'
|
||||
|
||||
export default {
|
||||
name: 'TemplateZones',
|
||||
components: {
|
||||
TooltipButton
|
||||
},
|
||||
props: {
|
||||
resource: {
|
||||
type: Object,
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@
|
|||
:cancelText="$t('label.no')"
|
||||
placement="top"
|
||||
>
|
||||
<a-button :disabled="!('releaseDedicatedGuestVlanRange' in $store.getters.apis)" icon="delete" type="danger" shape="circle"></a-button>
|
||||
<tooltip-button :tooltip="$t('label.delete')" :disabled="!('releaseDedicatedGuestVlanRange' in $store.getters.apis)" icon="delete" type="danger" />
|
||||
</a-popconfirm>
|
||||
</template>
|
||||
</a-table>
|
||||
|
|
@ -134,9 +134,13 @@
|
|||
|
||||
<script>
|
||||
import { api } from '@/api'
|
||||
import TooltipButton from '@/components/view/TooltipButton'
|
||||
|
||||
export default {
|
||||
name: 'DedicatedVLANTab',
|
||||
components: {
|
||||
TooltipButton
|
||||
},
|
||||
props: {
|
||||
resource: {
|
||||
type: Object,
|
||||
|
|
|
|||
|
|
@ -39,15 +39,13 @@
|
|||
</template>
|
||||
<template slot="actions" slot-scope="record">
|
||||
<div class="actions">
|
||||
<a-popover placement="bottom">
|
||||
<template slot="content">{{ $t('label.remove.ip.range') }}</template>
|
||||
<a-button
|
||||
:disabled="!('deleteManagementNetworkIpRange' in $store.getters.apis)"
|
||||
icon="delete"
|
||||
shape="circle"
|
||||
type="danger"
|
||||
@click="handleDeleteIpRange(record)"></a-button>
|
||||
</a-popover>
|
||||
<tooltip-button
|
||||
tooltipPlacement="bottom"
|
||||
:tooltip="$t('label.remove.ip.range')"
|
||||
:disabled="!('deleteManagementNetworkIpRange' in $store.getters.apis)"
|
||||
icon="delete"
|
||||
type="danger"
|
||||
@click="handleDeleteIpRange(record)" />
|
||||
</div>
|
||||
</template>
|
||||
</a-table>
|
||||
|
|
@ -125,9 +123,13 @@
|
|||
|
||||
<script>
|
||||
import { api } from '@/api'
|
||||
import TooltipButton from '@/components/view/TooltipButton'
|
||||
|
||||
export default {
|
||||
name: 'IpRangesTabManagement',
|
||||
components: {
|
||||
TooltipButton
|
||||
},
|
||||
props: {
|
||||
resource: {
|
||||
type: Object,
|
||||
|
|
|
|||
|
|
@ -39,34 +39,28 @@
|
|||
</template>
|
||||
<template slot="actions" slot-scope="record">
|
||||
<div class="actions">
|
||||
<a-popover v-if="record.account === 'system'" placement="bottom">
|
||||
<template slot="content">{{ $t('label.add.account') }}</template>
|
||||
<a-button
|
||||
icon="user-add"
|
||||
shape="circle"
|
||||
@click="() => handleOpenAddAccountModal(record)"
|
||||
:disabled="!('dedicatePublicIpRange' in $store.getters.apis)"></a-button>
|
||||
</a-popover>
|
||||
<a-popover
|
||||
<tooltip-button
|
||||
v-if="record.account === 'system'"
|
||||
tooltipPlacement="bottom"
|
||||
:tooltip="$t('label.add.account')"
|
||||
icon="user-add"
|
||||
@click="() => handleOpenAddAccountModal(record)"
|
||||
:disabled="!('dedicatePublicIpRange' in $store.getters.apis)" />
|
||||
<tooltip-button
|
||||
v-else
|
||||
placement="bottom">
|
||||
<template slot="content">{{ $t('label.release.account') }}</template>
|
||||
<a-button
|
||||
icon="user-delete"
|
||||
shape="circle"
|
||||
type="danger"
|
||||
@click="() => handleRemoveAccount(record.id)"
|
||||
:disabled="!('releasePublicIpRange' in $store.getters.apis)"></a-button>
|
||||
</a-popover>
|
||||
<a-popover placement="bottom">
|
||||
<template slot="content">{{ $t('label.remove.ip.range') }}</template>
|
||||
<a-button
|
||||
icon="delete"
|
||||
shape="circle"
|
||||
type="danger"
|
||||
@click="handleDeleteIpRange(record.id)"
|
||||
:disabled="!('deleteVlanIpRange' in $store.getters.apis)"></a-button>
|
||||
</a-popover>
|
||||
tooltipPlacement="bottom"
|
||||
:tooltip="$t('label.release.account')"
|
||||
icon="user-delete"
|
||||
type="danger"
|
||||
@click="() => handleRemoveAccount(record.id)"
|
||||
:disabled="!('releasePublicIpRange' in $store.getters.apis)" />
|
||||
<tooltip-button
|
||||
tooltipPlacement="bottom"
|
||||
:tooltip="$t('label.remove.ip.range')"
|
||||
icon="delete"
|
||||
type="danger"
|
||||
@click="handleDeleteIpRange(record.id)"
|
||||
:disabled="!('deleteVlanIpRange' in $store.getters.apis)" />
|
||||
</div>
|
||||
</template>
|
||||
</a-table>
|
||||
|
|
@ -201,9 +195,13 @@
|
|||
|
||||
<script>
|
||||
import { api } from '@/api'
|
||||
import TooltipButton from '@/components/view/TooltipButton'
|
||||
|
||||
export default {
|
||||
name: 'IpRangesTabPublic',
|
||||
components: {
|
||||
TooltipButton
|
||||
},
|
||||
props: {
|
||||
resource: {
|
||||
type: Object,
|
||||
|
|
|
|||
|
|
@ -38,15 +38,12 @@
|
|||
<div>{{ returnPodName(record.podid) }}</div>
|
||||
</template>
|
||||
<template slot="actions" slot-scope="record">
|
||||
<a-popover placement="bottom">
|
||||
<template slot="content">{{ $t('label.remove.ip.range') }}</template>
|
||||
<a-button
|
||||
:disabled="!('deleteStorageNetworkIpRange' in $store.getters.apis)"
|
||||
icon="delete"
|
||||
shape="circle"
|
||||
type="danger"
|
||||
@click="handleDeleteIpRange(record.id)"></a-button>
|
||||
</a-popover>
|
||||
<tooltip-button
|
||||
:tooltip="$t('label.remove.ip.range')"
|
||||
:disabled="!('deleteStorageNetworkIpRange' in $store.getters.apis)"
|
||||
icon="delete"
|
||||
type="danger"
|
||||
@click="handleDeleteIpRange(record.id)" />
|
||||
</template>
|
||||
</a-table>
|
||||
<a-pagination
|
||||
|
|
@ -120,9 +117,13 @@
|
|||
|
||||
<script>
|
||||
import { api } from '@/api'
|
||||
import TooltipButton from '@/components/view/TooltipButton'
|
||||
|
||||
export default {
|
||||
name: 'IpRangesTabStorage',
|
||||
components: {
|
||||
TooltipButton
|
||||
},
|
||||
props: {
|
||||
resource: {
|
||||
type: Object,
|
||||
|
|
|
|||
|
|
@ -67,18 +67,17 @@
|
|||
{{ $t('label.delete.ciscoasa1000v') }}
|
||||
</span>
|
||||
</template>
|
||||
<a-button
|
||||
<tooltip-button
|
||||
v-if="resource.name==='Ovs'"
|
||||
type="default"
|
||||
shape="circle"
|
||||
:tooltip="$t('label.configure')"
|
||||
icon="setting"
|
||||
size="small"
|
||||
:loading="actionLoading"
|
||||
@click="onConfigureOvs(record)"/>
|
||||
<a-button
|
||||
<tooltip-button
|
||||
v-else
|
||||
:tooltip="$t('label.delete')"
|
||||
type="danger"
|
||||
shape="circle"
|
||||
icon="close"
|
||||
size="small"
|
||||
:loading="actionLoading"
|
||||
|
|
@ -117,10 +116,11 @@
|
|||
<script>
|
||||
import { api } from '@/api'
|
||||
import Status from '@/components/widgets/Status'
|
||||
import TooltipButton from '@/components/view/TooltipButton'
|
||||
|
||||
export default {
|
||||
name: 'ProviderListView',
|
||||
components: { Status },
|
||||
components: { Status, TooltipButton },
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@
|
|||
:pagination="false"
|
||||
style="margin-bottom: 24px; width: 100%" >
|
||||
<template slot="actions" slot-scope="text, record">
|
||||
<a-button type="danger" shape="circle" icon="delete" @click="onDelete(record.key)" />
|
||||
<tooltip-button :tooltip="$t('label.delete')" type="danger" icon="delete" @click="onDelete(record.key)" />
|
||||
</template>
|
||||
<template slot="footer">
|
||||
<a-form
|
||||
|
|
@ -141,9 +141,14 @@
|
|||
</div>
|
||||
</template>
|
||||
<script>
|
||||
|
||||
import TooltipButton from '@/components/view/TooltipButton'
|
||||
import { mixinDevice } from '@/utils/mixin.js'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
TooltipButton
|
||||
},
|
||||
mixins: [mixinDevice],
|
||||
props: {
|
||||
traffic: {
|
||||
|
|
|
|||
|
|
@ -76,16 +76,16 @@
|
|||
{{ traffic.toUpperCase() }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
<a-button
|
||||
class="icon-button"
|
||||
shape="circle"
|
||||
<tooltip-button
|
||||
:tooltip="$t('label.add')"
|
||||
buttonClass="icon-button"
|
||||
icon="plus"
|
||||
size="small"
|
||||
@click="trafficAdded" />
|
||||
<a-button
|
||||
class="icon-button"
|
||||
<tooltip-button
|
||||
:tooltip="$t('label.cancel')"
|
||||
buttonClass="icon-button"
|
||||
type="danger"
|
||||
shape="circle"
|
||||
icon="close"
|
||||
size="small"
|
||||
@click="() => { addingTrafficForKey = null }" />
|
||||
|
|
@ -102,7 +102,7 @@
|
|||
</div>
|
||||
</template>
|
||||
<template slot="actions" slot-scope="text, record">
|
||||
<a-button v-if="physicalNetworks.indexOf(record) > 0" type="danger" shape="circle" icon="delete" @click="onDelete(record)" />
|
||||
<tooltip-button :tooltip="$t('label.delete')" v-if="physicalNetworks.indexOf(record) > 0" type="danger" icon="delete" @click="onDelete(record)" />
|
||||
</template>
|
||||
<template slot="footer" v-if="isAdvancedZone">
|
||||
<a-button
|
||||
|
|
@ -166,7 +166,12 @@
|
|||
</template>
|
||||
<script>
|
||||
|
||||
import TooltipButton from '@/components/view/TooltipButton'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
TooltipButton
|
||||
},
|
||||
props: {
|
||||
prefillContent: {
|
||||
type: Object,
|
||||
|
|
|
|||
|
|
@ -87,9 +87,9 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="list__actions">
|
||||
<a-button shape="circle" icon="tag" @click="() => openTagsModal(acl)"></a-button>
|
||||
<a-button shape="circle" icon="edit" @click="() => openEditRuleModal(acl)"></a-button>
|
||||
<a-button shape="circle" icon="delete" type="danger" :disabled="!('deleteNetworkACL' in $store.getters.apis)" @click="() => handleDeleteRule(acl.id)"></a-button>
|
||||
<tooltip-button :tooltip="$t('label.tags')" icon="tag" @click="() => openTagsModal(acl)" />
|
||||
<tooltip-button :tooltip="$t('label.edit')" icon="edit" @click="() => openEditRuleModal(acl)" />
|
||||
<tooltip-button :tooltip="$t('label.delete')" icon="delete" type="danger" :disabled="!('deleteNetworkACL' in $store.getters.apis)" @click="() => handleDeleteRule(acl.id)" />
|
||||
</div>
|
||||
</div>
|
||||
</transition-group>
|
||||
|
|
@ -198,11 +198,13 @@
|
|||
<script>
|
||||
import { api } from '@/api'
|
||||
import draggable from 'vuedraggable'
|
||||
import TooltipButton from '@/components/view/TooltipButton'
|
||||
|
||||
export default {
|
||||
name: 'AclListRulesTab',
|
||||
components: {
|
||||
draggable
|
||||
draggable,
|
||||
TooltipButton
|
||||
},
|
||||
props: {
|
||||
resource: {
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@
|
|||
{{ record.icmpcode || record.endport >= 0 ? record.icmpcode || record.endport : 'All' }}
|
||||
</template>
|
||||
<template slot="actions" slot-scope="record">
|
||||
<a-button :disabled="!('deleteEgressFirewallRule' in $store.getters.apis)" shape="circle" type="danger" icon="delete" @click="deleteRule(record)" />
|
||||
<tooltip-button :tooltip="$t('label.delete')" :disabled="!('deleteEgressFirewallRule' in $store.getters.apis)" type="danger" icon="delete" @click="deleteRule(record)" />
|
||||
</template>
|
||||
</a-table>
|
||||
<a-pagination
|
||||
|
|
@ -102,9 +102,13 @@
|
|||
|
||||
<script>
|
||||
import { api } from '@/api'
|
||||
import TooltipButton from '@/components/view/TooltipButton'
|
||||
|
||||
export default {
|
||||
name: 'EgressRulesTab',
|
||||
components: {
|
||||
TooltipButton
|
||||
},
|
||||
props: {
|
||||
resource: {
|
||||
type: Object,
|
||||
|
|
|
|||
|
|
@ -74,12 +74,12 @@
|
|||
</template>
|
||||
<template slot="actions" slot-scope="record">
|
||||
<div class="actions">
|
||||
<a-button shape="circle" icon="tag" class="rule-action" @click="() => openTagsModal(record.id)" />
|
||||
<a-button
|
||||
shape="circle"
|
||||
<tooltip-button :tooltip="$t('label.edit.tags')" icon="tag" buttonClass="rule-action" @click="() => openTagsModal(record.id)" />
|
||||
<tooltip-button
|
||||
:tooltip="$t('label.delete')"
|
||||
type="danger"
|
||||
icon="delete"
|
||||
class="rule-action"
|
||||
buttonClass="rule-action"
|
||||
:disabled="!('deleteFirewallRule' in $store.getters.apis)"
|
||||
@click="deleteRule(record)" />
|
||||
</div>
|
||||
|
|
@ -137,8 +137,12 @@
|
|||
|
||||
<script>
|
||||
import { api } from '@/api'
|
||||
import TooltipButton from '@/components/view/TooltipButton'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
TooltipButton
|
||||
},
|
||||
props: {
|
||||
resource: {
|
||||
type: Object,
|
||||
|
|
|
|||
|
|
@ -37,21 +37,17 @@
|
|||
:pagination="false" >
|
||||
|
||||
<template slot="action" slot-scope="text, record">
|
||||
<a-tooltip placement="bottom">
|
||||
<template slot="title">
|
||||
{{ $t('label.action.delete.ip.range') }}
|
||||
</template>
|
||||
<a-popconfirm
|
||||
:title="$t('message.confirm.remove.ip.range')"
|
||||
@confirm="removeIpRange(record.id)"
|
||||
:okText="$t('label.yes')"
|
||||
:cancelText="$t('label.no')" >
|
||||
<a-button
|
||||
type="danger"
|
||||
icon="delete"
|
||||
shape="circle" />
|
||||
</a-popconfirm>
|
||||
</a-tooltip>
|
||||
<a-popconfirm
|
||||
:title="$t('message.confirm.remove.ip.range')"
|
||||
@confirm="removeIpRange(record.id)"
|
||||
:okText="$t('label.yes')"
|
||||
:cancelText="$t('label.no')" >
|
||||
<tooltip-button
|
||||
tooltipPlacement="bottom"
|
||||
:tooltip="$t('label.action.delete.ip.range')"
|
||||
type="danger"
|
||||
icon="delete" />
|
||||
</a-popconfirm>
|
||||
</template>
|
||||
|
||||
</a-table>
|
||||
|
|
@ -92,10 +88,12 @@
|
|||
<script>
|
||||
import { api } from '@/api'
|
||||
import CreateVlanIpRange from '@/views/network/CreateVlanIpRange'
|
||||
import TooltipButton from '@/components/view/TooltipButton'
|
||||
export default {
|
||||
name: 'GuestIpRanges',
|
||||
components: {
|
||||
CreateVlanIpRange
|
||||
CreateVlanIpRange,
|
||||
TooltipButton
|
||||
},
|
||||
props: {
|
||||
resource: {
|
||||
|
|
|
|||
|
|
@ -106,14 +106,19 @@
|
|||
<div v-else>{{ text }}</div>
|
||||
</template>
|
||||
<template slot="actions" slot-scope="record">
|
||||
<a-button shape="circle" icon="tag" class="rule-action" @click="() => openTagsModal(record)" />
|
||||
<tooltip-button :tooltip="$t('label.edit.tags')" icon="tag" buttonClass="rule-action" @click="() => openTagsModal(record)" />
|
||||
<a-popconfirm
|
||||
:title="$t('label.delete') + '?'"
|
||||
@confirm="handleDeleteRule(record)"
|
||||
:okText="$t('label.yes')"
|
||||
:cancelText="$t('label.no')"
|
||||
>
|
||||
<a-button :disabled="!('revokeSecurityGroupIngress' in $store.getters.apis) && !('revokeSecurityGroupEgress' in $store.getters.apis)" shape="circle" type="danger" icon="delete" class="rule-action" />
|
||||
<tooltip-button
|
||||
:disabled="!('revokeSecurityGroupIngress' in $store.getters.apis) && !('revokeSecurityGroupEgress' in $store.getters.apis)"
|
||||
:tooltip="$t('label.delete')"
|
||||
type="danger"
|
||||
icon="delete"
|
||||
buttonClass="rule-action" />
|
||||
</a-popconfirm>
|
||||
</template>
|
||||
</a-table>
|
||||
|
|
@ -165,8 +170,12 @@
|
|||
|
||||
<script>
|
||||
import { api } from '@/api'
|
||||
import TooltipButton from '@/components/view/TooltipButton'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
TooltipButton
|
||||
},
|
||||
props: {
|
||||
resource: {
|
||||
type: Object,
|
||||
|
|
|
|||
|
|
@ -36,10 +36,10 @@
|
|||
</span>
|
||||
</template>
|
||||
<template slot="remove" slot-scope="text, record">
|
||||
<a-button
|
||||
<tooltip-button
|
||||
:tooltip="$t('label.remove.vm.from.lb')"
|
||||
type="danger"
|
||||
icon="delete"
|
||||
shape="circle"
|
||||
@click="removeVmFromLB(record)" />
|
||||
</template>
|
||||
<a-divider />
|
||||
|
|
@ -63,9 +63,13 @@
|
|||
</template>
|
||||
<script>
|
||||
import { api } from '@/api'
|
||||
import TooltipButton from '@/components/view/TooltipButton'
|
||||
|
||||
export default {
|
||||
name: 'InternalLBAssignedVmTab',
|
||||
components: {
|
||||
TooltipButton
|
||||
},
|
||||
props: {
|
||||
resource: {
|
||||
type: Object,
|
||||
|
|
|
|||
|
|
@ -72,11 +72,11 @@
|
|||
</template>
|
||||
|
||||
<template slot="action" slot-scope="text, record">
|
||||
<a-button
|
||||
<tooltip-button
|
||||
v-if="record.issourcenat !== true && record.forvirtualnetwork === true"
|
||||
:tooltip="$t('label.action.release.ip')"
|
||||
type="danger"
|
||||
icon="delete"
|
||||
shape="circle"
|
||||
:disabled="!('disassociateIpAddress' in $store.getters.apis)"
|
||||
@click="releaseIpAddress(record)" />
|
||||
</template>
|
||||
|
|
@ -129,11 +129,13 @@
|
|||
<script>
|
||||
import { api } from '@/api'
|
||||
import Status from '@/components/widgets/Status'
|
||||
import TooltipButton from '@/components/view/TooltipButton'
|
||||
|
||||
export default {
|
||||
name: 'IpAddressesTab',
|
||||
components: {
|
||||
Status
|
||||
Status,
|
||||
TooltipButton
|
||||
},
|
||||
props: {
|
||||
resource: {
|
||||
|
|
|
|||
|
|
@ -99,8 +99,8 @@
|
|||
</router-link>
|
||||
</div>
|
||||
<div>{{ ip }}</div>
|
||||
<a-button
|
||||
shape="circle"
|
||||
<tooltip-button
|
||||
:tooltip="$t('label.action.delete.load.balancer')"
|
||||
type="danger"
|
||||
icon="delete"
|
||||
@click="() => handleDeleteInstanceFromRule(instance, record, ip)" />
|
||||
|
|
@ -110,15 +110,15 @@
|
|||
</template>
|
||||
<template slot="actions" slot-scope="record">
|
||||
<div class="actions">
|
||||
<a-button shape="circle" icon="edit" @click="() => openEditRuleModal(record)"></a-button>
|
||||
<a-button :disabled="!('updateLoadBalancerRule' in $store.getters.apis)" shape="circle" icon="tag" @click="() => openTagsModal(record.id)" />
|
||||
<tooltip-button :tooltip="$t('label.edit')" icon="edit" @click="() => openEditRuleModal(record)" />
|
||||
<tooltip-button :tooltip="$t('label.edit.tags')" :disabled="!('updateLoadBalancerRule' in $store.getters.apis)" icon="tag" @click="() => openTagsModal(record.id)" />
|
||||
<a-popconfirm
|
||||
:title="$t('label.delete') + '?'"
|
||||
@confirm="handleDeleteRule(record)"
|
||||
:okText="$t('label.yes')"
|
||||
:cancelText="$t('label.no')"
|
||||
>
|
||||
<a-button :disabled="!('deleteLoadBalancerRule' in $store.getters.apis)" shape="circle" type="danger" icon="delete" />
|
||||
<tooltip-button :tooltip="$t('label.delete')" :disabled="!('deleteLoadBalancerRule' in $store.getters.apis)" type="danger" icon="delete" />
|
||||
</a-popconfirm>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -384,11 +384,13 @@
|
|||
<script>
|
||||
import { api } from '@/api'
|
||||
import Status from '@/components/widgets/Status'
|
||||
import TooltipButton from '@/components/view/TooltipButton'
|
||||
|
||||
export default {
|
||||
name: 'LoadBalancing',
|
||||
components: {
|
||||
Status
|
||||
Status,
|
||||
TooltipButton
|
||||
},
|
||||
props: {
|
||||
resource: {
|
||||
|
|
|
|||
|
|
@ -97,12 +97,12 @@
|
|||
</template>
|
||||
<template slot="actions" slot-scope="record">
|
||||
<div class="actions">
|
||||
<a-button shape="circle" icon="tag" class="rule-action" @click="() => openTagsModal(record.id)" />
|
||||
<a-button
|
||||
shape="circle"
|
||||
<tooltip-button :tooltip="$t('label.tags')" icon="tag" buttonClass="rule-action" @click="() => openTagsModal(record.id)" />
|
||||
<tooltip-button
|
||||
:tooltip="$t('label.remove.rule')"
|
||||
type="danger"
|
||||
icon="delete"
|
||||
class="rule-action"
|
||||
buttonClass="rule-action"
|
||||
:disabled="!('deletePortForwardingRule' in $store.getters.apis)"
|
||||
@click="deleteRule(record)" />
|
||||
</div>
|
||||
|
|
@ -252,10 +252,12 @@
|
|||
<script>
|
||||
import { api } from '@/api'
|
||||
import Status from '@/components/widgets/Status'
|
||||
import TooltipButton from '@/components/view/TooltipButton'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Status
|
||||
Status,
|
||||
TooltipButton
|
||||
},
|
||||
props: {
|
||||
resource: {
|
||||
|
|
|
|||
|
|
@ -29,8 +29,8 @@
|
|||
<div>{{ route.cidr }}</div>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<a-button shape="circle" icon="tag" @click="() => openTagsModal(route)"></a-button>
|
||||
<a-button :disabled="!('deleteStaticRoute' in $store.getters.apis)" shape="circle" icon="delete" type="danger" @click="() => handleDelete(route)"></a-button>
|
||||
<tooltip-button :tooltip="$t('label.edit.tags')" icon="tag" @click="() => openTagsModal(route)" />
|
||||
<tooltip-button :tooltip="$t('label.delete')" :disabled="!('deleteStaticRoute' in $store.getters.apis)" icon="delete" type="danger" @click="() => handleDelete(route)" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -76,9 +76,13 @@
|
|||
|
||||
<script>
|
||||
import { api } from '@/api'
|
||||
import TooltipButton from '@/components/view/TooltipButton'
|
||||
|
||||
export default {
|
||||
name: 'StaticRoutesTab',
|
||||
components: {
|
||||
TooltipButton
|
||||
},
|
||||
props: {
|
||||
resource: {
|
||||
type: Object,
|
||||
|
|
|
|||
|
|
@ -33,43 +33,30 @@
|
|||
{{ getProjectRole(record) }}
|
||||
</span>
|
||||
<span v-if="imProjectAdmin && dataSource.length > 1" slot="action" slot-scope="text, record" class="account-button-action">
|
||||
<a-tooltip
|
||||
slot="title"
|
||||
placement="top"
|
||||
:title="record.userid ? $t('label.make.user.project.owner') : $t('label.make.project.owner')">
|
||||
<a-button
|
||||
v-if="record.role !== owner"
|
||||
type="default"
|
||||
shape="circle"
|
||||
icon="arrow-up"
|
||||
size="small"
|
||||
@click="promoteAccount(record)" />
|
||||
</a-tooltip>
|
||||
<a-tooltip
|
||||
slot="title"
|
||||
placement="top"
|
||||
:title="record.userid ? $t('label.demote.project.owner.user') : $t('label.demote.project.owner')"
|
||||
v-if="updateProjectApi.params.filter(x => x.name === 'swapowner').length > 0">
|
||||
<a-button
|
||||
v-if="record.role === owner"
|
||||
type="default"
|
||||
shape="circle"
|
||||
icon="arrow-down"
|
||||
size="small"
|
||||
@click="demoteAccount(record)" />
|
||||
</a-tooltip>
|
||||
<a-tooltip
|
||||
slot="title"
|
||||
placement="top"
|
||||
:title="record.userid ? $t('label.remove.project.user') : $t('label.remove.project.account')">
|
||||
<a-button
|
||||
type="danger"
|
||||
shape="circle"
|
||||
icon="delete"
|
||||
size="small"
|
||||
:disabled="!('deleteAccountFromProject' in $store.getters.apis)"
|
||||
@click="onShowConfirmDelete(record)"/>
|
||||
</a-tooltip>
|
||||
<tooltip-button
|
||||
tooltipPlacement="top"
|
||||
:tooltip="record.userid ? $t('label.make.user.project.owner') : $t('label.make.project.owner')"
|
||||
v-if="record.role !== owner"
|
||||
type="default"
|
||||
icon="arrow-up"
|
||||
size="small"
|
||||
@click="promoteAccount(record)" />
|
||||
<tooltip-button
|
||||
tooltipPlacement="top"
|
||||
:tooltip="record.userid ? $t('label.demote.project.owner.user') : $t('label.demote.project.owner')"
|
||||
v-if="updateProjectApi.params.filter(x => x.name === 'swapowner').length > 0 && record.role === owner"
|
||||
type="default"
|
||||
icon="arrow-down"
|
||||
size="small"
|
||||
@click="demoteAccount(record)" />
|
||||
<tooltip-button
|
||||
tooltipPlacement="top"
|
||||
:tooltip="record.userid ? $t('label.remove.project.user') : $t('label.remove.project.account')"
|
||||
type="danger"
|
||||
icon="delete"
|
||||
size="small"
|
||||
:disabled="!('deleteAccountFromProject' in $store.getters.apis)"
|
||||
@click="onShowConfirmDelete(record)" />
|
||||
</span>
|
||||
</a-table>
|
||||
<a-pagination
|
||||
|
|
@ -94,9 +81,13 @@
|
|||
|
||||
<script>
|
||||
import { api } from '@/api'
|
||||
import TooltipButton from '@/components/view/TooltipButton'
|
||||
|
||||
export default {
|
||||
name: 'AccountsTab',
|
||||
components: {
|
||||
TooltipButton
|
||||
},
|
||||
props: {
|
||||
resource: {
|
||||
type: Object,
|
||||
|
|
|
|||
|
|
@ -40,28 +40,20 @@
|
|||
<status :text="text ? text : ''" displayText />
|
||||
</template>
|
||||
<span slot="action" v-if="record.state===stateAllow" slot-scope="text, record" class="account-button-action">
|
||||
<a-tooltip placement="top">
|
||||
<template slot="title">
|
||||
{{ $t('label.accept.project.invitation') }}
|
||||
</template>
|
||||
<a-button
|
||||
type="success"
|
||||
shape="circle"
|
||||
icon="check"
|
||||
size="small"
|
||||
@click="onShowConfirmAcceptInvitation(record)"/>
|
||||
</a-tooltip>
|
||||
<a-tooltip placement="top">
|
||||
<template slot="title">
|
||||
{{ $t('label.decline.invitation') }}
|
||||
</template>
|
||||
<a-button
|
||||
type="danger"
|
||||
shape="circle"
|
||||
icon="close"
|
||||
size="small"
|
||||
@click="onShowConfirmRevokeInvitation(record)"/>
|
||||
</a-tooltip>
|
||||
<tooltip-button
|
||||
tooltipPlacement="top"
|
||||
:tooltip="$t('label.accept.project.invitation')"
|
||||
type="success"
|
||||
icon="check"
|
||||
size="small"
|
||||
@click="onShowConfirmAcceptInvitation(record)"/>
|
||||
<tooltip-button
|
||||
tooltipPlacement="top"
|
||||
:tooltip="$t('label.decline.invitation')"
|
||||
type="danger"
|
||||
icon="close"
|
||||
size="small"
|
||||
@click="onShowConfirmRevokeInvitation(record)"/>
|
||||
</span>
|
||||
</a-table>
|
||||
<a-pagination
|
||||
|
|
@ -87,11 +79,13 @@
|
|||
<script>
|
||||
import { api } from '@/api'
|
||||
import Status from '@/components/widgets/Status'
|
||||
import TooltipButton from '@/components/view/TooltipButton'
|
||||
|
||||
export default {
|
||||
name: 'InvitationsTemplate',
|
||||
components: {
|
||||
Status
|
||||
Status,
|
||||
TooltipButton
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -46,19 +46,12 @@
|
|||
<a-input v-model="newRuleDescription" placeholder="Description"></a-input>
|
||||
</div>
|
||||
<div class="rules-table__col rules-table__col--actions">
|
||||
<a-tooltip
|
||||
placement="bottom">
|
||||
<template slot="title">
|
||||
Save new Rule
|
||||
</template>
|
||||
<a-button
|
||||
icon="plus"
|
||||
type="primary"
|
||||
shape="circle"
|
||||
@click="onRuleSave"
|
||||
>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<tooltip-button
|
||||
tooltipPlacement="bottom"
|
||||
:tooltip="$t('label.save.new.rule')"
|
||||
icon="plus"
|
||||
type="primary"
|
||||
@click="onRuleSave" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -109,13 +102,15 @@ import { api } from '@/api'
|
|||
import draggable from 'vuedraggable'
|
||||
import PermissionEditable from '@/views/iam/PermissionEditable'
|
||||
import RuleDelete from '@/views/iam/RuleDelete'
|
||||
import TooltipButton from '@/components/view/TooltipButton'
|
||||
|
||||
export default {
|
||||
name: 'ProjectRolePermissionTab',
|
||||
components: {
|
||||
RuleDelete,
|
||||
PermissionEditable,
|
||||
draggable
|
||||
draggable,
|
||||
TooltipButton
|
||||
},
|
||||
props: {
|
||||
resource: {
|
||||
|
|
|
|||
|
|
@ -36,29 +36,20 @@
|
|||
{{ record }}
|
||||
</template>
|
||||
<span slot="action" slot-scope="text, record">
|
||||
<a-tooltip placement="top">
|
||||
<template slot="title">
|
||||
{{ $t('label.update.project.role') }}
|
||||
</template>
|
||||
<a-button
|
||||
type="default"
|
||||
shape="circle"
|
||||
icon="edit"
|
||||
size="small"
|
||||
style="margin:10px"
|
||||
@click="openUpdateModal(record)" />
|
||||
</a-tooltip>
|
||||
<a-tooltip placement="top">
|
||||
<template slot="title">
|
||||
{{ $t('label.remove.project.role') }}
|
||||
</template>
|
||||
<a-button
|
||||
type="danger"
|
||||
shape="circle"
|
||||
icon="delete"
|
||||
size="small"
|
||||
@click="deleteProjectRole(record)"/>
|
||||
</a-tooltip>
|
||||
<tooltip-button
|
||||
tooltipPlacement="top"
|
||||
:tooltip="$t('label.update.project.role')"
|
||||
icon="edit"
|
||||
size="small"
|
||||
style="margin:10px"
|
||||
@click="openUpdateModal(record)" />
|
||||
<tooltip-button
|
||||
tooltipPlacement="top"
|
||||
:tooltip="$t('label.remove.project.role')"
|
||||
type="danger"
|
||||
icon="delete"
|
||||
size="small"
|
||||
@click="deleteProjectRole(record)" />
|
||||
</span>
|
||||
</a-table>
|
||||
<a-modal
|
||||
|
|
@ -82,29 +73,20 @@
|
|||
<a-button type="primary" @click="updateProjectRole" :loading="loading">{{ $t('label.ok') }}</a-button>
|
||||
</div>
|
||||
<span slot="action" slot-scope="text, record">
|
||||
<a-tooltip placement="top">
|
||||
<template slot="title">
|
||||
{{ $t('label.update.project.role') }}
|
||||
</template>
|
||||
<a-button
|
||||
type="default"
|
||||
shape="circle"
|
||||
icon="edit"
|
||||
size="small"
|
||||
style="margin:10px"
|
||||
@click="openUpdateModal(record)" />
|
||||
</a-tooltip>
|
||||
<a-tooltip placement="top">
|
||||
<template slot="title">
|
||||
{{ $t('label.remove.project.role') }}
|
||||
</template>
|
||||
<a-button
|
||||
type="danger"
|
||||
shape="circle"
|
||||
icon="delete"
|
||||
size="small"
|
||||
@click="deleteProjectRole(record)"/>
|
||||
</a-tooltip>
|
||||
<tooltip-button
|
||||
tooltipPlacement="top"
|
||||
:tooltip="$t('label.update.project.role')"
|
||||
icon="edit"
|
||||
size="small"
|
||||
style="margin:10px"
|
||||
@click="openUpdateModal(record)" />
|
||||
<tooltip-button
|
||||
tooltipPlacement="top"
|
||||
:tooltip="$t('label.remove.project.role')"
|
||||
type="danger"
|
||||
icon="edit"
|
||||
size="small"
|
||||
@click="deleteProjectRole(record)" />
|
||||
</span>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
|
|
@ -139,6 +121,7 @@
|
|||
<script>
|
||||
import { api } from '@/api'
|
||||
import ProjectRolePermissionTab from '@/views/project/iam/ProjectRolePermissionTab'
|
||||
import TooltipButton from '@/components/view/TooltipButton'
|
||||
export default {
|
||||
name: 'ProjectRoleTab',
|
||||
props: {
|
||||
|
|
@ -148,7 +131,8 @@ export default {
|
|||
}
|
||||
},
|
||||
components: {
|
||||
ProjectRolePermissionTab
|
||||
ProjectRolePermissionTab,
|
||||
TooltipButton
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -163,12 +163,8 @@
|
|||
<a-input ref="input" :value="inputKey" @change="handleKeyChange" style="width: 100px; text-align: center" :placeholder="$t('label.key')" />
|
||||
<a-input style=" width: 30px; border-left: 0; pointer-events: none; backgroundColor: #fff" placeholder="=" disabled />
|
||||
<a-input :value="inputValue" @change="handleValueChange" style="width: 100px; text-align: center; border-left: 0" :placeholder="$t('label.value')" />
|
||||
<a-button shape="circle" size="small" @click="handleInputConfirm">
|
||||
<a-icon type="check"/>
|
||||
</a-button>
|
||||
<a-button shape="circle" size="small" @click="inputVisible=false">
|
||||
<a-icon type="close"/>
|
||||
</a-button>
|
||||
<tooltip-button :tooltip="$t('label.ok')" icon="check" size="small" @click="handleInputConfirm" />
|
||||
<tooltip-button :tooltip="$t('label.cancel')" icon="close" size="small" @click="inputVisible=false" />
|
||||
</a-input-group>
|
||||
</div>
|
||||
<a-tag v-else @click="showInput" style="background: #fff; borderStyle: dashed;">
|
||||
|
|
@ -197,11 +193,15 @@
|
|||
|
||||
<script>
|
||||
import { api } from '@/api'
|
||||
import TooltipButton from '@/components/view/TooltipButton'
|
||||
import { timeZone } from '@/utils/timezone'
|
||||
import debounce from 'lodash/debounce'
|
||||
|
||||
export default {
|
||||
name: 'FormSchedule',
|
||||
components: {
|
||||
TooltipButton
|
||||
},
|
||||
props: {
|
||||
loading: {
|
||||
type: Boolean,
|
||||
|
|
|
|||
|
|
@ -61,18 +61,14 @@
|
|||
<a-tag v-for="(tag, index) in record.tags" :key="index">{{ tag.key + '=' + tag.value }}</a-tag>
|
||||
</div>
|
||||
<div slot="action" class="account-button-action" slot-scope="text, record">
|
||||
<a-tooltip placement="top">
|
||||
<template slot="title">
|
||||
{{ $t('label.delete') }}
|
||||
</template>
|
||||
<a-button
|
||||
type="danger"
|
||||
shape="circle"
|
||||
icon="close"
|
||||
size="small"
|
||||
:loading="actionLoading"
|
||||
@click="handleClickDelete(record)"/>
|
||||
</a-tooltip>
|
||||
<tooltip-button
|
||||
tooltipPlacement="top"
|
||||
:tooltip="$t('label.delete')"
|
||||
type="danger"
|
||||
icon="close"
|
||||
size="small"
|
||||
:loading="actionLoading"
|
||||
@click="handleClickDelete(record)" />
|
||||
</div>
|
||||
</a-table>
|
||||
</div>
|
||||
|
|
@ -80,10 +76,14 @@
|
|||
|
||||
<script>
|
||||
import { api } from '@/api'
|
||||
import TooltipButton from '@/components/view/TooltipButton'
|
||||
import { timeZoneName } from '@/utils/timezone'
|
||||
|
||||
export default {
|
||||
name: 'ScheduledSnapshots',
|
||||
components: {
|
||||
TooltipButton
|
||||
},
|
||||
props: {
|
||||
loading: {
|
||||
type: Boolean,
|
||||
|
|
|
|||
|
|
@ -64,12 +64,8 @@
|
|||
<a-input ref="input" :value="inputKey" @change="handleKeyChange" style="width: 100px; text-align: center" :placeholder="$t('label.key')" />
|
||||
<a-input style=" width: 30px; border-left: 0; pointer-events: none; backgroundColor: #fff" placeholder="=" disabled />
|
||||
<a-input :value="inputValue" @change="handleValueChange" style="width: 100px; text-align: center; border-left: 0" :placeholder="$t('label.value')" />
|
||||
<a-button shape="circle" size="small" @click="handleInputConfirm">
|
||||
<a-icon type="check"/>
|
||||
</a-button>
|
||||
<a-button shape="circle" size="small" @click="inputVisible=false">
|
||||
<a-icon type="close"/>
|
||||
</a-button>
|
||||
<tooltip-button :tooltip="$t('label.ok')" icon="check" size="small" @click="handleInputConfirm" />
|
||||
<tooltip-button :tooltip="$t('label.cancel')" icon="close" size="small" @click="inputVisible=false" />
|
||||
</a-input-group>
|
||||
</div>
|
||||
<a-tag v-else @click="showInput" style="background: #fff; borderStyle: dashed;">
|
||||
|
|
@ -97,9 +93,13 @@
|
|||
|
||||
<script>
|
||||
import { api } from '@/api'
|
||||
import TooltipButton from '@/components/view/TooltipButton'
|
||||
|
||||
export default {
|
||||
name: 'TakeSnapshot',
|
||||
components: {
|
||||
TooltipButton
|
||||
},
|
||||
props: {
|
||||
loading: {
|
||||
type: Boolean,
|
||||
|
|
|
|||
Loading…
Reference in New Issue