4.21.3
This commit is contained in:
286
app/templates/dashboard/alias_contact_manager.html
Normal file
286
app/templates/dashboard/alias_contact_manager.html
Normal file
@ -0,0 +1,286 @@
|
||||
{% extends "default.html" %}
|
||||
|
||||
{% set active_page = "dashboard" %}
|
||||
{% block title %}Alias Contact Manager{% endblock %}
|
||||
{% block default_content %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<h1 class="h3">
|
||||
{{ alias.email }} contacts
|
||||
<a class="ml-3 text-info"
|
||||
style="font-size: 12px"
|
||||
data-toggle="collapse"
|
||||
href="#howtouse"
|
||||
role="button"
|
||||
aria-expanded="false"
|
||||
aria-controls="collapseExample">
|
||||
How to use <i class="fe fe-chevrons-down"></i>
|
||||
</a>
|
||||
</h1>
|
||||
<div class="alert alert-primary collapse" id="howtouse" role="alert">
|
||||
<p>
|
||||
To send an email from your alias to a contact, you need to create a <em>reverse-alias</em>,
|
||||
a special email address.
|
||||
<br />
|
||||
When you send an email to the reverse-alias, the email will be sent <b>from your alias</b> to the contact.
|
||||
<br />
|
||||
<img src="/static/images/reverse-alias.svg"
|
||||
style="border: 1px solid"
|
||||
class="my-2 img-fluid"/>
|
||||
</p>
|
||||
<p>This might seem like "magic" but trust us, only the first time is a bit awkward.</p>
|
||||
<p>
|
||||
{% if alias.mailbox_id %}
|
||||
{% if alias.mailboxes | length == 1 %}
|
||||
|
||||
Make sure you send the email from your mailbox <b>{{ alias.mailbox.email }}</b>.
|
||||
{% else %}
|
||||
Make sure you send the email from one of the following mailboxes:
|
||||
<br />
|
||||
{% for mailbox in alias.mailboxes %}
|
||||
|
||||
- <b>{{ mailbox.email }}</b>
|
||||
<br />
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% else %}
|
||||
Make sure you send the email from your personal email address ({{ current_user.email }}).
|
||||
{% endif %}
|
||||
</p>
|
||||
<p>
|
||||
This Youtube video can also quickly walk you through the steps:
|
||||
<a href="https://www.youtube.com/watch?v=VsypF-DBaow" target="_blank">
|
||||
How to send emails from an alias <i class="fe fe-external-link"></i>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-5">
|
||||
<div class="col-12 col-lg-6 pt-1">
|
||||
<form method="post">
|
||||
<input type="hidden" name="form-name" value="create" />
|
||||
{{ new_contact_form.csrf_token }}
|
||||
{{ new_contact_form.email(class="form-control", placeholder="First Last <email@example.com>", autofocus=True) }}
|
||||
{{ render_field_errors(new_contact_form.email) }}
|
||||
<div class="small-text">Where do you want to send the email?</div>
|
||||
{% if can_create_contacts %}
|
||||
|
||||
<button class="btn btn-primary mt-2">Create reverse-alias</button>
|
||||
{% else %}
|
||||
<button disabled
|
||||
title="Upgrade to premium to create reverse-aliases"
|
||||
class="btn btn-primary mt-2">
|
||||
Create reverse-alias
|
||||
</button>
|
||||
{% endif %}
|
||||
</form>
|
||||
</div>
|
||||
<div class="col-12 col-lg-6 pt-1">
|
||||
<div class="float-right d-flex">
|
||||
<form method="post">
|
||||
{{ csrf_form.csrf_token }}
|
||||
<input type="hidden" name="form-name" value="search">
|
||||
<input type="search"
|
||||
name="query"
|
||||
value="{{ query }}"
|
||||
placeholder="Enter to search for contacts"
|
||||
class="form-control shadow mr-2"
|
||||
style="max-width: 20em">
|
||||
</form>
|
||||
{% if query %}
|
||||
{% if highlight_contact_id %}
|
||||
|
||||
<a href="{{ url_for("dashboard.alias_contact_manager", alias_id=alias.id, highlight_contact_id=highlight_contact_id) }}"
|
||||
class="btn btn-light">
|
||||
Reset
|
||||
</a>
|
||||
{% else %}
|
||||
<a href="{{ url_for("dashboard.alias_contact_manager", alias_id=alias.id) }}"
|
||||
class="btn btn-light">Reset</a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
{% for contact_info in contact_infos %}
|
||||
|
||||
{% set contact = contact_info.contact %}
|
||||
<div class="col-md-6">
|
||||
<div class="my-2 p-2 card {% if contact.id == highlight_contact_id %} highlight-row{% endif %}">
|
||||
<div class="mb-2 row">
|
||||
<div class="col">
|
||||
<span class="font-weight-bold">{{ contact.website_email }}</span>
|
||||
{% if contact.pgp_finger_print %}
|
||||
|
||||
<span class="cursor"
|
||||
data-toggle="tooltip"
|
||||
data-original-title="PGP Enabled">🗝</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="col text-right">
|
||||
<label class="custom-switch cursor" data-toggle="tooltip" {% if contact.block_forward %}
|
||||
title="Unblock sender - start receiving emails from this sender" {% else %} title="Block sender - stop receiving emails from this sender" {% endif %} style="padding-left: 0px">
|
||||
<input type="checkbox" class="enable-disable-contact custom-switch-input" data-contact="{{ contact.id }}" data-contact-email="{{ contact.website_email }}" {{ "checked" if not contact.block_forward else "" }}>
|
||||
<span class="custom-switch-indicator"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<span>
|
||||
<a href="{{ 'mailto:' + contact.website_send_to() }}"
|
||||
data-toggle="tooltip"
|
||||
title="You can click on this to open your email client. Or use the copy button 👉"
|
||||
class="font-weight-bold">
|
||||
*************************
|
||||
</a>
|
||||
<span class="clipboard btn btn-sm btn-success copy-btn"
|
||||
data-toggle="tooltip"
|
||||
title="Copy the reverse-alias to clipboard"
|
||||
data-clipboard-text="{{ contact.website_send_to() }}">
|
||||
Copy reverse-alias
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="mb-2 text-muted small-text">
|
||||
{% if contact_info.latest_email_log != None %}
|
||||
|
||||
{% set email_log = contact_info.latest_email_log %}
|
||||
{% if email_log.is_reply %}
|
||||
|
||||
<i class="fa fa-reply mr-2"
|
||||
data-toggle="tooltip"
|
||||
title="Email reply/sent from alias"></i>
|
||||
{{ email_log.created_at | dt }}
|
||||
{% elif email_log.bounced %}
|
||||
<span class="text-danger">
|
||||
<i class="fa fa-warning mr-2"
|
||||
data-toggle="tooltip"
|
||||
title="Email bounced and cannot be forwarded to your mailbox"></i>
|
||||
{{ email_log.created_at | dt }}
|
||||
</span>
|
||||
{% elif email_log.blocked %}
|
||||
<i class="fa fa-ban mr-2 text-danger"
|
||||
data-toggle="tooltip"
|
||||
title="Email blocked"></i>
|
||||
{{ email_log.created_at | dt }}
|
||||
{% else %}
|
||||
<i class="fa fa-paper-plane mr-2"
|
||||
data-toggle="tooltip"
|
||||
title="Email sent to alias"></i>
|
||||
{{ email_log.created_at | dt }}
|
||||
{% endif %}
|
||||
<br />
|
||||
Contact created {{ contact.created_at | dt }}
|
||||
{% else %}
|
||||
No Activity in the last 14 days. Contact created {{ contact.created_at | dt }}
|
||||
{% endif %}
|
||||
<div>
|
||||
<span class="alias-activity">{{ contact_info.nb_forward }}</span> forwarded,
|
||||
<span class="alias-activity">{{ contact_info.nb_reply }}</span> sent
|
||||
in the last 14 days.
|
||||
</div>
|
||||
</div>
|
||||
<a href="{{ url_for('dashboard.contact_detail_route', contact_id=contact.id) }}">Edit ➡</a>
|
||||
<form method="post">
|
||||
{{ csrf_form.csrf_token }}
|
||||
<input type="hidden" name="form-name" value="delete">
|
||||
<input type="hidden" name="contact-id" value="{{ contact.id }}">
|
||||
<span class="card-link btn btn-link float-right delete-forward-email text-danger">Delete</span>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% if nb_contact > PAGE_LIMIT or page > 0 %}
|
||||
|
||||
<div class="row mt-3">
|
||||
<div class="col">
|
||||
<nav aria-label="Contact navigation">
|
||||
<ul class="pagination">
|
||||
<li class="page-item">
|
||||
<a class="btn btn-outline-secondary {% if page == 0 %}disabled{% endif %}"
|
||||
href="{{ url_for('dashboard.alias_contact_manager', alias_id=alias.id, page=page-1) }}">
|
||||
Previous
|
||||
</a>
|
||||
</li>
|
||||
<li class="page-item">
|
||||
<a class="btn btn-outline-secondary {% if last_page %}disabled{% endif %}"
|
||||
href="{{ url_for('dashboard.alias_contact_manager', alias_id=alias.id, page=page+1) }}">
|
||||
Next
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% block script %}
|
||||
|
||||
<script>
|
||||
$(".delete-forward-email").on("click", function (e) {
|
||||
let that = $(this);
|
||||
|
||||
bootbox.confirm({
|
||||
message: "All activities associated with this contact will also be deleted, please confirm.",
|
||||
buttons: {
|
||||
confirm: {
|
||||
label: 'Yes, delete it',
|
||||
className: 'btn-danger'
|
||||
},
|
||||
cancel: {
|
||||
label: 'Cancel',
|
||||
className: 'btn-outline-primary'
|
||||
}
|
||||
},
|
||||
callback: function (result) {
|
||||
if (result) {
|
||||
that.closest("form").submit();
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
$(".enable-disable-contact").change(async function () {
|
||||
let contactID = $(this).data("contact");
|
||||
let contactEmail = $(this).data("contact-email");
|
||||
|
||||
await blockContact(contactID, contactEmail);
|
||||
})
|
||||
|
||||
async function blockContact(contactID, contactEmail) {
|
||||
let oldValue;
|
||||
try {
|
||||
let res = await fetch(`/api/contacts/${contactID}/toggle`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
let json = await res.json();
|
||||
|
||||
if (json.block_forward) {
|
||||
toastr.success(`${contactEmail} is blocked`);
|
||||
} else {
|
||||
toastr.success(`${contactEmail} is unblocked`);
|
||||
}
|
||||
} else {
|
||||
toastr.error("Sorry for the inconvenience! Could you refresh the page & retry please?", "Unknown Error");
|
||||
// reset to the original value
|
||||
oldValue = !$(this).prop("checked");
|
||||
$(this).prop("checked", oldValue);
|
||||
}
|
||||
} catch (e) {
|
||||
toastr.error("Sorry for the inconvenience! Could you refresh the page & retry please?", "Unknown Error");
|
||||
// reset to the original value
|
||||
oldValue = !$(this).prop("checked");
|
||||
$(this).prop("checked", oldValue);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
128
app/templates/dashboard/alias_log.html
Normal file
128
app/templates/dashboard/alias_log.html
Normal file
@ -0,0 +1,128 @@
|
||||
{% extends "default.html" %}
|
||||
|
||||
{% set active_page = "dashboard" %}
|
||||
{% block title %}Alias Activity{% endblock %}
|
||||
{% block default_content %}
|
||||
|
||||
<h1 class="h3">{{ alias.email }}</h1>
|
||||
<!-- Stats -->
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-6 col-lg-3">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="subheader">Total</div>
|
||||
<div class="text-muted"
|
||||
style="order: 2; margin-left: auto; font-size: .8rem">Last 14 days</div>
|
||||
</div>
|
||||
<div class="h1 m-0">{{ total }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-md-6 col-lg-3">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="subheader">Forwarded</div>
|
||||
<div class="text-muted"
|
||||
style="order: 2; margin-left: auto; font-size: .8rem">Last 14 days</div>
|
||||
</div>
|
||||
<div class="h1 m-0">{{ email_forwarded }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-md-6 col-lg-3">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="subheader">Replies/Sent</div>
|
||||
<div class="text-muted"
|
||||
style="order: 2; margin-left: auto; font-size: .8rem">Last 14 days</div>
|
||||
</div>
|
||||
<div class="h1 m-0">{{ email_replied }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-md-6 col-lg-3">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="subheader">Blocked</div>
|
||||
<div class="text-muted"
|
||||
style="order: 2; margin-left: auto; font-size: .8rem">Last 14 days</div>
|
||||
</div>
|
||||
<div class="h1 m-0">{{ email_blocked }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- END Stats -->
|
||||
<div class="row mt-4">
|
||||
{% for log in logs %}
|
||||
|
||||
<div class="col-lg-6">
|
||||
<div class="my-2 p-2 card border-light">
|
||||
<div class="font-weight-bold">
|
||||
{{ log.when | dt }}
|
||||
<div class="float-right pr-3">
|
||||
{% if log.bounced %}
|
||||
|
||||
⚠️
|
||||
{% else %}
|
||||
{% if log.is_reply %}
|
||||
|
||||
<i class="fa fa-reply"></i>
|
||||
{% elif log.blocked %}
|
||||
<i class="fa fa-ban text-danger"></i>
|
||||
{% else %}
|
||||
<i class="fa fa-paper-plane"></i>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% if log.bounced and not log.is_reply %}
|
||||
|
||||
<div>
|
||||
<span class="mr-2">{{ log.website_email }}</span>
|
||||
<img src="{{ url_for('static', filename='arrows/forward-arrow.svg') }}"
|
||||
class="arrow">
|
||||
<span class="ml-2">{{ log.alias }}</span>
|
||||
<img src="{{ url_for('static', filename='arrows/blocked-arrow.svg') }}"
|
||||
class="arrow">
|
||||
<span class="ml-2">{{ log.email_log.bounced_mailbox() }}</span>
|
||||
</div>
|
||||
{% elif log.bounced and log.is_reply %}
|
||||
<div>
|
||||
<span class="ml-2">{{ log.email_log.bounced_mailbox() }}</span>
|
||||
<img src="{{ url_for('static', filename='arrows/forward-arrow.svg') }}"
|
||||
class="arrow">
|
||||
<span class="ml-2">{{ log.alias }}</span>
|
||||
<img src="{{ url_for('static', filename='arrows/blocked-arrow.svg') }}"
|
||||
class="arrow">
|
||||
<span class="mr-2">{{ log.website_email }}</span>
|
||||
</div>
|
||||
{% else %}
|
||||
<div>{{ log.website_email }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<nav aria-label="Alias log navigation">
|
||||
<ul class="pagination">
|
||||
<li class="page-item">
|
||||
<a class="btn btn-outline-secondary {% if page_id == 0 %}disabled{% endif %}"
|
||||
href="{{ url_for('dashboard.alias_log', alias_id=alias_id, page_id=page_id-1) }}">
|
||||
Previous
|
||||
</a>
|
||||
</li>
|
||||
<li class="page-item">
|
||||
<a class="btn btn-outline-secondary {% if last_page %}disabled{% endif %}"
|
||||
href="{{ url_for('dashboard.alias_log', alias_id=alias_id, page_id=page_id+1) }}">
|
||||
Next
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
{% endblock %}
|
||||
{% block script %}{% endblock %}
|
29
app/templates/dashboard/alias_transfer_receive.html
Normal file
29
app/templates/dashboard/alias_transfer_receive.html
Normal file
@ -0,0 +1,29 @@
|
||||
{% extends "default.html" %}
|
||||
|
||||
{% set active_page = "dashboard" %}
|
||||
{% block title %}Receive {{ alias.email }}{% endblock %}
|
||||
{% block default_content %}
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h1 class="h3">Receive {{ alias.email }}</h1>
|
||||
<p>
|
||||
You are invited to become the owner of the alias <b>{{ alias.email }}</b>
|
||||
</p>
|
||||
<p>Please choose the mailbox(es) that owns this alias 👇</p>
|
||||
<form method="post" class="mt-2">
|
||||
<select data-width="100%" 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>
|
||||
<button class="btn btn-success mt-2">Confirm</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block script %}<script>$('.mailbox-select').multipleSelect();</script>{% endblock %}
|
61
app/templates/dashboard/alias_transfer_send.html
Normal file
61
app/templates/dashboard/alias_transfer_send.html
Normal file
@ -0,0 +1,61 @@
|
||||
{% extends "default.html" %}
|
||||
|
||||
{% set active_page = "dashboard" %}
|
||||
{% block title %}Send {{ alias.email }}{% endblock %}
|
||||
{% block default_content %}
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h1 class="h3">Transfer {{ alias.email }}</h1>
|
||||
<p>
|
||||
This page allows you to transfer {{ alias.email }} to another person so they can use it to receive and send
|
||||
emails.
|
||||
</p>
|
||||
{% if alias_transfer_url %}
|
||||
|
||||
<em data-toggle="tooltip"
|
||||
title="Click to copy"
|
||||
class="clipboard"
|
||||
data-clipboard-text="{{ alias_transfer_url }}">
|
||||
{{ alias_transfer_url }}
|
||||
</em>
|
||||
<p class="mt-5">
|
||||
Please copy the transfer URL. <strong>We won't be able to display it again</strong>. If you need to access it again you can generate a new URL.
|
||||
</p>
|
||||
<p class="mt-2">
|
||||
This transfer URL is <strong>valid for 24 hours</strong>. If it hasn't been used by then it will be automatically disabled.
|
||||
</p>
|
||||
<form method="post">
|
||||
{{ csrf_form.csrf_token }}
|
||||
<input type="hidden" name="form-name" value="remove">
|
||||
<button class="btn btn-warning mt-2">Remove alias transfer URL</button>
|
||||
<div class="small-text">If you don't want to share this alias anymore, you can remove the share URL.</div>
|
||||
</form>
|
||||
{% else %}
|
||||
{% if link_active %}
|
||||
|
||||
<p class="alert alert-info">
|
||||
You have an active transfer link. If you don't want to share this alias anymore, please delete the link.
|
||||
</p>
|
||||
<form method="post">
|
||||
{{ csrf_form.csrf_token }}
|
||||
<input type="hidden" name="form-name" value="remove">
|
||||
<button class="btn btn-warning mt-2">Remove alias transfer URL</button>
|
||||
</form>
|
||||
{% else %}
|
||||
<p>
|
||||
In order to transfer ownership,
|
||||
please create the <b>Share URL</b> 👇 and send it to the other person.
|
||||
</p>
|
||||
<form method="post">
|
||||
{{ csrf_form.csrf_token }}
|
||||
<input type="hidden" name="form-name" value="create">
|
||||
<button class="btn btn-primary mt-2">Generate a new alias transfer URL</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
<p class="mt-5">This person can then confirm the reception and become the owner of the alias.</p>
|
||||
<div class="alert alert-danger">After the confirmation, you can no longer use this alias.</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
134
app/templates/dashboard/api_key.html
Normal file
134
app/templates/dashboard/api_key.html
Normal file
@ -0,0 +1,134 @@
|
||||
{% extends "default.html" %}
|
||||
|
||||
{% block title %}API Key{% endblock %}
|
||||
{% set active_page = "api_key" %}
|
||||
{% block head %}{% endblock %}
|
||||
{% block default_content %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<h1 class="h3">API Keys</h1>
|
||||
<div class="alert alert-info">
|
||||
When you log in on a SimpleLogin mobile app or browser extension,
|
||||
a new API Key is automatically created and stored on your device.
|
||||
It's usually named after the device where it was created, e.g. Samsung S8, John's iPhone, etc.
|
||||
</div>
|
||||
<div class="alert alert-danger">
|
||||
️API Keys should be kept secret and treated like passwords, they can be used to gain access to your account.
|
||||
</div>
|
||||
<div class="row">
|
||||
{% for api_key in api_keys %}
|
||||
|
||||
<div class="col-12 col-lg-6">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">{{ api_key.name or "N/A" }}</h5>
|
||||
<h6 class="card-subtitle mb-2 text-muted">
|
||||
{% if api_key.last_used %}
|
||||
|
||||
Created {{ api_key.created_at | dt }}.
|
||||
Used {{ api_key.times }} times.
|
||||
Was last used {{ api_key.last_used | dt }}.
|
||||
{% else %}
|
||||
Never used
|
||||
{% endif %}
|
||||
</h6>
|
||||
<div class="input-group">
|
||||
<input class="form-control"
|
||||
id="apikey-{{ api_key.id }}"
|
||||
readonly
|
||||
value="**********">
|
||||
</div>
|
||||
<br />
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<form method="post">
|
||||
<input type="hidden" name="form-name" value="delete">
|
||||
<input type="hidden" name="api-key-id" value="{{ api_key.id }}">
|
||||
<span class="card-link btn btn-link float-right text-danger delete-api-key">Delete</span>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% if api_keys|length > 0 %}
|
||||
|
||||
<form method="post">
|
||||
<input type="hidden" name="form-name" value="delete-all">
|
||||
<span class="delete btn btn-outline-danger delete-all-api-keys float-right">
|
||||
Delete All <i class="fe fe-trash"></i>
|
||||
</span>
|
||||
</form>
|
||||
<br />
|
||||
{% endif %}
|
||||
<hr />
|
||||
<form method="post">
|
||||
{{ new_api_key_form.csrf_token }}
|
||||
<input type="hidden" name="form-name" value="create">
|
||||
<h2 class="h4">New API Key</h2>
|
||||
{{ new_api_key_form.name(class="form-control", placeholder="Chrome") }}
|
||||
{{ render_field_errors(new_api_key_form.name) }}
|
||||
<div class="small-text">Name of the api key, e.g. where it will be used.</div>
|
||||
<button class="btn btn-success mt-2">Create</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block script %}
|
||||
|
||||
<script>
|
||||
$(".delete-api-key").on("click", function (e) {
|
||||
let that = $(this);
|
||||
|
||||
bootbox.confirm({
|
||||
message: "If this API Key is currently in use, you might need to login again on the corresponding device, please confirm.",
|
||||
buttons: {
|
||||
confirm: {
|
||||
label: 'Yes, delete it',
|
||||
className: 'btn-danger'
|
||||
},
|
||||
cancel: {
|
||||
label: 'Cancel',
|
||||
className: 'btn-outline-primary'
|
||||
}
|
||||
},
|
||||
callback: function (result) {
|
||||
if (result) {
|
||||
that.closest("form").submit();
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
});
|
||||
|
||||
$(".delete-all-api-keys").on("click", function (e) {
|
||||
let that = $(this);
|
||||
|
||||
bootbox.confirm({
|
||||
message: "This will delete all API Keys, they will all stop working, are you sure?",
|
||||
buttons: {
|
||||
confirm: {
|
||||
label: 'Delete All',
|
||||
className: 'btn-danger'
|
||||
},
|
||||
cancel: {
|
||||
label: 'Cancel',
|
||||
className: 'btn-outline-primary'
|
||||
}
|
||||
},
|
||||
callback: function (result) {
|
||||
if (result) {
|
||||
that.closest("form").submit();
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
});
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
73
app/templates/dashboard/app.html
Normal file
73
app/templates/dashboard/app.html
Normal file
@ -0,0 +1,73 @@
|
||||
{% extends "default.html" %}
|
||||
|
||||
{% set active_page = "app" %}
|
||||
{% block title %}Sign in with SimpleLogin apps{% endblock %}
|
||||
{% block default_content %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<h1 class="h3">Apps</h1>
|
||||
<div class="small-text">
|
||||
List of websites/apps that you have used <b>Sign in with SimpleLogin</b>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row row-cards row-deck mt-4">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover table-outline table-vcenter text-nowrap card-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>App</th>
|
||||
<th>
|
||||
Info
|
||||
<i class="fe fe-help-circle"
|
||||
data-toggle="tooltip"
|
||||
title="Info this app/website receives"></i>
|
||||
</th>
|
||||
<th>
|
||||
First used
|
||||
<i class="fe fe-help-circle"
|
||||
data-toggle="tooltip"
|
||||
title="The first time you have used the 'Sign in with SimpleLogin' button on this app/website"></i>
|
||||
</th>
|
||||
<th>Actions</th>
|
||||
<!--<th class="text-center">Last used</th>-->
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for client_user in client_users %}
|
||||
|
||||
<tr>
|
||||
<td>{{ client_user.client.name }}</td>
|
||||
<td>
|
||||
{% for scope, val in client_user.get_user_info().items() %}
|
||||
|
||||
<div>
|
||||
{% if scope == "email" %}
|
||||
|
||||
Email:
|
||||
<a href="mailto:{{ val }}">{{ val }}</a>
|
||||
{% elif scope == "name" %}
|
||||
Name: {{ val }}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</td>
|
||||
<td>{{ client_user.created_at | dt }}</td>
|
||||
<td>
|
||||
<form method="post">
|
||||
<input type="hidden" name="client-user-id" value="{{ client_user.id }}">
|
||||
<button class="btn btn-warning">Remove</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
72
app/templates/dashboard/batch_import.html
Normal file
72
app/templates/dashboard/batch_import.html
Normal file
@ -0,0 +1,72 @@
|
||||
{% extends "default.html" %}
|
||||
|
||||
{% set active_page = "setting" %}
|
||||
{% block title %}Alias Batch Import{% endblock %}
|
||||
{% block default_content %}
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h1 class="h3">Alias Batch Import</h1>
|
||||
<div class="alert alert-primary">
|
||||
Only aliases created with <b>your verified domains</b> can be imported.
|
||||
<br />
|
||||
If mailboxes are set for an alias, they will only be linked if they already exist.
|
||||
<br />
|
||||
Please make sure to use the csv template file.
|
||||
</div>
|
||||
<p>
|
||||
The import can take several minutes.
|
||||
Please come back to this page to verify the import status.
|
||||
<br />
|
||||
If an alias already exists, it won't be imported.
|
||||
</p>
|
||||
<a href="{{ url_for('static', filename='batch_import_template.csv') }}"
|
||||
download>Download CSV Template</a>
|
||||
<hr />
|
||||
<form method="post" enctype="multipart/form-data" class="mt-4">
|
||||
{{ csrf_form.csrf_token }}
|
||||
<input required
|
||||
type="file"
|
||||
name="alias-file"
|
||||
accept=".csv"
|
||||
class="form-control-file">
|
||||
<label>
|
||||
Only <b>.csv</b> file is supported.
|
||||
</label>
|
||||
<br />
|
||||
<button class="btn btn-success mt-2">Upload</button>
|
||||
</form>
|
||||
{% if batch_imports %}
|
||||
|
||||
<hr />
|
||||
<h2 class="h3 mt-7">Batch imports</h2>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Uploaded</th>
|
||||
<th scope="col">Number Alias Imported</th>
|
||||
<th scope="col">Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for batch_import in batch_imports %}
|
||||
|
||||
<tr>
|
||||
<td>{{ batch_import.created_at | dt }}</td>
|
||||
<td>{{ batch_import.nb_alias() }}</td>
|
||||
<td>
|
||||
{% if batch_import.processed %}
|
||||
|
||||
Processed ✅
|
||||
{% else %}
|
||||
Pending
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
108
app/templates/dashboard/billing.html
Normal file
108
app/templates/dashboard/billing.html
Normal file
@ -0,0 +1,108 @@
|
||||
{% extends "default.html" %}
|
||||
|
||||
{% block title %}Billing{% endblock %}
|
||||
{% block head %}{% endblock %}
|
||||
{% block default_content %}
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h1 class="h3 mb-5">Billing</h1>
|
||||
{% if sub.cancelled %}
|
||||
|
||||
<p>
|
||||
You are on the <b>{{ sub.plan_name() }}</b> plan.
|
||||
<br />
|
||||
You have canceled your subscription and it will end on {{ sub.next_bill_date.strftime("%Y-%m-%d") }}
|
||||
</p>
|
||||
<p class="alert alert-info">
|
||||
If you change your mind you can subscribe again to SimpleLogin but
|
||||
please note that this will be a completely
|
||||
new subscription and
|
||||
your payment method will be charged <b>immediately</b>.
|
||||
<br />
|
||||
The period left in the current subscription isn't taken into account.
|
||||
<br />
|
||||
<a href="{{ url_for('dashboard.pricing') }}"
|
||||
class="btn btn-primary mt-2">Re-subscribe</a>
|
||||
</p>
|
||||
{% else %}
|
||||
<p>
|
||||
You are on the <b>{{ sub.plan_name() }}</b> plan. Thank you very much for supporting
|
||||
SimpleLogin. 🙌
|
||||
<br />
|
||||
The next billing cycle starts at {{ sub.next_bill_date.strftime("%Y-%m-%d") }}.
|
||||
</p>
|
||||
<div class="mt-3">
|
||||
Click here to update billing information on Paddle, our payment partner:
|
||||
<br />
|
||||
<a class="btn btn-outline-success mt-2" href="{{ sub.update_url }}">Update billing information</a>
|
||||
</div>
|
||||
<hr />
|
||||
<div class="mt-6">
|
||||
<h4>Change Plan</h4>
|
||||
You can change the plan at any moment.
|
||||
<br />
|
||||
Please note that the new billing cycle starts instantly
|
||||
i.e. you will be charged <b>immediately</b> the annual fee ($30) when switching from monthly plan or vice-versa
|
||||
<b>without pro rata computation </b>.
|
||||
<br />
|
||||
To change the plan you can also cancel the current one and subscribe a new one <b>by the end</b> of this plan.
|
||||
{% if sub.plan == PlanEnum.yearly %}
|
||||
|
||||
<form method="post"
|
||||
onsubmit="return confirm('This will change your current subscription plan, please confirm');">
|
||||
<input type="hidden" name="form-name" value="change-monthly">
|
||||
<button class="btn btn-outline-primary mt-2">Change to Monthly Plan</button>
|
||||
</form>
|
||||
{% else %}
|
||||
<form method="post"
|
||||
onsubmit="return confirm('This will change your current subscription plan, please confirm');">
|
||||
<input type="hidden" name="form-name" value="change-yearly">
|
||||
<button class="btn btn-outline-primary mt-2">Change to Yearly Plan</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
<hr />
|
||||
<div>
|
||||
<h4>Cancel subscription</h4>
|
||||
Don't want to protect your inbox anymore?
|
||||
<br />
|
||||
<form method="post">
|
||||
<input type="hidden" name="form-name" value="cancel">
|
||||
<span class="cancel btn btn-outline-danger mt-2">
|
||||
Cancel subscription <i class="fe fe-alert-triangle text-danger"></i>
|
||||
</span>
|
||||
</form>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block script %}
|
||||
|
||||
<script>
|
||||
$(".cancel").on("click", function (e) {
|
||||
let that = $(this);
|
||||
|
||||
bootbox.confirm({
|
||||
message: "This operation is irreversible, please confirm.",
|
||||
buttons: {
|
||||
confirm: {
|
||||
label: 'Cancel subscription',
|
||||
className: 'btn-danger'
|
||||
},
|
||||
cancel: {
|
||||
label: 'Close',
|
||||
className: 'btn-outline-primary'
|
||||
}
|
||||
},
|
||||
callback: function (result) {
|
||||
if (result) {
|
||||
that.closest("form").submit();
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
19
app/templates/dashboard/block_contact.html
Normal file
19
app/templates/dashboard/block_contact.html
Normal file
@ -0,0 +1,19 @@
|
||||
{% extends "default.html" %}
|
||||
|
||||
{% set active_page = "dashboard" %}
|
||||
{% block title %}Block a sender{% endblock %}
|
||||
{% block default_content %}
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h1 class="h3">Block sender</h1>
|
||||
<p>
|
||||
You are about to block the sender <b>{{ contact.website_email }}</b> from sending emails to
|
||||
<b>{{ contact.alias.email }}</b>
|
||||
</p>
|
||||
<form method="post">
|
||||
<button class="btn btn-warning">Confirm</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
66
app/templates/dashboard/contact_detail.html
Normal file
66
app/templates/dashboard/contact_detail.html
Normal file
@ -0,0 +1,66 @@
|
||||
{% extends "default.html" %}
|
||||
|
||||
{% set active_page = "dashboard" %}
|
||||
{% block title %}Contact {{ contact.email }} - Alias {{ alias.email }}{% endblock %}
|
||||
{% block default_content %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<h1 class="h3">
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item">
|
||||
<a href="{{ url_for('dashboard.alias_contact_manager', alias_id=alias.id) }}">{{ alias.email }}</a>
|
||||
</li>
|
||||
<li class="breadcrumb-item active" aria-current="page">
|
||||
{{ contact.email }}
|
||||
{% if contact.pgp_finger_print %}
|
||||
|
||||
<span class="cursor"
|
||||
data-toggle="tooltip"
|
||||
data-original-title="PGP Enabled">🗝</span>
|
||||
{% endif %}
|
||||
</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</h1>
|
||||
<div class="card">
|
||||
<form method="post">
|
||||
{{ pgp_form.csrf_token }}
|
||||
<input type="hidden" name="form-name" value="pgp">
|
||||
<div class="card-body">
|
||||
<div class="card-title">
|
||||
Pretty Good Privacy (PGP)
|
||||
<div class="small-text">
|
||||
By importing your contact PGP Public Key into SimpleLogin, all emails sent to
|
||||
<b>{{ contact.email }}</b> from your alias <b>{{ alias.email }}</b>
|
||||
are <b>encrypted</b>.
|
||||
</div>
|
||||
</div>
|
||||
{% if not current_user.is_premium() %}
|
||||
|
||||
<div class="alert alert-danger" role="alert">This feature is only available in premium plan.</div>
|
||||
{% endif %}
|
||||
<div class="form-group">
|
||||
<label class="form-label">PGP Public Key</label>
|
||||
<textarea name="pgp" {% if not current_user.is_premium() %} disabled {% endif %} class="form-control" rows=10 id="pgp-public-key" placeholder="-----BEGIN PGP PUBLIC KEY BLOCK-----">{{ contact.pgp_public_key or "" }}</textarea>
|
||||
</div>
|
||||
<button class="btn btn-primary" name="action" {% if not current_user.is_premium() %}
|
||||
disabled {% endif %} value="save">
|
||||
Save
|
||||
</button>
|
||||
{% if contact.pgp_finger_print %}
|
||||
|
||||
<button class="btn btn-danger float-right" name="action" value="remove">Remove</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block script %}
|
||||
|
||||
<script src="/static/js/utils/drag-drop-into-text.js"></script>
|
||||
<script>enableDragDropForPGPKeys('#pgp-public-key');</script>
|
||||
{% endblock %}
|
53
app/templates/dashboard/coupon.html
Normal file
53
app/templates/dashboard/coupon.html
Normal file
@ -0,0 +1,53 @@
|
||||
{% extends "default.html" %}
|
||||
|
||||
{% set active_page = "dashboard" %}
|
||||
{% block head %}
|
||||
|
||||
<script src="https://cdn.paddle.com/paddle/paddle.js"></script>
|
||||
<script>
|
||||
if (window.Paddle === undefined) {
|
||||
console.log("cannot load Paddle from CDN");
|
||||
document.write('<script src="/static/vendor/paddle.js"><\/script>')
|
||||
}
|
||||
</script>
|
||||
<style type="text/css">
|
||||
html.mvc__a.mvc__lot.mvc__of.mvc__classes.mvc__to.mvc__increase.mvc__the.mvc__odds.mvc__of.mvc__winning.mvc__specificity, html.mvc__a.mvc__lot.mvc__of.mvc__classes.mvc__to.mvc__increase.mvc__the.mvc__odds.mvc__of.mvc__winning.mvc__specificity > body {
|
||||
position: static;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
{% block title %}Coupon{% endblock %}
|
||||
{% block default_content %}
|
||||
|
||||
{% if can_use_coupon %}
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h1 class="h2">Coupon</h1>
|
||||
<div class="mb-4">Please enter the coupon here to upgrade your account or extend your current subscription.</div>
|
||||
<form method="post">
|
||||
{{ coupon_form.csrf_token }}
|
||||
{{ coupon_form.code(class="form-control", placeholder="Licence Code") }}
|
||||
{{ render_field_errors(coupon_form.code) }}
|
||||
<button class="btn btn-success mt-2">Apply</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h2>1-year coupon</h2>
|
||||
<div class="mb-3">
|
||||
You can buy a 1-year coupon that allows anyone to have the SimpleLogin premium for 1 year.
|
||||
Can be an idea for a gift card :).
|
||||
After the payment, the coupon will be sent to you by email.
|
||||
</div>
|
||||
<div class="alert alert-info">The coupon must be used before {{ max_coupon_date.date().isoformat() }}</div>
|
||||
<a href="#!" class="paddle_button btn btn-primary" data-product="{{ PADDLE_COUPON_ID }}">
|
||||
Buy 1-year SimpleLogin
|
||||
coupon
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block script %}<script type="text/javascript">Paddle.Setup({vendor: {{PADDLE_VENDOR_ID }} });</script>{% endblock %}
|
134
app/templates/dashboard/custom_alias.html
Normal file
134
app/templates/dashboard/custom_alias.html
Normal file
@ -0,0 +1,134 @@
|
||||
{% extends "default.html" %}
|
||||
|
||||
{% set active_page = "dashboard" %}
|
||||
{% block title %}Custom Alias{% endblock %}
|
||||
{% block default_content %}
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h1 class="h3">New Custom Alias</h1>
|
||||
{% if user_custom_domains|length == 0 and not DISABLE_ALIAS_SUFFIX %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col p-1">
|
||||
<div class="alert alert-primary" role="alert">
|
||||
You might notice a random word after the dot(<em>.</em>) in the alias.
|
||||
This part is to avoid a person taking all the "nice" aliases like
|
||||
<b>hello@{{ FIRST_ALIAS_DOMAIN }}</b>,
|
||||
<b>me@{{ FIRST_ALIAS_DOMAIN }}</b>, etc.
|
||||
<br />
|
||||
If you add your own domain, this restriction is removed, and you can fully customize the alias.
|
||||
<br />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<form method="post" data-parsley-validate>
|
||||
<div class="row mb-2">
|
||||
<div class="col-sm-6 mb-1 p-1" style="min-width: 4em">
|
||||
<input name="prefix"
|
||||
class="form-control"
|
||||
id="prefix"
|
||||
type="text"
|
||||
data-parsley-pattern="[0-9a-z-_.]{1,}"
|
||||
data-parsley-trigger="change"
|
||||
data-parsley-error-message="Only lowercase letters, dots, numbers, dashes (-) and underscores (_) are currently supported."
|
||||
maxlength="40"
|
||||
placeholder="Alias prefix, for example newsletter.com-123_xyz"
|
||||
autofocus
|
||||
required>
|
||||
</div>
|
||||
<div class="col-sm-6 p-1">
|
||||
<select class="form-control" name="signed-alias-suffix">
|
||||
{% for alias_suffix in alias_suffixes %}
|
||||
|
||||
<option value="{{ alias_suffix.signed_suffix }}" {% if alias_suffix.is_premium %}
|
||||
title="Only available to Premium accounts" {% elif not alias_suffix.is_custom and at_least_a_premium_domain %} title="Available to all accounts" {% endif %}>
|
||||
{% if alias_suffix.is_custom %}
|
||||
{% if alias_suffix.mx_verified %}
|
||||
|
||||
{{ alias_suffix.suffix }} (your domain)
|
||||
{% else %}
|
||||
{{ alias_suffix.suffix }} (your domain, not MX verified yet)
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{% if alias_suffix.is_premium %}
|
||||
|
||||
{{ alias_suffix.suffix }} (Premium domain)
|
||||
{% else %}
|
||||
{{ alias_suffix.suffix }} (Public domain)
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
<div class="col p-1">
|
||||
<select data-width="100%"
|
||||
class="mailbox-select"
|
||||
id="mailboxes"
|
||||
multiple
|
||||
name="mailboxes"
|
||||
required>
|
||||
{% for mailbox in mailboxes %}
|
||||
|
||||
<option value="{{ mailbox.id }}"
|
||||
{% if mailbox.id == current_user.default_mailbox_id %} selected{% endif %}>
|
||||
{{ mailbox.email }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<div class="small-text">The mailbox(es) that owns this alias.</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
<div class="col p-1">
|
||||
<textarea name="note"
|
||||
class="form-control"
|
||||
rows="3"
|
||||
placeholder="Note, can be anything to help you remember why you created this alias. This field is optional."></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col p-1">
|
||||
<button type="submit" id="create" class="btn btn-primary mt-1">Create</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block script %}
|
||||
|
||||
<script>
|
||||
$('.mailbox-select').multipleSelect();
|
||||
|
||||
// Ctrl-enter submit the form
|
||||
$('form').keydown(function (event) {
|
||||
if (event.ctrlKey && event.keyCode === 13) {
|
||||
$("#submit").click();
|
||||
}
|
||||
})
|
||||
|
||||
$("#create").on("click", async function () {
|
||||
let that = $(this);
|
||||
let mailbox_ids = $(`#mailboxes`).val();
|
||||
let prefix = $('#prefix').val();
|
||||
|
||||
if (mailbox_ids.length == 0) {
|
||||
toastr.error("You must select at least a mailbox", "Error");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!prefix) {
|
||||
toastr.error("Alias cannot be empty", "Error");
|
||||
return;
|
||||
}
|
||||
|
||||
that.closest("form").submit();
|
||||
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
101
app/templates/dashboard/custom_domain.html
Normal file
101
app/templates/dashboard/custom_domain.html
Normal file
@ -0,0 +1,101 @@
|
||||
{% extends "default.html" %}
|
||||
|
||||
{% set active_page = "custom_domain" %}
|
||||
{% block title %}Custom Domains{% endblock %}
|
||||
{% block head %}{% endblock %}
|
||||
{% block default_content %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<h1 class="h3">
|
||||
Custom Domains
|
||||
<a class="ml-3 text-info"
|
||||
style="font-size: 12px"
|
||||
data-toggle="collapse"
|
||||
href="#howtouse"
|
||||
role="button"
|
||||
aria-expanded="false"
|
||||
aria-controls="collapseExample">
|
||||
How to use <i class="fe fe-chevrons-down"></i>
|
||||
</a>
|
||||
</h1>
|
||||
{% if not current_user.is_premium() %}
|
||||
|
||||
<div class="alert alert-danger" role="alert">
|
||||
This feature is only available on Premium plan.
|
||||
<a href="{{ URL }}/dashboard/pricing" target="_blank" rel="noopener">
|
||||
Upgrade<i class="fe fe-external-link"></i>
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="alert alert-primary collapse {% if not custom_domains %} show{% endif %}"
|
||||
id="howtouse"
|
||||
role="alert">
|
||||
By adding your domain, you can create aliases like <b>hi@my-domain.com</b>
|
||||
<br />
|
||||
You can also enable <b>catch-all</b> to create aliases on-the-fly:
|
||||
simply use <b>anything@my-domain.com</b> and it'll be created when
|
||||
it receives an email.
|
||||
</div>
|
||||
<div class="row">
|
||||
{% for custom_domain in custom_domains %}
|
||||
|
||||
<div class="col-12 col-lg-6">
|
||||
<div class="card" style="">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">
|
||||
<a href="{{ url_for('dashboard.domain_detail', custom_domain_id=custom_domain.id) }}">{{ custom_domain.domain }}</a>
|
||||
{% if custom_domain.ownership_verified and not custom_domain.verified %}
|
||||
|
||||
<a href="{{ url_for('dashboard.domain_detail_dns', custom_domain_id=custom_domain.id, _anchor='dns-setup') }}"
|
||||
class="btn btn-info btn-sm">
|
||||
Ownership verified. Setup the DNS
|
||||
</a>
|
||||
{% elif custom_domain.ownership_verified and custom_domain.verified %}
|
||||
<span class="badge badge-success">Domain ready</span>
|
||||
<!-- custom_domain.ownership_verified is False -->
|
||||
{% else %}
|
||||
<a href="{{ url_for('dashboard.domain_detail_dns', custom_domain_id=custom_domain.id, _anchor='ownership-form') }}"
|
||||
class="btn btn-warning btn-sm"
|
||||
role="button">
|
||||
Verify domain ownership
|
||||
</a>
|
||||
{% endif %}
|
||||
</h5>
|
||||
<h6 class="card-subtitle mb-4 text-muted">
|
||||
Created {{ custom_domain.created_at | dt }}
|
||||
<br />
|
||||
<span class="font-weight-bold">{{ custom_domain.nb_alias() }}</span> aliases.
|
||||
<br />
|
||||
</h6>
|
||||
<a href="{{ url_for('dashboard.domain_detail', custom_domain_id=custom_domain.id) }}"
|
||||
class="mt-3">Details ➡</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<form method="post" class="mt-2">
|
||||
{{ new_custom_domain_form.csrf_token }}
|
||||
<input type="hidden" name="form-name" value="create">
|
||||
<h2 class="h4">New Domain</h2>
|
||||
{{ new_custom_domain_form.domain(class="form-control", placeholder="my-domain.com", maxlength=128) }}
|
||||
{{ render_field_errors(new_custom_domain_form.domain) }}
|
||||
<div class="small-text">
|
||||
Please use full path domain, for example <b>my-domain.com</b>
|
||||
or <b>my-subdomain.my-domain.com</b> if you are using a subdomain.
|
||||
</div>
|
||||
<button class="btn btn-primary mt-2">Create</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block script %}<script>$('.mailbox-select').multipleSelect();</script>{% endblock %}
|
51
app/templates/dashboard/delete_account.html
Normal file
51
app/templates/dashboard/delete_account.html
Normal file
@ -0,0 +1,51 @@
|
||||
{% extends "default.html" %}
|
||||
|
||||
{% set active_page = "setting" %}
|
||||
{% block title %}Delete account{% endblock %}
|
||||
{% block default_content %}
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="h2">Account Deletion</div>
|
||||
<div class="my-3 alert alert-warning">
|
||||
Once an account is deleted, it can't be restored.
|
||||
All its records (aliases, domains, settings, etc.) are immediately deleted.
|
||||
</div>
|
||||
<form method="post">
|
||||
<input type="hidden" name="form-name" value="delete-account">
|
||||
{{ delete_form.csrf_token }}
|
||||
<span class="delete-account btn btn-outline-danger">Delete account</span>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block script %}
|
||||
|
||||
<script>
|
||||
$(".delete-account").on("click", function (e) {
|
||||
let that = $(this);
|
||||
|
||||
bootbox.confirm({
|
||||
message: "All your data including your aliases will be deleted, " +
|
||||
"other people might not be able to reach you after, " +
|
||||
" please confirm.",
|
||||
buttons: {
|
||||
confirm: {
|
||||
label: 'Yes, delete my account',
|
||||
className: 'btn-danger'
|
||||
},
|
||||
cancel: {
|
||||
label: 'Cancel',
|
||||
className: 'btn-outline-primary'
|
||||
}
|
||||
},
|
||||
callback: function (result) {
|
||||
if (result) {
|
||||
that.closest("form").submit();
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
218
app/templates/dashboard/directory.html
Normal file
218
app/templates/dashboard/directory.html
Normal file
@ -0,0 +1,218 @@
|
||||
{% extends "default.html" %}
|
||||
|
||||
{% set active_page = "directory" %}
|
||||
{% block title %}Directory{% endblock %}
|
||||
{% block default_content %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<h1 class="h3">
|
||||
Directories
|
||||
<a class="ml-3 text-info"
|
||||
style="font-size: 12px"
|
||||
data-toggle="collapse"
|
||||
href="#howtouse"
|
||||
role="button"
|
||||
aria-expanded="false"
|
||||
aria-controls="collapseExample">
|
||||
How to use <i class="fe fe-chevrons-down"></i>
|
||||
</a>
|
||||
</h1>
|
||||
{% if not current_user.is_premium() %}
|
||||
|
||||
<div class="alert alert-danger" role="alert">This feature is only available in premium plan.</div>
|
||||
{% endif %}
|
||||
<div class="alert alert-primary collapse {% if not dirs %} show{% endif %}"
|
||||
id="howtouse"
|
||||
role="alert">
|
||||
<div>
|
||||
Directory allows you to create aliases <b>on the fly</b>.
|
||||
</div>
|
||||
<div class="mt-2 pb-2">
|
||||
1️⃣ Pick a name for your directory, says <em>my_directory</em>
|
||||
<br />
|
||||
2️⃣ Quickly use one of the following formats to create an alias on-the-fly <b>without creating this alias
|
||||
beforehand</b>
|
||||
</div>
|
||||
<div class="pl-3 py-2 bg-secondary">
|
||||
<em>my_directory/<b>anything</b>@{{ FIRST_ALIAS_DOMAIN }}</em> or
|
||||
<br />
|
||||
<em>my_directory+<b>anything</b>@{{ FIRST_ALIAS_DOMAIN }}</em> or
|
||||
<br />
|
||||
<em>my_directory#<b>anything</b>@{{ FIRST_ALIAS_DOMAIN }}</em>
|
||||
<br />
|
||||
</div>
|
||||
<em><b>anything</b></em> is any string composed of lowercase characters.
|
||||
<br />
|
||||
You can find more info on directory on our
|
||||
<a href="https://simplelogin.io/blog/alias-directory/">blog post</a>
|
||||
.
|
||||
<div class="mt-2">
|
||||
You can use this feature on the following domains:
|
||||
{% for alias_domain in ALIAS_DOMAINS %}<div class="font-weight-bold">{{ alias_domain }}</div>{% endfor %}
|
||||
</div>
|
||||
<div class="h4 text-primary mt-3">
|
||||
ℹ️
|
||||
The alias will be created the first time it receives an email.
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
{% for dir in dirs %}
|
||||
|
||||
<div class="col-12 col-lg-6">
|
||||
<div class="card" style="">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">
|
||||
<div class="d-flex">
|
||||
{{ dir.name }}
|
||||
<form method="post">
|
||||
{{ toggle_dir_form.csrf_token }}
|
||||
<input type="hidden" name="form-name" value="toggle-directory">
|
||||
{{ toggle_dir_form.directory_id( type="hidden", value=dir.id) }}
|
||||
<label class="custom-switch cursor" style="padding-left: 1rem" data-toggle="tooltip" {% if dir.disabled %}
|
||||
title="Enable directory on-the-fly alias creation" {% else %} title="Disable directory on-the-fly alias creation" {% endif %}>
|
||||
{{ toggle_dir_form.directory_enabled( class="custom-switch-input", checked=(not dir.disabled) ) }}
|
||||
<span class="custom-switch-indicator"></span>
|
||||
</label>
|
||||
</form>
|
||||
</div>
|
||||
</h5>
|
||||
<h6 class="card-subtitle mb-2 text-muted">
|
||||
{% if dir.disabled %}
|
||||
|
||||
<div class="mb-3">⚠️ On-the-fly alias creation is disabled, you can't create new aliases with this directory.</div>
|
||||
{% endif %}
|
||||
Created {{ dir.created_at | dt }}
|
||||
<br />
|
||||
<span class="font-weight-bold">{{ dir.nb_alias() }}</span> aliases.
|
||||
<br />
|
||||
<br />
|
||||
<b>Mailboxes:</b> <i class="fe fe-info"
|
||||
data-toggle="tooltip"
|
||||
title="Aliases created with this directory are automatically owned by these mailboxes"></i>
|
||||
<br />
|
||||
{% set dir_mailboxes=dir.mailboxes %}
|
||||
<form method="post" class="mt-2">
|
||||
{{ update_dir_form.csrf_token }}
|
||||
<input type="hidden" name="form-name" value="update">
|
||||
{{ update_dir_form.directory_id( type="hidden", value=dir.id) }}
|
||||
<select data-width="100%"
|
||||
required
|
||||
class="mailbox-select"
|
||||
multiple
|
||||
name="mailbox_ids">
|
||||
{% for mailbox in mailboxes %}
|
||||
|
||||
<option value="{{ mailbox.id }}"
|
||||
{% if mailbox in dir_mailboxes %} selected{% endif %}>
|
||||
{{ mailbox.email }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<button class="mt-2 btn btn-outline-primary btn-sm">Update</button>
|
||||
</form>
|
||||
</h6>
|
||||
</div>
|
||||
<div class="card-footer p-0">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<form method="post">
|
||||
{{ delete_dir_form.csrf_token }}
|
||||
<input type="hidden" name="form-name" value="delete">
|
||||
{{ delete_dir_form.directory_id( type="hidden", value=dir.id) }}
|
||||
<span class="card-link btn btn-link float-right text-danger delete-dir">Delete</span>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="row {% if current_user.directory_quota <= 0 %} disabled-content{% endif %}">
|
||||
<div class="col">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<form method="post" class="mt-2">
|
||||
{{ new_dir_form.csrf_token }}
|
||||
<input type="hidden" name="form-name" value="create">
|
||||
<h2 class="h4 mb-1">New Directory</h2>
|
||||
<div class="small-text mb-4">You can create up to {{ current_user.directory_quota }} directories.</div>
|
||||
{{ new_dir_form.name(class="form-control", placeholder="my-directory",
|
||||
pattern="[0-9a-z-_]{3,}",
|
||||
title="Only letter, number, dash (-), underscore (_) can be used. Directory name must be at least 3 characters.") }}
|
||||
{{ render_field_errors(new_dir_form.name) }}
|
||||
<div class="small-text">
|
||||
Directory name must be at least 3 characters.
|
||||
Only lowercase letters, numbers, dashes (-) and underscores (_) are currently supported.
|
||||
</div>
|
||||
<div class="mt-3 small-text alert alert-info">
|
||||
By default, aliases created with directory are "owned" by your default
|
||||
mailbox <b>{{ current_user.default_mailbox.email }}</b>.
|
||||
<br />
|
||||
You can however choose the mailbox(es) that new alias automatically belongs to by setting this below
|
||||
option.
|
||||
</div>
|
||||
<select data-width="100%" 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>
|
||||
<button id="btn-create-directory" class="btn btn-primary mt-2">Create</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block script %}
|
||||
|
||||
<script>
|
||||
$(".delete-dir").on("click", function (e) {
|
||||
let directory_name = $(this).parent().find("#directory_name").val();
|
||||
|
||||
const element = document.createElement('div');
|
||||
element.innerText = directory_name;
|
||||
const sanitized_name = element.innerHTML;
|
||||
|
||||
const message = `All aliases associated with <b>${sanitized_name}</b> directory will also be deleted. ` +
|
||||
`As a deleted directory can't be used by someone else, deleting a directory doesn't reset your directory quota. ` +
|
||||
`Your directory quota will be {{ current_user.directory_quota }} after the deletion, ` +
|
||||
" please confirm.";
|
||||
|
||||
|
||||
bootbox.confirm({
|
||||
message: message,
|
||||
buttons: {
|
||||
confirm: {
|
||||
label: 'Yes, delete it',
|
||||
className: 'btn-danger'
|
||||
},
|
||||
cancel: {
|
||||
label: 'Cancel',
|
||||
className: 'btn-outline-primary'
|
||||
}
|
||||
},
|
||||
callback: (result) => {
|
||||
if (result) {
|
||||
this.closest("form").submit();
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
});
|
||||
|
||||
$('.mailbox-select').multipleSelect();
|
||||
|
||||
$(".custom-switch-input").change(function (e) {
|
||||
$(this).closest("form").submit();
|
||||
});
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
138
app/templates/dashboard/domain_detail/auto-create.html
Normal file
138
app/templates/dashboard/domain_detail/auto-create.html
Normal file
@ -0,0 +1,138 @@
|
||||
{% 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 <b>fully</b> matches the expression,
|
||||
it'll be automatically created.
|
||||
</div>
|
||||
<div class="alert alert-info">
|
||||
Only the <b>local</b> 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" id="rule-list">
|
||||
{% 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" id="new-rule">
|
||||
<hr />
|
||||
<h3>New rule</h3>
|
||||
<form method="post" action="#rule-list" 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.*"
|
||||
) }}
|
||||
{{ 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 id="debug-zone">
|
||||
<hr />
|
||||
<h3>Debug Zone</h3>
|
||||
<p>You can test whether an alias will be automatically created given the rules above</p>
|
||||
<div class="alert alert-info">No worries, no alias will be created during the test :)</div>
|
||||
<form method="post" action="#debug-zone">
|
||||
<input type="hidden" name="form-name" value="test-auto-create-rule">
|
||||
{{ auto_create_test_form.csrf_token }}
|
||||
<div class="d-flex">
|
||||
<div class="form-group d-flex">
|
||||
{{ auto_create_test_form.local(class="form-control", type="text", placeholder="local", value=auto_create_test_local) }}
|
||||
{{ render_field_errors(auto_create_test_form.local) }}
|
||||
<b class="pt-2 ml-1">@{{ custom_domain.domain }}</b>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<button class="btn btn-outline-primary">Test</button>
|
||||
</div>
|
||||
</form>
|
||||
{% if auto_create_test_result %}
|
||||
|
||||
<div class="alert {% if auto_create_test_passed %}
|
||||
alert-success {% else %} alert-warning {% endif %}">
|
||||
{{ auto_create_test_result }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block script %}<script>$('.mailbox-select').multipleSelect();</script>{% endblock %}
|
45
app/templates/dashboard/domain_detail/base.html
Normal file
45
app/templates/dashboard/domain_detail/base.html
Normal file
@ -0,0 +1,45 @@
|
||||
{% extends "default.html" %}
|
||||
|
||||
{% if custom_domain.is_sl_subdomain %}
|
||||
|
||||
{% set active_page = "subdomain" %}
|
||||
{% else %}
|
||||
{% set active_page = "custom_domain" %}
|
||||
{% endif %}
|
||||
{% block default_content %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-3 order-lg-1 mb-4">
|
||||
<div class="list-group list-group-transparent mb-0">
|
||||
<a href="{{ url_for('dashboard.domain_detail', custom_domain_id=custom_domain.id) }}"
|
||||
class="list-group-item list-group-item-action {{ 'active' if domain_detail_page == 'info' }}">
|
||||
<span class="icon mr-3"><i class="fe fe-flag"></i></span>Info
|
||||
</a>
|
||||
{% if not custom_domain.is_sl_subdomain %}
|
||||
|
||||
<a href="{{ url_for('dashboard.domain_detail_dns', custom_domain_id=custom_domain.id) }}"
|
||||
class="list-group-item list-group-item-action {{ 'active' if domain_detail_page == 'dns' }}">
|
||||
<span class="icon mr-3"><i class="fe fe-cloud"></i></span>DNS
|
||||
</a>
|
||||
{% endif %}
|
||||
<a href="{{ url_for('dashboard.domain_detail_trash', custom_domain_id=custom_domain.id) }}"
|
||||
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>
|
||||
<div class="col-lg-9">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="text-wrap p-lg-6 domain_detail_content">
|
||||
{% block domain_detail_content %}{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
409
app/templates/dashboard/domain_detail/dns.html
Normal file
409
app/templates/dashboard/domain_detail/dns.html
Normal file
@ -0,0 +1,409 @@
|
||||
{% extends "dashboard/domain_detail/base.html" %}
|
||||
|
||||
{% set domain_detail_page = "dns" %}
|
||||
{% block title %}{{ custom_domain.domain }} DNS{% endblock %}
|
||||
{% block domain_detail_content %}
|
||||
|
||||
<div class="p-4 mr-auto" style="max-width: 60rem;">
|
||||
<h1 class="h2">{{ custom_domain.domain }}</h1>
|
||||
<div>Please follow the steps below to set up your domain.</div>
|
||||
<div class="small-text mb-5">DNS changes could take up to 24 hours to update.</div>
|
||||
{% if not custom_domain.ownership_verified %}
|
||||
|
||||
<div id="ownership-form">
|
||||
<div class="font-weight-bold">
|
||||
Domain ownership verification
|
||||
{% if custom_domain.ownership_verified %}
|
||||
|
||||
<span class="cursor"
|
||||
data-toggle="tooltip"
|
||||
data-original-title="Domain Ownership Verified">✅</span>
|
||||
{% else %}
|
||||
<span class="cursor"
|
||||
data-toggle="tooltip"
|
||||
data-original-title="Domain Ownership Required">🚫</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if not custom_domain.ownership_verified %}
|
||||
|
||||
<div class="mb-2">
|
||||
To verify ownership of the domain, please add the following TXT record.
|
||||
Some domain registrars (Namecheap, CloudFlare, etc) might use <em>@</em> for the root domain.
|
||||
</div>
|
||||
<div class="mb-3 p-3 dns-record">
|
||||
Record: TXT
|
||||
<br />
|
||||
Domain: {{ custom_domain.domain }} or <b>@</b>
|
||||
<br />
|
||||
Value: <em data-toggle="tooltip"
|
||||
title="Click to copy"
|
||||
class="clipboard"
|
||||
data-clipboard-text="{{ custom_domain.get_ownership_dns_txt_value() }}">{{ custom_domain.get_ownership_dns_txt_value() }}</em>
|
||||
</div>
|
||||
<form method="post" action="#ownership-form">
|
||||
{{ csrf_form.csrf_token }}
|
||||
<input type="hidden" name="form-name" value="check-ownership">
|
||||
<button type="submit" class="btn btn-primary">Verify</button>
|
||||
</form>
|
||||
{% if not ownership_ok %}
|
||||
|
||||
<div class="text-danger mt-4">
|
||||
Your DNS is not correctly set. The TXT record we obtain is:
|
||||
<div class="mb-3 p-3 dns-record">
|
||||
{% if not ownership_errors %}(Empty){% endif %}
|
||||
{% for r in ownership_errors %}
|
||||
|
||||
{{ r }}
|
||||
<br />
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<hr />
|
||||
{% endif %}
|
||||
<div class="{% if not custom_domain.ownership_verified %} disabled-content{% endif %}"
|
||||
id="dns-setup">
|
||||
{% if not custom_domain.ownership_verified %}
|
||||
|
||||
<div class="alert alert-warning">A domain ownership must be verified first.</div>
|
||||
{% endif %}
|
||||
<div id="mx-form">
|
||||
<div class="font-weight-bold">
|
||||
1. MX record
|
||||
{% if custom_domain.verified %}
|
||||
|
||||
<span class="cursor"
|
||||
data-toggle="tooltip"
|
||||
data-original-title="MX Record Verified">✅</span>
|
||||
{% else %}
|
||||
<span class="cursor"
|
||||
data-toggle="tooltip"
|
||||
data-original-title="MX Record Not Verified">🚫</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
Add the following MX DNS record to your domain.
|
||||
<br />
|
||||
Please note that there's a dot (<em>.</em>) at the end target addresses.
|
||||
If your domain registrar doesn't allow this trailing dot, please remove it when adding the DNS record.
|
||||
<br />
|
||||
Some domain registrars (Namecheap, CloudFlare, etc) might also use <em>@</em> for the root domain.
|
||||
</div>
|
||||
{% for priority, email_server in EMAIL_SERVERS_WITH_PRIORITY %}
|
||||
|
||||
<div class="mb-3 p-3 dns-record">
|
||||
Record: MX
|
||||
<br />
|
||||
Domain: {{ custom_domain.domain }} or
|
||||
<b>@</b>
|
||||
<br />
|
||||
Priority: {{ priority }}
|
||||
<br />
|
||||
Target: <em data-toggle="tooltip"
|
||||
title="Click to copy"
|
||||
class="clipboard"
|
||||
data-clipboard-text="{{ email_server }}">{{ email_server }}</em>
|
||||
</div>
|
||||
{% endfor %}
|
||||
<form method="post" action="#mx-form">
|
||||
{{ csrf_form.csrf_token }}
|
||||
<input type="hidden" name="form-name" value="check-mx">
|
||||
{% if custom_domain.verified %}
|
||||
|
||||
<button type="submit" class="btn btn-outline-primary">Re-verify</button>
|
||||
{% else %}
|
||||
<button type="submit" class="btn btn-primary">Verify</button>
|
||||
{% endif %}
|
||||
</form>
|
||||
{% if not mx_ok %}
|
||||
|
||||
<div class="text-danger mt-4">
|
||||
Your DNS is not correctly set. The MX record we obtain is:
|
||||
<div class="mb-3 p-3 dns-record">
|
||||
{% if not mx_errors %}(Empty){% endif %}
|
||||
{% for r in mx_errors %}
|
||||
|
||||
{{ r }}
|
||||
<br />
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% if custom_domain.verified %}
|
||||
|
||||
<div class="alert alert-danger">
|
||||
Without the MX record set up correctly, you can miss emails sent to your aliases.
|
||||
Please update the MX record ASAP.
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<hr />
|
||||
<div id="spf-form">
|
||||
<div class="font-weight-bold">
|
||||
2. SPF (Optional)
|
||||
{% if custom_domain.spf_verified %}
|
||||
|
||||
<span class="cursor"
|
||||
data-toggle="tooltip"
|
||||
data-original-title="SPF Verified">✅</span>
|
||||
{% else %}
|
||||
<span class="cursor"
|
||||
data-toggle="tooltip"
|
||||
data-original-title="SPF Not Verified">🚫</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div>
|
||||
SPF
|
||||
<a href="https://en.wikipedia.org/wiki/Sender_Policy_Framework"
|
||||
target="_blank"
|
||||
rel="noopener">(Wikipedia↗)</a>
|
||||
is an email
|
||||
authentication method
|
||||
designed to detect forging sender addresses during the delivery of the email.
|
||||
<br />
|
||||
Setting up SPF is highly recommended to reduce the chance your emails ending up in the recipient's Spam
|
||||
folder.
|
||||
</div>
|
||||
<div class="mb-2">Add the following TXT DNS record to your domain.</div>
|
||||
<div class="mb-2 p-3 dns-record">
|
||||
Record: TXT
|
||||
<br />
|
||||
Domain: {{ custom_domain.domain }} or
|
||||
<b>@</b>
|
||||
<br />
|
||||
Value:
|
||||
<em data-toggle="tooltip"
|
||||
title="Click to copy"
|
||||
class="clipboard"
|
||||
data-clipboard-text="{{ spf_record }}">
|
||||
{{ spf_record }}
|
||||
</em>
|
||||
</div>
|
||||
<form method="post" action="#spf-form">
|
||||
{{ csrf_form.csrf_token }}
|
||||
<input type="hidden" name="form-name" value="check-spf">
|
||||
{% if custom_domain.spf_verified %}
|
||||
|
||||
<button type="submit" class="btn btn-outline-primary">Re-verify</button>
|
||||
{% else %}
|
||||
<button type="submit" class="btn btn-primary">Verify</button>
|
||||
{% endif %}
|
||||
</form>
|
||||
{% if not spf_ok %}
|
||||
|
||||
<div class="text-danger mt-4">
|
||||
Your DNS is not correctly set. The TXT record we obtain is:
|
||||
<div class="mb-3 p-3 dns-record">
|
||||
{% if not spf_errors %}(Empty){% endif %}
|
||||
{% for r in spf_errors %}
|
||||
|
||||
{{ r }}
|
||||
<br />
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% if custom_domain.spf_verified %}
|
||||
|
||||
Without SPF setup, emails you sent from your alias might end up in Spam/Junk folder.
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<hr />
|
||||
<div id="dkim-form">
|
||||
<div class="font-weight-bold">
|
||||
3. DKIM (Optional)
|
||||
{% if custom_domain.dkim_verified %}
|
||||
|
||||
<span class="cursor"
|
||||
data-toggle="tooltip"
|
||||
data-original-title="SPF Verified">✅</span>
|
||||
{% else %}
|
||||
<span class="cursor"
|
||||
data-toggle="tooltip"
|
||||
data-original-title="DKIM Not Verified">🚫</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div>
|
||||
DKIM
|
||||
<a href="https://en.wikipedia.org/wiki/DomainKeys_Identified_Mail"
|
||||
target="_blank"
|
||||
rel="noopener">(Wikipedia↗)</a>
|
||||
is an
|
||||
email
|
||||
authentication method
|
||||
designed to avoid email spoofing.
|
||||
<br />
|
||||
Setting up DKIM is highly recommended to reduce the chance your emails ending up in the recipient's Spam
|
||||
folder.
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
Add the following CNAME DNS records to your domain.
|
||||
</div>
|
||||
{% for dkim_prefix, dkim_cname_value in dkim_records %}
|
||||
|
||||
<div class="mb-2 p-3 dns-record">
|
||||
Record: CNAME
|
||||
<br />
|
||||
Domain: <em data-toggle="tooltip"
|
||||
title="Click to copy"
|
||||
class="clipboard"
|
||||
data-clipboard-text="{{ dkim_prefix }}">{{ dkim_prefix }}</em>
|
||||
<br />
|
||||
Value:
|
||||
<em data-toggle="tooltip"
|
||||
title="Click to copy"
|
||||
class="clipboard"
|
||||
data-clipboard-text="{{ dkim_cname_value }}."
|
||||
style="overflow-wrap: break-word">
|
||||
{{ dkim_cname_value }}.
|
||||
</em>
|
||||
</div>
|
||||
{% endfor %}
|
||||
<div class="alert alert-info">
|
||||
Some DNS registrar might require a full record path, in this case please use
|
||||
<i>dkim._domainkey.{{ custom_domain.domain }}</i> as domain value instead.
|
||||
<br />
|
||||
If you are using a subdomain, e.g. <i>subdomain.domain.com</i>,
|
||||
you need to use <i>dkim._domainkey.subdomain</i> as domain value instead.
|
||||
<br />
|
||||
</div>
|
||||
<div class="alert alert-info">
|
||||
If you are using CloudFlare, please make sure to <b>not</b> select the Proxy option.
|
||||
<br />
|
||||
<br />
|
||||
<img src="/static/images/cloudflare-proxy.png" class="w-100">
|
||||
</div>
|
||||
<form method="post" action="#dkim-form">
|
||||
{{ csrf_form.csrf_token }}
|
||||
<input type="hidden" name="form-name" value="check-dkim">
|
||||
{% if custom_domain.dkim_verified %}
|
||||
|
||||
<button type="submit" class="btn btn-outline-primary">
|
||||
Re-verify
|
||||
</button>
|
||||
{% else %}
|
||||
<button type="submit" class="btn btn-primary">
|
||||
Verify
|
||||
</button>
|
||||
{% endif %}
|
||||
</form>
|
||||
{% if not dkim_ok %}
|
||||
|
||||
<div class="text-danger mt-4">
|
||||
<p>
|
||||
Your DNS is not correctly set.
|
||||
</p>
|
||||
<ul>
|
||||
{% for custom_record, retrieved_cname in dkim_errors.items() %}
|
||||
|
||||
<li>
|
||||
The CNAME record we obtain for <em>{{ custom_record }}</em> is {{ retrieved_cname }}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% if custom_domain.dkim_verified %}
|
||||
|
||||
Without DKIM setup, emails you sent from your alias might end up in Spam/Junk folder.
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if custom_domain.dkim_verified %}
|
||||
|
||||
<div class="text-danger mt-4">
|
||||
DKIM is still enabled. Please update your DKIM settings with all CNAME records
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
<hr />
|
||||
<div id="dmarc-form">
|
||||
<div class="font-weight-bold">
|
||||
4. DMARC (Optional)
|
||||
{% if custom_domain.dmarc_verified %}
|
||||
|
||||
<span class="cursor"
|
||||
data-toggle="tooltip"
|
||||
data-original-title="DMARC Verified">✅</span>
|
||||
{% else %}
|
||||
<span class="cursor"
|
||||
data-toggle="tooltip"
|
||||
data-original-title="DMARC Not Verified">🚫 </span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div>
|
||||
DMARC
|
||||
<a href="https://en.wikipedia.org/wiki/DMARC"
|
||||
target="_blank"
|
||||
rel="noopener">
|
||||
(Wikipedia↗)
|
||||
</a>
|
||||
is designed to protect the domain from unauthorized use, commonly known as email spoofing.
|
||||
<br />
|
||||
Built around SPF and DKIM, a DMARC policy tells the receiving mail server what to do if
|
||||
neither of those authentication methods passes.
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
Add the following TXT DNS record to your domain.
|
||||
</div>
|
||||
<div class="mb-2 p-3 dns-record">
|
||||
Record: TXT
|
||||
<br />
|
||||
Domain: <em data-toggle="tooltip"
|
||||
title="Click to copy"
|
||||
class="clipboard"
|
||||
data-clipboard-text="_dmarc">_dmarc</em>
|
||||
<br />
|
||||
Value:
|
||||
<em data-toggle="tooltip"
|
||||
title="Click to copy"
|
||||
class="clipboard"
|
||||
data-clipboard-text="{{ dmarc_record }}">
|
||||
{{ dmarc_record }}
|
||||
</em>
|
||||
</div>
|
||||
<div class="alert alert-info">
|
||||
Some DNS registrar might require a full record path, in this case please use
|
||||
<i>_dmarc.{{ custom_domain.domain }}</i> as domain value instead.
|
||||
<br />
|
||||
If you are using a subdomain, e.g. <i>subdomain.domain.com</i>,
|
||||
you need to use <i>_dmarc.subdomain</i> as domain value instead.
|
||||
<br />
|
||||
</div>
|
||||
<form method="post" action="#dmarc-form">
|
||||
{{ csrf_form.csrf_token }}
|
||||
<input type="hidden" name="form-name" value="check-dmarc">
|
||||
{% if custom_domain.dmarc_verified %}
|
||||
|
||||
<button type="submit" class="btn btn-outline-primary">
|
||||
Re-verify
|
||||
</button>
|
||||
{% else %}
|
||||
<button type="submit" class="btn btn-primary">
|
||||
Verify
|
||||
</button>
|
||||
{% endif %}
|
||||
</form>
|
||||
{% if not dmarc_ok %}
|
||||
|
||||
<div class="text-danger mt-4">
|
||||
Your DNS is not correctly set.
|
||||
The TXT record we obtain is:
|
||||
<div class="mb-3 p-3" style="background-color: #eee">
|
||||
{% if not dmarc_errors %}(Empty){% endif %}
|
||||
{% for r in dmarc_errors %}
|
||||
|
||||
{{ r }}
|
||||
<br />
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% if custom_domain.dmarc_verified %}
|
||||
|
||||
Without DMARC setup, emails sent from your alias might end up in the Spam/Junk folder.
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
179
app/templates/dashboard/domain_detail/info.html
Normal file
179
app/templates/dashboard/domain_detail/info.html
Normal file
@ -0,0 +1,179 @@
|
||||
{% extends "dashboard/domain_detail/base.html" %}
|
||||
|
||||
{% set domain_detail_page = "info" %}
|
||||
{% block title %}{{ custom_domain.domain }} Info{% endblock %}
|
||||
{% block domain_detail_content %}
|
||||
|
||||
<h1 class="h2 mb-1">{{ custom_domain.domain }}</h1>
|
||||
<div class="small-text">Created {{ custom_domain.created_at | dt }}. {{ nb_alias }} aliases</div>
|
||||
<hr />
|
||||
<h3 class="mb-1">Auto create/on the fly alias</h3>
|
||||
<div>
|
||||
<form method="post">
|
||||
{{ csrf_form.csrf_token }}
|
||||
<input type="hidden" name="form-name" value="switch-catch-all">
|
||||
<label class="custom-switch cursor mt-2 pl-0" data-toggle="tooltip" {% if custom_domain.catch_all %}
|
||||
title="Disable catch-all" {% else %} title="Enable catch-all" {% endif %}>
|
||||
<input type="checkbox" class="custom-switch-input" {{ "checked" if custom_domain.catch_all else "" }}>
|
||||
<span class="custom-switch-indicator"></span>
|
||||
<spam class="ml-2">
|
||||
Catch All
|
||||
</spam>
|
||||
</label>
|
||||
</form>
|
||||
<div>
|
||||
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 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>
|
||||
<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>
|
||||
.
|
||||
</div>
|
||||
{% set domain_mailboxes=custom_domain.mailboxes %}
|
||||
<form method="post" class="mt-2">
|
||||
{{ csrf_form.csrf_token }}
|
||||
<input type="hidden" name="form-name" value="update">
|
||||
<input type="hidden" name="domain-id" value="{{ custom_domain.id }}">
|
||||
<div class="d-flex">
|
||||
<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 in domain_mailboxes %} selected{% endif %}>
|
||||
{{ mailbox.email }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<button class="btn btn-outline-primary btn-sm">Update</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<hr />
|
||||
<h3 class="mb-1">Default Display Name</h3>
|
||||
<div>
|
||||
Default display name for aliases created with <b>{{ custom_domain.domain }}</b>
|
||||
unless overwritten by the alias display name.
|
||||
</div>
|
||||
<div>
|
||||
<form method="post" class="form-inline">
|
||||
{{ csrf_form.csrf_token }}
|
||||
<input type="hidden" name="form-name" value="set-name">
|
||||
<div class="form-group">
|
||||
<input class="form-control mr-2"
|
||||
value="{{ custom_domain.name or "" }}"
|
||||
name="alias-name"
|
||||
placeholder="Alias Display Name">
|
||||
</div>
|
||||
<button class="btn btn-outline-primary" name="action" value="save">Save</button>
|
||||
{% if custom_domain.name %}
|
||||
|
||||
<button class="btn btn-outline-danger float-right ml-2"
|
||||
name="action"
|
||||
value="remove">Remove</button>
|
||||
{% endif %}
|
||||
</form>
|
||||
</div>
|
||||
<hr />
|
||||
<h3 class="mb-1">Random Prefix Generation</h3>
|
||||
<div>Add a random prefix for this domain when creating a new alias.</div>
|
||||
<div>
|
||||
<form method="post">
|
||||
{{ csrf_form.csrf_token }}
|
||||
<input type="hidden"
|
||||
name="form-name"
|
||||
value="switch-random-prefix-generation">
|
||||
<label class="custom-switch cursor mt-2 pl-0" data-toggle="tooltip" {% if custom_domain.random_prefix_generation %}
|
||||
title="Disable random prefix generation" {% else %} title="Enable random prefix generation" {% endif %}>
|
||||
<input type="checkbox" class="custom-switch-input" {{ "checked" if custom_domain.random_prefix_generation else "" }}>
|
||||
<span class="custom-switch-indicator"></span>
|
||||
</label>
|
||||
</form>
|
||||
</div>
|
||||
<hr />
|
||||
<h3 class="mb-1">
|
||||
{% if custom_domain.is_sl_subdomain %}
|
||||
|
||||
Delete Subdomain
|
||||
{% else %}
|
||||
Delete Domain
|
||||
{% endif %}
|
||||
</h3>
|
||||
<div class="mb-3">
|
||||
{% if custom_domain.is_sl_subdomain %}
|
||||
|
||||
<div class="alert alert-danger">
|
||||
This operation is <b>irreversible</b>.
|
||||
All aliases associated with this subdomain will be deleted.
|
||||
</div>
|
||||
<div class="alert alert-warning">
|
||||
Because a deleted subdomain can't be recycled, i.e. reused by someone else,
|
||||
deleting a subdomain won't restore the subdomain quota.
|
||||
After deletion, your subdomain quota will still be {{ current_user.subdomain_quota }}.
|
||||
We recommend to disable the <b>catch-all</b> option instead of deleting this subdomain.
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="alert alert-danger">
|
||||
This operation is <b>irreversible</b>.
|
||||
All aliases associated with this domain will be deleted.
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<form method="post">
|
||||
{{ csrf_form.csrf_token }}
|
||||
<input type="hidden" name="form-name" value="delete">
|
||||
<span class="delete-custom-domain btn btn-danger">Delete {{ custom_domain.domain }}</span>
|
||||
</form>
|
||||
{% endblock %}
|
||||
{% block script %}
|
||||
|
||||
<script>
|
||||
$('.mailbox-select').multipleSelect();
|
||||
|
||||
$(".custom-switch-input").change(function (e) {
|
||||
$(this).closest("form").submit();
|
||||
});
|
||||
|
||||
$(".delete-custom-domain").on("click", function (e) {
|
||||
let that = $(this);
|
||||
|
||||
bootbox.confirm({
|
||||
message: "All aliases associated with <b>{{ custom_domain.domain }}</b> will be also deleted. <br />" +
|
||||
"This operation is not reversible, please confirm.",
|
||||
buttons: {
|
||||
confirm: {
|
||||
label: 'Yes, delete it',
|
||||
className: 'btn-danger'
|
||||
},
|
||||
cancel: {
|
||||
label: 'Cancel',
|
||||
className: 'btn-outline-primary'
|
||||
}
|
||||
},
|
||||
callback: function (result) {
|
||||
if (result) {
|
||||
that.closest("form").submit();
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
52
app/templates/dashboard/domain_detail/trash.html
Normal file
52
app/templates/dashboard/domain_detail/trash.html
Normal file
@ -0,0 +1,52 @@
|
||||
{% extends "dashboard/domain_detail/base.html" %}
|
||||
|
||||
{% set domain_detail_page = "trash" %}
|
||||
{% block title %}{{ custom_domain.domain }} deleted aliases{% endblock %}
|
||||
{% block domain_detail_content %}
|
||||
|
||||
<h1 class="h3">
|
||||
{{ custom_domain.domain }} deleted alias (aka Trash)
|
||||
<a class="ml-3 text-info"
|
||||
style="font-size: 12px"
|
||||
data-toggle="collapse"
|
||||
href="#howtouse"
|
||||
role="button"
|
||||
aria-expanded="false"
|
||||
aria-controls="collapseExample">
|
||||
How to use <i class="fe fe-chevrons-down"></i>
|
||||
</a>
|
||||
</h1>
|
||||
<div class="alert alert-primary collapse" id="howtouse" role="alert">
|
||||
On this page you can view all aliases that have been deleted and belong to the domain
|
||||
<b>{{ custom_domain.domain }}</b>.
|
||||
<br />
|
||||
When an alias is in the trash, it cannot be re-created, either via the alias creation page or on-the-fly with the
|
||||
domain catch-all option.
|
||||
</div>
|
||||
{% if domain_deleted_aliases | length > 0 %}
|
||||
|
||||
<form method="post">
|
||||
{{ csrf_form.csrf_token }}
|
||||
<input type="hidden" name="form-name" value="empty-all">
|
||||
<button class="btn btn-outline-danger">Empty Trash</button>
|
||||
<div class="small-text">
|
||||
Remove all deleted aliases from the trash, allowing them to be re-created.
|
||||
That operation is irreversible.
|
||||
</div>
|
||||
</form>
|
||||
{% else %}
|
||||
There's no deleted alias recorded for this domain.
|
||||
{% endif %}
|
||||
{% for deleted_alias in domain_deleted_aliases %}
|
||||
|
||||
<hr />
|
||||
<b>{{ deleted_alias.email }}</b> -
|
||||
deleted {{ deleted_alias.created_at | dt }}
|
||||
<form method="post">
|
||||
{{ csrf_form.csrf_token }}
|
||||
<input type="hidden" name="form-name" value="remove-single">
|
||||
<input type="hidden" name="deleted-alias-id" value="{{ deleted_alias.id }}">
|
||||
<button class="btn btn-sm btn-outline-warning">Remove from trash</button>
|
||||
</form>
|
||||
{% endfor %}
|
||||
{% endblock %}
|
32
app/templates/dashboard/enter_sudo.html
Normal file
32
app/templates/dashboard/enter_sudo.html
Normal file
@ -0,0 +1,32 @@
|
||||
{% extends "default.html" %}
|
||||
|
||||
{% set active_page = "setting" %}
|
||||
{% block title %}SUDO MODE{% endblock %}
|
||||
{% block default_content %}
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h1 class="h2">Entering Sudo Mode</h1>
|
||||
<p>The next page contains security related setting.</p>
|
||||
<p>Please enter your account password so that we can ensure it's you.</p>
|
||||
<form method="post">
|
||||
{{ password_check_form.csrf_token }}
|
||||
<div class="font-weight-bold mt-5">Password</div>
|
||||
{{ password_check_form.password(class="form-control", autofocus="true") }}
|
||||
{{ render_field_errors(password_check_form.password) }}
|
||||
<button class="btn btn-lg btn-danger mt-2">Submit</button>
|
||||
</form>
|
||||
{% if connect_with_proton %}
|
||||
|
||||
<div class="my-3">
|
||||
<p>Alternatively you can use your Proton credentials to ensure it's you.</p>
|
||||
</div>
|
||||
<a class="btn btn-primary btn-block mt-2 proton-button w-25"
|
||||
href="{{ url_for("auth.proton_login", next=next) }}">
|
||||
<img class="mr-2" src="/static/images/proton.svg" />
|
||||
Authenticate with Proton
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
24
app/templates/dashboard/extend_subscription.html
Normal file
24
app/templates/dashboard/extend_subscription.html
Normal file
@ -0,0 +1,24 @@
|
||||
{% extends "default.html" %}
|
||||
|
||||
{% set active_page = "dashboard" %}
|
||||
{% block title %}Extend Subscription{% endblock %}
|
||||
{% block default_content %}
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h1 class="h2">Extend Subscription</h1>
|
||||
<p>Your subscription is expired on {{ coinbase_subscription.end_at.format("YYYY-MM-DD") }}</p>
|
||||
<div>
|
||||
<a class="buy-with-crypto"
|
||||
data-custom="{{ current_user.id }}"
|
||||
href="{{ coinbase_url }}">Extend for 1 year - $30</a>
|
||||
<script src="https://commerce.coinbase.com/v1/checkout.js?version=201807"></script>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
Your subscription will be extended when the payment is confirmed and we'll send you a confirmation email.
|
||||
<br />
|
||||
Please note that it can take up to 1h for processing a cryptocurrency payment.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
57
app/templates/dashboard/fido_manage.html
Normal file
57
app/templates/dashboard/fido_manage.html
Normal file
@ -0,0 +1,57 @@
|
||||
{% extends "default.html" %}
|
||||
|
||||
{% set active_page = "setting" %}
|
||||
{% block title %}Manage Security Key{% endblock %}
|
||||
{% block head %}
|
||||
|
||||
<script src="{{ url_for('static', filename='node_modules/qrious/dist/qrious.min.js') }}"></script>
|
||||
{% endblock %}
|
||||
{% block default_content %}
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h1 class="h2">Manage Your Security Key</h1>
|
||||
<p>Unlink all keys will also disable WebAuthn 2FA.</p>
|
||||
<form id="formManageKey" method="post">
|
||||
{{ fido_manage_form.csrf_token }}
|
||||
{{ fido_manage_form.credential_id(class="form-control", placeholder="") }}
|
||||
</form>
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">ID</th>
|
||||
<th scope="col">Name</th>
|
||||
<th scope="col">Linked At</th>
|
||||
<th scope="col" class="text-center">Operation</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for key in keys %}
|
||||
|
||||
<tr>
|
||||
<th scope="row">{{ key.id }}</th>
|
||||
<td>{{ key.name }}</td>
|
||||
<td>{{ key.created_at | dt }}</td>
|
||||
<td class="text-center">
|
||||
<button class="btn btn-outline-danger"
|
||||
onclick="$('#credential_id').val('{{ key.credential_id }}'); $('#formManageKey').submit();">
|
||||
Unlink
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
<tr>
|
||||
<th scope="row">#</th>
|
||||
<td>Link a New Key</td>
|
||||
<td></td>
|
||||
<td class="text-center">
|
||||
<a href="{{ url_for('dashboard.fido_setup') }}">
|
||||
<button class="btn btn-outline-success">Link</button>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
73
app/templates/dashboard/fido_setup.html
Normal file
73
app/templates/dashboard/fido_setup.html
Normal file
@ -0,0 +1,73 @@
|
||||
{% extends "default.html" %}
|
||||
|
||||
{% set active_page = "setting" %}
|
||||
{% block title %}Security Key Setup{% endblock %}
|
||||
{% block head %}
|
||||
|
||||
<script src="{{ url_for('static', filename='node_modules/qrious/dist/qrious.min.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='assets/js/vendors/base64.js') }}"></script>
|
||||
<script src="/static/assets/js/vendors/webauthn.js?v={{ VERSION }}"></script>
|
||||
{% endblock %}
|
||||
{% block default_content %}
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h1 class="h2 text-center">Register Your Security Key</h1>
|
||||
<p class="text-center">Follow your browser's steps to register your security key with SimpleLogin</p>
|
||||
<form id="formRegisterKey" method="post">
|
||||
{{ fido_token_form.csrf_token }}
|
||||
{{ fido_token_form.sk_assertion(class="form-control", placeholder="") }}
|
||||
{{ fido_token_form.key_name(id="key-name", class="form-control", placeholder="Name of your key (Required)") }}
|
||||
{{ render_field_errors(fido_token_form.key_name) }}
|
||||
<div class="text-center">
|
||||
<span id="btnRegisterKey"
|
||||
class="btn btn-lg btn-primary mt-2"
|
||||
onclick="registerKey();">Register Key</span>
|
||||
</div>
|
||||
</form>
|
||||
<script>
|
||||
// disable submit when enter
|
||||
$('form input').keydown(function (e) {
|
||||
if (e.keyCode == 13) {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
async function registerKey() {
|
||||
// make sure key name is not empty
|
||||
if (!$("#key-name").val()) {
|
||||
toastr.error("Key name cannot be empty.");
|
||||
return
|
||||
}
|
||||
|
||||
$("#btnRegisterKey").prop('disabled', true);
|
||||
$("#btnRegisterKey").text('Waiting for Security Key...');
|
||||
|
||||
const pkCredentialCreateOptions = transformCredentialCreateOptions(
|
||||
JSON.parse('{{credential_create_options|tojson|safe}}')
|
||||
)
|
||||
|
||||
|
||||
let credential
|
||||
try {
|
||||
credential = await navigator.credentials.create({
|
||||
publicKey: pkCredentialCreateOptions
|
||||
});
|
||||
} catch (err) {
|
||||
toastr.error("An error occurred when we trying to register your key.");
|
||||
$("#btnRegisterKey").prop('disabled', false);
|
||||
$("#btnRegisterKey").text('Register Key');
|
||||
return console.error("Error when trying to create credential:", err);
|
||||
}
|
||||
|
||||
const skAssertion = transformNewAssertionForServer(credential);
|
||||
|
||||
$('#sk_assertion').val(JSON.stringify(skAssertion));
|
||||
$('#formRegisterKey').submit();
|
||||
}
|
||||
|
||||
</script>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
559
app/templates/dashboard/index.html
Normal file
559
app/templates/dashboard/index.html
Normal file
@ -0,0 +1,559 @@
|
||||
{% extends "default.html" %}
|
||||
|
||||
{% set active_page = "dashboard" %}
|
||||
{% block head %}
|
||||
|
||||
<style>
|
||||
.alias-activity {
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.btn-group-border-left {
|
||||
border-left: 1px #fbfbfb4f solid;
|
||||
}
|
||||
|
||||
em {
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
{#CSS to change collapse button display from https://stackoverflow.com/a/31967516/1428034#}
|
||||
[data-toggle="collapse"].collapsed .if-not-collapsed {
|
||||
display: none;
|
||||
}
|
||||
|
||||
[data-toggle="collapse"]:not(.collapsed) .if-collapsed {
|
||||
display: none;
|
||||
}
|
||||
|
||||
</style>
|
||||
{% endblock %}
|
||||
{% block title %}Alias{% endblock %}
|
||||
{% block default_content %}
|
||||
|
||||
<!-- Global Stats -->
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-6 col-lg-3">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="subheader">Aliases</div>
|
||||
<div class="text-muted"
|
||||
style="order: 2; margin-left: auto; font-size: .8rem">All time</div>
|
||||
</div>
|
||||
<div class="h1 m-0">{{ stats.nb_alias }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-md-6 col-lg-3">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="subheader">Forwarded</div>
|
||||
<div class="text-muted"
|
||||
style="order: 2; margin-left: auto; font-size: .8rem">Last 14 days</div>
|
||||
</div>
|
||||
<div class="h1 m-0">{{ stats.nb_forward }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-md-6 col-lg-3">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="subheader">Replies/Sent</div>
|
||||
<div class="text-muted"
|
||||
style="order: 2; margin-left: auto; font-size: .8rem">Last 14 days</div>
|
||||
</div>
|
||||
<div class="h1 m-0">{{ stats.nb_reply }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-md-6 col-lg-3">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="subheader">Blocked</div>
|
||||
<div class="text-muted"
|
||||
style="order: 2; margin-left: auto; font-size: .8rem">Last 14 days</div>
|
||||
</div>
|
||||
<div class="h1 m-0">{{ stats.nb_block }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- END Global Stats -->
|
||||
<!-- Controls: buttons & search -->
|
||||
<div id="filter-app">
|
||||
<div class="row mb-3">
|
||||
<div class="col d-flex">
|
||||
<div>
|
||||
<div class="btn-group" role="group">
|
||||
<form method="post">
|
||||
{{ csrf_form.csrf_token }}
|
||||
<input type="hidden" name="form-name" value="create-custom-email">
|
||||
<button data-toggle="tooltip"
|
||||
title="Create a custom alias"
|
||||
class="btn btn-primary mr-2">
|
||||
<i class="fa fa-plus"></i> New Custom Alias
|
||||
</button>
|
||||
</form>
|
||||
<div class="btn-group" role="group">
|
||||
<form method="post">
|
||||
{{ csrf_form.csrf_token }}
|
||||
<input type="hidden" name="form-name" value="create-random-email">
|
||||
<button data-toggle="tooltip"
|
||||
title="Create a totally random alias"
|
||||
class="btn btn-success">
|
||||
<i class="fa fa-random"></i> Random Alias
|
||||
</button>
|
||||
</form>
|
||||
<button id="btnGroupDrop1"
|
||||
type="button"
|
||||
class="btn btn-success dropdown-toggle btn-group-border-left"
|
||||
data-toggle="dropdown"
|
||||
aria-haspopup="true"
|
||||
aria-expanded="false">
|
||||
</button>
|
||||
<div class="dropdown-menu dropdown-menu-right border-left"
|
||||
aria-labelledby="btnGroupDrop1">
|
||||
<div>
|
||||
<form method="post">
|
||||
{{ csrf_form.csrf_token }}
|
||||
<input type="hidden" name="form-name" value="create-random-email">
|
||||
<input type="hidden"
|
||||
name="generator_scheme"
|
||||
value="{{ AliasGeneratorEnum.word.value }}">
|
||||
<button class="dropdown-item">By Random Words</button>
|
||||
</form>
|
||||
</div>
|
||||
<div>
|
||||
<form method="post">
|
||||
{{ csrf_form.csrf_token }}
|
||||
<input type="hidden" name="form-name" value="create-random-email">
|
||||
<input type="hidden"
|
||||
name="generator_scheme"
|
||||
value="{{ AliasGeneratorEnum.uuid.value }}">
|
||||
<button class="dropdown-item">By UUID</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="margin-left: auto">
|
||||
<div class="btn-group">
|
||||
<a v-if="!showFilter"
|
||||
@click="toggleFilter()"
|
||||
class="btn btn-outline-secondary">
|
||||
<i class="fe fe-chevrons-down"></i> Filters
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2" v-if="showFilter" id="filter-control">
|
||||
<!-- Filter Control -->
|
||||
<div class="col d-flex">
|
||||
<form method="get" class="form-inline">
|
||||
{{ csrf_form.csrf_token }}
|
||||
<select name="sort"
|
||||
onchange="this.form.submit()"
|
||||
class="form-control mr-3 shadow">
|
||||
<option value="" {% if sort == "" %} selected{% endif %}>
|
||||
Sort by most recent activity
|
||||
</option>
|
||||
<option value="old2new" {% if sort == "old2new" %} selected{% endif %}>
|
||||
Alias Old-Recent
|
||||
</option>
|
||||
<option value="new2old" {% if sort == "new2old" %} selected{% endif %}>
|
||||
Alias Recent-Old
|
||||
</option>
|
||||
<option value="a2z" {% if sort == "a2z" %} selected{% endif %}>
|
||||
Alias A-Z
|
||||
</option>
|
||||
<option value="z2a" {% if sort == "z2a" %} selected{% endif %}>
|
||||
Alias Z-A
|
||||
</option>
|
||||
</select>
|
||||
<select name="filter"
|
||||
onchange="this.form.submit()"
|
||||
class="form-control mr-3 shadow"
|
||||
style="max-width: 200px">
|
||||
<option value="" {% if filter == "" %} selected{% endif %}>
|
||||
All Aliases
|
||||
</option>
|
||||
<option value="pinned" {% if filter == "pinned" %} selected{% endif %}>
|
||||
Pinned Aliases
|
||||
</option>
|
||||
<option value="enabled" {% if filter == "enabled" %} selected{% endif %}>
|
||||
Only Enabled Aliases
|
||||
</option>
|
||||
<option value="disabled" {% if filter == "disabled" %} selected{% endif %}>
|
||||
Only Disabled Aliases
|
||||
</option>
|
||||
<option value="hibp" {% if filter == "hibp" %} selected{% endif %}>
|
||||
Only Aliases Found In Data Breaches
|
||||
</option>
|
||||
{% for mailbox in current_user.mailboxes() %}
|
||||
|
||||
<option value="mailbox:{{ mailbox.id }}" {% if filter == "mailbox:" ~ mailbox.id %}
|
||||
selected {% endif %}>
|
||||
{{ mailbox.email }}'s aliases
|
||||
</option>
|
||||
{% endfor %}
|
||||
{% for directory in current_user.directories %}
|
||||
|
||||
<option value="directory:{{ directory.id }}" {% if filter == "directory:" ~ directory.id %}
|
||||
selected {% endif %}>
|
||||
Directory <b>{{ directory.name }}</b> aliases
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<input type="search"
|
||||
name="query"
|
||||
placeholder="Enter to search for alias"
|
||||
class="form-control shadow mr-2"
|
||||
style="max-width: 15em"
|
||||
value="{{ query }}">
|
||||
</form>
|
||||
<div style="margin-left: auto">
|
||||
{% if query or sort or filter %}
|
||||
|
||||
<a href="{{ url_for('dashboard.index') }}"
|
||||
class="btn btn-outline-secondary">Reset</a>
|
||||
{% endif %}
|
||||
<a v-if="showFilter"
|
||||
@click="toggleFilter()"
|
||||
class="btn btn-outline-secondary">
|
||||
<i class="fe fe-chevrons-up"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- END Controls: buttons & search -->
|
||||
<!-- Alias list -->
|
||||
<div class="row">
|
||||
{% for alias_info in alias_infos %}
|
||||
|
||||
{% set alias = alias_info.alias %}
|
||||
<div class="col-12 col-lg-6" id="alias-container-{{ alias.id }}">
|
||||
<div class="card p-4 shadow-sm {% if alias.id == highlight_alias_id %} highlight-row{% endif %} ">
|
||||
<div class="row">
|
||||
<div class="col-8">
|
||||
<span class="{% if alias.id == highlight_alias_id %} highlighted{% endif %} clipboard cursor mb-0" {% if loop.index ==1 %}
|
||||
data-intro="This is your first <em>alias</em>.
|
||||
<br />
|
||||
<br />
|
||||
Emails sent to an alias are <em>forwarded</em> to your <em>real</em> email address.
|
||||
<br />
|
||||
<br />
|
||||
" data-step="2"
|
||||
{% endif %}
|
||||
{% if alias.enabled %}data-toggle="tooltip" title="Click to copy" data-clipboard-text="{{ alias.email }}"{% endif %}
|
||||
>
|
||||
<span class="font-weight-bold">{{ alias.email }}</span>
|
||||
</span>
|
||||
{% if alias.automatic_creation %}
|
||||
|
||||
<span class="fa fa-inbox"
|
||||
data-toggle="tooltip"
|
||||
title="This alias was automatically generated because of an incoming email"></span>
|
||||
{% endif %}
|
||||
{% if alias.pinned %}
|
||||
|
||||
<span class="fa fa-thumb-tack"
|
||||
data-toggle="tooltip"
|
||||
title="This alias is pinned"></span>
|
||||
{% endif %}
|
||||
{% if alias.hibp_breaches | length > 0 %}
|
||||
|
||||
<a href="https://haveibeenpwned.com/account/{{ alias.email }}">
|
||||
<span class="fa fa-warning text-danger"
|
||||
data-toggle="tooltip"
|
||||
title="This alias was found in {{ alias.hibp_breaches | length }} data breaches. Check haveibeenpwned.com for more information."></span>
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if alias.custom_domain and not alias.custom_domain.verified %}
|
||||
|
||||
<span class="fa fa-warning text-warning"
|
||||
data-toggle="tooltip"
|
||||
title="Alias can't receive emails as its domain doesn't have MX records set up."></span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="col text-right">
|
||||
<label class="custom-switch cursor" data-toggle="tooltip" {% if alias.enabled %}
|
||||
title="Disable alias - stop receiving emails sent to this alias" {% else %} title="Enable alias - start receiving emails sent to this alias" {% endif %} {% if loop.index ==1 %}
|
||||
data-intro="By <em>disabling</em> an alias, emails sent to this alias will <em>not</em> be forwarded to your inbox.
|
||||
<br />
|
||||
<br />
|
||||
" data-step="3"
|
||||
{% endif %}
|
||||
style="padding-left: 0px">
|
||||
<input type="checkbox" class="enable-disable-alias custom-switch-input" data-alias="{{ alias.id }}" data-alias-email="{{ alias.email }}" {{ "checked" if alias.enabled else "" }}>
|
||||
<span class="custom-switch-indicator"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Email Activity -->
|
||||
<div class="row mb-2">
|
||||
<div class="col">
|
||||
<div style="font-size: 12px">
|
||||
{% if alias_info.latest_email_log != None %}
|
||||
|
||||
{% set email_log = alias_info.latest_email_log %}
|
||||
{% set contact = alias_info.latest_contact %}
|
||||
{% if email_log.is_reply %}
|
||||
|
||||
{{ contact.website_email }}
|
||||
<i class="fa fa-reply mr-2"
|
||||
data-toggle="tooltip"
|
||||
title="Email reply/sent from alias"></i>
|
||||
{{ email_log.created_at | dt }}
|
||||
{% elif email_log.bounced %}
|
||||
<span class="text-danger">
|
||||
{{ contact.website_email }}
|
||||
<i class="fa fa-warning mr-2"
|
||||
data-toggle="tooltip"
|
||||
title="Email bounced and cannot be forwarded to your mailbox"></i>
|
||||
{{ email_log.created_at | dt }}
|
||||
</span>
|
||||
{% elif email_log.blocked %}
|
||||
{{ contact.website_email }}
|
||||
<i class="fa fa-ban mr-2 text-danger"
|
||||
data-toggle="tooltip"
|
||||
title="Email blocked"></i>
|
||||
{{ email_log.created_at | dt }}
|
||||
{% else %}
|
||||
{{ contact.website_email }}
|
||||
<i class="fa fa-paper-plane mr-2"
|
||||
data-toggle="tooltip"
|
||||
title="Email sent to alias"></i>
|
||||
{{ email_log.created_at | dt }}
|
||||
{% include 'partials/toggle_contact.html' %}
|
||||
|
||||
{% endif %}
|
||||
{% else %}
|
||||
No emails received/sent in the last 14 days. Created {{ alias.created_at | dt }}.
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- END Email Activity -->
|
||||
<div class="small-text mt-1">
|
||||
Alias description
|
||||
</div>
|
||||
<div class="d-flex mb-2">
|
||||
<div class="flex-grow-1 mr-2">
|
||||
<textarea id="note-{{ alias.id }}" name="note" class="form-control" style="font-size: 12px" rows="2" placeholder="e.g. where the alias is used or why is it created">{{ alias.note or "" }}</textarea>
|
||||
</div>
|
||||
<div>
|
||||
<a data-alias="{{ alias.id }}"
|
||||
class="save-note btn btn-sm btn-outline-success w-100">
|
||||
Save
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Send Email && More button -->
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<a href="{{ url_for('dashboard.alias_contact_manager', alias_id=alias.id) }}" id="send-email-{{ alias.id }}" {% if loop.index ==1 %}
|
||||
data-intro="Not only can an alias receive emails, it can <em>send</em> emails too!
|
||||
<br />
|
||||
<br />
|
||||
You can add a new <em>contact</em> for your alias here.
|
||||
<br />
|
||||
<br />
|
||||
If you need to reply to an email, just hit 'Reply': the response will come from your alias and your real email address stays <em>hidden</em>." data-step="4"
|
||||
{% endif %}
|
||||
class="btn btn-sm btn-outline-primary
|
||||
{% if not alias.enabled %}disabled{% endif %}
|
||||
" data-toggle="tooltip" title="Add new contact, manage your existing contacts">
|
||||
Contacts <i class="fe fe-send"></i>
|
||||
</a>
|
||||
</div>
|
||||
{% if not current_user.expand_alias_info %}
|
||||
|
||||
<div class="col text-right">
|
||||
<a class="btn btn-sm collapsed"
|
||||
data-toggle="collapse"
|
||||
href="#alias-{{ alias.id }}"
|
||||
role="button"
|
||||
aria-expanded="false">
|
||||
<span class="if-collapsed">
|
||||
More <i class="fe fe-chevron-down"></i>
|
||||
</span>
|
||||
<span class="if-not-collapsed">Less
|
||||
<i class="fe fe-chevron-up"></i>
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<!-- END Send Email && More button -->
|
||||
<!-- Collapse section -->
|
||||
<div class="{% if not current_user.expand_alias_info %} collapse{% endif %} mt-2"
|
||||
id="alias-{{ alias.id }}">
|
||||
{% if alias_info.latest_email_log != None %}
|
||||
|
||||
<div style="font-size: 12px">
|
||||
Alias created {{ alias.created_at | dt }}
|
||||
</div>
|
||||
{% endif %}
|
||||
<span class="alias-activity">{{ alias_info.nb_forward }}</span> forwarded,
|
||||
<span class="alias-activity">{{ alias_info.nb_blocked }}</span> blocked,
|
||||
<span class="alias-activity">{{ alias_info.nb_reply }}</span> sent
|
||||
in the last 14 days
|
||||
<a href="{{ url_for('dashboard.alias_log', alias_id=alias.id) }}"
|
||||
class="btn btn-sm btn-link">
|
||||
See All →
|
||||
</a>
|
||||
{% if mailboxes|length > 1 %}
|
||||
|
||||
<div class="small-text">
|
||||
Current mailbox
|
||||
</div>
|
||||
<div class="d-flex">
|
||||
<div class="flex-grow-1 mr-2">
|
||||
<select required
|
||||
id="mailbox-{{ alias.id }}"
|
||||
data-width="100%"
|
||||
class="mailbox-select"
|
||||
multiple
|
||||
name="mailbox">
|
||||
{% for mailbox in mailboxes %}
|
||||
|
||||
<option value="{{ mailbox.id }}" {% if alias_info.contain_mailbox(mailbox.id) %}
|
||||
selected {% endif %}>
|
||||
{{ mailbox.email }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<a data-alias="{{ alias.id }}"
|
||||
class="save-mailbox btn btn-sm btn-outline-info w-100">
|
||||
Update
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{% elif alias_info.mailbox != None and alias_info.mailbox.email != current_user.email %}
|
||||
<div class="small-text">
|
||||
Owned by <b>{{ alias_info.mailbox.email }}</b> mailbox
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="small-text mt-2"
|
||||
data-toogle="tooltip"
|
||||
title="When sending an email from this alias, the email will have 'Display Name <{{ alias.email }}>' as sender.">
|
||||
Display name
|
||||
<i class="fe fe-help-circle"></i>
|
||||
</div>
|
||||
<div class="d-flex">
|
||||
<div class="flex-grow-1 mr-2">
|
||||
<input id="alias-name-{{ alias.id }}"
|
||||
value="{{ alias.name or '' }}"
|
||||
class="form-control"
|
||||
placeholder="{{ alias.custom_domain.name or "Alias name" }}">
|
||||
</div>
|
||||
<div>
|
||||
<a data-alias="{{ alias.id }}"
|
||||
class="save-alias-name btn btn-sm btn-outline-primary w-100">
|
||||
Save
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{% if alias.mailbox_support_pgp() %}
|
||||
|
||||
<div class="small-text mt-2"
|
||||
data-toogle="tooltip"
|
||||
title="You can decide to turn off the PGP for an alias. This can be useful if the sender already encrypts the emails">
|
||||
PGP
|
||||
<i class="fe fe-help-circle"></i>
|
||||
</div>
|
||||
<div>
|
||||
<label class="custom-switch cursor pl-0">
|
||||
<input type="checkbox" class="enable-disable-pgp custom-switch-input" data-alias="{{ alias.id }}" data-alias-email="{{ alias.email }}" {{ "checked" if alias.pgp_enabled() else "" }}>
|
||||
<span class="custom-switch-indicator"></span>
|
||||
</label>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="small-text mt-2"
|
||||
data-toogle="tooltip"
|
||||
title="it's always pinned on top">
|
||||
Pin this alias
|
||||
<i class="fe fe-help-circle"></i>
|
||||
</div>
|
||||
<div>
|
||||
<label class="custom-switch cursor pl-0">
|
||||
<input type="checkbox" class="pin-alias custom-switch-input" data-alias="{{ alias.id }}" data-alias-email="{{ alias.email }}" {{ "checked" if alias.pinned else "" }}>
|
||||
<span class="custom-switch-indicator"></span>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<div class="btn-group float-right" role="group" aria-label="Basic example">
|
||||
<a href="{{ url_for('dashboard.alias_transfer_send_route', alias_id=alias.id) }}"
|
||||
class="btn btn-sm btn-link">
|
||||
Transfer
|
||||
<i class="ml-0 dropdown-icon fe fe-share-2 text-primary"></i>
|
||||
</a>
|
||||
<form method="post">
|
||||
{{ csrf_form.csrf_token }}
|
||||
<input type="hidden" name="form-name" value="delete-alias">
|
||||
<input type="hidden" name="alias-id" value="{{ alias.id }}">
|
||||
<input type="hidden" name="alias" class="alias" value="{{ alias.email }}">
|
||||
<span class="btn btn-link btn-sm float-right text-danger"
|
||||
onclick="confirmDeleteAlias.call(this)"
|
||||
{% if alias.custom_domain %} data-custom-domain-trash-url="{{ alias.custom_domain.get_trash_url() }}"{% endif %}
|
||||
data-alias="{{ alias.id }}"
|
||||
data-alias-email="{{ alias.email }}">
|
||||
Delete <i class="dropdown-icon fe fe-trash-2 text-danger"></i>
|
||||
</span>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- END Collapse section -->
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<!-- END Alias list -->
|
||||
<!-- Only show pagination control if there are previous/next page -->
|
||||
{% if page > 0 or not last_page %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<nav aria-label="Alias navigation">
|
||||
<ul class="pagination">
|
||||
<li class="page-item mr-1">
|
||||
<a class="btn btn-outline-primary {% if page == 0 %}disabled{% endif %}"
|
||||
href="{{ url_for('dashboard.index', page=page-1, query=query, sort=sort, filter=filter) }}">
|
||||
Previous
|
||||
</a>
|
||||
</li>
|
||||
<li class="page-item">
|
||||
<a class="btn btn-outline-primary {% if last_page %}disabled{% endif %}"
|
||||
href="{{ url_for('dashboard.index', page=page+1, query=query, sort=sort, filter=filter) }}">
|
||||
Next
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% block script %}
|
||||
<script src="/static/js/index.js?v=0"></script>
|
||||
<script>
|
||||
{% if show_intro %}
|
||||
// only show intro when screen is big enough to show "developer" tab
|
||||
if (window.innerWidth >= 1024) {
|
||||
introJs().start();
|
||||
}
|
||||
{% endif %}
|
||||
|
||||
$('.highlighted').tooltip("show");
|
||||
</script>
|
||||
{% endblock %}
|
22
app/templates/dashboard/lifetime_licence.html
Normal file
22
app/templates/dashboard/lifetime_licence.html
Normal file
@ -0,0 +1,22 @@
|
||||
{% extends "default.html" %}
|
||||
|
||||
{% set active_page = "dashboard" %}
|
||||
{% block title %}Lifetime Licence{% endblock %}
|
||||
{% block default_content %}
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h1 class="h2">Lifetime Licence</h1>
|
||||
<div class="mb-4">
|
||||
If you have a lifetime licence, please paste it here.
|
||||
<br />
|
||||
</div>
|
||||
<form method="post">
|
||||
{{ coupon_form.csrf_token }}
|
||||
{{ coupon_form.code(class="form-control", placeholder="Licence Code") }}
|
||||
{{ render_field_errors(coupon_form.code) }}
|
||||
<button class="btn btn-success mt-2">Apply</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
158
app/templates/dashboard/mailbox.html
Normal file
158
app/templates/dashboard/mailbox.html
Normal file
@ -0,0 +1,158 @@
|
||||
{% extends "default.html" %}
|
||||
|
||||
{% set active_page = "mailbox" %}
|
||||
{% block title %}Mailboxes{% endblock %}
|
||||
{% block default_content %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<h1 class="h3">
|
||||
Mailboxes
|
||||
<a class="ml-3 text-info"
|
||||
style="font-size: 12px"
|
||||
data-toggle="collapse"
|
||||
href="#howtouse"
|
||||
role="button"
|
||||
aria-expanded="false"
|
||||
aria-controls="collapseExample">
|
||||
How to use <i class="fe fe-chevrons-down"></i>
|
||||
</a>
|
||||
</h1>
|
||||
{% if not current_user.is_premium() %}
|
||||
|
||||
<div class="alert alert-danger" role="alert">This feature is only available in premium plan.</div>
|
||||
{% endif %}
|
||||
<div class="alert alert-primary collapse {% if mailboxes|length == 1 %} show{% endif %}"
|
||||
id="howtouse"
|
||||
role="alert">
|
||||
A <em>mailbox</em> is just another personal email address. When creating a new alias, you could choose the
|
||||
mailbox that <em>owns</em> this alias, i.e:
|
||||
<br />
|
||||
- all emails sent to this alias will be forwarded to this mailbox
|
||||
<br />
|
||||
- from this mailbox, you can reply/send emails from the alias.
|
||||
<br />
|
||||
<br />
|
||||
When you signed up, a mailbox is automatically created with your email <b>{{ current_user.email }}</b>
|
||||
<br />
|
||||
<br />
|
||||
The mailbox doesn't have to be your email: it can be your friend's email
|
||||
if you want to create aliases for your buddy.
|
||||
</div>
|
||||
<div class="row">
|
||||
{% for mailbox in mailboxes %}
|
||||
|
||||
<div class="col-12 col-lg-6">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">
|
||||
{{ mailbox.email }}
|
||||
{% if mailbox.verified %}
|
||||
|
||||
<span class="cursor"
|
||||
data-toggle="tooltip"
|
||||
data-original-title="Mailbox Verified">✅</span>
|
||||
{% else %}
|
||||
<span class="cursor"
|
||||
data-toggle="tooltip"
|
||||
data-original-title="Mailbox Not Verified">🚫</span>
|
||||
{% endif %}
|
||||
{% if mailbox.pgp_enabled() %}
|
||||
|
||||
<span class="cursor"
|
||||
data-toggle="tooltip"
|
||||
data-original-title="PGP Enabled">🗝</span>
|
||||
{% endif %}
|
||||
{% if mailbox.id == current_user.default_mailbox_id %}
|
||||
|
||||
<div class="badge badge-primary float-right"
|
||||
data-toggle="tooltip"
|
||||
title="When a new random alias is created, it belongs to the default mailbox">
|
||||
Default Mailbox
|
||||
</div>
|
||||
{% endif %}
|
||||
</h5>
|
||||
<h6 class="card-subtitle mb-2 text-muted">
|
||||
Created {{ mailbox.created_at | dt }}
|
||||
<br />
|
||||
<span class="font-weight-bold">{{ mailbox.nb_alias() }}</span> aliases.
|
||||
<br />
|
||||
</h6>
|
||||
<a href="{{ url_for('dashboard.mailbox_detail_route', mailbox_id=mailbox.id) }}">Edit ➡</a>
|
||||
</div>
|
||||
<div class="card-footer p-0">
|
||||
<div class="row">
|
||||
{% if mailbox.verified %}
|
||||
|
||||
<div class="col">
|
||||
<form method="post">
|
||||
{{ csrf_form.csrf_token }}
|
||||
<input type="hidden" name="form-name" value="set-default">
|
||||
<input type="hidden" class="mailbox" value="{{ mailbox.email }}">
|
||||
<input type="hidden" name="mailbox-id" value="{{ mailbox.id }}">
|
||||
<button class="card-link btn btn-link {% if mailbox.id == current_user.default_mailbox_id %} disabled{% endif %}">
|
||||
Set As Default Mailbox
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="col">
|
||||
<form method="post">
|
||||
{{ csrf_form.csrf_token }}
|
||||
<input type="hidden" name="form-name" value="delete">
|
||||
<input type="hidden" class="mailbox" value="{{ mailbox.email }}">
|
||||
<input type="hidden" name="mailbox-id" value="{{ mailbox.id }}">
|
||||
<span class="card-link btn btn-link text-danger float-right delete-mailbox {% if mailbox.id == current_user.default_mailbox_id %} disabled{% endif %}">
|
||||
Delete
|
||||
</span>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<form method="post" class="mt-2">
|
||||
{{ new_mailbox_form.csrf_token }}
|
||||
<input type="hidden" name="form-name" value="create">
|
||||
<h2 class="h4">New Mailbox</h2>
|
||||
{{ new_mailbox_form.email(class="form-control", placeholder="email@example.com") }}
|
||||
{{ render_field_errors(new_mailbox_form.email) }}
|
||||
<div class="small-text">A mailbox can't be a disposable or forwarding email address.</div>
|
||||
<button class="btn btn-primary mt-2">Create</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block script %}
|
||||
|
||||
<script>
|
||||
$(".delete-mailbox").on("click", function (e) {
|
||||
let mailbox = $(this).parent().find(".mailbox").val();
|
||||
|
||||
let that = $(this);
|
||||
let message = `All aliases owned by this mailbox <b>${mailbox}</b> will be also deleted, ` +
|
||||
" please confirm.";
|
||||
|
||||
bootbox.confirm({
|
||||
message: message,
|
||||
buttons: {
|
||||
confirm: {
|
||||
label: 'Yes, delete it',
|
||||
className: 'btn-danger'
|
||||
},
|
||||
cancel: {
|
||||
label: 'Cancel',
|
||||
className: 'btn-outline-primary'
|
||||
}
|
||||
},
|
||||
callback: function (result) {
|
||||
if (result) {
|
||||
that.closest("form").submit();
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
247
app/templates/dashboard/mailbox_detail.html
Normal file
247
app/templates/dashboard/mailbox_detail.html
Normal file
@ -0,0 +1,247 @@
|
||||
{% extends "default.html" %}
|
||||
|
||||
{% set active_page = "mailbox" %}
|
||||
{% block title %}Mailbox {{ mailbox.email }}{% endblock %}
|
||||
{% block head %}
|
||||
|
||||
<style>
|
||||
div[disabled]
|
||||
{
|
||||
pointer-events: none;
|
||||
opacity: 0.7;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
{% block default_content %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<h1 class="h3">
|
||||
{{ mailbox.email }}
|
||||
{% if mailbox.verified %}
|
||||
|
||||
<span class="cursor"
|
||||
data-toggle="tooltip"
|
||||
data-original-title="Mailbox Verified">✅</span>
|
||||
{% else %}
|
||||
<span class="cursor"
|
||||
data-toggle="tooltip"
|
||||
data-original-title="Mailbox Not Verified">🚫</span>
|
||||
{% endif %}
|
||||
{% if mailbox.pgp_enabled() %}
|
||||
|
||||
<span class="cursor"
|
||||
data-toggle="tooltip"
|
||||
data-original-title="PGP Enabled">🗝</span>
|
||||
{% endif %}
|
||||
</h1>
|
||||
{% if not mailbox.verified %}
|
||||
|
||||
<div class="alert alert-info">
|
||||
Mailbox not verified, please check your inbox/spam folder for the verification email.
|
||||
<br />
|
||||
To receive the verification email again, you can delete and re-add the mailbox.
|
||||
</div>
|
||||
{% endif %}
|
||||
<!-- Change email -->
|
||||
<div class="card">
|
||||
<form method="post" enctype="multipart/form-data">
|
||||
<input type="hidden" name="form-name" value="update-email">
|
||||
{{ change_email_form.csrf_token }}
|
||||
<div class="card-body">
|
||||
<div class="card-title">Change Mailbox Address</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Address</label>
|
||||
<!-- Not allow user to change mailbox if there's a pending change -->
|
||||
{{ change_email_form.email(class="form-control", value=mailbox.email, readonly=pending_email != None) }}
|
||||
{{ render_field_errors(change_email_form.email) }}
|
||||
{% if pending_email %}
|
||||
|
||||
<div class="mt-2">
|
||||
<span class="text-danger">Pending change: {{ pending_email }}</span>
|
||||
<a href="{{ url_for('dashboard.cancel_mailbox_change_route', mailbox_id=mailbox.id) }}"
|
||||
class="btn btn-secondary btn-sm">
|
||||
Cancel mailbox change
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<button class="btn btn-primary">Change</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<!-- END Change email -->
|
||||
{% if mailbox.pgp_finger_print and not mailbox.disable_pgp and current_user.include_sender_in_reverse_alias %}
|
||||
|
||||
<div class="alert alert-info">
|
||||
Email headers like <span class="italic">From, To, Subject</span> aren't encrypted by PGP.
|
||||
Currently, your reverse alias includes the sender address.
|
||||
You can disable this on <a href="/dashboard/setting#sender-in-ra">Settings</a>.
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="card-title">
|
||||
<div class="d-flex">
|
||||
Pretty Good Privacy (PGP)
|
||||
{% if mailbox.pgp_finger_print %}
|
||||
|
||||
<form method="post">
|
||||
{{ csrf_form.csrf_token }}
|
||||
<input type="hidden" name="form-name" value="toggle-pgp">
|
||||
<label class="custom-switch cursor" style="padding-left: 1rem" data-toggle="tooltip" {% if mailbox.disable_pgp %}
|
||||
title="Enable PGP" {% else %} title="Disable PGP" {% endif %}>
|
||||
<input type="checkbox" class="custom-switch-input" name="pgp-enabled" {{ "" if mailbox.disable_pgp else "checked" }}>
|
||||
<span class="custom-switch-indicator"></span>
|
||||
</label>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="small-text mt-1">
|
||||
By importing your PGP Public Key into SimpleLogin, all emails sent to {{ mailbox.email }} are
|
||||
<b>encrypted</b> with your key.
|
||||
<br />
|
||||
{% if PGP_SIGNER %}All forwarded emails will be signed with <b>{{ PGP_SIGNER }}</b>.{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% if not current_user.is_premium() %}
|
||||
|
||||
<div class="alert alert-danger" role="alert">This feature is only available in premium plan.</div>
|
||||
{% endif %}
|
||||
<form method="post">
|
||||
{{ csrf_form.csrf_token }}
|
||||
<div class="form-group">
|
||||
<label class="form-label">PGP Public Key</label>
|
||||
<textarea name="pgp" {% if not current_user.is_premium() %} disabled {% endif %} class="form-control" rows=10 id="pgp-public-key" placeholder="-----BEGIN PGP PUBLIC KEY BLOCK-----">{{ mailbox.pgp_public_key or "" }}</textarea>
|
||||
</div>
|
||||
<input type="hidden" name="form-name" value="pgp">
|
||||
<button class="btn btn-primary" name="action" {% if not current_user.is_premium() %}
|
||||
disabled {% endif %} value="save">
|
||||
Save
|
||||
</button>
|
||||
{% if mailbox.pgp_finger_print %}
|
||||
|
||||
<button class="btn btn-danger float-right" name="action" value="remove">Remove</button>
|
||||
{% endif %}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card" {% if not mailbox.pgp_enabled() %}
|
||||
disabled {% endif %}>
|
||||
<form method="post">
|
||||
{{ csrf_form.csrf_token }}
|
||||
<input type="hidden" name="form-name" value="generic-subject">
|
||||
<div class="card-body">
|
||||
<div class="card-title">
|
||||
Hide email subject when PGP is enabled
|
||||
<div class="small-text mt-1">
|
||||
When PGP is enabled, you can choose to use a <b>generic</b> subject for the forwarded emails.
|
||||
The original subject is then added into the email body.
|
||||
<br />
|
||||
As PGP does not encrypt the email subject and the email subject might contain sensitive information,
|
||||
this option will allow a further protection of your email content.
|
||||
</div>
|
||||
</div>
|
||||
<div class="alert alert-info">
|
||||
As the email is encrypted, a subject like "Email for you"
|
||||
will probably be rejected by your mailbox since it sounds like a spam.
|
||||
<br />
|
||||
Something like "Encrypted Email" would work much better :).
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Generic Subject</label>
|
||||
<input name="generic-subject" {% if not mailbox.pgp_enabled() %}
|
||||
disabled {% endif %} class="form-control" maxlength="78" placeholder="Generic Subject" value="{{ mailbox.generic_subject or "" }}">
|
||||
</div>
|
||||
<button class="btn btn-primary" name="action" {% if not mailbox.pgp_enabled() %}
|
||||
disabled {% endif %} value="save">
|
||||
Save
|
||||
</button>
|
||||
{% if mailbox.generic_subject %}
|
||||
|
||||
<button class="btn btn-danger float-right" name="action" value="remove">Remove</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<hr />
|
||||
<h2 class="h4">Advanced Options</h2>
|
||||
{% if spf_available %}
|
||||
|
||||
<div class="card" id="spf">
|
||||
<form method="post">
|
||||
{{ csrf_form.csrf_token }}
|
||||
<input type="hidden" name="form-name" value="force-spf">
|
||||
<div class="card-body">
|
||||
<div class="card-title">
|
||||
Enforce SPF
|
||||
<div class="small-text">
|
||||
To avoid email-spoofing, SimpleLogin blocks email that
|
||||
<em data-toggle="tooltip"
|
||||
title="Email that has your mailbox as envelope-sender address">seems</em> to come from your
|
||||
mailbox
|
||||
but sent from <em data-toggle="tooltip"
|
||||
title="IP Address that is not known by your mailbox email service">unknown</em>
|
||||
IP address.
|
||||
<br />
|
||||
Only turn off this option if you know what you're doing :).
|
||||
</div>
|
||||
</div>
|
||||
<label class="custom-switch cursor mt-2 pl-0" data-toggle="tooltip" {% if mailbox.force_spf %}
|
||||
title="Disable SPF enforcement" {% else %} title="Enable SPF enforcement" {% endif %}>
|
||||
<input type="checkbox" name="spf-status" class="custom-switch-input" {{ "checked" if mailbox.force_spf else "" }}>
|
||||
<span class="custom-switch-indicator"></span>
|
||||
</label>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="card" id="authorized-address">
|
||||
<div class="card-body">
|
||||
<div class="card-title">
|
||||
Authorized addresses
|
||||
<div class="small-text">
|
||||
Emails sent from these addresses to a <b>reverse-alias</b> are considered as being sent
|
||||
from {{ mailbox.email }}
|
||||
</div>
|
||||
</div>
|
||||
{% if mailbox.authorized_addresses | length == 0 %}
|
||||
|
||||
{% else %}
|
||||
<ul>
|
||||
{% for authorized_address in mailbox.authorized_addresses %}
|
||||
|
||||
<li>
|
||||
{{ authorized_address.email }}
|
||||
<form method="post" action="#authorized-address" style="display: inline">
|
||||
{{ csrf_form.csrf_token }}
|
||||
<input type="hidden" name="form-name" value="delete-authorized-address">
|
||||
<input type="hidden"
|
||||
name="authorized-address-id"
|
||||
value="{{ authorized_address.id }}">
|
||||
<input type="submit" class="btn btn-sm btn-outline-warning" value="Delete">
|
||||
</form>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
<form method="post" action="#authorized-address" class="form-inline">
|
||||
{{ csrf_form.csrf_token }}
|
||||
<input type="hidden" name="form-name" value="add-authorized-address">
|
||||
<input type="email" name="email" size="50" class="form-control" required>
|
||||
<input type="submit" class="btn btn-primary" value="Add">
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block script %}
|
||||
<script src="/static/js/utils/drag-drop-into-text.js"></script>
|
||||
<script>
|
||||
$(".custom-switch-input").change(function (e) {
|
||||
$(this).closest("form").submit();
|
||||
});
|
||||
enableDragDropForPGPKeys('#pgp-public-key');
|
||||
</script>
|
||||
{% endblock %}
|
14
app/templates/dashboard/mailbox_validation.html
Normal file
14
app/templates/dashboard/mailbox_validation.html
Normal file
@ -0,0 +1,14 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Mailbox Validation{% endblock %}
|
||||
{% block content %}
|
||||
|
||||
<div class="mx-auto p-7 card" style="max-width: 50rem">
|
||||
<div class="text-center mb-7">
|
||||
Mailbox <b>{{ mailbox.email }}</b> verified, you can now start creating alias with it
|
||||
</div>
|
||||
<div class="mx-auto">
|
||||
<a href="{{ url_for('dashboard.index') }}" class="btn btn-primary">Go To Home Page</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
19
app/templates/dashboard/mfa_cancel.html
Normal file
19
app/templates/dashboard/mfa_cancel.html
Normal file
@ -0,0 +1,19 @@
|
||||
{% extends "default.html" %}
|
||||
|
||||
{% set active_page = "setting" %}
|
||||
{% block title %}Cancel MFA{% endblock %}
|
||||
{% block default_content %}
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h1 class="h2">Two Factor Authentication</h1>
|
||||
<div>
|
||||
Disabling TOTP reduces the security of your account, please make sure to re-activate it later
|
||||
or use WebAuthn (FIDO).
|
||||
</div>
|
||||
<form method="post">
|
||||
<button class="btn btn-danger mt-2">Disable TOTP</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
38
app/templates/dashboard/mfa_setup.html
Normal file
38
app/templates/dashboard/mfa_setup.html
Normal file
@ -0,0 +1,38 @@
|
||||
{% extends "default.html" %}
|
||||
|
||||
{% set active_page = "setting" %}
|
||||
{% block title %}MFA Setup{% endblock %}
|
||||
{% block head %}
|
||||
|
||||
<script src="{{ url_for('static', filename='node_modules/qrious/dist/qrious.min.js') }}"></script>
|
||||
{% endblock %}
|
||||
{% block default_content %}
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h1 class="h3">Two Factor Authentication - TOTP</h1>
|
||||
<p>
|
||||
You will need to use a 2FA application like Google Authenticator or Authy on your phone or PC and scan the following QR Code:
|
||||
</p>
|
||||
<canvas id="qr"></canvas>
|
||||
<script>
|
||||
(function () {
|
||||
var qr = new QRious({
|
||||
element: document.getElementById('qr'),
|
||||
value: '{{otp_uri}}'
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
<div class="mt-3 mb-2">Or you can manually enter the following secret key:</div>
|
||||
<input class="form-control" disabled value="{{ current_user.otp_secret }}">
|
||||
<form method="post">
|
||||
{{ otp_token_form.csrf_token }}
|
||||
<div class="font-weight-bold mt-5">Token</div>
|
||||
<div class="small-text">Please enter the 6-digit number displayed in your authenticator app.</div>
|
||||
{{ otp_token_form.token(class="form-control", placeholder="") }}
|
||||
{{ render_field_errors(otp_token_form.token) }}
|
||||
<button class="btn btn-lg btn-success mt-2">Submit</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
54
app/templates/dashboard/new_api_key.html
Normal file
54
app/templates/dashboard/new_api_key.html
Normal file
@ -0,0 +1,54 @@
|
||||
{% extends "default.html" %}
|
||||
|
||||
{% block title %}API Key{% endblock %}
|
||||
{% set active_page = "api_key" %}
|
||||
{% block default_content %}
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h1 class="h3">New API Key {{ api_key.name }} is created</h1>
|
||||
<div class="alert alert-warning">For security reasons, API Key is only visible when it is created.</div>
|
||||
<div class="input-group mb-2">
|
||||
<input class="form-control"
|
||||
id="apikey-{{ api_key.id }}"
|
||||
readonly
|
||||
value="**********">
|
||||
<div class="input-group-append">
|
||||
<span class="input-group-text">
|
||||
<i class="fe fe-eye toggle-api-key"
|
||||
data-show="off"
|
||||
data-secret="{{ api_key.code }}"></i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<button class="clipboard btn btn-primary"
|
||||
data-clipboard-action="copy"
|
||||
data-clipboard-text="{{ api_key.code }}"
|
||||
data-clipboard-target="#apikey-{{ api_key.id }}">
|
||||
Copy <i class="fe fe-clipboard"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block script %}
|
||||
|
||||
<script>
|
||||
$(".toggle-api-key").on('click', function (event) {
|
||||
let that = $(this);
|
||||
let apiInput = that.parent().parent().parent().find("input");
|
||||
if (that.attr("data-show") === "off") {
|
||||
let apiKey = $(this).attr("data-secret");
|
||||
apiInput.val(apiKey);
|
||||
that.addClass("fe-eye-off");
|
||||
that.removeClass("fe-eye");
|
||||
that.attr("data-show", "on");
|
||||
} else {
|
||||
that.removeClass("fe-eye-off");
|
||||
that.addClass("fe-eye");
|
||||
apiInput.val("**********");
|
||||
that.attr("data-show", "off");
|
||||
}
|
||||
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
18
app/templates/dashboard/notification.html
Normal file
18
app/templates/dashboard/notification.html
Normal file
@ -0,0 +1,18 @@
|
||||
{% extends "default.html" %}
|
||||
|
||||
{% set active_page = "dashboard" %}
|
||||
{% block title %}{{ notification.title }}{% endblock %}
|
||||
{% block default_content %}
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h1 class="h3">{{ notification.title or "" }}</h1>
|
||||
<div>{{ notification.message | safe }}</div>
|
||||
<form method="post"
|
||||
class="float-right mt-3"
|
||||
onsubmit="return confirm('This operation is not reversible, please confirm');">
|
||||
<button class="btn btn-outline-danger">Delete</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
57
app/templates/dashboard/notifications.html
Normal file
57
app/templates/dashboard/notifications.html
Normal file
@ -0,0 +1,57 @@
|
||||
{% extends "default.html" %}
|
||||
|
||||
{% set active_page = "dashboard" %}
|
||||
{% block title %}Notifications{% endblock %}
|
||||
{% block default_content %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<h1 class="h3">Notifications</h1>
|
||||
{% for notification in notifications %}
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="h4">{{ notification.title or "" }}</div>
|
||||
<div style="width: 40em;
|
||||
word-wrap:break-word;
|
||||
white-space: normal;
|
||||
overflow: hidden;
|
||||
max-height: 100px;
|
||||
text-overflow: ellipsis;">
|
||||
{{ notification.message | safe }}
|
||||
</div>
|
||||
<a href="{{ url_for('dashboard.notification_route', notification_id=notification.id) }}"
|
||||
class="mt-2 btn btn-outline-primary">
|
||||
More ➡
|
||||
</a>
|
||||
<div class="small text-muted mt-2">{{ notification.created_at | dt }}</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
<!-- Only show pagination control if there are previous/next page -->
|
||||
{% if page > 0 or not last_page %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<nav aria-label="Notification navigation">
|
||||
<ul class="pagination">
|
||||
<li class="page-item mr-1">
|
||||
<a class="btn btn-outline-primary {% if page == 0 %}disabled{% endif %}"
|
||||
href="{{ url_for('dashboard.notifications_route', page=page-1) }}">
|
||||
Previous
|
||||
</a>
|
||||
</li>
|
||||
<li class="page-item">
|
||||
<a class="btn btn-outline-primary {% if last_page %}disabled{% endif %}"
|
||||
href="{{ url_for('dashboard.notifications_route', page=page+1) }}">
|
||||
Next
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
218
app/templates/dashboard/pricing.html
Normal file
218
app/templates/dashboard/pricing.html
Normal file
@ -0,0 +1,218 @@
|
||||
{% extends "default.html" %}
|
||||
|
||||
{% set active_page = "dashboard" %}
|
||||
{% block title %}Pricing{% endblock %}
|
||||
{% block head %}
|
||||
|
||||
<script src="https://cdn.paddle.com/paddle/paddle.js"></script>
|
||||
<script>
|
||||
if (window.Paddle === undefined) {
|
||||
console.log("cannot load Paddle from CDN");
|
||||
document.write('<script src="/static/vendor/paddle.js"><\/script>')
|
||||
}
|
||||
</script>
|
||||
<style type="text/css">
|
||||
html.mvc__a.mvc__lot.mvc__of.mvc__classes.mvc__to.mvc__increase.mvc__the.mvc__odds.mvc__of.mvc__winning.mvc__specificity, html.mvc__a.mvc__lot.mvc__of.mvc__classes.mvc__to.mvc__increase.mvc__the.mvc__odds.mvc__of.mvc__winning.mvc__specificity > body {
|
||||
position: static;
|
||||
}
|
||||
|
||||
|
||||
{#CSS to change collapse button display from https://stackoverflow.com/a/31967516/1428034#}
|
||||
[data-toggle="collapse"].collapsed .if-not-collapsed {
|
||||
display: none;
|
||||
}
|
||||
|
||||
[data-toggle="collapse"]:not(.collapsed) .if-collapsed {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
{% block announcement %}
|
||||
|
||||
{# TODO: to remove#}
|
||||
{# <div class="alert alert-danger text-center mb-0" role="alert">#}
|
||||
{# Our payment provider Paddle is experiencing#}
|
||||
{# <a href="https://paddle.status.io" target="_blank">server issue <i class="fe fe-external-link"></i></a>#}
|
||||
{# that can make our checkout page unusable. <br />#}
|
||||
{# Please retry later and sorry for this issue!#}
|
||||
{# </div>#}
|
||||
{% endblock %}
|
||||
{% block default_content %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-6 col-lg-6">
|
||||
<div class="card">
|
||||
<div class="card-body text-center">
|
||||
<div class="h3">Premium</div>
|
||||
<ul class="list-unstyled leading-loose mb-3">
|
||||
<li>
|
||||
<i class="fe fe-check text-success mr-2" aria-hidden="true"></i>
|
||||
Unlimited aliases
|
||||
</li>
|
||||
<li>
|
||||
<i class="fe fe-check text-success mr-2" aria-hidden="true"></i>
|
||||
Unlimited custom domains
|
||||
</li>
|
||||
<li>
|
||||
<i class="fe fe-check text-success mr-2" aria-hidden="true"></i>
|
||||
Catch-all (or wildcard) aliases
|
||||
</li>
|
||||
<li>
|
||||
<i class="fe fe-check text-success mr-2" aria-hidden="true"></i>
|
||||
Up to 50 directories (or usernames)
|
||||
</li>
|
||||
<li>
|
||||
<i class="fe fe-check text-success mr-2" aria-hidden="true"></i>
|
||||
Unlimited mailboxes
|
||||
</li>
|
||||
<li>
|
||||
<i class="fe fe-check text-success mr-2" aria-hidden="true"></i>
|
||||
PGP Encryption
|
||||
</li>
|
||||
</ul>
|
||||
<div class="small-text">
|
||||
More information on our
|
||||
<a href="https://simplelogin.io/pricing" target="_blank" rel="noopener">
|
||||
Pricing
|
||||
Page <i class="fe fe-external-link"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6 col-lg-6">
|
||||
{% if manual_sub %}
|
||||
|
||||
<div class="alert alert-info">
|
||||
You currently have a subscription until <b>{{ manual_sub.end_at.format("YYYY-MM-DD") }}</b>
|
||||
({{ (manual_sub.end_at - now).days }} days left).
|
||||
<br />
|
||||
Please note that the time left will <b>not</b> be taken into account in a new subscription.
|
||||
</div>
|
||||
<hr />
|
||||
{% endif %}
|
||||
{% if proton_upgrade %}
|
||||
|
||||
<div id="proton-upgrade">
|
||||
<h4>Proton Unlimited, Business and Visionary plans include SimpleLogin premium and more!</h4>
|
||||
<a class="btn btn-primary" role="button" href="https://account.proton.me/u/0/mail/upgrade">
|
||||
<b>Upgrade your Proton account</b>
|
||||
</a>
|
||||
<p class="mt-2 small">
|
||||
Starts at $9.99/month (billed yearly), starting with 500GB of storage, VPN, encrypted
|
||||
calendar & file storage and more.
|
||||
</p>
|
||||
<div class="middle-line my-5 h4">OR</div>
|
||||
<div id="normal-upgrade-button">
|
||||
<a class="btn btn-secondary collapsed" data-toggle="collapse" href="#normal-upgrade" role="button">
|
||||
Upgrade your SimpleLogin account
|
||||
<span class="if-collapsed">
|
||||
<i class="fe fe-chevron-down"></i>
|
||||
</span>
|
||||
<span class="if-not-collapsed">
|
||||
<i class="fe fe-chevron-up"></i>
|
||||
</span>
|
||||
</a>
|
||||
<p class="mt-2 small">Starts at $2.5/month (billed yearly)</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div id="normal-upgrade" class="{% if proton_upgrade %} collapse{% endif %}">
|
||||
<div class="display-6 my-3">
|
||||
🔐 Secure payments by
|
||||
<a href="https://paddle.com" target="_blank" rel="noopener">
|
||||
Paddle <i class="fe fe-external-link"></i>
|
||||
</a>
|
||||
</div>
|
||||
{% set sub = current_user.get_paddle_subscription() %}
|
||||
{% if sub and sub.cancelled %}
|
||||
|
||||
<div class="alert alert-primary" role="alert">
|
||||
You have an active subscription until {{ sub.next_bill_date.strftime("%Y-%m-%d") }}.
|
||||
<br />
|
||||
Please note that if you re-subscribe now, this will be a completely
|
||||
new subscription and
|
||||
your payment method will be charged <b>immediately</b>.
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if coinbase_sub %}
|
||||
|
||||
<div class="alert alert-info">
|
||||
You currently have a Coinbase subscription until <b>{{ coinbase_sub.end_at.format("YYYY-MM-DD") }}</b>
|
||||
({{ (coinbase_sub.end_at - now).days }} days left).
|
||||
<br />
|
||||
Please note that the time left will <b>not</b> be taken into account in a new Paddle subscription.
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="mb-3">
|
||||
Paddle supports bank cards
|
||||
(Mastercard, Visa, American Express, etc) and PayPal.
|
||||
</div>
|
||||
<button class="btn btn-primary" onclick="upgrade({{ PADDLE_YEARLY_PRODUCT_ID }})">
|
||||
Yearly billing
|
||||
<span class="badge badge-success">Save $18</span>
|
||||
<br />
|
||||
<span style="font-size: 18px">$30/year</span>
|
||||
</button>
|
||||
<button class="btn btn-secondary" onclick="upgrade({{ PADDLE_MONTHLY_PRODUCT_ID }})">
|
||||
Monthly billing
|
||||
<br />
|
||||
<b>
|
||||
$4/month
|
||||
</b>
|
||||
</button>
|
||||
<hr />
|
||||
<i class="fa fa-bitcoin"></i>
|
||||
Payment via
|
||||
<a href="https://commerce.coinbase.com/?lang=en" target="_blank">
|
||||
Coinbase Commerce<i class="fe fe-external-link"></i>
|
||||
</a>
|
||||
<br />
|
||||
Currently Bitcoin, Bitcoin Cash, Dai, Ethereum, Litecoin and USD Coin are supported.
|
||||
<br />
|
||||
<a class="btn btn-outline-primary" href="{{ url_for('dashboard.coinbase_checkout_route') }}" target="_blank">
|
||||
Yearly billing - Crypto
|
||||
<br />
|
||||
$30/year
|
||||
<i class="fe fe-external-link"></i>
|
||||
</a>
|
||||
<hr />
|
||||
For other payment options, please send us an email at
|
||||
<a href="mailto:hi@simplelogin.io">hi@simplelogin.io</a>
|
||||
.
|
||||
<br />
|
||||
If you have bought a coupon, please go to the
|
||||
<a href="{{ url_for('dashboard.coupon_route') }}">coupon page</a>
|
||||
to apply the coupon code.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
Paddle.Setup({vendor: {{ PADDLE_VENDOR_ID }}});
|
||||
|
||||
function upgrade(productId) {
|
||||
bootbox.dialog({
|
||||
title: `Payment with credit card or PayPal via Paddle`,
|
||||
message: `Paddle will ask for an email address for sending out the invoices, please feel free to use an alias. <br />
|
||||
You don't have to use your SimpleLogin account email address`,
|
||||
size: 'large',
|
||||
onEscape: true,
|
||||
backdrop: true,
|
||||
buttons: {
|
||||
got_it: {
|
||||
label: 'Got it!',
|
||||
className: 'btn-outline-primary',
|
||||
callback: function () {
|
||||
Paddle.Checkout.open({
|
||||
product: productId,
|
||||
success: "{{ success_url }}",
|
||||
passthrough: "{\"user_id\": {{current_user.id}} }"
|
||||
});
|
||||
}
|
||||
},
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
26
app/templates/dashboard/recovery_code.html
Normal file
26
app/templates/dashboard/recovery_code.html
Normal file
@ -0,0 +1,26 @@
|
||||
{% extends "default.html" %}
|
||||
|
||||
{% set active_page = "setting" %}
|
||||
{% block title %}Recovery Codes{% endblock %}
|
||||
{% block default_content %}
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h1 class="h3">Recovery codes</h1>
|
||||
<p>
|
||||
In the event you lose access to your authenticator app you can use one of these recovery codes to gain access to
|
||||
your account again.
|
||||
Each code can only be used once, make sure to store them in a safe place.
|
||||
</p>
|
||||
<p class="alert alert-warning">
|
||||
<strong>
|
||||
If you had recovery codes before, they have been invalidated.
|
||||
Store these codes in a safe place. You won't be able to retrieve them again!
|
||||
</strong>
|
||||
</p>
|
||||
<ul>
|
||||
{% for recovery_code in recovery_codes %}<li>{{ recovery_code }}</li>{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
197
app/templates/dashboard/referral.html
Normal file
197
app/templates/dashboard/referral.html
Normal file
@ -0,0 +1,197 @@
|
||||
{% extends "default.html" %}
|
||||
|
||||
{% block title %}Referral{% endblock %}
|
||||
{% set active_page = "setting" %}
|
||||
{% block default_content %}
|
||||
|
||||
<div class="col">
|
||||
<h1 class="h3 mb-5">
|
||||
Referrals
|
||||
<a class="ml-3 text-info"
|
||||
style="font-size: 12px"
|
||||
data-toggle="collapse"
|
||||
href="#howtouse"
|
||||
role="button"
|
||||
aria-expanded="false"
|
||||
aria-controls="collapseExample">
|
||||
How to use <i class="fe fe-chevrons-down"></i>
|
||||
</a>
|
||||
</h1>
|
||||
<div class="alert alert-primary collapse" id="howtouse" role="alert">
|
||||
On this page, you can create a <b>referral code</b> that you can use when referring people to SimpleLogin.
|
||||
For every user who <b>upgrades</b> and stays with us at least 3 months, you'll get $5 :).
|
||||
<br />
|
||||
The payout can be initiated any time, just send us an email at
|
||||
<a href="mailto:hi@simplelogin.io">hi@simplelogin.io</a>
|
||||
when you want to receive the payout.
|
||||
</div>
|
||||
{% if referrals|length == 0 %}
|
||||
|
||||
<div class="alert alert-info">
|
||||
You don't have any referral code yet. Let's create the first one and start inviting your friends!
|
||||
<br />
|
||||
</div>
|
||||
{% endif %}
|
||||
{% for referral in referrals %}
|
||||
|
||||
<div class="card p-4 shadow-sm {% if referral.id == highlight_id %} highlight-row{% endif %}">
|
||||
<form method="post">
|
||||
<input type="hidden" name="form-name" value="update">
|
||||
<input type="hidden" name="referral-id" value="{{ referral.id }}">
|
||||
<div class="d-flex mb-3">
|
||||
<div class="mr-2">
|
||||
<input name="name"
|
||||
class="form-control"
|
||||
required
|
||||
value="{{ referral.name or '' }}">
|
||||
</div>
|
||||
<div>
|
||||
<button class="btn btn-outline-success">Update</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{% set nb_user = referral.nb_user %}
|
||||
{% set nb_paid_user = referral.nb_paid_user %}
|
||||
{% if nb_user > 0 %}
|
||||
|
||||
<div class="mb-3">
|
||||
<b class="h1">{{ nb_user }}</b>
|
||||
{% if nb_user == 1 %}
|
||||
|
||||
person
|
||||
{% else %}
|
||||
people
|
||||
{% endif %}
|
||||
has their online privacy protected thanks to you!
|
||||
<br />
|
||||
Among them, <b class="h1">{{ nb_paid_user }}</b>
|
||||
{% if nb_paid_user == 1 %}
|
||||
|
||||
person
|
||||
{% else %}
|
||||
people
|
||||
{% endif %}
|
||||
has upgraded their accounts.
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="mt-2">
|
||||
Please use this referral link to invite your friends trying out SimpleLogin:
|
||||
<br />
|
||||
<div class="d-flex mb-5 mt-2" style="max-width: 40em">
|
||||
<div class="flex-grow-1 mr-2">
|
||||
<input class="form-control"
|
||||
id="referral-{{ referral.id }}"
|
||||
readonly
|
||||
value="{{ referral.link() }}">
|
||||
</div>
|
||||
<div>
|
||||
<button class="clipboard btn btn-outline-primary"
|
||||
data-clipboard-action="copy"
|
||||
data-clipboard-text="{{ referral.link() }}"
|
||||
data-clipboard-target="#referral-{{ referral.id }}">
|
||||
Copy <i class="fe fe-clipboard"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
You can also use the referral code <b>{{ referral.code }}</b> when sharing any link on SimpleLogin.
|
||||
<br />
|
||||
Just append
|
||||
<em data-toggle="tooltip"
|
||||
title="Click to copy"
|
||||
class="clipboard"
|
||||
data-clipboard-text="{{ '?slref=' + referral.code }}"
|
||||
style="overflow-wrap: break-word">
|
||||
?slref={{ referral.code }}
|
||||
</em>
|
||||
to any link on SimpleLogin website.
|
||||
</div>
|
||||
<div>
|
||||
<form method="post">
|
||||
<input type="hidden" name="form-name" value="delete">
|
||||
<input type="hidden" name="referral-id" value="{{ referral.id }}">
|
||||
<span class="delete-referral float-right btn btn-outline-danger">Delete</span>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
<form method="post" class="mt-6 card p-4 shadow">
|
||||
<input type="hidden" name="form-name" value="create">
|
||||
<div class="form-group">
|
||||
<input name="code"
|
||||
class="form-control"
|
||||
pattern="[0-9a-z-_]{3,}"
|
||||
placeholder="Referral Code"
|
||||
title="At least 3 characters. Only lowercase letters, numbers, dashes (-) and underscores (_) are currently supported.">
|
||||
<div class="small-text">
|
||||
At least 3 characters. Only lowercase letters, numbers,
|
||||
dashes (-) and underscores (_) are currently supported.
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input name="name"
|
||||
class="form-control"
|
||||
required
|
||||
placeholder="Referral name, something to help you remember why you create it :)">
|
||||
</div>
|
||||
<button class="btn btn-success mb-5">Create</button>
|
||||
</form>
|
||||
{% if payouts|length > 0 %}
|
||||
|
||||
<div class="mt-6 card p-4 shadow">
|
||||
<h3 class="h4">Payouts</h3>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Sent at</th>
|
||||
<th>Amount</th>
|
||||
<th>Payment Method</th>
|
||||
<th>Number of upgraded accounts</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for payout in payouts %}
|
||||
|
||||
<tr>
|
||||
<td>{{ payout.created_at | dt }}</td>
|
||||
<td>${{ payout.amount }}</td>
|
||||
<td>{{ payout.payment_method }}</td>
|
||||
<td>{{ payout.number_upgraded_account }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block script %}
|
||||
|
||||
<script>
|
||||
$(".delete-referral").on("click", function (e) {
|
||||
let that = $(this);
|
||||
|
||||
bootbox.confirm({
|
||||
message: "This operation is irreversible, please confirm.",
|
||||
buttons: {
|
||||
confirm: {
|
||||
label: 'Yes, delete it',
|
||||
className: 'btn-danger'
|
||||
},
|
||||
cancel: {
|
||||
label: 'Cancel',
|
||||
className: 'btn-outline-primary'
|
||||
}
|
||||
},
|
||||
callback: function (result) {
|
||||
if (result) {
|
||||
that.closest("form").submit();
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
89
app/templates/dashboard/refused_email.html
Normal file
89
app/templates/dashboard/refused_email.html
Normal file
@ -0,0 +1,89 @@
|
||||
{% extends "default.html" %}
|
||||
|
||||
{% block title %}Quarantine{% endblock %}
|
||||
{% block head %}
|
||||
|
||||
<style>
|
||||
li {
|
||||
margin-top: 8px;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
{% set active_page = "setting" %}
|
||||
{% block default_content %}
|
||||
|
||||
<div class="col">
|
||||
<h1 class="h3 mb-5">Quarantine & Bounce</h1>
|
||||
<div class="alert alert-info">
|
||||
This page shows all emails that are either refused by your mailbox (bounced) or detected as spams/phishing (quarantine) via our
|
||||
<a href="https://simplelogin.io/docs/getting-started/anti-phishing/"
|
||||
target="_blank">anti-phishing program ↗</a>
|
||||
<ul class="p-4 mb-0">
|
||||
<li>
|
||||
If the email is indeed spam, this means the alias is now in the hands of a spammer,
|
||||
you should probably <b>disable</b> the alias.
|
||||
</li>
|
||||
<li>
|
||||
If the email isn't spam and your mailbox refuses the email, we recommend to create a <b>filter</b> to avoid your mailbox provider from blocking legitimate emails. Please refer to
|
||||
<a href="https://simplelogin.io/docs/getting-started/troubleshooting/#emails-end-up-in-spam"
|
||||
target="_blank">Setting up filter for SimpleLogin emails ↗</a>
|
||||
</li>
|
||||
<li>
|
||||
If the email is flagged as spams/phishing, this means that the sender explicitly states their emails should respect
|
||||
<b>DMARC</b> (an email authentication protocol)
|
||||
and any email that violates this should either be quarantined or rejected. If possible, please contact the sender
|
||||
so they can update their DMARC setting or fix their SPF/DKIM that cause the DMARC failure.
|
||||
Their emails are probably being rejected or end up in spam at other email providers as well.
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
{% if email_logs|length == 0 %}
|
||||
|
||||
<div class="my-4 p-4 card">You don't have any emails in Quarantine/Bounce.</div>
|
||||
{% endif %}
|
||||
{% for email_log in email_logs %}
|
||||
|
||||
{% set refused_email = email_log.refused_email %}
|
||||
{% set contact = email_log.contact %}
|
||||
{% set alias = contact.alias %}
|
||||
<div class="card p-4 shadow-sm {% if email_log.id == highlight_id %} highlight-row{% endif %}">
|
||||
<div class="small-text">
|
||||
Sent {{ refused_email.created_at | dt }}
|
||||
{% if email_log.bounced %}
|
||||
|
||||
<span class="badge badge-info">Bounce</span>
|
||||
{% else %}
|
||||
<span class="badge badge-warning">Quarantine</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if email_log.is_reply %}
|
||||
|
||||
From: {{ alias.email }}
|
||||
<br />
|
||||
To: {{ contact.website_email }}
|
||||
{% else %}
|
||||
From: {{ contact.website_email }}
|
||||
<br />
|
||||
<span>
|
||||
To: {{ alias.email }}
|
||||
<a href='{{ url_for("dashboard.index", highlight_alias_id=alias.id) }}'
|
||||
class="text-danger small-text"
|
||||
style="text-decoration: underline">
|
||||
Disable Alias
|
||||
</a>
|
||||
</span>
|
||||
{% endif %}
|
||||
{% if refused_email.deleted %}
|
||||
|
||||
<div class="mt-6" style="font-weight: 600">Email deleted {{ refused_email.delete_at | dt }}</div>
|
||||
{% else %}
|
||||
<a href="{{ refused_email.get_url() }}"
|
||||
download
|
||||
class="btn btn-outline-primary mt-4"
|
||||
style="max-width: 20em">Download →</a>
|
||||
<div class="small-text">This will download a ".eml" file that you can open in your email client</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endblock %}
|
767
app/templates/dashboard/setting.html
Normal file
767
app/templates/dashboard/setting.html
Normal file
@ -0,0 +1,767 @@
|
||||
{% extends "default.html" %}
|
||||
|
||||
{% set active_page = "setting" %}
|
||||
{% block title %}Settings{% endblock %}
|
||||
{% block head %}
|
||||
|
||||
<style>
|
||||
.card-title {
|
||||
font-size: 22px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
.highlighted{
|
||||
border: solid 2px #5675E2;
|
||||
}
|
||||
li {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
</style>
|
||||
{% endblock %}
|
||||
{% block default_content %}
|
||||
|
||||
<div class="col pb-3">
|
||||
<!-- Current plan -->
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="card-title mb-3">Current Plan</div>
|
||||
{% if current_user.lifetime %}
|
||||
|
||||
You have lifetime access to the Premium plan.
|
||||
{% elif current_user.lifetime_or_active_subscription() %}
|
||||
{% if paddle_sub %}
|
||||
|
||||
<div>
|
||||
{% if paddle_sub.cancelled %}(Cancelled){% endif %}
|
||||
{{ paddle_sub.plan_name() }} plan subscribed via Paddle.
|
||||
<a href="{{ url_for('dashboard.billing') }}">Manage Subscription ➡</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if manual_sub and manual_sub.is_active() %}
|
||||
|
||||
<div>
|
||||
Manual plan which expires {{ manual_sub.end_at | dt }}
|
||||
({{ manual_sub.end_at.format("YYYY-MM-DD") }}).
|
||||
{% if manual_sub.is_giveaway %}
|
||||
|
||||
<br />
|
||||
To gain additional features and support SimpleLogin you can upgrade to a Premium plan.
|
||||
<br />
|
||||
<a href="{{ url_for('dashboard.pricing') }}"
|
||||
class="btn btn-sm btn-outline-primary">Upgrade</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if apple_sub and apple_sub.is_valid() %}
|
||||
|
||||
<div>
|
||||
Premium plan subscribed via Apple which expires {{ apple_sub.expires_date | dt }}
|
||||
({{ apple_sub.expires_date.format("YYYY-MM-DD") }}).
|
||||
<div class="alert alert-info">
|
||||
If you want to subscribe via the Web instead, please make sure to cancel your subscription
|
||||
on Apple first.
|
||||
<a href="{{ url_for('dashboard.pricing') }}">
|
||||
Upgrade <i class="fa fa-arrow-right" aria-hidden="true"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if coinbase_sub and coinbase_sub.is_active() %}
|
||||
|
||||
<div>
|
||||
Yearly plan subscribed with cryptocurrency which expires on
|
||||
{{ coinbase_sub.end_at.format("YYYY-MM-DD") }}.
|
||||
<a href="{{ url_for('dashboard.coinbase_checkout_route') }}"
|
||||
target="_blank">
|
||||
Extend Subscription <i class="fe fe-external-link"></i>
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if partner_sub %}<div>Premium subscription managed by {{ partner_name }}.</div>{% endif %}
|
||||
{% elif current_user.in_trial() %}
|
||||
Your Premium trial expires {{ current_user.trial_end | dt }}.
|
||||
{% else %}
|
||||
You are on the Free plan.
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<!-- END Current plan -->
|
||||
<!-- TOTP -->
|
||||
<div class="card" id="totp">
|
||||
<div class="card-body">
|
||||
<div class="card-title">Two Factor Authentication</div>
|
||||
<div class="mb-3">
|
||||
Secure your account with 2FA, you'll be asked for a code generated through an app when you login.
|
||||
<br />
|
||||
</div>
|
||||
{% if not current_user.enable_otp %}
|
||||
|
||||
<a href="{{ url_for('dashboard.mfa_setup') }}"
|
||||
class="btn btn-outline-primary">Setup TOTP</a>
|
||||
{% else %}
|
||||
<a href="{{ url_for('dashboard.mfa_cancel') }}"
|
||||
class="btn btn-outline-danger">Disable TOTP</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<!-- END TOTP -->
|
||||
<!-- WebAuthn -->
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="card-title">Security Key (WebAuthn)</div>
|
||||
<div class="mb-3">
|
||||
You can secure your account by linking either your FIDO-supported physical key such as Yubikey, Google
|
||||
Titan,
|
||||
or a device with appropriate hardware to your account.
|
||||
</div>
|
||||
{% if current_user.fido_uuid is none %}
|
||||
|
||||
<a href="{{ url_for('dashboard.fido_setup') }}"
|
||||
class="btn btn-outline-primary">Setup WebAuthn</a>
|
||||
{% else %}
|
||||
<a href="{{ url_for('dashboard.fido_manage') }}"
|
||||
class="btn btn-outline-info">Manage WebAuthn</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<!-- END WebAuthn -->
|
||||
<!-- Newsletter -->
|
||||
<div class="card" id="notification">
|
||||
<div class="card-body">
|
||||
<div class="card-title">Newsletters</div>
|
||||
<div class="mb-3">We will occasionally send you emails with new feature announcements.</div>
|
||||
<form method="post">
|
||||
{{ csrf_form.csrf_token }}
|
||||
<input type="hidden" name="form-name" value="notification-preference">
|
||||
<div class="form-check">
|
||||
<input type="checkbox"
|
||||
id="notification"
|
||||
name="notification"
|
||||
{% if current_user.notification %} checked{% endif %}
|
||||
class="form-check-input">
|
||||
<label for="notification">I want to be emailed when new features are released.</label>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-outline-primary">Submit</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<!-- END Newsletter -->
|
||||
<!-- Change name & profile picture -->
|
||||
<div class="card">
|
||||
<form method="post" enctype="multipart/form-data">
|
||||
{{ form.csrf_token }}
|
||||
<input type="hidden" name="form-name" value="update-profile">
|
||||
<div class="card-body">
|
||||
<div class="card-title">Profile</div>
|
||||
<div>
|
||||
This information will be filled in automatically when you use the
|
||||
<em>Sign in with SimpleLogin</em> button.
|
||||
</div>
|
||||
<div class="form-group mt-3">
|
||||
<label class="form-label">Name</label>
|
||||
{{ form.name(class="form-control", value=current_user.name) }}
|
||||
{{ render_field_errors(form.name) }}
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="form-label">Profile picture</div>
|
||||
{{ form.profile_picture(class="form-control-file") }}
|
||||
{{ render_field_errors(form.profile_picture) }}
|
||||
{% if current_user.profile_picture_id %}
|
||||
|
||||
<img src="{{ current_user.profile_picture_url() }}"
|
||||
class="profile-picture">
|
||||
{% endif %}
|
||||
</div>
|
||||
<button class="btn btn-outline-primary">Update</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<!-- END change name & profile picture -->
|
||||
<!-- Change email -->
|
||||
<div class="card">
|
||||
<form method="post" enctype="multipart/form-data">
|
||||
<input type="hidden" name="form-name" value="update-email">
|
||||
{{ change_email_form.csrf_token }}
|
||||
<div class="card-body">
|
||||
<div class="card-title">Account Email</div>
|
||||
<div class="mb-3">
|
||||
This email address is used to log in to SimpleLogin.
|
||||
<br />
|
||||
If you want to change the mailbox that emails are forwarded to, use the
|
||||
<a href="{{ url_for('dashboard.mailbox_route') }}">
|
||||
<i class="fe fe-inbox"></i> Mailboxes page
|
||||
</a>
|
||||
instead.
|
||||
</div>
|
||||
<div class="form-group mt-2">
|
||||
<!-- Not allow user to change email if there's a pending change -->
|
||||
{{ change_email_form.email(class="form-control", value=current_user.email, readonly=pending_email != None) }}
|
||||
{{ render_field_errors(change_email_form.email) }}
|
||||
{% if pending_email %}
|
||||
|
||||
<div class="mt-2">
|
||||
<span class="text-danger">Pending email change: {{ pending_email }}</span>
|
||||
<a href="{{ url_for('dashboard.resend_email_change') }}"
|
||||
class="btn btn-secondary btn-sm">
|
||||
Resend
|
||||
confirmation email
|
||||
</a>
|
||||
<a href="{{ url_for('dashboard.cancel_email_change') }}"
|
||||
class="btn btn-secondary btn-sm">
|
||||
Cancel email
|
||||
change
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<button class="btn btn-outline-primary">Change Email</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<!-- END Change email -->
|
||||
<!-- Connect with Proton -->
|
||||
{% if connect_with_proton %}
|
||||
|
||||
<div class="card" id="connect-with-proton">
|
||||
<div class="card-body">
|
||||
<div class="card-title">Connect with Proton</div>
|
||||
{% if proton_linked_account != None %}
|
||||
|
||||
<div class="mb-3">
|
||||
Your account is currently linked to the Proton account <b>{{ proton_linked_account }}</b>
|
||||
<br />
|
||||
</div>
|
||||
<form method="post"
|
||||
action="{{ url_for("dashboard.unlink_proton_account") }}">
|
||||
{{ csrf_form.csrf_token }}
|
||||
<button class="btn btn-primary mt-2 proton-button">
|
||||
<img class="mr-2" src="/static/images/proton.svg">
|
||||
Unlink account
|
||||
</button>
|
||||
</form>
|
||||
{% else %}
|
||||
<div class="mb-3">
|
||||
You can connect your Proton and SimpleLogin accounts.
|
||||
<br>
|
||||
You can then quickly log in to your SimpleLogin account using the Proton one.
|
||||
<br>
|
||||
If you have Proton Unlimited, Business or Visionary, you can have SimpleLogin premium for free.
|
||||
<br />
|
||||
</div>
|
||||
<a class="btn btn-primary mt-2 proton-button"
|
||||
href="{{ url_for("auth.proton_login", action="link") }}">
|
||||
<img class="mr-2"
|
||||
src="{{ url_for('static', filename='images/proton.svg') }}"/>
|
||||
Connect with Proton
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<!-- END Connect with Proton -->
|
||||
<!-- Change password -->
|
||||
<div class="card" id="change_password">
|
||||
<div class="card-body">
|
||||
<div class="card-title">Password</div>
|
||||
<div class="mb-3">You will receive an email containing instructions on how to change your password.</div>
|
||||
<form method="post">
|
||||
{{ csrf_form.csrf_token }}
|
||||
<input type="hidden" name="form-name" value="change-password">
|
||||
<button class="btn btn-outline-primary">Change password</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<!-- END Change password -->
|
||||
<!-- Random alias -->
|
||||
<div id="random-alias" class="card">
|
||||
<div class="card-body">
|
||||
<div class="card-title">
|
||||
Aliases
|
||||
</div>
|
||||
<div class="mt-3 mb-1">
|
||||
Change the way random aliases are generated by default.
|
||||
</div>
|
||||
<form method="post" action="#random-alias" class="form-inline">
|
||||
{{ csrf_form.csrf_token }}
|
||||
<input type="hidden" name="form-name" value="change-alias-generator">
|
||||
<select class="form-control mr-sm-2" name="alias-generator-scheme">
|
||||
<option value="{{ AliasGeneratorEnum.word.value }}"
|
||||
{% if current_user.alias_generator == AliasGeneratorEnum.word.value %} selected{% endif %}>
|
||||
Based on
|
||||
Random {{ AliasGeneratorEnum.word.name.capitalize() }}
|
||||
</option>
|
||||
<option value="{{ AliasGeneratorEnum.uuid.value }}"
|
||||
{% if current_user.alias_generator == AliasGeneratorEnum.uuid.value %} selected{% endif %}>
|
||||
Based
|
||||
on {{ AliasGeneratorEnum.uuid.name.upper() }}
|
||||
</option>
|
||||
</select>
|
||||
<button class="btn btn-outline-primary">
|
||||
Update
|
||||
</button>
|
||||
</form>
|
||||
<div class="mt-3 mb-1">
|
||||
Select the default domain for aliases.
|
||||
</div>
|
||||
<form method="post" action="#random-alias" class="form-inline">
|
||||
{{ csrf_form.csrf_token }}
|
||||
<input type="hidden"
|
||||
name="form-name"
|
||||
value="change-random-alias-default-domain">
|
||||
<select class="form-control mr-sm-2" name="random-alias-default-domain">
|
||||
<option value="">
|
||||
Not Selected
|
||||
</option>
|
||||
{% for is_public, domain in current_user.available_domains_for_random_alias() %}
|
||||
|
||||
<option value="{{ domain }}" {% if current_user.default_alias_custom_domain_id or current_user.default_alias_public_domain_id %}
|
||||
{% if current_user.default_random_alias_domain() == domain %}
|
||||
selected {% endif %} {% endif %}>
|
||||
{{ domain }} (
|
||||
{% if is_public %}
|
||||
|
||||
SimpleLogin domain
|
||||
{% else %}
|
||||
your domain
|
||||
{% endif %}
|
||||
)
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<button class="btn btn-outline-primary">
|
||||
Update
|
||||
</button>
|
||||
</form>
|
||||
<div id="random-alias-suffix" class="mt-3 mb-1">
|
||||
Select the default suffix generator for aliases.
|
||||
</div>
|
||||
<form method="post" action="#random-alias-suffix" class="form-inline">
|
||||
{{ csrf_form.csrf_token }}
|
||||
<input type="hidden" name="form-name" value="random-alias-suffix">
|
||||
<select class="form-control mr-sm-2" name="random-alias-suffix-generator">
|
||||
<option value="0"
|
||||
{% if current_user.random_alias_suffix==0 %} selected{% endif %}>
|
||||
Random word from our dictionary
|
||||
</option>
|
||||
<option value="1"
|
||||
{% if current_user.random_alias_suffix==1 %} selected{% endif %}>
|
||||
Random combination of {{ ALIAS_RAND_SUFFIX_LENGTH }} letter and digits
|
||||
</option>
|
||||
</select>
|
||||
<button class="btn btn-outline-primary">
|
||||
Update
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<!-- END Random alias -->
|
||||
<!-- Sender Format -->
|
||||
<div class="card" id="sender-format">
|
||||
<div class="card-body">
|
||||
<div class="card-title">
|
||||
Sender Address Format
|
||||
</div>
|
||||
<div class="mt-1 mb-3">
|
||||
When your alias receives an email, say from: <b>John Wick <john@wick.com></b>,
|
||||
SimpleLogin forwards it to your mailbox.
|
||||
<br />
|
||||
Due to some email constraints, SimpleLogin cannot keep the sender email address
|
||||
in the original form and needs to <b>transform</b> it to one of the formats below.
|
||||
</div>
|
||||
<form method="post" action="#sender-format">
|
||||
{{ csrf_form.csrf_token }}
|
||||
<input type="hidden" name="form-name" value="change-sender-format">
|
||||
<select class="form-control mr-sm-2" name="sender-format">
|
||||
<option value="{{ SenderFormatEnum.AT.value }}"
|
||||
{% if current_user.sender_format == SenderFormatEnum.AT.value %} selected{% endif %}>
|
||||
John Wick - john at wick.com
|
||||
</option>
|
||||
<option value="{{ SenderFormatEnum.A.value }}"
|
||||
{% if current_user.sender_format == SenderFormatEnum.A.value %} selected{% endif %}>
|
||||
John Wick - john(a)wick.com
|
||||
</option>
|
||||
<option value="{{ SenderFormatEnum.NAME_ONLY.value }}"
|
||||
{% if current_user.sender_format == SenderFormatEnum.NAME_ONLY.value %} selected{% endif %}>
|
||||
John Wick
|
||||
</option>
|
||||
<option value="{{ SenderFormatEnum.AT_ONLY.value }}"
|
||||
{% if current_user.sender_format == SenderFormatEnum.AT_ONLY.value %} selected{% endif %}>
|
||||
john at wick.com
|
||||
</option>
|
||||
<option value="{{ SenderFormatEnum.NO_NAME.value }}"
|
||||
{% if current_user.sender_format == SenderFormatEnum.NO_NAME.value %} selected{% endif %}>
|
||||
No Name (i.e. only reverse-alias)
|
||||
</option>
|
||||
</select>
|
||||
<button class="btn btn-outline-primary mt-3">
|
||||
Update
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<!-- END Sender Format -->
|
||||
<!-- Reverse-alias replacement -->
|
||||
<div class="card" id="reverse-alias-replacement-section">
|
||||
<div class="card-body">
|
||||
<div class="card-title">
|
||||
Reverse Alias Replacement
|
||||
<div class="badge badge-warning">
|
||||
Experimental
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
When replying to a forwarded email, the <b>reverse-alias</b> can be automatically included
|
||||
in the attached message by your email client.
|
||||
If this option is enabled, SimpleLogin will try to <b>replace</b> the reverse-alias by your contact email.
|
||||
<br />
|
||||
</div>
|
||||
<form method="post" action="#reverse-alias-replacement-section">
|
||||
{{ csrf_form.csrf_token }}
|
||||
<input type="hidden" name="form-name" value="replace-ra">
|
||||
<div class="form-check">
|
||||
<input type="checkbox"
|
||||
id="replace-ra"
|
||||
name="replace-ra"
|
||||
{% if current_user.replace_reverse_alias %} checked{% endif %}
|
||||
class="form-check-input">
|
||||
<label for="replace-ra">
|
||||
Enable replacing reverse alias
|
||||
</label>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-outline-primary">
|
||||
Update
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<!-- END Reverse-alias -->
|
||||
<!-- Sender included in reverse-alias -->
|
||||
<div class="card" id="sender-in-ra">
|
||||
<div class="card-body">
|
||||
<div class="card-title">
|
||||
Include sender address in reverse-alias
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
If this option is enabled, new reverse-alias will include the sender address (e.g. <span class="italic">sender_domain_com_gibberish@simplelogin.co</span>)
|
||||
so you can quickly know the sender.
|
||||
<br />
|
||||
If disabled, a new reverse-alias will be randomly generated.
|
||||
Please note that existing reverse-aliases won't change.
|
||||
</div>
|
||||
<form method="post" action="#sender-in-ra">
|
||||
{{ csrf_form.csrf_token }}
|
||||
<input type="hidden" name="form-name" value="sender-in-ra">
|
||||
<div class="form-check">
|
||||
<input type="checkbox" id="include-sender-ra" name="enable"
|
||||
{# todo: remove current_user.include_sender_in_reverse_alias is none condition #}
|
||||
{% if current_user.include_sender_in_reverse_alias is none or current_user.include_sender_in_reverse_alias %}
|
||||
|
||||
checked {% endif %} class="form-check-input">
|
||||
<label for="include-sender-ra">
|
||||
Include sender address in reverse-alias
|
||||
</label>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-outline-primary">
|
||||
Update
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<!-- END Reverse-alias -->
|
||||
<!-- Always expand alias info -->
|
||||
<div class="card" id="expand-alias-info-section">
|
||||
<div class="card-body">
|
||||
<div class="card-title">
|
||||
Always expand alias info
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
By default, additional alias info is shown after clicking on the "More" button.
|
||||
<br />
|
||||
When this option is enabled, alias additional info will always be shown.
|
||||
<br />
|
||||
</div>
|
||||
<form method="post" action="#expand-alias-info-section">
|
||||
{{ csrf_form.csrf_token }}
|
||||
<input type="hidden" name="form-name" value="expand-alias-info">
|
||||
<div class="form-check">
|
||||
<input type="checkbox"
|
||||
id="expand-alias-info"
|
||||
name="enable"
|
||||
{% if current_user.expand_alias_info %} checked{% endif %}
|
||||
class="form-check-input">
|
||||
<label for="expand-alias-info">
|
||||
Automatically expand alias info
|
||||
</label>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-outline-primary">
|
||||
Update
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<!-- END Always expand alias info -->
|
||||
<!-- Include website address in alias -->
|
||||
<div class="card" id="include_website_in_one_click_alias">
|
||||
<div class="card-body">
|
||||
<div class="card-title">
|
||||
Include website address in one-click alias creation on browser extension
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
If enabled, the website name will be used as alias prefix
|
||||
when you create an alias via SimpleLogin browser extension via the email input field
|
||||
<br />
|
||||
<img src="https://simplelogin.io/images/one-click-alias.gif"
|
||||
class="my-2"
|
||||
style="max-width: 40%">
|
||||
</div>
|
||||
<form method="post" action="#include_website_in_one_click_alias">
|
||||
{{ csrf_form.csrf_token }}
|
||||
<input type="hidden"
|
||||
name="form-name"
|
||||
value="include_website_in_one_click_alias">
|
||||
<div class="form-check">
|
||||
<input type="checkbox"
|
||||
id="include-website-in-alias"
|
||||
name="enable"
|
||||
{% if current_user.include_website_in_one_click_alias %} checked{% endif %}
|
||||
class="form-check-input">
|
||||
<label for="include-website-in-alias">
|
||||
Include website address in alias
|
||||
</label>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-outline-primary">
|
||||
Update
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<!-- END Include website address in alias -->
|
||||
<!-- Ignore Loop Email -->
|
||||
{# <div class="card" id="ignore-loop-email-section">#}
|
||||
{# <div class="card-body">#}
|
||||
{# <div class="card-title">Ignore Loop Emails</div>#}
|
||||
{# <div class="mb-3">#}
|
||||
{# On some email clients, "Reply All" automatically includes your alias that#}
|
||||
{# would send the same email to your mailbox.#}
|
||||
{# <br />#}
|
||||
{# You can disable these "loop" emails by enabling this option.#}
|
||||
{# </div>#}
|
||||
{# <form method="post" action="#ignore-loop-email-section">#}
|
||||
{# {{ csrf_form.csrf_token }} #}
|
||||
{# <input type="hidden" name="form-name" value="ignore-loop-email">#}
|
||||
{# <div class="form-check">#}
|
||||
{# <input type="checkbox" id="ignore-loop-email" name="enable"#}
|
||||
{# {% if current_user.ignore_loop_email %} checked{% endif %} class="form-check-input">#}
|
||||
{# <label for="ignore-loop-email">Ignore Loop Emails</label>#}
|
||||
{# </div>#}
|
||||
{# <button type="submit" class="btn btn-outline-primary">Update</button>#}
|
||||
{# </form>#}
|
||||
{# </div>#}
|
||||
{# </div>#}
|
||||
<!-- END Ignore Loop Email -->
|
||||
<!-- One-click subscribe -->
|
||||
<div class="card" id="one-click-unsubscribe-section">
|
||||
<div class="card-body">
|
||||
<div class="card-title">
|
||||
One-click unsubscribe
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
On email clients that support the
|
||||
<a href="https://simplelogin.io/docs/getting-started/one-click-unsubscribe/">
|
||||
One-click unsubscribe
|
||||
</a>
|
||||
button, clicking on it will allow you to do one of these actions:
|
||||
<ul>
|
||||
<li>
|
||||
<b>Original action:</b> Use the same unsubscribe policy set by your sender.
|
||||
SimpleLogin will make sure to hide your mailbox address.
|
||||
</li>
|
||||
<li>
|
||||
<b>Disable alias:</b> The unsubscribe action will disable the alias that received the email.
|
||||
</li>
|
||||
<li>
|
||||
<b>Block contact:</b> The sender of the email will be blocked: you won't receive emails from this sender to your alias anymore.
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<form method="post"
|
||||
action="#one-click-unsubscribe-section"
|
||||
class="form-inline">
|
||||
{{ csrf_form.csrf_token }}
|
||||
<input type="hidden" name="form-name" value="one-click-unsubscribe">
|
||||
<select class="form-control mr-sm-2" name="unsubscribe-behaviour">
|
||||
<option value="{{ UnsubscribeBehaviourEnum.PreserveOriginal.name }}" {% if current_user.unsub_behaviour.value == UnsubscribeBehaviourEnum.PreserveOriginal.value %}
|
||||
selected="selected" {% endif %}>
|
||||
Preserve the original unsubscribe policy
|
||||
</option>
|
||||
<option value="{{ UnsubscribeBehaviourEnum.DisableAlias.name }}" {% if current_user.unsub_behaviour.value == UnsubscribeBehaviourEnum.DisableAlias.value %}
|
||||
selected="selected" {% endif %}>
|
||||
Disable the alias that received the email
|
||||
</option>
|
||||
<option value="{{ UnsubscribeBehaviourEnum.BlockContact.name }}" {% if current_user.unsub_behaviour.value == UnsubscribeBehaviourEnum.BlockContact.value %}
|
||||
selected="selected" {% endif %}>
|
||||
Block the sender that sent the original email
|
||||
</option>
|
||||
</select>
|
||||
<button type="submit" class="btn btn-outline-primary">
|
||||
Update
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<!-- END One-click subscribe -->
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="card-title">
|
||||
Quarantine & Bounces
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
When an email is refused (or bounced) by your mailbox provider or flagged by
|
||||
<a href="https://simplelogin.io/docs/getting-started/anti-phishing/">SimpleLogin anti-phishing program</a>,
|
||||
SimpleLogin keeps a copy of this email for 7 days so you can take a look at its content and take appropriate actions.
|
||||
<br />
|
||||
The emails are deleted in 7 days.
|
||||
<br />
|
||||
This is an exceptional case where SimpleLogin temporarily stores the email.
|
||||
</div>
|
||||
<a href="{{ url_for('dashboard.refused_email_route') }}"
|
||||
class="btn btn-outline-primary">
|
||||
See quarantine & bounce emails
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card" id="blocked-behaviour">
|
||||
<div class="card-body">
|
||||
<div class="card-title">
|
||||
Disabled alias/Blocked contact
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
When an email is sent to a <b>disabled</b> alias or sent from a <b>blocked</b> contact, you can decide what
|
||||
response the sender should see.
|
||||
<br />
|
||||
<b>Ignore</b> means they will see the message as delivered, but SimpleLogin won't actually forward it to you.
|
||||
This is the default option as you can start receiving the emails again
|
||||
by re-enabling the alias or unblocking a contact.
|
||||
<br />
|
||||
<b>Reject</b> means SimpleLogin will tell them that the alias does not exist.
|
||||
<br />
|
||||
</div>
|
||||
<form method="post" action="#blocked-behaviour" class="form-inline">
|
||||
{{ csrf_form.csrf_token }}
|
||||
<input type="hidden" name="form-name" value="change-blocked-behaviour">
|
||||
<select class="form-control mr-sm-2" name="blocked-behaviour">
|
||||
<option value="{{ BlockBehaviourEnum.return_2xx.value }}"
|
||||
{% if current_user.block_behaviour.value == BlockBehaviourEnum.return_2xx.value %} selected="selected"{% endif %}>
|
||||
Ignore (the sender will see the email as delivered, but you won't receive anything).
|
||||
</option>
|
||||
<option value="{{ BlockBehaviourEnum.return_5xx.value }}"
|
||||
{% if current_user.block_behaviour.value == BlockBehaviourEnum.return_5xx.value %} selected="selected"{% endif %}>
|
||||
Reject (the sender will be told that your alias does not exist).
|
||||
</option>
|
||||
</select>
|
||||
<button class="btn btn-outline-primary">
|
||||
Update
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card" id="sender-header">
|
||||
<div class="card-body">
|
||||
<div class="card-title">
|
||||
Include original sender in email headers
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
SimpleLogin forwards emails to your mailbox from the <b>reverse-alias</b> and not from the <b>original</b>
|
||||
sender address.
|
||||
<br />
|
||||
If this option is enabled, the original sender addresses is stored in the email header <b>X-SimpleLogin-Envelope-From</b>.
|
||||
You can choose to display this header in your email client.
|
||||
<br />
|
||||
As email headers aren't encrypted, your mailbox service can know the sender address via this header.
|
||||
</div>
|
||||
<form method="post" action="#sender-header">
|
||||
{{ csrf_form.csrf_token }}
|
||||
<input type="hidden" name="form-name" value="sender-header">
|
||||
<div class="form-check">
|
||||
<input type="checkbox"
|
||||
id="include-sender-header"
|
||||
name="enable"
|
||||
{% if current_user.include_header_email_header %} checked{% endif %}
|
||||
class="form-check-input">
|
||||
<label for="include-sender-header">
|
||||
Include sender address in email headers
|
||||
</label>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-outline-primary">
|
||||
Update
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="card-title">
|
||||
Alias import/export
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
You can import your aliases created on other platforms into SimpleLogin.
|
||||
You can also export your aliases to a readable csv format for a future batch import.
|
||||
</div>
|
||||
<a href="{{ url_for('dashboard.batch_import_route') }}"
|
||||
class="btn btn-outline-primary">
|
||||
Batch Import
|
||||
</a>
|
||||
<a href="{{ url_for('dashboard.alias_export_route') }}"
|
||||
class="btn btn-outline-secondary">
|
||||
Export Aliases
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="card-title">
|
||||
SimpleLogin data export
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
As per GDPR (General Data Protection Regulation) law, you can request a copy of your data which are stored on
|
||||
SimpleLogin.
|
||||
A zip file that contains all information will be sent to your SimpleLogin account address.
|
||||
</div>
|
||||
<div class="d-flex">
|
||||
<div>
|
||||
<form method="post">
|
||||
{{ csrf_form.csrf_token }}
|
||||
<input type="hidden" name="form-name" value="send-full-user-report">
|
||||
<button class="btn btn-outline-info">
|
||||
Request your data
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="card-title">
|
||||
Account Deletion
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
If SimpleLogin isn't the right fit for you, you can simply delete your account.
|
||||
</div>
|
||||
<a href="{{ url_for('dashboard.delete_account') }}"
|
||||
class="btn btn-outline-danger">
|
||||
Delete account
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block script %}
|
||||
<script>
|
||||
let anchor = window.location.hash;
|
||||
$(anchor).addClass("highlighted")
|
||||
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
138
app/templates/dashboard/subdomain.html
Normal file
138
app/templates/dashboard/subdomain.html
Normal file
@ -0,0 +1,138 @@
|
||||
{% extends "default.html" %}
|
||||
|
||||
{% set active_page = "subdomain" %}
|
||||
{% block title %}Subdomains{% endblock %}
|
||||
{% block head %}{% endblock %}
|
||||
{% block default_content %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<h1 class="h3">
|
||||
Subdomains
|
||||
<a class="ml-3 text-info"
|
||||
style="font-size: 12px"
|
||||
data-toggle="collapse"
|
||||
href="#howtouse"
|
||||
role="button"
|
||||
aria-expanded="false"
|
||||
aria-controls="collapseExample">
|
||||
How to use <i class="fe fe-chevrons-down"></i>
|
||||
</a>
|
||||
</h1>
|
||||
{% if not current_user.is_premium() %}
|
||||
|
||||
<div class="alert alert-danger" role="alert">
|
||||
This feature is only available on Premium plan.
|
||||
<a href="{{ url_for('dashboard.pricing') }}"
|
||||
target="_blank"
|
||||
rel="noopener">
|
||||
Upgrade<i class="fe fe-external-link"></i>
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="alert alert-primary collapse {% if not subdomains %} show{% endif %}"
|
||||
id="howtouse"
|
||||
role="alert">
|
||||
You can use subdomain to quickly create email aliases without opening SimpleLogin app.
|
||||
<br />
|
||||
Handy when you need to quickly give out an email address, for example on a phone call, in a meeting or just
|
||||
anywhere you want.
|
||||
<br />
|
||||
After choosing a subdomain, simply use <b>anything@my-subdomain.simplelogin.co</b>
|
||||
next time you need an alias:
|
||||
it'll be <b>automatically created</b> the first time it receives an email.
|
||||
<br />
|
||||
</div>
|
||||
<div class="row">
|
||||
{% for subdomain in subdomains %}
|
||||
|
||||
<div class="col-12 col-lg-6">
|
||||
<div class="card" style="">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">
|
||||
<a href="{{ url_for('dashboard.domain_detail', custom_domain_id=subdomain.id) }}">{{ subdomain.domain }}</a>
|
||||
</h5>
|
||||
<h6 class="card-subtitle mb-4 text-muted">
|
||||
Created {{ subdomain.created_at | dt }}
|
||||
<br />
|
||||
<span class="font-weight-bold">{{ subdomain.nb_alias() }}</span> aliases.
|
||||
<br />
|
||||
</h6>
|
||||
<a href="{{ url_for('dashboard.domain_detail', custom_domain_id=subdomain.id) }}"
|
||||
class="mt-3">Details ➡</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="row {% if current_user.subdomain_quota <= 0 %} disabled-content{% endif %}"
|
||||
id="new-subdomain">
|
||||
<div class="col">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h2 class="h4 mb-1">New Subdomain</h2>
|
||||
<form method="post" class="mt-2" data-parsley-validate>
|
||||
<input type="hidden" name="form-name" value="create">
|
||||
<div class="form-group">
|
||||
<label>Subdomain</label>
|
||||
<input name="subdomain"
|
||||
v-model="subdomain"
|
||||
data-parsley-pattern="[0-9a-z-]{3,}"
|
||||
data-parsley-trigger="change"
|
||||
data-parsley-error-message="At least 3 characters. Only lowercase letters, numbers and dashes (-) are supported."
|
||||
class="form-control"
|
||||
minlength="3"
|
||||
maxlength="32">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Root domain</label>
|
||||
<select name="domain" v-model="domain" class="form-control">
|
||||
{% for sl_domain in sl_domains %}
|
||||
|
||||
<option value="{{ sl_domain.domain }}">
|
||||
{{ sl_domain.domain }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<button class="btn btn-primary">Create</button>
|
||||
</form>
|
||||
<div v-if="toShow" class="text-info mt-2">
|
||||
You are about to create <b>[[ subdomain]].[[domain]]</b> subdomain.
|
||||
</div>
|
||||
<div class="alert alert-info mt-3" style="font-size: 12px">
|
||||
Deleting a subdomain will <b>not</b> restore the subdomain quota
|
||||
so please make sure
|
||||
to choose the subdomain you want to keep.
|
||||
<br />
|
||||
Currently you can create up to <b style="font-size: 1.5em">{{ current_user.subdomain_quota }}</b>
|
||||
subdomains.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block script %}
|
||||
|
||||
<script>
|
||||
new Vue({
|
||||
el: '#new-subdomain',
|
||||
delimiters: ["[[", "]]"], // necessary to avoid conflict with jinja
|
||||
data: {
|
||||
subdomain: "",
|
||||
domain: "{{ sl_domains[0].domain }}"
|
||||
},
|
||||
computed: {
|
||||
toShow: function () {
|
||||
if (this.subdomain && this.domain) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
104
app/templates/dashboard/support.html
Normal file
104
app/templates/dashboard/support.html
Normal file
@ -0,0 +1,104 @@
|
||||
{% extends "default.html" %}
|
||||
|
||||
{% set active_page = 'dashboard' %}
|
||||
{% block title %}Support{% endblock %}
|
||||
{% block default_content %}
|
||||
|
||||
<div class="col pb-3">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="card-title mb-3">Report a problem</div>
|
||||
<div class="alert alert-info">
|
||||
If an email cannot be delivered to your mailbox, please check
|
||||
<a href="{{ url_for('dashboard.notifications_route') }}">
|
||||
your
|
||||
notifications
|
||||
</a>
|
||||
for error messages.
|
||||
<br />
|
||||
For generic questions, i.e. not related to your account, we recommend to post the question on
|
||||
our
|
||||
<a href="https://www.reddit.com/r/Simplelogin/">Reddit</a>
|
||||
where our community can help answer the question
|
||||
and other people with the same question can find the answer there.
|
||||
</div>
|
||||
<div class="alert alert-warning">
|
||||
A support ticket will be created in Zendesk. Please do not include any sensitive information in the ticket.
|
||||
</div>
|
||||
<form id="supportZendeskForm" method="post" enctype="multipart/form-data">
|
||||
<div class="mt-4 mb-5">
|
||||
<label for="issueDescription" class="form-label font-weight-bold">What happened?</label>
|
||||
<textarea class="form-control" required name="ticket_content" id="issueDescription" rows="3" placeholder="Please provide as much information as possible. For example which alias(es), mailbox(es) ar affected, if this is a persistent issue...">{{- ticket_content or '' -}}</textarea>
|
||||
</div>
|
||||
<div class="mt-5 font-weight-bold">Attach files to support request</div>
|
||||
<div class="text-muted">Only images, text and emails are accepted</div>
|
||||
<div class="custom-file">
|
||||
<input type="file"
|
||||
class="custom-file-input"
|
||||
name="ticket_files"
|
||||
id="ticketFileGroup"
|
||||
multiple="multiple">
|
||||
<label class="custom-file-label" for="ticketFileGroup">Choose file</label>
|
||||
</div>
|
||||
<div class="mt-5 font-weight-bold">Where can we reach you?</div>
|
||||
<div class="text-muted">
|
||||
Conversations related to this ticket will be sent to this address. Feel free to use an alias here.
|
||||
</div>
|
||||
<div class="input-group mb-3" id="alias-group">
|
||||
<input type="text"
|
||||
required
|
||||
class="form-control"
|
||||
placeholder="Email"
|
||||
name="ticket_email"
|
||||
v-model='ticket_email'
|
||||
aria-label="Email to send responses to"
|
||||
aria-describedby="button-addon2">
|
||||
<div class="input-group-append">
|
||||
<button class="btn btn-outline-primary"
|
||||
type="button"
|
||||
@click="generateRandomAlias"
|
||||
id="button-addon2">
|
||||
Generate a random alias
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-5">
|
||||
<button class="btn btn-primary">Create support ticket</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block script %}
|
||||
|
||||
<script>
|
||||
new Vue({
|
||||
el: '#alias-group',
|
||||
data: {
|
||||
ticket_email: '{{ ticket_email }}'
|
||||
},
|
||||
methods: {
|
||||
generateRandomAlias: async function (event) {
|
||||
let result = await fetch('/api/alias/random/new', {method: 'POST'});
|
||||
if (result.ok) {
|
||||
let data = await result.json();
|
||||
this.ticket_email = data.alias;
|
||||
} else if (result.status === 429 ){
|
||||
toastr.warning("You've created too many aliases recently. Wait a bit before creating more");
|
||||
} else {
|
||||
toastr.warning("You can't create more aliases");
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$('.custom-file input').change(function (e) {
|
||||
let files = [];
|
||||
for (let i = 0; i < $(this)[0].files.length; i++) {
|
||||
files.push($(this)[0].files[i].name);
|
||||
}
|
||||
$(this).next('.custom-file-label').html(files.join(', '));
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
20
app/templates/dashboard/unsubscribe.html
Normal file
20
app/templates/dashboard/unsubscribe.html
Normal file
@ -0,0 +1,20 @@
|
||||
{% extends "default.html" %}
|
||||
|
||||
{% set active_page = "dashboard" %}
|
||||
{% block title %}Block an alias{% endblock %}
|
||||
{% block default_content %}
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h1 class="h3">Block alias</h1>
|
||||
<p>
|
||||
You are about to block the alias
|
||||
<a href="mailto:{{ alias }}">{{ alias }}</a>
|
||||
</p>
|
||||
<p>After this, you will stop receiving all emails sent to this alias, please confirm.</p>
|
||||
<form method="post">
|
||||
<button class="btn btn-warning">Confirm</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
Reference in New Issue
Block a user