Addition of the status filter in the backup listing API (#13254)

Co-authored-by: gean.silva <gean.silva@scclouds.com.br>
Co-authored-by: Gean Jair Silva <89494158+GeanJS@users.noreply.github.com>
Co-authored-by: Bernardo De Marco Gonçalves <bernardomg2004@gmail.com>
This commit is contained in:
Gean Jair Silva 2026-07-03 11:58:39 -03:00 committed by GitHub
parent 7e84850a03
commit 2032bbd7f6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 97 additions and 3 deletions

View File

@ -89,6 +89,12 @@ public class ListBackupsCmd extends BaseListProjectAndAccountResourcesCmd {
description = "list backups by backup offering")
private Long backupOfferingId;
@Parameter(name = ApiConstants.STATUS,
type = CommandType.STRING,
since = "4.23.0",
description = "list backups by status")
private String backupStatus;
@Parameter(name = ApiConstants.LIST_VM_DETAILS,
type = CommandType.BOOLEAN,
since = "4.21.0",
@ -119,6 +125,10 @@ public class ListBackupsCmd extends BaseListProjectAndAccountResourcesCmd {
return zoneId;
}
public String getBackupStatus() {
return backupStatus;
}
public Boolean getListVmDetails() {
return listVmDetails;
}

View File

@ -39,6 +39,7 @@ import javax.inject.Inject;
import javax.naming.ConfigurationException;
import com.cloud.utils.DomainHelper;
import com.cloud.utils.EnumUtils;
import org.apache.cloudstack.api.ApiCommandResourceType;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.InternalIdentity;
@ -256,6 +257,8 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager {
private static Map<String, BackupProvider> backupProvidersMap = new HashMap<>();
private List<BackupProvider> backupProviders;
private static final List<Backup.Status> INVALID_BACKUP_STATUS = List.of(Backup.Status.Expunged, Backup.Status.Removed);
public AsyncJobDispatcher getAsyncJobDispatcher() {
return asyncJobDispatcher;
}
@ -1084,6 +1087,20 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager {
}
}
private Backup.Status validateBackupStatus(final String backupStatus) {
if (backupStatus == null) {
return null;
}
Backup.Status status = EnumUtils.getEnumIgnoreCase(Backup.Status.class, backupStatus);
if (status == null || INVALID_BACKUP_STATUS.contains(status)) {
throw new InvalidParameterValueException(String.format("Invalid backup status: %s. Valid values are: " +
"Allocated, Queued, BackingUp, BackedUp, Error, Failed, Restoring.", backupStatus));
}
return status;
}
@Override
public Pair<List<Backup>, Integer> listBackups(final ListBackupsCmd cmd) {
final Long id = cmd.getId();
@ -1091,6 +1108,7 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager {
final String name = cmd.getName();
final Long zoneId = cmd.getZoneId();
final Long backupOfferingId = cmd.getBackupOfferingId();
final Backup.Status backupStatus = validateBackupStatus(cmd.getBackupStatus());
final Account caller = CallContext.current().getCallingAccount();
final String keyword = cmd.getKeyword();
List<Long> permittedAccounts = new ArrayList<Long>();
@ -1119,6 +1137,7 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager {
sb.and("name", sb.entity().getName(), SearchCriteria.Op.EQ);
sb.and("zoneId", sb.entity().getZoneId(), SearchCriteria.Op.EQ);
sb.and("backupOfferingId", sb.entity().getBackupOfferingId(), SearchCriteria.Op.EQ);
sb.and("backupStatus", sb.entity().getStatus(), SearchCriteria.Op.EQ);
if (keyword != null) {
sb.and().op("keywordName", sb.entity().getName(), SearchCriteria.Op.LIKE);
@ -1151,6 +1170,8 @@ public class BackupManagerImpl extends ManagerBase implements BackupManager {
sc.setParameters("backupOfferingId", backupOfferingId);
}
sc.setParametersIfNotNull("backupStatus", backupStatus);
if (keyword != null) {
String keywordMatch = "%" + keyword + "%";
sc.setParameters("keywordName", keywordMatch);

View File

@ -469,6 +469,8 @@
"label.back.login": "Back to login",
"label.backup": "Backup",
"label.backups": "Backups",
"label.backedup": "BackedUp",
"label.backingup": "BackingUp",
"label.backup.attach.restore": "Restore and attach backup volume",
"label.backup.configure.schedule": "Configure Backup Schedule",
"label.backup.offering.assign": "Assign Instance to backup offering",
@ -2052,6 +2054,7 @@
"label.purge.usage.records.error": "Failed while purging usage records",
"label.purpose": "Purpose",
"label.qostype": "QoS type",
"label.queued": "Queued",
"label.quickview": "Quick view",
"label.quiescevm": "Quiesce Instance",
"label.quiettime": "Quiet time (in sec)",
@ -2210,6 +2213,7 @@
"label.restartrequired": "Restart required",
"label.restore": "Restore",
"label.restore.volume.attach": "Restore volume and attach",
"label.restoring": "Restoring",
"label.use.backup.ip.address": "Use IP Addresses from Backup",
"label.use.backup.ip.address.tooltip": "Use the same IP/MAC addresses as stored in the backup metadata. The command will error out if the IP/MAC addresses are not available",
"label.review": "Review",

View File

@ -436,7 +436,8 @@
"label.backup": "Backup",
"label.backups": "Backups",
"label.back.login": "Voltar à página de login",
"label.backup": "Backups",
"label.backedup": "Salvo",
"label.backingup": "Salvando",
"label.backup.attach.restore": "Restaurar e anexar volume de backup",
"label.backuplimit": "Limite de backups",
"label.backup.storage": "Armazenamento de backup",
@ -1828,6 +1829,7 @@
"label.purgeresources": "Limpar Recursos",
"label.purpose": "Prop\u00f3sito",
"label.qostype": "Tipo de QoS",
"label.queued": "Enfileirado",
"label.quickview": "Visualiza\u00e7\u00e3o r\u00e1pida",
"label.quiescevm": "Quiesce VM",
"label.quiettime": "Tempo em espera (em seg)",
@ -1982,6 +1984,7 @@
"label.restartrequired": "Reiniciar obrigat\u00f3rio",
"label.restore": "Restaurar",
"label.restore.volume.attach": "Restaurar volume e anex\u00e1-lo",
"label.restoring": "Restaurando",
"label.review": "Revisar",
"label.role": "Fun\u00e7\u00e3o",
"label.roleid": "Fun\u00e7\u00e3o",

View File

@ -337,7 +337,7 @@ export default {
'type', 'scope', 'managementserverid', 'serviceofferingid',
'diskofferingid', 'networkid', 'usagetype', 'restartrequired', 'gpuenabled',
'displaynetwork', 'guestiptype', 'usersource', 'arch', 'oscategoryid', 'templatetype', 'gpucardid', 'vgpuprofileid',
'extensionid', 'backupoffering', 'volumeid', 'virtualmachineid', 'hsmprofileid', 'kmskeyid'].includes(item)
'extensionid', 'backupoffering', 'volumeid', 'virtualmachineid', 'hsmprofileid', 'kmskeyid', 'status'].includes(item)
) {
type = 'list'
} else if (item === 'tags') {
@ -530,6 +530,7 @@ export default {
let extensionIndex = -1
let hsmProfileIndex = -1
let kmsKeyIndex = -1
let backupStatusIndex = -1
if (arrayField.includes('type')) {
if (this.$route.path === '/alert') {
@ -687,6 +688,11 @@ export default {
promises.push(await this.fetchKMSKeys(searchKeyword))
}
if (arrayField.includes('status')) {
backupStatusIndex = this.fields.findIndex(item => item.name === 'status')
this.fields[backupStatusIndex].opts = this.fetchAvailableBackupStatus()
}
Promise.all(promises).then(response => {
if (typeIndex > -1) {
const types = response.filter(item => item.type === 'type')
@ -1662,6 +1668,38 @@ export default {
})
})
},
fetchAvailableBackupStatus () {
return [
{
id: 'Allocated',
name: 'label.allocated'
},
{
id: 'Queued',
name: 'label.queued'
},
{
id: 'BackingUp',
name: 'label.backingup'
},
{
id: 'BackedUp',
name: 'label.backedup'
},
{
id: 'Error',
name: 'label.error'
},
{
id: 'Failed',
name: 'label.failed'
},
{
id: 'Restoring',
name: 'label.restoring'
}
]
},
onSearch (value) {
this.paramsFilter = {}
this.searchQuery = value

View File

@ -106,6 +106,24 @@ export default {
case 'no':
state = this.$t('label.no')
break
case 'backedup':
state = this.$t('label.backedup')
break
case 'backingup':
state = this.$t('label.backingup')
break
case 'allocated':
state = this.$t('label.allocated')
break
case 'queued':
state = this.$t('label.queued')
break
case 'restoring':
state = this.$t('label.restoring')
break
case 'failed':
state = this.$t('label.failed')
break
}
return state.charAt(0).toUpperCase() + state.slice(1)
}

View File

@ -495,7 +495,7 @@ export default {
columns: ['name', 'status', 'size', 'virtualsize', 'virtualmachinename', 'backupofferingname', 'intervaltype', 'type', 'created', 'account', 'domain', 'zone'],
details: ['name', 'description', 'virtualmachinename', 'id', 'intervaltype', 'type', 'externalid', 'size', 'virtualsize', 'volumes', 'backupofferingname', 'zone', 'account', 'domain', 'created'],
searchFilters: () => {
var filters = ['name', 'zoneid', 'domainid', 'account', 'backupofferingid']
var filters = ['name', 'zoneid', 'domainid', 'account', 'backupofferingid', 'status']
return filters
},
tabs: [