create auto create page, remove custom domain auto_create_regex part

This commit is contained in:
Son Nguyen Kim 2021-09-20 18:28:43 +02:00
parent f36f8b94e2
commit 56c72d5fba
6 changed files with 282 additions and 85 deletions

View File

@ -0,0 +1,140 @@
{% extends 'dashboard/domain_detail/base.html' %}
{% set domain_detail_page = "auto_create" %}
{% block title %}
{{ custom_domain.domain }} Auto Create Rules
{% endblock %}
{% block domain_detail_content %}
<h1 class="h2 mb-1"> {{ custom_domain.domain }} auto create alias rules </h1>
<div>
<span class="badge badge-info">Advanced</span>
<span class="badge badge-warning">Beta</span>
</div>
{% if custom_domain.catch_all %}
<div class="alert alert-warning mt-3">
Rules are ineffective when catch-all is enabled.
</div>
{% endif %}
<div class="{% if custom_domain.catch_all %} disabled-content {% endif %}">
<div class="mt-3 mb-2">
For a greater control than a simple catch-all, you can define a set of <b>rules</b> to auto create aliases. <br>
A rule is based on a regular expression (<b>regex</b>): if an alias matches the expression, it'll be automatically
created.
</div>
<div class="alert alert-info">
Only the local part of the alias (i.e. <b>@{{ custom_domain.domain }}</b> is ignored) during the
regex test.
</div>
<div class="alert alert-info">
When there are several rules, rules will be evaluated by their order.
</div>
{% if custom_domain.auto_create_rules | length > 0 %}
<div class="mt-2">
{% for auto_create_rule in custom_domain.auto_create_rules %}
<div class="card">
<div class="card-body">
Order: <b>{{ auto_create_rule.order }}</b> <br>
<input readonly value="{{ auto_create_rule.regex }}" class="form-control">
New alias will belong to
{% for mailbox in auto_create_rule.mailboxes %}
<b>{{ mailbox.email }}</b>
{% if not loop.last %},{% endif %}
{% endfor %}
<form method="post" class="mt-2">
<input type="hidden" name="form-name" value="delete-auto-create-rule">
<input type="hidden" name="rule-id" value="{{ auto_create_rule.id }}">
<button class="btn btn-outline-danger btn-sm float-right">Delete</button>
</form>
</div>
</div>
{% endfor %}
</div>
{% endif %}
<div class="mt-2">
<hr>
<h3>New rule </h3>
<form method="post" data-parsley-validate>
<input type="hidden" name="form-name" value="create-auto-create-rule">
{{ new_auto_create_rule_form.csrf_token }}
<div class="form-group">
<label>Regex</label>
{{ new_auto_create_rule_form.regex(class="form-control",
placeholder="prefix\..*",
data_parsley_pattern="[0-9a-z-_.(\\\)(\*)(\|)]{1,}",
data_parsley_trigger="change",
data_parsley_error_message="Only lowercase letter, number, dot (.), dash (-), underscore (_), backslash (\), star (*) are currently supported.") }}
{{ render_field_errors(new_auto_create_rule_form.regex) }}
<div class="small-text">
For example, if you want aliases that starts with <b>prefix.</b> to be automatically created, you can set
the
regex to <em data-toggle="tooltip"
title="Click to copy"
class="clipboard"
data-clipboard-text="prefix\..*">prefix\..*</em>
<br>
If you want aliases that ends with <b>.suffix</b> to be automatically created, you can use the regex
<em data-toggle="tooltip"
title="Click to copy"
class="clipboard"
data-clipboard-text=".*\.suffix">.*\.suffix</em>
<br>
To test out regex, we recommend using regex tester tool like
<a href="https://regex101.com" target="_blank">https://regex101.com↗</a>
</div>
</div>
<div class="form-group">
<label>Order</label>
{{ new_auto_create_rule_form.order(class="form-control", placeholder="10", min=1, value=1, type="number") }}
{{ render_field_errors(new_auto_create_rule_form.order) }}
</div>
<div class="form-group">
<div class="flex-grow-1 mr-2">
<select data-width="100%" required
class="mailbox-select" multiple name="mailbox_ids">
{% for mailbox in mailboxes %}
<option value="{{ mailbox.id }}" {% if mailbox.id == current_user.default_mailbox_id %}
selected {% endif %}>
{{ mailbox.email }}
</option>
{% endfor %}
</select>
</div>
</div>
<button class="btn btn-primary mt-2">Create</button>
</form>
</div>
</div>
{% endblock %}
{% block script %}
<script>
$('.mailbox-select').multipleSelect();
</script>
{% endblock %}

View File

@ -20,6 +20,12 @@
class="list-group-item list-group-item-action {{ 'active' if domain_detail_page == 'trash' }}">
<span class="icon mr-3"><i class="fe fe-trash"></i></span>Deleted Alias
</a>
<a href="{{ url_for('dashboard.domain_detail_auto_create', custom_domain_id=custom_domain.id) }}"
class="list-group-item list-group-item-action {{ 'active' if domain_detail_page == 'auto_create' }}">
<span class="icon mr-3"><i class="fe fe-layers"></i></span>Auto Create
</a>
</div>
</div>

View File

@ -41,68 +41,19 @@
Simply use <b>anything@{{ custom_domain.domain }}</b>
next time you need an alias: it'll be <b>automatically</b>
created the first time it receives an email.
To have more fine-grained control, you can also use the
<a data-toggle="collapse" href="#regex-section">regular expression <i class="fe fe-chevrons-down"></i></a>.
To have more fine-grained control, you can also define
<a href="{{ url_for('dashboard.domain_detail_auto_create', custom_domain_id=custom_domain.id) }}">auto create
rules
<i class="fe fe-chevrons-right"></i></a>.
</div>
<div class="{% if custom_domain.auto_create_regex is none %} collapse {% endif %}
{% if custom_domain.catch_all %} disabled-content {% endif %}
border border-info p-2"
id="regex-section">
<span class="badge badge-info">Advanced</span>
<span class="badge badge-warning">Beta</span> <br>
You can also set a regular expression (regex): if an alias matches the expression, it'll be automatically created.
<br>
Please note that only the local part of the alias (i.e. <b>@{{ custom_domain.domain }}</b> is ignored) during the
regex
test.
<form method="post" class="form-inline" data-parsley-validate>
<input type="hidden" name="form-name" value="set-auto_create_regex">
<div class="form-group">
<input class="form-control mr-2"
value="{{ custom_domain.auto_create_regex or "" }}"
name="auto_create_regex"
required
data-parsley-pattern="[0-9a-z-_.(\\)(\*)(\|)]{1,}"
data-parsley-trigger="change"
data-parsley-error-message="Only lowercase letter, number, dot (.), dash (-), underscore (_), backslash (\), star (*) are currently supported."
placeholder="prefix\..*">
</div>
<button class="btn btn-outline-primary" name="action" value="save">Save</button>
{% if custom_domain.auto_create_regex %}
<button class="btn btn-outline-danger float-right ml-2" name="action" value="remove">Remove</button>
{% endif %}
</form>
For example, if you want aliases that starts with <b>prefix.</b> to be automatically created, you can set the
regex to <em data-toggle="tooltip"
title="Click to copy"
class="clipboard"
data-clipboard-text="prefix\..*">prefix\..*</em>
<br>
If you want aliases that ends with <b>.suffix</b> to be automatically created, you can use the regex
<em data-toggle="tooltip"
title="Click to copy"
class="clipboard"
data-clipboard-text=".*\.suffix">.*\.suffix</em>
<br>
To test out regex, we recommend using regex tester tool like <a href="https://regex101.com" target="_blank">https://regex101.com↗</a>
</div>
<div>
The new alias will belong to
{% for mailbox in custom_domain.mailboxes %}
<b>{{ mailbox.email }}</b>
{% if not loop.last %},{% endif %}
{% endfor %}
</div>
</div>
<div class="{% if not custom_domain.auto_create_alias_enabled %} disabled-content {% endif %}">
<div>Auto-created aliases are automatically owned by these mailboxes</div>
<div class="{% if not custom_domain.catch_all %} disabled-content {% endif %}">
<div>Auto-created aliases are automatically owned by the following mailboxes
<i class="fe fe-corner-right-down"></i></a>.
</div>
{% set domain_mailboxes=custom_domain.mailboxes %}
<form method="post" class="mt-2">
<input type="hidden" name="form-name" value="update">

View File

@ -2,6 +2,8 @@ from threading import Thread
from flask import render_template, request, redirect, url_for, flash
from flask_login import login_required, current_user
from flask_wtf import FlaskForm
from wtforms import StringField, validators, IntegerField
from app.config import EMAIL_SERVERS_WITH_PRIORITY, EMAIL_DOMAIN
from app.dashboard.base import dashboard_bp
@ -14,7 +16,15 @@ from app.dns_utils import (
from app.email_utils import send_email
from app.extensions import db
from app.log import LOG
from app.models import CustomDomain, Alias, DomainDeletedAlias, Mailbox, DomainMailbox
from app.models import (
CustomDomain,
Alias,
DomainDeletedAlias,
Mailbox,
DomainMailbox,
AutoCreateRule,
AutoCreateRuleMailbox,
)
from app.utils import random_string
@ -261,25 +271,6 @@ def domain_detail(custom_domain_id):
return redirect(
url_for("dashboard.domain_detail", custom_domain_id=custom_domain.id)
)
elif request.form.get("form-name") == "set-auto_create_regex":
if request.form.get("action") == "save":
auto_create_regex = request.form.get("auto_create_regex")
if auto_create_regex:
custom_domain.auto_create_regex = auto_create_regex
db.session.commit()
flash("The auto create regex has been updated", "success")
else:
flash("The auto create regex cannot be empty", "error")
else:
custom_domain.auto_create_regex = None
db.session.commit()
flash(
f"The auto create regex has been has been removed",
"info",
)
return redirect(
url_for("dashboard.domain_detail", custom_domain_id=custom_domain.id)
)
elif request.form.get("form-name") == "delete":
name = custom_domain.domain
@ -378,3 +369,120 @@ def domain_detail_trash(custom_domain_id):
domain_deleted_aliases=domain_deleted_aliases,
custom_domain=custom_domain,
)
class AutoCreateRuleForm(FlaskForm):
regex = StringField(
"regex", validators=[validators.DataRequired(), validators.Length(max=128)]
)
order = IntegerField(
"order",
validators=[validators.DataRequired(), validators.NumberRange(min=0, max=100)],
)
@dashboard_bp.route(
"/domains/<int:custom_domain_id>/auto-create", methods=["GET", "POST"]
)
@login_required
def domain_detail_auto_create(custom_domain_id):
custom_domain: CustomDomain = CustomDomain.get(custom_domain_id)
mailboxes = current_user.mailboxes()
new_auto_create_rule_form = AutoCreateRuleForm()
if not custom_domain or custom_domain.user_id != current_user.id:
flash("You cannot see this page", "warning")
return redirect(url_for("dashboard.index"))
if request.method == "POST":
if request.form.get("form-name") == "create-auto-create-rule":
if new_auto_create_rule_form.validate():
# make sure order isn't used before
for auto_create_rule in custom_domain.auto_create_rules:
auto_create_rule: AutoCreateRule
if auto_create_rule.order == int(
new_auto_create_rule_form.order.data
):
flash(
"Another rule with the same order already exists", "error"
)
break
else:
mailbox_ids = request.form.getlist("mailbox_ids")
# check if mailbox is not tempered with
mailboxes = []
for mailbox_id in mailbox_ids:
mailbox = Mailbox.get(mailbox_id)
if (
not mailbox
or mailbox.user_id != current_user.id
or not mailbox.verified
):
flash("Something went wrong, please retry", "warning")
return redirect(
url_for(
"dashboard.domain_detail_auto_create",
custom_domain_id=custom_domain.id,
)
)
mailboxes.append(mailbox)
if not mailboxes:
flash("You must select at least 1 mailbox", "warning")
return redirect(
url_for(
"dashboard.domain_detail_auto_create",
custom_domain_id=custom_domain.id,
)
)
rule = AutoCreateRule.create(
custom_domain_id=custom_domain.id,
order=int(new_auto_create_rule_form.order.data),
regex=new_auto_create_rule_form.regex.data,
flush=True,
)
for mailbox in mailboxes:
AutoCreateRuleMailbox.create(
auto_create_rule_id=rule.id, mailbox_id=mailbox.id
)
db.session.commit()
flash("New auto create rule has been created", "success")
return redirect(
url_for(
"dashboard.domain_detail_auto_create",
custom_domain_id=custom_domain.id,
)
)
elif request.form.get("form-name") == "delete-auto-create-rule":
rule_id = request.form.get("rule-id")
rule: AutoCreateRule = AutoCreateRule.get(int(rule_id))
if not rule or rule.custom_domain_id != custom_domain.id:
flash("Something wrong, please retry", "error")
return redirect(
url_for(
"dashboard.domain_detail_auto_create",
custom_domain_id=custom_domain.id,
)
)
rule_order = rule.order
AutoCreateRule.delete(rule_id)
db.session.commit()
flash(f"Rule #{rule_order} has been deleted", "success")
return redirect(
url_for(
"dashboard.domain_detail_auto_create", custom_domain_id=custom_domain.id
)
)
nb_alias = Alias.filter_by(custom_domain_id=custom_domain.id).count()
return render_template("dashboard/domain_detail/auto-create.html", **locals())

View File

@ -1844,10 +1844,6 @@ class CustomDomain(db.Model, ModelMixin):
user = db.relationship(User, foreign_keys=[user_id])
@property
def auto_create_alias_enabled(self) -> bool:
return self.catch_all or self.auto_create_regex is not None
@property
def mailboxes(self):
if self._mailboxes:

4
static/style.css vendored
View File

@ -170,7 +170,3 @@ textarea.parsley-error {
.domain_detail_content {
font-size: 15px;
}
.domain_detail_content .parsley-errors-list {
max-width: 20em;
}