@@ -52,6 +55,7 @@ import DomainDNSForm from './DomainDNSForm'
import DomainGeneralForm from './DomainGeneralForm'
import DomainLimitationsForm from './DomainLimitationsForm'
import DomainOptionsForm from './DomainOptionsForm'
+import DomainTransportForm from './DomainTransportForm'
export default {
props: ['value'],
@@ -60,7 +64,8 @@ export default {
'domain-dns-form': DomainDNSForm,
DomainGeneralForm,
DomainLimitationsForm,
- DomainOptionsForm
+ DomainOptionsForm,
+ DomainTransportForm
},
computed: {
summarySections () {
@@ -87,12 +92,6 @@ export default {
{ key: this.$gettext('Default mailbox quota'), value: this.domain.default_mailbox_quota },
{ key: this.$gettext('Message sending limit'), value: this.domain.message_sending_limit }
]
- },
- {
- title: this.$gettext('Options'),
- items: [
- { key: this.$gettext('Create a domain administrator'), value: this.createAdmin, type: 'yesno' }
- ]
}
]
if (this.domain.enable_dkim) {
@@ -100,25 +99,53 @@ export default {
result[1].items.push({ key: this.$gettext('DKIM key length'), value: this.domain.dkim_key_length })
}
if (this.createAdmin) {
- result[3].items.push({ key: this.$gettext('Administrator name'), value: this.domain.domain_admin.username })
- result[3].items.push({
- name: 'with_random_password',
- key: this.$gettext('Random password'),
- value: this.domain.domain_admin.with_random_password,
- type: 'yesno'
- })
- result[3].items.push({
- key: this.$gettext('With mailbox'),
- value: this.domain.domain_admin.with_mailbox,
- type: 'yesno'
- })
- result[3].items.push({
- key: this.$gettext('Create aliases'),
- value: this.domain.domain_admin.with_aliases,
- type: 'yesno'
+ result.push({
+ title: this.$gettext('Options'),
+ items: [
+ { key: this.$gettext('Create a domain administrator'), value: this.createAdmin, type: 'yesno' },
+ { key: this.$gettext('Administrator name'), value: this.domain.domain_admin.username },
+ {
+ name: 'with_random_password',
+ key: this.$gettext('Random password'),
+ value: this.domain.domain_admin.with_random_password,
+ type: 'yesno'
+ },
+ {
+ key: this.$gettext('With mailbox'),
+ value: this.domain.domain_admin.with_mailbox,
+ type: 'yesno'
+ },
+ {
+ key: this.$gettext('Create aliases'),
+ value: this.domain.domain_admin.with_aliases,
+ type: 'yesno'
+ }
+ ]
})
}
+ if (this.domain.type === 'relaydomain') {
+ const relayEntry = {
+ title: this.$gettext('Transport'),
+ items: [
+ { key: this.$gettext('Service'), value: this.domain.transport.service }
+ ]
+ }
+ if (this.$refs.form_4.service) {
+ const service = this.$refs.form_4.service
+ for (const setting of service.settings) {
+ const item = { key: setting.label, value: this.domain.transport.settings[`${service.name}_${setting.name}`] }
+ if (setting.type === 'boolean') {
+ item.type = 'yesno'
+ }
+ relayEntry.items.push(item)
+ }
+ }
+ result.push(relayEntry)
+ }
return result
+ },
+ steps () {
+ return this.domain.type === 'domain' ? this.domainSteps : this.relaySteps
}
},
data () {
@@ -128,11 +155,17 @@ export default {
domain_admin: {}
},
formErrors: {},
- steps: [
+ domainSteps: [
{ name: 'general', title: this.$gettext('General') },
{ name: 'dns', title: this.$gettext('DNS') },
{ name: 'limitations', title: this.$gettext('Limitations') },
{ name: 'options', title: this.$gettext('Options') }
+ ],
+ relaySteps: [
+ { name: 'general', title: this.$gettext('General') },
+ { name: 'dns', title: this.$gettext('DNS') },
+ { name: 'limitations', title: this.$gettext('Limitations') },
+ { name: 'transport', title: this.$gettext('Transport') }
]
}
},
@@ -160,7 +193,8 @@ export default {
with_random_password: false,
with_mailbox: false,
with_aliases: false
- }
+ },
+ transport: {}
}
},
close () {
diff --git a/frontend/src/components/domains/DomainList.vue b/frontend/src/components/domains/DomainList.vue
index 8890ecaf4..be5958cbd 100644
--- a/frontend/src/components/domains/DomainList.vue
+++ b/frontend/src/components/domains/DomainList.vue
@@ -42,6 +42,14 @@
{{ item.name }}
+
+ Relay
+
(disabled)
diff --git a/frontend/src/components/domains/DomainTransportForm.vue b/frontend/src/components/domains/DomainTransportForm.vue
new file mode 100644
index 000000000..a6839368c
--- /dev/null
+++ b/frontend/src/components/domains/DomainTransportForm.vue
@@ -0,0 +1,93 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/components/domains/TransportForm.vue b/frontend/src/components/domains/TransportForm.vue
new file mode 100644
index 000000000..e69de29bb
diff --git a/modoboa/admin/api/v2/serializers.py b/modoboa/admin/api/v2/serializers.py
index 9c2d77829..ca08ecb11 100644
--- a/modoboa/admin/api/v2/serializers.py
+++ b/modoboa/admin/api/v2/serializers.py
@@ -18,6 +18,8 @@ from modoboa.lib import fields as lib_fields
from modoboa.lib import validators, web_utils
from modoboa.lib.sysutils import exec_cmd
from modoboa.parameters import tools as param_tools
+from modoboa.transport import models as transport_models
+from modoboa.transport.api.v2 import serializers as transport_serializers
from ... import constants, models
@@ -35,16 +37,31 @@ class DomainSerializer(v1_serializers.DomainSerializer):
"""Domain serializer for v2 API."""
domain_admin = CreateDomainAdminSerializer(required=False)
+ transport = transport_serializers.TransportSerializer(required=False)
class Meta(v1_serializers.DomainSerializer.Meta):
fields = v1_serializers.DomainSerializer.Meta.fields + (
- "domain_admin",
+ "domain_admin", "transport"
)
+ def validate(self, data):
+ result = super().validate(data)
+ if data["type"] == "relaydomain" and not data.get("transport"):
+ raise serializers.ValidationError({"transport": _("This field is required")})
+ return result
+
def create(self, validated_data):
"""Create administrator and other stuff if needed."""
domain_admin = validated_data.pop("domain_admin", None)
+ transport_def = validated_data.pop("transport", None)
domain = super().create(validated_data)
+ if transport_def:
+ transport = transport_models.Transport(**transport_def)
+ transport.pattern = domain.name
+ transport.save()
+ domain.transport = transport
+ domain.save()
+
if not domain_admin:
return domain
diff --git a/modoboa/transport/api/__init__.py b/modoboa/transport/api/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/modoboa/transport/api/v2/__init__.py b/modoboa/transport/api/v2/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/modoboa/transport/api/v2/serializers.py b/modoboa/transport/api/v2/serializers.py
new file mode 100644
index 000000000..7ce8aa984
--- /dev/null
+++ b/modoboa/transport/api/v2/serializers.py
@@ -0,0 +1,42 @@
+"""Transport related serializers."""
+
+from rest_framework import serializers
+
+from ... import backends
+from ... import models
+
+
+class TransportBackendSettingSerializer(serializers.Serializer):
+ """Serializer for transport backend setting."""
+
+ name = serializers.CharField()
+ label = serializers.CharField()
+ type = serializers.CharField(default="str")
+ required = serializers.BooleanField(default=True)
+ default = serializers.CharField(default=None)
+
+
+class TransportBackendSerializer(serializers.Serializer):
+ """Serializer for transport backend."""
+
+ name = serializers.CharField()
+ settings = TransportBackendSettingSerializer(many=True)
+
+
+class TransportSerializer(serializers.ModelSerializer):
+ """Serializer for Transport model."""
+
+ settings = serializers.DictField(source="_settings")
+
+ class Meta:
+ fields = ("service", "settings")
+ model = models.Transport
+
+ def validate(self, data):
+ self.backend = backends.manager.get_backend(data["service"])
+ errors = {}
+ for field, error in self.backend.clean_fields(data["_settings"]):
+ errors[field] = error
+ if errors:
+ raise serializers.ValidationError(errors)
+ return data
diff --git a/modoboa/transport/api/v2/urls.py b/modoboa/transport/api/v2/urls.py
new file mode 100644
index 000000000..816410035
--- /dev/null
+++ b/modoboa/transport/api/v2/urls.py
@@ -0,0 +1,12 @@
+"""App related urls."""
+
+from rest_framework import routers
+
+from . import viewsets
+
+
+router = routers.SimpleRouter()
+router.register(
+ r"transports", viewsets.TransportViewSet, basename="transport")
+
+urlpatterns = router.urls
diff --git a/modoboa/transport/api/v2/viewsets.py b/modoboa/transport/api/v2/viewsets.py
new file mode 100644
index 000000000..59cd4cdca
--- /dev/null
+++ b/modoboa/transport/api/v2/viewsets.py
@@ -0,0 +1,25 @@
+"""Transport related viewsets."""
+
+from drf_spectacular.utils import extend_schema
+from rest_framework import permissions, response, viewsets
+
+from . import serializers
+from ... import backends
+
+
+class TransportViewSet(viewsets.ViewSet):
+ """Viewset for Transport."""
+
+ permissions = (permissions.IsAuthenticated, )
+
+ @extend_schema(
+ responses={200: serializers.TransportBackendSerializer}
+ )
+ def list(self, request, *args, **kwargs):
+ """Return the list of all defined transport backends."""
+ all_settings = backends.manager.get_all_backend_settings()
+ result = []
+ for name, settings in all_settings.items():
+ serializer = serializers.TransportBackendSerializer({"name": name, "settings": settings})
+ result.append(serializer.data)
+ return response.Response(result)
diff --git a/modoboa/urls_api_v2.py b/modoboa/urls_api_v2.py
index 665cd5155..b6376b088 100644
--- a/modoboa/urls_api_v2.py
+++ b/modoboa/urls_api_v2.py
@@ -14,6 +14,7 @@ urlpatterns = [
path('', include("modoboa.relaydomains.api.v1.urls")),
path('', include("modoboa.dnstools.api.v2.urls")),
path('', include("modoboa.maillog.api.v2.urls")),
+ path('', include("modoboa.transport.api.v2.urls")),
]
urlpatterns += exts_pool.get_urls(category="api")
|