mirror of
https://github.com/jwinterm/modoboa.git
synced 2026-10-19 05:58:34 +02:00
Added new form to manipulate domain admins.
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
import repository from './repository'
|
||||
|
||||
const resource = 'accounts'
|
||||
|
||||
export default {
|
||||
getAll ({ domain, role }) {
|
||||
return repository.get(`/${resource}/`, { params: { domain, role } })
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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',
|
||||
|
||||
@@ -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")
|
||||
|
||||
|
||||
@@ -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()
|
||||
)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user