diff --git a/frontend/src/api/alarms.js b/frontend/src/api/alarms.js index be141dd3c..535b7cb34 100644 --- a/frontend/src/api/alarms.js +++ b/frontend/src/api/alarms.js @@ -11,5 +11,8 @@ export default { }, delete (alarmId) { return repository.delete(`${resource}/${alarmId}/`) + }, + bulkDelete (alarmsIds) { + return repository.delete(`${resource}/bulk_delete/`, { params: { ids: alarmsIds } }) } } diff --git a/frontend/src/components/alarms/AlarmList.vue b/frontend/src/components/alarms/AlarmList.vue index dfda1b339..eaabeff12 100644 --- a/frontend/src/components/alarms/AlarmList.vue +++ b/frontend/src/components/alarms/AlarmList.vue @@ -107,9 +107,6 @@ export default { } }, methods: { - sleep (milliseconds) { - return new Promise((resolve) => setTimeout(resolve, milliseconds)) - }, fetchAlarms () { const params = { page: this.options.page @@ -122,7 +119,7 @@ export default { } this.loading = true alarms.getAll(params).then(resp => { - this.alarms = resp.data + this.alarms = resp.data.results this.totalAlarms = resp.data.count this.loading = false }) @@ -148,21 +145,34 @@ export default { }) }, async deleteAlarms () { - for (const alarm of this.selected) { - this.deleteAlarm(alarm, false) - await this.sleep(100) - } + await alarms.bulkDelete(this.selected.map(alarm => alarm.id)) bus.$emit('notification', { msg: this.$gettext('Alarms deleted') }) this.selected = [] this.fetchAlarms() }, getMenuItems (item) { const result = [] - result.push({ label: this.$gettext('Delete'), icon: 'mdi-delete-outline', onClick: this.deleteAlarm, color: 'red' }) - if (item.status === 1) { - result.push({ label: this.$gettext('Close alarm'), icon: 'mdi-check', onClick: this.closeAlarm, color: 'green' }) + result.push({ + label: this.$gettext('Delete'), + icon: 'mdi-delete-outline', + onClick: this.deleteAlarm, + color: 'red' + }) + if (item.status === constants.ALARM_OPENED) { + result.push({ + label: this.$gettext('Close alarm'), + icon: 'mdi-check', + onClick: this.closeAlarm, + color: 'green' + }) } else { - result.push({ label: this.$gettext('Reopen alarm'), icon: 'mdi-alert-circle-outline', onClick: this.openAlarm, color: 'orange' }) + result.push({ + label: this.$gettext('Reopen alarm'), + icon: 'mdi-alert-circle-outline', + onClick: + this.openAlarm, + color: 'orange' + }) } return result }, @@ -171,7 +181,7 @@ export default { if (this.selected.length > 0) { result.push({ label: this.$gettext('Delete'), icon: 'mdi-delete-outline', onClick: this.deleteAlarms, color: 'red' }) } - result.push({ label: 'Reload', icon: 'mdi-reload', onClick: this.fetchAlarms }) + result.push({ label: this.$gettext('Reload'), icon: 'mdi-reload', onClick: this.fetchAlarms }) return result } }, diff --git a/frontend/src/constants.json b/frontend/src/constants.json index 7f66341bd..b67288602 100644 --- a/frontend/src/constants.json +++ b/frontend/src/constants.json @@ -1 +1,4 @@ -{"ALARM_OPENED":1, "ALARM_CLOSED":2} +{ + "ALARM_OPENED": 1, + "ALARM_CLOSED": 2 +} diff --git a/modoboa/admin/api/v2/tests.py b/modoboa/admin/api/v2/tests.py index b58a31199..347d4be99 100644 --- a/modoboa/admin/api/v2/tests.py +++ b/modoboa/admin/api/v2/tests.py @@ -503,7 +503,7 @@ class AlarmViewSetTestCase(ModoAPITestCase): url = reverse("v2:alarm-switch", args=[alarm_restricted.pk]) resp = self.client.post(url) self.assertEqual(resp.status_code, 405) - url = reverse("v2:alarm-delete", args=[alarm_restricted.pk]) + url = reverse("v2:alarm-detail", args=[alarm_restricted.pk]) resp = self.client.delete(url) self.assertEqual(resp.status_code, 404) @@ -540,8 +540,23 @@ class AlarmViewSetTestCase(ModoAPITestCase): self.assertEqual(resp.status_code, 400) # Delete the alarm - url = reverse("v2:alarm-delete", args=[alarm.pk]) + url = reverse("v2:alarm-detail", args=[alarm.pk]) resp = self.client.delete(url) self.assertEqual(resp.status_code, 204) - + def test_bulk_delete(self): + url = reverse("v2:alarm-bulk-delete") + resp = self.client.delete(url) + self.assertEqual(resp.status_code, 400) + resp = self.client.delete(f"{url}?ids[]=toto") + self.assertEqual(resp.status_code, 400) + alarm1 = factories.AlarmFactory( + domain__name="test.com", mailbox=None, title="Test alarm") + alarm2 = factories.AlarmFactory( + domain__name="test.com", mailbox=None, title="Test alarm") + resp = self.client.delete(f"{url}?ids[]={alarm1.pk}&ids[]={alarm2.pk}") + self.assertEqual(resp.status_code, 204) + with self.assertRaises(models.Alarm.DoesNotExist): + alarm1.refresh_from_db() + with self.assertRaises(models.Alarm.DoesNotExist): + alarm2.refresh_from_db() diff --git a/modoboa/admin/api/v2/viewsets.py b/modoboa/admin/api/v2/viewsets.py index d3d5e4c04..54d42b3f2 100644 --- a/modoboa/admin/api/v2/viewsets.py +++ b/modoboa/admin/api/v2/viewsets.py @@ -340,6 +340,7 @@ class AlarmFilterSet(dj_filters.FilterSet): class AlarmViewSet(GetThrottleViewsetMixin, + mixins.DestroyModelMixin, mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet): @@ -363,13 +364,6 @@ class AlarmViewSet(GetThrottleViewsetMixin, self.request.user) ).order_by("-created") - @action(methods=["delete"], detail=True) - def delete(self, request, **kwargs): - """Allow admins to delete alarms.""" - alarm = self.get_object() - alarm.delete() - return response.Response(status=status.HTTP_204_NO_CONTENT) - @action(methods=['patch'], detail=True) def switch(self, request, **kwargs): """Custom update method that switch status of an alarm.""" @@ -381,3 +375,17 @@ class AlarmViewSet(GetThrottleViewsetMixin, else: alarm.reopen() return response.Response(status=status.HTTP_204_NO_CONTENT) + + @action(methods=['delete'], detail=False) + def bulk_delete(self, request, **kwargs): + """Delete multiple alarms at the same time.""" + ids = request.query_params.getlist("ids[]") + if not ids: + return response.Response(_("No alarm ID provided"), status=400) + try: + ids = [int(alarm_id) for alarm_id in ids] + except ValueError: + return response.Response( + _("Received invalid alarm id(s)"), status=400) + models.Alarm.objects.filter(pk__in=ids).delete() + return response.Response(status=204) diff --git a/modoboa/admin/models/mxrecord.py b/modoboa/admin/models/mxrecord.py index c03ae3e4f..c0653ffbe 100644 --- a/modoboa/admin/models/mxrecord.py +++ b/modoboa/admin/models/mxrecord.py @@ -60,7 +60,7 @@ class MXRecord(models.Model): managed = models.BooleanField(default=False) updated = models.DateTimeField() - objects = MXRecordManager.from_queryset(MXRecordQuerySet)() + objects: MXRecordManager = MXRecordManager.from_queryset(MXRecordQuerySet)() def is_managed(self): if not param_tools.get_global_parameter("enable_mx_checks"):