modoboa 2.4.8__py3-none-any.whl → 2.4.10__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (162) hide show
  1. modoboa/admin/api/v1/serializers.py +7 -1
  2. modoboa/admin/api/v2/serializers.py +9 -2
  3. modoboa/admin/api/v2/tests.py +5 -3
  4. modoboa/admin/api/v2/viewsets.py +12 -10
  5. modoboa/core/api/v2/serializers.py +34 -15
  6. modoboa/core/api/v2/tests.py +58 -10
  7. modoboa/core/api/v2/viewsets.py +32 -23
  8. modoboa/core/app_settings.py +1 -1
  9. modoboa/core/management/commands/load_initial_data.py +25 -10
  10. modoboa/core/migrations/0025_rename_user_email_is_active_core_user_email_c0c03f_idx.py +5 -5
  11. modoboa/core/tests/test_core.py +47 -6
  12. modoboa/frontend_dist/assets/{AccountAliasForm-BkmO1XF4.js → AccountAliasForm-DVXatAhB.js} +1 -1
  13. modoboa/frontend_dist/assets/{AccountEditView-BDQ_CECV.js → AccountEditView-DmvQjxpx.js} +1 -1
  14. modoboa/frontend_dist/assets/AccountLayout-OGtZvlHR.js +1 -0
  15. modoboa/frontend_dist/assets/{AccountPasswordSubForm-bJ0vggm0.js → AccountPasswordSubForm-g3IEGrgM.js} +1 -1
  16. modoboa/frontend_dist/assets/{AccountView-zVZjR-hV.js → AccountView-DsxYqr3k.js} +1 -1
  17. modoboa/frontend_dist/assets/{AddressBook-lIXK7nkB.js → AddressBook-3RoKiKon.js} +1 -1
  18. modoboa/frontend_dist/assets/AdminLayout-CWfn8zaQ.js +1 -0
  19. modoboa/frontend_dist/assets/AlarmsView-D3Mh8ntf.js +1 -0
  20. modoboa/frontend_dist/assets/{AliasEditView-BmdncwjA.js → AliasEditView-C15eUZ11.js} +1 -1
  21. modoboa/frontend_dist/assets/{AliasRecipientForm-uNOU6Es3.js → AliasRecipientForm-DVZXWaUX.js} +1 -1
  22. modoboa/frontend_dist/assets/{AliasView-CMagk14H.js → AliasView-Cyvc5vMb.js} +1 -1
  23. modoboa/frontend_dist/assets/{AuditTrailView-BHBh-Gnb.js → AuditTrailView-LI2XuLLn.js} +1 -1
  24. modoboa/frontend_dist/assets/{CalendarView-Bt7WsWZj.js → CalendarView-BpOlPh3f.js} +1 -1
  25. modoboa/frontend_dist/assets/{ChoiceField-DT-S7jc3.js → ChoiceField-7eU7c_rI.js} +1 -1
  26. modoboa/frontend_dist/assets/{ComposeEmailForm-CMmmKn24.js → ComposeEmailForm-CNfI7ept.js} +1 -1
  27. modoboa/frontend_dist/assets/ComposeEmailView-B866Xsrc.js +1 -0
  28. modoboa/frontend_dist/assets/{ConfirmDialog-DpaDmFAr.js → ConfirmDialog-BvqxQsD1.js} +1 -1
  29. modoboa/frontend_dist/assets/ConnectedLayout-BQ3ug6Jh.js +1 -0
  30. modoboa/frontend_dist/assets/{ConnectedLayout-AhwwAZvE.css → ConnectedLayout-BaJZ3BeR.css} +1 -1
  31. modoboa/frontend_dist/assets/CreationForm-C9Kh05ax.js +1 -0
  32. modoboa/frontend_dist/assets/CreationForm-HaiQhqwK.css +1 -0
  33. modoboa/frontend_dist/assets/{DashboardView-DfQoFJFl.css → DashboardView-BLlMi6Qb.css} +1 -1
  34. modoboa/frontend_dist/assets/DashboardView-Cw-gIcuB.js +1 -0
  35. modoboa/frontend_dist/assets/{DomainAdminList-DzeKY_wp.js → DomainAdminList-CsWUNKVk.js} +1 -1
  36. modoboa/frontend_dist/assets/DomainEditView-DocxeOeW.js +1 -0
  37. modoboa/frontend_dist/assets/DomainTransportForm-DPnPGBOp.js +1 -0
  38. modoboa/frontend_dist/assets/{DomainView-DCfSLvj-.css → DomainView-BDKoBFYr.css} +1 -1
  39. modoboa/frontend_dist/assets/DomainView-Djc_0PsF.js +5 -0
  40. modoboa/frontend_dist/assets/DomainsView-B-Lxru7P.js +1 -0
  41. modoboa/frontend_dist/assets/{DomainsView-BUgJLN9o.css → DomainsView-DasJ0NdZ.css} +1 -1
  42. modoboa/frontend_dist/assets/{EmailField-5oAk_8mr.js → EmailField-BEKxuYni.js} +1 -1
  43. modoboa/frontend_dist/assets/{EmailView-CHtw29LH.js → EmailView-BsR1Wes5.js} +1 -1
  44. modoboa/frontend_dist/assets/EmptyLayout-BzPFOeLU.js +1 -0
  45. modoboa/frontend_dist/assets/{FiltersView-CLZD8Zeu.js → FiltersView-5rmpC5cC.js} +1 -1
  46. modoboa/frontend_dist/assets/ForwardEmailView-D7MbetcT.js +1 -0
  47. modoboa/frontend_dist/assets/{HtmlEditor-Bothw4PW.js → HtmlEditor-uM4AtIGi.js} +1 -1
  48. modoboa/frontend_dist/assets/IdentitiesView-BqjD9Lue.js +1 -0
  49. modoboa/frontend_dist/assets/IdentitiesView-jmuItyMZ.css +1 -0
  50. modoboa/frontend_dist/assets/{InformationView-DV9jmCfk.js → InformationView-CghcvPn2.js} +1 -1
  51. modoboa/frontend_dist/assets/{LoadingData-DHVSExRd.js → LoadingData-CVD2Aen8.js} +1 -1
  52. modoboa/frontend_dist/assets/{LoginCallbackView-CKVmv2U7.js → LoginCallbackView-sWzBke1g.js} +1 -1
  53. modoboa/frontend_dist/assets/{LoginView-BM4hh8GD.js → LoginView-wmN73W0f.js} +1 -1
  54. modoboa/frontend_dist/assets/{MailboxView-BFT6E7QZ.js → MailboxView-T_p-_ZtJ.js} +1 -1
  55. modoboa/frontend_dist/assets/{MenuItems-DfTqHL7X.js → MenuItems-kHCMzR5E.js} +1 -1
  56. modoboa/frontend_dist/assets/{MessagesView-DdJLcDON.js → MessagesView-Cerv3xsy.js} +1 -1
  57. modoboa/frontend_dist/assets/{MigrationsView-CUmfnPuD.js → MigrationsView-7kjqPyYU.js} +1 -1
  58. modoboa/frontend_dist/assets/{ParametersForm-DpxcA8Ib.js → ParametersForm-BCeQljir.js} +1 -1
  59. modoboa/frontend_dist/assets/ParametersView-DspBxVMV.js +1 -0
  60. modoboa/frontend_dist/assets/ParametersView-Dy0H5ep1.js +1 -0
  61. modoboa/frontend_dist/assets/{ProviderEditView-DX4PGAI_.js → ProviderEditView-DDLMOylC.js} +1 -1
  62. modoboa/frontend_dist/assets/{ProviderGeneralForm-Bn5Bb3Qn.js → ProviderGeneralForm-BwOSKNHK.js} +1 -1
  63. modoboa/frontend_dist/assets/{ProvidersView-BbwFDjef.js → ProvidersView-C99UD0WB.js} +1 -1
  64. modoboa/frontend_dist/assets/ReplyEmailView-DAPBHldd.js +1 -0
  65. modoboa/frontend_dist/assets/{ResourcesForm-DmXW37Xr.js → ResourcesForm-D87PHeH0.js} +1 -1
  66. modoboa/frontend_dist/assets/{SettingsView-CDZADq3D.js → SettingsView-OxDo9wNd.js} +2 -2
  67. modoboa/frontend_dist/assets/{StatisticsView-SQdOdBYh.js → StatisticsView-CM__Eqku.js} +1 -1
  68. modoboa/frontend_dist/assets/{TimeSerieChart-D2qkZzVF.js → TimeSerieChart-C6j0uYR_.js} +1 -1
  69. modoboa/frontend_dist/assets/UserLayout-CckCGnPS.js +1 -0
  70. modoboa/frontend_dist/assets/{VAlert-CEP3cFn3.js → VAlert-B7mzOJIO.js} +1 -1
  71. modoboa/frontend_dist/assets/{VApp-DPJ3HTOA.js → VApp-BKxnjOoi.js} +1 -1
  72. modoboa/frontend_dist/assets/{VAutocomplete-CNumFmgr.js → VAutocomplete-Cc4_tcl1.js} +1 -1
  73. modoboa/frontend_dist/assets/{VAvatar-BlQCGAjd.js → VAvatar-BAgTUIvX.js} +1 -1
  74. modoboa/frontend_dist/assets/{VCard-_Wrwy8vE.js → VCard-DFWiFORP.js} +1 -1
  75. modoboa/frontend_dist/assets/{VCheckbox-2el3v3GD.js → VCheckbox-CKsH_vq3.js} +1 -1
  76. modoboa/frontend_dist/assets/{VCheckboxBtn-hftATz3z.js → VCheckboxBtn-j7di4leN.js} +1 -1
  77. modoboa/frontend_dist/assets/{VChip-YJr5NpVo.js → VChip-nZ0uhY7t.js} +1 -1
  78. modoboa/frontend_dist/assets/{VColorPicker-BeyI--Be.js → VColorPicker-Os2aeP6J.js} +1 -1
  79. modoboa/frontend_dist/assets/VContainer-Btam4lk2.js +1 -0
  80. modoboa/frontend_dist/assets/{VDataTable-DEL5kb0r.js → VDataTable-D_0_xJTl.js} +1 -1
  81. modoboa/frontend_dist/assets/{VDataTableServer-BSjI3T24.js → VDataTableServer-BWTt4Mzi.js} +1 -1
  82. modoboa/frontend_dist/assets/{VDataTableVirtual-CAmjoZpK.js → VDataTableVirtual-BwVmkt4u.js} +1 -1
  83. modoboa/frontend_dist/assets/{VDialog-BNvBrVUr.js → VDialog-CZqM2Ofu.js} +1 -1
  84. modoboa/frontend_dist/assets/{VExpansionPanels-b3QiyPDE.js → VExpansionPanels-B5D6GOa3.js} +1 -1
  85. modoboa/frontend_dist/assets/{VFileInput-B_0qadyu.js → VFileInput-Cv9DIPki.js} +1 -1
  86. modoboa/frontend_dist/assets/{VForm-BZayccL6.js → VForm-CpoZf60D.js} +1 -1
  87. modoboa/frontend_dist/assets/{VMenu-CTWHnLTn.js → VMenu-B_dVqOmo.js} +1 -1
  88. modoboa/frontend_dist/assets/{VPicker-jsHPuz0p.js → VPicker-CXkIGEze.js} +1 -1
  89. modoboa/frontend_dist/assets/{VProgressCircular-Bnqidj3C.js → VProgressCircular-CrEXxs7k.js} +1 -1
  90. modoboa/frontend_dist/assets/{VRadioGroup-s4-ckZ2k.js → VRadioGroup-D8ypjYOO.js} +1 -1
  91. modoboa/frontend_dist/assets/{VRow-Feh4EzZW.js → VRow-C_Ydf6yr.js} +1 -1
  92. modoboa/frontend_dist/assets/{VSelect-NMZYMJds.js → VSelect-CS51PDEt.js} +1 -1
  93. modoboa/frontend_dist/assets/{VSelectionControl-DzdbYleR.js → VSelectionControl-DiOqtY38.js} +1 -1
  94. modoboa/frontend_dist/assets/VSheet-5VVWtHvs.js +1 -0
  95. modoboa/frontend_dist/assets/VSpacer-C6PZ3X24.js +1 -0
  96. modoboa/frontend_dist/assets/{VSwitch-BNTZUUIk.js → VSwitch-Chg5o-Cp.js} +1 -1
  97. modoboa/frontend_dist/assets/{VTable-DKtDp0hU.js → VTable-KsiZ3cBz.js} +1 -1
  98. modoboa/frontend_dist/assets/{VTabs-DgRFLQFX.js → VTabs-tNrJIYO0.js} +1 -1
  99. modoboa/frontend_dist/assets/{VTextField-B1sRHgeW.js → VTextField-CLwRV0Cb.js} +1 -1
  100. modoboa/frontend_dist/assets/{VTextarea-B-FbARtV.js → VTextarea-Di8jbl8m.js} +1 -1
  101. modoboa/frontend_dist/assets/{VToolbar-BYYuEslS.js → VToolbar-CGwhgdmI.js} +1 -1
  102. modoboa/frontend_dist/assets/{VWindowItem-ZP4a7tq_.js → VWindowItem-wWSFAGI-.js} +1 -1
  103. modoboa/frontend_dist/assets/{WebmailLayout-DXdBsd-A.js → WebmailLayout-DsYThBrz.js} +1 -1
  104. modoboa/frontend_dist/assets/{admin-Crbc8EzJ.js → admin-CJVLMHh9.js} +1 -1
  105. modoboa/frontend_dist/assets/{aliases-DLwQ9O5G.js → aliases-c3n-dCV_.js} +1 -1
  106. modoboa/frontend_dist/assets/{contacts-yHT-CIHj.js → contacts-Bqckz8sr.js} +1 -1
  107. modoboa/frontend_dist/assets/{domains-_NaRU-qe.js → domains-nBMR-fRf.js} +1 -1
  108. modoboa/frontend_dist/assets/{domains.store-DOrci7Lr.js → domains.store-ChZgLcqP.js} +1 -1
  109. modoboa/frontend_dist/assets/{filter-_-hv7dYy.js → filter-D7NrAf6a.js} +1 -1
  110. modoboa/frontend_dist/assets/{forwardRefs-BySF_L7N.js → forwardRefs-Cpc3YYl6.js} +1 -1
  111. modoboa/frontend_dist/assets/{global.store-CkyRP6fP.js → global.store-DUP26-A5.js} +1 -1
  112. modoboa/frontend_dist/assets/importExport-Dn9vYw7T.js +1 -0
  113. modoboa/frontend_dist/assets/{index-BQtPj9kp.js → index-DV9Li2cg.js} +19 -19
  114. modoboa/frontend_dist/assets/{index-DzLisnN3.js → index-DzL89N4E.js} +1 -1
  115. modoboa/frontend_dist/assets/layout-CHO37cA6.js +1 -0
  116. modoboa/frontend_dist/assets/{layout.store-D1RpozRo.js → layout.store-BxBoBlgf.js} +1 -1
  117. modoboa/frontend_dist/assets/{logs-O2XN95C5.js → logs-DrTzylW7.js} +1 -1
  118. modoboa/frontend_dist/assets/{parameters-BlgF3Z86.js → parameters-Lgiqp7aw.js} +1 -1
  119. modoboa/frontend_dist/assets/{parameters.store-BeAYoSBO.js → parameters.store-C9k9DuTj.js} +1 -1
  120. modoboa/frontend_dist/assets/{permissions-CZTQi1hx.js → permissions-mL5hLHcW.js} +1 -1
  121. modoboa/frontend_dist/assets/{ssrBoot-D7JTzupl.js → ssrBoot-BsxW6uW4.js} +1 -1
  122. modoboa/frontend_dist/assets/{tag-DczKoIjT.js → tag-CFK9dzMJ.js} +1 -1
  123. modoboa/frontend_dist/assets/transports-TO08iTXJ.js +1 -0
  124. modoboa/frontend_dist/assets/{webmail-Dk1MjSSb.js → webmail-KrD8ZhNM.js} +1 -1
  125. modoboa/frontend_dist/config.json +1 -1
  126. modoboa/frontend_dist/index.html +1 -1
  127. modoboa/maillog/management/commands/logparser.py +1 -0
  128. modoboa/pdfcredentials/api/v2/serializers.py +17 -5
  129. modoboa/pdfcredentials/api/v2/tests.py +28 -0
  130. modoboa/pdfcredentials/app_settings.py +1 -1
  131. {modoboa-2.4.8.dist-info → modoboa-2.4.10.dist-info}/METADATA +2 -2
  132. {modoboa-2.4.8.dist-info → modoboa-2.4.10.dist-info}/RECORD +137 -136
  133. modoboa/frontend_dist/assets/AccountLayout-CdO0SiyN.js +0 -1
  134. modoboa/frontend_dist/assets/AdminLayout-lq_An0r_.js +0 -1
  135. modoboa/frontend_dist/assets/AlarmsView-BAiihAQo.js +0 -1
  136. modoboa/frontend_dist/assets/ComposeEmailView-DCktvQ_Q.js +0 -1
  137. modoboa/frontend_dist/assets/ConnectedLayout-C9ym17MK.js +0 -1
  138. modoboa/frontend_dist/assets/CreationForm-BvFT2siw.js +0 -1
  139. modoboa/frontend_dist/assets/CreationForm-DLumMdUy.css +0 -1
  140. modoboa/frontend_dist/assets/DashboardView-B-2znOCL.js +0 -1
  141. modoboa/frontend_dist/assets/DomainEditView-BXkqO64o.js +0 -1
  142. modoboa/frontend_dist/assets/DomainTransportForm-DgCR3Vtg.js +0 -1
  143. modoboa/frontend_dist/assets/DomainView-CwqRXXjc.js +0 -5
  144. modoboa/frontend_dist/assets/DomainsView-7RpzOtVN.js +0 -1
  145. modoboa/frontend_dist/assets/EmptyLayout-DjwbFWme.js +0 -1
  146. modoboa/frontend_dist/assets/ForwardEmailView-BqD8yMYF.js +0 -1
  147. modoboa/frontend_dist/assets/IdentitiesView-CeqGJ0Dz.js +0 -1
  148. modoboa/frontend_dist/assets/IdentitiesView-cvIq8q24.css +0 -1
  149. modoboa/frontend_dist/assets/ParametersView-CVc4kkYy.js +0 -1
  150. modoboa/frontend_dist/assets/ParametersView-Dwy8L1wH.js +0 -1
  151. modoboa/frontend_dist/assets/ReplyEmailView-DnaqKPIc.js +0 -1
  152. modoboa/frontend_dist/assets/UserLayout-BfuBes96.js +0 -1
  153. modoboa/frontend_dist/assets/VContainer-CFkETYWT.js +0 -1
  154. modoboa/frontend_dist/assets/VSheet-DHWcony1.js +0 -1
  155. modoboa/frontend_dist/assets/VSpacer-C9mNCXnQ.js +0 -1
  156. modoboa/frontend_dist/assets/importExport-BdyTHtRb.js +0 -1
  157. modoboa/frontend_dist/assets/layout-Dlgl7jya.js +0 -1
  158. {modoboa-2.4.8.data → modoboa-2.4.10.data}/scripts/modoboa-admin.py +0 -0
  159. {modoboa-2.4.8.dist-info → modoboa-2.4.10.dist-info}/WHEEL +0 -0
  160. {modoboa-2.4.8.dist-info → modoboa-2.4.10.dist-info}/entry_points.txt +0 -0
  161. {modoboa-2.4.8.dist-info → modoboa-2.4.10.dist-info}/licenses/LICENSE +0 -0
  162. {modoboa-2.4.8.dist-info → modoboa-2.4.10.dist-info}/top_level.txt +0 -0
@@ -68,12 +68,18 @@ class DomainSerializer(serializers.ModelSerializer):
68
68
  "message_limit",
69
69
  "creation",
70
70
  "last_modification",
71
+ "allocated_quota_in_percent",
72
+ "mailbox_count",
73
+ "mbalias_count",
74
+ "domainalias_count",
75
+ "enable_dns_checks",
71
76
  )
72
77
  read_only_fields = (
73
78
  "pk",
74
79
  "dkim_public_key",
75
80
  "dns_global_status",
76
- "allocated_quota_in_percent" "mailbox_count",
81
+ "allocated_quota_in_percent",
82
+ "mailbox_count",
77
83
  "mbalias_count",
78
84
  "domainalias_count",
79
85
  "enable_dns_checks",
@@ -61,6 +61,8 @@ class DomainSerializer(v1_serializers.DomainSerializer):
61
61
  "domain_admin",
62
62
  "transport",
63
63
  "opened_alarms_count",
64
+ "sent_messages",
65
+ "sent_messages_in_percent",
64
66
  )
65
67
 
66
68
  def __init__(self, *args, **kwargs):
@@ -221,9 +223,9 @@ class AdminGlobalParametersSerializer(serializers.Serializer):
221
223
  enable_dkim_checks = serializers.BooleanField(default=True)
222
224
  enable_dmarc_checks = serializers.BooleanField(default=True)
223
225
  enable_autoconfig_checks = serializers.BooleanField(default=True)
224
- custom_dns_server = serializers.IPAddressField(allow_blank=True)
226
+ custom_dns_server = serializers.IPAddressField(allow_blank=True, allow_null=True)
225
227
  enable_dnsbl_checks = serializers.BooleanField(default=True)
226
- dkim_keys_storage_dir = serializers.CharField(allow_blank=True)
228
+ dkim_keys_storage_dir = serializers.CharField(allow_blank=True, allow_null=True)
227
229
  dkim_default_key_length = serializers.ChoiceField(
228
230
  default=2048, choices=constants.DKIM_KEY_LENGTHS
229
231
  )
@@ -741,6 +743,11 @@ class AlarmSerializer(serializers.ModelSerializer):
741
743
  model = models.Alarm
742
744
 
743
745
 
746
+ class AlarmBulkDeleteSerializer(serializers.Serializer):
747
+
748
+ ids = serializers.ListField(child=serializers.IntegerField())
749
+
750
+
744
751
  class AlarmSwitchStatusSerializer(serializers.Serializer):
745
752
  """Serializer to switch the status of an Alarm."""
746
753
 
@@ -806,9 +806,10 @@ class AlarmViewSetTestCase(ModoAPITestCase):
806
806
 
807
807
  def test_bulk_delete(self):
808
808
  url = reverse("v2:alarm-bulk-delete")
809
- resp = self.client.delete(url)
809
+ resp = self.client.post(url)
810
810
  self.assertEqual(resp.status_code, 400)
811
- resp = self.client.delete(f"{url}?ids[]=toto")
811
+ data = {"ids": ["toto"]}
812
+ resp = self.client.post(url, data, format="json")
812
813
  self.assertEqual(resp.status_code, 400)
813
814
  alarm1 = factories.AlarmFactory(
814
815
  domain__name="test.com", mailbox=None, title="Test alarm"
@@ -816,7 +817,8 @@ class AlarmViewSetTestCase(ModoAPITestCase):
816
817
  alarm2 = factories.AlarmFactory(
817
818
  domain__name="test.com", mailbox=None, title="Test alarm"
818
819
  )
819
- resp = self.client.delete(f"{url}?ids[]={alarm1.pk}&ids[]={alarm2.pk}")
820
+ data = {"ids": [alarm1.pk, alarm2.pk]}
821
+ resp = self.client.post(url, data, format="json")
820
822
  self.assertEqual(resp.status_code, 204)
821
823
  with self.assertRaises(models.Alarm.DoesNotExist):
822
824
  alarm1.refresh_from_db()
@@ -387,11 +387,18 @@ class AlarmViewSet(
387
387
  .order_by("-created")
388
388
  )
389
389
 
390
+ def get_serializer_class(self, *args, **kwargs):
391
+ if self.action == "bulk_delete":
392
+ return serializers.AlarmBulkDeleteSerializer
393
+ elif self.action == "switch":
394
+ return serializers.AlarmSwitchStatusSerializer
395
+ return serializers.AlarmSerializer
396
+
390
397
  @action(methods=["patch"], detail=True)
391
398
  def switch(self, request, **kwargs):
392
399
  """Custom update method that switch status of an alarm."""
393
400
  alarm = self.get_object()
394
- serializer = serializers.AlarmSwitchStatusSerializer(data=request.data)
401
+ serializer = self.get_serializer(data=request.data)
395
402
  serializer.is_valid(raise_exception=True)
396
403
  if serializer.data["status"] == constants.ALARM_CLOSED:
397
404
  alarm.close()
@@ -399,15 +406,10 @@ class AlarmViewSet(
399
406
  alarm.reopen()
400
407
  return response.Response(status=status.HTTP_204_NO_CONTENT)
401
408
 
402
- @action(methods=["delete"], detail=False)
409
+ @action(methods=["post"], detail=False)
403
410
  def bulk_delete(self, request, **kwargs):
404
411
  """Delete multiple alarms at the same time."""
405
- ids = request.query_params.getlist("ids[]")
406
- if not ids:
407
- return response.Response(_("No alarm ID provided"), status=400)
408
- try:
409
- ids = [int(alarm_id) for alarm_id in ids]
410
- except ValueError:
411
- return response.Response(_("Received invalid alarm id(s)"), status=400)
412
- models.Alarm.objects.filter(pk__in=ids).delete()
412
+ serializer = self.get_serializer(data=request.data)
413
+ serializer.is_valid(raise_exception=True)
414
+ models.Alarm.objects.filter(pk__in=serializer.validated_data["ids"]).delete()
413
415
  return response.Response(status=204)
@@ -76,8 +76,12 @@ class CoreGlobalParametersSerializer(serializers.Serializer):
76
76
  default_password = serializers.CharField(default="ChangeMe1!")
77
77
  random_password_length = serializers.IntegerField(min_value=8, default=8)
78
78
  allow_special_characters = serializers.BooleanField(default=False)
79
- update_password_url = serializers.URLField(required=False, allow_blank=True)
80
- password_recovery_msg = serializers.CharField(required=False, allow_blank=True)
79
+ update_password_url = serializers.URLField(
80
+ required=False, allow_blank=True, allow_null=True
81
+ )
82
+ password_recovery_msg = serializers.CharField(
83
+ required=False, allow_blank=True, allow_null=True
84
+ )
81
85
  sms_password_recovery = serializers.BooleanField(default=False)
82
86
  sms_provider = serializers.ChoiceField(
83
87
  choices=constants.SMS_BACKENDS, required=False, allow_null=True
@@ -87,7 +91,9 @@ class CoreGlobalParametersSerializer(serializers.Serializer):
87
91
  ldap_server_address = serializers.CharField(default="localhost")
88
92
  ldap_server_port = serializers.IntegerField(default=389)
89
93
  ldap_enable_secondary_server = serializers.BooleanField(default=False)
90
- ldap_secondary_server_address = serializers.CharField(required=False)
94
+ ldap_secondary_server_address = serializers.CharField(
95
+ required=False, allow_blank=True, allow_null=True
96
+ )
91
97
  ldap_secondary_server_port = serializers.IntegerField(default=389, required=False)
92
98
  ldap_secured = serializers.ChoiceField(
93
99
  choices=constants.LDAP_SECURE_MODES, default="none"
@@ -109,9 +115,11 @@ class CoreGlobalParametersSerializer(serializers.Serializer):
109
115
  choices=constants.LDAP_AUTH_METHODS,
110
116
  default="searchbind",
111
117
  )
112
- ldap_bind_dn = serializers.CharField(default="", required=False, allow_blank=True)
118
+ ldap_bind_dn = serializers.CharField(
119
+ default="", required=False, allow_blank=True, allow_null=True
120
+ )
113
121
  ldap_bind_password = serializers.CharField(
114
- default="", required=False, allow_blank=True
122
+ default="", required=False, allow_blank=True, allow_null=True
115
123
  )
116
124
  ldap_search_base = serializers.CharField(
117
125
  default="", required=False, allow_blank=True
@@ -124,15 +132,21 @@ class CoreGlobalParametersSerializer(serializers.Serializer):
124
132
  )
125
133
 
126
134
  # LDAP sync settings
127
- ldap_sync_bind_dn = serializers.CharField(required=False, allow_blank=True)
128
- ldap_sync_bind_password = serializers.CharField(required=False, allow_blank=True)
135
+ ldap_sync_bind_dn = serializers.CharField(
136
+ required=False, allow_blank=True, allow_null=True
137
+ )
138
+ ldap_sync_bind_password = serializers.CharField(
139
+ required=False, allow_blank=True, allow_null=True
140
+ )
129
141
  ldap_enable_sync = serializers.BooleanField(default=False)
130
142
  ldap_sync_delete_remote_account = serializers.BooleanField(default=False)
131
143
  ldap_sync_account_dn_template = serializers.CharField(
132
- required=False, allow_blank=True
144
+ required=False, allow_blank=True, allow_null=True
133
145
  )
134
146
  ldap_enable_import = serializers.BooleanField(default=False)
135
- ldap_import_search_base = serializers.CharField(required=False, allow_blank=True)
147
+ ldap_import_search_base = serializers.CharField(
148
+ required=False, allow_blank=True, allow_null=True
149
+ )
136
150
  ldap_import_search_filter = serializers.CharField(default="(cn=*)", required=False)
137
151
  ldap_import_username_attr = serializers.CharField(default="cn")
138
152
  ldap_dovecot_sync = serializers.BooleanField(default=False)
@@ -141,7 +155,9 @@ class CoreGlobalParametersSerializer(serializers.Serializer):
141
155
  )
142
156
 
143
157
  # Dashboard settings
144
- custom_welcome_message = serializers.CharField(required=False, allow_blank=True)
158
+ custom_welcome_message = serializers.CharField(
159
+ required=False, allow_blank=True, allow_null=True
160
+ )
145
161
  rss_feed_url = serializers.URLField(
146
162
  allow_blank=True, required=False, allow_null=True
147
163
  )
@@ -162,7 +178,9 @@ class CoreGlobalParametersSerializer(serializers.Serializer):
162
178
  enable_api_communication = serializers.BooleanField(default=True)
163
179
  check_new_versions = serializers.BooleanField(default=True)
164
180
  send_new_versions_email = serializers.BooleanField(default=False)
165
- new_versions_email_rcpt = lib_fields.DRFEmailFieldUTF8(required=False)
181
+ new_versions_email_rcpt = lib_fields.DRFEmailFieldUTF8(
182
+ required=False, allow_null=True
183
+ )
166
184
  send_statistics = serializers.BooleanField(default=True)
167
185
 
168
186
  # Misc settings
@@ -195,10 +213,11 @@ class CoreGlobalParametersSerializer(serializers.Serializer):
195
213
  return value
196
214
 
197
215
  def validate_ldap_sync_account_dn_template(self, value):
198
- try:
199
- value % {"user": "toto"}
200
- except (KeyError, ValueError):
201
- raise serializers.ValidationError(_("Invalid syntax")) from None
216
+ if value:
217
+ try:
218
+ value % {"user": "toto"}
219
+ except (KeyError, ValueError):
220
+ raise serializers.ValidationError(_("Invalid syntax")) from None
202
221
  return value
203
222
 
204
223
  def validate_ldap_search_filter(self, value):
@@ -19,7 +19,6 @@ from modoboa.admin import (
19
19
  from modoboa.core import models, constants
20
20
  from modoboa.core.tests import utils
21
21
  from modoboa.lib.tests import ModoAPITestCase
22
- from rest_framework.authtoken.models import Token
23
22
 
24
23
  DOVEADM_TEST_PATH = utils.get_doveadm_test_path()
25
24
  DOVECOT_USER = getpass.getuser()
@@ -183,6 +182,13 @@ class ParametersAPITestCase(ModoAPITestCase):
183
182
 
184
183
 
185
184
  class AccountViewSetTestCase(ModoAPITestCase):
185
+
186
+ @classmethod
187
+ def setUpTestData(cls): # NOQA:N802
188
+ """Create test data."""
189
+ super().setUpTestData()
190
+ factories.populate_database()
191
+
186
192
  def test_me(self):
187
193
  url = reverse("v2:account-me")
188
194
  resp = self.client.get(url)
@@ -261,6 +267,57 @@ class AccountViewSetTestCase(ModoAPITestCase):
261
267
  user.refresh_from_db()
262
268
  self.assertEqual(user.totp_enabled, False)
263
269
 
270
+ def test_available_applications(self):
271
+ url = reverse("v2:account-available-applications")
272
+ resp = self.client.get(url)
273
+ self.assertEqual(resp.status_code, 200)
274
+ # admin -> only 1 app.
275
+ self.assertEqual(len(resp.json()), 1)
276
+
277
+ # Domain admin with mailbox
278
+ dadmin = models.User.objects.get(username="admin@test.com")
279
+ self.authenticate_user(dadmin)
280
+ resp = self.client.get(url)
281
+ self.assertEqual(resp.status_code, 200)
282
+ self.assertEqual(len(resp.json()), 4)
283
+
284
+ # Simple user
285
+ user = models.User.objects.get(username="user@test.com")
286
+ self.authenticate_user(user)
287
+ resp = self.client.get(url)
288
+ self.assertEqual(resp.status_code, 200)
289
+ self.assertEqual(len(resp.json()), 3)
290
+
291
+ @override_settings(
292
+ MODOBOA_APPS=[
293
+ "modoboa",
294
+ "modoboa.core",
295
+ "modoboa.lib",
296
+ "modoboa.admin",
297
+ "modoboa.transport",
298
+ "modoboa.relaydomains",
299
+ "modoboa.limits",
300
+ "modoboa.parameters",
301
+ "modoboa.dnstools",
302
+ "modoboa.policyd",
303
+ "modoboa.maillog",
304
+ "modoboa.pdfcredentials",
305
+ "modoboa.dmarc",
306
+ "modoboa.imap_migration",
307
+ "modoboa.autoreply",
308
+ "modoboa.sievefilters",
309
+ "modoboa.rspamd",
310
+ ]
311
+ )
312
+ def test_available_applications_with_disabled(self):
313
+ url = reverse("v2:account-available-applications")
314
+ # Domain admin with mailbox
315
+ dadmin = models.User.objects.get(username="admin@test.com")
316
+ self.authenticate_user(dadmin)
317
+ resp = self.client.get(url)
318
+ self.assertEqual(resp.status_code, 200)
319
+ self.assertEqual(len(resp.json()), 1)
320
+
264
321
 
265
322
  class PasswordResetTestCase(AccountViewSetTestCase):
266
323
  def __init__(self, *args, **kwargs):
@@ -268,15 +325,6 @@ class PasswordResetTestCase(AccountViewSetTestCase):
268
325
  self.id = 0
269
326
  self.sms_token = 0
270
327
 
271
- @classmethod
272
- def setUpTestData(cls): # NOQA:N802
273
- """Create test data."""
274
- super().setUpTestData()
275
- factories.populate_database()
276
- cls.da_token = Token.objects.create(
277
- user=models.User.objects.get(username="admin@test.com")
278
- )
279
-
280
328
  @mock.patch("ovh.Client.get")
281
329
  @mock.patch("ovh.Client.post")
282
330
  def test_reset_password(self, client_post, client_get):
@@ -1,5 +1,6 @@
1
1
  """Core API v2 viewsets."""
2
2
 
3
+ from django.conf import settings
3
4
  from django.utils.translation import gettext as _
4
5
 
5
6
  from django_otp.plugins.otp_static.models import StaticDevice, StaticToken
@@ -166,29 +167,37 @@ class AccountViewSet(core_v1_viewsets.AccountViewSet):
166
167
  }
167
168
  )
168
169
  if hasattr(request.user, "mailbox"):
169
- apps += [
170
- {
171
- "name": "calendar",
172
- "label": _("Calendars"),
173
- "icon": "mdi-calendar",
174
- "description": _("Calendar"),
175
- "url": "/user/calendars",
176
- },
177
- {
178
- "name": "contacts",
179
- "label": _("Contacts"),
180
- "icon": "mdi-contacts-outline",
181
- "description": _("Address book"),
182
- "url": "/user/contacts",
183
- },
184
- {
185
- "name": "webmail",
186
- "label": _("Webmail"),
187
- "icon": "mdi-at",
188
- "description": _("Webmail"),
189
- "url": "/user/webmail",
190
- },
191
- ]
170
+ if "modoboa.contacts" in settings.MODOBOA_APPS:
171
+ apps += [
172
+ {
173
+ "name": "contacts",
174
+ "label": _("Contacts"),
175
+ "icon": "mdi-contacts-outline",
176
+ "description": _("Address book"),
177
+ "url": "/user/contacts",
178
+ }
179
+ ]
180
+ if "modoboa.calendars" in settings.MODOBOA_APPS:
181
+ apps += [
182
+ {
183
+ "name": "calendar",
184
+ "label": _("Calendars"),
185
+ "icon": "mdi-calendar",
186
+ "description": _("Calendar"),
187
+ "url": "/user/calendars",
188
+ }
189
+ ]
190
+ if "modoboa.webmail" in settings.MODOBOA_APPS:
191
+ apps += [
192
+ {
193
+ "name": "webmail",
194
+ "label": _("Webmail"),
195
+ "icon": "mdi-at",
196
+ "description": _("Webmail"),
197
+ "url": "/user/webmail",
198
+ }
199
+ ]
200
+
192
201
  apps += exts_pool.get_available_apps()
193
202
  serializer = serializers.ModoboaApplicationSerializer(apps, many=True)
194
203
  return response.Response(serializer.data)
@@ -649,7 +649,7 @@ GLOBAL_PARAMETERS_STRUCT = collections.OrderedDict(
649
649
  "new_versions_email_rcpt",
650
650
  {
651
651
  "label": gettext_lazy("Recipient"),
652
- "display": "check_new_versions=true",
652
+ "display": "send_new_versions_email=true",
653
653
  "help_text": gettext_lazy(
654
654
  "Recipient of new versions notification emails."
655
655
  ),
@@ -57,6 +57,12 @@ class Command(BaseCommand):
57
57
  default=False,
58
58
  help="Omit everything related to frontend initialisation",
59
59
  )
60
+ parser.add_argument(
61
+ "--relative-urls-in-config",
62
+ action="store_true",
63
+ default=False,
64
+ help="Use relative urls in generated config.json file",
65
+ )
60
66
 
61
67
  def handle(self, *args, **options):
62
68
  """Command entry point."""
@@ -117,7 +123,10 @@ class Command(BaseCommand):
117
123
  base_uris = " ".join(base_uris_list)
118
124
  base_uri = base_uris_list[0]
119
125
  redirect_uris = " ".join([f"{uri}/login/logged" for uri in base_uris_list])
120
- redirect_uri = redirect_uris.split(" ")[0]
126
+ if not options["relative_urls_in_config"]:
127
+ redirect_uri = redirect_uris.split(" ")[0]
128
+ else:
129
+ redirect_uri = "/login/logged"
121
130
  client_id = ""
122
131
  if options["dev"]:
123
132
  base_uri = "https://localhost:3000/"
@@ -150,10 +159,6 @@ class Command(BaseCommand):
150
159
  os.path.dirname(__file__), "../../../frontend_dist/"
151
160
  )
152
161
  frontend_target_dir = f"{settings.BASE_DIR}/frontend"
153
- if hasattr(settings, "MODOBOA_CUSTOM_LOGO"):
154
- logo_path = settings.MODOBOA_CUSTOM_LOGO
155
- else:
156
- logo_path = f"{frontend_target_dir}/assets/Modoboa_RVB-BLANC-SANS.png"
157
162
  if os.path.isdir(base_frontend_dir):
158
163
  shutil.rmtree(frontend_target_dir, ignore_errors=True)
159
164
  os.makedirs(frontend_target_dir, exist_ok=True)
@@ -164,16 +169,26 @@ class Command(BaseCommand):
164
169
  f"{frontend_target_dir}/{entry.name}",
165
170
  target_is_directory=entry.is_dir(),
166
171
  )
172
+ api_base_url = "/api/v2"
173
+ api_doc_url = "/api/schema-v2/swagger/"
174
+ oauth_authority_url = "/api/o"
175
+ if not options["relative_urls_in_config"]:
176
+ api_base_url = f"{base_uri}{api_base_url}"
177
+ api_doc_url = f"{base_uri}{base_uri}"
178
+ oauth_authority_url = f"{base_uri}{oauth_authority_url}"
179
+ oauth_post_logout_redirect_uri = base_uri
180
+ else:
181
+ oauth_post_logout_redirect_uri = ""
182
+
167
183
  with open(f"{frontend_target_dir}/config.json", "w") as fp:
168
184
  fp.write(
169
185
  f"""{{
170
- "API_BASE_URL": "{base_uri}/api/v2",
171
- "API_DOC_URL": "{base_uri}/api/schema-v2/swagger/",
172
- "OAUTH_AUTHORITY_URL": "{base_uri}/api/o",
186
+ "API_BASE_URL": "{api_base_url}",
187
+ "API_DOC_URL": "{api_doc_url}",
188
+ "OAUTH_AUTHORITY_URL": "{oauth_authority_url}",
173
189
  "OAUTH_CLIENT_ID": "{client_id}",
174
190
  "OAUTH_REDIRECT_URI": "{redirect_uri}",
175
- "OAUTH_POST_REDIRECT_URI": "{base_uri}",
176
- "MENU_LOGO_PATH": "{logo_path}"
191
+ "OAUTH_POST_REDIRECT_URI": "{oauth_post_logout_redirect_uri}"
177
192
  }}
178
193
  """
179
194
  )
@@ -9,9 +9,9 @@ class Migration(migrations.Migration):
9
9
  ]
10
10
 
11
11
  operations = [
12
- migrations.RenameIndex(
13
- model_name="user",
14
- new_name="core_user_email_c0c03f_idx",
15
- old_fields=("email", "is_active"),
16
- ),
12
+ # migrations.RenameIndex(
13
+ # model_name="user",
14
+ # new_name="core_user_email_c0c03f_idx",
15
+ # old_fields=("email", "is_active"),
16
+ # ),
17
17
  ]
@@ -86,20 +86,61 @@ class ManagementCommandsTestCase(SimpleModoTestCase):
86
86
  self.addCleanup(shutil.rmtree, frontend_src)
87
87
 
88
88
  frontend_dir = pathlib.Path(settings.BASE_DIR) / "frontend"
89
- self.assertFalse(frontend_dir.exists(), f"frontend directory {frontend_dir} doesn’t exist before load_initial_data")
89
+ self.assertFalse(
90
+ frontend_dir.exists(),
91
+ f"frontend directory {frontend_dir} doesn’t exist before load_initial_data",
92
+ )
90
93
  management.call_command("load_initial_data")
91
- self.assertTrue(frontend_dir.is_dir(), f"frontend directory {frontend_dir} is directory after load_initial_data")
94
+ self.assertTrue(
95
+ frontend_dir.is_dir(),
96
+ f"frontend directory {frontend_dir} is directory after load_initial_data",
97
+ )
92
98
  for entry in frontend_dir.iterdir():
93
99
  if entry.name == "config.json":
94
- self.assertTrue(entry.is_file() and not entry.is_symlink(), f"frontend directory entry {entry} is regular file")
100
+ self.assertTrue(
101
+ entry.is_file() and not entry.is_symlink(),
102
+ f"frontend directory entry {entry} is regular file",
103
+ )
95
104
  try:
96
105
  content = json.loads(entry.read_text("utf-8"))
97
106
  except (ValueError, UnicodeDecodeError):
98
- self.fail(f"frontend directory entry {entry} is not UTF-8 encoded JSON")
99
- for key_name in ("API_BASE_URL", "OAUTH_AUTHORITY_URL", "OAUTH_CLIENT_ID", "OAUTH_REDIRECT_URI", "OAUTH_POST_REDIRECT_URI"):
107
+ self.fail(
108
+ f"frontend directory entry {entry} is not UTF-8 encoded JSON"
109
+ )
110
+ for key_name in (
111
+ "API_BASE_URL",
112
+ "OAUTH_AUTHORITY_URL",
113
+ "OAUTH_CLIENT_ID",
114
+ "OAUTH_REDIRECT_URI",
115
+ "OAUTH_POST_REDIRECT_URI",
116
+ ):
100
117
  self.assertIn(key_name, content)
101
118
  else:
102
- self.assertTrue(entry.is_symlink(), f"frontend directory entry {entry} is symlink")
119
+ self.assertTrue(
120
+ entry.is_symlink(), f"frontend directory entry {entry} is symlink"
121
+ )
122
+
123
+ def test_init_data_relative_urls(self):
124
+ """Test behavior of --relative-urls-in-config option."""
125
+ # Create our own dummy frontend directory if `vite` hasn’t been invoked yet
126
+ frontend_src = pathlib.Path(__file__).parent.parent.parent / "frontend_dist"
127
+ if not frontend_src.is_dir():
128
+ (frontend_src / "dummy").mkdir(parents=True)
129
+ self.addCleanup(shutil.rmtree, frontend_src)
130
+
131
+ management.call_command("load_initial_data", "--relative-urls-in-config")
132
+ frontend_dir = pathlib.Path(settings.BASE_DIR) / "frontend"
133
+ filepath = f"{frontend_dir}/config.json"
134
+ self.assertTrue(frontend_dir.exists())
135
+ with open(filepath) as fp:
136
+ config = json.loads(fp.read())
137
+ for param in [
138
+ "API_BASE_URL",
139
+ "API_DOC_URL",
140
+ "OAUTH_REDIRECT_URI",
141
+ "OAUTH_POST_REDIRECT_URI",
142
+ ]:
143
+ self.assertFalse(config[param].startswith("http"))
103
144
 
104
145
  def test_clean_inactive_accounts(self):
105
146
  """Run clean_inactive_accounts command."""
@@ -1 +1 @@
1
- import{C as O}from"./ChoiceField-DT-S7jc3.js";import{u as N,b as S,g as V,h as f,i as _,c as R,o as y,w as h,a as d,f as s,e as U,t as x,l as M,j as C,L as D,q as j,m as Q,F as G,G as T,N as H}from"./index-BQtPj9kp.js";import{V as P,a as L}from"./VRow-Feh4EzZW.js";import{V as B}from"./VAlert-CEP3cFn3.js";import{d as z}from"./VAvatar-BlQCGAjd.js";import{V as k,r as F}from"./VForm-BZayccL6.js";import{_ as K}from"./AccountPasswordSubForm-bJ0vggm0.js";import{E as q}from"./EmailField-5oAk_8mr.js";import{V as E}from"./VTextField-B1sRHgeW.js";import{V as $}from"./VSwitch-BNTZUUIk.js";import{u as Y}from"./domains.store-DOrci7Lr.js";import{V as J}from"./VChip-YJr5NpVo.js";const W={class:"headline"},X={class:"mt-4"},_e={__name:"AccountRoleForm",props:{modelValue:{type:Object,default:null}},setup(g,{expose:w}){const i=N(),{$gettext:a}=S(),v=g,m=V(),c=f(()=>v.modelValue),l=f(()=>i.authUser),p=f(()=>{const n=b.value.find(r=>r.value===c.value.role);return n!==void 0?n.label:""}),e=f(()=>{const n=b.value.find(r=>r.value===c.value.role);return n!==void 0?n.help:""}),b=f(()=>l.value.role===_.SUPER_ADMIN?[...A,...o,...u,...t]:l.value.role===_.DOMAIN_ADMIN?[...A]:l.value.role===_.RESELLER?[...o,...A]:[]),A=[{label:a("Simple user"),value:_.USER,help:a("A user with no privileges but with a mailbox. He will be allowed to use all end-user features: webmail, calendar, contacts, filters.")}],o=[{label:a("Domain administrator"),value:_.DOMAIN_ADMIN,help:a("A user with privileges on one or more domain. He will be allowed to administer mailboxes and he can also have a mailbox.")}],u=[{label:a("Reseller"),value:_.RESELLER,help:a("An intermediate user who has the same privileges than a Domain administrator, plus the possibility to create domains and to assign resources.")}],t=[{label:a("Super administrator"),value:_.SUPER_ADMIN,help:a("A user with all privileges, can do anything. By default, such a user does not have a mailbox so he can't access end-user features.")}];return w({vFormRef:m,roleLabel:p}),(n,r)=>(y(),R(k,{ref_key:"vFormRef",ref:m},{default:h(()=>[d(P,null,{default:h(()=>[d(L,{cols:"7"},{default:h(()=>[d(s(O),{modelValue:c.value.role,"onUpdate:modelValue":r[0]||(r[0]=I=>c.value.role=I),label:s(a)("Choose a role"),choices:b.value,"choices-per-line":2},null,8,["modelValue","label","choices"])]),_:1}),d(L,{cols:"5"},{default:h(()=>[d(B,{color:"primary",class:"mt-11 ml-4 rounded-lg"},{default:h(()=>[U("h3",W,x(p.value),1),U("p",X,x(e.value),1),d(z,{color:"white",class:"float-right",size:"large",icon:"mdi-help-circle-outline"})]),_:1})]),_:1})]),_:1})]),_:1},512))}},Z={class:"m-label"},ee={class:"m-label"},le={class:"m-label"},Ve={__name:"AccountGeneralForm",props:{modelValue:{type:Object,default:null},editing:{type:Boolean,default:!1}},setup(g,{expose:w}){const{$gettext:i}=S(),a=N(),v=g,m=V(),c=V({}),l=f(()=>v.modelValue),p=f(()=>l.value.role===_.USER?i("Enter an email address"):i("Enter a simple username or an email address")),e=f(()=>l.value.role===_.USER?"email":"text");function b(){l.value.username.indexOf("@")!==-1&&(l.value.mailbox.full_address=l.value.username,l.value.mailbox.message_limit=null)}return w({vFormRef:m,formErrors:c}),(A,o)=>(y(),R(k,{ref_key:"vFormRef",ref:m},{default:h(()=>[U("label",Z,x(s(i)("Username")),1),d(q,{ref:"username",modelValue:l.value.username,"onUpdate:modelValue":[o[0]||(o[0]=u=>l.value.username=u),b],placeholder:p.value,type:e.value,rules:e.value==="email"?[s(F).required,s(F).email]:[s(F).required],role:l.value.role,"error-msg":c.value.value?c.value.value.username:[]},null,8,["modelValue","placeholder","type","rules","role","error-msg"]),U("label",ee,x(s(i)("First name")),1),d(E,{modelValue:l.value.first_name,"onUpdate:modelValue":o[1]||(o[1]=u=>l.value.first_name=u),autocomplete:"new-password",variant:"outlined",density:"compact"},null,8,["modelValue"]),U("label",le,x(s(i)("Last name")),1),d(E,{modelValue:l.value.last_name,"onUpdate:modelValue":o[2]||(o[2]=u=>l.value.last_name=u),autocomplete:"new-password",variant:"outlined",density:"compact"},null,8,["modelValue"]),s(a).authUser.pk!==l.value.pk?(y(),R(K,{key:0,ref:"passwordForm",modelValue:l.value,"onUpdate:modelValue":o[3]||(o[3]=u=>l.value=u),editing:g.editing,"form-errors":c.value},null,8,["modelValue","editing","form-errors"])):(y(),R(B,{key:1,type:"info",variant:"tonal",class:"py-2"},{default:h(()=>[M(x(s(i)("You can update your password from the Account section")),1)]),_:1})),d($,{modelValue:l.value.is_active,"onUpdate:modelValue":o[4]||(o[4]=u=>l.value.is_active=u),label:s(i)("Enabled"),density:"compact",color:"primary"},null,8,["modelValue","label"])]),_:1},512))}},ae={class:"m-label"},oe={class:"m-label"},te={__name:"AccountMailboxForm",props:{modelValue:{type:Object,default:null}},emits:["update:modelValue"],setup(g,{expose:w,emit:i}){const{$gettext:a}=S(),v=i,m=g,c=Y(),l=f(()=>c.domains),p=V(),e=V({mailbox:{}});D(m.modelValue,t=>{t?(e.value={...t},e.value.role===_.USER&&(e.value.mailbox||(e.value.mailbox={}),e.value.mailbox.full_address=e.value.username),e.value.mailbox.message_limit===""&&(e.value.mailbox.message_limit=null)):e.value={mailbox:{}}},{immediate:!0}),D(e,t=>{v("update:modelValue",t)},{deep:!0});const b=f(()=>{const t=e.value.mailbox.full_address;if(t&&t.indexOf("@")!==-1){const n=l.value.find(r=>r.name===t.split("@")[1]);if(n)return parseInt(n.default_mailbox_quota)}}),A=f(()=>{let t=a("Use domain default value");return b.value!==void 0&&(b.value===0?t+=` (${a("unlimited")})`:t+=` (${b.value} MB)`),t});function o(t){t&&delete e.value.mailbox.quota}function u(t){t===""&&(e.value.mailbox.message_limit=null)}return w({vFormRef:p}),(t,n)=>(y(),R(k,{ref_key:"vFormRef",ref:p},{default:h(()=>[U("label",ae,x(s(a)("Quota")),1),d($,{modelValue:e.value.mailbox.use_domain_quota,"onUpdate:modelValue":[n[0]||(n[0]=r=>e.value.mailbox.use_domain_quota=r),o],label:A.value,color:"primary"},null,8,["modelValue","label"]),e.value.mailbox.use_domain_quota?j("",!0):(y(),R(E,{key:0,modelValue:e.value.mailbox.quota,"onUpdate:modelValue":n[1]||(n[1]=r=>e.value.mailbox.quota=r),placeholder:s(a)("Ex: 10MB. Leave empty for no limit"),hint:s(a)("Quota for this mailbox, can be expressed in KB, MB (default) or GB. Define a custom value or use domain's default one. Leave empty to define an unlimited value (not allowed for domain administrators)."),"persistent-hint":"",variant:"outlined",density:"compact"},null,8,["modelValue","placeholder","hint"])),U("label",oe,x(s(a)("Daily message sending limit")),1),d(E,{modelValue:e.value.mailbox.message_limit,"onUpdate:modelValue":[n[2]||(n[2]=r=>e.value.mailbox.message_limit=r),u],placeholder:s(a)("Leave empty for no limit"),hint:s(a)("Number of messages this mailbox can send per day. Leave empty for no limit."),"persistent-hint":"",variant:"outlined",density:"compact",rules:[s(F).numericOrNull]},null,8,["modelValue","placeholder","hint","rules"]),d($,{modelValue:e.value.mailbox.is_send_only,"onUpdate:modelValue":n[3]||(n[3]=r=>e.value.mailbox.is_send_only=r),label:s(a)("Send only account"),density:"compact",color:"primary"},null,8,["modelValue","label"])]),_:1},512))}},he=C(te,[["__scopeId","data-v-41714779"]]),xe={__name:"AccountAliasForm",props:{modelValue:{type:Object,default:null}},setup(g,{expose:w}){const{$gettext:i}=S(),a=g,v=f(()=>a.modelValue),m=V(""),c=V(),l=V(),p=V(!1),e=V([]);async function b(){if(e.value=[],p.value=!0,v.value.aliases.indexOf(m.value)!==-1){e.value=[i("Alias already added")],m.value="",p.value=!1;return}try{await H.validate({aliases:[m.value],mailbox:v.value.mailbox}),v.value.aliases.push(m.value),m.value="",l.value.resetValidation()}catch(o){o.response.data.aliases?o.response.data.aliases:o.response.data.non_field_errors&&o.response.data.non_field_errors}finally{p.value=!1}}function A(o){v.value.aliases.splice(o,1)}return w({vFormRef:l}),(o,u)=>(y(),R(k,{ref_key:"vFormRef",ref:l},{default:h(()=>[d(s(q),{ref_key:"aliasField",ref:c,modelValue:m.value,"onUpdate:modelValue":u[0]||(u[0]=t=>m.value=t),placeholder:s(i)("Start typing a name here..."),hint:s(i)("Alias(es) of this mailbox. To create a catchall alias, just enter the domain name (@domain.tld)."),"persistent-hint":"",loading:p.value,"error-msg":e.value,onDomainSelected:b},null,8,["modelValue","placeholder","hint","loading","error-msg"]),(y(!0),Q(G,null,T(v.value.aliases,(t,n)=>(y(),R(J,{key:n,class:"mr-2 mt-2",closable:"","onClick:close":r=>A(n)},{default:h(()=>[M(x(t),1)]),_:2},1032,["onClick:close"]))),128))]),_:1},512))}};export{he as A,xe as _,Ve as a,_e as b};
1
+ import{C as O}from"./ChoiceField-7eU7c_rI.js";import{u as N,b as S,g as V,h as f,i as _,c as R,o as y,w as h,a as d,f as s,e as U,t as x,l as M,k as C,L as D,q as j,m as Q,F as G,G as T,N as H}from"./index-DV9Li2cg.js";import{V as P,a as L}from"./VRow-C_Ydf6yr.js";import{V as B}from"./VAlert-B7mzOJIO.js";import{d as z}from"./VAvatar-BAgTUIvX.js";import{V as k,r as F}from"./VForm-CpoZf60D.js";import{_ as K}from"./AccountPasswordSubForm-g3IEGrgM.js";import{E as q}from"./EmailField-BEKxuYni.js";import{V as E}from"./VTextField-CLwRV0Cb.js";import{V as $}from"./VSwitch-Chg5o-Cp.js";import{u as Y}from"./domains.store-ChZgLcqP.js";import{V as J}from"./VChip-nZ0uhY7t.js";const W={class:"headline"},X={class:"mt-4"},_e={__name:"AccountRoleForm",props:{modelValue:{type:Object,default:null}},setup(g,{expose:w}){const i=N(),{$gettext:a}=S(),v=g,m=V(),c=f(()=>v.modelValue),l=f(()=>i.authUser),p=f(()=>{const n=b.value.find(r=>r.value===c.value.role);return n!==void 0?n.label:""}),e=f(()=>{const n=b.value.find(r=>r.value===c.value.role);return n!==void 0?n.help:""}),b=f(()=>l.value.role===_.SUPER_ADMIN?[...A,...o,...u,...t]:l.value.role===_.DOMAIN_ADMIN?[...A]:l.value.role===_.RESELLER?[...o,...A]:[]),A=[{label:a("Simple user"),value:_.USER,help:a("A user with no privileges but with a mailbox. He will be allowed to use all end-user features: webmail, calendar, contacts, filters.")}],o=[{label:a("Domain administrator"),value:_.DOMAIN_ADMIN,help:a("A user with privileges on one or more domain. He will be allowed to administer mailboxes and he can also have a mailbox.")}],u=[{label:a("Reseller"),value:_.RESELLER,help:a("An intermediate user who has the same privileges than a Domain administrator, plus the possibility to create domains and to assign resources.")}],t=[{label:a("Super administrator"),value:_.SUPER_ADMIN,help:a("A user with all privileges, can do anything. By default, such a user does not have a mailbox so he can't access end-user features.")}];return w({vFormRef:m,roleLabel:p}),(n,r)=>(y(),R(k,{ref_key:"vFormRef",ref:m},{default:h(()=>[d(P,null,{default:h(()=>[d(L,{cols:"7"},{default:h(()=>[d(s(O),{modelValue:c.value.role,"onUpdate:modelValue":r[0]||(r[0]=I=>c.value.role=I),label:s(a)("Choose a role"),choices:b.value,"choices-per-line":2},null,8,["modelValue","label","choices"])]),_:1}),d(L,{cols:"5"},{default:h(()=>[d(B,{color:"primary",class:"mt-11 ml-4 rounded-lg"},{default:h(()=>[U("h3",W,x(p.value),1),U("p",X,x(e.value),1),d(z,{color:"white",class:"float-right",size:"large",icon:"mdi-help-circle-outline"})]),_:1})]),_:1})]),_:1})]),_:1},512))}},Z={class:"m-label"},ee={class:"m-label"},le={class:"m-label"},Ve={__name:"AccountGeneralForm",props:{modelValue:{type:Object,default:null},editing:{type:Boolean,default:!1}},setup(g,{expose:w}){const{$gettext:i}=S(),a=N(),v=g,m=V(),c=V({}),l=f(()=>v.modelValue),p=f(()=>l.value.role===_.USER?i("Enter an email address"):i("Enter a simple username or an email address")),e=f(()=>l.value.role===_.USER?"email":"text");function b(){l.value.username.indexOf("@")!==-1&&(l.value.mailbox.full_address=l.value.username,l.value.mailbox.message_limit=null)}return w({vFormRef:m,formErrors:c}),(A,o)=>(y(),R(k,{ref_key:"vFormRef",ref:m},{default:h(()=>[U("label",Z,x(s(i)("Username")),1),d(q,{ref:"username",modelValue:l.value.username,"onUpdate:modelValue":[o[0]||(o[0]=u=>l.value.username=u),b],placeholder:p.value,type:e.value,rules:e.value==="email"?[s(F).required,s(F).email]:[s(F).required],role:l.value.role,"error-msg":c.value.value?c.value.value.username:[]},null,8,["modelValue","placeholder","type","rules","role","error-msg"]),U("label",ee,x(s(i)("First name")),1),d(E,{modelValue:l.value.first_name,"onUpdate:modelValue":o[1]||(o[1]=u=>l.value.first_name=u),autocomplete:"new-password",variant:"outlined",density:"compact"},null,8,["modelValue"]),U("label",le,x(s(i)("Last name")),1),d(E,{modelValue:l.value.last_name,"onUpdate:modelValue":o[2]||(o[2]=u=>l.value.last_name=u),autocomplete:"new-password",variant:"outlined",density:"compact"},null,8,["modelValue"]),s(a).authUser.pk!==l.value.pk?(y(),R(K,{key:0,ref:"passwordForm",modelValue:l.value,"onUpdate:modelValue":o[3]||(o[3]=u=>l.value=u),editing:g.editing,"form-errors":c.value},null,8,["modelValue","editing","form-errors"])):(y(),R(B,{key:1,type:"info",variant:"tonal",class:"py-2"},{default:h(()=>[M(x(s(i)("You can update your password from the Account section")),1)]),_:1})),d($,{modelValue:l.value.is_active,"onUpdate:modelValue":o[4]||(o[4]=u=>l.value.is_active=u),label:s(i)("Enabled"),density:"compact",color:"primary"},null,8,["modelValue","label"])]),_:1},512))}},ae={class:"m-label"},oe={class:"m-label"},te={__name:"AccountMailboxForm",props:{modelValue:{type:Object,default:null}},emits:["update:modelValue"],setup(g,{expose:w,emit:i}){const{$gettext:a}=S(),v=i,m=g,c=Y(),l=f(()=>c.domains),p=V(),e=V({mailbox:{}});D(m.modelValue,t=>{t?(e.value={...t},e.value.role===_.USER&&(e.value.mailbox||(e.value.mailbox={}),e.value.mailbox.full_address=e.value.username),e.value.mailbox.message_limit===""&&(e.value.mailbox.message_limit=null)):e.value={mailbox:{}}},{immediate:!0}),D(e,t=>{v("update:modelValue",t)},{deep:!0});const b=f(()=>{const t=e.value.mailbox.full_address;if(t&&t.indexOf("@")!==-1){const n=l.value.find(r=>r.name===t.split("@")[1]);if(n)return parseInt(n.default_mailbox_quota)}}),A=f(()=>{let t=a("Use domain default value");return b.value!==void 0&&(b.value===0?t+=` (${a("unlimited")})`:t+=` (${b.value} MB)`),t});function o(t){t&&delete e.value.mailbox.quota}function u(t){t===""&&(e.value.mailbox.message_limit=null)}return w({vFormRef:p}),(t,n)=>(y(),R(k,{ref_key:"vFormRef",ref:p},{default:h(()=>[U("label",ae,x(s(a)("Quota")),1),d($,{modelValue:e.value.mailbox.use_domain_quota,"onUpdate:modelValue":[n[0]||(n[0]=r=>e.value.mailbox.use_domain_quota=r),o],label:A.value,color:"primary"},null,8,["modelValue","label"]),e.value.mailbox.use_domain_quota?j("",!0):(y(),R(E,{key:0,modelValue:e.value.mailbox.quota,"onUpdate:modelValue":n[1]||(n[1]=r=>e.value.mailbox.quota=r),placeholder:s(a)("Ex: 10MB. Leave empty for no limit"),hint:s(a)("Quota for this mailbox, can be expressed in KB, MB (default) or GB. Define a custom value or use domain's default one. Leave empty to define an unlimited value (not allowed for domain administrators)."),"persistent-hint":"",variant:"outlined",density:"compact"},null,8,["modelValue","placeholder","hint"])),U("label",oe,x(s(a)("Daily message sending limit")),1),d(E,{modelValue:e.value.mailbox.message_limit,"onUpdate:modelValue":[n[2]||(n[2]=r=>e.value.mailbox.message_limit=r),u],placeholder:s(a)("Leave empty for no limit"),hint:s(a)("Number of messages this mailbox can send per day. Leave empty for no limit."),"persistent-hint":"",variant:"outlined",density:"compact",rules:[s(F).numericOrNull]},null,8,["modelValue","placeholder","hint","rules"]),d($,{modelValue:e.value.mailbox.is_send_only,"onUpdate:modelValue":n[3]||(n[3]=r=>e.value.mailbox.is_send_only=r),label:s(a)("Send only account"),density:"compact",color:"primary"},null,8,["modelValue","label"])]),_:1},512))}},he=C(te,[["__scopeId","data-v-41714779"]]),xe={__name:"AccountAliasForm",props:{modelValue:{type:Object,default:null}},setup(g,{expose:w}){const{$gettext:i}=S(),a=g,v=f(()=>a.modelValue),m=V(""),c=V(),l=V(),p=V(!1),e=V([]);async function b(){if(e.value=[],p.value=!0,v.value.aliases.indexOf(m.value)!==-1){e.value=[i("Alias already added")],m.value="",p.value=!1;return}try{await H.validate({aliases:[m.value],mailbox:v.value.mailbox}),v.value.aliases.push(m.value),m.value="",l.value.resetValidation()}catch(o){o.response.data.aliases?o.response.data.aliases:o.response.data.non_field_errors&&o.response.data.non_field_errors}finally{p.value=!1}}function A(o){v.value.aliases.splice(o,1)}return w({vFormRef:l}),(o,u)=>(y(),R(k,{ref_key:"vFormRef",ref:l},{default:h(()=>[d(s(q),{ref_key:"aliasField",ref:c,modelValue:m.value,"onUpdate:modelValue":u[0]||(u[0]=t=>m.value=t),placeholder:s(i)("Start typing a name here..."),hint:s(i)("Alias(es) of this mailbox. To create a catchall alias, just enter the domain name (@domain.tld)."),"persistent-hint":"",loading:p.value,"error-msg":e.value,onDomainSelected:b},null,8,["modelValue","placeholder","hint","loading","error-msg"]),(y(!0),Q(G,null,T(v.value.aliases,(t,n)=>(y(),R(J,{key:n,class:"mr-2 mt-2",closable:"","onClick:close":r=>A(n)},{default:h(()=>[M(x(t),1)]),_:2},1032,["onClick:close"]))),128))]),_:1},512))}};export{he as A,xe as _,Ve as a,_e as b};