4.21.3
This commit is contained in:
23
app/templates/_formhelpers.html
Normal file
23
app/templates/_formhelpers.html
Normal file
@ -0,0 +1,23 @@
|
||||
{% macro render_field(field) %}
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-2 col-form-label">{{ field.label }}</label>
|
||||
<div class="col-sm-10">
|
||||
{{ field(**kwargs)|safe }}
|
||||
<small class="form-text text-muted">{{ field.description }}</small>
|
||||
{% if field.errors %}
|
||||
|
||||
<ul class="errors">
|
||||
{% for error in field.errors %}<li>{{ error }}</li>{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
{% macro render_field_errors(field) %}
|
||||
{% if field.errors %}
|
||||
|
||||
<ul class="errors">
|
||||
{% for error in field.errors %}<li class="text-danger">{{ error }}</li>{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% endmacro %}
|
25
app/templates/admin/model/newsletter-edit.html
Normal file
25
app/templates/admin/model/newsletter-edit.html
Normal file
@ -0,0 +1,25 @@
|
||||
{#
|
||||
Automatically increase textarea height to match content to facilitate editing
|
||||
#}
|
||||
{% extends 'admin/model/edit.html' %}
|
||||
|
||||
{% block head %}
|
||||
|
||||
{{ super() }}
|
||||
<style>
|
||||
body{
|
||||
max-width: 80%;
|
||||
margin: auto;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
{% block tail %}
|
||||
|
||||
{{ super() }}
|
||||
<script type="application/javascript">
|
||||
$('textarea').each(function (index) {
|
||||
this.style.height = "";
|
||||
this.style.height = this.scrollHeight + "px";
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
30
app/templates/admin/model/newsletter-list.html
Normal file
30
app/templates/admin/model/newsletter-list.html
Normal file
@ -0,0 +1,30 @@
|
||||
{#
|
||||
Add custom input form so admin can enter a user id to send a newsletter to
|
||||
Based on https://github.com/flask-admin/flask-admin/issues/974#issuecomment-168215285
|
||||
#}
|
||||
{% extends 'admin/model/list.html' %}
|
||||
|
||||
{% block model_menu_bar_before_filters %}
|
||||
|
||||
<br>
|
||||
<li id="here" class="form-row">
|
||||
<input name="user_id"
|
||||
class="form-control"
|
||||
placeholder="User ID"
|
||||
aria-describedby="userID"/>
|
||||
<input name="to_address"
|
||||
class="form-control"
|
||||
placeholder="Specify an address to receive the newsletter for testing"
|
||||
aria-describedby="Email address"/>
|
||||
</li>
|
||||
{% endblock %}
|
||||
{% block tail %}
|
||||
|
||||
{{ super() }}
|
||||
<script type="application/javascript">
|
||||
$("input[name='user_id']").appendTo($("#action_form"))
|
||||
$("input[name='to_address']").appendTo($("#action_form"))
|
||||
$("#action_form").appendTo($("#here"))
|
||||
$("#action_form").attr("style", "")
|
||||
</script>
|
||||
{% endblock %}
|
13
app/templates/auth/activate.html
Normal file
13
app/templates/auth/activate.html
Normal file
@ -0,0 +1,13 @@
|
||||
{% extends "error.html" %}
|
||||
|
||||
{% block error_name %}{{ error }}{% endblock %}
|
||||
{% block error_description %}
|
||||
|
||||
{% if show_resend_activation %}
|
||||
|
||||
<div class="text-center text-muted small mt-4">
|
||||
Ask for another activation email?
|
||||
<a href="{{ url_for('auth.resend_activation') }}" style="color: #4d21ff">Resend</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
21
app/templates/auth/change_email.html
Normal file
21
app/templates/auth/change_email.html
Normal file
@ -0,0 +1,21 @@
|
||||
{% extends "single.html" %}
|
||||
|
||||
{% block title %}Change Email{% endblock %}
|
||||
{% block single_content %}
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body p-6">
|
||||
<div class="h3 text-center">Email Update</div>
|
||||
<div class="text-danger text-center h4">
|
||||
Incorrect or expired link.
|
||||
<br />
|
||||
<br />
|
||||
</div>
|
||||
<div class="text-center">
|
||||
Please go to
|
||||
<a href="{{ url_for('dashboard.setting') }}">settings</a>
|
||||
page to re-send the confirmation email.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
75
app/templates/auth/fido.html
Normal file
75
app/templates/auth/fido.html
Normal file
@ -0,0 +1,75 @@
|
||||
{% extends "single.html" %}
|
||||
|
||||
{% block title %}Verify Your Security Key{% endblock %}
|
||||
{% block head %}
|
||||
|
||||
<script src="{{ url_for('static', filename='assets/js/vendors/base64.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='assets/js/vendors/webauthn.js') }}"></script>
|
||||
{% endblock %}
|
||||
{% block single_content %}
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="mb-2">
|
||||
Your account is protected with your security key (WebAuthn).
|
||||
<br />
|
||||
<br />
|
||||
Follow your browser's steps to continue the sign-in process.
|
||||
</div>
|
||||
<form id="formRegisterKey" method="post">
|
||||
{{ fido_token_form.csrf_token }}
|
||||
{{ fido_token_form.sk_assertion(class="form-control", placeholder="") }}
|
||||
<div class="text-center">
|
||||
<button id="btnVerifyKey" class="btn btn-success mt-2" onclick="verifyKey();">Use your security key</button>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
{{ fido_token_form.remember(class="form-check-input", id="remember") }}
|
||||
<label class="form-check-label" for="remember">{{ fido_token_form.remember.description }}</label>
|
||||
</div>
|
||||
</form>
|
||||
{% if enable_otp %}
|
||||
|
||||
<hr />
|
||||
<div class="text-muted mt-5" style="margin-top: 1em;">
|
||||
Don't have your key with you?
|
||||
<br />
|
||||
<a href="{{ url_for('auth.mfa') }}">Verify by One-Time Password</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
<hr />
|
||||
<div class="mt-5">
|
||||
If you have troubles with your authentication app, you can use the recovery code to login.
|
||||
<br />
|
||||
<a href="{{ url_for('auth.recovery_route', next=next_url) }}">Use Recovery Codes</a>
|
||||
</div>
|
||||
<script>
|
||||
async function verifyKey() {
|
||||
$("#btnVerifyKey").prop('disabled', true);
|
||||
$("#btnVerifyKey").text('Waiting for Security Key...');
|
||||
|
||||
const credentialRequestOptions = transformCredentialRequestOptions(
|
||||
JSON.parse('{{webauthn_assertion_options|tojson|safe}}')
|
||||
)
|
||||
|
||||
let assertion;
|
||||
try {
|
||||
assertion = await navigator.credentials.get({
|
||||
publicKey: credentialRequestOptions
|
||||
});
|
||||
} catch (err) {
|
||||
toastr.error("An error occurred when we trying to verify your key.");
|
||||
$("#btnVerifyKey").prop('disabled', false);
|
||||
$("#btnVerifyKey").text('Use your security key');
|
||||
return console.error("Error when trying to get credential:", err);
|
||||
}
|
||||
|
||||
const skAssertion = transformAssertionForServer(assertion);
|
||||
$('#sk_assertion').val(JSON.stringify(skAssertion));
|
||||
$('#formRegisterKey').submit();
|
||||
}
|
||||
|
||||
</script>
|
||||
{% if auto_activate %}<script>$('document').ready(verifyKey());</script>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
26
app/templates/auth/forgot_password.html
Normal file
26
app/templates/auth/forgot_password.html
Normal file
@ -0,0 +1,26 @@
|
||||
{% extends "single.html" %}
|
||||
|
||||
{% block title %}Forgot Password{% endblock %}
|
||||
{% block single_content %}
|
||||
|
||||
{% if error %}<div class="text-danger text-center mb-4">{{ error }}</div>{% endif %}
|
||||
<form class="card" method="post">
|
||||
{{ form.csrf_token }}
|
||||
<div class="card-body p-6">
|
||||
<h1 class="card-title">Forgot password</h1>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Email address</label>
|
||||
{{ form.email(class="form-control", type="email") }}
|
||||
{{ render_field_errors(form.email) }}
|
||||
</div>
|
||||
<div class="form-footer">
|
||||
<button type="submit" class="btn btn-primary btn-block">Reset Password</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<div class="text-center text-muted">
|
||||
Forget it,
|
||||
<a href="{{ url_for('auth.login') }}">send me back</a>
|
||||
to the sign in screen.
|
||||
</div>
|
||||
{% endblock %}
|
52
app/templates/auth/login.html
Normal file
52
app/templates/auth/login.html
Normal file
@ -0,0 +1,52 @@
|
||||
{% extends "single.html" %}
|
||||
|
||||
{% block title %}Login{% endblock %}
|
||||
{% block single_content %}
|
||||
|
||||
{% if show_resend_activation %}
|
||||
|
||||
<div class="text-center text-muted small mb-4">
|
||||
You haven't received the activation email?
|
||||
<a href="{{ url_for('auth.resend_activation') }}">Resend</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="card" style="border-radius: 2%">
|
||||
<div class="card-body p-6">
|
||||
<h1 class="card-title">Welcome back!</h1>
|
||||
<form method="post">
|
||||
{{ form.csrf_token }}
|
||||
<div class="form-group">
|
||||
<label class="form-label">Email address</label>
|
||||
{{ form.email(class="form-control", type="email", autofocus="true") }}
|
||||
{{ render_field_errors(form.email) }}
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Password</label>
|
||||
{{ form.password(class="form-control", type="password") }}
|
||||
{{ render_field_errors(form.password) }}
|
||||
<div class="text-muted">
|
||||
<a href="{{ url_for('auth.forgot_password') }}" class="small">I forgot my password</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-footer">
|
||||
<button type="submit" class="btn btn-primary btn-block">Log in</button>
|
||||
</div>
|
||||
</form>
|
||||
{% if connect_with_proton %}
|
||||
|
||||
<div class="text-center my-2 text-gray">
|
||||
<span>or</span>
|
||||
</div>
|
||||
<a class="btn btn-primary btn-block mt-2 proton-button"
|
||||
href="{{ url_for("auth.proton_login", next=next_url) }}">
|
||||
<img class="mr-2" src="/static/images/proton.svg" />
|
||||
Log in with Proton
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center text-muted mt-2">
|
||||
Don't have an account yet?
|
||||
<a href="{{ url_for('auth.register') }}">Sign up</a>
|
||||
</div>
|
||||
{% endblock %}
|
47
app/templates/auth/mfa.html
Normal file
47
app/templates/auth/mfa.html
Normal file
@ -0,0 +1,47 @@
|
||||
{% extends "single.html" %}
|
||||
|
||||
{% block title %}MFA{% endblock %}
|
||||
{% block single_content %}
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body p-6">
|
||||
<div class="mb-2">
|
||||
Your account is protected with Two Factor Authentication.
|
||||
<br />
|
||||
<br />
|
||||
You will need to enter your 2FA authentication code.
|
||||
</div>
|
||||
<form method="post">
|
||||
{{ otp_token_form.csrf_token }}
|
||||
<input type="hidden" name="form-name" value="create" />
|
||||
<div class="font-weight-bold mt-5">Token</div>
|
||||
<div class="small-text mb-3">Please enter the 2FA code from your 2FA authenticator</div>
|
||||
{{ otp_token_form.token(class="form-control", autofocus="true") }}
|
||||
{{ render_field_errors(otp_token_form.token) }}
|
||||
<div class="form-check">
|
||||
{{ otp_token_form.remember(class="form-check-input", id="remember") }}
|
||||
<label class="form-check-label" for="remember">{{ otp_token_form.remember.description }}</label>
|
||||
</div>
|
||||
<button class="btn btn-success mt-2">Submit</button>
|
||||
</form>
|
||||
{% if enable_fido %}
|
||||
|
||||
<hr />
|
||||
<div class="text-muted mt-5" style="margin-top: 1em;">
|
||||
Having trouble with your authenticator?
|
||||
<br />
|
||||
<a href="{{ url_for('auth.fido') }}">
|
||||
Verify by your security
|
||||
key
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
<hr />
|
||||
<div class="mt-5">
|
||||
If you cannot access your authenticator application you can instead use a recovery code.
|
||||
<br />
|
||||
<a href="{{ url_for('auth.recovery_route', next=next_url) }}">Use Recovery Code</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
18
app/templates/auth/recovery.html
Normal file
18
app/templates/auth/recovery.html
Normal file
@ -0,0 +1,18 @@
|
||||
{% extends "single.html" %}
|
||||
|
||||
{% block title %}Recovery Code{% endblock %}
|
||||
{% block single_content %}
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body p-6">
|
||||
<form method="post">
|
||||
{{ recovery_form.csrf_token }}
|
||||
<div class="font-weight-bold mt-5">Code</div>
|
||||
<div class="small-text">Please enter one of the recovery codes here</div>
|
||||
{{ recovery_form.code(class="form-control", autofocus="true") }}
|
||||
{{ render_field_errors(recovery_form.code) }}
|
||||
<button class="btn btn-success mt-2">Submit</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
61
app/templates/auth/register.html
Normal file
61
app/templates/auth/register.html
Normal file
@ -0,0 +1,61 @@
|
||||
{% extends "single.html" %}
|
||||
|
||||
{% block title %}Register{% endblock %}
|
||||
{% block single_content %}
|
||||
|
||||
<form class="card" style="border-radius: 2%" method="post">
|
||||
{{ form.csrf_token }}
|
||||
<div class="card-body p-6">
|
||||
<h1 class="card-title">Create new account</h1>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Email address</label>
|
||||
{{ form.email(class="form-control", type="email") }}
|
||||
<div class="small-text alert alert-info" style="margin-top: 1px">
|
||||
Emails sent to your alias will be forwarded to this email address.
|
||||
It can't be a disposable or forwarding email address.
|
||||
</div>
|
||||
{{ render_field_errors(form.email) }}
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Password</label>
|
||||
{{ form.password(class="form-control", type="password") }}
|
||||
{{ render_field_errors(form.password) }}
|
||||
</div>
|
||||
<!-- TODO: add terms
|
||||
<div class="form-group">
|
||||
<label class="custom-control custom-checkbox">
|
||||
<input type="checkbox" class="custom-control-input"/>
|
||||
<span class="custom-control-label">Agree the <a href="terms.html">terms and policy</a></span>
|
||||
</label>
|
||||
</div>
|
||||
-->
|
||||
{% if HCAPTCHA_SITEKEY %}
|
||||
|
||||
<div class="h-captcha" data-sitekey="{{ HCAPTCHA_SITEKEY }}"></div>
|
||||
<script src="https://hcaptcha.com/1/api.js" async defer></script>
|
||||
{% endif %}
|
||||
<small class="text-center mt-3">
|
||||
By clicking Create Account, you agree to abide by
|
||||
<a href="https://simplelogin.io/terms">SimpleLogin's Terms and Conditions.</a>
|
||||
</small>
|
||||
<div class="mt-2">
|
||||
<button type="submit" class="btn btn-primary btn-block">Create Account</button>
|
||||
</div>
|
||||
{% if connect_with_proton %}
|
||||
|
||||
<div class="text-center my-2 text-gray">
|
||||
<span>or</span>
|
||||
</div>
|
||||
<a class="btn btn-primary btn-block mt-2 proton-button"
|
||||
href="{{ url_for("auth.proton_login", next=next_url) }}">
|
||||
<img class="mr-2" src="/static/images/proton.svg" />
|
||||
Sign up with Proton
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</form>
|
||||
<div class="text-center text-muted mb-6">
|
||||
Already have account?
|
||||
<a href="{{ url_for('auth.login') }}">Sign in</a>
|
||||
</div>
|
||||
{% endblock %}
|
13
app/templates/auth/register_waiting_activation.html
Normal file
13
app/templates/auth/register_waiting_activation.html
Normal file
@ -0,0 +1,13 @@
|
||||
{% extends "single.html" %}
|
||||
|
||||
{% block title %}Activation Email Sent{% endblock %}
|
||||
{% block single_content %}
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body p-6 text-center">
|
||||
<h1 class="h4">An email to validate your email is on its way.</h1>
|
||||
<p>Please check your inbox/spam folder.</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block script %}<script>plausible('Complete registration')</script>{% endblock %}
|
24
app/templates/auth/resend_activation.html
Normal file
24
app/templates/auth/resend_activation.html
Normal file
@ -0,0 +1,24 @@
|
||||
{% extends "single.html" %}
|
||||
|
||||
{% block title %}Resend activation email{% endblock %}
|
||||
{% block single_content %}
|
||||
|
||||
<form class="card" method="post">
|
||||
{{ form.csrf_token }}
|
||||
<div class="card-body p-6">
|
||||
<div class="card-title">Resend activation email</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Email address</label>
|
||||
{{ form.email(class="form-control", type="email") }}
|
||||
{{ render_field_errors(form.email) }}
|
||||
</div>
|
||||
<div class="form-footer">
|
||||
<button type="submit" class="btn btn-primary btn-block">Resend</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<div class="text-center text-muted">
|
||||
Don't have account yet?
|
||||
<a href="{{ url_for('auth.register') }}">Sign up</a>
|
||||
</div>
|
||||
{% endblock %}
|
21
app/templates/auth/reset_password.html
Normal file
21
app/templates/auth/reset_password.html
Normal file
@ -0,0 +1,21 @@
|
||||
{% extends "single.html" %}
|
||||
|
||||
{% block title %}Reset password{% endblock %}
|
||||
{% block single_content %}
|
||||
|
||||
{% if error %}<div class="text-danger text-center mb-4">{{ error }}</div>{% endif %}
|
||||
<form class="card" method="post">
|
||||
{{ form.csrf_token }}
|
||||
<div class="card-body p-6">
|
||||
<div class="card-title">Reset your password</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Password</label>
|
||||
{{ form.password(class="form-control", type="password") }}
|
||||
{{ render_field_errors(form.password) }}
|
||||
</div>
|
||||
<div class="form-footer">
|
||||
<button type="submit" class="btn btn-primary btn-block">Reset</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
46
app/templates/auth/social.html
Normal file
46
app/templates/auth/social.html
Normal file
@ -0,0 +1,46 @@
|
||||
{% extends "single.html" %}
|
||||
|
||||
{% block title %}Social Login{% endblock %}
|
||||
{% block single_content %}
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body p-6">
|
||||
<div class="card-title text-center">Social login</div>
|
||||
{% if GITHUB_CLIENT_ID %}
|
||||
|
||||
<a href="{{ url_for('auth.github_login', next=next_url) }}"
|
||||
class="btn btn-block btn-social btn-github">
|
||||
<i class="fa fa-github"></i> Sign in with GitHub
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if GOOGLE_CLIENT_ID %}
|
||||
|
||||
<a href="{{ url_for('auth.google_login', next=next_url) }}"
|
||||
class="btn btn-block btn-social btn-google">
|
||||
<i class="fa fa-google"></i> Sign in with Google
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if FACEBOOK_CLIENT_ID %}
|
||||
|
||||
<a href="{{ url_for('auth.facebook_login', next=next_url) }}"
|
||||
class="btn btn-block btn-social btn-facebook">
|
||||
<i class="fa fa-facebook"></i> Sign in with Facebook
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="text-center p-3"
|
||||
style="font-size: 12px; font-weight: 300; margin: auto">
|
||||
<span class="badge badge-warning">Warning</span>
|
||||
Please note that social login is now <b>deprecated</b>.
|
||||
<br />
|
||||
<br />
|
||||
Though practical, these social providers do not respect your privacy and therefore we recommend using
|
||||
email/password.
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center text-muted mt-2">
|
||||
<a href="{{ url_for('auth.register') }}">Sign up</a>
|
||||
/
|
||||
<a href="{{ url_for('auth.login') }}">Login</a>
|
||||
</div>
|
||||
{% endblock %}
|
188
app/templates/base.html
Normal file
188
app/templates/base.html
Normal file
@ -0,0 +1,188 @@
|
||||
{% from "_formhelpers.html" import render_field, render_field_errors %}
|
||||
<!doctype html>
|
||||
<html lang="en"
|
||||
dir="ltr"
|
||||
data-theme="{%- if request.cookies.get('dark-mode') == 'true' -%} dark{%- endif -%}">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport"
|
||||
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"/>
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
|
||||
<meta http-equiv="Content-Language" content="en" />
|
||||
<meta name="msapplication-TileColor" content="#2d89ef" />
|
||||
<meta name="theme-color" content="#4188c9" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style"
|
||||
content="black-translucent"/>
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="mobile-web-app-capable" content="yes" />
|
||||
<meta name="HandheldFriendly" content="True" />
|
||||
<meta name="MobileOptimized" content="320" />
|
||||
<meta name="referrer" content="no-referrer" />
|
||||
<!-- Bing -->
|
||||
<meta name="msvalidate.01" content="2A313A69CBFD1A378C3B91734DC221A8" />
|
||||
<!-- Yandex -->
|
||||
<meta name="yandex-verification" content="c9e5d4d68bc983a1" />
|
||||
<meta name="description"
|
||||
content="Protect your email address with email ALIAS. Create a different email alias for each website. No more phishing, spams."/>
|
||||
<link rel="icon" href="/static/favicon.ico" type="image/x-icon" />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="/static/favicon.ico" />
|
||||
<link rel="canonical" href="{{ CANONICAL_URL }}" />
|
||||
<title>
|
||||
{% block title %}{% endblock %}
|
||||
| SimpleLogin
|
||||
</title>
|
||||
<link rel="stylesheet"
|
||||
href="{{ url_for('static', filename='node_modules/font-awesome/css/font-awesome.css') }}"/>
|
||||
<!-- Dashboard Core -->
|
||||
<link href="/static/assets/css/dashboard.css" rel="stylesheet" />
|
||||
<!-- Tabler JS -->
|
||||
<script src="/static/assets/js/vendors/jquery-3.2.1.min.js"></script>
|
||||
<script src="/static/assets/js/vendors/bootstrap.bundle.min.js"></script>
|
||||
<script src="/static/assets/js/vendors/jquery.sparkline.min.js"></script>
|
||||
<script src="/static/assets/js/vendors/selectize.min.js"></script>
|
||||
<script src="/static/assets/js/vendors/jquery.tablesorter.min.js"></script>
|
||||
<script src="/static/assets/js/vendors/jquery-jvectormap-2.0.3.min.js"></script>
|
||||
<script src="/static/assets/js/vendors/jquery-jvectormap-de-merc.js"></script>
|
||||
<script src="/static/assets/js/vendors/jquery-jvectormap-world-mill.js"></script>
|
||||
<script src="/static/assets/js/vendors/circle-progress.min.js"></script>
|
||||
<script src="/static/assets/js/core.js"></script>
|
||||
<!-- ClipboardJS -->
|
||||
<script src="/static/vendor/clipboard.min.js"></script>
|
||||
<!-- IntroJS -->
|
||||
<link rel="stylesheet"
|
||||
type="text/css"
|
||||
href="{{ url_for('static', filename='node_modules/intro.js/minified/introjs.min.css') }}"/>
|
||||
<script src="{{ url_for('static', filename='node_modules/intro.js/minified/intro.min.js') }}"></script>
|
||||
<!-- Sentry -->
|
||||
<script src="{{ url_for('static', filename='node_modules/@sentry/browser/build/bundle.min.js') }}"></script>
|
||||
<link rel="stylesheet" href="/static/vendor/bootstrap-social.min.css" />
|
||||
<!-- Toastr library -->
|
||||
<link rel="stylesheet"
|
||||
href="{{ url_for('static', filename='node_modules/toastr/build/toastr.min.css') }}"/>
|
||||
<script src="{{ url_for('static', filename='node_modules/toastr/build/toastr.min.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='node_modules/bootbox/dist/bootbox.min.js') }}"></script>
|
||||
<!-- Multiple-select library -->
|
||||
<link rel="stylesheet"
|
||||
href="{{ url_for('static', filename='node_modules/multiple-select/dist/multiple-select.min.css') }}"/>
|
||||
<script src="{{ url_for('static', filename='node_modules/multiple-select/dist/multiple-select.min.js') }}"></script>
|
||||
<!-- Parseley library -->
|
||||
<script src="{{ url_for('static', filename='node_modules/parsleyjs/dist/parsley.min.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='node_modules/parsleyjs/dist/i18n/en.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='node_modules/htmx.org/dist/htmx.min.js') }}"></script>
|
||||
{% if PLAUSIBLE_HOST and PLAUSIBLE_DOMAIN %}
|
||||
|
||||
<!-- Plausible Analytics library -->
|
||||
<script async defer data-domain=”{{ PLAUSIBLE_DOMAIN }}” src=”{{ PLAUSIBLE_HOST }}/js/plausible.outbound-links.js”></script>
|
||||
{% endif %}
|
||||
<link rel="stylesheet"
|
||||
href="{{ url_for('static', filename='darkmode.css') }}?v={{ VERSION }}"/>
|
||||
<link rel="stylesheet"
|
||||
type="text/css"
|
||||
href="/static/style.css?v={{ VERSION }}"/>
|
||||
<script src="{{ url_for('static', filename='js/theme.js') }}"></script>
|
||||
<script>toastr.options.closeButton = true;</script>
|
||||
<!-- For additional head -->
|
||||
{% block head %}{% endblock %}
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
{% block announcement %}{% endblock %}
|
||||
<div class="container">
|
||||
<!-- For flash messages -->
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
<!-- Categories: success (green), info (blue), warning (yellow), danger (red) -->
|
||||
{% if messages %}
|
||||
|
||||
{% for category, message in messages %}<script>toastr.{{category }}("{{ message }}");</script>{% endfor %}
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
{% block content %}{% endblock %}
|
||||
</div>
|
||||
<script>
|
||||
{% if SENTRY_DSN %}
|
||||
console.log("Init sentry");
|
||||
try {
|
||||
Sentry.init({
|
||||
dsn: '{{SENTRY_DSN}}',
|
||||
// https://docs.sentry.io/platforms/javascript/#filter-events--custom-logic
|
||||
// Original gist https://gist.github.com/impressiver/5092952
|
||||
ignoreErrors: [
|
||||
// Random plugins/extensions
|
||||
'top.GLOBALS',
|
||||
// See: http://blog.errorception.com/2012/03/tale-of-unfindable-js-error.html
|
||||
'originalCreateNotification',
|
||||
'canvas.contentDocument',
|
||||
'MyApp_RemoveAllHighlights',
|
||||
'http://tt.epicplay.com',
|
||||
'Can\'t find variable: ZiteReader',
|
||||
'jigsaw is not defined',
|
||||
'ComboSearch is not defined',
|
||||
'http://loading.retry.widdit.com/',
|
||||
'atomicFindClose',
|
||||
// Facebook borked
|
||||
'fb_xd_fragment',
|
||||
// ISP "optimizing" proxy - `Cache-Control: no-transform` seems to
|
||||
// reduce this. (thanks @acdha)
|
||||
// See http://stackoverflow.com/questions/4113268
|
||||
'bmi_SafeAddOnload',
|
||||
'EBCallBackMessageReceived',
|
||||
// See http://toolbar.conduit.com/Developer/HtmlAndGadget/Methods/JSInjection.aspx
|
||||
'conduitPage'
|
||||
],
|
||||
blacklistUrls: [
|
||||
// Facebook flakiness
|
||||
/graph\.facebook\.com/i,
|
||||
// Facebook blocked
|
||||
/connect\.facebook\.net\/en_US\/all\.js/i,
|
||||
// Woopra flakiness
|
||||
/eatdifferent\.com\.woopra-ns\.com/i,
|
||||
/static\.woopra\.com\/js\/woopra\.js/i,
|
||||
// Chrome extensions
|
||||
/extensions\//i,
|
||||
/^chrome:\/\//i,
|
||||
// Other plugins
|
||||
/127\.0\.0\.1:4001\/isrunning/i, // Cacaoweb
|
||||
/webappstoolbarba\.texthelp\.com\//i,
|
||||
/metrics\.itunes\.apple\.com\.edgesuite\.net\//i
|
||||
]
|
||||
});
|
||||
} catch (error) {
|
||||
console.log("Sentry error, probably due to AdBlocker ...")
|
||||
}
|
||||
{% endif %}
|
||||
|
||||
// default options for bootbox
|
||||
bootbox.setDefaults({
|
||||
closeButton: false,
|
||||
backdrop: true
|
||||
})
|
||||
|
||||
var clipboard = new ClipboardJS('.clipboard');
|
||||
|
||||
clipboard.on('success', function (e) {
|
||||
toastr.success("Copied to clipboard");
|
||||
e.clearSelection();
|
||||
});
|
||||
|
||||
// Handle back or close button
|
||||
$('.back-or-close').on("click", function () {
|
||||
// the window is actually a popup, in this case just close it
|
||||
if (history.length == 1) {
|
||||
window.close();
|
||||
} else {
|
||||
history.back();
|
||||
}
|
||||
});
|
||||
|
||||
document.body.addEventListener('htmx:responseError', function(evt) {
|
||||
toastr.error("Sorry for the inconvenience! Could you refresh the page & retry please?", "Unknown Error");
|
||||
});
|
||||
|
||||
</script>
|
||||
<script src="{{ url_for('static', filename='local-storage-polyfill.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/an.js', v='2') }}"></script>
|
||||
<!-- For additional script -->
|
||||
{% block script %}{% endblock %}
|
||||
</body>
|
||||
</html>
|
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 %}
|
16
app/templates/default.html
Normal file
16
app/templates/default.html
Normal file
@ -0,0 +1,16 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="flex-fill">
|
||||
{% include "header.html" %}
|
||||
|
||||
<div class="my-2 my-md-2">
|
||||
<div class="container pt-1" style="min-height: 800px">
|
||||
{% block default_content %}{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% include "footer.html" %}
|
||||
|
||||
{% endblock %}
|
14
app/templates/developer/client_details/advanced.html
Normal file
14
app/templates/developer/client_details/advanced.html
Normal file
@ -0,0 +1,14 @@
|
||||
{% extends "developer/client_details/base.html" %}
|
||||
|
||||
{% set client_details_page = "advanced" %}
|
||||
{% block client_details_content %}
|
||||
|
||||
<h1 class="h2">Danger Zone</h1>
|
||||
<form method="post">
|
||||
{{ form.csrf_token }}
|
||||
<div class="alert alert-danger">
|
||||
When your website is deleted, users can no longer log into it. This operation is not reversible!
|
||||
</div>
|
||||
<button type="submit" class="btn btn-danger">Delete "{{ client.name }}"</button>
|
||||
</form>
|
||||
{% endblock %}
|
50
app/templates/developer/client_details/base.html
Normal file
50
app/templates/developer/client_details/base.html
Normal file
@ -0,0 +1,50 @@
|
||||
{% extends "default.html" %}
|
||||
|
||||
{% set active_page = "developer" %}
|
||||
{% block title %}Developer - App {{ client.name }}{% endblock %}
|
||||
{% 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('developer.client_detail', client_id=client.id) }}"
|
||||
class="list-group-item list-group-item-action {{ 'active' if client_details_page == 'basic_info' }}">
|
||||
<span class="icon mr-3"><i class="fe fe-flag"></i></span>Info
|
||||
</a>
|
||||
<a href="{{ url_for('developer.client_detail_oauth_setting', client_id=client.id) }}"
|
||||
class="list-group-item list-group-item-action {{ 'active' if client_details_page == 'oauth_setting' }}">
|
||||
<span class="icon mr-3"><i class="fe fe-settings"></i></span>OAuth Settings
|
||||
</a>
|
||||
<a href="{{ url_for('developer.client_detail_oauth_endpoint', client_id=client.id) }}"
|
||||
class="list-group-item list-group-item-action {{ 'active' if client_details_page == 'oauth_endpoint' }}">
|
||||
<span class="icon mr-3"><i class="fe fe-shield"></i></span>OAuth Endpoints
|
||||
</a>
|
||||
{% if current_user.referrals|count > 0 %}
|
||||
|
||||
<a href="{{ url_for('developer.client_detail_referral', client_id=client.id) }}"
|
||||
class="list-group-item list-group-item-action {{ 'active' if client_details_page == 'referral' }}">
|
||||
<span class="icon mr-3"><i class="fe fe-share"></i></span>Referral
|
||||
</a>
|
||||
{% endif %}
|
||||
<a href="{{ url_for('developer.client_detail_advanced', client_id=client.id) }}"
|
||||
class="list-group-item list-group-item-action {{ 'active' if client_details_page == 'advanced' }}">
|
||||
<span class="icon mr-3"><i class="fe fe-alert-octagon"></i></span>Danger
|
||||
</a>
|
||||
</div>
|
||||
<a href="https://docs.simplelogin.io"
|
||||
target="_blank"
|
||||
class="btn btn-block btn-secondary mt-4">
|
||||
Documentation <i class="fe fe-external-link"></i>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-lg-9">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="text-wrap p-lg-6">
|
||||
{% block client_details_content %}{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
78
app/templates/developer/client_details/basic_info.html
Normal file
78
app/templates/developer/client_details/basic_info.html
Normal file
@ -0,0 +1,78 @@
|
||||
{% extends "developer/client_details/base.html" %}
|
||||
|
||||
{% set client_details_page = "basic_info" %}
|
||||
{% block client_details_content %}
|
||||
|
||||
{% if is_new %}
|
||||
|
||||
<div class="alert alert-warning alert-dismissible fade show mb-4"
|
||||
role="alert">
|
||||
<h4 class="alert-heading">Well done!</h4>
|
||||
<p>
|
||||
Please head to our
|
||||
<a href="https://docs.simplelogin.io" target="_blank" rel="noopener">
|
||||
documentation <i class="fe fe-external-link"></i>
|
||||
</a>
|
||||
to see how to add SIWSL into your app.
|
||||
</p>
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
|
||||
<span aria-hidden="true"></span>
|
||||
</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
<form method="post"
|
||||
enctype="multipart/form-data"
|
||||
action="{{ url_for('developer.client_detail', client_id=client.id, action="edit") }}">
|
||||
{{ form.csrf_token }}
|
||||
<h1 class="h2">Info</h1>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Name</label>
|
||||
{{ form.name(class="form-control", value=client.name) }}
|
||||
{{ render_field_errors(form.name) }}
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Url</label>
|
||||
{{ form.url(class="form-control", value=client.home_url) }}
|
||||
{{ render_field_errors(form.url) }}
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">
|
||||
Icon
|
||||
<span class="text-muted small-text">The icon is displayed on the authorization page</span>
|
||||
</label>
|
||||
{{ form.icon(class="form-control-file") }}
|
||||
{{ render_field_errors(form.icon) }}
|
||||
{% if client.icon_id %}<img src="{{ client.icon.get_url() }}" class="client-icon">{% endif %}
|
||||
</div>
|
||||
<button type="submit" class="mt-2 btn btn-primary">Update</button>
|
||||
</form>
|
||||
{# <hr />#}
|
||||
{# <h3>Submit for approval</h3>#}
|
||||
{##}
|
||||
{# <div class="alert alert-info">#}
|
||||
{# Approval is only needed when you deploy the <b>Sign in with SimpleLogin</b> integration#}
|
||||
{# in production. <br />#}
|
||||
{# For local/testing/staging environment, you don't have to submit your app/website for approval. <br />#}
|
||||
{# </div>#}
|
||||
{##}
|
||||
{# <form method="post" enctype="multipart/form-data"#}
|
||||
{# action="{{ url_for('developer.client_detail', client_id=client.id, action="submit") }}">#}
|
||||
{# {{ approval_form.csrf_token }}#}
|
||||
{##}
|
||||
{# <div class="form-group">#}
|
||||
{# <label class="form-label">Tell us about your app</label>#}
|
||||
{# {{ approval_form.description(#}
|
||||
{# class="form-control", rows="10",#}
|
||||
{# placeholder="This information is used for approving your application. Please give us as much info as you can, for example where you plan to use SimpleLogin, for which community, etc."#}
|
||||
{# ) }}#}
|
||||
{# {{ render_field_errors(approval_form.description) }}#}
|
||||
{# </div>#}
|
||||
{##}
|
||||
{# <div class="alert alert-warning">#}
|
||||
{# Don't make this frequent mistake: make sure to add your production URL to the <b>Authorized Redirect URIs</b> on#}
|
||||
{# <a href="{{ url_for('developer.client_detail_oauth_setting', client_id=client.id) }}">OAuth Settings</a>.#}
|
||||
{# </div>#}
|
||||
{##}
|
||||
{# <button type="submit" class="btn btn-success">Submit</button>#}
|
||||
{# </form>#}
|
||||
{% endblock %}
|
71
app/templates/developer/client_details/oauth_endpoint.html
Normal file
71
app/templates/developer/client_details/oauth_endpoint.html
Normal file
@ -0,0 +1,71 @@
|
||||
{% extends "developer/client_details/base.html" %}
|
||||
|
||||
{% set client_details_page = "oauth_endpoint" %}
|
||||
{% block client_details_content %}
|
||||
|
||||
<h1 class="h2">OAuth2 endpoints</h1>
|
||||
<div class="form-group">
|
||||
<label class="form-label">OpenID Connect Discovery Document</label>
|
||||
<div class="input-group mt-2">
|
||||
<input type="text"
|
||||
disabled
|
||||
value="{{ URL + "/.well-known/openid-configuration" }}"
|
||||
class="form-control">
|
||||
<span class="input-group-append">
|
||||
<button data-clipboard-text="{{ URL + "/.well-known/openid-configuration" }}"
|
||||
class="clipboard btn btn-primary"
|
||||
type="button">
|
||||
<i class="fe fe-clipboard"></i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Authorization endpoint</label>
|
||||
<div class="input-group mt-2">
|
||||
<input type="text"
|
||||
disabled
|
||||
value="{{ URL + "/oauth2/authorize" }}"
|
||||
class="form-control">
|
||||
<span class="input-group-append">
|
||||
<button data-clipboard-text="{{ URL + "/oauth2/authorize" }}"
|
||||
class="clipboard btn btn-primary"
|
||||
type="button">
|
||||
<i class="fe fe-clipboard"></i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Token endpoint</label>
|
||||
<div class="input-group mt-2">
|
||||
<input type="text"
|
||||
disabled
|
||||
value="{{ URL + "/oauth2/token" }}"
|
||||
class="form-control">
|
||||
<span class="input-group-append">
|
||||
<button data-clipboard-text="{{ URL + "/oauth2/token" }}"
|
||||
class="clipboard btn btn-primary"
|
||||
type="button">
|
||||
<i class="fe fe-clipboard"></i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">UserInfo endpoint</label>
|
||||
<div class="input-group mt-2">
|
||||
<input type="text"
|
||||
disabled
|
||||
value="{{ URL + "/oauth2/userinfo" }}"
|
||||
class="form-control">
|
||||
<span class="input-group-append">
|
||||
<button data-clipboard-text="{{ URL + "/oauth2/userinfo" }}"
|
||||
class="clipboard btn btn-primary"
|
||||
type="button">
|
||||
<i class="fe fe-clipboard"></i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
130
app/templates/developer/client_details/oauth_setting.html
Normal file
130
app/templates/developer/client_details/oauth_setting.html
Normal file
@ -0,0 +1,130 @@
|
||||
{% extends "developer/client_details/base.html" %}
|
||||
|
||||
{% set client_details_page = "oauth_setting" %}
|
||||
{% block client_details_content %}
|
||||
|
||||
<form method="post">
|
||||
{{ form.csrf_token }}
|
||||
<h1 class="h2">OAuth2 Settings</h1>
|
||||
<div class="form-group">
|
||||
<label class="form-label">AppID / OAuth2 Client ID</label>
|
||||
<div class="input-group mt-2">
|
||||
<input disabled
|
||||
type="text"
|
||||
value="{{ client.oauth_client_id }}"
|
||||
class="form-control">
|
||||
<span class="input-group-append">
|
||||
<button data-clipboard-text="{{ client.oauth_client_id }}"
|
||||
class="clipboard btn btn-primary"
|
||||
type="button">
|
||||
<i class="fe fe-clipboard"></i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">AppSecret / OAuth2 Client Secret</label>
|
||||
<div class="input-group mt-2">
|
||||
<input disabled
|
||||
type="password"
|
||||
value="{{ client.oauth_client_secret }}"
|
||||
class="form-control">
|
||||
<span class="input-group-append">
|
||||
<button data-clipboard-text="{{ client.oauth_client_secret }}"
|
||||
class="clipboard btn btn-primary"
|
||||
type="button">
|
||||
<i class="fe fe-clipboard"></i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<hr />
|
||||
<div class="form-group">
|
||||
<label class="form-label">Authorized Redirect URIs</label>
|
||||
<small class="text-muted">
|
||||
By default <b>localhost</b> is whitelisted.
|
||||
<br />
|
||||
A <b>redirect_uri</b> must be <b>HTTPS</b> for security reason.
|
||||
</small>
|
||||
{% if not client.redirect_uris %}
|
||||
|
||||
<div class="alert alert-warning alert-dismissible fade show mb-4"
|
||||
role="alert">
|
||||
<p>
|
||||
You haven't added any
|
||||
<a href="https://www.oauth.com/oauth2-servers/redirect-uris/">redirect_uri</a>
|
||||
,
|
||||
that is the url that will receive the <b>code</b> or <b>access-token</b> in OAuth2 flow.
|
||||
</p>
|
||||
<p>
|
||||
There's NO NEED to add <em>http://localhost:*</em> as by default,
|
||||
SimpleLogin <b>whitelists</b> localhost (unlike Facebook).
|
||||
</p>
|
||||
<p>
|
||||
You DO need to add your <b>redirect_uri</b> once your app goes live (i.e. deployed on production).
|
||||
</p>
|
||||
<p>
|
||||
The <b>redirect_uri</b> needs to be <b>HTTPS</b> for security reason.
|
||||
</p>
|
||||
<p>
|
||||
Start by adding your first <b>redirect_uri</b> here 👇
|
||||
</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% for redirect_uri in client.redirect_uris %}
|
||||
|
||||
<div class="input-group mt-2">
|
||||
<input type="url"
|
||||
name="uri"
|
||||
class="form-control"
|
||||
value="{{ redirect_uri.uri }}"
|
||||
required
|
||||
pattern="^https:\/\/.*"
|
||||
title="redirect_uri must be https">
|
||||
<span class="input-group-append">
|
||||
<button class="remove-uri btn btn-primary" type="button">
|
||||
<i class="fe fe-x"></i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
{% endfor %}
|
||||
<div id="new-uris">
|
||||
<!-- New uri will be put here -->
|
||||
</div>
|
||||
<button type="button" id="create-new-uri" class="mt-2 btn btn-secondary">Add new uri</button>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Update</button>
|
||||
</form>
|
||||
<!-- template for new uri -->
|
||||
<div class="input-group mt-2" id="hidden-uri" style="display: none">
|
||||
<input type="url"
|
||||
name="uri"
|
||||
class="form-control"
|
||||
required
|
||||
pattern="^https:\/\/.*"
|
||||
title="redirect_uri must be https">
|
||||
<span class="input-group-append">
|
||||
<button class="remove-uri btn btn-primary" type="button">
|
||||
<i class="fe fe-x"></i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block script %}
|
||||
|
||||
<script>
|
||||
$("#create-new-uri").on("click", function (e) {
|
||||
var clone = $("#hidden-uri").clone(true, true); // (true, true) to clone withDataAndEvents, deepWithDataAndEvents
|
||||
clone.removeAttr("id");
|
||||
|
||||
$("#new-uris").append(clone);
|
||||
clone.show();
|
||||
});
|
||||
|
||||
$(".remove-uri").click(function (e) {
|
||||
var currentElement = $(this);
|
||||
currentElement.parent().parent().remove();
|
||||
});
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
35
app/templates/developer/client_details/referral.html
Normal file
35
app/templates/developer/client_details/referral.html
Normal file
@ -0,0 +1,35 @@
|
||||
{% extends "developer/client_details/base.html" %}
|
||||
|
||||
{% set client_details_page = "referral" %}
|
||||
{% block client_details_content %}
|
||||
|
||||
<h1 class="h2">Referral</h1>
|
||||
<div>
|
||||
If you are in the
|
||||
<a href="{{ url_for('dashboard.referral_route') }}">referral</a>
|
||||
program, you can attach a
|
||||
referral to this website.
|
||||
Any SimpleLogin sign up thanks to the SIWSL on your website will be counted towards this referral.
|
||||
</div>
|
||||
<form method="post" class="mt-3">
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="client-select">Referral</label>
|
||||
<select class="form-control" name="referral-id" id="client-select">
|
||||
{% for referral in current_user.referrals %}
|
||||
|
||||
<option value="{{ referral.id }}"
|
||||
{% if client.referral_id == referral.id %} selected{% endif %}>
|
||||
{{ referral.name }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
{% if client.referral_id is none %}
|
||||
|
||||
<option value="" selected>
|
||||
No referral selected
|
||||
</option>
|
||||
{% endif %}
|
||||
</select>
|
||||
</div>
|
||||
<input type="submit" class="btn btn-primary" value="Update">
|
||||
</form>
|
||||
{% endblock %}
|
91
app/templates/developer/index.html
Normal file
91
app/templates/developer/index.html
Normal file
@ -0,0 +1,91 @@
|
||||
{% extends "default.html" %}
|
||||
|
||||
{% set active_page = "developer" %}
|
||||
{% block title %}Sign in with SimpleLogin{% endblock %}
|
||||
{% block default_content %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<h1 class="h3">
|
||||
Sign in with SimpleLogin (SIWSL)
|
||||
<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 {% if not clients %} show{% endif %}"
|
||||
id="howtouse"
|
||||
role="alert">
|
||||
If you want to integrate SIWSL into your website,
|
||||
this page is for you.
|
||||
<br />
|
||||
<br />
|
||||
If you are using a CMS or any system that supports a OpenID Connect plugin, you can just point
|
||||
it to SimpleLogin OpenID Configuration endpoint 👇
|
||||
<div class="input-group mt-2">
|
||||
<input type="text"
|
||||
disabled
|
||||
value="{{ URL + "/.well-known/openid-configuration" }}"
|
||||
class="form-control">
|
||||
<span class="input-group-append">
|
||||
<button data-clipboard-text="{{ URL + "/.well-known/openid-configuration" }}"
|
||||
class="clipboard btn btn-primary"
|
||||
type="button">
|
||||
<i class="fe fe-clipboard"></i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="btn-group" role="group" aria-label="Basic example">
|
||||
<a href="{{ url_for('developer.new_client') }}" class="btn btn-primary">New website</a>
|
||||
<a href="https://docs.simplelogin.io"
|
||||
target="_blank"
|
||||
class="ml-2 btn btn-secondary">
|
||||
Docs <i class="fe fe-external-link"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row row-cards row-deck mt-4">
|
||||
{% for client in clients %}
|
||||
|
||||
<div class="col-12 col-lg-6">
|
||||
<div class="card" style="">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">
|
||||
<a href="{{ url_for('developer.client_detail', client_id=client.id) }}">{{ client.name }}</a>
|
||||
{% if client.approved %}
|
||||
|
||||
<span class="cursor" data-toggle="tooltip" data-original-title="Approved">✅</span>
|
||||
{% else %}
|
||||
<span class="cursor"
|
||||
data-toggle="tooltip"
|
||||
data-original-title="In Dev mode. Please contact us to publish your app.">
|
||||
<a href="{{ url_for('developer.client_detail', client_id=client.id) }}"
|
||||
class="text-decoration-none">🚫</a>
|
||||
</span>
|
||||
{% endif %}
|
||||
</h5>
|
||||
<h6 class="card-subtitle mb-4 text-muted">
|
||||
Created {{ client.created_at | dt }}
|
||||
<br />
|
||||
<span class="font-weight-bold">{{ client.nb_user() }}</span> users
|
||||
<br />
|
||||
</h6>
|
||||
<a href="{{ url_for('developer.client_detail', client_id=client.id) }}"
|
||||
class="mt-3">Details ➡</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endblock %}
|
26
app/templates/developer/new_client.html
Normal file
26
app/templates/developer/new_client.html
Normal file
@ -0,0 +1,26 @@
|
||||
{% extends "default.html" %}
|
||||
|
||||
{% set active_page = "developer" %}
|
||||
{% block title %}Developer - Create new website{% endblock %}
|
||||
{% block default_content %}
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h1 class="h3">New website</h1>
|
||||
<form method="post" enctype="multipart/form-data">
|
||||
{{ form.csrf_token }}
|
||||
<div class="form-group">
|
||||
<label class="form-label">Name</label>
|
||||
{{ form.name(class="form-control", placeholder="My Community Forum") }}
|
||||
{{ render_field_errors(form.name) }}
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Url</label>
|
||||
{{ form.url(class="form-control", type="url", placeholder="https://forum.com") }}
|
||||
{{ render_field_errors(form.url) }}
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Create</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
29
app/templates/discover/index.html
Normal file
29
app/templates/discover/index.html
Normal file
@ -0,0 +1,29 @@
|
||||
{% extends "default.html" %}
|
||||
|
||||
{% set active_page = "discover" %}
|
||||
{% block title %}Discover{% endblock %}
|
||||
{% block default_content %}
|
||||
|
||||
<h3>Apps</h3>
|
||||
<p class="text-muted">
|
||||
App/Website that have implemented <b>Connect with SimpeLogin</b>
|
||||
</p>
|
||||
<div class="row row-cards row-deck">
|
||||
{% for client in clients %}
|
||||
|
||||
<div class="col-sm-4 col-xl-2">
|
||||
<div class="card">
|
||||
<a href="{{ client.home_url }}" target="_blank" rel="noopener">
|
||||
<img class="card-img-top" src="{{ client.get_icon_url() }}">
|
||||
</a>
|
||||
<div class="card-body d-flex flex-column">
|
||||
<h4>
|
||||
<a href="{{ client.home_url }}">{{ client.name }}</a>
|
||||
</h4>
|
||||
<div class="text-muted">{{ client.home_url }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endblock %}
|
137
app/templates/emails/_emailhelpers.html
Normal file
137
app/templates/emails/_emailhelpers.html
Normal file
@ -0,0 +1,137 @@
|
||||
{% macro render_text(text) %}
|
||||
<p style="font-size: 16px;
|
||||
line-height: 1.625;
|
||||
color: #51545E;
|
||||
margin: .4em 0 1.1875em;">{{ text }}</p>
|
||||
{% endmacro %}
|
||||
<!-- To be used instead of render_text, much better! -->
|
||||
{% macro text() %}
|
||||
<p style="font-size: 16px;
|
||||
line-height: 1.625;
|
||||
color: #51545E;
|
||||
margin: .4em 0 1.1875em;">{{ caller() }}</p>
|
||||
{% endmacro %}
|
||||
{% macro render_button(button_text, link) %}
|
||||
<!-- Action -->
|
||||
<table class="body-action"
|
||||
align="center"
|
||||
width="100%"
|
||||
cellpadding="0"
|
||||
cellspacing="0"
|
||||
role="presentation"
|
||||
style="width: 100%;
|
||||
-premailer-width: 100%;
|
||||
-premailer-cellpadding: 0;
|
||||
-premailer-cellspacing: 0;
|
||||
text-align: center;
|
||||
margin: 30px auto;
|
||||
padding: 0;">
|
||||
<tr>
|
||||
<td align="center"
|
||||
style="word-break: break-word;
|
||||
font-family: Helvetica, Arial, sans-serif;
|
||||
font-size: 16px;">
|
||||
<!-- Border based button
|
||||
https://litmus.com/blog/a-guide-to-bulletproof-buttons-in-email-design -->
|
||||
<table width="100%"
|
||||
border="0"
|
||||
cellspacing="0"
|
||||
cellpadding="0"
|
||||
role="presentation">
|
||||
<tr>
|
||||
<td align="center"
|
||||
style="word-break: break-word;
|
||||
font-family: Helvetica, Arial, sans-serif;
|
||||
font-size: 16px;">
|
||||
<a href="{{ link }}"
|
||||
class="f-fallback button"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
style="color: #FFF;
|
||||
border-color: #3869d4;
|
||||
border-style: solid;
|
||||
border-width: 10px 18px;
|
||||
background-color: #3869D4;
|
||||
display: inline-block;
|
||||
text-decoration: none;
|
||||
border-radius: 3px;
|
||||
box-shadow: 0 2px 3px rgba(0, 0, 0, 0.16);
|
||||
-webkit-text-size-adjust: none;
|
||||
box-sizing: border-box;">
|
||||
{{ button_text }}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
{% endmacro %}
|
||||
{% macro raw_url(link) %}
|
||||
<!-- Sub copy -->
|
||||
<table class="body-sub"
|
||||
role="presentation"
|
||||
style="margin-top: 25px;
|
||||
padding-top: 25px;
|
||||
border-top-width: 1px;
|
||||
border-top-color: #EAEAEC;
|
||||
border-top-style: solid;">
|
||||
<tr>
|
||||
<td style="word-break: break-word;
|
||||
font-family: Helvetica, Arial, sans-serif;
|
||||
font-size: 16px;">
|
||||
<p class="f-fallback sub"
|
||||
style="font-size: 13px;
|
||||
line-height: 1.625;
|
||||
color: #51545E;
|
||||
margin: .4em 0 1.1875em;">
|
||||
If you’re having trouble with the button above, copy and paste the URL below into your web browser.
|
||||
</p>
|
||||
<p class="f-fallback sub"
|
||||
style="font-size: 13px;
|
||||
line-height: 1.625;
|
||||
color: #51545E;
|
||||
margin: .4em 0 1.1875em;">
|
||||
{{ link }}
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
{% endmacro %}
|
||||
{% macro grey_section(parts) %}
|
||||
<table class="attributes"
|
||||
width="100%"
|
||||
cellpadding="0"
|
||||
cellspacing="0"
|
||||
role="presentation"
|
||||
style="margin: 0 0 21px;">
|
||||
<tr>
|
||||
<td class="attributes_content"
|
||||
style="word-break: break-word;
|
||||
font-family: Helvetica, Arial, sans-serif;
|
||||
font-size: 16px;
|
||||
background-color: #F4F4F7;
|
||||
padding: 16px;"
|
||||
bgcolor="#F4F4F7">
|
||||
<table width="100%" cellpadding="0" cellspacing="0" role="presentation">
|
||||
{% for part in parts %}
|
||||
|
||||
<tr>
|
||||
<td class="attributes_item"
|
||||
style="word-break: break-word;
|
||||
font-family: Helvetica, Arial, sans-serif;
|
||||
font-size: 16px;
|
||||
padding: 0;">
|
||||
<div class="f-fallback">
|
||||
{{ part }}
|
||||
<br />
|
||||
<br />
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
{% endmacro %}
|
623
app/templates/emails/base.html
Normal file
623
app/templates/emails/base.html
Normal file
@ -0,0 +1,623 @@
|
||||
{% from "_emailhelpers.html" import render_text, text, render_button, raw_url, grey_section, section %}
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="x-apple-disable-message-reformatting" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<style type="text/css" rel="stylesheet" media="all">
|
||||
/* Base ------------------------------ */
|
||||
body {
|
||||
width: 100% !important;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
-webkit-text-size-adjust: none;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #3869D4;
|
||||
}
|
||||
|
||||
a img {
|
||||
border: none;
|
||||
}
|
||||
|
||||
td {
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.preheader {
|
||||
display: none !important;
|
||||
visibility: hidden;
|
||||
mso-hide: all;
|
||||
font-size: 1px;
|
||||
line-height: 1px;
|
||||
max-height: 0;
|
||||
max-width: 0;
|
||||
opacity: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
/* Type ------------------------------ */
|
||||
|
||||
body,
|
||||
td,
|
||||
th {
|
||||
font-family: "Nunito Sans", Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin-top: 0;
|
||||
color: #333333;
|
||||
font-size: 22px;
|
||||
font-weight: bold;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin-top: 0;
|
||||
color: #333333;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin-top: 0;
|
||||
color: #333333;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
td,
|
||||
th {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
p,
|
||||
ul,
|
||||
ol,
|
||||
blockquote {
|
||||
margin: .4em 0 1.1875em;
|
||||
font-size: 16px;
|
||||
line-height: 1.625;
|
||||
}
|
||||
|
||||
p.sub {
|
||||
font-size: 13px;
|
||||
}
|
||||
/* Utilities ------------------------------ */
|
||||
|
||||
.align-right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.align-left {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.align-center {
|
||||
text-align: center;
|
||||
}
|
||||
/* Buttons ------------------------------ */
|
||||
|
||||
.button {
|
||||
background-color: #3869D4;
|
||||
border-top: 10px solid #3869D4;
|
||||
border-right: 18px solid #3869D4;
|
||||
border-bottom: 10px solid #3869D4;
|
||||
border-left: 18px solid #3869D4;
|
||||
display: inline-block;
|
||||
color: #FFF;
|
||||
text-decoration: none;
|
||||
border-radius: 3px;
|
||||
box-shadow: 0 2px 3px rgba(0, 0, 0, 0.16);
|
||||
-webkit-text-size-adjust: none;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.button--green {
|
||||
background-color: #22BC66;
|
||||
border-top: 10px solid #22BC66;
|
||||
border-right: 18px solid #22BC66;
|
||||
border-bottom: 10px solid #22BC66;
|
||||
border-left: 18px solid #22BC66;
|
||||
}
|
||||
|
||||
.button--red {
|
||||
background-color: #FF6136;
|
||||
border-top: 10px solid #FF6136;
|
||||
border-right: 18px solid #FF6136;
|
||||
border-bottom: 10px solid #FF6136;
|
||||
border-left: 18px solid #FF6136;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 500px) {
|
||||
.button {
|
||||
width: 100% !important;
|
||||
text-align: center !important;
|
||||
}
|
||||
}
|
||||
/* Attribute list ------------------------------ */
|
||||
|
||||
.attributes {
|
||||
margin: 0 0 21px;
|
||||
}
|
||||
|
||||
.attributes_content {
|
||||
background-color: #F4F4F7;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.attributes_item {
|
||||
padding: 0;
|
||||
}
|
||||
/* Related Items ------------------------------ */
|
||||
|
||||
.related {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 25px 0 0 0;
|
||||
-premailer-width: 100%;
|
||||
-premailer-cellpadding: 0;
|
||||
-premailer-cellspacing: 0;
|
||||
}
|
||||
|
||||
.related_item {
|
||||
padding: 10px 0;
|
||||
color: #CBCCCF;
|
||||
font-size: 15px;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.related_item-title {
|
||||
display: block;
|
||||
margin: .5em 0 0;
|
||||
}
|
||||
|
||||
.related_item-thumb {
|
||||
display: block;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.related_heading {
|
||||
border-top: 1px solid #CBCCCF;
|
||||
text-align: center;
|
||||
padding: 25px 0 10px;
|
||||
}
|
||||
/* Discount Code ------------------------------ */
|
||||
|
||||
.discount {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 24px;
|
||||
-premailer-width: 100%;
|
||||
-premailer-cellpadding: 0;
|
||||
-premailer-cellspacing: 0;
|
||||
background-color: #F4F4F7;
|
||||
border: 2px dashed #CBCCCF;
|
||||
}
|
||||
|
||||
.discount_heading {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.discount_body {
|
||||
text-align: center;
|
||||
font-size: 15px;
|
||||
}
|
||||
/* Social Icons ------------------------------ */
|
||||
|
||||
.social {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.social td {
|
||||
padding: 0;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.social_icon {
|
||||
height: 20px;
|
||||
margin: 0 8px 10px 8px;
|
||||
padding: 0;
|
||||
}
|
||||
/* Data table ------------------------------ */
|
||||
|
||||
.purchase {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 35px 0;
|
||||
-premailer-width: 100%;
|
||||
-premailer-cellpadding: 0;
|
||||
-premailer-cellspacing: 0;
|
||||
}
|
||||
|
||||
.purchase_content {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 25px 0 0 0;
|
||||
-premailer-width: 100%;
|
||||
-premailer-cellpadding: 0;
|
||||
-premailer-cellspacing: 0;
|
||||
}
|
||||
|
||||
.purchase_item {
|
||||
padding: 10px 0;
|
||||
color: #51545E;
|
||||
font-size: 15px;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.purchase_heading {
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 1px solid #EAEAEC;
|
||||
}
|
||||
|
||||
.purchase_heading p {
|
||||
margin: 0;
|
||||
color: #85878E;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.purchase_footer {
|
||||
padding-top: 15px;
|
||||
border-top: 1px solid #EAEAEC;
|
||||
}
|
||||
|
||||
.purchase_total {
|
||||
margin: 0;
|
||||
text-align: right;
|
||||
font-weight: bold;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.purchase_total--label {
|
||||
padding: 0 15px 0 0;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: #F2F4F6;
|
||||
color: #51545E;
|
||||
}
|
||||
|
||||
p {
|
||||
color: #51545E;
|
||||
}
|
||||
|
||||
.email-wrapper {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
-premailer-width: 100%;
|
||||
-premailer-cellpadding: 0;
|
||||
-premailer-cellspacing: 0;
|
||||
background-color: #F2F4F6;
|
||||
}
|
||||
|
||||
.email-content {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
-premailer-width: 100%;
|
||||
-premailer-cellpadding: 0;
|
||||
-premailer-cellspacing: 0;
|
||||
}
|
||||
/* Masthead ----------------------- */
|
||||
|
||||
.email-masthead {
|
||||
padding: 25px 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.email-masthead_logo {
|
||||
width: 94px;
|
||||
}
|
||||
|
||||
.email-masthead_name {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: #A8AAAF;
|
||||
text-decoration: none;
|
||||
text-shadow: 0 1px 0 white;
|
||||
}
|
||||
/* Body ------------------------------ */
|
||||
|
||||
.email-body {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
-premailer-width: 100%;
|
||||
-premailer-cellpadding: 0;
|
||||
-premailer-cellspacing: 0;
|
||||
}
|
||||
|
||||
.email-body_inner {
|
||||
width: 750px;
|
||||
margin: 0 auto;
|
||||
padding: 0;
|
||||
-premailer-width: 750px;
|
||||
-premailer-cellpadding: 0;
|
||||
-premailer-cellspacing: 0;
|
||||
background-color: #FFFFFF;
|
||||
}
|
||||
|
||||
.email-footer {
|
||||
width: 750px;
|
||||
margin: 0 auto;
|
||||
padding: 0;
|
||||
-premailer-width: 750px;
|
||||
-premailer-cellpadding: 0;
|
||||
-premailer-cellspacing: 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.email-footer p {
|
||||
color: #A8AAAF;
|
||||
}
|
||||
|
||||
.body-action {
|
||||
width: 100%;
|
||||
margin: 30px auto;
|
||||
padding: 0;
|
||||
-premailer-width: 100%;
|
||||
-premailer-cellpadding: 0;
|
||||
-premailer-cellspacing: 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.body-sub {
|
||||
margin-top: 25px;
|
||||
padding-top: 25px;
|
||||
border-top: 1px solid #EAEAEC;
|
||||
}
|
||||
|
||||
.content-cell {
|
||||
padding: 30px;
|
||||
}
|
||||
/*Media Queries ------------------------------ */
|
||||
|
||||
@media only screen and (max-width: 600px) {
|
||||
.email-body_inner,
|
||||
.email-footer {
|
||||
width: 100% !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body,
|
||||
.email-body,
|
||||
.email-body_inner,
|
||||
.email-content,
|
||||
.email-wrapper,
|
||||
.email-masthead,
|
||||
.email-footer {
|
||||
background-color: #333333 !important;
|
||||
color: #FFF !important;
|
||||
}
|
||||
p,
|
||||
ul,
|
||||
ol,
|
||||
blockquote,
|
||||
h1,
|
||||
h2,
|
||||
h3 {
|
||||
color: #FFF !important;
|
||||
}
|
||||
.attributes_content,
|
||||
.discount {
|
||||
background-color: #222 !important;
|
||||
}
|
||||
.email-masthead_name {
|
||||
text-shadow: none !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<!--[if mso]>
|
||||
<style type="text/css">
|
||||
.f-fallback {
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
</style>
|
||||
<![endif]-->
|
||||
<style type="text/css" rel="stylesheet" media="all">
|
||||
body {
|
||||
width: 100% !important;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
-webkit-text-size-adjust: none;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: "Nunito Sans", Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: #F2F4F6;
|
||||
color: #51545E;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body style="width: 100% !important;
|
||||
height: 100%;
|
||||
-webkit-text-size-adjust: none;
|
||||
font-family: Helvetica, Arial, sans-serif;
|
||||
background-color: #F2F4F6;
|
||||
color: #51545E;
|
||||
margin: 0;"
|
||||
bgcolor="#F2F4F6">
|
||||
<span class="preheader"
|
||||
style="display: none !important;
|
||||
visibility: hidden;
|
||||
mso-hide: all;
|
||||
font-size: 1px;
|
||||
line-height: 1px;
|
||||
max-height: 0;
|
||||
max-width: 0;
|
||||
opacity: 0;
|
||||
overflow: hidden;">{{ pre_header }}</span>
|
||||
<table class="email-wrapper"
|
||||
width="100%"
|
||||
cellpadding="0"
|
||||
cellspacing="0"
|
||||
role="presentation"
|
||||
style="width: 100%;
|
||||
-premailer-width: 100%;
|
||||
-premailer-cellpadding: 0;
|
||||
-premailer-cellspacing: 0;
|
||||
background-color: #F2F4F6;
|
||||
margin: 0;
|
||||
padding: 0;"
|
||||
bgcolor="#F2F4F6">
|
||||
<tr>
|
||||
<td align="center"
|
||||
style="word-break: break-word;
|
||||
font-family: Helvetica, Arial, sans-serif;
|
||||
font-size: 16px;">
|
||||
<table class="email-content"
|
||||
width="100%"
|
||||
cellpadding="0"
|
||||
cellspacing="0"
|
||||
role="presentation"
|
||||
style="width: 100%;
|
||||
-premailer-width: 100%;
|
||||
-premailer-cellpadding: 0;
|
||||
-premailer-cellspacing: 0;
|
||||
margin: 0;
|
||||
padding: 0;">
|
||||
<tr>
|
||||
<td class="email-masthead"
|
||||
style="word-break: break-word;
|
||||
font-family: Helvetica, Arial, sans-serif;
|
||||
font-size: 16px;
|
||||
text-align: center;
|
||||
padding: 25px 0;"
|
||||
align="center">
|
||||
<a href="{{ LANDING_PAGE_URL }}"
|
||||
class="f-fallback email-masthead_name"
|
||||
style="color: #A8AAAF;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
text-shadow: 0 1px 0 white;">
|
||||
{% block logo %}<img src="{{ URL }}/static/logo.png" style="width: 150px; margin: auto">{% endblock %}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- Email Body -->
|
||||
<tr>
|
||||
<td class="email-body"
|
||||
width="750"
|
||||
cellpadding="0"
|
||||
cellspacing="0"
|
||||
style="word-break: break-word;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: Helvetica, Arial, sans-serif;
|
||||
font-size: 16px;
|
||||
width: 100%;
|
||||
-premailer-width: 100%;
|
||||
-premailer-cellpadding: 0;
|
||||
-premailer-cellspacing: 0;">
|
||||
<table class="email-body_inner"
|
||||
align="center"
|
||||
width="750"
|
||||
cellpadding="0"
|
||||
cellspacing="0"
|
||||
role="presentation"
|
||||
style="width: 750px;
|
||||
-premailer-width: 750px;
|
||||
-premailer-cellpadding: 0;
|
||||
-premailer-cellspacing: 0;
|
||||
background-color: #FFFFFF;
|
||||
margin: 0 auto;
|
||||
padding: 0;"
|
||||
bgcolor="#FFFFFF">
|
||||
<!-- Body content -->
|
||||
<tr>
|
||||
<td class="content-cell"
|
||||
style="word-break: break-word;
|
||||
font-family: Helvetica, Arial, sans-serif;
|
||||
font-size: 16px;
|
||||
padding: 30px;">
|
||||
<div class="f-fallback">
|
||||
{% block greeting %}{% endblock %}
|
||||
{% block content %}{% endblock %}
|
||||
<!-- Sub copy -->
|
||||
{% block sub_copy %}{% endblock %}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="word-break: break-word;
|
||||
font-family: Helvetica, Arial, sans-serif;
|
||||
font-size: 16px;">
|
||||
<table class="email-footer"
|
||||
align="center"
|
||||
width="750"
|
||||
cellpadding="0"
|
||||
cellspacing="0"
|
||||
role="presentation"
|
||||
style="width: 750px;
|
||||
-premailer-width: 750px;
|
||||
-premailer-cellpadding: 0;
|
||||
-premailer-cellspacing: 0;
|
||||
text-align: center;
|
||||
margin: 0 auto;
|
||||
padding: 0;">
|
||||
<tr>
|
||||
<td class="content-cell"
|
||||
align="center"
|
||||
style="word-break: break-word;
|
||||
font-family: Helvetica, Arial, sans-serif;
|
||||
font-size: 16px;
|
||||
padding: 30px;">
|
||||
<p class="f-fallback sub align-center"
|
||||
style="font-size: 13px;
|
||||
line-height: 1.625;
|
||||
text-align: center;
|
||||
color: #A8AAAF;
|
||||
margin: .4em 0 1.1875em;"
|
||||
align="center">
|
||||
© {{ YEAR }} SimpleLogin - a Proton product. All rights reserved.
|
||||
<br />
|
||||
{% block footer %}{% endblock %}
|
||||
</p>
|
||||
{% if unsubscribe_oneclick is defined %}
|
||||
|
||||
<p class="f-fallback sub align-center"
|
||||
style="font-size: 13px;
|
||||
line-height: 1.625;
|
||||
text-align: center;
|
||||
margin: .4em 0 1.1875em;">
|
||||
<a href="{{ unsubscribe_oneclick }}">Unsubscribe from our newsletter</a>
|
||||
</p>
|
||||
{% endif %}
|
||||
<p class="f-fallback sub align-center"
|
||||
style="font-size: 13px;
|
||||
line-height: 1.625;
|
||||
text-align: center;
|
||||
color: #A8AAAF;
|
||||
margin: .4em 0 1.1875em;"
|
||||
align="center">
|
||||
<a href="https://app.simplelogin.io/dashboard/support">Do you have a question?</a>
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
6
app/templates/emails/base.txt.jinja2
Normal file
6
app/templates/emails/base.txt.jinja2
Normal file
@ -0,0 +1,6 @@
|
||||
{% block content %} {% endblock %}
|
||||
|
||||
Best,
|
||||
SimpleLogin team.
|
||||
|
||||
Do you have a question? Contact us at https://app.simplelogin.io/dashboard/support
|
29
app/templates/emails/com/newsletter/mailbox.html
Normal file
29
app/templates/emails/com/newsletter/mailbox.html
Normal file
@ -0,0 +1,29 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{{ render_text("Hi") }}
|
||||
{{ render_text("Our most requested feature is finally ready: you can now add several <b>real</b> email addresses into SimpleLogin
|
||||
and choose which one to use when creating aliases!") }}
|
||||
{{ render_text("A real email address is called <b>mailbox</b> in SimpleLogin.") }}
|
||||
{{ render_text('This feature is particularly useful if you have several email addresses,
|
||||
maybe for different uses: a Gmail account for social networks & forums, a Prontonmail account for professional emails, etc.') }}
|
||||
<img src="https://simplelogin.io/blog/mailbox-gmail.png"
|
||||
alt="Mailbox Gmail">
|
||||
<img src="https://simplelogin.io/blog/mailbox-protonmail.png"
|
||||
alt="Mailbox Proton Mail">
|
||||
{{ render_text("When creating an alias, you can choose which mailbox that <b>owns</b> this alias, meaning:") }}
|
||||
{{ render_text("1. Emails sent to this alias are *forwarded* to the owning mailbox.") }}
|
||||
{{ render_text("2. The owning mailbox can *send* or reply emails from this alias.") }}
|
||||
{{ render_text("You can also change the owning mailbox for an existing alias.") }}
|
||||
{{ render_text("The mailbox doesn't have to be your personal email: you can also create aliases for your friend by adding his/her email as a mailbox.") }}
|
||||
{{ render_text('Thanks,
|
||||
<br />
|
||||
SimpleLogin Team.') }}
|
||||
{{ render_text('<strong>P.S.</strong> Need immediate help getting started? Just reply to this email, the SimpleLogin support team is always ready to help!.') }}
|
||||
{% endblock %}
|
||||
{% block footer %}
|
||||
|
||||
This email is sent to {{ user.email }}. Unsubscribe on
|
||||
<a href="{{ URL }}/dashboard/setting#notification">Settings</a>
|
||||
{% endblock %}
|
28
app/templates/emails/com/newsletter/mailbox.txt
Normal file
28
app/templates/emails/com/newsletter/mailbox.txt
Normal file
@ -0,0 +1,28 @@
|
||||
This email is sent to {{ user.email }}.
|
||||
Unsubscribe from our emails on {{URL}}/dashboard/setting#notification
|
||||
----------------
|
||||
|
||||
Hi
|
||||
|
||||
Our most requested feature is finally ready: you can now add several *real* email addresses into SimpleLogin
|
||||
and choose which one to use when creating aliases!
|
||||
|
||||
A real email address is called *mailbox* in SimpleLogin.
|
||||
|
||||
This feature is particularly useful if you have several email addresses,
|
||||
maybe for different uses: a Gmail account for social networks & forums, a Prontonmail account for professional emails, etc.
|
||||
|
||||
When creating an alias, you can choose which mailbox that *owns* this alias, meaning:
|
||||
|
||||
- emails sent to this alias are *forwarded* to the owning mailbox.
|
||||
|
||||
- the owning mailbox can *send* or reply emails from this alias.
|
||||
|
||||
You can also change the owning mailbox for an existing alias.
|
||||
|
||||
The mailbox doesn't have to be your personal email: you can also create aliases for your friend by adding his/her email as a mailbox.
|
||||
|
||||
As usual, let us know if you have any question by replying to this email.
|
||||
|
||||
Best regards,
|
||||
SimpleLogin team.
|
134
app/templates/emails/com/newsletter/mobile-darkmode.html
Normal file
134
app/templates/emails/com/newsletter/mobile-darkmode.html
Normal file
@ -0,0 +1,134 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{{ render_text("Hi") }}
|
||||
{% call text() %}
|
||||
Son from SimpleLogin here. I hope you are doing well and are staying at home in this difficult time. By the way I'm
|
||||
writing this newsletter from my couch with my cats proofreading the text :).
|
||||
<br />
|
||||
Please find below some of our latest news.
|
||||
<br />
|
||||
{% endcall %}
|
||||
|
||||
{% call text() %}
|
||||
1) <b>Mobile apps</b>
|
||||
<br />
|
||||
<br />
|
||||
<img src="https://simplelogin.io/blog/devices.png" style="max-width: 100%">
|
||||
<br />
|
||||
<br />
|
||||
Now you can quickly create aliases on-the-go with SimpleLogin Android and iOS app,
|
||||
thanks to our mobile guy Thanh-Nhon!
|
||||
<br />
|
||||
Download the Android app on
|
||||
<a href="https://play.google.com/store/apps/details?id=io.simplelogin.android">Play Store</a>
|
||||
and the iOS app on
|
||||
<a href="https://apps.apple.com/app/id1494359858">App Store</a>
|
||||
.
|
||||
<br />
|
||||
With the release of the mobile apps, SimpleLogin now covers most major platforms:
|
||||
<br />
|
||||
- Desktop with SimpleLogin web app or Chrome, Firefox and Safari extension
|
||||
<br />
|
||||
- Mobile with Android and iOS app
|
||||
<br />
|
||||
The code is of course open-source and available on our
|
||||
<a href="https://github.com/simple-login/">Github</a>
|
||||
{% endcall %}
|
||||
|
||||
{% call text() %}
|
||||
2) <b>Dark mode</b>
|
||||
<br />
|
||||
<br />
|
||||
<img src="https://simplelogin.io/blog/dark-mode.gif" style="width: 100%">
|
||||
<br />
|
||||
<br />
|
||||
You have asked for it and now the dark mode is finally available, kudos to Dung - our full-stack guy.
|
||||
<br />
|
||||
You can finally enjoy using SimpleLogin in the dark.
|
||||
{% endcall %}
|
||||
|
||||
{% call text() %}
|
||||
3) <b>Alias name, new UI, security page, new policy privacy</b>
|
||||
<br />
|
||||
<br />
|
||||
<img src="https://simplelogin.io/blog/new-ui.gif" style="width: 100%">
|
||||
<br />
|
||||
<br />
|
||||
You might have noticed that the web UI is now more compact: the web app has undergone a remake
|
||||
to make it more responsive for usual actions like enabling/disabling an alias, updating alias note, etc.
|
||||
<br />
|
||||
You can set a name for your alias too: this name is used when you send emails or reply from your alias.
|
||||
<br />
|
||||
We have also created a new
|
||||
<a href="https://simplelogin.io/security/">security page</a>
|
||||
that goes into the technical
|
||||
details of SimpleLogin.
|
||||
Our
|
||||
<a href="https://simplelogin.io/privacy/">privacy page</a>
|
||||
is also rewritten from scratch: nothing changes about
|
||||
your data protection
|
||||
but the page is more clear and detailed now.
|
||||
{% endcall %}
|
||||
|
||||
{% call text() %}
|
||||
4) <b>Facebook, Google, Github login deprecation</b>
|
||||
<br />
|
||||
We have decided to deprecate those social login options because of several reasons:
|
||||
<br />
|
||||
- Privacy: every time you sign in using one of these methods, the respective company knows and
|
||||
we have no information on what they do with this data.
|
||||
<br />
|
||||
- Not fully open-standard compatible: these platforms enjoy their monopolies and
|
||||
don't play well with open standards like OAuth2/OpenID: in fact, implementations on mobile of these social login
|
||||
require their SDK that we refuse to add because of privacy concern.
|
||||
<br />
|
||||
- Uniform experiences for all users: to have these social login in our iOS app, we need to support "Sign in with
|
||||
Apple" that isn't broadly available for Android users.
|
||||
Again, another big tech enjoying its monopoly.
|
||||
<br />
|
||||
If you happen to use one of these social login options, please create a password for your account on the
|
||||
<a href="{{ URL }}/dashboard/setting">Setting page</a>
|
||||
<br />
|
||||
You can still sign in using these social login until 2020-05-31. After this date, they will be removed.
|
||||
{% endcall %}
|
||||
|
||||
{% call text() %}
|
||||
5) <b>WebAuthn (Beta)</b>
|
||||
<br />
|
||||
Thanks to Raymond, a user of SimpleLogin, the WebAuthn is now available in Beta.
|
||||
Please reply to this email if you want to try this out.
|
||||
{% endcall %}
|
||||
|
||||
{% call text() %}
|
||||
<hr style="margin: 10px;">
|
||||
On behalf of the team, I want to say thank you to all users who have helped to improve SimpleLogin code
|
||||
and even contribute important features.
|
||||
That means a lot to us as SimpleLogin is after all an open-source project.
|
||||
{% endcall %}
|
||||
|
||||
{% call text() %}
|
||||
That's all for today. If you want to follow all our latest features, you can follow our
|
||||
<a href="https://twitter.com/simplelogin">Twitter</a>
|
||||
or join our
|
||||
<a href="https://www.reddit.com/r/Simplelogin/">Reddit</a>
|
||||
or subscribe to our
|
||||
<a href="https://feed43.com/simplelogin.xml">RSS feed</a>
|
||||
.
|
||||
<br />
|
||||
Now back to coding :).
|
||||
{% endcall %}
|
||||
|
||||
{% call text() %}
|
||||
Best,
|
||||
<br />
|
||||
Son.
|
||||
{% endcall %}
|
||||
|
||||
{% endblock %}
|
||||
{% block footer %}
|
||||
|
||||
This email is sent to {{ user.email }}. Unsubscribe on
|
||||
<a href="{{ URL }}/dashboard/setting#notification">Settings</a>
|
||||
{% endblock %}
|
71
app/templates/emails/com/newsletter/mobile-darkmode.txt
Normal file
71
app/templates/emails/com/newsletter/mobile-darkmode.txt
Normal file
@ -0,0 +1,71 @@
|
||||
This email is sent to {{ user.email }}.
|
||||
Unsubscribe from our emails on {{URL}}/dashboard/setting#notification
|
||||
----------------
|
||||
|
||||
Hi
|
||||
|
||||
Son from SimpleLogin here. I hope you are doing well and are staying at home in this difficult time.
|
||||
By the way I'm writing this newsletter from my couch with my cats proofreading the text :).
|
||||
|
||||
Here are some of our latest news:
|
||||
|
||||
1) Mobile apps
|
||||
|
||||
Now you can quickly create aliases on-the-go with SimpleLogin Android and iOS app, thanks to our mobile guy Thanh-Nhon!
|
||||
Download:
|
||||
- the Android app on Play Store https://play.google.com/store/apps/details?id=io.simplelogin.android
|
||||
- the iOS app on App Store https://apps.apple.com/app/id1494359858
|
||||
|
||||
With the release of the mobile apps, SimpleLogin now covers most major platforms:
|
||||
|
||||
- Desktop with SimpleLogin web app or Chrome, Firefox and Safari extension
|
||||
- Mobile with Android and iOS app
|
||||
|
||||
The apps code is of course open-source and available on our Github http://github.com/simple-login/
|
||||
|
||||
2) Dark mode
|
||||
|
||||
No worries, we are not going to the dark side :).
|
||||
You have asked for it and now the dark mode is finally available, thanks to Dung - our full-stack guy.
|
||||
You can finally enjoy using SimpleLogin in the dark.
|
||||
|
||||
3) Alias name, new UI, security page, new policy privacy
|
||||
|
||||
You might have noticed that the web UI is now more compact: the web app has undergone a remake
|
||||
to make it more responsive for usual actions like enabling/disabling an alias, updating alias note, etc.
|
||||
|
||||
You can set a name for your alias: this name is used when you send emails or reply from your alias.
|
||||
|
||||
We have also created a new security page that goes into the technical details of SimpleLogin.
|
||||
Our privacy page is also rewritten from scratch: nothing changes about your data protection
|
||||
but the page is now much more clear and detailed now.
|
||||
|
||||
4) Facebook, Google, Github login deprecation
|
||||
|
||||
We have decided to deprecate those social login options because of several reasons:
|
||||
|
||||
- Privacy: every time you sign in using one of these methods, the respective company knows and
|
||||
we have no information on what they do with this data.
|
||||
- Not fully open-standard compatible: these platforms enjoy their monopolies and
|
||||
don't play well with open standards like OAuth2/OpenID: in fact, implementations on mobile of these social login
|
||||
require their SDK that we refuse to add because of privacy concern.
|
||||
- Uniform experiences for all users: to have these social login in our iOS app, we need to support "Sign in with Apple"
|
||||
that isn't broadly available for Android users. Again, another big tech enjoying its monopoly.
|
||||
|
||||
If you happen to use one of these social login options, please create a password for your account on the Setting page
|
||||
{{URL}}/dashboard/setting
|
||||
|
||||
You can still sign in using these social login until 2020-05-31. After this date, they will be removed.
|
||||
|
||||
5) WebAuthn (Beta)
|
||||
|
||||
Thanks to one of SimpleLogin users, the WebAuthn is now available in Beta.
|
||||
Please reply to this email if you want to try this out.
|
||||
|
||||
We want to say thank you to all users who have helped to improve SimpleLogin code and even contribute important features.
|
||||
That means a lot to us as SimpleLogin is after all an open-source project.
|
||||
|
||||
We always welcome your feedback. Get in touch on social media, where you can also follow all our latest updates.
|
||||
|
||||
Best regards,
|
||||
Son.
|
33
app/templates/emails/com/newsletter/pgp.html
Normal file
33
app/templates/emails/com/newsletter/pgp.html
Normal file
@ -0,0 +1,33 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{{ render_text("Hi") }}
|
||||
{{ render_text("If you happen to use Gmail, Yahoo, Outlook, etc, do you know these services can read your emails?") }}
|
||||
{{ render_text("If you want to keep your emails only readable by you, Pretty Good Privacy (PGP) is maybe the solution.") }}
|
||||
{{ render_text('Highly recommended, open source and free, PGP is unfortunately not widely supported. However with SimpleLogin most recent PGP support, you can now enable PGP on emails sent to your aliases easily.') }}
|
||||
{{ render_text('Without PGP the emails sent to an alias are forwarded by SimpleLogin as-is to your mailbox, leaving anyone in-between or your email service able to read your emails:') }}
|
||||
<img src="https://simplelogin.io/blog/without-pgp.png"
|
||||
alt="Without PGP"
|
||||
style="max-width: 100%">
|
||||
{{ render_text("With PGP enabled, all emails arrived at SimpleLogin are encrypted with your public key before being forwarded to your mailbox:") }}
|
||||
<img src="https://simplelogin.io/blog/with-pgp.png"
|
||||
alt="Without PGP"
|
||||
style="max-width: 100%">
|
||||
{{ render_text("You can find more info on our announcement post on https://simplelogin.io/blog/introducing-pgp/") }}
|
||||
{{ render_text("You can create and manage your PGP keys when adding or editing your mailboxes. Check it out on your mailbox dashboard.") }}
|
||||
{{ render_button("Add your PGP key", URL ~ "/dashboard/mailbox") }}
|
||||
{{ render_text("Our next important feature is the coming of an iOS app. If you use iPhone or iPad want to help us testing out the app, please reply to this email so we can add you into the TestFlight program.
|
||||
") }}
|
||||
{{ render_text("For Android users, don't worry: the Android version is already in progress.
|
||||
") }}
|
||||
{{ render_text('Thanks,
|
||||
<br />
|
||||
SimpleLogin Team.') }}
|
||||
{{ render_text('<strong>P.S.</strong> Need immediate help getting started? Just reply to this email, the SimpleLogin support team is always ready to help!.') }}
|
||||
{% endblock %}
|
||||
{% block footer %}
|
||||
|
||||
This email is sent to {{ user.email }}. Unsubscribe on
|
||||
<a href="{{ URL }}/dashboard/setting#notification">Settings</a>
|
||||
{% endblock %}
|
32
app/templates/emails/com/newsletter/pgp.txt
Normal file
32
app/templates/emails/com/newsletter/pgp.txt
Normal file
@ -0,0 +1,32 @@
|
||||
This email is sent to {{ user.email }}.
|
||||
Unsubscribe from our emails on {{URL}}/dashboard/setting#notification
|
||||
----------------
|
||||
|
||||
Hi
|
||||
|
||||
If you happen to use Gmail, Yahoo, Outlook, etc, do you know these services can read your emails?
|
||||
|
||||
If you want to keep your emails only readable by you, Pretty Good Privacy (PGP) is maybe the solution.
|
||||
|
||||
Highly recommended, open source and free, PGP is unfortunately not widely supported. However with SimpleLogin most recent PGP support, you can now enable PGP on emails sent to your aliases easily.
|
||||
|
||||
Without PGP the emails sent to an alias are forwarded by SimpleLogin as-is to your mailbox, leaving anyone in-between or your email service able to read your emails:
|
||||
|
||||
https://simplelogin.io/blog/without-pgp.png
|
||||
|
||||
With PGP enabled, all emails arrived at SimpleLogin are encrypted with your public key before being forwarded to your mailbox:
|
||||
|
||||
https://simplelogin.io/blog/with-pgp.png
|
||||
|
||||
You can find more info on our announcement post on https://simplelogin.io/blog/introducing-pgp/
|
||||
|
||||
You can create and manage your PGP keys when adding or editing your mailboxes. Check it out on your mailbox dashboard at {{URL}}/dashboard/mailbox
|
||||
|
||||
Our next important feature is the coming of an iOS app. If you use iPhone or iPad want to help us testing out the app, please reply to this email so we can add you into the TestFlight program.
|
||||
|
||||
For Android users, don't worry: the Android version is already in progress.
|
||||
|
||||
As usual, let us know if you have any question by replying to this email.
|
||||
|
||||
Best regards,
|
||||
SimpleLogin team.
|
43
app/templates/emails/com/newsletter/safari-extension.html
Normal file
43
app/templates/emails/com/newsletter/safari-extension.html
Normal file
@ -0,0 +1,43 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<tr>
|
||||
<td align="left"
|
||||
valign="top"
|
||||
style="border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
padding-left: 6.25%;
|
||||
padding-right: 6.25%;
|
||||
width: 87.5%;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
line-height: 160%;
|
||||
padding-top: 25px;
|
||||
color: #000000;
|
||||
font-family: sans-serif;"
|
||||
class="paragraph">
|
||||
This email is sent to {{ user.email }}.
|
||||
Unsubscribe on
|
||||
<a href="{{ URL }}/dashboard/setting#notification">Settings</a>
|
||||
<hr />
|
||||
</td>
|
||||
</tr>
|
||||
{{ render_text("Hi") }}
|
||||
{{ render_text("If you use Safari on a MacBook or iMac, you should check out our new Safari extension.") }}
|
||||
{{ render_text('It can be installed on
|
||||
<a href="https://apps.apple.com/app/id1494051017">App Store</a>
|
||||
. Its code is available on
|
||||
<a href="https://github.com/simple-login/mac-app">GitHub</a>
|
||||
.') }}
|
||||
{{ render_text('
|
||||
<img src="https://static.simplelogin.io/safari-extension.png"
|
||||
style="max-width: 600px">
|
||||
') }}
|
||||
{{ render_text('See our annoucement post for more information on this feature
|
||||
<a href="https://simplelogin.io/blog/safari-extension/">Introducing Safari extension</a>
|
||||
.') }}
|
||||
{{ render_text("As usual, let me know if you have any question by replying to this email.") }}
|
||||
{% endblock %}
|
16
app/templates/emails/com/newsletter/safari-extension.txt
Normal file
16
app/templates/emails/com/newsletter/safari-extension.txt
Normal file
@ -0,0 +1,16 @@
|
||||
This email is sent to {{ user.email }}.
|
||||
Unsubscribe from our emails on {{URL}}/dashboard/setting#notification
|
||||
----------------
|
||||
|
||||
Hi
|
||||
|
||||
If you use Safari on a MacBook or iMac, you should check out our new Safari extension.
|
||||
|
||||
It can be installed on:
|
||||
|
||||
https://apps.apple.com/app/id1494051017
|
||||
|
||||
As usual, let me know if you have any question by replying to this email.
|
||||
|
||||
Best regards,
|
||||
SimpleLogin Team.
|
35
app/templates/emails/com/onboarding/browser-extension.html
Normal file
35
app/templates/emails/com/onboarding/browser-extension.html
Normal file
@ -0,0 +1,35 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% call text() %}
|
||||
<h1>Download SimpleLogin browser extensions and mobile apps to create aliases on-the-fly.</h1>
|
||||
{% endcall %}
|
||||
|
||||
{% call text() %}
|
||||
If you want to quickly create aliases <b>without</b> going to SimpleLogin website, you can do that with SimpleLogin
|
||||
<a href="https://chrome.google.com/webstore/detail/dphilobhebphkdjbpfohgikllaljmgbn">Chrome</a>
|
||||
(or other Chromium-based browsers like Brave or Vivaldi),
|
||||
<a href="https://addons.mozilla.org/firefox/addon/simplelogin/">Firefox</a>
|
||||
and
|
||||
<a href="https://apps.apple.com/app/id1494051017 ">Safari</a>
|
||||
extension.
|
||||
{% endcall %}
|
||||
|
||||
{% call text() %}
|
||||
You can also manage your aliases using SimpleLogin
|
||||
<a href="https://play.google.com/store/apps/details?id=io.simplelogin.android">Android App</a>
|
||||
or
|
||||
<a href="https://apps.apple.com/app/id1494359858">iOS app</a>
|
||||
.
|
||||
{% endcall %}
|
||||
|
||||
<img src="https://simplelogin.io/images/everywhere.png"
|
||||
alt="Available Everywhere"
|
||||
style="max-width: 100%;">
|
||||
{% endblock %}
|
||||
{% block footer %}
|
||||
|
||||
This email is sent to {{ to_email }} and is part of our onboarding series. Unsubscribe on
|
||||
<a href="{{ URL }}/dashboard/setting#notification">Settings</a>
|
||||
{% endblock %}
|
23
app/templates/emails/com/onboarding/browser-extension.txt
Normal file
23
app/templates/emails/com/onboarding/browser-extension.txt
Normal file
@ -0,0 +1,23 @@
|
||||
This email is sent to {{ to_email }} and is part of our onboarding series.
|
||||
Unsubscribe from our emails on {{URL}}/dashboard/setting#notification
|
||||
----------------
|
||||
|
||||
Hi
|
||||
|
||||
If you want to quickly create aliases without going to SimpleLogin website, you can do that with
|
||||
SimpleLogin Chrome (or other Chromium-based browsers like Brave or Vivaldi), Firefox and Safari extension.
|
||||
|
||||
Chrome: https://chrome.google.com/webstore/detail/dphilobhebphkdjbpfohgikllaljmgbn
|
||||
|
||||
Firefox: https://addons.mozilla.org/firefox/addon/simplelogin/
|
||||
|
||||
Safari: https://apps.apple.com/app/id1494051017
|
||||
|
||||
You can also manage your aliases using SimpleLogin mobile apps, available at
|
||||
- Play Store https://play.google.com/store/apps/details?id=io.simplelogin.android
|
||||
- App Store https://apps.apple.com/app/id1494359858
|
||||
|
||||
As usual, let us know if you have any question by replying to this email.
|
||||
|
||||
Best regards,
|
||||
SimpleLogin team.
|
37
app/templates/emails/com/onboarding/mailbox.html
Normal file
37
app/templates/emails/com/onboarding/mailbox.html
Normal file
@ -0,0 +1,37 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% call text() %}
|
||||
<h1>Add other mailboxes to SimpleLogin.</h1>
|
||||
{% endcall %}
|
||||
|
||||
{% call text() %}
|
||||
If you have several email inboxes, say Gmail and Proton Mail,
|
||||
you can add them into SimpleLogin as <b>mailboxes</b>.
|
||||
{% endcall %}
|
||||
|
||||
<img src="https://simplelogin.io/images/multiple-mailboxes.png"
|
||||
alt="Multiple Mailboxes"
|
||||
style="max-width: 100%; margin: auto; border: 1px solid">
|
||||
{% call text() %}
|
||||
When creating an alias, you can choose the mailbox(es) that
|
||||
<b>owns</b> this alias, meaning:
|
||||
<br />
|
||||
1. Emails sent to this alias are forwarded to the owning mailbox(es).
|
||||
<br />
|
||||
2. The owning mailbox(es) can send emails from this alias.
|
||||
{% endcall %}
|
||||
|
||||
{% call text() %}
|
||||
Please note that adding additional mailboxes is only available in the Premium plan.
|
||||
{% endcall %}
|
||||
|
||||
{{ render_button("Create mailbox", URL ~ "/dashboard/mailbox") }}
|
||||
{{ raw_url(URL ~ "/dashboard/mailbox") }}
|
||||
{% endblock %}
|
||||
{% block footer %}
|
||||
|
||||
This email is sent to {{ to_email }} and is part of our onboarding series. Unsubscribe on
|
||||
<a href="{{ URL }}/dashboard/setting#notification">Settings</a>
|
||||
{% endblock %}
|
26
app/templates/emails/com/onboarding/mailbox.txt
Normal file
26
app/templates/emails/com/onboarding/mailbox.txt
Normal file
@ -0,0 +1,26 @@
|
||||
This email is sent to {{ to_email }} and is part of our onboarding series.
|
||||
Unsubscribe from our emails on {{URL}}/dashboard/setting#notification
|
||||
----------------
|
||||
|
||||
Hi
|
||||
|
||||
If you have several email addresses, e.g. Gmail for work and Proton Mail for personal stuffs, you can add them into SimpleLogin and create aliases for them.
|
||||
|
||||
A (real) email address is called *mailbox* in SimpleLogin.
|
||||
|
||||
When creating an alias, you can choose which mailbox that *owns* this alias, meaning:
|
||||
|
||||
- emails sent to this alias are *forwarded* to the owning mailbox.
|
||||
|
||||
- the owning mailbox can *send* or reply emails from this alias.
|
||||
|
||||
You can also change the owning mailbox for an existing alias.
|
||||
|
||||
The mailbox doesn't have to be your personal email: you can also create aliases for your friend by adding his/her email as a mailbox.
|
||||
|
||||
Start create you mailbox on {{URL}}/dashboard/mailbox
|
||||
|
||||
As usual, let us know if you have any question by replying to this email.
|
||||
|
||||
Best regards,
|
||||
SimpleLogin team.
|
37
app/templates/emails/com/onboarding/pgp.html
Normal file
37
app/templates/emails/com/onboarding/pgp.html
Normal file
@ -0,0 +1,37 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% call text() %}
|
||||
<h1>Secure your emails with PGP.</h1>
|
||||
{% endcall %}
|
||||
|
||||
{% call text() %}
|
||||
If you use Gmail, Yahoo, Outlook, etc, you might want to use
|
||||
<a href="https://en.wikipedia.org/wiki/Pretty_Good_Privacy">PGP</a>
|
||||
(Pretty Good Privacy)
|
||||
to make sure your emails can't be read by these email providers.
|
||||
{% endcall %}
|
||||
|
||||
{% call text() %}
|
||||
Without PGP, emails are stored <b>in plaintext</b> leaving your email service able to read your emails.
|
||||
{% endcall %}
|
||||
|
||||
<img src="https://simplelogin.io/blog/without-pgp.png"
|
||||
alt="Without PGP"
|
||||
style="max-width: 100%; margin-bottom: 10px">
|
||||
{% call text() %}
|
||||
With PGP enabled, SimpleLogin <b>encrypts</b> your emails with your public key before forwarding to your mailbox.
|
||||
{% endcall %}
|
||||
|
||||
<img src="https://simplelogin.io/blog/with-pgp.png"
|
||||
alt="Without PGP"
|
||||
style="max-width: 100%; margin-bottom: 20px">
|
||||
{{ render_button("Enable PGP on your mailbox", URL ~ "/dashboard/mailbox/" ~ user.default_mailbox_id) }}
|
||||
{{ raw_url(URL ~ "/dashboard/mailbox/" ~ user.default_mailbox_id) }}
|
||||
{% endblock %}
|
||||
{% block footer %}
|
||||
|
||||
This email is sent to {{ to_email }} and is part of our onboarding series. Unsubscribe on
|
||||
<a href="{{ URL }}/dashboard/setting#notification">Settings</a>
|
||||
{% endblock %}
|
28
app/templates/emails/com/onboarding/pgp.txt
Normal file
28
app/templates/emails/com/onboarding/pgp.txt
Normal file
@ -0,0 +1,28 @@
|
||||
This email is sent to {{ to_email }} and is part of our onboarding series.
|
||||
Unsubscribe from our emails on {{URL}}/dashboard/setting#notification
|
||||
----------------
|
||||
|
||||
Hi
|
||||
|
||||
If you happen to use Gmail, Yahoo, Outlook, etc, do you know these services can read your emails?
|
||||
|
||||
If you want to keep your emails only readable by you, Pretty Good Privacy (PGP) is maybe the solution.
|
||||
|
||||
Highly recommended, open source and free, PGP is unfortunately not widely supported. However with SimpleLogin most recent PGP support, you can now enable PGP on emails sent to your aliases easily.
|
||||
|
||||
Without PGP the emails sent to an alias are forwarded by SimpleLogin as-is to your mailbox, leaving anyone in-between or your email service able to read your emails:
|
||||
|
||||
https://simplelogin.io/blog/without-pgp.png
|
||||
|
||||
With PGP enabled, all emails arrived at SimpleLogin are encrypted with your public key before being forwarded to your mailbox:
|
||||
|
||||
https://simplelogin.io/blog/with-pgp.png
|
||||
|
||||
You can find more info on our announcement post on https://simplelogin.io/blog/introducing-pgp/
|
||||
|
||||
You can create and manage your PGP keys when adding or editing your mailboxes. Check it out on your mailbox dashboard at {{URL}}/dashboard/mailbox
|
||||
|
||||
As usual, let us know if you have any question by replying to this email.
|
||||
|
||||
Best regards,
|
||||
SimpleLogin team.
|
52
app/templates/emails/com/onboarding/send-from-alias.html
Normal file
52
app/templates/emails/com/onboarding/send-from-alias.html
Normal file
@ -0,0 +1,52 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% call text() %}
|
||||
<h1>Send emails from your alias.</h1>
|
||||
{% endcall %}
|
||||
|
||||
{% call text() %}
|
||||
If you want to reply to an email, just hit "Reply"
|
||||
and the response will come from your alias. Your personal email address stays hidden.
|
||||
{% endcall %}
|
||||
|
||||
{% call text() %}
|
||||
To send an email to a <b>new contact</b>, please follow the steps below.
|
||||
You can also watch this
|
||||
<a href="https://youtu.be/GN060XMt6Pc">Youtube video</a>
|
||||
that quickly walks you through the steps.
|
||||
{% endcall %}
|
||||
|
||||
{% call text() %}
|
||||
1. Click the <b>Contacts</b> button on the alias you want to send emails from
|
||||
<br />
|
||||
<img src="https://simplelogin.io/docs/getting-started/send-email/contacts.png"
|
||||
style="max-width: 500px">
|
||||
{% endcall %}
|
||||
|
||||
{% call text() %}
|
||||
2. Enter your contact email, this will create a <b>reverse-alias</b> for the contact.
|
||||
<br />
|
||||
<img src="https://simplelogin.io/docs/getting-started/send-email/new-contact.png"
|
||||
style="max-width: 500px">
|
||||
{% endcall %}
|
||||
|
||||
{% call text() %}
|
||||
3. Send the email to this reverse-alias <b>instead of the contact email</b>.
|
||||
<br />
|
||||
<img src="https://simplelogin.io/docs/getting-started/send-email/reverse-alias.png"
|
||||
style="max-width: 500px">
|
||||
{% endcall %}
|
||||
|
||||
{% call text() %}
|
||||
And voilà, your contact will receive this email sent from your alias!
|
||||
Your real mailbox address will stay hidden.
|
||||
{% endcall %}
|
||||
|
||||
{% endblock %}
|
||||
{% block footer %}
|
||||
|
||||
This email is sent to {{ to_email }} and is part of our onboarding series. Unsubscribe on
|
||||
<a href="{{ URL }}/dashboard/setting#notification">Settings</a>
|
||||
{% endblock %}
|
21
app/templates/emails/com/onboarding/send-from-alias.txt.j2
Normal file
21
app/templates/emails/com/onboarding/send-from-alias.txt.j2
Normal file
@ -0,0 +1,21 @@
|
||||
This email is sent to {{ to_email }} and is part of our onboarding series.
|
||||
Unsubscribe from our emails on {{URL}}/dashboard/setting#notification
|
||||
----------------
|
||||
|
||||
Hi
|
||||
|
||||
Do you know you can send an email to anyone from your alias?
|
||||
This below Youtube video walks you quickly through the steps:
|
||||
|
||||
https://youtu.be/GN060XMt6Pc
|
||||
|
||||
Here are the steps:
|
||||
1. First click "Contacts" on your alias you want to send email from
|
||||
2. Enter your contact email, create a "reverse-alias"
|
||||
3. Use this reverse-alias instead of your contact email when composing your email
|
||||
|
||||
And voilà, your contact will receive this email sent from your alias!
|
||||
Your real mailbox address will stay hidden.
|
||||
|
||||
Best regards,
|
||||
SimpleLogin Team.
|
62
app/templates/emails/com/onboarding/welcome-proton-user.html
Normal file
62
app/templates/emails/com/onboarding/welcome-proton-user.html
Normal file
@ -0,0 +1,62 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block logo %}<img src="{{ URL }}/static/logo.svg" style="width: 150px; margin: auto">{% endblock %}
|
||||
{% block content %}
|
||||
|
||||
{% call text() %}
|
||||
Welcome to SimpleLogin, a service developed by Proton to protect your email address!
|
||||
{% endcall %}
|
||||
|
||||
{% call text() %}
|
||||
This is the first email you receive via your <b>first alias</b> {{ to_address }}
|
||||
{% endcall %}
|
||||
|
||||
{% call text() %}
|
||||
This alias is automatically created when you use SimpleLogin for the first time.
|
||||
Emails sent to it are forwarded to your Proton mailbox.
|
||||
If you want to reply to an email, just hit "Reply" and the response will come from your alias.
|
||||
Your personal email address stays hidden.
|
||||
{% endcall %}
|
||||
|
||||
{% call text() %}
|
||||
To create new aliases, use the SimpleLogin browser extension (recommended) or web dashboard.
|
||||
SimpleLogin is available on
|
||||
<a href="https://chrome.google.com/webstore/detail/dphilobhebphkdjbpfohgikllaljmgbn">Chrome</a>
|
||||
,
|
||||
<a href="https://addons.mozilla.org/firefox/addon/simplelogin/">Firefox</a>
|
||||
and
|
||||
<a href="https://microsoftedge.microsoft.com/addons/detail/simpleloginreceive-sen/diacfpipniklenphgljfkmhinphjlfff">
|
||||
Edge
|
||||
</a>
|
||||
{% endcall %}
|
||||
|
||||
{% call text() %}
|
||||
SimpleLogin is also available on
|
||||
<a href="https://play.google.com/store/apps/details?id=io.simplelogin.android">Android</a>
|
||||
and
|
||||
<a href="https://apps.apple.com/app/id1494359858">iOS</a>
|
||||
so you can manage your aliases on the go.
|
||||
{% endcall %}
|
||||
|
||||
{% call text() %}
|
||||
Note, if you are a paying Proton Mail user, you automatically receive the premium version of SimpleLogin.
|
||||
{% endcall %}
|
||||
|
||||
{% call text() %}
|
||||
For any question, feedback or feature request, please join our
|
||||
<a href="https://github.com/simple-login/app/discussions">GitHub forum</a>
|
||||
.
|
||||
You can also join our
|
||||
<a href="https://www.reddit.com/r/Simplelogin/">Reddit</a>
|
||||
or follow our
|
||||
<a href="https://twitter.com/simple_login">Twitter</a>
|
||||
.
|
||||
{% endcall %}
|
||||
|
||||
{% call text() %}
|
||||
Best,
|
||||
<br />
|
||||
SimpleLogin Team.
|
||||
{% endcall %}
|
||||
|
||||
{% endblock %}
|
@ -0,0 +1,33 @@
|
||||
Welcome to SimpleLogin, a service developed by Proton to protect your email address!
|
||||
|
||||
This is the first email you receive via your first alias {{ to_address }}
|
||||
|
||||
This alias is automatically created when you use SimpleLogin for the first time.
|
||||
Emails sent to it are forwarded to your Proton mailbox.
|
||||
If you want to reply to an email, just hit "Reply" and the response will come from your alias.
|
||||
Your personal email address stays hidden.
|
||||
|
||||
To create new aliases, use the SimpleLogin browser extension (recommended) or web dashboard.
|
||||
SimpleLogin is available on Chrome, Firefox, and Edge.
|
||||
SimpleLogin is also available on Android and iOS so you can manage your aliases on the go.
|
||||
|
||||
Note, if you are a paying Proton Mail user, you automatically receive the premium version of SimpleLogin.
|
||||
|
||||
For any question, feedback or feature request, please join our GitHub forum.
|
||||
You can also join our Reddit or follow our Twitter.
|
||||
|
||||
Best,
|
||||
SimpleLogin Team.
|
||||
|
||||
---
|
||||
Links:
|
||||
Chrome: https://chrome.google.com/webstore/detail/dphilobhebphkdjbpfohgikllaljmgbn
|
||||
Firefox: https://addons.mozilla.org/firefox/addon/simplelogin/
|
||||
Edge: https://microsoftedge.microsoft.com/addons/detail/simpleloginreceive-sen/diacfpipniklenphgljfkmhinphjlfff
|
||||
Android: https://play.google.com/store/apps/details?id=io.simplelogin.android
|
||||
iOS: https://apps.apple.com/app/id1494359858
|
||||
Github forum: https://github.com/simple-login/app/discussions
|
||||
Reddit: https://www.reddit.com/r/Simplelogin/
|
||||
Twitter: https://twitter.com/simple_login
|
||||
|
||||
|
84
app/templates/emails/com/welcome.html
Normal file
84
app/templates/emails/com/welcome.html
Normal file
@ -0,0 +1,84 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block greeting %}
|
||||
|
||||
<h1 style="margin-top: 0;
|
||||
color: #333333;
|
||||
font-size: 22px;
|
||||
font-weight: bold;
|
||||
text-align: left;"
|
||||
align="left">
|
||||
Welcome!
|
||||
</h1>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
|
||||
{% if alias %}
|
||||
|
||||
{% call text() %}
|
||||
This is the first email you receive via your <b>first alias</b> <em>{{ alias }}</em>.
|
||||
{% endcall %}
|
||||
|
||||
{% call text() %}
|
||||
This alias is automatically created for receiving SimpleLogin news and tips.
|
||||
<br />
|
||||
In the next coming days, we'll send you 3 emails to help you get the best out of SimpleLogin.
|
||||
<br />
|
||||
Please
|
||||
<a href="{{ URL + '/dashboard/setting#notification' }}">disable</a>
|
||||
it if you don't need this.
|
||||
{% endcall %}
|
||||
|
||||
{% endif %}
|
||||
{% call text() %}
|
||||
If you are using Firefox or a Chromium-browser like Chrome, Edge, Brave, you can
|
||||
install our
|
||||
<a href="https://addons.mozilla.org/firefox/addon/simplelogin/">Firefox add-on</a>
|
||||
or
|
||||
<a href="https://chrome.google.com/webstore/detail/dphilobhebphkdjbpfohgikllaljmgbn">Chrome extension</a>
|
||||
to create aliases in one click (like in the below gif 👇).
|
||||
{% endcall %}
|
||||
|
||||
{% call text() %}
|
||||
<img src="https://simplelogin.io/images/one-click-alias.gif"
|
||||
style="max-width: 80%; margin: auto; border: 1px solid">
|
||||
{% endcall %}
|
||||
|
||||
{% call text() %}
|
||||
SimpleLogin is also available on
|
||||
<a href="https://play.google.com/store/apps/details?id=io.simplelogin.android">Android</a>
|
||||
and
|
||||
<a href="https://apps.apple.com/app/id1494359858">iOS</a>
|
||||
so you can manage your aliases on the go.
|
||||
{% endcall %}
|
||||
|
||||
{% if user.in_trial() and user.trial_end %}
|
||||
|
||||
{% call text() %}
|
||||
When you signed up, you can use all premium features like
|
||||
<em>custom domain</em>, <em>alias directory</em>,
|
||||
<em>mailbox</em>,
|
||||
<em>PGP</em> without any limit during 7 days (the "trial period").
|
||||
Everything you create during this period will
|
||||
continue to work normally even if you don't upgrade.
|
||||
<br />
|
||||
{% endcall %}
|
||||
|
||||
{% call text() %}
|
||||
Please note that you can't create more than {{ MAX_NB_EMAIL_FREE_PLAN }} aliases during the trial period.
|
||||
<br />
|
||||
{% endcall %}
|
||||
|
||||
{% endif %}
|
||||
{% call text() %}
|
||||
For any question, feedback or feature request, please join our
|
||||
<a href="https://github.com/simple-login/app/discussions">GitHub forum</a>
|
||||
.
|
||||
You can also join our
|
||||
<a href="https://www.reddit.com/r/Simplelogin/">Reddit</a>
|
||||
or follow our
|
||||
<a href="https://twitter.com/simplelogin">Twitter</a>
|
||||
.
|
||||
{% endcall %}
|
||||
|
||||
{% endblock %}
|
31
app/templates/emails/com/welcome.txt
Normal file
31
app/templates/emails/com/welcome.txt
Normal file
@ -0,0 +1,31 @@
|
||||
Hi!
|
||||
|
||||
{% if alias %}
|
||||
This is the first email you receive via your first alias {{ alias }}.
|
||||
This alias is automatically created for receiving SimpleLogin news and tips -
|
||||
feel free to disable it on {{URL}}/dashboard/setting#notification
|
||||
if you don't need any of these.
|
||||
{% endif %}
|
||||
|
||||
To better secure your account, we recommend enabling Multi-Factor Authentication (MFA) or WebAuthn (Yubikey)
|
||||
on your setting page at {{URL}}/dashboard/setting
|
||||
|
||||
|
||||
If you are using Firefox or a Chromium-browser like Chrome, Edge, Brave, you can
|
||||
install our Firefox add-on or Chrome extension
|
||||
to create aliases in one click (literally).
|
||||
|
||||
Firefox: https://addons.mozilla.org/firefox/addon/simplelogin/
|
||||
Chrome: https://chrome.google.com/webstore/detail/dphilobhebphkdjbpfohgikllaljmgbn
|
||||
|
||||
{% if user.in_trial() and user.trial_end %}
|
||||
When you signed up, you can use all premium features like custom domain, alias directory, mailbox, PGP
|
||||
without any limit during 7 days (the "trial period").
|
||||
No worries: all aliases you create during this period will continue to work normally even if you don't upgrade.
|
||||
{% endif %}
|
||||
|
||||
At any time, you can reach out to us by simply replying to this email.
|
||||
|
||||
For any question, feedback or feature request, please join our GitHub forum at https://github.com/simple-login/app/discussions
|
||||
|
||||
You can also join our Reddit at https://www.reddit.com/r/Simplelogin/ follow our Twitter at https://twitter.com/simplelogin
|
16
app/templates/emails/transactional/account-delete.html
Normal file
16
app/templates/emails/transactional/account-delete.html
Normal file
@ -0,0 +1,16 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% call text() %}
|
||||
<h1>Your SimpleLogin account has been deleted successfully.</h1>
|
||||
{% endcall %}
|
||||
|
||||
{% call text() %}
|
||||
Thank you for having used SimpleLogin.
|
||||
{% endcall %}
|
||||
|
||||
{{ render_text('Best,
|
||||
<br />
|
||||
SimpleLogin Team.') }}
|
||||
{% endblock %}
|
7
app/templates/emails/transactional/account-delete.txt
Normal file
7
app/templates/emails/transactional/account-delete.txt
Normal file
@ -0,0 +1,7 @@
|
||||
{% extends "base.txt.jinja2" %}
|
||||
|
||||
{% block content %}
|
||||
Your SimpleLogin account has been deleted successfully.
|
||||
|
||||
Thank you for having used SimpleLogin.
|
||||
{% endblock %}
|
12
app/templates/emails/transactional/activation.html
Normal file
12
app/templates/emails/transactional/activation.html
Normal file
@ -0,0 +1,12 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{{ render_text("Thank you for choosing SimpleLogin.") }}
|
||||
{{ render_text("To get started, please confirm that <b>" + email + "</b> is your email address by clicking on the button below within 1 hour.") }}
|
||||
{{ render_button("Verify email", activation_link) }}
|
||||
{{ render_text('Thanks,
|
||||
<br />
|
||||
SimpleLogin Team.') }}
|
||||
{{ raw_url(activation_link) }}
|
||||
{% endblock %}
|
7
app/templates/emails/transactional/activation.txt
Normal file
7
app/templates/emails/transactional/activation.txt
Normal file
@ -0,0 +1,7 @@
|
||||
{% extends "base.txt.jinja2" %}
|
||||
|
||||
{% block content %}
|
||||
Thank you for choosing SimpleLogin.
|
||||
|
||||
To get started, please confirm that {{email}} is your email address using this link {{activation_link}} within 1 hour.
|
||||
{% endblock %}
|
19
app/templates/emails/transactional/alias-transferred.html
Normal file
19
app/templates/emails/transactional/alias-transferred.html
Normal file
@ -0,0 +1,19 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% call text() %}
|
||||
<h1>{{ alias.email }} has been transferred.</h1>
|
||||
{% endcall %}
|
||||
|
||||
{% call text() %}
|
||||
Your (previously) alias {{ alias.email }} has been received by another user.
|
||||
{% endcall %}
|
||||
|
||||
{% call text() %}
|
||||
Best,
|
||||
<br />
|
||||
SimpleLogin Team.
|
||||
{% endcall %}
|
||||
|
||||
{% endblock %}
|
7
app/templates/emails/transactional/alias-transferred.txt
Normal file
7
app/templates/emails/transactional/alias-transferred.txt
Normal file
@ -0,0 +1,7 @@
|
||||
{% extends "base.txt.jinja2" %}
|
||||
|
||||
{% block content %}
|
||||
{{ alias.email }} has been transferred.
|
||||
|
||||
Your (previously) alias {{ alias.email }} has been received by another user.
|
||||
{% endblock %}
|
@ -0,0 +1,24 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% call text() %}
|
||||
<h1>{{ alias.email }} is disabled</h1>
|
||||
{% endcall %}
|
||||
|
||||
{% call text() %}
|
||||
There are several emails sent to your alias {{ alias.email }} that have been bounced
|
||||
by your mailbox {{ mailbox_email }}.
|
||||
{% endcall %}
|
||||
|
||||
{{ render_button("View the refused email", refused_email_url) }}
|
||||
{% call text() %}
|
||||
As security measure, we have disabled the alias.
|
||||
{% endcall %}
|
||||
|
||||
{{ render_text('Please let us know if you have any question.') }}
|
||||
{{ render_text('Thanks,
|
||||
<br />
|
||||
SimpleLogin Team.') }}
|
||||
{{ raw_url(refused_email_url) }}
|
||||
{% endblock %}
|
@ -0,0 +1,13 @@
|
||||
{{alias.email}} is disabled.
|
||||
|
||||
There are several emails sent to your alias {{alias.email}} that have been bounced by your mailbox {{mailbox_email}}.
|
||||
|
||||
You can view this email here:
|
||||
{{ refused_email_url }}
|
||||
|
||||
As security measure, we have disabled the alias {{alias.email}}.
|
||||
|
||||
Please let us know if you have any question.
|
||||
|
||||
Best,
|
||||
SimpleLogin team.
|
@ -0,0 +1,29 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% call text() %}
|
||||
<h1>Email cannot be sent to {{ contact.email }} from your alias {{ alias.email }}</h1>
|
||||
{% endcall %}
|
||||
|
||||
{% call text() %}
|
||||
This might mean {{ contact.email }}
|
||||
<ul>
|
||||
<li>is not a valid email address, or</li>
|
||||
<li>doesn't exist, or</li>
|
||||
<li>its mail server refuses your email</li>
|
||||
</ul>
|
||||
{% endcall %}
|
||||
|
||||
{{ render_button("View the original email", refused_email_url) }}
|
||||
{% call text() %}
|
||||
This email is automatically deleted in 7 days.
|
||||
{% endcall %}
|
||||
|
||||
{% call text() %}
|
||||
Best,
|
||||
<br />
|
||||
SimpleLogin Team.
|
||||
{% endcall %}
|
||||
|
||||
{% endblock %}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user