Added new form to manipulate domain admins.

This commit is contained in:
Antoine Nguyen
2021-02-20 19:05:54 +01:00
parent 571fecf098
commit 332f626bdc
10 changed files with 292 additions and 41 deletions
+9
View File
@@ -0,0 +1,9 @@
import repository from './repository'
const resource = 'accounts'
export default {
getAll ({ domain, role }) {
return repository.get(`/${resource}/`, { params: { domain, role } })
}
}
+9
View File
@@ -20,6 +20,15 @@ export default {
}
return repository.get(url)
},
getDomainAdministrators (domainId) {
return repository.get(`/${domainResource}/${domainId}/administrators/`)
},
addDomainAdministrator (domainId, accountId) {
return repository.post(`/${domainResource}/${domainId}/administrators/add/`, { account: accountId })
},
removeDomainAdministrator (domainId, accountId) {
return repository.post(`/${domainResource}/${domainId}/administrators/remove/`, { account: accountId })
},
createDomain (data) {
return repository.post(`/${domainResource}/`, data)
},
@@ -0,0 +1,131 @@
<template>
<v-card>
<v-card-title>
<span class="headline">
<translate class="headline">Administrators of </translate>
{{ domain.name }}
</span>
</v-card-title>
<v-card-text>
<v-data-table
:headers="headers"
:items="administrators"
hide-default-footer
>
<template v-slot:item.username="{ item }">
<span v-if="item.id">{{ item.username }}</span>
<v-autocomplete
v-else
v-model="selectedAccount"
:label="'Select an account'|translate"
:items="accounts"
item-text="username"
return-object
>
<template v-slot:append-outer>
<v-btn v-if="selectedAccount" icon color="primary" @click="addAdministrator">
<v-icon>mdi-content-save</v-icon>
</v-btn>
</template>
</v-autocomplete>
</template>
<template v-slot:item.name="{ item }">
{{ item.first_name }} {{ item.last_name }}
</template>
<template v-slot:item.actions="{ item }">
<v-btn v-if="item.id"
icon
color="red"
@click="removeAdministrator(item)"
:title="'Remove this administrator'|translate"
>
<v-icon>mdi-delete-outline</v-icon>
</v-btn>
</template>
</v-data-table>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn
color="primary darken-1"
text
@click="addRow"
>
<translate>Add</translate>
</v-btn>
<v-btn
color="grey darken-1"
text
@click="close"
>
<translate>Close</translate>
</v-btn>
</v-card-actions>
</v-card>
</template>
<script>
import accounts from '@/api/accounts'
import domains from '@/api/domains'
import { bus } from '@/main'
export default {
props: ['domain'],
data () {
return {
accounts: [],
administrators: [],
headers: [
{ text: this.$gettext('Username'), value: 'username' },
{ text: this.$gettext('Name'), value: 'name' },
{ text: '', value: 'actions', align: 'right', sortable: false }
],
selectedAccount: null
}
},
methods: {
addRow () {
this.administrators.push({})
},
close () {
this.$emit('close')
},
fetchAdministrators (domain) {
domains.getDomainAdministrators(domain.pk).then(resp => {
this.administrators = resp.data
})
},
fetchAccounts () {
accounts.getAll({ role: 'DomainAdmins' }).then(resp => {
this.accounts = resp.data.filter(
account => this.administrators.find(admin => admin.id === account.pk) === undefined
)
})
},
addAdministrator () {
domains.addDomainAdministrator(this.domain.pk, this.selectedAccount.pk).then(resp => {
this.fetchAdministrators(this.domain)
this.accounts = this.accounts.filter(account => account.pk !== this.selectedAccount.pk)
this.selectedAccount = null
bus.$emit('notification', { msg: this.$gettext('Administrator added') })
})
},
removeAdministrator (admin) {
domains.removeDomainAdministrator(this.domain.pk, admin.id).then(resp => {
this.fetchAdministrators(this.domain)
this.fetchAccounts()
bus.$emit('notification', { msg: this.$gettext('Administrator removed') })
})
}
},
watch: {
domain: {
handler: function (value) {
this.fetchAdministrators(value)
this.fetchAccounts()
},
immediate: true
}
}
}
</script>
+25 -19
View File
@@ -74,24 +74,7 @@
<v-icon>mdi-dots-horizontal</v-icon>
</v-btn>
</template>
<v-list dense>
<v-list-item @click="editDomain(item)">
<v-list-item-icon>
<v-icon>mdi-circle-edit-outline</v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title><translate>Edit</translate></v-list-item-title>
</v-list-item-content>
</v-list-item>
<v-list-item @click="deleteDomain(item)" color="red">
<v-list-item-icon>
<v-icon color="red">mdi-delete-outline</v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title><translate>Delete</translate></v-list-item-title>
</v-list-item-content>
</v-list-item>
</v-list>
<menu-items :items="domainMenuItems" :object="item" />
</v-menu>
</div>
</td>
@@ -132,6 +115,15 @@
@alias-deleted="domainAliasDeleted"
/>
</v-dialog>
<v-dialog v-model="showAdminList"
persistent
max-width="800px"
>
<administrator-list
:domain="selectedDomain"
@close="showAdminList = false"
/>
</v-dialog>
</v-card>
</template>
@@ -139,23 +131,32 @@
import { mapGetters } from 'vuex'
import { bus } from '@/main'
import domainApi from '@/api/domains'
import AdministratorList from './AdministratorList'
import ConfirmDialog from '@/components/layout/ConfirmDialog'
import DNSDetail from '@/components/domains/DNSDetail'
import DomainAliasForm from '@/components/domains/DomainAliasForm'
import DomainForm from '@/components/domains/DomainForm'
import MenuItems from '@/components/tools/MenuItems'
export default {
components: {
AdministratorList,
ConfirmDialog,
'dns-detail': DNSDetail,
DomainAliasForm,
DomainForm
DomainForm,
MenuItems
},
computed: mapGetters({
domains: 'domains/domains'
}),
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: 'Name', value: 'name' },
{ text: 'Aliases', value: 'domainalias_count' },
@@ -166,6 +167,7 @@ export default {
],
selectedDomain: null,
selectedDomainAlias: null,
showAdminList: false,
showConfirmDialog: false,
showAliasForm: false,
showDNSdetail: false,
@@ -260,6 +262,10 @@ export default {
this.selectedDomain = domain
this.showDNSdetail = true
},
openAdminList (domain) {
this.selectedDomain = domain
this.showAdminList = true
},
showAliases (item) {
this.expanded = [{ item }]
}
@@ -0,0 +1,21 @@
<template>
<v-list dense>
<v-list-item v-for="(item, index) in items"
:key="index"
@click="item.onClick(object)"
>
<v-list-item-icon>
<v-icon :color="item.color">{{ item.icon }}</v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title>{{ item.label }}</v-list-item-title>
</v-list-item-content>
</v-list-item>
</v-list>
</template>
<script>
export default {
props: ['items', 'object']
}
</script>
-17
View File
@@ -3,7 +3,6 @@ import VueRouter from 'vue-router'
import store from '@/store'
import DomainForm from '@/components/domains/DomainForm'
import ParametersForm from '@/components/parameters/ParametersForm'
import LogList from '@/components/logs/LogList'
@@ -26,22 +25,6 @@ const routes = [
requiresAuth: true
}
},
{
path: '/domains/add/',
name: 'DomainAdd',
component: DomainForm,
meta: {
requiresAuth: true
}
},
{
path: '/domains/:domainPk([0-9]+)/',
name: 'DomainEdit',
component: DomainForm,
meta: {
requiresAuth: true
}
},
{
path: '/parameters/:app',
name: 'ParametersEdit',
+1 -1
View File
@@ -84,7 +84,7 @@ class DomainAliasViewSet(lib_viewsets.RevisionModelMixin,
class AccountViewSet(lib_viewsets.RevisionModelMixin, viewsets.ModelViewSet):
"""ViewSet for User/Mailbox."""
filter_backends = [filters.SearchFilter]
filter_backends = (filters.SearchFilter, )
permission_classes = [IsAuthenticated, DjangoModelPermissions, ]
search_fields = ("^first_name", "^last_name", "^email")
+16
View File
@@ -116,3 +116,19 @@ class AdminGlobalParametersSerializer(serializers.Serializer):
auto_account_removal = serializers.BooleanField(default=False)
auto_create_domain_and_mailbox = serializers.BooleanField(default=True)
create_alias_on_mbox_rename = serializers.BooleanField(default=False)
class DomainAdminSerializer(serializers.ModelSerializer):
"""Serializer used for administrator related routes."""
class Meta:
model = core_models.User
fields = ("id", "username", "first_name", "last_name")
class SimpleDomainAdminSerializer(serializers.Serializer):
"""Serializer used for add/remove operations."""
account = serializers.PrimaryKeyRelatedField(
queryset=core_models.User.objects.all()
)
+1 -1
View File
@@ -10,7 +10,7 @@ router = routers.SimpleRouter()
router.register(r"domains", viewsets.DomainViewSet, basename="domain")
router.register(
r"domainaliases", v1_viewsets.DomainAliasViewSet, basename="domain_alias")
router.register(r"accounts", v1_viewsets.AccountViewSet, basename="account")
router.register(r"accounts", viewsets.AccountViewSet, basename="account")
router.register(r"aliases", v1_viewsets.AliasViewSet, basename="alias")
router.register(
r"senderaddresses", v1_viewsets.SenderAddressViewSet,
+79 -3
View File
@@ -2,12 +2,20 @@
from django.utils.translation import ugettext as _
from django.contrib.contenttypes.models import ContentType
from django_filters import rest_framework as dj_filters
from drf_spectacular.utils import extend_schema, extend_schema_view
from rest_framework import mixins, permissions, response, status, viewsets
from rest_framework import (
filters, mixins, permissions, response, status, viewsets
)
from rest_framework.decorators import action
from rest_framework.exceptions import PermissionDenied
from modoboa.admin.api.v1 import serializers as v1_serializers
from modoboa.admin.api.v1 import (
serializers as v1_serializers, viewsets as v1_viewsets
)
from modoboa.core import models as core_models
from modoboa.lib import viewsets as lib_viewsets
from ... import models
@@ -30,7 +38,7 @@ from . import serializers
delete=extend_schema(
description="Delete a particular domain",
summary="Delete a particular domain"
)
),
)
class DomainViewSet(lib_viewsets.RevisionModelMixin,
mixins.ListModelMixin,
@@ -53,6 +61,10 @@ class DomainViewSet(lib_viewsets.RevisionModelMixin,
return serializers.DomainSerializer
if self.action == "delete":
return serializers.DeleteDomainSerializer
if self.action == "administrators":
return serializers.DomainAdminSerializer
if self.action in ["add_administrator", "remove_administrator"]:
return serializers.SimpleDomainAdminSerializer
return v1_serializers.DomainSerializer
@action(methods=["post"], detail=True)
@@ -68,3 +80,67 @@ class DomainViewSet(lib_viewsets.RevisionModelMixin,
serializer.is_valid(raise_exception=True)
domain.delete(request.user, serializer.validated_data["keep_folder"])
return response.Response(status=status.HTTP_204_NO_CONTENT)
@action(methods=["get"], detail=True)
def administrators(self, request, **kwargs):
"""Retrieve all administrators of a domain."""
domain = self.get_object()
serializer = self.get_serializer(domain.admins, many=True)
return response.Response(serializer.data)
@action(methods=["post"], detail=True, url_path="administrators/add")
def add_administrator(self, request, **kwargs):
"""Add an administrator to a domain."""
domain = self.get_object()
context = self.get_serializer_context()
context["domain"] = domain
serializer = self.get_serializer(data=request.data, context=context)
serializer.is_valid(raise_exception=True)
domain.add_admin(serializer.validated_data["account"])
return response.Response()
@action(methods=["post"], detail=True, url_path="administrators/remove")
def remove_administrator(self, request, **kwargs):
"""Remove an administrator from a domain."""
domain = self.get_object()
context = self.get_serializer_context()
context["domain"] = domain
serializer = self.get_serializer(data=request.data, context=context)
serializer.is_valid(raise_exception=True)
domain.remove_admin(serializer.validated_data["account"])
return response.Response()
class AccountFilterSet(dj_filters.FilterSet):
"""Custom FilterSet for Account."""
domain = dj_filters.ModelChoiceFilter(
queryset=lambda request: models.Domain.objects.get_for_admin(
request.user),
field_name="mailbox__domain"
)
role = dj_filters.CharFilter(method="filter_role")
class Meta:
model = core_models.User
fields = ["domain", "role"]
def filter_role(self, queryset, name, value):
return queryset.filter(groups__name=value)
class AccountViewSet(v1_viewsets.AccountViewSet):
"""ViewSet for User/Mailbox."""
filter_backends = (filters.SearchFilter, dj_filters.DjangoFilterBackend)
filterset_class = AccountFilterSet
def get_queryset(self):
"""Filter queryset based on current user."""
user = self.request.user
ids = (
user.objectaccess_set
.filter(content_type=ContentType.objects.get_for_model(user))
.values_list("object_id", flat=True)
)
return core_models.User.objects.filter(pk__in=ids)