null
+ }
},
computed: {
...mapGetters({
@@ -96,7 +99,9 @@ export default {
methods: {
close () {
this.$emit('close')
- this.form = {}
+ this.form = {
+ enabled: true
+ }
},
deleteAlias () {
domains.deleteDomainAlias(this.domainAlias.pk).then(resp => {
diff --git a/frontend/src/components/domains/DomainCreationForm.vue b/frontend/src/components/domains/DomainCreationForm.vue
index 03a7c900a..d9f45769e 100644
--- a/frontend/src/components/domains/DomainCreationForm.vue
+++ b/frontend/src/components/domains/DomainCreationForm.vue
@@ -31,35 +31,7 @@
New domain /
General
-
-
-
-
-
-
-
-
-
+
New domain /
DNS
-
-
-
-
+
Back
@@ -113,49 +66,7 @@
New domain /
Limitations
-
-
-
-
-
-
-
-
-
-
-
+
Back
@@ -174,45 +85,7 @@
New domain /
Options
-
-
-
-
-
-
-
-
-
-
+
Back
@@ -324,14 +197,20 @@
diff --git a/frontend/src/components/domains/DomainForm.vue b/frontend/src/components/domains/DomainForm.vue
index 3327a6c3c..8e2c7653a 100644
--- a/frontend/src/components/domains/DomainForm.vue
+++ b/frontend/src/components/domains/DomainForm.vue
@@ -3,68 +3,122 @@
- Add domain
- Edit domain
+ Edit domain
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+ General
+
+
+
+
+
+
+ Name: {{ editedDomain.name }}
+
+
+ Type: {{ editedDomain.type }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ DNS
+
+
+
+
+
+
+ DNS checks
+ mdi-check-circle-outline
+ mdi-close-circle-outline
+
+
+ DKIM signing
+ mdi-check-circle-outline
+ mdi-close-circle-outline
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Limitations
+
+
+
+
+
+
+ Quota: {{ domain.quota }}
+
+
+ Sending limit: {{ domain.message_sending_limit }}
+
+
+ No sending limit
+
+
+
+
+
+
+
+
+
+
+
-
- Cancel
+
+ Cancel
-
- Save
+
+ Save
@@ -73,63 +127,51 @@
diff --git a/frontend/src/components/domains/DomainLimitationsForm.vue b/frontend/src/components/domains/DomainLimitationsForm.vue
new file mode 100644
index 000000000..42d7c7848
--- /dev/null
+++ b/frontend/src/components/domains/DomainLimitationsForm.vue
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/components/domains/DomainList.vue b/frontend/src/components/domains/DomainList.vue
index 7eb117ace..e909c248c 100644
--- a/frontend/src/components/domains/DomainList.vue
+++ b/frontend/src/components/domains/DomainList.vue
@@ -75,7 +75,7 @@
-
+
mdi-circle-edit-outline
@@ -117,6 +117,12 @@
max-width="800px">
+
+
+
@@ -131,16 +137,19 @@
diff --git a/frontend/src/store/modules/domains.js b/frontend/src/store/modules/domains.js
index 94322ab93..5ffd3781a 100644
--- a/frontend/src/store/modules/domains.js
+++ b/frontend/src/store/modules/domains.js
@@ -5,6 +5,7 @@ import domainApi from '@/api/domains'
const SET_DOMAINS = 'SET_DOMAINS'
const ADD_DOMAIN = 'ADD_DOMAIN'
const UPDATE_DOMAIN = 'UPDATE_DOMAIN'
+const DELETE_DOMAIN = 'DELETE_DOMAIN'
const SET_DOMAIN_ALIASES = 'SET_DOMAIN_ALIASES'
const ADD_DOMAIN_ALIAS = 'ADD_DOMAIN_ALIAS'
const UPDATE_DOMAIN_ALIAS = 'UPDATE_DOMAIN_ALIAS'
@@ -48,6 +49,11 @@ const actions = {
commit(UPDATE_DOMAIN, { domain: response.data })
})
},
+ deleteDomain ({ commit }, { id, data }) {
+ return domainApi.deleteDomain(id, data).then(resp => {
+ commit(DELETE_DOMAIN, id)
+ })
+ },
getDomainAliases ({ commit }) {
return Vue.prototype.$axios.get('/domainaliases/?expand=target').then(response => {
commit(SET_DOMAIN_ALIASES, response.data)
@@ -86,6 +92,9 @@ const mutations = {
}
})
},
+ [DELETE_DOMAIN] (state, domainId) {
+ state.domains = state.domains.filter(item => item.pk !== domainId)
+ },
[SET_DOMAIN_ALIASES] (state, domainAliases) {
state.domainAliases = domainAliases
},
diff --git a/modoboa/admin/api/__init__.py b/modoboa/admin/api/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/modoboa/admin/api/v1/__init__.py b/modoboa/admin/api/v1/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/modoboa/admin/serializers.py b/modoboa/admin/api/v1/serializers.py
similarity index 81%
rename from modoboa/admin/serializers.py
rename to modoboa/admin/api/v1/serializers.py
index a0d3951d0..a9fdc57bc 100644
--- a/modoboa/admin/serializers.py
+++ b/modoboa/admin/api/v1/serializers.py
@@ -24,7 +24,7 @@ from modoboa.lib import (
)
from modoboa.parameters import tools as param_tools
-from . import constants, lib, models
+from ... import lib, models
class DomainSerializer(serializers.ModelSerializer):
@@ -110,85 +110,6 @@ class DomainSerializer(serializers.ModelSerializer):
return domain
-class CreateDomainAdminSerializer(serializers.Serializer):
- """Sub serializer for domain administrator creation."""
-
- username = serializers.CharField()
- with_random_password = serializers.BooleanField(default=False)
- with_mailbox = serializers.BooleanField(default=False)
- with_aliases = serializers.BooleanField(default=False)
-
-
-class DomainSerializerV2(DomainSerializer):
- """Domain serializer for v2 API."""
-
- domain_admin = CreateDomainAdminSerializer(required=False)
-
- class Meta(DomainSerializer.Meta):
- fields = DomainSerializer.Meta.fields + (
- "domain_admin",
- )
-
- def create(self, validated_data):
- """Create administrator and other stuff if needed."""
- domain_admin = validated_data.pop("domain_admin", None)
- domain = super().create(validated_data)
- if not domain_admin:
- return domain
-
- # 1. Create a domain administrator
- username = "%s@%s" % (domain_admin["username"], domain.name)
- try:
- da = core_models.User.objects.get(username=username)
- except core_models.User.DoesNotExist:
- pass
- else:
- raise lib_exceptions.Conflict(
- _("User '%s' already exists") % username)
- user = self.context["request"].user
- core_signals.can_create_object.send(
- self.__class__, context=user, klass=models.Mailbox)
- da = core_models.User(username=username, email=username, is_active=True)
- if domain_admin["with_random_password"]:
- password = lib.make_password()
- else:
- password = param_tools.get_global_parameter(
- "default_password", app="core")
- da.set_password(password)
- da.save()
- da.role = "DomainAdmins"
- da.post_create(user)
-
- # 2. Create mailbox if needed
- if domain_admin["with_mailbox"]:
- dom_admin_username = domain_admin["username"]
- mb = models.Mailbox(
- address=dom_admin_username, domain=domain,
- user=da, use_domain_quota=True
- )
- mb.set_quota(
- override_rules=user.has_perm("admin.change_domain"))
- mb.save(creator=user)
-
- # 3. Create aliases if needed
- condition = (
- domain.type == "domain" and
- domain_admin["with_aliases"] and
- dom_admin_username != "postmaster"
- )
- if condition:
- core_signals.can_create_object.send(
- self.__class__, context=user, klass=models.Alias)
- address = u"postmaster@{}".format(domain.name)
- alias = models.Alias.objects.create(
- address=address, domain=domain, enabled=True)
- alias.set_recipients([mb.full_address])
- alias.post_create(user)
-
- domain.add_admin(da)
- return domain
-
-
class DomainAliasSerializer(FlexFieldsModelSerializer):
"""Base DomainAlias serializer."""
@@ -571,28 +492,6 @@ class SenderAddressSerializer(serializers.ModelSerializer):
return value
-class AdminGlobalParametersSerializer(serializers.Serializer):
- """A serializer for global parameters."""
-
- # Domain settings
- enable_mx_checks = serializers.BooleanField(default=True)
- valid_mxs = serializers.CharField(allow_blank=True)
- domains_must_have_authorized_mx = serializers.BooleanField(default=False)
- enable_dnsbl_checks = serializers.BooleanField(default=True)
- dkim_keys_storage_dir = serializers.CharField(allow_blank=True)
- dkim_default_key_length = serializers.ChoiceField(
- default=2048, choices=constants.DKIM_KEY_LENGTHS)
-
- # Mailboxes settings
- handle_mailboxes = serializers.BooleanField(default=False)
- mailboxes_owner = serializers.CharField(default="vmail")
- default_domain_quota = serializers.IntegerField(default=0)
- default_mailbox_quota = serializers.IntegerField(default=0)
- 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 ResetPasswordSerializer(serializers.Serializer):
"""Serializer by the reset password endpoint."""
diff --git a/modoboa/admin/api/v1/urls.py b/modoboa/admin/api/v1/urls.py
new file mode 100644
index 000000000..e2e514aaa
--- /dev/null
+++ b/modoboa/admin/api/v1/urls.py
@@ -0,0 +1,16 @@
+"""Admin API urls."""
+
+from rest_framework import routers
+
+from . import viewsets
+
+router = routers.SimpleRouter()
+router.register(r"domains", viewsets.DomainViewSet, basename="domain")
+router.register(
+ r"domainaliases", viewsets.DomainAliasViewSet, basename="domain_alias")
+router.register(r"accounts", viewsets.AccountViewSet, basename="account")
+router.register(r"aliases", viewsets.AliasViewSet, basename="alias")
+router.register(
+ r"senderaddresses", viewsets.SenderAddressViewSet, basename="sender_address")
+
+urlpatterns = router.urls
diff --git a/modoboa/admin/api.py b/modoboa/admin/api/v1/viewsets.py
similarity index 96%
rename from modoboa/admin/api.py
rename to modoboa/admin/api/v1/viewsets.py
index 299c3c3f3..164b05f31 100644
--- a/modoboa/admin/api.py
+++ b/modoboa/admin/api/v1/viewsets.py
@@ -17,7 +17,8 @@ from modoboa.core import sms_backends
from modoboa.lib import renderers as lib_renderers
from modoboa.lib import viewsets as lib_viewsets
-from . import lib, models, serializers
+from ... import lib, models
+from . import serializers
@extend_schema_view(
@@ -38,16 +39,12 @@ class DomainViewSet(lib_viewsets.RevisionModelMixin, viewsets.ModelViewSet):
"""Domain viewset."""
permission_classes = [IsAuthenticated, DjangoModelPermissions, ]
+ serializer_class = serializers.DomainSerializer
def get_queryset(self):
"""Filter queryset based on current user."""
return models.Domain.objects.get_for_admin(self.request.user)
- def get_serializer_class(self, *args, **kwargs):
- if self.request.version == "v2" and self.action == "create":
- return serializers.DomainSerializerV2
- return serializers.DomainSerializer
-
def perform_destroy(self, instance):
"""Add custom args to delete call."""
instance.delete(self.request.user)
diff --git a/modoboa/admin/api/v2/__init__.py b/modoboa/admin/api/v2/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/modoboa/admin/api/v2/serializers.py b/modoboa/admin/api/v2/serializers.py
new file mode 100644
index 000000000..5f055fca4
--- /dev/null
+++ b/modoboa/admin/api/v2/serializers.py
@@ -0,0 +1,118 @@
+"""Admin API v2 serializers."""
+
+from django.utils.translation import ugettext as _
+
+from rest_framework import serializers
+
+from modoboa.admin.api.v1 import serializers as v1_serializers
+from modoboa.core import models as core_models, signals as core_signals
+from modoboa.lib import exceptions as lib_exceptions
+from modoboa.parameters import tools as param_tools
+
+from ... import constants, lib, models
+
+
+class CreateDomainAdminSerializer(serializers.Serializer):
+ """Sub serializer for domain administrator creation."""
+
+ username = serializers.CharField()
+ with_random_password = serializers.BooleanField(default=False)
+ with_mailbox = serializers.BooleanField(default=False)
+ with_aliases = serializers.BooleanField(default=False)
+
+
+class DomainSerializer(v1_serializers.DomainSerializer):
+ """Domain serializer for v2 API."""
+
+ domain_admin = CreateDomainAdminSerializer(required=False)
+
+ class Meta(v1_serializers.DomainSerializer.Meta):
+ fields = v1_serializers.DomainSerializer.Meta.fields + (
+ "domain_admin",
+ )
+
+ def create(self, validated_data):
+ """Create administrator and other stuff if needed."""
+ domain_admin = validated_data.pop("domain_admin", None)
+ domain = super().create(validated_data)
+ if not domain_admin:
+ return domain
+
+ # 1. Create a domain administrator
+ username = "%s@%s" % (domain_admin["username"], domain.name)
+ try:
+ da = core_models.User.objects.get(username=username)
+ except core_models.User.DoesNotExist:
+ pass
+ else:
+ raise lib_exceptions.Conflict(
+ _("User '%s' already exists") % username)
+ user = self.context["request"].user
+ core_signals.can_create_object.send(
+ self.__class__, context=user, klass=models.Mailbox)
+ da = core_models.User(username=username, email=username, is_active=True)
+ if domain_admin["with_random_password"]:
+ password = lib.make_password()
+ else:
+ password = param_tools.get_global_parameter(
+ "default_password", app="core")
+ da.set_password(password)
+ da.save()
+ da.role = "DomainAdmins"
+ da.post_create(user)
+
+ # 2. Create mailbox if needed
+ if domain_admin["with_mailbox"]:
+ dom_admin_username = domain_admin["username"]
+ mb = models.Mailbox(
+ address=dom_admin_username, domain=domain,
+ user=da, use_domain_quota=True
+ )
+ mb.set_quota(
+ override_rules=user.has_perm("admin.change_domain"))
+ mb.save(creator=user)
+
+ # 3. Create aliases if needed
+ condition = (
+ domain.type == "domain" and
+ domain_admin["with_aliases"] and
+ dom_admin_username != "postmaster"
+ )
+ if condition:
+ core_signals.can_create_object.send(
+ self.__class__, context=user, klass=models.Alias)
+ address = u"postmaster@{}".format(domain.name)
+ alias = models.Alias.objects.create(
+ address=address, domain=domain, enabled=True)
+ alias.set_recipients([mb.full_address])
+ alias.post_create(user)
+
+ domain.add_admin(da)
+ return domain
+
+
+class DeleteDomainSerializer(serializers.Serializer):
+ """Serializer used with delete operation."""
+
+ keep_folder = serializers.BooleanField(default=False)
+
+
+class AdminGlobalParametersSerializer(serializers.Serializer):
+ """A serializer for global parameters."""
+
+ # Domain settings
+ enable_mx_checks = serializers.BooleanField(default=True)
+ valid_mxs = serializers.CharField(allow_blank=True)
+ domains_must_have_authorized_mx = serializers.BooleanField(default=False)
+ enable_dnsbl_checks = serializers.BooleanField(default=True)
+ dkim_keys_storage_dir = serializers.CharField(allow_blank=True)
+ dkim_default_key_length = serializers.ChoiceField(
+ default=2048, choices=constants.DKIM_KEY_LENGTHS)
+
+ # Mailboxes settings
+ handle_mailboxes = serializers.BooleanField(default=False)
+ default_domain_quota = serializers.IntegerField(default=0)
+ default_mailbox_quota = serializers.IntegerField(default=0)
+ 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)
diff --git a/modoboa/admin/api/v2/urls.py b/modoboa/admin/api/v2/urls.py
new file mode 100644
index 000000000..3c15e3e18
--- /dev/null
+++ b/modoboa/admin/api/v2/urls.py
@@ -0,0 +1,19 @@
+"""Admin API v2 urls."""
+
+from rest_framework import routers
+
+from modoboa.admin.api.v1 import viewsets as v1_viewsets
+
+from . import viewsets
+
+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"aliases", v1_viewsets.AliasViewSet, basename="alias")
+router.register(
+ r"senderaddresses", v1_viewsets.SenderAddressViewSet,
+ basename="sender_address")
+
+urlpatterns = router.urls
diff --git a/modoboa/admin/api/v2/viewsets.py b/modoboa/admin/api/v2/viewsets.py
new file mode 100644
index 000000000..6afdece7a
--- /dev/null
+++ b/modoboa/admin/api/v2/viewsets.py
@@ -0,0 +1,70 @@
+"""Admin API v2 viewsets."""
+
+from django.utils.translation import ugettext as _
+
+from drf_spectacular.utils import extend_schema, extend_schema_view
+from rest_framework import 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.lib import viewsets as lib_viewsets
+
+from ... import models
+from . import serializers
+
+
+@extend_schema_view(
+ retrieve=extend_schema(
+ description="Retrieve a particular domain",
+ summary="Retrieve a particular domain"
+ ),
+ list=extend_schema(
+ description="Retrieve a list of domains",
+ summary="Retrieve a list of domains"
+ ),
+ create=extend_schema(
+ description="Create a new domain",
+ summary="Create a new domain"
+ ),
+ delete=extend_schema(
+ description="Delete a particular domain",
+ summary="Delete a particular domain"
+ )
+)
+class DomainViewSet(lib_viewsets.RevisionModelMixin,
+ mixins.ListModelMixin,
+ mixins.RetrieveModelMixin,
+ mixins.CreateModelMixin,
+ mixins.UpdateModelMixin,
+ viewsets.GenericViewSet):
+ """V2 viewset."""
+
+ permission_classes = (
+ permissions.IsAuthenticated, permissions.DjangoModelPermissions,
+ )
+
+ def get_queryset(self):
+ """Filter queryset based on current user."""
+ return models.Domain.objects.get_for_admin(self.request.user)
+
+ def get_serializer_class(self, *args, **kwargs):
+ if self.action == "create":
+ return serializers.DomainSerializer
+ if self.action == "delete":
+ return serializers.DeleteDomainSerializer
+ return v1_serializers.DomainSerializer
+
+ @action(methods=["post"], detail=True)
+ def delete(self, request, **kwargs):
+ """Custom delete method that accepts body arguments."""
+ domain = self.get_object()
+ if not request.user.can_access(domain):
+ raise PermissionDenied(_("You don't have access to this domain"))
+ mb = getattr(request.user, "mailbox", None)
+ if mb and mb.domain == domain:
+ raise PermissionDenied(_("You can't delete your own domain"))
+ serializer = self.get_serializer(data=request.data)
+ serializer.is_valid(raise_exception=True)
+ domain.delete(request.user, serializer.validated_data["keep_folder"])
+ return response.Response(status=status.HTTP_204_NO_CONTENT)
diff --git a/modoboa/admin/app_settings.py b/modoboa/admin/app_settings.py
index c6809ac91..016c5064d 100644
--- a/modoboa/admin/app_settings.py
+++ b/modoboa/admin/app_settings.py
@@ -385,7 +385,7 @@ GLOBAL_PARAMETERS_STRUCT = collections.OrderedDict([
def load_admin_settings():
"""Load admin settings."""
from modoboa.parameters import tools as param_tools
- from . import serializers
+ from .api.v2 import serializers
param_tools.registry.add(
"global", AdminParametersForm, ugettext_lazy("Administration"))
diff --git a/modoboa/admin/urls_api.py b/modoboa/admin/urls_api.py
deleted file mode 100644
index f0c61b428..000000000
--- a/modoboa/admin/urls_api.py
+++ /dev/null
@@ -1,16 +0,0 @@
-"""Admin API urls."""
-
-from rest_framework import routers
-
-from . import api
-
-router = routers.SimpleRouter()
-router.register(r"domains", api.DomainViewSet, basename="domain")
-router.register(
- r"domainaliases", api.DomainAliasViewSet, basename="domain_alias")
-router.register(r"accounts", api.AccountViewSet, basename="account")
-router.register(r"aliases", api.AliasViewSet, basename="alias")
-router.register(
- r"senderaddresses", api.SenderAddressViewSet, basename="sender_address")
-
-urlpatterns = router.urls
diff --git a/modoboa/core/api/v2/viewsets.py b/modoboa/core/api/v2/viewsets.py
index 6f703bc0a..f96600feb 100644
--- a/modoboa/core/api/v2/viewsets.py
+++ b/modoboa/core/api/v2/viewsets.py
@@ -3,7 +3,7 @@
from rest_framework import permissions, response, viewsets
from rest_framework.decorators import action
-from modoboa.admin import serializers as admin_serializers
+from modoboa.admin.api.v1 import serializers as admin_v1_serializers
from modoboa.core.api.v1 import viewsets as core_v1_viewsets
from ... import models
@@ -16,7 +16,7 @@ class AccountViewSet(core_v1_viewsets.AccountViewSet):
@action(methods=["get"], detail=False)
def me(self, request):
"""Return information about connected user."""
- serializer = admin_serializers.AccountSerializer(request.user)
+ serializer = admin_v1_serializers.AccountSerializer(request.user)
return response.Response(serializer.data)
diff --git a/modoboa/urls_api_v1.py b/modoboa/urls_api_v1.py
index 62a5de134..0db20c44b 100644
--- a/modoboa/urls_api_v1.py
+++ b/modoboa/urls_api_v1.py
@@ -8,7 +8,7 @@ app_name = "api"
urlpatterns = [
path('', include("modoboa.core.api.v1.urls")),
- path('', include("modoboa.admin.urls_api")),
+ path('', include("modoboa.admin.api.v1.urls")),
path('', include("modoboa.limits.urls_api")),
path('', include("modoboa.relaydomains.urls_api"))
]
diff --git a/modoboa/urls_api_v2.py b/modoboa/urls_api_v2.py
index 8886a6bd3..c215bee21 100644
--- a/modoboa/urls_api_v2.py
+++ b/modoboa/urls_api_v2.py
@@ -16,7 +16,7 @@ urlpatterns = [
path('token/refresh/', TokenRefreshView.as_view(),
name='token_refresh'),
path('', include("modoboa.core.api.v2.urls")),
- path('', include("modoboa.admin.urls_api")),
+ path('', include("modoboa.admin.api.v2.urls")),
path('', include("modoboa.parameters.api.v2.urls")),
path('', include("modoboa.limits.urls_api")),
path('', include("modoboa.relaydomains.urls_api")),