Merge remote-tracking branch 'origin/4.16'

Signed-off-by: Rohit Yadav <rohit.yadav@shapeblue.com>
This commit is contained in:
Rohit Yadav 2021-12-30 16:52:30 +05:30
commit c84198d76d
102 changed files with 737 additions and 286 deletions

2
debian/control vendored
View File

@ -22,7 +22,7 @@ Description: CloudStack server library
Package: cloudstack-agent
Architecture: all
Depends: ${python:Depends}, ${python3:Depends}, openjdk-11-jre-headless | java11-runtime-headless | java11-runtime | openjdk-11-jre-headless | zulu-11, cloudstack-common (= ${source:Version}), lsb-base (>= 9), openssh-client, qemu-kvm (>= 2.5), libvirt-bin (>= 1.3) | libvirt-daemon-system (>= 3.0), iproute2, ebtables, vlan, ipset, python3-libvirt, ethtool, iptables, lsb-release, aria2, ufw, apparmor
Depends: ${python:Depends}, ${python3:Depends}, openjdk-11-jre-headless | java11-runtime-headless | java11-runtime | openjdk-11-jre-headless | zulu-11, cloudstack-common (= ${source:Version}), lsb-base (>= 9), openssh-client, qemu-kvm (>= 2.5) | qemu-system-x86 (>= 5.2), libvirt-bin (>= 1.3) | libvirt-daemon-system (>= 3.0), iproute2, ebtables, vlan, ipset, python3-libvirt, ethtool, iptables, lsb-release, aria2, ufw, apparmor
Recommends: init-system-helpers
Conflicts: cloud-agent, cloud-agent-libs, cloud-agent-deps, cloud-agent-scripts
Description: CloudStack agent

View File

@ -3714,10 +3714,6 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
return null;
}
if (_guestCpuArch != null && _guestCpuArch.equals("aarch64")) {
return DiskDef.DiskBus.SCSI;
}
String rootDiskController = details.get(VmDetailConstants.ROOT_DISK_CONTROLLER);
if (StringUtils.isNotBlank(rootDiskController)) {
s_logger.debug("Passed custom disk controller for ROOT disk " + rootDiskController);
@ -3751,10 +3747,6 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
}
private DiskDef.DiskBus getGuestDiskModel(final String platformEmulator, boolean isUefiEnabled) {
if (_guestCpuArch != null && _guestCpuArch.equals("aarch64")) {
return DiskDef.DiskBus.SCSI;
}
if (platformEmulator == null) {
return DiskDef.DiskBus.IDE;
} else if (platformEmulator.startsWith("Other PV Virtio-SCSI")) {
@ -3765,6 +3757,8 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
return DiskDef.DiskBus.VIRTIO;
} else if (isUefiEnabled && StringUtils.startsWithAny(platformEmulator, "Windows", "Other")) {
return DiskDef.DiskBus.SATA;
} else if (_guestCpuArch != null && _guestCpuArch.equals("aarch64")) {
return DiskDef.DiskBus.SCSI;
} else {
return DiskDef.DiskBus.IDE;
}

View File

@ -420,7 +420,7 @@ public class LibvirtComputingResourceTest {
public void testCreateDevicesWithSCSIDisk() {
VirtualMachineTO to = createDefaultVM(false);
to.setDetails(new HashMap<>());
libvirtComputingResourceSpy._guestCpuArch = "aarch64";
to.setPlatformEmulator("Other PV Virtio-SCSI");
GuestDef guest = new GuestDef();
guest.setGuestType(GuestType.KVM);

View File

@ -629,7 +629,7 @@ public class ScaleIOPrimaryDataStoreDriver implements PrimaryDataStoreDriver {
private Answer copyTemplateToVolume(DataObject srcData, DataObject destData, Host destHost) {
// Copy PowerFlex/ScaleIO template to volume
LOGGER.debug("Initiating copy from PowerFlex template volume on host " + destHost != null ? destHost.getId() : "");
LOGGER.debug(String.format("Initiating copy from PowerFlex template volume on host %s", destHost != null ? destHost.getId() : "<not specified>"));
int primaryStorageDownloadWait = StorageManager.PRIMARY_STORAGE_DOWNLOAD_WAIT.value();
CopyCommand cmd = new CopyCommand(srcData.getTO(), destData.getTO(), primaryStorageDownloadWait, VirtualMachineManager.ExecuteInSequence.value());
@ -648,7 +648,7 @@ public class ScaleIOPrimaryDataStoreDriver implements PrimaryDataStoreDriver {
private Answer copyVolume(DataObject srcData, DataObject destData, Host destHost) {
// Copy PowerFlex/ScaleIO volume
LOGGER.debug("Initiating copy from PowerFlex volume on host " + destHost != null ? destHost.getId() : "");
LOGGER.debug(String.format("Initiating copy from PowerFlex template volume on host %s", destHost != null ? destHost.getId() : "<not specified>"));
String value = configDao.getValue(Config.CopyVolumeWait.key());
int copyVolumeWait = NumbersUtil.parseInt(value, Integer.parseInt(Config.CopyVolumeWait.getDefaultValue()));

View File

@ -3152,6 +3152,7 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q
SearchCriteria<ServiceOfferingJoinVO> cpuSearchCriteria = _srvOfferingJoinDao.createSearchCriteria();
cpuSearchCriteria.addOr("minCpu", Op.NULL);
cpuSearchCriteria.addOr("constraints", Op.SC, cpuConstraintSearchCriteria);
cpuSearchCriteria.addOr("minCpu", Op.GTEQ, cpuNumber);
sc.addAnd("cpuConstraints", SearchCriteria.Op.SC, cpuSearchCriteria);
}
@ -3164,6 +3165,7 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q
SearchCriteria<ServiceOfferingJoinVO> memSearchCriteria = _srvOfferingJoinDao.createSearchCriteria();
memSearchCriteria.addOr("minMemory", Op.NULL);
memSearchCriteria.addOr("memconstraints", Op.SC, memoryConstraintSearchCriteria);
memSearchCriteria.addOr("minMemory", Op.GTEQ, memory);
sc.addAnd("memoryConstraints", SearchCriteria.Op.SC, memSearchCriteria);
}
@ -3171,7 +3173,7 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q
if (cpuSpeed != null) {
SearchCriteria<ServiceOfferingJoinVO> cpuSpeedSearchCriteria = _srvOfferingJoinDao.createSearchCriteria();
cpuSpeedSearchCriteria.addOr("speed", Op.NULL);
cpuSpeedSearchCriteria.addOr("speed", Op.EQ, cpuSpeed);
cpuSpeedSearchCriteria.addOr("speed", Op.GTEQ, cpuSpeed);
sc.addAnd("cpuspeedconstraints", SearchCriteria.Op.SC, cpuSpeedSearchCriteria);
}

View File

@ -259,9 +259,17 @@ public abstract class LibvirtServerDiscoverer extends DiscovererBase implements
sshConnection = new Connection(agentIp, 22);
sshConnection.connect(null, 60000, 60000);
final String privateKey = _configDao.getValue("ssh.privatekey");
if (!SSHCmdHelper.acquireAuthorizedConnectionWithPublicKey(sshConnection, username, privateKey)) {
s_logger.error("Failed to authenticate with ssh key");
if (org.apache.commons.lang3.StringUtils.isEmpty(password)) {
throw new DiscoveredWithErrorException("Authentication error with ssh private key");
}
if (!sshConnection.authenticateWithPassword(username, password)) {
s_logger.debug("Failed to authenticate");
throw new DiscoveredWithErrorException("Authentication error");
s_logger.error("Failed to authenticate with password");
throw new DiscoveredWithErrorException("Authentication error with host password");
}
}
if (!SSHCmdHelper.sshExecuteCmd(sshConnection, "ls /dev/kvm")) {

View File

@ -165,7 +165,8 @@ import com.cloud.storage.dao.StoragePoolHostDao;
import com.cloud.storage.dao.VMTemplateDao;
import com.cloud.user.Account;
import com.cloud.user.AccountManager;
import com.cloud.utils.Pair;
import com.cloud.utils.Ternary;
import com.cloud.utils.StringUtils;
import com.cloud.utils.UriUtils;
import com.cloud.utils.component.Manager;
import com.cloud.utils.component.ManagerBase;
@ -695,10 +696,17 @@ public class ResourceManagerImpl extends ManagerBase implements ResourceManager,
throw new InvalidParameterValueException("Can't specify cluster without specifying the pod");
}
List<String> skipList = Arrays.asList(HypervisorType.VMware.name().toLowerCase(Locale.ROOT), Type.SecondaryStorage.name().toLowerCase(Locale.ROOT));
if (!skipList.contains(hypervisorType.toLowerCase(Locale.ROOT)) &&
(StringUtils.isAnyEmpty(username, password))) {
if (!skipList.contains(hypervisorType.toLowerCase(Locale.ROOT))) {
if (HypervisorType.KVM.toString().equalsIgnoreCase(hypervisorType)) {
if (org.apache.commons.lang3.StringUtils.isBlank(username)) {
throw new InvalidParameterValueException("Username need to be provided.");
}
} else {
if (org.apache.commons.lang3.StringUtils.isBlank(username) || org.apache.commons.lang3.StringUtils.isBlank(password)) {
throw new InvalidParameterValueException("Username and Password need to be provided.");
}
}
}
if (clusterId != null) {
if (_clusterDao.findById(clusterId) == null) {
@ -2731,8 +2739,8 @@ public class ResourceManagerImpl extends ManagerBase implements ResourceManager,
}
final boolean sshToAgent = Boolean.parseBoolean(_configDao.getValue(KvmSshToAgentEnabled.key()));
if (sshToAgent) {
Pair<String, String> credentials = getHostCredentials(host);
connectAndRestartAgentOnHost(host, credentials.first(), credentials.second());
Ternary<String, String, String> credentials = getHostCredentials(host);
connectAndRestartAgentOnHost(host, credentials.first(), credentials.second(), credentials.third());
} else {
throw new CloudRuntimeException("SSH access is disabled, cannot cancel maintenance mode as " +
"host agent is not connected");
@ -2743,22 +2751,23 @@ public class ResourceManagerImpl extends ManagerBase implements ResourceManager,
* Get host credentials
* @throws CloudRuntimeException if username or password are not found
*/
protected Pair<String, String> getHostCredentials(HostVO host) {
protected Ternary<String, String, String> getHostCredentials(HostVO host) {
_hostDao.loadDetails(host);
final String password = host.getDetail("password");
final String username = host.getDetail("username");
if (password == null || username == null) {
throw new CloudRuntimeException("SSH to agent is enabled, but username/password credentials are not found");
final String privateKey = _configDao.getValue("ssh.privatekey");
if ((password == null && privateKey == null) || username == null) {
throw new CloudRuntimeException("SSH to agent is enabled, but username and password or private key are not found");
}
return new Pair<>(username, password);
return new Ternary<>(username, password, privateKey);
}
/**
* True if agent is restarted via SSH. Assumes kvm.ssh.to.agent = true and host status is not Up
*/
protected void connectAndRestartAgentOnHost(HostVO host, String username, String password) {
protected void connectAndRestartAgentOnHost(HostVO host, String username, String password, String privateKey) {
final com.trilead.ssh2.Connection connection = SSHCmdHelper.acquireAuthorizedConnection(
host.getPrivateIpAddress(), 22, username, password);
host.getPrivateIpAddress(), 22, username, password, privateKey);
if (connection == null) {
throw new CloudRuntimeException(String.format("SSH to agent is enabled, but failed to connect to %s via IP address [%s].", host, host.getPrivateIpAddress()));
}

View File

@ -2136,6 +2136,7 @@ public class StorageManagerImpl extends ManagerBase implements StorageManager, C
if (hostIds.isEmpty()) {
return null;
}
Collections.shuffle(hostIds);
for (Long hostId : hostIds) {
Host host = _hostDao.findById(hostId);

View File

@ -67,7 +67,7 @@ import com.cloud.host.Status;
import com.cloud.host.dao.HostDao;
import com.cloud.hypervisor.Hypervisor;
import com.cloud.storage.StorageManager;
import com.cloud.utils.Pair;
import com.cloud.utils.Ternary;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.utils.fsm.NoTransitionException;
import com.cloud.utils.ssh.SSHCmdHelper;
@ -125,6 +125,7 @@ public class ResourceManagerImplTest {
private static long hostId = 1L;
private static final String hostUsername = "user";
private static final String hostPassword = "password";
private static final String hostPrivateKey = "privatekey";
private static final String hostPrivateIp = "192.168.1.10";
private static long vm1Id = 1L;
@ -148,6 +149,7 @@ public class ResourceManagerImplTest {
when(hostDao.findById(hostId)).thenReturn(host);
when(host.getDetail("username")).thenReturn(hostUsername);
when(host.getDetail("password")).thenReturn(hostPassword);
when(configurationDao.getValue("ssh.privatekey")).thenReturn(hostPrivateKey);
when(host.getStatus()).thenReturn(Status.Up);
when(host.getPrivateIpAddress()).thenReturn(hostPrivateIp);
when(vm1.getId()).thenReturn(vm1Id);
@ -171,7 +173,7 @@ public class ResourceManagerImplTest {
PowerMockito.mockStatic(SSHCmdHelper.class);
BDDMockito.given(SSHCmdHelper.acquireAuthorizedConnection(eq(hostPrivateIp), eq(22),
eq(hostUsername), eq(hostPassword))).willReturn(sshConnection);
eq(hostUsername), eq(hostPassword), eq(hostPrivateKey))).willReturn(sshConnection);
BDDMockito.given(SSHCmdHelper.sshExecuteCmdOneShot(eq(sshConnection),
eq("service cloudstack-agent restart"))).
willReturn(new SSHCmdHelper.SSHCmdResult(0,"",""));
@ -292,34 +294,36 @@ public class ResourceManagerImplTest {
@Test(expected = CloudRuntimeException.class)
public void testGetHostCredentialsMissingParameter() {
when(host.getDetail("password")).thenReturn(null);
when(configurationDao.getValue("ssh.privatekey")).thenReturn(null);
resourceManager.getHostCredentials(host);
}
@Test
public void testGetHostCredentials() {
Pair<String, String> credentials = resourceManager.getHostCredentials(host);
Ternary<String, String, String> credentials = resourceManager.getHostCredentials(host);
Assert.assertNotNull(credentials);
Assert.assertEquals(hostUsername, credentials.first());
Assert.assertEquals(hostPassword, credentials.second());
Assert.assertEquals(hostPrivateKey, credentials.third());
}
@Test(expected = CloudRuntimeException.class)
public void testConnectAndRestartAgentOnHostCannotConnect() {
BDDMockito.given(SSHCmdHelper.acquireAuthorizedConnection(eq(hostPrivateIp), eq(22),
eq(hostUsername), eq(hostPassword))).willReturn(null);
resourceManager.connectAndRestartAgentOnHost(host, hostUsername, hostPassword);
eq(hostUsername), eq(hostPassword), eq(hostPrivateKey))).willReturn(null);
resourceManager.connectAndRestartAgentOnHost(host, hostUsername, hostPassword, hostPrivateKey);
}
@Test(expected = CloudRuntimeException.class)
public void testConnectAndRestartAgentOnHostCannotRestart() throws Exception {
BDDMockito.given(SSHCmdHelper.sshExecuteCmdOneShot(eq(sshConnection),
eq("service cloudstack-agent restart"))).willThrow(new SshException("exception"));
resourceManager.connectAndRestartAgentOnHost(host, hostUsername, hostPassword);
resourceManager.connectAndRestartAgentOnHost(host, hostUsername, hostPassword, hostPrivateKey);
}
@Test
public void testConnectAndRestartAgentOnHost() {
resourceManager.connectAndRestartAgentOnHost(host, hostUsername, hostPassword);
resourceManager.connectAndRestartAgentOnHost(host, hostUsername, hostPassword, hostPrivateKey);
}
@Test
@ -327,7 +331,7 @@ public class ResourceManagerImplTest {
when(host.getStatus()).thenReturn(Status.Disconnected);
resourceManager.handleAgentIfNotConnected(host, false);
verify(resourceManager).getHostCredentials(eq(host));
verify(resourceManager).connectAndRestartAgentOnHost(eq(host), eq(hostUsername), eq(hostPassword));
verify(resourceManager).connectAndRestartAgentOnHost(eq(host), eq(hostUsername), eq(hostPassword), eq(hostPrivateKey));
}
@Test
@ -335,7 +339,7 @@ public class ResourceManagerImplTest {
when(host.getStatus()).thenReturn(Status.Up);
resourceManager.handleAgentIfNotConnected(host, false);
verify(resourceManager, never()).getHostCredentials(eq(host));
verify(resourceManager, never()).connectAndRestartAgentOnHost(eq(host), eq(hostUsername), eq(hostPassword));
verify(resourceManager, never()).connectAndRestartAgentOnHost(eq(host), eq(hostUsername), eq(hostPassword), eq(hostPrivateKey));
}
@Test(expected = CloudRuntimeException.class)
@ -351,14 +355,14 @@ public class ResourceManagerImplTest {
when(configurationDao.getValue(ResourceManager.KvmSshToAgentEnabled.key())).thenReturn("false");
resourceManager.handleAgentIfNotConnected(host, false);
verify(resourceManager, never()).getHostCredentials(eq(host));
verify(resourceManager, never()).connectAndRestartAgentOnHost(eq(host), eq(hostUsername), eq(hostPassword));
verify(resourceManager, never()).connectAndRestartAgentOnHost(eq(host), eq(hostUsername), eq(hostPassword), eq(hostPrivateKey));
}
@Test
public void testHandleAgentVMsMigrating() {
resourceManager.handleAgentIfNotConnected(host, true);
verify(resourceManager, never()).getHostCredentials(eq(host));
verify(resourceManager, never()).connectAndRestartAgentOnHost(eq(host), eq(hostUsername), eq(hostPassword));
verify(resourceManager, never()).connectAndRestartAgentOnHost(eq(host), eq(hostUsername), eq(hostPassword), eq(hostPrivateKey));
}
private void setupNoPendingMigrationRetries() {

View File

@ -635,7 +635,7 @@ class ConfigDriveUtils:
"""
ssh.execute("umount -d %s" % mount_path)
# Give the VM time to unlock the iso device
time.sleep(2)
time.sleep(0.5)
# Verify umount
result = ssh.execute("ls %s" % mount_path)
self.assertTrue(len(result) == 0,
@ -1089,10 +1089,8 @@ class ConfigDriveUtils:
vm.details = vm_new_ssh.details
# reset SSH key also resets the password.
self._decrypt_password(vm)
vm.password_test = ConfigDriveUtils.PasswordTest(vm=vm)
# reset SSH key also removes the password (see https://github.com/apache/cloudstack/pull/4819)
vm.password_test = ConfigDriveUtils.PasswordTest(expect_pw=False)
vm.key_pair = self.keypair
if public_ip:
@ -1119,7 +1117,7 @@ class ConfigDriveUtils:
cipher = PKCS1_v1_5.new(key)
new_password = cipher.decrypt(b64decode(password_), None)
if new_password:
vm.password = new_password
vm.password = new_password.decode()
else:
self.fail("Failed to decrypt new password")
except ImportError:
@ -1511,7 +1509,6 @@ class TestConfigDrive(cloudstackTestCase, ConfigDriveUtils):
tries = 1 if negative_test else 3
private_key_file_location = keypair.private_key_file if keypair else None
@retry(tries=tries)
def retry_ssh():
ssh_client = vm.get_ssh_client(
ipaddress=public_ip.ipaddress.ipaddress,
@ -1732,8 +1729,7 @@ class TestConfigDrive(cloudstackTestCase, ConfigDriveUtils):
self.api_client.restartVPC(cmd)
self.debug("Restarted VPC with ID - %s" % vpc.id)
# was tags=["advanced", "isonw"]
@attr(tags=["TODO"], required_hardware="true")
@attr(tags=["advanced", "isonw"], required_hardware="true")
def test_configdrive_isolated_network(self):
"""Test Configdrive as provider for isolated Networks
to provide userdata and password reset functionality
@ -2500,6 +2496,7 @@ class TestConfigDrive(cloudstackTestCase, ConfigDriveUtils):
# =====================================================================
self.debug("+++ Scenario: "
"validate updated userdata after migrate")
time.sleep(30)
host = self.migrate_VM(vm)
vm.hostname = host.name
self.then_config_drive_is_as_expected(vm, public_ip_1, metadata=True)

View File

@ -10,6 +10,7 @@
"docBase": "http://docs.cloudstack.apache.org/en/latest",
"appTitle": "CloudStack",
"footer": "Licensed under the <a href='http://www.apache.org/licenses/' target='_blank'>Apache License</a>, Version 2.0.",
"loginFooter": "",
"logo": "assets/logo.svg",
"banner": "assets/banner.svg",
"error": {

View File

@ -468,6 +468,8 @@
"label.associatednetworkid": "Associated Network ID",
"label.associatednetworkname": "Network Name",
"label.asyncbackup": "Async Backup",
"label.authentication.method": "Authentication Method",
"label.authentication.sshkey": "System SSH Key",
"label.author.email": "Author e-mail",
"label.author.name": "Author name",
"label.auto.assign": "Automatically assign",
@ -577,6 +579,7 @@
"label.cks.cluster.size": "Cluster size (Worker nodes)",
"label.cleanup": "Clean up",
"label.clear": "Clear",
"label.clear.notification": "Clear notification",
"label.clear.list": "Clear list",
"label.close": "Close",
"label.cloud.console": "Cloud Management Console",
@ -2595,6 +2598,7 @@
"message.add.firewall.rule.processing": "Adding new Firewall rule...",
"message.add.guest.network": "Please confirm that you would like to add a guest network",
"message.add.host": "Please specify the following parameters to add a new host",
"message.add.host.sshkey": "WARNING: In order to add a host with SSH key, you must ensure your hypervisor host has been configured correctly.",
"message.add.ip.range": "Add an IP range to public network in zone",
"message.add.ip.range.direct.network": "Add an IP range to direct network <b><span id=\"directnetwork_name\"></span></b> in zone <b><span id=\"zone_name\"></span></b>",
"message.add.ip.range.to.pod": "<p>Add an IP range to pod: <b><span id=\"pod_name_label\"></span></b></p>",

View File

@ -20,7 +20,6 @@
<a-row :gutter="6">
<a-col :md="24" :lg="layout === 'horizontal' ? 12 : 24">
<a-checkbox
v-decorator="[checkBoxDecorator, {}]"
:checked="checked"
@change="handleCheckChange">
{{ checkBoxLabel }}
@ -31,8 +30,7 @@
v-if="reversed != checked"
:label="selectLabel">
<a-select
v-decorator="[selectDecorator, { initialValue: selectedOption ? selectedOption : getSelectInitialValue()}]"
:defaultValue="selectDecorator ? undefined : selectedOption ? selectedOption : getSelectInitialValue()"
v-model="selectedOption"
showSearch
optionFilterProp="children"
:filterOption="(input, option) => {
@ -69,10 +67,6 @@ export default {
type: String,
required: true
},
checkBoxDecorator: {
type: String,
default: ''
},
defaultCheckBoxValue: {
type: Boolean,
default: false
@ -85,10 +79,6 @@ export default {
type: String,
default: ''
},
selectDecorator: {
type: String,
default: ''
},
reversed: {
type: Boolean,
default: false
@ -97,12 +87,21 @@ export default {
data () {
return {
checked: false,
selectedOption: null
selectedOption: null,
selectOptionsTimer: null
}
},
created () {
this.checked = this.defaultCheckBoxValue
},
watch: {
selectOptions () {
clearTimeout(this.selectOptionsTimer)
this.selectOptionsTimer = setTimeout(() => {
this.handleSelectOptionsUpdated()
}, 50)
}
},
computed: {
selectSource () {
return this.selectOptions.map(item => {
@ -118,18 +117,23 @@ export default {
arrayHasItems (array) {
return array !== null && array !== undefined && Array.isArray(array) && array.length > 0
},
getSelectInitialValue () {
const initialValue = this.selectSource?.filter(x => x.enabled !== false)?.[0]?.id || ''
this.handleSelectChange(initialValue)
return initialValue
},
handleCheckChange (e) {
this.checked = e.target.checked
if (this.checked && !this.selectedOption) {
this.selectedOption = this.selectSource?.filter(x => x.enabled !== false)?.[0]?.id || null
}
this.$emit('handle-checkselectpair-change', this.resourceKey, this.checked, this.selectedOption)
},
handleSelectChange (val) {
this.selectedOption = val
this.$emit('handle-checkselectpair-change', this.resourceKey, this.checked, this.selectedOption)
},
handleSelectOptionsUpdated () {
if (!this.checked) return
var enabledOptions = this.selectSource?.filter(x => x.enabled !== false) || []
if (this.selectedOption && !enabledOptions.includes(this.selectedOption)) {
this.handleSelectChange(enabledOptions[0]?.id || null)
}
}
}
}

View File

@ -84,7 +84,8 @@ export default {
},
data () {
return {
image: ''
image: '',
countNotify: 0
}
},
created () {
@ -92,6 +93,12 @@ export default {
eventBus.$on('refresh-header', () => {
this.getIcon()
})
this.$store.watch(
(state, getters) => getters.countNotify,
(newValue, oldValue) => {
this.countNotify = newValue
}
)
},
watch: {
image () {
@ -137,6 +144,10 @@ export default {
description: err.message
})
})
},
clearAllNotify () {
this.$store.commit('SET_COUNT_NOTIFY', 0)
this.$notification.destroy()
}
}
}

View File

@ -91,6 +91,13 @@
/>
</a-affix>
<a-button
v-if="showClear"
type="default"
size="small"
class="button-clear-notification"
@click="onClearNotification">{{ $t('label.clear.notification') }}</a-button>
<!-- layout content -->
<a-layout-content class="layout-content" :class="{'is-header-fixed': fixedHeader}">
<slot></slot>
@ -128,7 +135,8 @@ export default {
return {
collapsed: false,
menus: [],
showSetting: false
showSetting: false,
showClear: false
}
},
computed: {
@ -161,6 +169,12 @@ export default {
} else {
document.body.classList.remove('dark-mode')
}
},
'$store.getters.countNotify' (countNotify) {
this.showClear = false
if (countNotify && countNotify > 0) {
this.showClear = true
}
}
},
provide: function () {
@ -212,6 +226,10 @@ export default {
},
toggleSetting (showSetting) {
this.showSetting = showSetting
},
onClearNotification () {
this.$notification.destroy()
this.$store.commit('SET_COUNT_NOTIFY', 0)
}
}
}

View File

@ -219,7 +219,8 @@ export default {
apiName = 'updateTemplate'
}
if (!(apiName in this.$store.getters.apis)) {
this.$notification.error({
this.$showNotification({
type: 'error',
message: this.$t('error.execute.api.failed') + ' ' + apiName,
description: this.$t('message.user.not.permitted.api')
})

View File

@ -651,7 +651,7 @@
</div>
</div>
<div class="account-center-tags" v-if="!isStatic && resourceType && 'listTags' in $store.getters.apis">
<div class="account-center-tags" v-if="!isStatic && resourceType && tagsSupportingResourceTypes.includes(this.resourceType) && 'listTags' in $store.getters.apis">
<a-divider/>
<a-spin :spinning="loadingTags">
<div class="title">{{ $t('label.tags') }}</div>
@ -772,11 +772,13 @@ export default {
this.showKeys = false
this.setData()
if (this.tagsSupportingResourceTypes.includes(this.resourceType)) {
if ('tags' in this.resource) {
this.tags = this.resource.tags
} else if (this.resourceType) {
this.getTags()
}
}
if ('apikey' in this.resource) {
this.getUserKeys()
}
@ -794,6 +796,12 @@ export default {
await this.getIcons()
},
computed: {
tagsSupportingResourceTypes () {
return ['UserVm', 'Template', 'ISO', 'Volume', 'Snapshot', 'Backup', 'Network',
'LoadBalancer', 'PortForwardingRule', 'FirewallRule', 'SecurityGroup', 'SecurityGroupRule',
'PublicIpAddress', 'Project', 'Account', 'Vpc', 'NetworkACL', 'StaticRoute', 'VMSnapshot',
'RemoteAccessVpn', 'User', 'SnapshotPolicy', 'VpcOffering']
},
name () {
return this.resource.displayname || this.resource.displaytext || this.resource.name || this.resource.username ||
this.resource.ipaddress || this.resource.virtualmachinename || this.resource.templatetype

View File

@ -514,7 +514,8 @@ export default {
json.updateconfigurationresponse.configuration &&
!json.updateconfigurationresponse.configuration.isdynamic &&
['Admin'].includes(this.$store.getters.userInfo.roletype)) {
this.$notification.warning({
this.$showNotification({
type: 'warning',
message: this.$t('label.status'),
description: this.$t('message.restart.mgmt.server')
})

View File

@ -107,7 +107,8 @@ export default {
this.dataResource = await this.listResourceLimits(params)
this.formLoading = false
} catch (e) {
this.$notification.error({
this.$showNotification({
type: 'error',
message: this.$t('message.request.failed'),
description: e
})

View File

@ -164,7 +164,8 @@ export default {
}).catch(error => {
console.error(error)
this.$message.error(this.$t('message.error.save.setting'))
this.$notification.error({
this.$showNotification({
type: 'error',
message: this.$t('label.error'),
description: this.$t('message.error.try.save.setting')
})

View File

@ -234,13 +234,15 @@ export default {
}).then(json => {
console.log(this.resource)
if (json?.uploadresourceiconresponse?.success) {
this.$notification.success({
this.$showNotification({
type: 'success',
message: this.$t('label.upload.icon'),
description: `${this.$t('message.success.upload.icon')} ${resourceType}: ` + (this.resource.name || this.resource.username || resourceid)
})
}
}).catch((error) => {
this.$notification.error({
this.$showNotification({
type: 'error',
message: this.$t('label.upload.icon'),
description: error?.response?.data?.uploadresourceiconresponse?.errortext || '',
duration: 0
@ -264,13 +266,15 @@ export default {
resourceids: resourceid
}).then(json => {
if (json?.deleteresourceiconresponse?.success) {
this.$notification.success({
this.$showNotification({
type: 'success',
message: this.$t('label.delete.icon'),
description: `${this.$t('message.success.delete.icon')} ${resourceType}: ` + (this.resource.name || this.resource.username || resourceid)
})
}
}).catch((error) => {
this.$notification.error({
this.$showNotification({
type: 'error',
message: this.$t('label.delete.icon'),
description: error?.response?.data?.deleteresourceiconresponse?.errortext || '',
duration: 0

View File

@ -152,7 +152,8 @@ export default {
const vm = result.jobresult.virtualmachine || {}
if (result.jobstatus === 1 && vm.password) {
const name = vm.displayname || vm.name || vm.id
obj.$notification.success({
obj.$showNotification({
type: 'success',
message: `${obj.$t('label.reinstall.vm')}: ` + name,
description: `${obj.$t('label.password.reset.confirm')}: ` + vm.password,
duration: 0
@ -361,7 +362,8 @@ export default {
const vm = result.jobresult.virtualmachine || {}
if (result.jobstatus === 1 && vm.password) {
const name = vm.displayname || vm.name || vm.id
obj.$notification.success({
obj.$showNotification({
type: 'success',
message: `${obj.$t('label.reset.ssh.key.pair.on.vm')}: ` + name,
description: `${obj.$t('label.password.reset.confirm')}: ` + vm.password,
duration: 0

View File

@ -17,6 +17,12 @@
<template>
<div id="userLayout" :class="['user-layout', device]">
<a-button
v-if="showClear"
type="default"
size="small"
class="button-clear-notification"
@click="onClearNotification">{{ $t('label.clear.notification') }}</a-button>
<div class="user-layout-container">
<div class="user-layout-header">
<img
@ -31,6 +37,9 @@
</div>
<route-view></route-view>
</div>
<div class="user-layout-footer" v-if="$config.loginFooter">
<label v-html="$config.loginFooter"></label>
</div>
</div>
</template>
@ -45,7 +54,9 @@ export default {
components: { RouteView },
mixins: [mixinDevice],
data () {
return {}
return {
showClear: false
}
},
watch: {
'$store.getters.darkMode' (darkMode) {
@ -54,6 +65,12 @@ export default {
} else {
document.body.classList.remove('dark-mode')
}
},
'$store.getters.countNotify' (countNotify) {
this.showClear = false
if (countNotify && countNotify > 0) {
this.showClear = true
}
}
},
mounted () {
@ -66,6 +83,12 @@ export default {
beforeDestroy () {
document.body.classList.remove('userLayout')
document.body.classList.remove('dark-mode')
},
methods: {
onClearNotification () {
this.$notification.destroy()
this.$store.commit('SET_COUNT_NOTIFY', 0)
}
}
}
</script>
@ -97,5 +120,25 @@ export default {
margin-bottom: 1rem;
}
}
&-footer {
display: flex;
flex-direction: column;
position: absolute;
bottom: 20px;
text-align: center;
width: 100%;
@media (max-height: 600px) {
position: relative;
margin-top: 50px;
}
label {
width: 368px;
font-weight: 500;
margin: 0 auto;
}
}
}
</style>

View File

@ -78,10 +78,19 @@ router.beforeEach((to, from, next) => {
})
})
.catch(() => {
let countNotify = store.getters.countNotify
countNotify++
store.commit('SET_COUNT_NOTIFY', countNotify)
notification.error({
top: '65px',
message: 'Error',
description: i18n.t('message.error.discovering.feature'),
duration: 0
duration: 0,
onClose: () => {
let countNotify = store.getters.countNotify
countNotify > 0 ? countNotify-- : countNotify = 0
store.commit('SET_COUNT_NOTIFY', countNotify)
}
})
store.dispatch('Logout').then(() => {
next({ path: '/user/login', query: { redirect: to.fullPath } })

View File

@ -40,7 +40,8 @@ const getters = {
domainStore: state => state.user.domainStore,
darkMode: state => state.user.darkMode,
themeSetting: state => state.user.themeSetting,
defaultListViewPageSize: state => state.user.defaultListViewPageSize
defaultListViewPageSize: state => state.user.defaultListViewPageSize,
countNotify: state => state.user.countNotify
}
export default getters

View File

@ -55,7 +55,8 @@ const user = {
domainStore: {},
darkMode: false,
themeSetting: {},
defaultListViewPageSize: 20
defaultListViewPageSize: 20,
countNotify: 0
},
mutations: {
@ -121,6 +122,9 @@ const user = {
},
SET_DEFAULT_LISTVIEW_PAGE_SIZE: (state, defaultListViewPageSize) => {
state.defaultListViewPageSize = defaultListViewPageSize
},
SET_COUNT_NOTIFY (state, number) {
state.countNotify = number
}
},

View File

@ -901,4 +901,8 @@
.ant-empty-normal {
color: @dark-text-color-3;
}
.button-clear-notification {
background-color: @dark-secondary-bgColor;
}
}

View File

@ -344,3 +344,10 @@ a {
border-color: @primary-color;
}
}
.button-clear-notification {
position: fixed;
right: 287px;
top: 35px;
z-index: 1001;
}

View File

@ -132,11 +132,20 @@ export const pollJobPlugin = {
desc = `(${name}) ${desc}`
}
if (!bulkAction) {
let countNotify = store.getters.countNotify
countNotify++
store.commit('SET_COUNT_NOTIFY', countNotify)
notification.error({
top: '65px',
message: errMessage,
description: desc,
key: jobId,
duration: 0
duration: 0,
onClose: () => {
let countNotify = store.getters.countNotify
countNotify > 0 ? countNotify-- : countNotify = 0
store.commit('SET_COUNT_NOTIFY', countNotify)
}
})
}
store.dispatch('AddHeaderNotice', {
@ -168,10 +177,19 @@ export const pollJobPlugin = {
}
}).catch(e => {
console.error(`${catchMessage} - ${e}`)
let countNotify = store.getters.countNotify
countNotify++
store.commit('SET_COUNT_NOTIFY', countNotify)
notification.error({
top: '65px',
message: i18n.t('label.error'),
description: catchMessage,
duration: 0
duration: 0,
onClose: () => {
let countNotify = store.getters.countNotify
countNotify > 0 ? countNotify-- : countNotify = 0
store.commit('SET_COUNT_NOTIFY', countNotify)
}
})
catchMethod && catchMethod()
})
@ -203,12 +221,50 @@ export const notifierPlugin = {
}
}
}
let countNotify = store.getters.countNotify
countNotify++
store.commit('SET_COUNT_NOTIFY', countNotify)
notification.error({
top: '65px',
message: msg,
description: desc,
duration: 0
duration: 0,
onClose: () => {
let countNotify = store.getters.countNotify
countNotify > 0 ? countNotify-- : countNotify = 0
store.commit('SET_COUNT_NOTIFY', countNotify)
}
})
}
Vue.prototype.$showNotification = function (config) {
let countNotify = store.getters.countNotify
countNotify++
store.commit('SET_COUNT_NOTIFY', countNotify)
const defaultConfig = {
top: '65px',
onClose: () => {
let countNotify = store.getters.countNotify
countNotify > 0 ? countNotify-- : countNotify = 0
store.commit('SET_COUNT_NOTIFY', countNotify)
}
}
config = Object.assign({}, defaultConfig, config)
switch (config.type) {
case 'info':
notification.info(config)
break
case 'error':
notification.error(config)
break
case 'success':
notification.success(config)
break
case 'warning':
notification.warning(config)
break
}
}
}
}

View File

@ -30,11 +30,23 @@ const service = axios.create({
const err = (error) => {
const response = error.response
let countNotify = store.getters.countNotify
if (response) {
console.log(response)
if (response.status === 403) {
const data = response.data
notification.error({ message: i18n.t('label.forbidden'), description: data.message })
countNotify++
store.commit('SET_COUNT_NOTIFY', countNotify)
notification.error({
top: '65px',
message: i18n.t('label.forbidden'),
description: data.message,
onClose: () => {
let countNotify = store.getters.countNotify
countNotify > 0 ? countNotify-- : countNotify = 0
store.commit('SET_COUNT_NOTIFY', countNotify)
}
})
}
if (response.status === 401) {
if (response.config && response.config.params && ['listIdps', 'cloudianIsEnabled'].includes(response.config.params.command)) {
@ -43,20 +55,36 @@ const err = (error) => {
for (const key in response.data) {
if (key.includes('response')) {
if (response.data[key].errortext.includes('not available for user')) {
countNotify++
store.commit('SET_COUNT_NOTIFY', countNotify)
notification.error({
top: '65px',
message: 'Error',
description: response.data[key].errortext + ' ' + i18n.t('error.unable.to.proceed'),
duration: 0
duration: 0,
onClose: () => {
let countNotify = store.getters.countNotify
countNotify > 0 ? countNotify-- : countNotify = 0
store.commit('SET_COUNT_NOTIFY', countNotify)
}
})
return
}
}
}
countNotify++
store.commit('SET_COUNT_NOTIFY', countNotify)
notification.error({
top: '65px',
message: i18n.t('label.unauthorized'),
description: i18n.t('message.authorization.failed'),
key: 'http-401',
duration: 0
duration: 0,
onClose: () => {
let countNotify = store.getters.countNotify
countNotify > 0 ? countNotify-- : countNotify = 0
store.commit('SET_COUNT_NOTIFY', countNotify)
}
})
store.dispatch('Logout').then(() => {
if (router.history.current.path !== '/user/login') {
@ -65,15 +93,34 @@ const err = (error) => {
})
}
if (response.status === 404) {
notification.error({ message: i18n.t('label.not.found'), description: i18n.t('message.resource.not.found') })
countNotify++
store.commit('SET_COUNT_NOTIFY', countNotify)
notification.error({
top: '65px',
message: i18n.t('label.not.found'),
description: i18n.t('message.resource.not.found'),
onClose: () => {
let countNotify = store.getters.countNotify
countNotify > 0 ? countNotify-- : countNotify = 0
store.commit('SET_COUNT_NOTIFY', countNotify)
}
})
router.push({ path: '/exception/404' })
}
}
if (error.isAxiosError && !error.response) {
countNotify++
store.commit('SET_COUNT_NOTIFY', countNotify)
notification.warn({
top: '65px',
message: error.message || i18n.t('message.network.error'),
description: i18n.t('message.network.error.description'),
key: 'network-error'
key: 'network-error',
onClose: () => {
let countNotify = store.getters.countNotify
countNotify > 0 ? countNotify-- : countNotify = 0
store.commit('SET_COUNT_NOTIFY', countNotify)
}
})
}
return Promise.reject(error)

View File

@ -1128,7 +1128,8 @@ export default {
if (action.response) {
const description = action.response(result.jobresult)
if (description) {
this.$notification.info({
this.$showNotification({
type: 'info',
message: this.$t(action.label),
description: (<span domPropsInnerHTML={description}></span>),
duration: 0

View File

@ -294,7 +294,8 @@ export default {
[variableKey]: variableValue,
networkids: this.selectedNetwork
}).then(response => {
this.$notification.success({
this.$showNotification({
type: 'success',
message: this.$t('label.loadbalancerinstance')
})
this.$parent.$parent.close()

View File

@ -149,7 +149,8 @@ export default {
id: this.resource.id,
affinitygroupids: this.selectedRowKeys.join(',')
}).then(response => {
this.$notification.success({
this.$showNotification({
type: 'success',
message: this.$t('message.success.change.affinity.group')
})
this.$parent.$parent.close()

View File

@ -198,7 +198,8 @@ export default {
this.hiddenElement.click()
},
notifyCopied () {
this.$notification.info({
this.$showNotification({
type: 'info',
message: this.$t('message.success.copy.clipboard')
})
},

View File

@ -141,7 +141,8 @@ export default {
const volumeId = result.jobresult.snapshot.volumeid
const snapshotId = result.jobresult.snapshot.id
const message = `${this.$t('label.create.snapshot.for.volume')} ${volumeId} ${this.$t('label.with.snapshotid')} ${snapshotId}`
this.$notification.success({
this.$showNotification({
type: 'success',
message: message,
duration: 0
})

View File

@ -1133,7 +1133,9 @@ export default {
this.vm.hostname = host.name
}
if (this.diskSize) {
if (this.serviceOffering.rootdisksize) {
this.vm.disksizetotalgb = this.serviceOffering.rootdisksize
} else if (this.diskSize) {
this.vm.disksizetotalgb = this.diskSize
}
@ -1489,14 +1491,16 @@ export default {
this.form.validateFieldsAndScroll(options, async (err, values) => {
if (err) {
if (err.licensesaccepted) {
this.$notification.error({
this.$showNotification({
type: 'error',
message: this.$t('message.license.agreements.not.accepted'),
description: this.$t('message.step.license.agreements.continue')
})
return
}
this.$notification.error({
this.$showNotification({
type: 'error',
message: this.$t('message.request.failed'),
description: this.$t('error.form.message')
})
@ -1504,27 +1508,31 @@ export default {
}
if (!values.templateid && !values.isoid) {
this.$notification.error({
this.$showNotification({
type: 'error',
message: this.$t('message.request.failed'),
description: this.$t('message.template.iso')
})
return
} else if (values.isoid && (!values.diskofferingid || values.diskofferingid === '0')) {
this.$notification.error({
this.$showNotification({
type: 'error',
message: this.$t('message.request.failed'),
description: this.$t('message.step.3.continue')
})
return
}
if (!values.computeofferingid) {
this.$notification.error({
this.$showNotification({
type: 'error',
message: this.$t('message.request.failed'),
description: this.$t('message.step.2.continue')
})
return
}
if (this.error) {
this.$notification.error({
this.$showNotification({
type: 'error',
message: this.$t('message.request.failed'),
description: this.$t('error.form.message')
})
@ -1637,7 +1645,8 @@ export default {
}
}
} else {
this.$notification.error({
this.$showNotification({
type: 'error',
message: this.$t('message.request.failed'),
description: this.$t('message.step.4.continue')
})
@ -1699,7 +1708,8 @@ export default {
const vm = result.jobresult.virtualmachine
const name = vm.displayname || vm.name || vm.id
if (vm.password) {
this.$notification.success({
this.$showNotification({
type: 'success',
message: password + ` ${this.$t('label.for')} ` + name,
description: vm.password,
duration: 0

View File

@ -321,7 +321,8 @@ export default {
config.configdata !== '') {
this.clusterConfig = config.configdata
} else {
this.$notification.error({
this.$showNotification({
type: 'error',
message: this.$t('message.request.failed'),
description: this.$t('message.error.retrieve.kubeconfig')
})

View File

@ -306,7 +306,8 @@ export default {
})
this.closeModal()
}).catch(error => {
this.$notification.error({
this.$showNotification({
type: 'error',
message: this.$t('message.request.failed'),
description: (error.response && error.response.headers && error.response.headers['x-description']) || error.message,
duration: 0

View File

@ -166,7 +166,8 @@ export default {
this.$pollJob({
jobId,
successMethod: result => {
this.$notification.success({
this.$showNotification({
type: 'success',
message: this.$t('message.success.change.offering')
})
},

View File

@ -143,7 +143,8 @@ export default {
api('listPods', params).then(json => {
this.pods = json.listpodsresponse.pod || []
if (this.pods.length === 0) {
this.$notification.error({
this.$showNotification({
type: 'error',
message: 'No pods found',
duration: 0
})
@ -162,7 +163,8 @@ export default {
api('listClusters', params).then(json => {
this.clusters = json.listclustersresponse.cluster || []
if (this.clusters.length === 0) {
this.$notification.error({
this.$showNotification({
type: 'error',
message: 'No clusters found',
duration: 0
})
@ -184,7 +186,8 @@ export default {
api('listHosts', params).then(json => {
this.hosts = json.listhostsresponse.host || []
if (this.hosts.length === 0) {
this.$notification.error({
this.$showNotification({
type: 'error',
message: 'No hosts found',
duration: 0
})

View File

@ -157,7 +157,8 @@ export default {
this.actionLoading = true
api('deleteBackupSchedule', params).then(json => {
if (json.deletebackupscheduleresponse.success) {
this.$notification.success({
this.$showNotification({
type: 'success',
message: this.$t('label.scheduled.backups'),
description: this.$t('message.success.delete.backup.schedule')
})

View File

@ -284,7 +284,8 @@ export default {
}
this.actionLoading = true
api('createBackupSchedule', params).then(json => {
this.$notification.success({
this.$showNotification({
type: 'success',
message: this.$t('label.scheduled.backups'),
description: this.$t('message.success.config.backup.schedule')
})

View File

@ -157,11 +157,11 @@ export default {
(item.iscustomized === true && maxCpuNumber < this.minimumCpunumber))) {
disabled = true
}
if (disabled === false && this.minimumCpuspeed > 0 && maxCpuSpeed && maxCpuSpeed !== this.minimumCpuspeed) {
if (disabled === false && this.minimumCpuspeed > 0 && maxCpuSpeed && maxCpuSpeed < this.minimumCpuspeed) {
disabled = true
}
if (disabled === false && maxMemory && this.minimumMemory > 0 &&
((item.iscustomized === false && maxMemory !== this.minimumMemory) ||
((item.iscustomized === false && maxMemory < this.minimumMemory) ||
(item.iscustomized === true && maxMemory < this.minimumMemory))) {
disabled = true
}

View File

@ -266,7 +266,8 @@ export default {
this.domainsList = response.listdomainsresponse.domain || []
this.selectedDomain = this.domainsList[0].id || ''
}).catch(error => {
this.$notification.error({
this.$showNotification({
type: 'error',
message: `${this.$t('label.error')} ${error.response.status}`,
description: error.response.data.errorresponse.errortext
})
@ -341,7 +342,8 @@ export default {
api('createAccount', {}, 'POST', params).then(response => {
this.$emit('refresh-data')
this.$notification.success({
this.$showNotification({
type: 'success',
message: this.$t('label.create.account'),
description: `${this.$t('message.success.create.account')} ${params.username}`
})
@ -353,12 +355,14 @@ export default {
entityid: values.samlentity,
userid: users[i].id
}).then(response => {
this.$notification.success({
this.$showNotification({
type: 'success',
message: this.$t('label.samlenable'),
description: this.$t('message.success.enable.saml.auth')
})
}).catch(error => {
this.$notification.error({
this.$showNotification({
type: 'error',
message: this.$t('message.request.failed'),
description: (error.response && error.response.headers && error.response.headers['x-description']) || error.message,
duration: 0
@ -368,7 +372,8 @@ export default {
}
this.closeAction()
}).catch(error => {
this.$notification.error({
this.$showNotification({
type: 'error',
message: this.$t('message.request.failed'),
description: (error.response && error.response.headers && error.response.headers['x-description']) || error.message,
duration: 0

View File

@ -410,13 +410,15 @@ export default {
this.authorizeUsersForSamlSSO(users, entityId)
}
} else if (apiName === 'importLdapUsers' && response.ldapuserresponse && values.samlEnable) {
this.$notification.error({
this.$showNotification({
type: 'error',
message: this.$t('message.request.failed'),
description: this.$t('message.error.enable.saml')
})
} else {
if (apiName === 'ldapCreateAccount') {
this.$notification.success({
this.$showNotification({
type: 'success',
message: this.$t('label.add.ldap.account'),
description: response.createaccountresponse.account.name
})

View File

@ -238,7 +238,8 @@ export default {
this.domainsList = response.listdomainsresponse.domain || []
this.selectedDomain = this.domainsList[0].id || ''
}).catch(error => {
this.$notification.error({
this.$showNotification({
type: 'error',
message: `${this.$t('label.error')} ${error.response.status}`,
description: error.response.data.errorresponse.errortext
})
@ -252,7 +253,8 @@ export default {
api('listAccounts', { listAll: true, showicon: true }).then(response => {
this.accountList = response.listaccountsresponse.account || []
}).catch(error => {
this.$notification.error({
this.$showNotification({
type: 'error',
message: `${this.$t('label.error')} ${error.response.status}`,
description: error.response.data.errorresponse.errortext
})
@ -319,7 +321,8 @@ export default {
api('createUser', {}, 'POST', params).then(response => {
this.$emit('refresh-data')
this.$notification.success({
this.$showNotification({
type: 'success',
message: this.$t('label.create.user'),
description: `${this.$t('message.success.create.user')} ${params.username}`
})
@ -330,12 +333,14 @@ export default {
entityid: values.samlentity,
userid: user.id
}).then(response => {
this.$notification.success({
this.$showNotification({
type: 'success',
message: this.$t('label.samlenable'),
description: this.$t('message.success.enable.saml.auth')
})
}).catch(error => {
this.$notification.error({
this.$showNotification({
type: 'error',
message: this.$t('message.request.failed'),
description: (error.response && error.response.headers && error.response.headers['x-description']) || error.message,
duration: 0
@ -344,7 +349,8 @@ export default {
}
this.closeAction()
}).catch(error => {
this.$notification.error({
this.$showNotification({
type: 'error',
message: this.$t('message.request.failed'),
description: (error.response && error.response.headers && error.response.headers['x-description']) || error.message,
duration: 0

View File

@ -128,7 +128,8 @@ export default {
params.currentpassword = values.currentpassword
}
api('updateUser', {}, 'POST', params).then(json => {
this.$notification.success({
this.$showNotification({
type: 'success',
message: this.$t('label.action.change.password'),
description: `${this.$t('message.success.change.password')} ${this.resource.username}`
})

View File

@ -108,14 +108,16 @@ export default {
userid: this.resource.id,
entityid: values.samlEntity
}).then(response => {
this.$notification.success({
this.$showNotification({
type: 'success',
message: values.samlEnable ? this.$t('label.saml.enable') : this.$t('label.saml.disable'),
description: values.samlEnable ? `${this.$t('message.success.enable.saml.auth')} ${this.$t('label.for')} ${this.resource.username}`
: `${this.$t('message.success.disable.saml.auth')} ${this.$t('label.for')} ${this.resource.username}`
})
this.handleClose()
}).catch(error => {
this.$notification.error({
this.$showNotification({
type: 'error',
message: this.$t('message.request.failed'),
description: (error.response && error.response.headers && error.response.headers['x-description']) || error.message,
duration: 0

View File

@ -174,7 +174,8 @@ export default {
const role = json.createroleresponse.role
if (role) {
this.$emit('refresh-data')
this.$notification.success({
this.$showNotification({
type: 'success',
message: 'Create Role',
description: 'Sucessfully created role ' + params.name
})

View File

@ -253,7 +253,8 @@ export default {
if (this.action.response) {
const description = this.action.response(result.jobresult)
if (description) {
this.$notification.info({
this.$showNotification({
type: 'info',
message: this.$t(this.action.label),
description: (<span domPropsInnerHTML={description}></span>),
duration: 0
@ -289,7 +290,8 @@ export default {
}
this.parentCloseAction()
}).catch(error => {
this.$notification.error({
this.$showNotification({
type: 'error',
message: this.$t('message.request.failed'),
description: (error.response && error.response.headers && error.response.headers['x-description']) || error.message
})

View File

@ -187,7 +187,8 @@ export default {
return
}
this.$notification.error({
this.$showNotification({
type: 'error',
message: this.$t('message.request.failed'),
description: error.response.headers['x-description'],
duration: 0

View File

@ -178,13 +178,15 @@ export default {
api('updateUser', params).then(response => {
this.$emit('refresh-data')
this.$notification.success({
this.$showNotification({
type: 'success',
message: this.$t('label.edit.user'),
description: `${this.$t('message.success.update.user')} ${params.username}`
})
this.closeAction()
}).catch(error => {
this.$notification.error({
this.$showNotification({
type: 'error',
message: this.$t('message.request.failed'),
description: (error.response && error.response.headers && error.response.headers['x-description']) || error.message,
duration: 0

View File

@ -128,7 +128,8 @@ export default {
},
handleChange (info) {
if (info.file.status === 'error') {
this.$notification.error({
this.$showNotification({
type: 'error',
message: this.$t('label.error.file.upload'),
description: this.$t('label.error.file.upload')
})
@ -185,7 +186,8 @@ export default {
const role = json.importroleresponse.role
if (role) {
this.$emit('refresh-data')
this.$notification.success({
this.$showNotification({
type: 'success',
message: 'Import Role',
description: 'Sucessfully imported role ' + params.name
})

View File

@ -455,7 +455,8 @@ export default {
catchMessage: this.$t('error.fetching.async.job.result')
})
}).catch(error => {
this.$notification.error({
this.$showNotification({
type: 'error',
message: this.$t('message.request.failed'),
description: (error.response && error.response.headers && error.response.headers['x-description']) || error.message
})

View File

@ -265,7 +265,8 @@ export default {
handleUpload () {
const { fileList } = this
if (this.fileList.length > 1) {
this.$notification.error({
this.$showNotification({
type: 'error',
message: this.$t('message.upload.iso.failed'),
description: this.$t('message.error.upload.iso.description'),
duration: 0
@ -290,14 +291,16 @@ export default {
},
timeout: 86400000
}).then((json) => {
this.$notification.success({
this.$showNotification({
type: 'success',
message: this.$t('message.success.upload'),
description: this.$t('message.success.upload.description')
})
this.closeAction()
this.$emit('refresh-data')
}).catch(e => {
this.$notification.error({
this.$showNotification({
type: 'error',
message: this.$t('message.upload.failed'),
description: `${this.$t('message.upload.iso.failed.description')} - ${e}`,
duration: 0
@ -342,7 +345,8 @@ export default {
if (this.currentForm === 'Create') {
this.loading = true
api('registerIso', params).then(json => {
this.$notification.success({
this.$showNotification({
type: 'success',
message: this.$t('label.action.register.iso'),
description: `${this.$t('message.success.register.iso')} ${params.name}`
})
@ -363,7 +367,8 @@ export default {
this.uploadParams = (json.postuploadisoresponse && json.postuploadisoresponse.getuploadparams) ? json.postuploadisoresponse.getuploadparams : ''
const response = this.handleUpload()
if (response === 'upload successful') {
this.$notification.success({
this.$showNotification({
type: 'success',
message: this.$t('message.success.upload'),
description: this.$t('message.success.upload.iso.description')
})

View File

@ -511,14 +511,16 @@ export default {
},
timeout: 86400000
}).then((json) => {
this.$notification.success({
this.$showNotification({
type: 'success',
message: this.$t('message.success.upload'),
description: this.$t('message.success.upload.template.description')
})
this.$emit('refresh-data')
this.closeAction()
}).catch(e => {
this.$notification.error({
this.$showNotification({
type: 'error',
message: this.$t('message.upload.failed'),
description: `${this.$t('message.upload.template.failed.description')} - ${e}`,
duration: 0
@ -887,7 +889,8 @@ export default {
if (this.currentForm === 'Create') {
this.loading = true
api('registerTemplate', params).then(json => {
this.$notification.success({
this.$showNotification({
type: 'success',
message: this.$t('label.register.template'),
description: `${this.$t('message.success.register.template')} ${params.name}`
})
@ -901,7 +904,8 @@ export default {
} else {
this.loading = true
if (this.fileList.length > 1) {
this.$notification.error({
this.$showNotification({
type: 'error',
message: this.$t('message.error.upload.template'),
description: this.$t('message.error.upload.template.description'),
duration: 0

View File

@ -554,7 +554,8 @@ export default {
catchMessage: this.$t('error.fetching.async.job.result')
})
}).catch(error => {
this.$notification.error({
this.$showNotification({
type: 'error',
message: this.$t('message.request.failed'),
description: (error.response && error.response.headers && error.response.headers['x-description']) || error.message
})

View File

@ -289,7 +289,8 @@ export default {
featured: this.resource.featured,
op: this.selectedOperation.toLowerCase()
}).then(response => {
this.$notification.success({
this.$showNotification({
type: 'success',
message: `${this.$t('label.success.updated')} ${resourceType} ${this.$t('label.permissions')}`
})
this.closeModal()

View File

@ -709,7 +709,8 @@ export default {
}
this.loading = true
api('createStoragePool', {}, 'POST', params).then(json => {
this.$notification.success({
this.$showNotification({
type: 'success',
message: this.$t('label.add.primary.storage'),
description: this.$t('label.add.primary.storage')
})

View File

@ -293,7 +293,8 @@ export default {
this.loading = true
api('addImageStore', data).then(json => {
this.$notification.success({
this.$showNotification({
type: 'success',
message: this.$t('label.add.secondary.storage'),
description: this.$t('label.add.secondary.storage')
})

View File

@ -257,7 +257,8 @@ export default {
}
this.addCluster()
}).catch(error => {
this.$notification.error({
this.$showNotification({
type: 'error',
message: `${this.$t('label.error')} ${error.response.status}`,
description: error.response.data.listvmwaredcsresponse.errortext,
duration: 0
@ -308,7 +309,8 @@ export default {
this.parentToggleLoading()
this.$parent.$parent.close()
}).catch(error => {
this.$notification.error({
this.$showNotification({
type: 'error',
message: `${this.$t('label.error')} ${error.response.status}`,
description: error.response.data.addclusterresponse.errortext,
duration: 0
@ -343,7 +345,8 @@ export default {
}
})
}).catch(error => {
this.$notification.error({
this.$showNotification({
type: 'error',
message: `${this.$t('label.error')} ${error.response.status}`,
description: error.response.data.errorresponse.errortext,
duration: 0

View File

@ -94,7 +94,30 @@
<a-input :placeholder="placeholder.username" v-model="username"></a-input>
</div>
<div class="form__item required-field" v-if="selectedClusterHyperVisorType !== 'VMware'">
<div class="form__item" v-if="selectedClusterHyperVisorType !== 'VMware'">
<div class="form__label"><span class="required">* </span>{{ $t('label.authentication.method') }}</div>
<a-radio-group
v-decorator="['authmethod', {
initialValue: authMethod
}]"
buttonStyle="solid"
:defaultValue="authMethod"
@change="selected => { handleAuthMethodChange(selected.target.value) }">
<a-radio-button value="password">
{{ $t('label.password') }}
</a-radio-button>
<a-radio-button value="sshkey" v-if="selectedClusterHyperVisorType === 'KVM'">
{{ $t('label.authentication.sshkey') }}
</a-radio-button>
</a-radio-group>
<span v-if="authMethod === 'sshkey'">
<a-alert type="warning">
<span style="display:block;width:300px;word-wrap:break-word;" slot="message" v-html="$t('message.add.host.sshkey')" />
</a-alert>
</span>
</div>
<div class="form__item required-field" v-if="selectedClusterHyperVisorType !== 'VMware' && authMethod === 'password'">
<div class="form__label"><span class="required">* </span>{{ $t('label.password') }}</div>
<span class="required required-label">{{ $t('label.required') }}</span>
<a-input :placeholder="placeholder.password" type="password" v-model="password"></a-input>
@ -190,6 +213,7 @@ export default {
agentusername: null,
agentpassword: null,
agentport: null,
authMethod: 'password',
selectedCluster: null,
selectedClusterHyperVisorType: null,
showDedicated: false,
@ -280,6 +304,9 @@ export default {
this.dedicatedAccount = null
this.showDedicated = !this.showDedicated
},
handleAuthMethodChange (val) {
this.authMethod = val
},
handleSubmitForm () {
if (this.loading) return
const requiredFields = document.querySelectorAll('.required-field')
@ -306,6 +333,10 @@ export default {
this.url = this.hostname
}
if (this.authMethod !== 'password') {
this.password = ''
}
const args = {
zoneid: this.zoneId,
podid: this.podId,
@ -331,7 +362,8 @@ export default {
this.parentFetchData()
this.$parent.$parent.close()
}).catch(error => {
this.$notification.error({
this.$showNotification({
type: 'error',
message: `${this.$t('label.error')} ${error.response.status}`,
description: error.response.data.addhostresponse.errortext,
duration: 0
@ -366,7 +398,8 @@ export default {
}
})
}).catch(error => {
this.$notification.error({
this.$showNotification({
type: 'error',
message: `${this.$t('label.error')} ${error.response.status}`,
description: error.response.data.errorresponse.errortext,
duration: 0

View File

@ -241,12 +241,14 @@ export default {
const result = json.queryasyncjobresultresponse
if (result.jobstatus === 1 && this.maxCerts === count) {
this.$message.success(`${this.$t('label.certificate.upload')}: ${result.jobresult.customcertificate.message}`)
this.$notification.success({
this.$showNotification({
type: 'success',
message: this.$t('label.certificate.upload'),
description: result.jobresult.customcertificate.message || this.$t('message.success.certificate.upload')
})
} else if (result.jobstatus === 2) {
this.$notification.error({
this.$showNotification({
type: 'error',
message: this.$t('label.certificate.upload.failed'),
description: result.jobresult.errortext || this.$t('label.certificate.upload.failed.description'),
duration: 0

View File

@ -154,12 +154,14 @@ export default {
const success = result.imagestore.success || false
const message = result.imagestore.message || ''
if (success) {
this.$notification.success({
this.$showNotification({
type: 'success',
message: title,
description: message
})
} else {
this.$notification.error({
this.$showNotification({
type: 'error',
message: title,
description: message,
duration: 0

View File

@ -207,7 +207,8 @@ export default {
this.parentFetchData()
this.$parent.$parent.close()
}).catch(error => {
this.$notification.error({
this.$showNotification({
type: 'error',
message: `${this.$t('label.error')} ${error.response.status}`,
description: error.response.data.createpodresponse.errortext,
duration: 0
@ -242,7 +243,8 @@ export default {
}
})
}).catch(error => {
this.$notification.error({
this.$showNotification({
type: 'error',
message: `${this.$t('label.error')} ${error.response.status}`,
description: error.response.data.errorresponse.errortext,
duration: 0

View File

@ -254,7 +254,8 @@ export default {
this.items = response.listdedicatedguestvlanrangesresponse.dedicatedguestvlanrange || []
this.totalCount = response.listdedicatedguestvlanrangesresponse.count || 0
}).catch(error => {
this.$notification.error({
this.$showNotification({
type: 'error',
message: `${this.$t('label.error')} ${error.response.status}`,
description: error.response.data.errorresponse.errortext,
duration: 0

View File

@ -133,7 +133,8 @@ export default {
this.traffictype = this.trafficTypes[0].traffictype || undefined
})
.catch(error => {
this.$notification.error({
this.$showNotification({
type: 'error',
message: `${this.$t('label.error')} ${error.response.status}`,
description: error.response.data.errorresponse.errortext
})

View File

@ -504,7 +504,8 @@ export default {
handleDeleteIpRange (id) {
this.componentLoading = true
api('deleteVlanIpRange', { id }).then(() => {
this.$notification.success({
this.$showNotification({
type: 'success',
message: 'Removed IP Range'
})
}).catch(error => {
@ -540,11 +541,13 @@ export default {
params.networkid = this.network.id
}
api('createVlanIpRange', params).then(() => {
this.$notification.success({
this.$showNotification({
type: 'success',
message: this.$t('message.success.add.iprange')
})
}).catch(error => {
this.$notification.error({
this.$showNotification({
type: 'error',
message: `${this.$t('label.error')} ${error.response.status}`,
description: error.response.data.createvlaniprangeresponse
? error.response.data.createvlaniprangeresponse.errortext : error.response.data.errorresponse.errortext,
@ -572,11 +575,13 @@ export default {
forsystemvms: values.forsystemvms
}
api('updateVlanIpRange', params).then(() => {
this.$notification.success({
this.$showNotification({
type: 'success',
message: this.$t('message.success.update.iprange')
})
}).catch(error => {
this.$notification.error({
this.$showNotification({
type: 'error',
message: `${this.$t('label.error')} ${error.response.status}`,
description: error.response.data.updatevlaniprangeresponse
? error.response.data.updatevlaniprangeresponse.errortext : error.response.data.errorresponse.errortext,

View File

@ -1219,7 +1219,8 @@ export default {
this.onCloseAction()
} catch (error) {
this.actionLoading = false
this.$notification.error({
this.$showNotification({
type: 'error',
message: this.$t('message.request.failed'),
description: error
})
@ -1355,7 +1356,8 @@ export default {
this.onCloseAction()
} catch (message) {
this.actionLoading = false
this.$notification.error({
this.$showNotification({
type: 'error',
message: this.$t('message.request.failed'),
description: message
})

View File

@ -253,7 +253,8 @@ export default {
this.loading = false
} catch (error) {
this.loading = false
this.$notification.error({
this.$showNotification({
type: 'error',
message: this.$t('message.request.failed'),
description: (error.response && error.response.headers && error.response.headers['x-description']) || error.message
})

View File

@ -288,7 +288,8 @@ export default {
this.loading = false
} catch (error) {
this.loading = false
this.$notification.error({
this.$showNotification({
type: 'error',
message: this.$t('message.request.failed'),
description: (error.response && error.response.headers && error.response.headers['x-description']) || error.message
})

View File

@ -160,7 +160,8 @@ export default {
this.loading = false
} catch (error) {
this.loading = false
this.$notification.error({
this.$showNotification({
type: 'error',
message: this.$t('message.request.failed'),
description: (error.response && error.response.headers && error.response.headers['x-description']) || error.message
})

View File

@ -377,7 +377,8 @@ export default {
this.loading = false
} catch (error) {
this.loading = false
this.$notification.error({
this.$showNotification({
type: 'error',
message: this.$t('message.request.failed'),
description: (error.response && error.response.headers && error.response.headers['x-description']) || error.message
})

View File

@ -322,7 +322,8 @@ export default {
this.loading = false
} catch (error) {
this.loading = false
this.$notification.error({
this.$showNotification({
type: 'error',
message: this.$t('message.request.failed'),
description: (error.response && error.response.headers && error.response.headers['x-description']) || error.message
})

View File

@ -188,7 +188,8 @@ export default {
this.listData[args.title].loading = false
} catch (error) {
this.listData[args.title].loading = false
this.$notification.error({
this.$showNotification({
type: 'error',
message: this.$t('message.request.failed'),
description: (error.response && error.response.headers && error.response.headers['x-description']) || error.message
})

View File

@ -300,7 +300,8 @@ export default {
this.actionLoading = false
} catch (error) {
this.actionLoading = false
this.$notification.error({
this.$showNotification({
type: 'error',
message: this.$t('message.request.failed'),
description: (error.response && error.response.headers && error.response.headers['x-description']) || error.message
})
@ -336,7 +337,8 @@ export default {
this.actionLoading = false
} catch (error) {
this.actionLoading = false
this.$notification.error({
this.$showNotification({
type: 'error',
message: this.$t('message.request.failed'),
description: (error.response && error.response.headers && error.response.headers['x-description']) || error.message
})

View File

@ -1547,7 +1547,8 @@ export default {
this.$emit('refresh-data')
} catch (e) {
this.loading = false
await this.$notification.error({
await this.$showNotification({
type: 'error',
message: this.$t('message.request.failed'),
description: e
})

View File

@ -475,6 +475,7 @@ export default {
Object.keys(fields).forEach(key => {
this.form.getFieldDecorator([key], { initialValue: fields[key] })
this.form.setFieldsValue({ [key]: fields[key] })
})
},
deleteTraffic (key, traffic, $event) {

View File

@ -571,7 +571,8 @@ export default {
data.aclid = this.resource.id
api('createNetworkACL', {}, 'POST', data).then(() => {
this.$notification.success({
this.$showNotification({
type: 'success',
message: this.$t('label.success'),
description: this.$t('message.success.add.rule')
})

View File

@ -442,7 +442,8 @@ export default {
}
}
api('createNetwork', params).then(json => {
this.$notification.success({
this.$showNotification({
type: 'success',
message: 'Network',
description: this.$t('message.success.create.isolated.network')
})

View File

@ -393,7 +393,8 @@ export default {
}
}
api('createNetwork', params).then(json => {
this.$notification.success({
this.$showNotification({
type: 'success',
message: 'Network',
description: this.$t('message.success.create.l2.network')
})

View File

@ -703,7 +703,8 @@ export default {
!this.isValidTextValueForKey(values, 'ip6gateway') && !this.isValidTextValueForKey(values, 'ip6cidr') &&
!this.isValidTextValueForKey(values, 'startipv6') && !this.isValidTextValueForKey(values, 'endipv6'))
) {
this.$notification.error({
this.$showNotification({
type: 'error',
message: this.$t('message.request.failed'),
description: this.$t('message.error.add.guest.network')
})
@ -788,7 +789,8 @@ export default {
params.hideipaddressusage = true
}
api('createNetwork', params).then(json => {
this.$notification.success({
this.$showNotification({
type: 'success',
message: this.$t('label.network'),
description: this.$t('message.success.add.guest.network')
})

View File

@ -248,13 +248,15 @@ export default {
api('createVlanIpRange', params)
.then(() => {
this.$notification.success({
this.$showNotification({
type: 'success',
message: this.$t('message.success.add.iprange')
})
this.closeAction()
this.$emit('refresh-data')
}).catch(error => {
this.$notification.error({
this.$showNotification({
type: 'error',
message: `${this.$t('label.error')} ${error.response.status}`,
description: error.response.data.createvlaniprangeresponse
? error.response.data.createvlaniprangeresponse.errortext : error.response.data.errorresponse.errortext,

View File

@ -223,7 +223,8 @@ export default {
})
this.closeModal()
}).catch(error => {
this.$notification.error({
this.$showNotification({
type: 'error',
message: `${this.$t('label.error')} ${error.response.status}`,
description: error.response.data.errorresponse.errortext,
duration: 0

View File

@ -359,7 +359,8 @@ export default {
})
this.onCloseModal()
}).catch(error => {
this.$notification.error({
this.$showNotification({
type: 'error',
message: `${this.$t('label.error')} ${error.response.status}`,
description: error.response.data.errorresponse.errortext,
duration: 0
@ -426,7 +427,8 @@ export default {
})
}).catch(error => {
this.fetchLoading = false
this.$notification.error({
this.$showNotification({
type: 'error',
message: `${this.$t('label.error')} ${error.response.status}`,
description: error.response.data.errorresponse.errortext,
duration: 0

View File

@ -207,7 +207,7 @@
<a-modal
:title="$t('label.configure.sticky.policy')"
v-model="stickinessModalVisible"
:visible="stickinessModalVisible"
:footer="null"
:afterClose="closeModal"
:maskClosable="false"
@ -288,7 +288,7 @@
<div :span="24" class="action-button">
<a-button @click="stickinessModalVisible = false">{{ $t('label.cancel') }}</a-button>
<a-button type="primary" @submit="handleSubmitStickinessForm">{{ $t('label.ok') }}</a-button>
<a-button type="primary" ref="submit" @click="handleSubmitStickinessForm">{{ $t('label.ok') }}</a-button>
</div>
</a-form>
</a-modal>
@ -890,6 +890,8 @@ export default {
})
}).catch(error => {
this.$notifyError(error)
}).finally(() => {
this.closeModal()
})
},
handleDeleteStickinessPolicy () {
@ -962,6 +964,7 @@ export default {
})
},
handleStickinessMethodSelectChange (e) {
this.stickinessPolicyForm.resetFields()
this.stickinessPolicyMethod = e
},
handleDeleteInstanceFromRule (instance, rule, ip) {

View File

@ -331,7 +331,7 @@ export default {
inject: ['parentFetchData', 'parentToggleLoading'],
data () {
return {
checked: true,
checked: false,
selectedRowKeys: [],
showGroupActionModal: false,
selectedItems: [],
@ -639,6 +639,7 @@ export default {
this.addVmModalNicLoading = false
this.showConfirmationAction = false
this.nics = []
this.checked = false
this.resetTagInputs()
},
openTagsModal (id) {

View File

@ -664,7 +664,8 @@ export default {
}
api('createNetwork', params).then(() => {
this.$notification.success({
this.$showNotification({
type: 'success',
message: this.$t('message.success.add.vpc.network')
})
}).catch(error => {

View File

@ -132,7 +132,8 @@ export default {
jobId: response.createremoteaccessvpnresponse.jobid,
successMethod: result => {
const res = result.jobresult.remoteaccessvpn
this.$notification.success({
this.$showNotification({
type: 'success',
message: this.$t('label.status'),
description:
`${this.$t('message.enabled.vpn')} ${res.publicip}. ${this.$t('message.enabled.vpn.ip.sec')} ${res.presharedkey}`,

View File

@ -89,25 +89,11 @@
</a-row>
<a-form-item v-if="guestType === 'isolated'">
<tooltip-label slot="label" :title="$t('label.vpc')" :tooltip="apiParams.forvpc.description"/>
<a-switch v-decorator="['forvpc', {initialValue: forVpc}]" :defaultChecked="forVpc" @change="val => { handleForVpcChange(val) }" />
<a-switch v-decorator="['forvpc']" :checked="forVpc" @change="val => { handleForVpcChange(val) }" />
</a-form-item>
<a-form-item :label="$t('label.userdatal2')" v-if="guestType === 'l2'">
<a-switch v-decorator="['userdatal2', {initialValue: false}]" />
</a-form-item>
<a-form-item :label="$t('label.lbtype')" v-if="forVpc && lbServiceChecked">
<a-radio-group
v-decorator="[' ', {
initialValue: 'publicLb'
}]"
buttonStyle="solid">
<a-radio-button value="publicLb">
{{ $t('label.public.lb') }}
</a-radio-button>
<a-radio-button value="internalLb">
{{ $t('label.internal.lb') }}
</a-radio-button>
</a-radio-group>
</a-form-item>
<a-row :gutter="12">
<a-col :md="12" :lg="12">
<a-form-item>
@ -202,15 +188,28 @@
v-decorator="['service.'+item.name, {}]"
:resourceKey="item.name"
:checkBoxLabel="item.description"
:checkBoxDecorator="'service.' + item.name"
:selectOptions="item.provider"
:selectDecorator="item.name + '.provider'"
:selectOptions="!supportedServiceLoading ? item.provider: []"
@handle-checkselectpair-change="handleSupportedServiceChange"/>
</a-list-item>
</a-list>
</div>
</a-form-item>
<a-form-item v-if="isVirtualRouterForAtLeastOneService">
<a-form-item :label="$t('label.lbtype')" v-if="forVpc && lbServiceChecked">
<a-radio-group
v-decorator="['lbType', {
initialValue: lbType
}]"
buttonStyle="solid"
@change="e => { handleLbTypeChange(e.target.value) }" >
<a-radio-button value="publicLb">
{{ $t('label.public.lb') }}
</a-radio-button>
<a-radio-button value="internalLb">
{{ $t('label.internal.lb') }}
</a-radio-button>
</a-radio-group>
</a-form-item>
<a-form-item v-if="isVirtualRouterForAtLeastOneService || isVpcVirtualRouterForAtLeastOneService">
<tooltip-label slot="label" :title="$t('label.serviceofferingid')" :tooltip="apiParams.serviceofferingid.description"/>
<a-select
v-decorator="['serviceofferingid', {
@ -453,6 +452,7 @@ export default {
selectedDomains: [],
selectedZones: [],
forVpc: false,
lbType: 'publicLb',
macLearningValue: '',
supportedServices: [],
supportedServiceLoading: false,
@ -463,6 +463,8 @@ export default {
sourceNatServiceChecked: false,
lbServiceChecked: false,
lbServiceProvider: '',
registeredServicePackages: [],
registeredServicePackageLoading: false,
isElasticIp: false,
staticNatServiceChecked: false,
staticNatServiceProvider: '',
@ -531,6 +533,27 @@ export default {
},
handleGuestTypeChange (val) {
this.guestType = val
if (val === 'l2') {
this.forVpc = false
this.lbType = 'publicLb'
this.isVirtualRouterForAtLeastOneService = false
this.isVpcVirtualRouterForAtLeastOneService = false
this.serviceOfferings = []
this.serviceOfferingLoading = false
this.sourceNatServiceChecked = false
this.lbServiceChecked = false
this.lbServiceProvider = ''
this.registeredServicePackages = []
this.registeredServicePackageLoading = false
this.isElasticIp = false
this.staticNatServiceChecked = false
this.staticNatServiceProvider = ''
this.connectivityServiceChecked = false
this.firewallServiceChecked = false
this.firewallServiceProvider = ''
this.selectedServiceProviderMap = {}
this.updateSupportedServices()
}
},
fetchSupportedServiceData () {
const params = {}
@ -594,18 +617,21 @@ export default {
this.supportedServices[i].provider = providers
this.supportedServices[i].description = serviceDisplayName
}
}).finally(() => {
this.supportedServiceLoading = false
this.updateSupportedServices()
})
},
fetchServiceOfferingData () {
const params = {}
params.issystem = true
params.systemvmtype = 'domainrouter'
this.supportedServiceLoading = true
this.serviceOfferingLoading = true
api('listServiceOfferings', params).then(json => {
const listServiceOfferings = json.listserviceofferingsresponse.serviceoffering
this.serviceOfferings = this.serviceOfferings.concat(listServiceOfferings)
}).finally(() => {
this.supportedServiceLoading = false
this.serviceOfferingLoading = false
})
},
fetchRegisteredServicePackageData () {
@ -627,32 +653,41 @@ export default {
this.registeredServicePackageLoading = false
})
},
handleForVpcChange (forVpc) {
updateSupportedServices () {
this.supportedServiceLoading = true
var supportedServices = this.supportedServices
var self = this
this.forVpc = forVpc
this.supportedServices.forEach(function (svc, index) {
if (svc !== 'Connectivity') {
supportedServices.forEach(function (svc, index) {
if (svc.name !== 'Connectivity') {
var providers = svc.provider
providers.forEach(function (provider, providerIndex) {
if (self.forVpc) { // *** vpc ***
if (provider.name === 'InternalLbVm' || provider.name === 'VpcVirtualRouter' || provider.name === 'Netscaler' || provider.name === 'BigSwitchBcf' || provider.name === 'ConfigDrive') {
provider.enabled = true
} else {
provider.enabled = false
var enabledProviders = ['VpcVirtualRouter', 'Netscaler', 'BigSwitchBcf', 'ConfigDrive']
if (self.lbType === 'internalLb') {
enabledProviders.push('InternalLbVm')
}
provider.enabled = enabledProviders.includes(provider.name)
} else { // *** non-vpc ***
if (provider.name === 'InternalLbVm' || provider.name === 'VpcVirtualRouter') {
provider.enabled = false
} else {
provider.enabled = true
}
provider.enabled = !['InternalLbVm', 'VpcVirtualRouter'].includes(provider.name)
}
providers[providerIndex] = provider
})
svc.provider = providers
self.supportedServices[index] = svc
supportedServices[index] = svc
}
})
setTimeout(() => {
self.supportedServices = supportedServices
self.supportedServiceLoading = false
}, 50)
},
handleForVpcChange (forVpc) {
this.forVpc = forVpc
this.updateSupportedServices()
},
handleLbTypeChange (lbType) {
this.lbType = lbType
this.updateSupportedServices()
},
handleSupportedServiceChange (service, checked, provider) {
if (service === 'SourceNat') {
@ -696,13 +731,14 @@ export default {
providers.forEach(function (prvdr, idx) {
if (prvdr === 'VirtualRouter') {
self.isVirtualRouterForAtLeastOneService = true
if (self.serviceOfferings.length === 0) {
self.fetchServiceOfferingData()
}
}
if (prvdr === 'VpcVirtualRouter') {
self.isVpcVirtualRouterForAtLeastOneService = true
}
if ((self.isVirtualRouterForAtLeastOneService || self.isVpcVirtualRouterForAtLeastOneService) &&
self.serviceOfferings.length === 0) {
self.fetchServiceOfferingData()
}
})
},
handleSubmit (e) {
@ -724,7 +760,7 @@ export default {
var selectedServices = null
var keys = Object.keys(values)
const detailsKey = ['promiscuousmode', 'macaddresschanges', 'forgedtransmits', 'maclearning']
const ignoredKeys = [...detailsKey, 'state', 'status', 'allocationstate', 'forvpc', 'specifyvlan', 'ispublic', 'domainid', 'zoneid', 'egressdefaultpolicy', 'isolation', 'supportspublicaccess']
const ignoredKeys = [...detailsKey, 'state', 'status', 'allocationstate', 'forvpc', 'lbType', 'specifyvlan', 'ispublic', 'domainid', 'zoneid', 'egressdefaultpolicy', 'isolation', 'supportspublicaccess']
keys.forEach(function (key, keyIndex) {
if (self.isSupportedServiceObject(values[key])) {
if (selectedServices == null) {

View File

@ -48,9 +48,7 @@
v-decorator="['service.'+item.name, {}]"
:resourceKey="item.name"
:checkBoxLabel="item.description"
:checkBoxDecorator="'service.' + item.name"
:selectOptions="item.provider"
:selectDecorator="item.name + '.provider'"
@handle-checkselectpair-change="handleSupportedServiceChange"/>
</a-list-item>
</a-list>

View File

@ -297,7 +297,8 @@ export default {
this.loading = true
api('update' + this.offeringType, params).then(json => {
this.$emit('refresh-data')
this.$notification.success({
this.$showNotification({
type: 'success',
message: this.$t('label.action.update.offering.access'),
description: this.$t('label.action.update.offering.access')
})

View File

@ -125,7 +125,8 @@ export default {
this.$message.success(`${this.$t('message.setting.updated')} ${this.resource.description}`)
this.onClose()
}).catch(error => {
this.$notification.error({
this.$showNotification({
type: 'error',
message: this.$t('message.request.failed'),
description: (error.response && error.response.headers && error.response.headers['x-description']) || error.message
})

View File

@ -229,7 +229,8 @@ export default {
params[key] = input
}
api('updateProjectRole', params).then(response => {
this.$notification.success({
this.$showNotification({
type: 'success',
message: this.$t('label.update.project.role'),
description: this.$t('label.update.project.role')
})
@ -268,7 +269,8 @@ export default {
params[key] = input
}
api('createProjectRole', params).then(response => {
this.$notification.success({
this.$showNotification({
type: 'success',
message: this.$t('label.create.project.role'),
description: this.$t('label.create.project.role')
})
@ -287,7 +289,8 @@ export default {
projectid: this.resource.id,
id: role.id
}).then(response => {
this.$notification.success({
this.$showNotification({
type: 'success',
message: this.$t('label.delete.project.role'),
description: this.$t('label.delete.project.role')
})

View File

@ -385,7 +385,8 @@ export default {
this.actionLoading = true
api('createSnapshotPolicy', params).then(json => {
this.$emit('refresh')
this.$notification.success({
this.$showNotification({
type: 'success',
message: this.$t('label.action.recurring.snapshot'),
description: this.$t('message.success.recurring.snapshot')
})

View File

@ -125,7 +125,8 @@ export default {
})
this.closeModal()
}).catch(error => {
this.$notification.error({
this.$showNotification({
type: 'error',
message: `${this.$t('label.error')} ${error.response.status}`,
description: error.response.data.errorresponse.errortext,
duration: 0

View File

@ -162,7 +162,8 @@ export default {
this.actionLoading = true
api('deleteSnapshotPolicies', params).then(json => {
if (json.deletesnapshotpoliciesresponse.success) {
this.$notification.success({
this.$showNotification({
type: 'success',
message: this.$t('label.delete.snapshot.policy'),
description: this.$t('message.success.delete.snapshot.policy')
})

View File

@ -191,7 +191,8 @@ export default {
this.uploadParams = (json.postuploadvolumeresponse && json.postuploadvolumeresponse.getuploadparams) ? json.postuploadvolumeresponse.getuploadparams : ''
const { fileList } = this
if (this.fileList.length > 1) {
this.$notification.error({
this.$showNotification({
type: 'error',
message: this.$t('message.upload.volume.failed'),
description: this.$t('message.upload.file.limit'),
duration: 0
@ -216,13 +217,15 @@ export default {
},
timeout: 86400000
}).then((json) => {
this.$notification.success({
this.$showNotification({
type: 'success',
message: this.$t('message.success.upload'),
description: this.$t('message.success.upload.volume.description')
})
this.closeAction()
}).catch(e => {
this.$notification.error({
this.$showNotification({
type: 'error',
message: this.$t('message.upload.failed'),
description: `${this.$t('message.upload.iso.failed.description')} - ${e}`,
duration: 0

View File

@ -631,7 +631,8 @@ export default {
displayname: values.displayname
}
if (!this.computeOffering || !this.computeOffering.id) {
this.$notification.error({
this.$showNotification({
type: 'error',
message: this.$t('message.request.failed'),
description: this.$t('message.step.2.continue')
})
@ -642,7 +643,8 @@ export default {
var details = [this.cpuNumberKey, this.cpuSpeedKey, this.memoryKey]
for (var detail of details) {
if (!(values[detail] || this.computeOffering[detail])) {
this.$notification.error({
this.$showNotification({
type: 'error',
message: this.$t('message.request.failed'),
description: this.$t('message.please.enter.valid.value') + ': ' + this.$t('label.' + detail.toLowerCase())
})
@ -657,7 +659,8 @@ export default {
var iopsDetails = [this.minIopsKey, this.maxIopsKey]
for (var iopsDetail of iopsDetails) {
if (!values[iopsDetail] || values[iopsDetail] < 0) {
this.$notification.error({
this.$showNotification({
type: 'error',
message: this.$t('message.request.failed'),
description: this.$t('message.please.enter.valid.value') + ': ' + this.$t('label.' + iopsDetail.toLowerCase())
})
@ -666,7 +669,8 @@ export default {
params['details[0].' + iopsDetail] = values[iopsDetail]
}
if (values[this.minIopsKey] > values[this.maxIopsKey]) {
this.$notification.error({
this.$showNotification({
type: 'error',
message: this.$t('message.request.failed'),
description: this.$t('error.form.message')
})
@ -684,7 +688,8 @@ export default {
var diskOfferingIndex = 0
for (var diskId in this.dataDisksOfferingsMapping) {
if (!this.dataDisksOfferingsMapping[diskId]) {
this.$notification.error({
this.$showNotification({
type: 'error',
message: this.$t('message.request.failed'),
description: this.$t('message.select.disk.offering') + ': ' + diskId
})
@ -698,7 +703,8 @@ export default {
var nicIpIndex = 0
for (var nicId in this.nicsNetworksMapping) {
if (!this.nicsNetworksMapping[nicId].network) {
this.$notification.error({
this.$showNotification({
type: 'error',
message: this.$t('message.request.failed'),
description: this.$t('message.select.nic.network') + ': ' + nicId
})
@ -709,7 +715,8 @@ export default {
nicNetworkIndex++
if ('ipAddress' in this.nicsNetworksMapping[nicId]) {
if (!this.nicsNetworksMapping[nicId].ipAddress) {
this.$notification.error({
this.$showNotification({
type: 'error',
message: this.$t('message.request.failed'),
description: this.$t('message.enter.valid.nic.ip') + ': ' + nicId
})

View File

@ -52,21 +52,14 @@ mocks = {
$notifyError: jest.fn((error) => {
return error
}),
$notification: {
info: jest.fn((option) => {
$showNotification: jest.fn((option) => {
return {
type: option.type,
message: option.message,
description: 'test-description',
duration: option.duration
}
}),
success: jest.fn((option) => {
return {
message: option.message,
description: option.description
}
})
},
$message: {
success: jest.fn((obj) => {
return obj
@ -1577,7 +1570,7 @@ describe('Views > AutogenView.vue', () => {
})
describe('pollActionCompletion()', () => {
it('check $notification when pollActionCompletion() is called with action is empty', (done) => {
it('check $showNotification when pollActionCompletion() is called with action is empty', (done) => {
const mockData = {
queryasyncjobresultresponse: {
jobstatus: 1,
@ -1595,7 +1588,7 @@ describe('Views > AutogenView.vue', () => {
wrapper.vm.pollActionCompletion(jobId, action)
setTimeout(() => {
expect(mocks.$notification.info).not.toHaveBeenCalled()
expect(mocks.$showNotification).not.toHaveBeenCalled()
expect(mockAxios).toHaveBeenCalled()
expect(mockAxios).toHaveBeenCalledWith({
url: '/',
@ -1612,7 +1605,7 @@ describe('Views > AutogenView.vue', () => {
})
})
it('check $notification when pollActionCompletion() is called with action is not empty', (done) => {
it('check $showNotification when pollActionCompletion() is called with action is not empty', (done) => {
const mockData = {
queryasyncjobresultresponse: {
jobstatus: 1,
@ -1633,8 +1626,9 @@ describe('Views > AutogenView.vue', () => {
wrapper.vm.pollActionCompletion(jobId, action)
setTimeout(() => {
expect(mocks.$notification.info).toHaveBeenCalled()
expect(mocks.$notification.info).toHaveLastReturnedWith({
expect(mocks.$showNotification).toHaveBeenCalled()
expect(mocks.$showNotification).toHaveLastReturnedWith({
type: 'info',
message: 'test-name-en',
description: 'test-description',
duration: 0
@ -2787,7 +2781,7 @@ describe('Views > AutogenView.vue', () => {
})
})
it('check $notification when api is called and response have not jobId result', async (done) => {
it('check $message when api is called and response have not jobId result', async (done) => {
wrapper = factory({
data: {
showAction: true,

Some files were not shown because too many files have changed in this diff Show More