diff --git a/api/src/main/java/com/cloud/projects/ProjectService.java b/api/src/main/java/com/cloud/projects/ProjectService.java index 4efc000fae5c..f93b3c576efe 100644 --- a/api/src/main/java/com/cloud/projects/ProjectService.java +++ b/api/src/main/java/com/cloud/projects/ProjectService.java @@ -55,7 +55,7 @@ public interface ProjectService { * - project id * @return true if the project was deleted successfully, false otherwise */ - boolean deleteProject(long id); + boolean deleteProject(long id, Boolean cleanup); /** * Gets a project by id diff --git a/api/src/main/java/com/cloud/storage/VolumeApiService.java b/api/src/main/java/com/cloud/storage/VolumeApiService.java index 5c4130158cd8..6087ece36102 100644 --- a/api/src/main/java/com/cloud/storage/VolumeApiService.java +++ b/api/src/main/java/com/cloud/storage/VolumeApiService.java @@ -99,7 +99,7 @@ Snapshot takeSnapshot(Long volumeId, Long policyId, Long snapshotId, Account acc Snapshot allocSnapshot(Long volumeId, Long policyId, String snapshotName, Snapshot.LocationType locationType) throws ResourceAllocationException; - Volume updateVolume(long volumeId, String path, String state, Long storageId, Boolean displayVolume, String customId, long owner, String chainInfo); + Volume updateVolume(long volumeId, String path, String state, Long storageId, Boolean displayVolume, String customId, long owner, String chainInfo, String name); /** * Extracts the volume to a particular location. diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/project/DeleteProjectCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/project/DeleteProjectCmd.java index 171d51712aeb..c15476c701e9 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/project/DeleteProjectCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/project/DeleteProjectCmd.java @@ -48,6 +48,9 @@ public class DeleteProjectCmd extends BaseAsyncCmd { @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = ProjectResponse.class, required = true, description = "id of the project to be deleted") private Long id; + @Parameter(name = ApiConstants.CLEANUP, type = CommandType.BOOLEAN, description = "true if all project resources have to be cleaned up, false otherwise") + private Boolean cleanup; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -56,6 +59,10 @@ public Long geId() { return id; } + public Boolean isCleanup() { + return cleanup; + } + @Override public String getCommandName() { return s_name; @@ -68,7 +75,7 @@ public String getCommandName() { @Override public void execute() { CallContext.current().setEventDetails("Project Id: " + id); - boolean result = _projectService.deleteProject(id); + boolean result = _projectService.deleteProject(id, isCleanup()); if (result) { SuccessResponse response = new SuccessResponse(getCommandName()); this.setResponseObject(response); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/volume/UpdateVolumeCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/volume/UpdateVolumeCmd.java index 71fb5764ed8f..345221e1de45 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/volume/UpdateVolumeCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/volume/UpdateVolumeCmd.java @@ -52,23 +52,23 @@ public class UpdateVolumeCmd extends BaseAsyncCustomIdCmd implements UserCmd { @Parameter(name=ApiConstants.ID, type=CommandType.UUID, entityType=VolumeResponse.class, description="the ID of the disk volume") private Long id; - @Parameter(name = ApiConstants.PATH, type = CommandType.STRING, description = "The path of the volume") + @Parameter(name = ApiConstants.PATH, type = CommandType.STRING, description = "The path of the volume", authorized = {RoleType.Admin}) private String path; @Parameter(name = ApiConstants.CHAIN_INFO, type = CommandType.STRING, description = "The chain info of the volume", - since = "4.4") + since = "4.4", authorized = {RoleType.Admin}) private String chainInfo; @Parameter(name = ApiConstants.STORAGE_ID, type = CommandType.UUID, entityType = StoragePoolResponse.class, description = "Destination storage pool UUID for the volume", - since = "4.3") + since = "4.3", authorized = {RoleType.Admin}) private Long storageId; - @Parameter(name = ApiConstants.STATE, type = CommandType.STRING, description = "The state of the volume", since = "4.3") + @Parameter(name = ApiConstants.STATE, type = CommandType.STRING, description = "The state of the volume", since = "4.3", authorized = {RoleType.Admin}) private String state; @Parameter(name = ApiConstants.DISPLAY_VOLUME, @@ -76,6 +76,9 @@ public class UpdateVolumeCmd extends BaseAsyncCustomIdCmd implements UserCmd { description = "an optional field, whether to the display the volume to the end user or not.", authorized = {RoleType.Admin}) private Boolean displayVolume; + @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, description = "new name of the volume", since = "4.16") + private String name; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -103,6 +106,11 @@ public Boolean getDisplayVolume() { public String getChainInfo() { return chainInfo; } + + public String getName() { + return name; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// @@ -150,6 +158,11 @@ public String getEventDescription() { if (getState() != null) { desc.append(", state " + getState()); } + + if (getName() != null) { + desc.append(", name " + getName()); + } + return desc.toString(); } @@ -157,7 +170,7 @@ public String getEventDescription() { public void execute() { CallContext.current().setEventDetails("Volume Id: " + this._uuidMgr.getUuid(Volume.class, getId())); Volume result = _volumeService.updateVolume(getId(), getPath(), getState(), getStorageId(), getDisplayVolume(), - getCustomId(), getEntityOwnerId(), getChainInfo()); + getCustomId(), getEntityOwnerId(), getChainInfo(), getName()); if (result != null) { VolumeResponse response = _responseGenerator.createVolumeResponse(getResponseView(), result); response.setResponseName(getCommandName()); diff --git a/server/src/main/java/com/cloud/projects/ProjectManagerImpl.java b/server/src/main/java/com/cloud/projects/ProjectManagerImpl.java index 88ad0c2ffc9d..4a43b4a1c630 100644 --- a/server/src/main/java/com/cloud/projects/ProjectManagerImpl.java +++ b/server/src/main/java/com/cloud/projects/ProjectManagerImpl.java @@ -29,6 +29,7 @@ import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; +import java.util.stream.Stream; import javax.inject.Inject; import javax.mail.Authenticator; @@ -40,6 +41,18 @@ import javax.mail.internet.InternetAddress; import javax.naming.ConfigurationException; +import com.cloud.network.dao.NetworkDao; +import com.cloud.network.dao.NetworkVO; +import com.cloud.network.vpc.Vpc; +import com.cloud.network.vpc.VpcManager; +import com.cloud.storage.VMTemplateVO; +import com.cloud.storage.VolumeVO; +import com.cloud.storage.dao.VMTemplateDao; +import com.cloud.storage.dao.VolumeDao; +import com.cloud.vm.UserVmVO; +import com.cloud.vm.dao.UserVmDao; +import com.cloud.vm.snapshot.VMSnapshotVO; +import com.cloud.vm.snapshot.dao.VMSnapshotDao; import org.apache.cloudstack.acl.ProjectRole; import org.apache.cloudstack.acl.SecurityChecker.AccessType; import org.apache.cloudstack.acl.dao.ProjectRoleDao; @@ -131,6 +144,18 @@ public class ProjectManagerImpl extends ManagerBase implements ProjectManager { private ProjectRoleDao projectRoleDao; @Inject private UserDao userDao; + @Inject + private VolumeDao _volumeDao; + @Inject + private UserVmDao _userVmDao; + @Inject + private VMTemplateDao _templateDao; + @Inject + private NetworkDao _networkDao; + @Inject + private VMSnapshotDao _vmSnapshotDao; + @Inject + private VpcManager _vpcMgr; protected boolean _invitationRequired = false; protected long _invitationTimeOut = 86400000; @@ -301,7 +326,7 @@ public Project enableProject(long projectId) { @Override @ActionEvent(eventType = EventTypes.EVENT_PROJECT_DELETE, eventDescription = "deleting project", async = true) - public boolean deleteProject(long projectId) { + public boolean deleteProject(long projectId, Boolean isCleanup) { CallContext ctx = CallContext.current(); ProjectVO project = getProject(projectId); @@ -313,7 +338,29 @@ public boolean deleteProject(long projectId) { CallContext.current().setProject(project); _accountMgr.checkAccess(ctx.getCallingAccount(), AccessType.ModifyProject, true, _accountMgr.getAccount(project.getProjectAccountId())); - return deleteProject(ctx.getCallingAccount(), ctx.getCallingUserId(), project); + if (isCleanup != null && isCleanup) { + return deleteProject(ctx.getCallingAccount(), ctx.getCallingUserId(), project); + } else { + List userTemplates = _templateDao.listByAccountId(project.getProjectAccountId()); + List vmSnapshots = _vmSnapshotDao.listByAccountId(project.getProjectAccountId()); + List vms = _userVmDao.listByAccountId(project.getProjectAccountId()); + List volumes = _volumeDao.findDetachedByAccount(project.getProjectAccountId()); + List networks = _networkDao.listByOwner(project.getProjectAccountId()); + List vpcs = _vpcMgr.getVpcsForAccount(project.getProjectAccountId()); + + Optional message = Stream.of(userTemplates, vmSnapshots, vms, volumes, networks, vpcs) + .filter(entity -> !entity.isEmpty()) + .map(entity -> entity.size() + " " + entity.get(0).getEntityType().getSimpleName() + " to clean up") + .findFirst(); + + if (message.isEmpty()) { + return deleteProject(ctx.getCallingAccount(), ctx.getCallingUserId(), project); + } + + CloudRuntimeException e = new CloudRuntimeException("Can't delete the project yet because it has " + message.get()); + e.addProxyObject(project.getUuid(), "projectId"); + throw e; + } } @DB diff --git a/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java b/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java index dfca5cd70d39..d86ff5131a60 100644 --- a/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java +++ b/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java @@ -1763,7 +1763,15 @@ public Volume attachVolumeToVM(Long vmId, Long volumeId, Long deviceId) { @Override @ActionEvent(eventType = EventTypes.EVENT_VOLUME_UPDATE, eventDescription = "updating volume", async = true) - public Volume updateVolume(long volumeId, String path, String state, Long storageId, Boolean displayVolume, String customId, long entityOwnerId, String chainInfo) { + public Volume updateVolume(long volumeId, String path, String state, Long storageId, Boolean displayVolume, + String customId, long entityOwnerId, String chainInfo, String name) { + + Account caller = CallContext.current().getCallingAccount(); + if (!_accountMgr.isRootAdmin(caller.getId())) { + if (path != null || state != null || storageId != null || displayVolume != null || customId != null || chainInfo != null) { + throw new InvalidParameterValueException("The domain admin and normal user are not allowed to update volume except volume name"); + } + } VolumeVO volume = _volsDao.findById(volumeId); @@ -1806,6 +1814,10 @@ public Volume updateVolume(long volumeId, String path, String state, Long storag volume.setUuid(customId); } + if (name != null) { + volume.setName(name); + } + updateDisplay(volume, displayVolume); _volsDao.update(volumeId, volume); diff --git a/server/src/test/java/com/cloud/projects/MockProjectManagerImpl.java b/server/src/test/java/com/cloud/projects/MockProjectManagerImpl.java index f8af30f2eb2f..988d675ec04e 100644 --- a/server/src/test/java/com/cloud/projects/MockProjectManagerImpl.java +++ b/server/src/test/java/com/cloud/projects/MockProjectManagerImpl.java @@ -37,7 +37,7 @@ public Project createProject(String name, String displayText, String accountName } @Override - public boolean deleteProject(long id) { + public boolean deleteProject(long id, Boolean cleanup) { // TODO Auto-generated method stub return false; } diff --git a/ui/legacy/scripts/storage.js b/ui/legacy/scripts/storage.js new file mode 100644 index 000000000000..23dc02f26b09 --- /dev/null +++ b/ui/legacy/scripts/storage.js @@ -0,0 +1,3271 @@ +// 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. +(function(cloudStack) { + var migrateVolumeCreateFormAction = { + title: 'label.migrate.volume', + fields: { + storagePool: { + label: 'label.storage.pool', + validation: { + required: true + }, + select: function(args) { + var mapStoragePoolsByUuid = new Map(); + var volumeId = args.context.volumes[0].id; + var volumeBeingMigrated = undefined; + $.ajax({ + url: createURL("listVolumes&id=" + volumeId), + dataType: "json", + async: false, + success: function(json){ + volumeBeingMigrated = json.listvolumesresponse.volume[0]; + } + }); + var currentStoragePool = undefined; + $.ajax({ + url: createURL("listStoragePools&id=" + volumeBeingMigrated.storageid), + dataType: "json", + async: false, + success: function(json){ + currentStoragePool = json.liststoragepoolsresponse.storagepool[0]; + } + }); + var isVolumeNotAttachedToVm = volumeBeingMigrated.virtualmachineid == undefined; + var urlToRetrieveStoragePools = "findStoragePoolsForMigration&id=" + args.context.volumes[0].id; + if(isVolumeNotAttachedToVm){ + urlToRetrieveStoragePools = "listStoragePools&zoneid=" + args.context.volumes[0].zoneid; + } + $.ajax({ + url: createURL(urlToRetrieveStoragePools), + dataType: "json", + async: true, + success: function(json) { + var pools = undefined; + if(isVolumeNotAttachedToVm){ + pools = json.liststoragepoolsresponse.storagepool; + }else{ + pools = json.findstoragepoolsformigrationresponse.storagepool; + } + var items = []; + $(pools).each(function() { + mapStoragePoolsByUuid.set(this.id, this); + var description = this.name; + if(!isVolumeNotAttachedToVm){ + description = description + " (" + (this.suitableformigration ? "Suitable" : "Not Suitable") + ")"; + } + items.push({ + id: this.id, + description: description + }); + }); + args.response.success({ + data: items + }); + var diskOfferings = cloudStack.listDiskOfferings({listAll: true}); + $('select[name=storagePool]').change(function(){ + var uuidOfStoragePoolSelected = $(this).val(); + var storagePoolSelected = mapStoragePoolsByUuid.get(uuidOfStoragePoolSelected); + if(currentStoragePool.scope === storagePoolSelected.scope){ + $('div[rel=newDiskOffering],div[rel=useNewDiskOffering]').hide(); + }else{ + $('div[rel=newDiskOffering],div[rel=useNewDiskOffering]').show(); + } + var storageType = 'shared'; + if(storagePoolSelected.scope == 'HOST'){ + storageType = 'local'; + } + $(diskOfferings).each(function(){ + var diskOfferingOption = $('option[value=' + this.id + ']'); + if(this.storagetype == storageType){ + diskOfferingOption.show(); + }else{ + diskOfferingOption.hide(); + } + }); + var firstAvailableDiskOfferingForStorageType = $('select#label_disk_newOffering').children('option:visible').first().attr('value'); + $('select#label_disk_newOffering').attr('value', firstAvailableDiskOfferingForStorageType); + }); + var functionHideShowNewDiskOfferint = function(){ + if($('div[rel=useNewDiskOffering] input[type=checkbox]').is(':checked')){ + $('div[rel=newDiskOffering]').show(); + }else{ + $('div[rel=newDiskOffering]').hide(); + } + }; + $('div[rel=useNewDiskOffering] input[type=checkbox]').click(functionHideShowNewDiskOfferint); + $('select[name=storagePool]').change(); + functionHideShowNewDiskOfferint(); + } + }); + } + }, + useNewDiskOffering:{ + label: 'label.migrate.volume.newDiskOffering', + desc: 'label.migrate.volume.newDiskOffering.desc', + validation: { + required: false + }, + isEditable: true, + isBoolean: true, + defaultValue: 'Yes' + }, + newDiskOffering: { + label: 'label.disk.newOffering', + desc: 'label.disk.newOffering.description', + validation: { + required: false + }, + select: function(args){ + var diskOfferings = cloudStack.listDiskOfferings({listAll: true}); + var items = []; + $(diskOfferings).each(function() { + items.push({ + id: this.id, + description: this.name + }); + }); + args.response.success({ + data: items + }); + } + } + } + }; + var functionMigrateVolume = function(args) { + var volumeBeingMigrated = args.context.volumes[0]; + var isLiveMigrate = volumeBeingMigrated.vmstate == 'Running'; + var migrateVolumeUrl = "migrateVolume&livemigrate="+ isLiveMigrate +"&storageid=" + args.data.storagePool + "&volumeid=" + volumeBeingMigrated.id; + if($('div[rel=useNewDiskOffering] input[name=useNewDiskOffering]:checkbox').is(':checked')){ + migrateVolumeUrl = migrateVolumeUrl + '&newdiskofferingid=' + $('div[rel=newDiskOffering] select').val(); + } + $.ajax({ + url: createURL(migrateVolumeUrl), + dataType: "json", + async: true, + success: function(json) { + $(window).trigger('cloudStack.fullRefresh'); + var jid = json.migratevolumeresponse.jobid; + args.response.success({ + _custom: { + jobId: jid + } + }); + } + }); + } + + var diskOfferingsObjList, selectedDiskOfferingObj = null; + + cloudStack.sections.storage = { + title: 'label.storage', + id: 'storage', + sectionSelect: { + label: 'label.select-view' + }, + sections: { + /** + * Volumes + */ + volumes: { + type: 'select', + title: 'label.volumes', + listView: { + id: 'volumes', + label: 'label.volumes', + preFilter: function(args) { + var hiddenFields = []; + if (isAdmin() != true) { + hiddenFields.push('hypervisor'); + hiddenFields.push('account'); + } + return hiddenFields; + }, + fields: { + name: { + label: 'label.name' + }, + type: { + label: 'label.type' + }, + vmdisplayname: { + label: 'label.vm.display.name' + }, + hypervisor: { + label: 'label.hypervisor' + }, + account: { + label: 'label.account' + }, + zonename: { + label: 'label.zone' + }, + vmdisplayname: { + label: 'label.vm.display.name' + }, + state: { + label: 'label.metrics.state', + converter: function (str) { + // For localization + return str; + }, + indicator: { + 'Allocated': 'on', + 'Ready': 'on', + 'Destroy': 'off', + 'Expunging': 'off', + 'Expunged': 'off', + 'Migrating': 'warning', + 'UploadOp': 'warning', + 'Snapshotting': 'warning', + } + } + }, + + // List view actions + actions: { + // Add volume + add: { + label: 'label.add', + + preFilter: function(args) { + return !args.context.instances; + }, + + messages: { + confirm: function(args) { + return 'message.add.volume'; + }, + notification: function(args) { + return 'label.add.volume'; + } + }, + + createForm: { + title: 'label.add.volume', + desc: 'message.add.volume', + fields: { + name: { + docID: 'helpVolumeName', + label: 'label.name' + }, + availabilityZone: { + label: 'label.availability.zone', + docID: 'helpVolumeAvailabilityZone', + select: function(args) { + $.ajax({ + url: createURL("listZones&available=true"), + dataType: "json", + async: true, + success: function(json) { + var zoneObjs = json.listzonesresponse.zone; + args.response.success({ + descriptionField: 'name', + data: zoneObjs + }); + } + }); + args.$select.change(function() { + var diskOfferingSelect = $(this).closest('form').find('select[name=diskOffering]'); + if(diskOfferingSelect) { + $(diskOfferingSelect).find('option').remove().end(); + var data = { + zoneid: $(this).val(), + }; + console.log(data); + var diskOfferings = cloudStack.listDiskOfferings({ data: data }); + diskOfferingsObjList = diskOfferings; + $(diskOfferings).each(function() { + $(diskOfferingSelect).append(new Option(this.displaytext, this.id)); + }); + } + }); + } + }, + diskOffering: { + label: 'label.disk.offering', + docID: 'helpVolumeDiskOffering', + select: function(args) { + var diskOfferings = cloudStack.listDiskOfferings({}); + diskOfferingsObjList = diskOfferings; + var items = []; + $(diskOfferings).each(function() { + items.push({ + id: this.id, + description: this.displaytext + }); + }); + args.response.success({ + data: items + }); + args.$select.change(function() { + var diskOfferingId = $(this).val(); + $(diskOfferingsObjList).each(function() { + if (this.id == diskOfferingId) { + selectedDiskOfferingObj = this; + return false; //break the $.each() loop + } + }); + if (selectedDiskOfferingObj == null){ + return; + } + var $form = $(this).closest('form'); + var $diskSize = $form.find('.form-item[rel=diskSize]'); + if (selectedDiskOfferingObj.iscustomized == true) { + $diskSize.css('display', 'inline-block'); + } else { + $diskSize.hide(); + } + var $minIops = $form.find('.form-item[rel=minIops]'); + var $maxIops = $form.find('.form-item[rel=maxIops]'); + if (selectedDiskOfferingObj.iscustomizediops == true) { + $minIops.css('display', 'inline-block'); + $maxIops.css('display', 'inline-block'); + } else { + $minIops.hide(); + $maxIops.hide(); + } + }); + } + } + + , + diskSize: { + label: 'label.disk.size.gb', + docID: 'helpVolumeSizeGb', + validation: { + required: true, + number: true + }, + isHidden: true + }, + + minIops: { + label: 'label.disk.iops.min', + validation: { + required: false, + number: true + }, + isHidden: true + }, + + maxIops: { + label: 'label.disk.iops.max', + validation: { + required: false, + number: true + }, + isHidden: true + } + + } + }, + + action: function(args) { + var data = { + name: args.data.name, + zoneId: args.data.availabilityZone, + diskOfferingId: args.data.diskOffering + }; + + // if(thisDialog.find("#size_container").css("display") != "none") { //wait for Brian to include $form in args + if (selectedDiskOfferingObj.iscustomized == true) { + $.extend(data, { + size: args.data.diskSize + }); + } + + if (selectedDiskOfferingObj.iscustomizediops == true) { + if (args.data.minIops != "" && args.data.minIops > 0) { + $.extend(data, { + miniops: args.data.minIops + }); + } + + if (args.data.maxIops != "" && args.data.maxIops > 0) { + $.extend(data, { + maxiops: args.data.maxIops + }); + } + } + + $.ajax({ + url: createURL('createVolume'), + data: data, + success: function(json) { + var jid = json.createvolumeresponse.jobid; + args.response.success({ + _custom: { + jobId: jid, + getUpdatedItem: function(json) { + return json.queryasyncjobresultresponse.jobresult.volume; + }, + getActionFilter: function() { + return volumeActionfilter; + } + } + }); + }, + error: function(json) { + args.response.error(parseXMLHttpResponse(json)); + } + }); + }, + + notification: { + poll: pollAsyncJobResult + } + }, + + viewMetrics: { + label: 'label.metrics', + isHeader: true, + addRow: false, + action: { + custom: cloudStack.uiCustom.metricsView({resource: 'volumes'}) + }, + messages: { + notification: function (args) { + return 'label.metrics'; + } + } + }, + + uploadVolume: { + isHeader: true, + label: 'label.upload', + preFilter: function(args) { + return !args.context.instances; + }, + messages: { + notification: function() { + return 'label.upload.volume.from.url'; + } + }, + createForm: { + title: 'label.upload.volume.from.url', + fields: { + url: { + label: 'label.url', + docID: 'helpUploadVolumeURL', + validation: { + required: true + } + }, + name: { + label: 'label.name', + validation: { + required: true + }, + docID: 'helpUploadVolumeName' + }, + availabilityZone: { + label: 'label.availability.zone', + docID: 'helpUploadVolumeZone', + select: function(args) { + $.ajax({ + url: createURL("listZones&available=true"), + dataType: "json", + async: true, + success: function(json) { + var zoneObjs = json.listzonesresponse.zone; + args.response.success({ + descriptionField: 'name', + data: zoneObjs + }); + } + }); + } + }, + format: { + label: 'label.format', + docID: 'helpUploadVolumeFormat', + select: function(args) { + var items = []; + items.push({ + id: 'RAW', + description: 'RAW' + }); + items.push({ + id: 'VHD', + description: 'VHD' + }); + items.push({ + id: 'VHDX', + description: 'VHDX' + }); + items.push({ + id: 'OVA', + description: 'OVA' + }); + items.push({ + id: 'QCOW2', + description: 'QCOW2' + }); + args.response.success({ + data: items + }); + } + + }, + diskOffering: { + label: 'label.custom.disk.offering', + docID: 'helpVolumeDiskOffering', + select: function(args) { + var diskOfferings = cloudStack.listDiskOfferings({}); + var items = [{ + id: '', + description: '' + }]; + $(diskOfferings).each(function() { + if (this.iscustomized == true) { + items.push({ + id: this.id, + description: this.name + }); + } + }); + args.response.success({ + data: items + }); + } + }, + checksum: { + docID: 'helpUploadVolumeChecksum', + label: 'label.md5.checksum' + } + } + }, + + action: function(args) { + var data = { + name: args.data.name, + zoneId: args.data.availabilityZone, + format: args.data.format, + url: args.data.url + }; + + if (args.data.diskOffering != '' && args.data.diskOffering.length > 0) { + $.extend(data, { + diskofferingid: args.data.diskOffering + }); + } + + if (args.data.checksum != null && args.data.checksum.length > 0) { + $.extend(data, { + checksum: args.data.checksum + }); + } + + $.ajax({ + url: createURL('uploadVolume'), + data: data, + success: function(json) { + var jid = json.uploadvolumeresponse.jobid; + args.response.success({ + _custom: { + jobId: jid, + getUpdatedItem: function(json) { + return json.queryasyncjobresultresponse.jobresult.volume; + }, + getActionFilter: function() { + return volumeActionfilter; + } + } + }); + }, + error: function(json) { + args.response.error(parseXMLHttpResponse(json)); + } + }); + }, + + notification: { + poll: pollAsyncJobResult + } + }, + + uploadVolumefromLocal: { + isHeader: true, + label: 'label.upload.from.local', + preFilter: function(args) { + return !args.context.instances; + }, + messages: { + notification: function() { + return 'label.upload.volume.from.local'; + } + }, + createForm: { + title: 'label.upload.volume.from.local', + fileUpload: { + getURL: function(args) { + args.data = args.formData; + + var data = { + name: args.data.name, + zoneId: args.data.availabilityZone, + format: args.data.format, + url: args.data.url + }; + + if (args.data.checksum != null && args.data.checksum.length > 0) { + $.extend(data, { + checksum: args.data.checksum + }); + } + + $.ajax({ + url: createURL('getUploadParamsForVolume'), + data: data, + async: false, + success: function(json) { + var uploadparams = json.postuploadvolumeresponse.getuploadparams; //son.postuploadvolumeresponse.getuploadparams is an object, not an array of object. + var volumeId = uploadparams.id; + + args.response.success({ + url: uploadparams.postURL, + ajaxPost: true, + data: { + 'X-signature': uploadparams.signature, + 'X-expires': uploadparams.expires, + 'X-metadata': uploadparams.metadata + } + }); + } + }); + }, + postUpload: function(args) { + if(args.error) { + args.response.error(args.errorMsg); + } else { + cloudStack.dialog.notice({ + message: "This volume file has been uploaded. Please check its status at Stroage menu > Volumes > " + args.data.name + " > Status field." + }); + args.response.success(); + } + } + }, + fields: { + volumeFileUpload: { + label: 'label.local.file', + isFileUpload: true, + validation: { + required: true + } + }, + name: { + label: 'label.name', + validation: { + required: true + }, + docID: 'helpUploadVolumeName' + }, + availabilityZone: { + label: 'label.availability.zone', + docID: 'helpUploadVolumeZone', + select: function(args) { + $.ajax({ + url: createURL("listZones&available=true"), + dataType: "json", + async: true, + success: function(json) { + var zoneObjs = json.listzonesresponse.zone; + args.response.success({ + descriptionField: 'name', + data: zoneObjs + }); + } + }); + } + }, + format: { + label: 'label.format', + docID: 'helpUploadVolumeFormat', + select: function(args) { + var items = []; + items.push({ + id: 'RAW', + description: 'RAW' + }); + items.push({ + id: 'VHD', + description: 'VHD' + }); + items.push({ + id: 'VHDX', + description: 'VHDX' + }); + items.push({ + id: 'OVA', + description: 'OVA' + }); + items.push({ + id: 'QCOW2', + description: 'QCOW2' + }); + args.response.success({ + data: items + }); + } + }, + checksum: { + docID: 'helpUploadVolumeChecksum', + label: 'label.md5.checksum' + } + } + }, + + action: function(args) { + return; //createForm.fileUpload.getURL() has executed the whole action. Therefore, nothing needs to be done here. + }, + + notification: { + poll: pollAsyncJobResult + } + } + }, + + advSearchFields: { + name: { + label: 'label.name' + }, + zoneid: { + label: 'label.zone', + select: function(args) { + $.ajax({ + url: createURL('listZones'), + data: {}, + success: function(json) { + var zones = json.listzonesresponse.zone ? json.listzonesresponse.zone : []; + + args.response.success({ + data: $.map(zones, function(zone) { + return { + id: zone.id, + description: zone.name + }; + }) + }); + } + }); + } + }, + + domainid: { + label: 'label.domain', + select: function(args) { + if (isAdmin() || isDomainAdmin()) { + $.ajax({ + url: createURL('listDomains'), + data: { + listAll: true, + details: 'min' + }, + success: function(json) { + var array1 = [{ + id: '', + description: '' + }]; + var domains = json.listdomainsresponse.domain; + if (domains != null && domains.length > 0) { + for (var i = 0; i < domains.length; i++) { + array1.push({ + id: domains[i].id, + description: domains[i].path + }); + } + } + array1.sort(function(a, b) { + return a.description.localeCompare(b.description); + }); + args.response.success({ + data: array1 + }); + } + }); + } else { + args.response.success({ + data: null + }); + } + }, + isHidden: function(args) { + if (isAdmin() || isDomainAdmin()) + return false; + else + return true; + } + }, + + account: { + label: 'label.account', + isHidden: function(args) { + if (isAdmin() || isDomainAdmin()) + return false; + else + return true; + } + }, + + state: { + label: 'label.state', + select: function(args) { + args.response.success({ + data: [{ + name: '', + description: '' + }, { + name: 'Allocated', + description: 'state.Allocated' + }, { + name: 'Ready', + description: 'state.Ready' + }, { + name: 'Destroy', + description: 'state.Destroy' + }, { + name: 'Expunging', + description: 'state.Expunging' + }, { + name: 'Expunged', + description: 'state.Expunged' + }] + }); + } + }, + + tagKey: { + label: 'label.tag.key' + }, + tagValue: { + label: 'label.tag.value' + } + }, + + dataProvider: function(args) { + var data = {}; + listViewDataProvider(args, data); + + if (args.context != null) { + if ("instances" in args.context) { + $.extend(data, { + virtualMachineId: args.context.instances[0].id + }); + } + if ("primarystorages" in args.context) { + $.extend(data, { + storageid: args.context.primarystorages[0].id + }); + } + } + + $.ajax({ + url: createURL('listVolumes'), + data: data, + success: function(json) { + var items = json.listvolumesresponse.volume; + args.response.success({ + actionFilter: volumeActionfilter, + data: items + }); + }, + error: function(XMLHttpResponse) { + cloudStack.dialog.notice({ + message: parseXMLHttpResponse(XMLHttpResponse) + }); + args.response.error(); + } + }); + }, + + detailView: { + name: 'label.volume.details', + viewAll: { + path: 'storage.snapshots', + label: 'label.snapshots' + }, + actions: { + migrateVolume: { + label: 'label.migrate.volume', + messages: { + confirm: function(args) { + return 'message.confirm.migrate.volume'; + }, + notification: function(args) { + return 'label.volume.migrated'; + } + }, + + createForm: migrateVolumeCreateFormAction, + + action: functionMigrateVolume, + notification: { + poll: pollAsyncJobResult + } + + }, + + takeSnapshot: { + label: 'label.action.take.snapshot', + messages: { + notification: function(args) { + return 'label.action.take.snapshot'; + } + }, + createForm: { + title: 'label.action.take.snapshot', + desc: 'message.action.take.snapshot', + fields: { + quiescevm: { + label: 'label.quiesce.vm', + isBoolean: true, + isHidden: function(args) { + if (args.context.volumes[0].quiescevm == true) + return false; + else + return true; + } + }, + name: { + label: 'label.name' + }, + asyncBackup: { + label: 'label.async.backup', + isBoolean: true + }, + tags: { + label: 'label.tags', + tagger: true + } + } + }, + action: function(args) { + var data = { + volumeId: args.context.volumes[0].id, + quiescevm: (args.data.quiescevm == 'on' ? true: false), + asyncBackup: (args.data.asyncBackup == 'on' ? true: false) + }; + if (args.data.name != null && args.data.name.length > 0) { + $.extend(data, { + name: args.data.name + }); + } + if (!$.isEmptyObject(args.data.tags)) { + $(args.data.tags).each(function(idx, tagData) { + var formattedTagData = {}; + formattedTagData["tags[" + _s(idx) + "].key"] = _s(tagData.key); + formattedTagData["tags[" + _s(idx) + "].value"] = _s(tagData.value); + $.extend(data, formattedTagData); + }); + } + + $.ajax({ + url: createURL("createSnapshot"), + data: data, + dataType: "json", + async: true, + success: function(json) { + var jid = json.createsnapshotresponse.jobid; + args.response.success({ + _custom: { + jobId: jid, //take snapshot from a volume doesn't change any property in this volume. So, don't need to specify getUpdatedItem() to return updated volume. Besides, createSnapshot API doesn't return updated volume. + onComplete: function(json, customData) { + var volumeId = json.queryasyncjobresultresponse.jobresult.snapshot.volumeid; + var snapshotId = json.queryasyncjobresultresponse.jobresult.snapshot.id; + cloudStack.dialog.notice({ + message: "Created snapshot for volume " + volumeId + " with snapshot ID " + snapshotId + }); + } + } + }); + } + }); + }, + notification: { + poll: pollAsyncJobResult + } + }, + + recurringSnapshot: { + label: 'label.snapshot.schedule', + action: { + custom: cloudStack.uiCustom.recurringSnapshots({ + desc: 'message.snapshot.schedule', + dataProvider: function(args) { + $.ajax({ + url: createURL('listSnapshotPolicies'), + data: { + volumeid: args.context.volumes[0].id + }, + async: true, + dataType: 'json', + success: function(data) { + args.response.success({ + data: $.map( + data.listsnapshotpoliciesresponse.snapshotpolicy ? data.listsnapshotpoliciesresponse.snapshotpolicy : [], + function(snapshot, index) { + return { + id: snapshot.id, + type: snapshot.intervaltype, + time: snapshot.intervaltype > 0 ? snapshot.schedule.split(':')[1] + ':' + snapshot.schedule.split(':')[0] : snapshot.schedule, + timezone: snapshot.timezone, + keep: snapshot.maxsnaps, + 'day-of-week': snapshot.intervaltype == 2 ? snapshot.schedule.split(':')[2] : null, + 'day-of-month': snapshot.intervaltype == 3 ? snapshot.schedule.split(':')[2] : null + }; + } + ) + }); + } + }); + }, + actions: { + add: function(args) { + var snap = args.snapshot; + + var data = { + volumeid: args.context.volumes[0].id, + intervaltype: snap['snapshot-type'], + maxsnaps: snap.maxsnaps, + timezone: snap.timezone + }; + + var convertTime = function(minute, hour, meridiem, extra) { + var convertedHour = meridiem == 'PM' ? + (hour != 12 ? parseInt(hour) + 12 : 12) : (hour != 12 ? hour : '00'); + var time = minute + ':' + convertedHour; + if (extra) time += ':' + extra; + + return time; + }; + + switch (snap['snapshot-type']) { + case 'hourly': // Hourly + $.extend(data, { + schedule: snap.schedule + }); + break; + + case 'daily': // Daily + $.extend(data, { + schedule: convertTime( + snap['time-minute'], + snap['time-hour'], + snap['time-meridiem'] + ) + }); + break; + + case 'weekly': // Weekly + $.extend(data, { + schedule: convertTime( + snap['time-minute'], + snap['time-hour'], + snap['time-meridiem'], + snap['day-of-week'] + ) + }); + break; + + case 'monthly': // Monthly + $.extend(data, { + schedule: convertTime( + snap['time-minute'], + snap['time-hour'], + snap['time-meridiem'], + snap['day-of-month'] + ) + }); + break; + } + + if (!$.isEmptyObject(snap.tags)) { + $(snap.tags).each(function(idx, tagData) { + var formattedTagData = {}; + formattedTagData["tags[" + _s(idx) + "].key"] = _s(tagData.key); + formattedTagData["tags[" + _s(idx) + "].value"] = _s(tagData.value); + $.extend(data, formattedTagData); + }); + } + + $.ajax({ + url: createURL('createSnapshotPolicy'), + data: data, + dataType: 'json', + async: true, + success: function(successData) { + var snapshot = successData.createsnapshotpolicyresponse.snapshotpolicy; + + args.response.success({ + data: { + id: snapshot.id, + type: snapshot.intervaltype, + time: snapshot.intervaltype > 0 ? snapshot.schedule.split(':')[1] + ':' + snapshot.schedule.split(':')[0] : snapshot.schedule, + timezone: snapshot.timezone, + keep: snapshot.maxsnaps, + 'day-of-week': snapshot.intervaltype == 2 ? snapshot.schedule.split(':')[2] : null, + 'day-of-month': snapshot.intervaltype == 3 ? snapshot.schedule.split(':')[2] : null + } + }); + } + }); + }, + remove: function(args) { + $.ajax({ + url: createURL('deleteSnapshotPolicies'), + data: { + id: args.snapshot.id + }, + dataType: 'json', + async: true, + success: function(data) { + args.response.success(); + } + }); + } + }, + + // Select data + selects: { + schedule: function(args) { + var time = []; + + for (var i = 1; i <= 59; i++) { + time.push({ + id: i, + name: i + }); + } + + args.response.success({ + data: time + }); + }, + timezone: function(args) { + args.response.success({ + data: $.map(timezoneMap, function(value, key) { + return { + id: key, + name: value + }; + }) + }); + }, + 'day-of-week': function(args) { + args.response.success({ + data: [{ + id: 1, + name: 'label.sunday' + }, { + id: 2, + name: 'label.monday' + }, { + id: 3, + name: 'label.tuesday' + }, { + id: 4, + name: 'label.wednesday' + }, { + id: 5, + name: 'label.thursday' + }, { + id: 6, + name: 'label.friday' + }, { + id: 7, + name: 'label.saturday' + }] + }); + }, + + 'day-of-month': function(args) { + var time = []; + + for (var i = 1; i <= 28; i++) { + time.push({ + id: i, + name: i + }); + } + + args.response.success({ + data: time + }); + }, + + 'time-hour': function(args) { + var time = []; + + for (var i = 1; i <= 12; i++) { + time.push({ + id: i, + name: i + }); + } + + args.response.success({ + data: time + }); + }, + + 'time-minute': function(args) { + var time = []; + + for (var i = 0; i <= 59; i++) { + time.push({ + id: i < 10 ? '0' + i : i, + name: i < 10 ? '0' + i : i + }); + } + + args.response.success({ + data: time + }); + }, + + 'time-meridiem': function(args) { + args.response.success({ + data: [{ + id: 'AM', + name: 'AM' + }, { + id: 'PM', + name: 'PM' + }] + }); + } + } + }) + }, + messages: { + notification: function(args) { + return 'label.snapshot.schedule'; + } + } + }, + + attachDisk: { + addRow: 'false', + label: 'label.action.attach.disk', + messages: { + confirm: function(args) { + return 'message.confirm.attach.disk'; + }, + notification: function(args) { + return 'label.action.attach.disk'; + } + }, + createForm: { + title: 'label.action.attach.disk', + desc: 'label.action.attach.disk', + fields: { + virtualMachineId: { + label: 'label.instance', + select: function(args) { + var zoneid = args.context.volumes[0].zoneid; + var items = []; + var data; + + if (!args.context.projects) { + data = { + zoneid: zoneid, + domainid: args.context.volumes[0].domainid, + account: args.context.volumes[0].account + }; + } else { + data = { + zoneid: zoneid, + projectid: args.context.projects[0].id + }; + } + + if (args.context.volumes[0].hypervisor != null && args.context.volumes[0].hypervisor.length > 0 && args.context.volumes[0].hypervisor != 'None') { + data = $.extend(data, { + hypervisor: args.context.volumes[0].hypervisor + }); + } + + $(['Running', 'Stopped']).each(function() { + $.ajax({ + url: createURL('listVirtualMachines'), + data: $.extend(data, { + state: this.toString() + }), + async: false, + success: function(json) { + var instanceObjs = json.listvirtualmachinesresponse.virtualmachine; + $(instanceObjs).each(function() { + items.push({ + id: this.id, + description: this.displayname ? this.displayname : this.name + }); + }); + } + }); + }); + + args.response.success({ + data: items + }); + } + } + } + }, + action: function(args) { + $.ajax({ + url: createURL("attachVolume&id=" + args.context.volumes[0].id + '&virtualMachineId=' + args.data.virtualMachineId), + dataType: "json", + async: true, + success: function(json) { + var jid = json.attachvolumeresponse.jobid; + args.response.success({ + _custom: { + jobId: jid, + getUpdatedItem: function(json) { + return json.queryasyncjobresultresponse.jobresult.volume; + }, + getActionFilter: function() { + return volumeActionfilter; + } + } + }); + } + }); + }, + notification: { + poll: pollAsyncJobResult + } + }, + detachDisk: { + label: 'label.action.detach.disk', + messages: { + confirm: function(args) { + return 'message.detach.disk'; + }, + notification: function(args) { + return 'label.action.detach.disk'; + } + }, + action: function(args) { + $.ajax({ + url: createURL("detachVolume&id=" + args.context.volumes[0].id), + dataType: "json", + async: true, + success: function(json) { + var jid = json.detachvolumeresponse.jobid; + args.response.success({ + _custom: { + jobId: jid, + getUpdatedItem: function(json) { + return { + virtualmachineid: null, + vmdisplayname: null + }; + }, + getActionFilter: function() { + return volumeActionfilter; + } + } + }); + } + }); + }, + notification: { + poll: pollAsyncJobResult + } + }, + + downloadVolume: { + label: 'label.action.download.volume', + messages: { + confirm: function(args) { + return 'message.download.volume.confirm'; + }, + notification: function(args) { + return 'label.action.download.volume'; + }, + complete: function(args) { + var url = args.url; + var htmlMsg = _l('message.download.volume'); + var htmlMsg2 = htmlMsg.replace(/#/, url).replace(/00000/, url); + //$infoContainer.find("#info").html(htmlMsg2); + return htmlMsg2; + } + }, + action: function(args) { + $.ajax({ + url: createURL("extractVolume&id=" + args.context.volumes[0].id + "&zoneid=" + args.context.volumes[0].zoneid + "&mode=HTTP_DOWNLOAD"), + dataType: "json", + async: true, + success: function(json) { + var jid = json.extractvolumeresponse.jobid; + args.response.success({ + _custom: { + jobId: jid, + getUpdatedItem: function(json) { + return json.queryasyncjobresultresponse.jobresult.volume; + }, + getActionFilter: function() { + return volumeActionfilter; + } + } + }); + } + }); + }, + notification: { + poll: pollAsyncJobResult + } + }, + + createTemplate: cloudStack.createTemplateMethod(false), + migrateToAnotherStorage: { + label: 'label.migrate.volume.to.primary.storage', + messages: { + confirm: function(args) { + return 'message.migrate.volume'; + }, + notification: function(args) { + return 'label.migrate.volume.to.primary.storage'; + } + }, + createForm: $.extend({}, migrateVolumeCreateFormAction, {title: 'label.migrate.volume.to.primary.storage'}), + action: functionMigrateVolume, + notification: { + poll: pollAsyncJobResult + } + }, + + remove: { + label: 'label.action.delete.volume', + messages: { + confirm: function(args) { + return 'message.action.delete.volume'; + }, + notification: function(args) { + return 'label.action.delete.volume'; + } + }, + action: function(args) { + $.ajax({ + url: createURL("deleteVolume&id=" + args.context.volumes[0].id), + dataType: "json", + async: true, + success: function(json) { + args.response.success(); + } + }); + }, + notification: { + poll: function(args) { + args.complete(); + } + } + }, + + destroy: { + label: 'label.action.destroy.volume', + createForm: { + title: 'label.action.destroy.volume', + desc: 'message.action.destroy.volume', + isWarning: true, + preFilter: function(args) { + if (!isAdmin() && ! g_allowUserExpungeRecoverVolume) { + args.$form.find('.form-item[rel=expunge]').hide(); + } + }, + fields: { + expunge: { + label: 'label.expunge', + isBoolean: true, + isChecked: false + } + } + }, + messages: { + confirm: function(args) { + return 'message.action.destroy.volume'; + }, + notification: function(args) { + return 'label.action.destroy.volume'; + } + }, + action: function(args) { + var data = { + id: args.context.volumes[0].id + }; + if (args.data.expunge == 'on') { + $.extend(data, { + expunge: true + }); + } + $.ajax({ + url: createURL("destroyVolume"), + data: data, + dataType: "json", + async: true, + success: function(json) { + var jid = json.destroyvolumeresponse.jobid; + args.response.success({ + _custom: { + jobId: jid, + getUpdatedItem: function(json) { + if ('volume' in json.queryasyncjobresultresponse.jobresult) { //destroy without expunge + var volume = json.queryasyncjobresultresponse.jobresult.volume; + if (volume.state == 'Expunged') { + return { 'toRemove': true }; + } else { + return volume; + } + } else //destroy with expunge + return { 'toRemove': true }; + }, + getActionFilter: function() { + return volumeActionfilter; + } + } + }); + } + }); + }, + notification: { + poll: pollAsyncJobResult + } + }, + + recover: { + label: 'label.action.recover.volume', + messages: { + confirm: function(args) { + return 'message.action.recover.volume'; + }, + notification: function(args) { + return 'label.action.recover.volume'; + } + }, + action: function(args) { + $.ajax({ + url: createURL("recoverVolume&id=" + args.context.volumes[0].id), + dataType: "json", + success: function(json) { + args.response.success(); + } + }); + }, + notification: { + poll: function(args) { + args.complete(); + } + } + }, + + edit: { + label: 'label.edit', + action: function(args) { + var data = { + id: args.context.volumes[0].id, + }; + if (args.data.name != args.context.volumes[0].name) { + $.extend(data, { + name: args.data.name + }); + } + $.ajax({ + url: createURL('updateVolume'), + data: data, + success: function(json) { + var item = json.updatevolumeresponse.volume; + args.response.success({ + data: item + }); + } + }); + } + }, + + resize: { + label: 'label.action.resize.volume', + messages: { + notification: function(args) { + return 'label.action.resize.volume'; + } + }, + createForm: { + title: 'label.action.resize.volume', + preFilter: function(args) { + var vol; + if (args.context.volumes != null) vol = args.context.volumes[0]; + if (vol.type == "ROOT" && (vol.hypervisor == "XenServer" || vol.hypervisor == "KVM" || vol.hypervisor == "VMware")) { + args.$form.find('.form-item[rel=newdiskoffering]').hide(); + args.$form.find('.form-item[rel=newsize]').css('display', 'inline-block'); + } else { + args.$form.find('.form-item[rel=newdiskoffering]').css('display', 'inline-block'); + args.$form.find('.form-item[rel=newsize]').hide(); + } + }, + fields: { + newdiskoffering: { + label: 'label.resize.new.offering.id', + isHidden: true, + select: function(args) { + if (args.context.volumes != null && args.context.volumes[0].type == 'ROOT') { + args.response.success({ + data: [] + }); + return; + } + var diskOfferings = cloudStack.listDiskOfferings({}); + var items = []; + $(diskOfferings).each(function() { + items.push({ + id: this.id, + description: this.displaytext + }); + }); + args.response.success({ + data: items + }); + args.$select.change(function() { + if(args.context.volumes[0].type == "ROOT") { + selectedDiskOfferingObj = null; + return; + } + + var diskOfferingId = $(this).val(); + $(diskOfferings).each(function() { + if (this.id == diskOfferingId) { + selectedDiskOfferingObj = this; + return false; //break the $.each() loop + } + }); + if (selectedDiskOfferingObj == null){ + return; + } + var $form = $(this).closest('form'); + + var $shrinkok = $form.find('.form-item[rel=shrinkok]'); + //unit of args.context.volumes[0].size is "byte" + //unit of selectedDiskOfferingObj.disksize is "gigabyte" ("GB"), so transfer it into "byte" by multiply (1024 * 1024 * 1024) + if (args.context.volumes[0].size > selectedDiskOfferingObj.disksize * (1024 * 1024 * 1024)) { //if original disk size > new disk size + $shrinkok.css('display', 'inline-block'); + } else { + $shrinkok.hide(); + } + + var $newsize = $form.find('.form-item[rel=newsize]'); + if (selectedDiskOfferingObj.iscustomized == true) { + $newsize.css('display', 'inline-block'); + } else { + $newsize.hide(); + } + + var $minIops = $form.find('.form-item[rel=minIops]'); + var $maxIops = $form.find('.form-item[rel=maxIops]'); + if (selectedDiskOfferingObj.iscustomizediops == true) { + $minIops.css('display', 'inline-block'); + $maxIops.css('display', 'inline-block'); + } else { + $minIops.hide(); + $maxIops.hide(); + } + }); + } + }, + newsize: { + label: 'label.resize.new.size', + validation: { + required: true, + number: true + } + }, + shrinkok: { + label: 'label.resize.shrink.ok', + isBoolean: true, + isChecked: false, + isHidden: true + }, + minIops: { + label: 'label.disk.iops.min', + validation: { + required: false, + number: true + }, + isHidden: true + }, + maxIops: { + label: 'label.disk.iops.max', + validation: { + required: false, + number: true + }, + isHidden: true + } + } + }, + action: function(args) { + var array1 = []; + if(args.$form.find('.form-item[rel=shrinkok]').css("display") != "none") { + array1.push("&shrinkok=" + (args.data.shrinkok == "on")); + } + + var newDiskOffering = args.data.newdiskoffering; + if (newDiskOffering != null && newDiskOffering.length > 0) { + array1.push("&diskofferingid=" + encodeURIComponent(newDiskOffering)); + } + if (args.context.volumes[0].type == "ROOT" || selectedDiskOfferingObj.iscustomized == true) { + cloudStack.addNewSizeToCommandUrlParameterArrayIfItIsNotNullAndHigherThanZero(array1, args.data.newsize); + } + + var minIops; + var maxIops + if (selectedDiskOfferingObj != null && selectedDiskOfferingObj.iscustomizediops == true) { + minIops = args.data.minIops; + maxIops = args.data.maxIops; + } + + if (minIops != null && minIops.length > 0) { + array1.push("&miniops=" + encodeURIComponent(minIops)); + } + + if (maxIops != null && maxIops.length > 0) { + array1.push("&maxiops=" + encodeURIComponent(maxIops)); + } + //if original disk size > new disk size + if (args.context.volumes[0].type == "ROOT" && + args.context.volumes[0].size > (args.data.newsize * (1024 * 1024 * 1024))) { + return args.response.error('message.volume.root.shrink.disk.size'); + } + + $.ajax({ + url: createURL("resizeVolume&id=" + args.context.volumes[0].id + array1.join("")), + dataType: "json", + async: true, + success: function(json) { + var jid = json.resizevolumeresponse.jobid; + args.response.success({ + _custom: { + jobId: jid, + getUpdatedItem: function(json) { + return json.queryasyncjobresultresponse.jobresult.volume; + }, + getActionFilter: function() { + return volumeActionfilter; + } + } + }); + } + }); + }, + notification: { + poll: pollAsyncJobResult + } + } + }, + tabs: { + details: { + title: 'label.details', + + preFilter: function(args) { + var hiddenFields; + if (isAdmin()) { + hiddenFields = []; + } else { + hiddenFields = ['storage', 'hypervisor', 'virtualsize', 'physicalsize', 'utilization', 'clusterid', 'clustername']; + } + return hiddenFields; + }, + + fields: [{ + name: { + label: 'label.name', + isEditable: true + } + }, { + id: { + label: 'label.id' + }, + zonename: { + label: 'label.zone' + }, + state: { + label: 'label.state', + pollAgainIfValueIsIn: { + 'UploadNotStarted': 1 + }, + pollAgainFn: function(context) { + var toClearInterval = false; + $.ajax({ + url: createURL("listVolumes&id=" + context.volumes[0].id), + dataType: "json", + async: false, + success: function(json) { + var jsonObj = json.listvolumesresponse.volume[0]; + if (jsonObj.state != context.volumes[0].state) { + toClearInterval = true; //to clear interval + } + } + }); + return toClearInterval; + } + }, + status: { + label: 'label.status' + }, + diskofferingdisplaytext: { + label: 'label.disk.offering' + }, + type: { + label: 'label.type' + }, + storagetype: { + label: 'label.storage.type' + }, + provisioningtype: { + label: 'label.disk.provisioningtype' + }, + hypervisor: { + label: 'label.hypervisor' + }, + size: { + label: 'label.size', + converter: function(args) { + if (args == null || args == 0) + return ""; + else + return cloudStack.converters.convertBytes(args); + } + }, + clusterid: { + label: 'label.cluster' + }, + clustername: { + label: 'label.cluster.name' + }, + physicalsize: { + label: 'label.disk.physicalsize', + converter: function(args) { + if (args == null || args == 0) + return ""; + else + return cloudStack.converters.convertBytes(args); + } + }, + utilization: { + label: 'label.disk.utilisation' + }, + virtualsize: { + label: 'label.disk.virtualsize', + converter: function(args) { + if (args == null || args == 0) + return ""; + else + return cloudStack.converters.convertBytes(args); + } + }, + miniops: { + label: 'label.disk.iops.min', + converter: function(args) { + if (args == null || args == 0) + return ""; + else + return args; + } + }, + maxiops: { + label: 'label.disk.iops.max', + converter: function(args) { + if (args == null || args == 0) + return ""; + else + return args; + } + }, + virtualmachineid: { + label: 'label.vm.id', + converter: function(args) { + if (args == null) + return _l('state.detached'); + else + return args; + } + }, + //vmname: { label: 'label.vm.name' }, + vmdisplayname: { + label: 'label.vm.display.name' + }, + vmstate: { + label: 'label.vm.state' + }, + deviceid: { + label: 'label.device.id' + }, + storage: { + label: 'label.storage' + }, + created: { + label: 'label.created', + converter: cloudStack.converters.toLocalDate + }, + domain: { + label: 'label.domain' + }, + account: { + label: 'label.account' + } + }], + + tags: cloudStack.api.tags({ + resourceType: 'Volume', + contextId: 'volumes' + }), + + + dataProvider: function(args) { + $.ajax({ + url: createURL("listVolumes&id=" + args.context.volumes[0].id), + dataType: "json", + async: true, + success: function(json) { + var jsonObj = json.listvolumesresponse.volume[0]; + + $(window).trigger('cloudStack.module.sharedFunctions.addExtraProperties', { + obj: jsonObj, + objType: "Volume" + }); + + args.response.success({ + actionFilter: volumeActionfilter, + data: jsonObj + }); + } + }); + } + } + } + } + } + }, + + /** + * Snapshots + */ + snapshots: { + type: 'select', + title: 'label.snapshots', + listView: { + id: 'snapshots', + label: 'label.snapshots', + fields: { + volumename: { + label: 'label.volume' + }, + name: { + label: 'label.name' + }, + intervaltype: { + label: 'label.interval.type' + }, + created: { + label: 'label.created', + converter: cloudStack.converters.toLocalDate + }, + state: { + label: 'label.state', + indicator: { + 'BackedUp': 'on', + 'Destroyed': 'off' + } + } + }, + + advSearchFields: { + name: { + label: 'label.name' + }, + + domainid: { + label: 'label.domain', + select: function(args) { + if (isAdmin() || isDomainAdmin()) { + $.ajax({ + url: createURL('listDomains'), + data: { + listAll: true, + details: 'min' + }, + success: function(json) { + var array1 = [{ + id: '', + description: '' + }]; + var domains = json.listdomainsresponse.domain; + if (domains != null && domains.length > 0) { + for (var i = 0; i < domains.length; i++) { + array1.push({ + id: domains[i].id, + description: domains[i].path + }); + } + } + array1.sort(function(a, b) { + return a.description.localeCompare(b.description); + }); + args.response.success({ + data: array1 + }); + } + }); + } else { + args.response.success({ + data: null + }); + } + }, + isHidden: function(args) { + if (isAdmin() || isDomainAdmin()) + return false; + else + return true; + } + }, + + account: { + label: 'label.account', + isHidden: function(args) { + if (isAdmin() || isDomainAdmin()) + return false; + else + return true; + } + }, + tagKey: { + label: 'label.tag.key' + }, + tagValue: { + label: 'label.tag.value' + } + }, + + dataProvider: function(args) { + var data = {}; + var instanceVolumeIds = []; + listViewDataProvider(args, data); + + if (args.context != null) { + if ("volumes" in args.context) { + $.extend(data, { + volumeid: args.context.volumes[0].id + }); + } else if (args.context.instances) { + $.ajax({ + url: createURL('listVolumes'), + data: { + virtualmachineid: args.context.instances[0].id, + listAll: true + }, + async: false, + success: function(json) { + instanceVolumeIds = $.map(json.listvolumesresponse.volume, function(volume) { + return volume.id; + }) + } + }); + data.volumeid = instanceVolumeIds.join(','); + } + } + + $.ajax({ + url: createURL('listSnapshots'), + data: data, + success: function(json) { + var items = json.listsnapshotsresponse.snapshot; + args.response.success({ + actionFilter: snapshotActionfilter, + data: items + }); + }, + error: function(XMLHttpResponse) { + cloudStack.dialog.notice({ + message: parseXMLHttpResponse(XMLHttpResponse) + }); + args.response.error(); + } + }); + }, + + detailView: { + name: 'Snapshot detail', + actions: { + createTemplate: cloudStack.createTemplateFromSnapshotMethod(), + + createVolume: { + label: 'label.action.create.volume', + messages: { + confirm: function(args) { + return 'message.confirm.create.volume'; + }, + notification: function(args) { + return 'label.action.create.volume'; + } + }, + createForm: { + title: 'label.action.create.volume', + desc: '', + preFilter: function(args) { + if (g_regionsecondaryenabled == true) { + args.$form.find('.form-item[rel=zoneid]').css('display', 'inline-block'); + } else { + args.$form.find('.form-item[rel=zoneid]').hide(); + } + if(args.context.snapshots[0].volumetype!='ROOT') { + args.$form.find('.form-item[rel=diskOffering]').hide(); + } + }, + fields: { + name: { + label: 'label.name', + validation: { + required: true + } + }, + zoneid: { + label: 'label.availability.zone', + isHidden: true, + select: function(args) { + $.ajax({ + url: createURL("listZones&available=true"), + dataType: "json", + async: true, + success: function(json) { + var zoneObjs = json.listzonesresponse.zone; + var items = [{ + id: '', + description: '' + }]; + if (zoneObjs != null) { + for (i = 0; i < zoneObjs.length; i++) { + items.push({ + id: zoneObjs[i].id, + description: zoneObjs[i].name + }); + } + } + args.response.success({ + data: items + }); + } + }); + } + }, + diskOffering: { + label: 'label.disk.offering', + docID: 'helpVolumeDiskOffering', + select: function(args) { + var snapshotSizeInGB = Math.floor(args.context.snapshots[0].virtualsize/(1024 * 1024 * 1024)) + $.ajax({ + url: createURL("listDiskOfferings"), + dataType: "json", + async: false, + success: function(json) { + diskofferingObjs = json.listdiskofferingsresponse.diskoffering; + var items = []; + // Sort offerings list with size and keep custom offerings at end + for(var i=0;idiskofferingObjs[j].disksize && + diskofferingObjs[j].disksize!=0) || + (diskofferingObjs[i].disksize==0 && + diskofferingObjs[j].disksize!=0)) { + var temp = diskofferingObjs[i]; + diskofferingObjs[i] = diskofferingObjs[j]; + diskofferingObjs[j] = temp; + } + } + } + $(diskofferingObjs).each(function() { + if(this.disksize==0 || this.disksize>=snapshotSizeInGB) { + items.push({ + id: this.id, + description: this.displaytext + }); + } + }); + args.response.success({ + data: items + }); + } + }); + + args.$select.change(function() { + var diskOfferingId = $(this).val(); + selectedDiskOfferingObj = null; + $(diskofferingObjs).each(function() { + if (this.id == diskOfferingId) { + selectedDiskOfferingObj = this; + return false; + } + }); + + if (selectedDiskOfferingObj == null) return; + + var $form = $(this).closest('form'); + var $diskSize = $form.find('.form-item[rel=diskSize]'); + if (selectedDiskOfferingObj.iscustomized == true) { + $diskSize.css('display', 'inline-block'); + $form.find('input[name=diskSize]').val(''+snapshotSizeInGB); + } else { + $diskSize.hide(); + } + + var $minIops = $form.find('.form-item[rel=minIops]'); + var $maxIops = $form.find('.form-item[rel=maxIops]'); + if (selectedDiskOfferingObj.iscustomizediops == true) { + $minIops.css('display', 'inline-block'); + $maxIops.css('display', 'inline-block'); + } else { + $minIops.hide(); + $maxIops.hide(); + } + }); + } + }, + diskSize: { + label: 'label.disk.size.gb', + docID: 'helpVolumeSizeGb', + validation: { + required: true, + number: true + }, + isHidden: true + }, + minIops: { + label: 'label.disk.iops.min', + validation: { + required: false, + number: true + }, + isHidden: true + }, + maxIops: { + label: 'label.disk.iops.max', + validation: { + required: false, + number: true + }, + isHidden: true + } + } + }, + action: function(args) { + var data = { + name: args.data.name, + snapshotid: args.context.snapshots[0].id + }; + + if (args.$form.find('.form-item[rel=zoneid]').css("display") != "none" && args.data.zoneid != '') { + $.extend(data, { + zoneId: args.data.zoneid + }); + } + + if (args.$form.find('.form-item[rel=diskOffering]').css("display") != "none") { + if (args.data.diskOffering) { + $.extend(data, { + diskofferingid: args.data.diskOffering + }); + } + if (selectedDiskOfferingObj) { + if(selectedDiskOfferingObj.iscustomized == true) { + $.extend(data, { + size: args.data.diskSize + }); + } + + if (selectedDiskOfferingObj.iscustomizediops == true) { + if (args.data.minIops != "" && args.data.minIops > 0) { + $.extend(data, { + miniops: args.data.minIops + }); + } + + if (args.data.maxIops != "" && args.data.maxIops > 0) { + $.extend(data, { + maxiops: args.data.maxIops + }); + } + } + } + } + + $.ajax({ + url: createURL('createVolume'), + data: data, + success: function(json) { + var jid = json.createvolumeresponse.jobid; + args.response.success({ + _custom: { + jobId: jid, + getUpdatedItem: function(json) { + return {}; //nothing in this snapshot needs to be updated + }, + getActionFilter: function() { + return snapshotActionfilter; + } + } + }); + }, + error: function(json) { + args.response.error(parseXMLHttpResponse(json)); + } + }); + }, + notification: { + poll: pollAsyncJobResult + } + }, + revertSnapshot: { + label: 'label.action.revert.snapshot', + messages: { + confirm: function(args) { + return 'message.action.revert.snapshot'; + }, + notification: function(args) { + return 'label.action.revert.snapshot'; + } + }, + action: function(args) { + $.ajax({ + url: createURL("revertSnapshot&id="+args.context.snapshots[0].id), + dataType: "json", + async: true, + success: function(json) { + var jid = json.revertsnapshotresponse.jobid; + args.response.success({ + _custom: { + jobId: jid + } + }); + } + }); + }, + notification: { + poll: pollAsyncJobResult + } + }, + + remove: { + label: 'label.action.delete.snapshot', + messages: { + confirm: function(args) { + return 'message.action.delete.snapshot'; + }, + notification: function(args) { + return 'label.action.delete.snapshot'; + } + }, + action: function(args) { + $.ajax({ + url: createURL("deleteSnapshot&id=" + args.context.snapshots[0].id), + dataType: "json", + async: true, + success: function(json) { + var jid = json.deletesnapshotresponse.jobid; + args.response.success({ + _custom: { + jobId: jid + } + }); + } + }); + }, + notification: { + poll: pollAsyncJobResult + } + } + }, + tabs: { + details: { + title: 'label.details', + fields: [{ + name: { + label: 'label.name' + } + }, { + id: { + label: 'label.id' + }, + volumename: { + label: 'label.volume.name' + }, + state: { + label: 'label.state' + }, + intervaltype: { + label: 'label.interval.type' + }, + domain: { + label: 'label.domain' + }, + account: { + label: 'label.account' + }, + created: { + label: 'label.created', + converter: cloudStack.converters.toLocalDate + } + }], + + tags: cloudStack.api.tags({ + resourceType: 'Snapshot', + contextId: 'snapshots' + }), + + dataProvider: function(args) { + $.ajax({ + url: createURL("listSnapshots&id=" + args.context.snapshots[0].id), + dataType: "json", + async: true, + success: function(json) { + var jsonObj = json.listsnapshotsresponse.snapshot[0]; + args.response.success({ + actionFilter: snapshotActionfilter, + data: jsonObj + }); + } + }); + } + } + } + } + }, + }, + + /** + * VM Snapshots + */ + vmsnapshots: { + type: 'select', + title: 'label.vmsnapshot', + listView: { + id: 'vmsnapshots', + isMaximized: true, + fields: { + displayname: { + label: 'label.name' + }, + state: { + label: 'label.state', + indicator: { + 'Ready': 'on', + 'Error': 'off' + } + }, + type: { + label: 'label.vmsnapshot.type' + }, + current: { + label: 'label.vmsnapshot.current', + converter: cloudStack.converters.toBooleanText + }, + parentName: { + label: 'label.vmsnapshot.parentname' + }, + created: { + label: 'label.date', + converter: cloudStack.converters.toLocalDate + } + }, + + advSearchFields: { + name: { + label: 'label.name' + }, + + domainid: { + label: 'label.domain', + select: function(args) { + if (isAdmin() || isDomainAdmin()) { + $.ajax({ + url: createURL('listDomains'), + data: { + listAll: true, + details: 'min' + }, + success: function(json) { + var array1 = [{ + id: '', + description: '' + }]; + var domains = json.listdomainsresponse.domain; + if (domains != null && domains.length > 0) { + for (var i = 0; i < domains.length; i++) { + array1.push({ + id: domains[i].id, + description: domains[i].path + }); + } + } + array1.sort(function(a, b) { + return a.description.localeCompare(b.description); + }); + args.response.success({ + data: array1 + }); + } + }); + } else { + args.response.success({ + data: null + }); + } + }, + isHidden: function(args) { + if (isAdmin() || isDomainAdmin()) + return false; + else + return true; + } + }, + + account: { + label: 'label.account', + isHidden: function(args) { + if (isAdmin() || isDomainAdmin()) + return false; + else + return true; + } + }, + tagKey: { + label: 'label.tag.key' + }, + tagValue: { + label: 'label.tag.value' + } + }, + + dataProvider: function(args) { + var data = { + listAll: true + }; + listViewDataProvider(args, data); + + if (args.context != null) { + if ("instances" in args.context) { + $.extend(data, { + virtualMachineId: args.context.instances[0].id + }); + } + } + $.ajax({ + url: createURL('listVMSnapshot'), + data: data, + dataType: "json", + async: true, + success: function(json) { + var jsonObj; + jsonObj = json.listvmsnapshotresponse.vmSnapshot; + args.response.success({ + actionFilter: vmSnapshotActionfilter, + data: jsonObj + }); + } + }); + }, + //dataProvider end + detailView: { + tabs: { + details: { + title: 'label.details', + fields: { + id: { + label: 'label.id' + }, + name: { + label: 'label.name' + }, + displayname: { + label: 'label.display.name' + }, + type: { + label: 'label.vmsnapshot.type' + }, + description: { + label: 'label.description' + }, + state: { + label: 'label.state', + indicator: { + 'Ready': 'on', + 'Error': 'off' + } + }, + current: { + label: 'label.vmsnapshot.current', + converter: cloudStack.converters.toBooleanText + }, + parentName: { + label: 'label.vmsnapshot.parentname' + }, + domain: { + label: 'label.domain' + }, + account: { + label: 'label.account' + }, + virtualmachineid: { + label: 'label.vm.id' + }, + created: { + label: 'label.date', + converter: cloudStack.converters.toLocalDate + } + }, + dataProvider: function(args) { + $.ajax({ + url: createURL("listVMSnapshot&listAll=true&vmsnapshotid=" + args.context.vmsnapshots[0].id), + dataType: "json", + async: true, + success: function(json) { + var jsonObj; + jsonObj = json.listvmsnapshotresponse.vmSnapshot[0]; + args.response.success({ + actionFilter: vmSnapshotActionfilter, + data: jsonObj + }); + } + }); + }, + tags: cloudStack.api.tags({ + resourceType: 'VMSnapshot', + contextId: 'vmsnapshots' + }) + } + }, + actions: { + //delete a snapshot + remove: { + label: 'label.action.vmsnapshot.delete', + messages: { + confirm: function(args) { + return 'message.action.vmsnapshot.delete'; + }, + notification: function(args) { + return 'label.action.vmsnapshot.delete'; + } + }, + action: function(args) { + $.ajax({ + url: createURL("deleteVMSnapshot&vmsnapshotid=" + args.context.vmsnapshots[0].id), + dataType: "json", + async: true, + success: function(json) { + var jid = json.deletevmsnapshotresponse.jobid; + args.response.success({ + _custom: { + jobId: jid + } + }); + } + }); + }, + notification: { + poll: pollAsyncJobResult + } + }, + revertToVMSnapshot: { + label: 'label.action.vmsnapshot.revert', + messages: { + confirm: function(args) { + return 'label.action.vmsnapshot.revert'; + }, + notification: function(args) { + return 'message.action.vmsnapshot.revert'; + } + }, + action: function(args) { + $.ajax({ + url: createURL("revertToVMSnapshot&vmsnapshotid=" + args.context.vmsnapshots[0].id), + dataType: "json", + async: true, + success: function(json) { + var jid = json.reverttovmsnapshotresponse.jobid; + args.response.success({ + _custom: { + jobId: jid + } + }); + } + }); + + }, + notification: { + poll: pollAsyncJobResult + } + }, + takeSnapshot: { + label: 'Create Snapshot From VM Snapshot', + messages: { + confirm: function(args) { + return 'Please confirm that you want to create a volume snapshot from the vm snapshot.'; + }, + notification: function(args) { + return 'Volume snapshot is created from vm snapshot'; + } + }, + createForm: { + title: 'label.action.take.snapshot', + desc: 'message.action.take.snapshot', + fields: { + name: { + label: 'label.name', + }, + volume: { + label: 'label.volume', + validation: { + required: true + }, + select: function(args) { + $.ajax({ + url: createURL("listVolumes&virtualMachineId=" + args.context.vmsnapshots[0].virtualmachineid), + dataType: "json", + async: true, + success: function(json) { + var volumes = json.listvolumesresponse.volume; + var items = []; + $(volumes).each(function() { + items.push({ + id: this.id, + description: this.name + }); + }); + args.response.success({ + data: items + }); + + } + }); + } + } + } + }, + action: function(args) { + var data = { + volumeid: args.data.volume, + vmsnapshotid: args.context.vmsnapshots[0].id + }; + if (args.data.name != null && args.data.name.length > 0) { + $.extend(data, { + name: args.data.name + }); + } + $.ajax({ + url: createURL("createSnapshotFromVMSnapshot"), + data: data, + dataType: "json", + async: true, + success: function(json) { + var jid = json.createsnapshotfromvmsnapshotresponse.jobid; + args.response.success({ + _custom: { + jobId: jid + } + }); + } + }); + + }, + notification: { + poll: pollAsyncJobResult + } + } + } + } + //detailview end + } + }, + + /** + * Backups + */ + backups: { + type: 'select', + title: 'label.backup', + listView: { + id: 'backups', + isMaximized: true, + fields: { + virtualmachinename: { + label: 'label.vm.name' + }, + status: { + label: 'label.state', + indicator: { + 'BackedUp': 'on', + 'Failed': 'off', + 'Error': 'off' + } + }, + type: { + label: 'label.type' + }, + created: { + label: 'label.date' + }, + account: { + label: 'label.account' + }, + zone: { + label: 'label.zone' + } + }, + + dataProvider: function(args) { + var data = { + listAll: true + }; + listViewDataProvider(args, data); + + if (args.context != null) { + if ("instances" in args.context) { + $.extend(data, { + virtualmachineid: args.context.instances[0].id + }); + } + } + + $.ajax({ + url: createURL('listBackups'), + data: data, + dataType: "json", + async: true, + success: function(json) { + var jsonObj; + jsonObj = json.listbackupsresponse.backup; + args.response.success({ + actionFilter: backupActionfilter, + data: jsonObj + }); + } + }); + }, + //dataProvider end + detailView: { + tabs: { + details: { + title: 'label.details', + fields: { + id: { + label: 'label.id' + }, + virtualmachinename: { + label: 'label.vm.name' + }, + virtualmachineid: { + label: 'label.vm.id' + }, + status: { + label: 'label.state' + }, + externalid: { + label: 'label.external.id' + }, + type: { + label: 'label.type' + }, + size: { + label: 'label.size' + }, + virtualsize: { + label: 'label.virtual.size' + }, + volumes: { + label: 'label.volumes' + }, + account: { + label: 'label.account' + }, + domain: { + label: 'label.domain' + }, + zone: { + label: 'label.zone' + }, + created: { + label: 'label.date' + } + }, + dataProvider: function(args) { + $.ajax({ + url: createURL("listBackups&id=" + args.context.backups[0].id), + dataType: "json", + async: true, + success: function(json) { + var jsonObj; + jsonObj = json.listbackupsresponse.backup[0]; + args.response.success({ + actionFilter: backupActionfilter, + data: jsonObj + }); + } + }); + } + } + }, + actions: { + remove: { + label: 'Delete Backup', + messages: { + confirm: function(args) { + return 'Are you sure you want to delete the backup?'; + }, + notification: function(args) { + return 'Delete Backup'; + } + }, + action: function(args) { + $.ajax({ + url: createURL("deleteBackup&id=" + args.context.backups[0].id), + dataType: "json", + async: true, + success: function(json) { + var jid = json.deletebackupresponse.jobid; + args.response.success({ + _custom: { + jobId: jid + } + }); + } + }); + }, + notification: { + poll: pollAsyncJobResult + } + }, + + restoreBackup: { + label: 'label.backup.restore', + messages: { + confirm: function(args) { + return 'Please confirm that you want to restore the vm backup?'; + }, + notification: function(args) { + return 'label.backup.restore'; + } + }, + action: function(args) { + var data = { + id: args.context.backups[0].id + }; + $.ajax({ + url: createURL("restoreBackup"), + data: data, + dataType: "json", + async: true, + success: function(json) { + var jid = json.restorebackupresponse.jobid; + args.response.success({ + _custom: { + jobId: jid + } + }); + } + }); + + }, + notification: { + poll: pollAsyncJobResult + } + }, + + restoreBackupVolume: { + label: 'Restore and Attach Backup Volume', + messages: { + confirm: function(args) { + return 'Please confirm that you want to restore and attach the volume from the backup?'; + }, + notification: function(args) { + return 'Restore and Attach Backup Volume'; + } + }, + createForm: { + title: 'Restore and Attach Backup Volume', + desc: 'Please select the volume you want to restore and attach to the VM.', + fields: { + volume: { + label: 'label.volume', + validation: { + required: true + }, + select: function(args) { + var volumes = JSON.parse(args.context.backups[0].volumes); + var items = []; + $(volumes).each(function() { + items.push({ + id: this.uuid, + description: '(' + this.type + ') ' + this.uuid + }); + }); + args.response.success({ + data: items + }); + } + }, + virtualmachine: { + label: 'label.virtual.machine', + validation: { + required: true + }, + select: function(args) { + $.ajax({ + url: createURL("listVirtualMachines"), + dataType: "json", + async: true, + success: function(json) { + var vms = json.listvirtualmachinesresponse.virtualmachine; + var items = []; + $(vms).each(function() { + items.push({ + id: this.id, + description: this.name + }); + }); + args.response.success({ + data: items + }); + + } + }); + } + } + } + }, + action: function(args) { + console.log(args); + var data = { + backupid: args.context.backups[0].id, + volumeid: args.data.volume, + virtualmachineid: args.data.virtualmachine + }; + $.ajax({ + url: createURL("restoreVolumeFromBackupAndAttachToVM"), + data: data, + dataType: "json", + async: true, + success: function(json) { + var jid = json.restorevolumefrombackupandattachtovmresponse.jobid; + args.response.success({ + _custom: { + jobId: jid + } + }); + } + }); + + }, + notification: { + poll: pollAsyncJobResult + } + }, + + removeBackupChain: { + label: 'Delete Backup Chain', + messages: { + confirm: function(args) { + return 'Are you sure you want to remove VM from backup offering and delete the backup chain?'; + }, + notification: function(args) { + return 'Delete Backup Chain'; + } + }, + action: function(args) { + $.ajax({ + url: createURL("removeVirtualMachineFromBackupOffering"), + data: { + virtualmachineid: args.context.backups[0].virtualmachineid, + forced: true + }, + dataType: "json", + async: true, + success: function(json) { + var jid = json.removevirtualmachinefrombackupofferingresponse.jobid; + args.response.success({ + _custom: { + jobId: jid + } + }); + } + }); + }, + notification: { + poll: pollAsyncJobResult + } + } + } + } + //detailview end + } + } + } + }; + + + var volumeActionfilter = cloudStack.actionFilter.volumeActionfilter = function(args) { + var jsonObj = args.context.item; + var allowedActions = []; + + if ((isAdmin() || g_allowUserExpungeRecoverVolume) && jsonObj.state == 'Destroy') { + return ["recover", "remove"]; + } else if (jsonObj.state == 'Destroy') { + return []; + } + + if (jsonObj.state == 'Expunging' || jsonObj.state == 'Expunged') { + return ["remove"]; + } + + if (jsonObj.state == 'Destroyed' || jsonObj.state == 'Migrating' || jsonObj.state == 'Uploading') { + return []; + } + if (jsonObj.state == 'UploadError') { + return ["remove"]; + } + + if (jsonObj.hypervisor != "Ovm" && jsonObj.state == "Ready") { + if (jsonObj.hypervisor == 'KVM') { + if (jsonObj.vmstate == 'Running') { + if (g_kvmsnapshotenabled == true) { //"kvm.snapshot.enabled" flag should be taken to account only when snapshot is being created for Running vm (CLOUDSTACK-4428) + allowedActions.push("takeSnapshot"); + allowedActions.push("recurringSnapshot"); + } + } else { + allowedActions.push("takeSnapshot"); + allowedActions.push("recurringSnapshot"); + } + } else { + allowedActions.push("takeSnapshot"); + allowedActions.push("recurringSnapshot"); + } + } + + if ((jsonObj.type == "DATADISK" || jsonObj.type == "ROOT") && (jsonObj.state == "Ready" || jsonObj.state == "Allocated")) { + allowedActions.push("resize"); + } + + if (jsonObj.state != "Allocated") { + if ((jsonObj.vmstate == "Stopped" || jsonObj.virtualmachineid == null) && jsonObj.state == "Ready") { + allowedActions.push("downloadVolume"); + } + } + + + + if (jsonObj.type == "ROOT" || jsonObj.type == "DATADISK") { + if (jsonObj.state == "Ready" && isAdmin() && jsonObj.virtualmachineid != null) { + allowedActions.push("migrateVolume"); + } + } + + if (jsonObj.state != "Creating") { + if (jsonObj.type == "ROOT") { + if (jsonObj.vmstate == "Stopped") { + allowedActions.push("createTemplate"); + } + } else { //jsonObj.type == "DATADISK" + if (jsonObj.virtualmachineid != null) { + if (jsonObj.vmstate == "Running" || jsonObj.vmstate == "Stopped" || jsonObj.vmstate == "Destroyed") { + allowedActions.push("detachDisk"); + } + } else { // Disk not attached + if (jsonObj.state == "Allocated" || jsonObj.state == "Uploaded") { + allowedActions.push("remove"); + } else { + allowedActions.push("createTemplate"); + allowedActions.push("destroy"); + } + if (jsonObj.state == "Ready" && isAdmin()) { + allowedActions.push("migrateToAnotherStorage"); + } + allowedActions.push("attachDisk"); + } + } + } + + allowedActions.push("edit"); + return allowedActions; + }; + + var snapshotActionfilter = cloudStack.actionFilter.snapshotActionfilter = function(args) { + var jsonObj = args.context.item; + + if (jsonObj.state == 'Destroyed') { + return []; + } + + var allowedActions = []; + if (jsonObj.state == "BackedUp") { + allowedActions.push("createTemplate"); + allowedActions.push("createVolume"); + + if (jsonObj.revertable) { + allowedActions.push("revertSnapshot"); + } + } + allowedActions.push("remove"); + + return allowedActions; + }; + + var vmSnapshotActionfilter = cloudStack.actionFilter.vmSnapshotActionfilter = function(args) { + var jsonObj = args.context.item; + + if (jsonObj.state == 'Error') { + return ["remove"]; + } + + var allowedActions = []; + if (jsonObj.state == "Ready") { + allowedActions.push("remove"); + allowedActions.push("revertToVMSnapshot"); + + if (args && args.context && args.context.instances && args.context.instances[0].hypervisor && args.context.instances[0].hypervisor === "KVM") { + allowedActions.push("takeSnapshot"); + } + } + + return allowedActions; + } + + var backupActionfilter = cloudStack.actionFilter.backupActionfilter = function(args) { + var jsonObj = args.context.item; + + if (jsonObj.state == 'Destroyed') { + return []; + } + + var allowedActions = []; + allowedActions.push("remove"); + allowedActions.push("restoreBackup"); + allowedActions.push("restoreBackupVolume"); + allowedActions.push("removeBackupChain"); + + return allowedActions; + }; + + + +})(cloudStack); diff --git a/ui/src/config/section/project.js b/ui/src/config/section/project.js index 72cdd713ad2c..73ac80fae144 100644 --- a/ui/src/config/section/project.js +++ b/ui/src/config/section/project.js @@ -139,6 +139,13 @@ export default { dataView: true, show: (record, store) => { return (['Admin', 'DomainAdmin'].includes(store.userInfo.roletype)) || record.isCurrentUserProjectAdmin + }, + args: (record, store) => { + const fields = [] + if (store.apis.deleteProject.params.filter(x => x.name === 'cleanup').length > 0) { + fields.push('cleanup') + } + return fields } } ]