Merge branch 'feat/mail-policy' into nightly

This commit is contained in:
FreddleSpl0it committed 2026-11-09 03:16:30 +01:00
commit 2afb03dd6a
38 files changed
+3161 -45

No files matched your search

@@ -0,0 +1,84 @@
{# Fields of a mail policy (add modal, edit page): policy, id (prefix of the element ids), groups (choices: id, name,
domain, address), mailboxes (choices), ro, label_class. Groups of a list are sent apart as <list>_groups; without
group choices (none in the domain, no access to groups) the group fields are left out and stay unchanged. #}
{% set entries = {} %}
{% set listed = {} %}
{% for key in ['send_allow', 'send_block', 'receive_allow', 'receive_block'] %}
{% set plain = [] %}
{% set group_ids = [] %}
{% for entry in attribute(policy, key)|default([]) %}
{% if entry starts with 'group:' %}
{% set group_ids = group_ids|merge([entry|slice(6)]) %}
{% else %}
{% set plain = plain|merge([entry]) %}
{% endif %}
{% endfor %}
{% set entries = entries|merge({(key): plain|join("\n")}) %}
{% set listed = listed|merge({(key): group_ids}) %}
{% endfor %}
{% for side in ['send', 'receive'] %}
<legend class="fs-5 mt-4"><i class="bi {{ side == 'send' ? 'bi-send' : 'bi-inbox' }}"></i> {{ attribute(lang.mail_policy, side) }}</legend><hr>
<p class="text-muted small">{{ attribute(lang.mail_policy, side ~ '_info') }}</p>
<div class="row mb-2">
<label class="control-label col-sm-2{{ label_class }}" for="{{ id }}_{{ side }}_mode">{{ lang.mail_policy.mode }}</label>
<div class="col-sm-10">
<select class="form-control" name="{{ side }}_mode" id="{{ id }}_{{ side }}_mode"{% if ro %} disabled{% endif %}>
{% for mode in ['all', 'internal', 'list'] %}
<option value="{{ mode }}"{% if attribute(policy, side ~ '_mode')|default('all') == mode %} selected{% endif %}>{{ attribute(lang.mail_policy, 'mode_' ~ side ~ '_' ~ mode) }}</option>
{% endfor %}
</select>
<small class="text-muted d-block">{{ lang.mail_policy.internal_info }}</small>
</div>
</div>
{% for list in ['allow', 'block'] %}
{% set key = side ~ '_' ~ list %}
<div class="row mb-2">
<label class="control-label col-sm-2{{ label_class }}" for="{{ id }}_{{ key }}">{{ attribute(lang.mail_policy, list ~ '_' ~ side) }}</label>
<div class="col-sm-10">
<textarea class="form-control" rows="3" name="{{ key }}" id="{{ id }}_{{ key }}" placeholder="partner.example, user@example.org"{% if ro %} disabled{% endif %}>{{ attribute(entries, key) }}</textarea>
<small class="text-muted d-block mb-1">{{ attribute(lang.mail_policy, 'entries_info_' ~ side) }}</small>
{% if groups %}
{% if not ro %}<input type="hidden" name="{{ key }}_groups" value="">{% endif %}
<select data-live-search="true" data-width="100%" name="{{ key }}_groups" class="form-control mail-policy-groups" multiple title="{{ lang.mail_policy.groups }}"{% if ro %} disabled{% endif %}>
{% for group in groups %}
<option value="{{ group.id }}" data-domain="{{ group.domain }}"{% if group.id ~ '' in attribute(listed, key) %} selected{% endif %}>{{ group.name }}{% if group.address %} ({{ group.address }}){% endif %}</option>
{% endfor %}
</select>
{% endif %}
</div>
</div>
{% endfor %}
{% endfor %}
<div class="row mb-2">
<label class="control-label col-sm-2{{ label_class }}" for="{{ id }}_reject_text">{{ lang.mail_policy.reject_text }}</label>
<div class="col-sm-10">
<input type="text" class="form-control" name="reject_text" id="{{ id }}_reject_text" maxlength="200" value="{{ policy.reject_text|default('') }}"{% if ro %} disabled{% endif %}>
<small class="text-muted d-block">{{ lang.mail_policy.reject_text_info }}</small>
</div>
</div>
<legend class="fs-5 mt-4"><i class="bi bi-people"></i> {{ lang.mail_policy.assign }}</legend><hr>
<p class="text-muted small">{{ lang.mail_policy.assign_info }}</p>
<div class="row mb-2">
<label class="control-label col-sm-2{{ label_class }}" for="{{ id }}_mailboxes">{{ lang.mail_policy.mailboxes }}</label>
<div class="col-sm-10">
{% if not ro %}<input type="hidden" name="mailboxes" value="">{% endif %}
<select data-live-search="true" data-width="100%" name="mailboxes" id="{{ id }}_mailboxes" class="form-control mail-policy-mailboxes" multiple title="{{ lang.mail_policy.mailboxes }}"{% if ro %} disabled{% endif %}>
{% for mailbox in mailboxes %}
<option value="{{ mailbox }}" data-domain="{{ mailbox|split('@')|last }}"{% if mailbox in policy.mailboxes|default([]) %} selected{% endif %}>{{ mailbox }}</option>
{% endfor %}
</select>
</div>
</div>
{% if groups %}
<div class="row mb-2">
<label class="control-label col-sm-2{{ label_class }}" for="{{ id }}_groups">{{ lang.mail_policy.groups }}</label>
<div class="col-sm-10">
{% if not ro %}<input type="hidden" name="groups" value="">{% endif %}
<select data-live-search="true" data-width="100%" name="groups" id="{{ id }}_groups" class="form-control mail-policy-groups" multiple title="{{ lang.mail_policy.groups }}"{% if ro %} disabled{% endif %}>
{% for group in groups %}
<option value="{{ group.id }}" data-domain="{{ group.domain }}"{% if group.id in policy.groups|default([]) %} selected{% endif %}>{{ group.name }}{% if group.address %} ({{ group.address }}){% endif %}</option>
{% endfor %}
</select>
</div>
</div>
{% endif %}
+46
View File
@@ -0,0 +1,46 @@
{% extends 'edit.twig' %}
{% block inner_content %}
{% if result %}
{% set ro = not result.can_edit %}
<h4>{{ lang.mail_policy.policy }}: {{ result.name }}</h4>
<form data-id="edit_mail_policy" class="form-horizontal" role="form" method="post" autocapitalize="none" autocorrect="off">
<input type="hidden" value="0" name="active">
<div class="row mb-2">
<label class="control-label col-sm-2" for="mail_policy_domain">{{ lang.mail_policy.domain }}</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="mail_policy_domain" value="{{ result.domain }}" disabled>
</div>
</div>
<div class="row mb-2">
<label class="control-label col-sm-2" for="mail_policy_name">{{ lang.mail_policy.name }}</label>
<div class="col-sm-10">
<input type="text" class="form-control" name="name" id="mail_policy_name" value="{{ result.name }}" maxlength="100" required{% if ro %} disabled{% endif %}>
</div>
</div>
<div class="row mb-2">
<label class="control-label col-sm-2" for="mail_policy_description">{{ lang.mail_policy.description }}</label>
<div class="col-sm-10">
<input type="text" class="form-control" name="description" id="mail_policy_description" value="{{ result.description }}" maxlength="255"{% if ro %} disabled{% endif %}>
</div>
</div>
{% include 'edit/mail-policy-fields.twig' with {'policy': result, 'id': 'mail_policy', 'groups': mail_policy_groups, 'mailboxes': mailboxes, 'ro': ro, 'label_class': ''} %}
<div class="row mb-4">
<div class="offset-sm-2 col-sm-10">
<div class="form-check">
<label><input type="checkbox" class="form-check-input" value="1" name="active"{% if result.active == 1 %} checked{% endif %}{% if ro %} disabled{% endif %}> {{ lang.mailbox.active }}</label>
</div>
</div>
</div>
{% if not ro %}
<div class="row">
<div class="offset-sm-2 col-sm-10">
<button class="btn btn-xs-lg d-block d-sm-inline btn-success" data-action="edit_selected" data-id="edit_mail_policy" data-item="{{ result.id }}" data-api-url='edit/mail-policy' data-api-attr='{}' href="#"><i class="bi bi-check-lg"></i> {{ lang.admin.save }}</button>
</div>
</div>
{% endif %}
</form>
{% else %}
{{ parent() }}
{% endif %}
{% endblock %}
+15 -1
View File
@@ -36,10 +36,20 @@
{% if acl.mailbox_groups == 1 %}
<li class="nav-item" role="presentation"><button class="nav-link" aria-selected="false" aria-controls="tab-groups" role="tab" data-bs-toggle="tab" data-bs-target="#tab-groups">{{ lang.groups.groups }}</button></li>
{% endif %}
{% if mailcow_cc_role == 'admin' %}
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" data-bs-toggle="dropdown" href="#">{{ lang.mailbox.policies }}</a>
<ul class="dropdown-menu">
<li role="presentation"><button class="dropdown-item" aria-selected="false" aria-controls="tab-tls-policy" role="tab" data-bs-toggle="tab" data-bs-target="#tab-tls-policy">{{ lang.mailbox.policies_tls }}</button></li>
<li role="presentation"><button class="dropdown-item" aria-selected="false" aria-controls="tab-mail-policies" role="tab" data-bs-toggle="tab" data-bs-target="#tab-mail-policies">{{ lang.mailbox.policies_mail }}</button></li>
</ul>
</li>
{% elseif acl.mail_policies == 1 %}
<li class="nav-item" role="presentation"><button class="nav-link" aria-selected="false" aria-controls="tab-mail-policies" role="tab" data-bs-toggle="tab" data-bs-target="#tab-mail-policies">{{ lang.mail_policy.mail_policies }}</button></li>
{% endif %}
<li class="nav-item" role="presentation"><button class="nav-link" aria-selected="false" aria-controls="tab-syncjobs" role="tab" data-bs-toggle="tab" data-bs-target="#tab-syncjobs">{{ lang.syncjobs.sync_jobs }}</button></li>
<li class="nav-item" role="presentation"><button class="nav-link" aria-selected="false" aria-controls="tab-filters" role="tab" data-bs-toggle="tab" data-bs-target="#tab-filters">{{ lang.mailbox.filters }}</button></li>
<li class="nav-item" role="presentation"><button class="nav-link" aria-selected="false" aria-controls="tab-bcc" role="tab" data-bs-toggle="tab" data-bs-target="#tab-bcc">{{ lang.mailbox.address_rewriting }}</button></li>
<li class="nav-item" role="presentation"{% if mailcow_cc_role != 'admin' %} class="d-none"{% endif %}><button class="nav-link" aria-selected="false" aria-controls="tab-tls-policy" role="tab" data-bs-toggle="tab" data-bs-target="#tab-tls-policy">{{ lang.mailbox.tls_policy_maps }}</button></li>
</ul>
<div class="row">
@@ -59,6 +69,9 @@
{% if acl.mailbox_groups == 1 %}
{% include 'mailbox/tab-groups.twig' %}
{% endif %}
{% if acl.mail_policies == 1 %}
{% include 'mailbox/tab-mail-policies.twig' %}
{% endif %}
{% include 'mailbox/tab-syncjobs.twig' %}
{% include 'mailbox/tab-filters.twig' %}
{% include 'mailbox/tab-bcc.twig' %}
@@ -75,6 +88,7 @@
var lang = {{ lang_mailbox|raw }};
lang.syncjobs = {{ lang_syncjobs|raw }};
lang.groups = {{ lang_groups|raw }};
lang.mail_policy = {{ lang_mail_policy|raw }};
var lang_rl = {{ lang_rl|raw }};
var lang_edit = {{ lang_edit|raw }};
var lang_datatables = {{ lang_datatables|raw }};
@@ -0,0 +1,54 @@
<div class="tab-pane fade" id="tab-mail-policies" role="tabpanel" aria-labelledby="tab-mail-policies">
<div class="card mb-4">
<div class="card-header d-flex fs-5">
<button class="btn d-md-none flex-grow-1 text-start" data-bs-target="#collapse-tab-mail-policies" data-bs-toggle="collapse" aria-controls="collapse-tab-mail-policies">
{{ lang.mail_policy.mail_policies }} <span class="badge bg-info table-lines"></span>
</button>
<span class="d-none d-md-block">{{ lang.mail_policy.mail_policies }} <span class="badge bg-info table-lines"></span></span>
<div class="btn-group ms-auto d-flex">
<button class="btn btn-xs btn-secondary refresh_table" data-table="mail_policy_table">{{ lang.admin.refresh }}</button>
</div>
</div>
<div id="collapse-tab-mail-policies" class="card-body collapse" data-bs-parent="#mail-content">
<p class="text-muted">{{ lang.mail_policy.info }}</p>
<table id="mail_policy_table" class="table table-striped dt-responsive w-100"></table>
<div class="mass-actions-mailbox mt-4">
<div class="btn-group">
<a class="btn btn-sm btn-xs-half d-block d-sm-inline btn-secondary" id="toggle_multi_select_all" data-id="mail_policy" href="#"><i class="bi bi-check-all"></i> {{ lang.mailbox.toggle_all }}</a>
<a class="btn btn-sm btn-xs-half d-block d-sm-inline btn-secondary dropdown-toggle" data-bs-toggle="dropdown" href="#">{{ lang.mailbox.quick_actions }}</a>
<ul class="dropdown-menu">
<li><a class="dropdown-item" data-action="edit_selected" data-id="mail_policy" data-api-url='edit/mail-policy' data-api-attr='{"active":"1"}' href="#">{{ lang.mailbox.activate }}</a></li>
<li><a class="dropdown-item" data-action="edit_selected" data-id="mail_policy" data-api-url='edit/mail-policy' data-api-attr='{"active":"0"}' href="#">{{ lang.mailbox.deactivate }}</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" data-action="delete_selected" data-id="mail_policy" data-api-url='delete/mail-policy' href="#">{{ lang.mailbox.remove }}</a></li>
</ul>
<a class="btn btn-sm d-block d-sm-inline btn-success" href="#" data-bs-toggle="modal" data-bs-target="#addMailPolicyModal"><i class="bi bi-plus-lg"></i> {{ lang.mail_policy.add }}</a>
</div>
</div>
{# Test a rule: would the mail be accepted? Filled by js/site/mail-policy.js #}
<div class="card mt-4">
<div class="card-header">{{ lang.mail_policy.test }}</div>
<div class="card-body">
<p class="text-muted small">{{ lang.mail_policy.test_info }}</p>
<form class="row g-2 align-items-center" id="mail_policy_test" autocomplete="off">
<div class="col-12 col-md-4">
<input type="email" class="form-control" id="mail_policy_test_sender" placeholder="{{ lang.mail_policy.test_sender }}" aria-label="{{ lang.mail_policy.test_sender }}">
</div>
<div class="col-12 col-md-4">
<input type="email" class="form-control" id="mail_policy_test_recipient" placeholder="{{ lang.mail_policy.test_recipient }}" aria-label="{{ lang.mail_policy.test_recipient }}" required>
</div>
<div class="col-12 col-md-4 d-flex align-items-center">
<div class="form-check me-3">
<input class="form-check-input" type="checkbox" id="mail_policy_test_login" checked>
<label class="form-check-label" for="mail_policy_test_login">{{ lang.mail_policy.test_logged_in }}</label>
</div>
<button type="submit" class="btn btn-sm btn-secondary ms-auto"><i class="bi bi-play"></i> {{ lang.mail_policy.test_run }}</button>
</div>
</form>
<div id="mail_policy_test_result" class="mt-3"></div>
</div>
</div>
</div>
</div>
</div>
@@ -0,0 +1,50 @@
<div class="modal fade" id="addMailPolicyModal" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog modal-xl">
<div class="modal-content">
<div class="modal-header">
<h3 class="modal-title">{{ lang.mail_policy.add }}</h3>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form class="form-horizontal" role="form" data-id="add_mail_policy" autocapitalize="none" autocorrect="off">
<input type="hidden" value="0" name="active">
<div class="row mb-2">
<label class="control-label col-sm-2 text-sm-end" for="add_mail_policy_domain">{{ lang.mail_policy.domain }}</label>
<div class="col-sm-10">
<select data-live-search="true" name="domain" id="add_mail_policy_domain" class="form-control mail-policy-domain-select" required>
{% for domain in domains %}
<option>{{ domain }}</option>
{% endfor %}
</select>
</div>
</div>
<div class="row mb-2">
<label class="control-label col-sm-2 text-sm-end" for="add_mail_policy_name">{{ lang.mail_policy.name }}</label>
<div class="col-sm-10">
<input type="text" class="form-control" name="name" id="add_mail_policy_name" maxlength="100" required>
</div>
</div>
<div class="row mb-2">
<label class="control-label col-sm-2 text-sm-end" for="add_mail_policy_description">{{ lang.mail_policy.description }}</label>
<div class="col-sm-10">
<input type="text" class="form-control" name="description" id="add_mail_policy_description" maxlength="255">
</div>
</div>
{% include 'edit/mail-policy-fields.twig' with {'policy': {}, 'id': 'add_mail_policy', 'groups': mail_policy_groups, 'mailboxes': mailboxes, 'ro': false, 'label_class': ' text-sm-end'} %}
<div class="row mb-2">
<div class="offset-sm-2 col-sm-10">
<div class="form-check">
<label><input type="checkbox" class="form-check-input" value="1" name="active" checked> {{ lang.mailbox.active }}</label>
</div>
</div>
</div>
<div class="row">
<div class="offset-sm-2 col-sm-10">
<button class="btn btn-xs-lg d-block d-sm-inline btn-success" data-action="add_item" data-id="add_mail_policy" data-api-url='add/mail-policy' data-api-attr='{}' href="#">{{ lang.admin.add }}</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
+3
View File
@@ -1401,3 +1401,6 @@
{% if acl.mailbox_groups == 1 %}
{% include "modals/mailbox_group.twig" %}
{% endif %}
{% if acl.mail_policies == 1 %}
{% include "modals/mail_policy.twig" %}
{% endif %}