Merge pull request #2526 from modoboa/feature/display_alarms_ui_v2

Added new page to display opened alarms in UI v2.
This commit is contained in:
Antoine Nguyen
2022-06-13 09:38:11 +02:00
committed by GitHub
12 changed files with 195 additions and 15 deletions
+9
View File
@@ -0,0 +1,9 @@
import repository from './repository'
const resource = 'alarms'
export default {
getAll () {
return repository.get(`${resource}/`)
}
}
@@ -0,0 +1,63 @@
<template>
<v-card class="mt-6">
<v-toolbar flat>
<v-menu offset-y>
<template v-slot:activator="{ on, attrs }">
<v-btn v-bind="attrs" v-on="on" small>
Actions <v-icon right>mdi-chevron-down</v-icon>
</v-btn>
</template>
<v-list dense>
</v-list>
</v-menu>
<v-spacer></v-spacer>
<v-text-field
v-model="search"
prepend-inner-icon="mdi-magnify"
placeholder="Search"
filled
outlined
dense
hide-details
></v-text-field>
</v-toolbar>
<v-data-table
v-model="selected"
:headers="headers"
:items="alarms"
:search="search"
item-key="pk"
class="elevation-1"
show-select
>
<template v-slot:item.created="{ item }">
{{ item.created|date }}
</template>
</v-data-table>
</v-card>
</template>
<script>
import alarms from '@/api/alarms'
export default {
data () {
return {
alarms: [],
headers: [
{ text: this.$gettext('Date'), value: 'created' },
{ text: this.$gettext('Domain'), value: 'domain.name' },
{ text: this.$gettext('Mailbox'), value: 'mailbox.name' },
{ text: this.$gettext('Message'), value: 'title' }
],
search: '',
selected: []
}
},
mounted () {
alarms.getAll().then(resp => {
this.alarms = resp.data
})
}
}
</script>
+29 -7
View File
@@ -75,11 +75,22 @@
<div class="text-right">
<v-menu offset-y>
<template v-slot:activator="{ on, attrs }">
<v-btn icon v-bind="attrs" v-on="on">
<v-badge
v-if="item.opened_alarms_count"
bordered
color="error"
icon="mdi-bell"
overlap
>
<v-btn icon v-bind="attrs" v-on="on">
<v-icon>mdi-dots-horizontal</v-icon>
</v-btn>
</v-badge>
<v-btn v-else icon v-bind="attrs" v-on="on">
<v-icon>mdi-dots-horizontal</v-icon>
</v-btn>
</template>
<menu-items :items="domainMenuItems" :object="item" />
<menu-items :items="getDomainMenuItems(item)" :object="item" />
</v-menu>
</div>
</td>
@@ -151,11 +162,6 @@ export default {
}),
data () {
return {
domainMenuItems: [
{ label: this.$gettext('Administrators'), icon: 'mdi-account-supervisor', onClick: this.openAdminList },
{ label: this.$gettext('Edit'), icon: 'mdi-circle-edit-outline', onClick: this.editDomain },
{ label: this.$gettext('Delete'), icon: 'mdi-delete-outline', onClick: this.deleteDomain, color: 'red' }
],
headers: [
{ text: this.$gettext('Name'), value: 'name' },
{ text: this.$gettext('Aliases'), value: 'domainalias_count' },
@@ -243,6 +249,22 @@ export default {
},
showAliases (item) {
this.expanded = [{ item }]
},
getDomainMenuItems (domain) {
const result = [
{ label: this.$gettext('Administrators'), icon: 'mdi-account-supervisor', onClick: this.openAdminList },
{ label: this.$gettext('Edit'), icon: 'mdi-circle-edit-outline', onClick: this.editDomain },
{ label: this.$gettext('Delete'), icon: 'mdi-delete-outline', onClick: this.deleteDomain, color: 'red' }
]
if (domain.opened_alarms_count) {
result.push({
label: this.$gettext('Alarms'),
icon: 'mdi-bell',
color: 'red',
onClick: () => this.$router.push({ name: 'Alarms' })
})
}
return result
}
}
}
@@ -174,6 +174,11 @@ export default {
icon: 'mdi-account',
roles: ['DomainAdmins', 'Resellers', 'SuperAdmins']
},
{
text: this.$gettext('Alarms'),
to: { name: 'Alarms' },
icon: 'mdi-bell'
},
{
icon: 'mdi-history',
text: this.$gettext('Logs'),
+8
View File
@@ -110,6 +110,14 @@ const routes = [
allowedRoles: ['SuperAdmins']
}
},
{
path: '/alarms',
name: 'Alarms',
component: () => import('../views/alarms/Alarms.vue'),
meta: {
requiresAuth: true
}
},
{
path: '/logs/audit_trail',
name: 'AuditTrail',
+24
View File
@@ -0,0 +1,24 @@
<template>
<div>
<v-toolbar flat>
<v-toolbar-title><translate>Opened alarms</translate></v-toolbar-title>
</v-toolbar>
<alarm-list />
</div>
</template>
<script>
import AlarmList from '@/components/alarms/AlarmList'
export default {
components: {
AlarmList
}
}
</script>
<style scoped>
.v-toolbar {
background-color: #f7f8fa !important;
}
</style>
+12 -2
View File
@@ -2,6 +2,7 @@
import ipaddress
import os
from typing import List
from django.conf import settings
from django.core.exceptions import ValidationError
@@ -54,7 +55,7 @@ class DomainSerializer(v1_serializers.DomainSerializer):
class Meta(v1_serializers.DomainSerializer.Meta):
fields = v1_serializers.DomainSerializer.Meta.fields + (
"domain_admin", "transport"
"domain_admin", "transport", "opened_alarms_count"
)
def __init__(self, *args, **kwargs):
@@ -321,7 +322,7 @@ class AccountSerializer(v1_serializers.AccountSerializer):
return
self.fields["resources"] = serializers.SerializerMethodField()
def get_resources(self, account):
def get_resources(self, account) -> List[AccountResourceSerializer]:
if account.role == 'SimpleUsers':
return []
resources = []
@@ -578,3 +579,12 @@ class CSVIdentityImportSerializer(CSVImportSerializer):
"""Custom serializer for identity import."""
crypt_password = serializers.BooleanField()
class AlarmSerializer(serializers.ModelSerializer):
"""Serializer for Alarm related endpoints."""
class Meta:
depth = 1
fields = "__all__"
model = models.Alarm
+17
View File
@@ -467,3 +467,20 @@ class UserAccountViewSetTestCase(ModoAPITestCase):
models.Alias.objects.filter(address=self.da.username).count(),
1
)
class AlarmViewSetTestCase(ModoAPITestCase):
@classmethod
def setUpTestData(cls): # NOQA:N802
"""Create test data."""
super().setUpTestData()
factories.populate_database()
factories.AlarmFactory(
domain__name="test.com", mailbox=None, title="Test alarm")
def test_list(self):
url = reverse("v2:alarm-list")
resp = self.client.get(url)
self.assertEqual(resp.status_code, 200)
self.assertEqual(len(resp.json()), 1)
+2
View File
@@ -19,5 +19,7 @@ router.register(
r"identities", viewsets.IdentityViewSet, basename="identities")
router.register(
r"account", viewsets.UserAccountViewSet, basename="account")
router.register(
r"alarms", viewsets.AlarmViewSet, basename="alarm")
urlpatterns = router.urls
+15
View File
@@ -321,3 +321,18 @@ class UserAccountViewSet(viewsets.ViewSet):
).update(enabled=False)
alias.set_recipients(recipients)
return response.Response(serializer.validated_data)
class AlarmViewSet(viewsets.ReadOnlyModelViewSet):
"""Viewset for Alarm."""
permission_classes = (
permissions.IsAuthenticated,
)
serializer_class = serializers.AlarmSerializer
def get_queryset(self):
return models.Alarm.objects.filter(
domain__in=models.Domain.objects.get_for_admin(
self.request.user)
)
+6 -1
View File
@@ -92,12 +92,17 @@ class Domain(mixins.MessageLimitMixin, AdminObject):
return self.alias_set.filter(internal=False).count()
@property
def identities_count(self):
def identities_count(self) -> int:
"""Total number of identities in this domain."""
return (
self.mailbox_set.count() +
self.alias_set.filter(internal=False).count())
@property
def opened_alarms_count(self) -> int:
"""Number of alarms currently opened for this domain."""
return self.alarms.opened().count()
@property
def tags(self):
label = ""
+5 -5
View File
@@ -13,7 +13,7 @@ class ObjectLimitMixin(object):
"""Common methods."""
@property
def label(self):
def label(self) -> str:
"""Return display name."""
return self.definition["label"]
@@ -23,7 +23,7 @@ class ObjectLimitMixin(object):
return self.definition.get("type", "count")
@property
def usage(self):
def usage(self) -> int:
"""Return current limit usage in %."""
if self.max_value < 0:
return -1
@@ -79,7 +79,7 @@ class UserObjectLimit(ObjectLimitMixin, models.Model):
return None
@property
def current_value(self):
def current_value(self) -> int:
"""Return the current number of objects."""
condition = (
self.type == "count" and
@@ -107,7 +107,7 @@ class UserObjectLimit(ObjectLimitMixin, models.Model):
return self.definition["label"]
@cached_property
def usage(self):
def usage(self) -> int:
"""Return current limit usage in %."""
if self.max_value < 0:
return -1
@@ -141,7 +141,7 @@ class DomainObjectLimit(ObjectLimitMixin, models.Model):
return None
@property
def current_value(self):
def current_value(self) -> int:
"""Return the current number of objects."""
definition = self.definition
if not definition: