Add djlint (#1122)

* Add DJlint configuration

* Initial reformat for djlint

* Add template linting to CI

* Add explanation for HTML template checks in CONTRIBUTING.md
This commit is contained in:
Carlos Quintana 2022-06-29 11:28:26 +02:00 committed by GitHub
parent f6a7ee981a
commit cb7868bdca
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
148 changed files with 4974 additions and 5283 deletions

View File

@ -77,6 +77,7 @@ jobs:
run: |
poetry run black --check .
poetry run flake8
poetry run djlint --check templates
- name: Run db migration
run: |

View File

@ -145,7 +145,11 @@ The code is also checked with `flake8`, make sure to run `flake8` before creatin
poetry run flake8
```
Nice to have: as we haven't found a good enough HTML code formatter, please reformat any HTML code with PyCharm.
For HTML templates, we use `djlint`. Before creating a pull request, please run
```bash
poetry run djlint --check templates
```
## Test sending email

314
poetry.lock generated
View File

@ -295,7 +295,7 @@ six = ">=1.9"
[[package]]
name = "colorama"
version = "0.4.3"
version = "0.4.5"
description = "Cross-platform colored terminal text."
category = "main"
optional = false
@ -317,14 +317,14 @@ cron = ["capturer (>=2.4)"]
[[package]]
name = "coverage"
version = "6.4"
version = "6.4.1"
description = "Code coverage measurement for Python"
category = "dev"
optional = false
python-versions = ">=3.7"
[package.dependencies]
tomli = {version = "*", optional = true, markers = "python_version < \"3.11\" and extra == \"toml\""}
tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""}
[package.extras]
toml = ["tomli"]
@ -386,6 +386,26 @@ category = "dev"
optional = false
python-versions = "*"
[[package]]
name = "djlint"
version = "0.7.3"
description = "HTML Template Linter and Formatter"
category = "dev"
optional = false
python-versions = ">=3.6.2,<4.0.0"
[package.dependencies]
click = ">=8.0.1,<9.0.0"
colorama = ">=0.4.4,<0.5.0"
pathspec = ">=0.9.0,<0.10.0"
PyYAML = ">=6.0,<7.0"
regex = ">=2022.1.18,<2023.0.0"
tomlkit = ">=0.8.0,<0.9.0"
tqdm = ">=4.62.2,<5.0.0"
[package.extras]
test = ["coverage (>=6.2,<7.0)", "pytest (>=6.2.5,<7.0.0)", "pytest-cov (>=3.0.0,<4.0.0)"]
[[package]]
name = "dkimpy"
version = "1.0.5"
@ -1462,19 +1482,19 @@ python-versions = "*"
[[package]]
name = "pyyaml"
version = "5.4"
version = "6.0"
description = "YAML parser and emitter for Python"
category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
python-versions = ">=3.6"
[[package]]
name = "regex"
version = "2020.9.27"
version = "2022.6.2"
description = "Alternative regular expression module, to replace re."
category = "main"
optional = false
python-versions = "*"
python-versions = ">=3.6"
[[package]]
name = "requests"
@ -1717,6 +1737,31 @@ category = "dev"
optional = false
python-versions = ">=3.7"
[[package]]
name = "tomlkit"
version = "0.8.0"
description = "Style preserving TOML library"
category = "dev"
optional = false
python-versions = ">=3.6,<4.0"
[[package]]
name = "tqdm"
version = "4.64.0"
description = "Fast, Extensible Progress Meter"
category = "dev"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7"
[package.dependencies]
colorama = {version = "*", markers = "platform_system == \"Windows\""}
[package.extras]
dev = ["py-make (>=0.1.0)", "twine", "wheel"]
notebook = ["ipywidgets (>=6)"]
slack = ["slack-sdk"]
telegram = ["requests"]
[[package]]
name = "traitlets"
version = "5.0.4"
@ -1960,7 +2005,7 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"]
[metadata]
lock-version = "1.1"
python-versions = "^3.7"
content-hash = "be129ac44e9114f1a25702b4d32c1caae617d892ed7f59d8e01d778fb3a2b53c"
content-hash = "29d95a850f0a87a38aabb8f5eddba072316860dae26943fd690898ea9bac2b02"
[metadata.files]
aiohttp = [
@ -2139,55 +2184,55 @@ coinbase-commerce = [
{file = "coinbase_commerce-1.0.1.tar.gz", hash = "sha256:5f6807655e9297210c1178dca62d1c1a997505368311c55c8ebe4f466344d966"},
]
colorama = [
{file = "colorama-0.4.3-py2.py3-none-any.whl", hash = "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff"},
{file = "colorama-0.4.3.tar.gz", hash = "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"},
{file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"},
{file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"},
]
coloredlogs = [
{file = "coloredlogs-14.0-py2.py3-none-any.whl", hash = "sha256:346f58aad6afd48444c2468618623638dadab76e4e70d5e10822676f2d32226a"},
{file = "coloredlogs-14.0.tar.gz", hash = "sha256:a1fab193d2053aa6c0a97608c4342d031f1f93a3d1218432c59322441d31a505"},
]
coverage = [
{file = "coverage-6.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:50ed480b798febce113709846b11f5d5ed1e529c88d8ae92f707806c50297abf"},
{file = "coverage-6.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:26f8f92699756cb7af2b30720de0c5bb8d028e923a95b6d0c891088025a1ac8f"},
{file = "coverage-6.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:60c2147921da7f4d2d04f570e1838db32b95c5509d248f3fe6417e91437eaf41"},
{file = "coverage-6.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:750e13834b597eeb8ae6e72aa58d1d831b96beec5ad1d04479ae3772373a8088"},
{file = "coverage-6.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af5b9ee0fc146e907aa0f5fb858c3b3da9199d78b7bb2c9973d95550bd40f701"},
{file = "coverage-6.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:a022394996419142b33a0cf7274cb444c01d2bb123727c4bb0b9acabcb515dea"},
{file = "coverage-6.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:5a78cf2c43b13aa6b56003707c5203f28585944c277c1f3f109c7b041b16bd39"},
{file = "coverage-6.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:9229d074e097f21dfe0643d9d0140ee7433814b3f0fc3706b4abffd1e3038632"},
{file = "coverage-6.4-cp310-cp310-win32.whl", hash = "sha256:fb45fe08e1abc64eb836d187b20a59172053999823f7f6ef4f18a819c44ba16f"},
{file = "coverage-6.4-cp310-cp310-win_amd64.whl", hash = "sha256:3cfd07c5889ddb96a401449109a8b97a165be9d67077df6802f59708bfb07720"},
{file = "coverage-6.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:03014a74023abaf5a591eeeaf1ac66a73d54eba178ff4cb1fa0c0a44aae70383"},
{file = "coverage-6.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c82f2cd69c71698152e943f4a5a6b83a3ab1db73b88f6e769fabc86074c3b08"},
{file = "coverage-6.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b546cf2b1974ddc2cb222a109b37c6ed1778b9be7e6b0c0bc0cf0438d9e45a6"},
{file = "coverage-6.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc173f1ce9ffb16b299f51c9ce53f66a62f4d975abe5640e976904066f3c835d"},
{file = "coverage-6.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c53ad261dfc8695062fc8811ac7c162bd6096a05a19f26097f411bdf5747aee7"},
{file = "coverage-6.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:eef5292b60b6de753d6e7f2d128d5841c7915fb1e3321c3a1fe6acfe76c38052"},
{file = "coverage-6.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:543e172ce4c0de533fa892034cce260467b213c0ea8e39da2f65f9a477425211"},
{file = "coverage-6.4-cp37-cp37m-win32.whl", hash = "sha256:00c8544510f3c98476bbd58201ac2b150ffbcce46a8c3e4fb89ebf01998f806a"},
{file = "coverage-6.4-cp37-cp37m-win_amd64.whl", hash = "sha256:b84ab65444dcc68d761e95d4d70f3cfd347ceca5a029f2ffec37d4f124f61311"},
{file = "coverage-6.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d548edacbf16a8276af13063a2b0669d58bbcfca7c55a255f84aac2870786a61"},
{file = "coverage-6.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:033ebec282793bd9eb988d0271c211e58442c31077976c19c442e24d827d356f"},
{file = "coverage-6.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:742fb8b43835078dd7496c3c25a1ec8d15351df49fb0037bffb4754291ef30ce"},
{file = "coverage-6.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d55fae115ef9f67934e9f1103c9ba826b4c690e4c5bcf94482b8b2398311bf9c"},
{file = "coverage-6.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5cd698341626f3c77784858427bad0cdd54a713115b423d22ac83a28303d1d95"},
{file = "coverage-6.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:62d382f7d77eeeaff14b30516b17bcbe80f645f5cf02bb755baac376591c653c"},
{file = "coverage-6.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:016d7f5cf1c8c84f533a3c1f8f36126fbe00b2ec0ccca47cc5731c3723d327c6"},
{file = "coverage-6.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:69432946f154c6add0e9ede03cc43b96e2ef2733110a77444823c053b1ff5166"},
{file = "coverage-6.4-cp38-cp38-win32.whl", hash = "sha256:83bd142cdec5e4a5c4ca1d4ff6fa807d28460f9db919f9f6a31babaaa8b88426"},
{file = "coverage-6.4-cp38-cp38-win_amd64.whl", hash = "sha256:4002f9e8c1f286e986fe96ec58742b93484195defc01d5cc7809b8f7acb5ece3"},
{file = "coverage-6.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e4f52c272fdc82e7c65ff3f17a7179bc5f710ebc8ce8a5cadac81215e8326740"},
{file = "coverage-6.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b5578efe4038be02d76c344007b13119b2b20acd009a88dde8adec2de4f630b5"},
{file = "coverage-6.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8099ea680201c2221f8468c372198ceba9338a5fec0e940111962b03b3f716a"},
{file = "coverage-6.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a00441f5ea4504f5abbc047589d09e0dc33eb447dc45a1a527c8b74bfdd32c65"},
{file = "coverage-6.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e76bd16f0e31bc2b07e0fb1379551fcd40daf8cdf7e24f31a29e442878a827c"},
{file = "coverage-6.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:8d2e80dd3438e93b19e1223a9850fa65425e77f2607a364b6fd134fcd52dc9df"},
{file = "coverage-6.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:341e9c2008c481c5c72d0e0dbf64980a4b2238631a7f9780b0fe2e95755fb018"},
{file = "coverage-6.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:21e6686a95025927775ac501e74f5940cdf6fe052292f3a3f7349b0abae6d00f"},
{file = "coverage-6.4-cp39-cp39-win32.whl", hash = "sha256:968ed5407f9460bd5a591cefd1388cc00a8f5099de9e76234655ae48cfdbe2c3"},
{file = "coverage-6.4-cp39-cp39-win_amd64.whl", hash = "sha256:e35217031e4b534b09f9b9a5841b9344a30a6357627761d4218818b865d45055"},
{file = "coverage-6.4-pp36.pp37.pp38-none-any.whl", hash = "sha256:e637ae0b7b481905358624ef2e81d7fb0b1af55f5ff99f9ba05442a444b11e45"},
{file = "coverage-6.4.tar.gz", hash = "sha256:727dafd7f67a6e1cad808dc884bd9c5a2f6ef1f8f6d2f22b37b96cb0080d4f49"},
{file = "coverage-6.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f1d5aa2703e1dab4ae6cf416eb0095304f49d004c39e9db1d86f57924f43006b"},
{file = "coverage-6.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4ce1b258493cbf8aec43e9b50d89982346b98e9ffdfaae8ae5793bc112fb0068"},
{file = "coverage-6.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83c4e737f60c6936460c5be330d296dd5b48b3963f48634c53b3f7deb0f34ec4"},
{file = "coverage-6.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84e65ef149028516c6d64461b95a8dbcfce95cfd5b9eb634320596173332ea84"},
{file = "coverage-6.4.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f69718750eaae75efe506406c490d6fc5a6161d047206cc63ce25527e8a3adad"},
{file = "coverage-6.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e57816f8ffe46b1df8f12e1b348f06d164fd5219beba7d9433ba79608ef011cc"},
{file = "coverage-6.4.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:01c5615d13f3dd3aa8543afc069e5319cfa0c7d712f6e04b920431e5c564a749"},
{file = "coverage-6.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:75ab269400706fab15981fd4bd5080c56bd5cc07c3bccb86aab5e1d5a88dc8f4"},
{file = "coverage-6.4.1-cp310-cp310-win32.whl", hash = "sha256:a7f3049243783df2e6cc6deafc49ea123522b59f464831476d3d1448e30d72df"},
{file = "coverage-6.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:ee2ddcac99b2d2aec413e36d7a429ae9ebcadf912946b13ffa88e7d4c9b712d6"},
{file = "coverage-6.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:fb73e0011b8793c053bfa85e53129ba5f0250fdc0392c1591fd35d915ec75c46"},
{file = "coverage-6.4.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:106c16dfe494de3193ec55cac9640dd039b66e196e4641fa8ac396181578b982"},
{file = "coverage-6.4.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:87f4f3df85aa39da00fd3ec4b5abeb7407e82b68c7c5ad181308b0e2526da5d4"},
{file = "coverage-6.4.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:961e2fb0680b4f5ad63234e0bf55dfb90d302740ae9c7ed0120677a94a1590cb"},
{file = "coverage-6.4.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:cec3a0f75c8f1031825e19cd86ee787e87cf03e4fd2865c79c057092e69e3a3b"},
{file = "coverage-6.4.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:129cd05ba6f0d08a766d942a9ed4b29283aff7b2cccf5b7ce279d50796860bb3"},
{file = "coverage-6.4.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:bf5601c33213d3cb19d17a796f8a14a9eaa5e87629a53979a5981e3e3ae166f6"},
{file = "coverage-6.4.1-cp37-cp37m-win32.whl", hash = "sha256:269eaa2c20a13a5bf17558d4dc91a8d078c4fa1872f25303dddcbba3a813085e"},
{file = "coverage-6.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:f02cbbf8119db68455b9d763f2f8737bb7db7e43720afa07d8eb1604e5c5ae28"},
{file = "coverage-6.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ffa9297c3a453fba4717d06df579af42ab9a28022444cae7fa605af4df612d54"},
{file = "coverage-6.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:145f296d00441ca703a659e8f3eb48ae39fb083baba2d7ce4482fb2723e050d9"},
{file = "coverage-6.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d44996140af8b84284e5e7d398e589574b376fb4de8ccd28d82ad8e3bea13"},
{file = "coverage-6.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2bd9a6fc18aab8d2e18f89b7ff91c0f34ff4d5e0ba0b33e989b3cd4194c81fd9"},
{file = "coverage-6.4.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3384f2a3652cef289e38100f2d037956194a837221edd520a7ee5b42d00cc605"},
{file = "coverage-6.4.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9b3e07152b4563722be523e8cd0b209e0d1a373022cfbde395ebb6575bf6790d"},
{file = "coverage-6.4.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1480ff858b4113db2718848d7b2d1b75bc79895a9c22e76a221b9d8d62496428"},
{file = "coverage-6.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:865d69ae811a392f4d06bde506d531f6a28a00af36f5c8649684a9e5e4a85c83"},
{file = "coverage-6.4.1-cp38-cp38-win32.whl", hash = "sha256:664a47ce62fe4bef9e2d2c430306e1428ecea207ffd68649e3b942fa8ea83b0b"},
{file = "coverage-6.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:26dff09fb0d82693ba9e6231248641d60ba606150d02ed45110f9ec26404ed1c"},
{file = "coverage-6.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d9c80df769f5ec05ad21ea34be7458d1dc51ff1fb4b2219e77fe24edf462d6df"},
{file = "coverage-6.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:39ee53946bf009788108b4dd2894bf1349b4e0ca18c2016ffa7d26ce46b8f10d"},
{file = "coverage-6.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f5b66caa62922531059bc5ac04f836860412f7f88d38a476eda0a6f11d4724f4"},
{file = "coverage-6.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd180ed867e289964404051a958f7cccabdeed423f91a899829264bb7974d3d3"},
{file = "coverage-6.4.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84631e81dd053e8a0d4967cedab6db94345f1c36107c71698f746cb2636c63e3"},
{file = "coverage-6.4.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:8c08da0bd238f2970230c2a0d28ff0e99961598cb2e810245d7fc5afcf1254e8"},
{file = "coverage-6.4.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d42c549a8f41dc103a8004b9f0c433e2086add8a719da00e246e17cbe4056f72"},
{file = "coverage-6.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:309ce4a522ed5fca432af4ebe0f32b21d6d7ccbb0f5fcc99290e71feba67c264"},
{file = "coverage-6.4.1-cp39-cp39-win32.whl", hash = "sha256:fdb6f7bd51c2d1714cea40718f6149ad9be6a2ee7d93b19e9f00934c0f2a74d9"},
{file = "coverage-6.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:342d4aefd1c3e7f620a13f4fe563154d808b69cccef415415aece4c786665397"},
{file = "coverage-6.4.1-pp36.pp37.pp38-none-any.whl", hash = "sha256:4803e7ccf93230accb928f3a68f00ffa80a88213af98ed338a57ad021ef06815"},
{file = "coverage-6.4.1.tar.gz", hash = "sha256:4321f075095a096e70aff1d002030ee612b65a205a0a0f5b815280d5dc58100c"},
]
crontab = [
{file = "crontab-0.22.8.tar.gz", hash = "sha256:1ac977fb1b8ba5b7b58e6f713cd7df36e61d7aee4c2b809abcf76adddd2deeaf"},
@ -2228,6 +2273,10 @@ distlib = [
{file = "distlib-0.3.1-py2.py3-none-any.whl", hash = "sha256:8c09de2c67b3e7deef7184574fc060ab8a793e7adbb183d942c389c8b13c52fb"},
{file = "distlib-0.3.1.zip", hash = "sha256:edf6116872c863e1aa9d5bb7cb5e05a022c519a4594dc703843343a9ddd9bff1"},
]
djlint = [
{file = "djlint-0.7.3-py3-none-any.whl", hash = "sha256:714ed457e022047149c8bff57d5be00ce30f8846b60e866791c66d27e7d11e7f"},
{file = "djlint-0.7.3.tar.gz", hash = "sha256:68aad9ddfef883cc9d9e0d177387b74840af5ca12dcce6e4629eb7075c97dc05"},
]
dkimpy = [
{file = "dkimpy-1.0.5.tar.gz", hash = "sha256:9a2420bf09af686736773153fca32a02ae11ecbe24b540c26104628959f91121"},
]
@ -2904,56 +2953,115 @@ pytz = [
{file = "pytz-2020.1.tar.gz", hash = "sha256:c35965d010ce31b23eeb663ed3cc8c906275d6be1a34393a1d73a41febf4a048"},
]
pyyaml = [
{file = "PyYAML-5.4-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:f7a21e3d99aa3095ef0553e7ceba36fb693998fbb1226f1392ce33681047465f"},
{file = "PyYAML-5.4-cp27-cp27m-win32.whl", hash = "sha256:52bf0930903818e600ae6c2901f748bc4869c0c406056f679ab9614e5d21a166"},
{file = "PyYAML-5.4-cp27-cp27m-win_amd64.whl", hash = "sha256:a36a48a51e5471513a5aea920cdad84cbd56d70a5057cca3499a637496ea379c"},
{file = "PyYAML-5.4-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:5e7ac4e0e79a53451dc2814f6876c2fa6f71452de1498bbe29c0b54b69a986f4"},
{file = "PyYAML-5.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:cc552b6434b90d9dbed6a4f13339625dc466fd82597119897e9489c953acbc22"},
{file = "PyYAML-5.4-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0dc9f2eb2e3c97640928dec63fd8dc1dd91e6b6ed236bd5ac00332b99b5c2ff9"},
{file = "PyYAML-5.4-cp36-cp36m-win32.whl", hash = "sha256:5a3f345acff76cad4aa9cb171ee76c590f37394186325d53d1aa25318b0d4a09"},
{file = "PyYAML-5.4-cp36-cp36m-win_amd64.whl", hash = "sha256:f3790156c606299ff499ec44db422f66f05a7363b39eb9d5b064f17bd7d7c47b"},
{file = "PyYAML-5.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:124fd7c7bc1e95b1eafc60825f2daf67c73ce7b33f1194731240d24b0d1bf628"},
{file = "PyYAML-5.4-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:8b818b6c5a920cbe4203b5a6b14256f0e5244338244560da89b7b0f1313ea4b6"},
{file = "PyYAML-5.4-cp37-cp37m-win32.whl", hash = "sha256:737bd70e454a284d456aa1fa71a0b429dd527bcbf52c5c33f7c8eee81ac16b89"},
{file = "PyYAML-5.4-cp37-cp37m-win_amd64.whl", hash = "sha256:7242790ab6c20316b8e7bb545be48d7ed36e26bbe279fd56f2c4a12510e60b4b"},
{file = "PyYAML-5.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cc547d3ead3754712223abb7b403f0a184e4c3eae18c9bb7fd15adef1597cc4b"},
{file = "PyYAML-5.4-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:8635d53223b1f561b081ff4adecb828fd484b8efffe542edcfdff471997f7c39"},
{file = "PyYAML-5.4-cp38-cp38-win32.whl", hash = "sha256:26fcb33776857f4072601502d93e1a619f166c9c00befb52826e7b774efaa9db"},
{file = "PyYAML-5.4-cp38-cp38-win_amd64.whl", hash = "sha256:b2243dd033fd02c01212ad5c601dafb44fbb293065f430b0d3dbf03f3254d615"},
{file = "PyYAML-5.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:31ba07c54ef4a897758563e3a0fcc60077698df10180abe4b8165d9895c00ebf"},
{file = "PyYAML-5.4-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:02c78d77281d8f8d07a255e57abdbf43b02257f59f50cc6b636937d68efa5dd0"},
{file = "PyYAML-5.4-cp39-cp39-win32.whl", hash = "sha256:fdc6b2cb4b19e431994f25a9160695cc59a4e861710cc6fc97161c5e845fc579"},
{file = "PyYAML-5.4-cp39-cp39-win_amd64.whl", hash = "sha256:8bf38641b4713d77da19e91f8b5296b832e4db87338d6aeffe422d42f1ca896d"},
{file = "PyYAML-5.4.tar.gz", hash = "sha256:3c49e39ac034fd64fd576d63bb4db53cda89b362768a67f07749d55f128ac18a"},
{file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"},
{file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"},
{file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"},
{file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"},
{file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"},
{file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"},
{file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"},
{file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"},
{file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"},
{file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"},
{file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"},
{file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"},
{file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"},
{file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"},
{file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"},
{file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"},
{file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"},
{file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"},
{file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"},
{file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"},
{file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"},
{file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"},
{file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"},
{file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"},
{file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"},
{file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"},
{file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"},
{file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"},
{file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"},
{file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"},
{file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"},
{file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"},
{file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"},
]
regex = [
{file = "regex-2020.9.27-cp27-cp27m-win32.whl", hash = "sha256:d23a18037313714fb3bb5a94434d3151ee4300bae631894b1ac08111abeaa4a3"},
{file = "regex-2020.9.27-cp27-cp27m-win_amd64.whl", hash = "sha256:84e9407db1b2eb368b7ecc283121b5e592c9aaedbe8c78b1a2f1102eb2e21d19"},
{file = "regex-2020.9.27-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:5f18875ac23d9aa2f060838e8b79093e8bb2313dbaaa9f54c6d8e52a5df097be"},
{file = "regex-2020.9.27-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:ae91972f8ac958039920ef6e8769277c084971a142ce2b660691793ae44aae6b"},
{file = "regex-2020.9.27-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:9a02d0ae31d35e1ec12a4ea4d4cca990800f66a917d0fb997b20fbc13f5321fc"},
{file = "regex-2020.9.27-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:ebbe29186a3d9b0c591e71b7393f1ae08c83cb2d8e517d2a822b8f7ec99dfd8b"},
{file = "regex-2020.9.27-cp36-cp36m-win32.whl", hash = "sha256:4707f3695b34335afdfb09be3802c87fa0bc27030471dbc082f815f23688bc63"},
{file = "regex-2020.9.27-cp36-cp36m-win_amd64.whl", hash = "sha256:9bc13e0d20b97ffb07821aa3e113f9998e84994fe4d159ffa3d3a9d1b805043b"},
{file = "regex-2020.9.27-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:f1b3afc574a3db3b25c89161059d857bd4909a1269b0b3cb3c904677c8c4a3f7"},
{file = "regex-2020.9.27-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5533a959a1748a5c042a6da71fe9267a908e21eded7a4f373efd23a2cbdb0ecc"},
{file = "regex-2020.9.27-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:1fe0a41437bbd06063aa184c34804efa886bcc128222e9916310c92cd54c3b4c"},
{file = "regex-2020.9.27-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:c570f6fa14b9c4c8a4924aaad354652366577b4f98213cf76305067144f7b100"},
{file = "regex-2020.9.27-cp37-cp37m-win32.whl", hash = "sha256:eda4771e0ace7f67f58bc5b560e27fb20f32a148cbc993b0c3835970935c2707"},
{file = "regex-2020.9.27-cp37-cp37m-win_amd64.whl", hash = "sha256:60b0e9e6dc45683e569ec37c55ac20c582973841927a85f2d8a7d20ee80216ab"},
{file = "regex-2020.9.27-cp38-cp38-manylinux1_i686.whl", hash = "sha256:088afc8c63e7bd187a3c70a94b9e50ab3f17e1d3f52a32750b5b77dbe99ef5ef"},
{file = "regex-2020.9.27-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:eaf548d117b6737df379fdd53bdde4f08870e66d7ea653e230477f071f861121"},
{file = "regex-2020.9.27-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:41bb65f54bba392643557e617316d0d899ed5b4946dccee1cb6696152b29844b"},
{file = "regex-2020.9.27-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:8d69cef61fa50c8133382e61fd97439de1ae623fe943578e477e76a9d9471637"},
{file = "regex-2020.9.27-cp38-cp38-win32.whl", hash = "sha256:f2388013e68e750eaa16ccbea62d4130180c26abb1d8e5d584b9baf69672b30f"},
{file = "regex-2020.9.27-cp38-cp38-win_amd64.whl", hash = "sha256:4318d56bccfe7d43e5addb272406ade7a2274da4b70eb15922a071c58ab0108c"},
{file = "regex-2020.9.27-cp39-cp39-manylinux1_i686.whl", hash = "sha256:84cada8effefe9a9f53f9b0d2ba9b7b6f5edf8d2155f9fdbe34616e06ececf81"},
{file = "regex-2020.9.27-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:816064fc915796ea1f26966163f6845de5af78923dfcecf6551e095f00983650"},
{file = "regex-2020.9.27-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:5d892a4f1c999834eaa3c32bc9e8b976c5825116cde553928c4c8e7e48ebda67"},
{file = "regex-2020.9.27-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:c9443124c67b1515e4fe0bb0aa18df640965e1030f468a2a5dc2589b26d130ad"},
{file = "regex-2020.9.27-cp39-cp39-win32.whl", hash = "sha256:49f23ebd5ac073765ecbcf046edc10d63dcab2f4ae2bce160982cb30df0c0302"},
{file = "regex-2020.9.27-cp39-cp39-win_amd64.whl", hash = "sha256:3d20024a70b97b4f9546696cbf2fd30bae5f42229fbddf8661261b1eaff0deb7"},
{file = "regex-2020.9.27.tar.gz", hash = "sha256:a6f32aea4260dfe0e55dc9733ea162ea38f0ea86aa7d0f77b15beac5bf7b369d"},
{file = "regex-2022.6.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:042d122f9fee3ceb6d7e3067d56557df697d1aad4ff5f64ecce4dc13a90a7c01"},
{file = "regex-2022.6.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ffef4b30785dc2d1604dfb7cf9fca5dc27cd86d65f7c2a9ec34d6d3ae4565ec2"},
{file = "regex-2022.6.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0afa6a601acf3c0dc6de4e8d7d8bbce4e82f8542df746226cd35d4a6c15e9456"},
{file = "regex-2022.6.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4a11cbe8eb5fb332ae474895b5ead99392a4ea568bd2a258ab8df883e9c2bf92"},
{file = "regex-2022.6.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c1f62ee2ba880e221bc950651a1a4b0176083d70a066c83a50ef0cb9b178e12"},
{file = "regex-2022.6.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5aba3d13c77173e9bfed2c2cea7fc319f11c89a36fcec08755e8fb169cf3b0df"},
{file = "regex-2022.6.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:249437f7f5b233792234aeeecb14b0aab1566280de42dfc97c26e6f718297d68"},
{file = "regex-2022.6.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:179410c79fa86ef318d58ace233f95b87b05a1db6dc493fa29404a43f4b215e2"},
{file = "regex-2022.6.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:5e201b1232d81ca1a7a22ab2f08e1eccad4e111579fd7f3bbf60b21ef4a16cea"},
{file = "regex-2022.6.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:fdecb225d0f1d50d4b26ac423e0032e76d46a788b83b4e299a520717a47d968c"},
{file = "regex-2022.6.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:be57f9c7b0b423c66c266a26ad143b2c5514997c05dd32ce7ca95c8b209c2288"},
{file = "regex-2022.6.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:ed657a07d8a47ef447224ea00478f1c7095065dfe70a89e7280e5f50a5725131"},
{file = "regex-2022.6.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:24908aefed23dd065b4a668c0b4ca04d56b7f09d8c8e89636cf6c24e64e67a1e"},
{file = "regex-2022.6.2-cp310-cp310-win32.whl", hash = "sha256:775694cd0bb2c4accf2f1cdd007381b33ec8b59842736fe61bdbad45f2ac7427"},
{file = "regex-2022.6.2-cp310-cp310-win_amd64.whl", hash = "sha256:809bbbbbcf8258049b031d80932ba71627d2274029386f0452e9950bcfa2c6e8"},
{file = "regex-2022.6.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:ecd2b5d983eb0adf2049d41f95205bdc3de4e6cc2350e9c80d4409d3a75229de"},
{file = "regex-2022.6.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f4c101746a8dac0401abefa716b357c546e61ea2e3d4a564a9db9eac57ccbce"},
{file = "regex-2022.6.2-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:166ae7674d0a0e0f8044e7335ba86d0716c9d49465cff1b153f908e0470b8300"},
{file = "regex-2022.6.2-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c5eac5d8a8ac9ccf00805d02a968a36f5c967db6c7d2b747ab9ed782b3b3a28b"},
{file = "regex-2022.6.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f57823f35b18d82b201c1b27ce4e55f88e79e81d9ca07b50ce625d33823e1439"},
{file = "regex-2022.6.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4d42e3b7b23473729adbf76103e7df75f9167a5a80b1257ca30688352b4bb2dc"},
{file = "regex-2022.6.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b2932e728bee0a634fe55ee54d598054a5a9ffe4cd2be21ba2b4b8e5f8064c2c"},
{file = "regex-2022.6.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:17764683ea01c2b8f103d99ae9de2473a74340df13ce306c49a721f0b1f0eb9e"},
{file = "regex-2022.6.2-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:2ac29b834100d2c171085ceba0d4a1e7046c434ddffc1434dbc7f9d59af1e945"},
{file = "regex-2022.6.2-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:f43522fb5d676c99282ca4e2d41e8e2388427c0cf703db6b4a66e49b10b699a8"},
{file = "regex-2022.6.2-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:9faa01818dad9111dbf2af26c6e3c45140ccbd1192c3a0981f196255bf7ec5e6"},
{file = "regex-2022.6.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:17443f99b8f255273731f915fdbfea4d78d809bb9c3aaf67b889039825d06515"},
{file = "regex-2022.6.2-cp36-cp36m-win32.whl", hash = "sha256:4a5449adef907919d4ce7a1eab2e27d0211d1b255bf0b8f5dd330ad8707e0fc3"},
{file = "regex-2022.6.2-cp36-cp36m-win_amd64.whl", hash = "sha256:4d206703a96a39763b5b45cf42645776f5553768ea7f3c2c1a39a4f59cafd4ba"},
{file = "regex-2022.6.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:fcd7c432202bcb8b642c3f43d5bcafc5930d82fe5b2bf2c008162df258445c1d"},
{file = "regex-2022.6.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:186c5a4a4c40621f64d771038ede20fca6c61a9faa8178f9e305aaa0c2442a97"},
{file = "regex-2022.6.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:047b2d1323a51190c01b6604f49fe09682a5c85d3c1b2c8b67c1cd68419ce3c4"},
{file = "regex-2022.6.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:30637e7fa4acfed444525b1ab9683f714be617862820578c9fd4e944d4d9ad1f"},
{file = "regex-2022.6.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3adafe6f2c6d86dbf3313866b61180530ca4dcd0c264932dc8fa1ffb10871d58"},
{file = "regex-2022.6.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:67ae3601edf86e15ebe40885e5bfdd6002d34879070be15cf18fc0d80ea24fed"},
{file = "regex-2022.6.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:48dddddce0ea7e7c3e92c1e0c5a28c13ca4dc9cf7e996c706d00479652bff76c"},
{file = "regex-2022.6.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:68e5c641645351eb9eb12c465876e76b53717f99e9b92aea7a2dd645a87aa7aa"},
{file = "regex-2022.6.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:8fd5f8ae42f789538bb634bdfd69b9aa357e76fdfd7ad720f32f8994c0d84f1e"},
{file = "regex-2022.6.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:71988a76fcb68cc091e901fddbcac0f9ad9a475da222c47d3cf8db0876cb5344"},
{file = "regex-2022.6.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:4b8838f70be3ce9e706df9d72f88a0aa7d4c1fea61488e06fdf292ccb70ad2be"},
{file = "regex-2022.6.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:663dca677bd3d2e2b5b7d0329e9f24247e6f38f3b740dd9a778a8ef41a76af41"},
{file = "regex-2022.6.2-cp37-cp37m-win32.whl", hash = "sha256:24963f0b13cc63db336d8da2a533986419890d128c551baacd934c249d51a779"},
{file = "regex-2022.6.2-cp37-cp37m-win_amd64.whl", hash = "sha256:ceff75127f828dfe7ceb17b94113ec2df4df274c4cd5533bb299cb099a18a8ca"},
{file = "regex-2022.6.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1a6f2698cfa8340dfe4c0597782776b393ba2274fe4c079900c7c74f68752705"},
{file = "regex-2022.6.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a8a08ace913c4101f0dc0be605c108a3761842efd5f41a3005565ee5d169fb2b"},
{file = "regex-2022.6.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26dbe90b724efef7820c3cf4a0e5be7f130149f3d2762782e4e8ac2aea284a0b"},
{file = "regex-2022.6.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b5f759a1726b995dc896e86f17f9c0582b54eb4ead00ed5ef0b5b22260eaf2d0"},
{file = "regex-2022.6.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1fc26bb3415e7aa7495c000a2c13bf08ce037775db98c1a3fac9ff04478b6930"},
{file = "regex-2022.6.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:52684da32d9003367dc1a1c07e059b9bbaf135ad0764cd47d8ac3dba2df109bc"},
{file = "regex-2022.6.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c1264eb40a71cf2bff43d6694ab7254438ca19ef330175060262b3c8dd3931a"},
{file = "regex-2022.6.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:bc635ab319c9b515236bdf327530acda99be995f9d3b9f148ab1f60b2431e970"},
{file = "regex-2022.6.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:27624b490b5d8880f25dac67e1e2ea93dfef5300b98c6755f585799230d6c746"},
{file = "regex-2022.6.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:555f7596fd1f123f8c3a67974c01d6ef80b9769e04d660d6c1a7cc3e6cff7069"},
{file = "regex-2022.6.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:933e72fbe1829cbd59da2bc51ccd73d73162f087f88521a87a8ec9cb0cf10fa8"},
{file = "regex-2022.6.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:cff5c87e941292c97d11dc81bd20679f56a2830f0f0e32f75b8ed6e0eb40f704"},
{file = "regex-2022.6.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:c757f3a27b6345de13ef3ca956aa805d7734ce68023e84d0fc74e1f09ce66f7a"},
{file = "regex-2022.6.2-cp38-cp38-win32.whl", hash = "sha256:a58d21dd1a2d6b50ed091554ff85e448fce3fe33a4db8b55d0eba2ca957ed626"},
{file = "regex-2022.6.2-cp38-cp38-win_amd64.whl", hash = "sha256:495a4165172848503303ed05c9d0409428f789acc27050fe2cf0a4549188a7d5"},
{file = "regex-2022.6.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1ab5cf7d09515548044e69d3a0ec77c63d7b9dfff4afc19653f638b992573126"},
{file = "regex-2022.6.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c1ea28f0ee6cbe4c0367c939b015d915aa9875f6e061ba1cf0796ca9a3010570"},
{file = "regex-2022.6.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3de1ecf26ce85521bf73897828b6d0687cc6cf271fb6ff32ac63d26b21f5e764"},
{file = "regex-2022.6.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fa7c7044aabdad2329974be2246babcc21d3ede852b3971a90fd8c2056c20360"},
{file = "regex-2022.6.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:53d69d77e9cfe468b000314dd656be85bb9e96de088a64f75fe128dfe1bf30dd"},
{file = "regex-2022.6.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c8d61883a38b1289fba9944a19a361875b5c0170b83cdcc95ea180247c1b7d3"},
{file = "regex-2022.6.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c5429202bef174a3760690d912e3a80060b323199a61cef6c6c29b30ce09fd17"},
{file = "regex-2022.6.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e85b10280cf1e334a7c95629f6cbbfe30b815a4ea5f1e28d31f79eb92c2c3d93"},
{file = "regex-2022.6.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c400dfed4137f32127ea4063447006d7153c974c680bf0fb1b724cce9f8567fc"},
{file = "regex-2022.6.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7f648037c503985aed39f85088acab6f1eb6a0482d7c6c665a5712c9ad9eaefc"},
{file = "regex-2022.6.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:e7b2ff451f6c305b516281ec45425dd423223c8063218c5310d6f72a0a7a517c"},
{file = "regex-2022.6.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:be456b4313a86be41706319c397c09d9fdd2e5cdfde208292a277b867e99e3d1"},
{file = "regex-2022.6.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c3db393b21b53d7e1d3f881b64c29d886cbfdd3df007e31de68b329edbab7d02"},
{file = "regex-2022.6.2-cp39-cp39-win32.whl", hash = "sha256:d70596f20a03cb5f935d6e4aad9170a490d88fc4633679bf00c652e9def4619e"},
{file = "regex-2022.6.2-cp39-cp39-win_amd64.whl", hash = "sha256:3b9b6289e03dbe6a6096880d8ac166cb23c38b4896ad235edee789d4e8697152"},
{file = "regex-2022.6.2.tar.gz", hash = "sha256:f7b43acb2c46fb2cd506965b2d9cf4c5e64c9c612bac26c1187933c7296bf08c"},
]
requests = [
{file = "requests-2.25.1-py2.py3-none-any.whl", hash = "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"},
@ -3135,6 +3243,14 @@ tomli = [
{file = "tomli-2.0.0-py3-none-any.whl", hash = "sha256:b5bde28da1fed24b9bd1d4d2b8cba62300bfb4ec9a6187a957e8ddb9434c5224"},
{file = "tomli-2.0.0.tar.gz", hash = "sha256:c292c34f58502a1eb2bbb9f5bbc9a5ebc37bee10ffb8c2d6bbdfa8eb13cc14e1"},
]
tomlkit = [
{file = "tomlkit-0.8.0-py3-none-any.whl", hash = "sha256:b824e3466f1d475b2b5f1c392954c6cb7ea04d64354ff7300dc7c14257dc85db"},
{file = "tomlkit-0.8.0.tar.gz", hash = "sha256:29e84a855712dfe0e88a48f6d05c21118dbafb283bb2eed614d46f80deb8e9a1"},
]
tqdm = [
{file = "tqdm-4.64.0-py2.py3-none-any.whl", hash = "sha256:74a2cdefe14d11442cedf3ba4e21a3b84ff9a2dbdc6cfae2c34addb2a14a5ea6"},
{file = "tqdm-4.64.0.tar.gz", hash = "sha256:40be55d30e200777a307a7585aee69e4eabb46b4ec6a4b4a5f2d9f11e7d5408d"},
]
traitlets = [
{file = "traitlets-5.0.4-py3-none-any.whl", hash = "sha256:9664ec0c526e48e7b47b7d14cd6b252efa03e0129011de0a9c1d70315d4309c3"},
{file = "traitlets-5.0.4.tar.gz", hash = "sha256:86c9351f94f95de9db8a04ad8e892da299a088a64fd283f9f6f18770ae5eae1b"},

View File

@ -18,6 +18,26 @@ exclude = '''
)
'''
[tool.djlint]
indent = 2
profile = "jinja"
blank_line_after_tag = "if,for,include,load,extends,block"
# H006: Images should have a height attribute
# H013: Images should have an alt attribute
# H016: Missing title tag in html. | False positive on template
# H017: Tag should be self closing
# H019: Replace 'javascript:abc()' with on_ event and real url. (javascript:history.back())
# H021: Avoid inline styles
# H025: Tag seems to be orphan (it messes up with indicators on strings such as <email@example.com>)
# H030: Consider adding a meta description | False positive on template
# H031: Consider adding meta keywords
# T003: Endblock should have a name
# J004: (Jinja) Static urls should follow {{ url_for('static'..) }} pattern
# J018: (Jinja) Internal links should use the {{ url_for() ... }} pattern. | Some false positives
# T001: Variables should be wrapped in a single whitespace. | Messes up with comments
ignore = "H006,H013,H016,H017,H019,H021,H025,H030,H031,T003,J004,J018,T001"
[tool.poetry]
name = "SimpleLogin"
version = "0.1.0"
@ -98,6 +118,7 @@ pre-commit = "^2.17.0"
black = "^22.1.0"
flake8 = "^4.0.1"
flake8-bugbear = "^22.1.11"
djlint = "0.7.3"
[build-system]
requires = ["poetry>=0.12"]

View File

@ -3,29 +3,21 @@
<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>
<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 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 class="errors">
{% for error in field.errors %}<li class="text-danger">{{ error }}</li>{% endfor %}
</ul>
{% endif %}
{% endmacro %}

View File

@ -1,16 +1,13 @@
{% extends "error.html" %}
{% block error_name %}
{{ error }}
{% endblock %}
{% 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 %}
{% endblock %}

View File

@ -1,21 +1,21 @@
{% extends "single.html" %}
{% block title %}
Change Email
{% endblock %}
{% 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>
Incorrect or expired link.
<br />
<br />
</div>
<div class="text-center">
Please go to <a href="{{ url_for('dashboard.setting') }}">settings</a>
Please go to
<a href="{{ url_for('dashboard.setting') }}">settings</a>
page to re-send the confirmation email.
</div>
</div>
</div>
{% endblock %}
{% endblock %}

View File

@ -1,56 +1,47 @@
{% extends "single.html" %}
{% block title %}
Verify Your Security Key
{% endblock %}
{% 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>
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>
<label class="form-check-label" for="remember">{{ fido_token_form.remember.description }}</label>
</div>
</form>
{% if enable_otp %}
<hr>
<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>
Don't have your key with you?
<br />
<a href="{{ url_for('auth.mfa') }}">Verify by One-Time Password</a>
</div>
{% endif %}
<hr>
<hr />
<div class="mt-5">
If you have troubles with your authentication app, you can use the recovery code to login. <br>
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);
@ -78,12 +69,7 @@
}
</script>
{% if auto_activate %}
<script>$('document').ready(verifyKey());</script>
{% endif %}
{% if auto_activate %}<script>$('document').ready(verifyKey());</script>{% endif %}
</div>
</div>
{% endblock %}
{% endblock %}

View File

@ -1,34 +1,26 @@
{% extends "single.html" %}
{% block title %}
Forgot Password
{% endblock %}
{% block title %}Forgot Password{% endblock %}
{% block single_content %}
{% if error %}
<div class="text-danger text-center mb-4">{{ error }}</div>
{% endif %}
{% 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.
Forget it,
<a href="{{ url_for('auth.login') }}">send me back</a>
to the sign in screen.
</div>
{% endblock %}
{% endblock %}

View File

@ -1,62 +1,52 @@
{% extends "single.html" %}
{% block title %}
Login
{% endblock %}
{% 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>
<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>
<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>
<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">
<img class="mr-2" src="/static/images/proton.svg" />
Login 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>
Don't have an account yet?
<a href="{{ url_for('auth.register') }}">Sign up</a>
</div>
{% endblock %}

View File

@ -1,54 +1,47 @@
{% extends "single.html" %}
{% block title %}
MFA
{% endblock %}
{% 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>
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">
<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>
<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>
<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>
Having trouble with your authenticator?
<br />
<a href="{{ url_for('auth.fido') }}">
Verify by your security
key
</a>
</div>
{% endif %}
<hr>
<hr />
<div class="mt-5">
If you cannot access your authenticator application you can instead use a recovery code. <br>
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 %}

View File

@ -1,27 +1,18 @@
{% extends "single.html" %}
{% block title %}
Recovery Code
{% endblock %}
{% 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>
<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 %}
{% endblock %}

View File

@ -1,15 +1,12 @@
{% extends "single.html" %}
{% block title %}
Register
{% endblock %}
{% 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") }}
@ -19,13 +16,11 @@
</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">
@ -33,34 +28,34 @@
<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>
<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">
<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>
Already have account?
<a href="{{ url_for('auth.login') }}">Sign in</a>
</div>
{% endblock %}
{% endblock %}

View File

@ -1,22 +1,14 @@
{% extends "single.html" %}
{% block title %}
Activation Email Sent
{% endblock %}
{% 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>
<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 %}
{% endblock %}

View File

@ -1,29 +1,24 @@
{% extends "single.html" %}
{% block title %}
Resend activation email
{% endblock %}
{% 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>
Don't have account yet?
<a href="{{ url_for('auth.register') }}">Sign up</a>
</div>
{% endblock %}
{% endblock %}

View File

@ -1,29 +1,21 @@
{% extends "single.html" %}
{% block title %}
Reset password
{% endblock %}
{% block title %}Reset password{% endblock %}
{% block single_content %}
{% if error %}
<div class="text-danger text-center mb-4">{{ error }}</div>
{% endif %}
{% 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 %}
{% endblock %}

View File

@ -1,52 +1,46 @@
{% extends "single.html" %}
{% block title %}
Social Login
{% endblock %}
{% 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>
<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 %}
<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 %}
<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>
<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">
<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>
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>
<a href="{{ url_for('auth.register') }}">Sign up</a>
/
<a href="{{ url_for('auth.login') }}">Login</a>
</div>
{% endblock %}
{% endblock %}

View File

@ -1,132 +1,105 @@
{% 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">
<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 %}
<!-- Bing -->
<meta name="msvalidate.01" content="2A313A69CBFD1A378C3B91734DC221A8"/>
<!-- 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 %}
<!-- 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 class="">
<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>
{% 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 {
@ -206,14 +179,10 @@
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>
<!-- For additional script -->
{% block script %}
{% endblock %}
<script src="{{ url_for('static', filename='js/analytics.js') }}"></script>
</body>
</html>
</script>
<script src="{{ url_for('static', filename='local-storage-polyfill.js') }}"></script>
<!-- For additional script -->
{% block script %}{% endblock %}
<script src="{{ url_for('static', filename='js/analytics.js') }}"></script>
</body>
</html>

View File

@ -1,42 +1,49 @@
{% extends 'default.html' %}
{% extends "default.html" %}
{% set active_page = "dashboard" %}
{% block title %}
Alias Contact Manager
{% endblock %}
{% 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">
<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>
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">
<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>
Make sure you send the email from one of the following mailboxes:
<br />
{% for mailbox in alias.mailboxes %}
- <b>{{ mailbox.email }}</b> <br>
- <b>{{ mailbox.email }}</b>
<br />
{% endfor %}
{% endif %}
{% else %}
@ -52,42 +59,44 @@
</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">
<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>
<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>
<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">
<input type="hidden" name="form-name" value="search">
<input type="search" name="query" value="{{ query }}"
<input type="search"
name="query"
value="{{ query }}"
placeholder="Enter to search for contacts"
class="form-control shadow mr-2"
style="max-width: 20em"
>
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>
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>
@ -96,105 +105,97 @@
</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="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>
<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 "" }}>
<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"
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 %}
{% 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>
<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"
<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>
<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>
<i class="fa fa-paper-plane mr-2"
data-toggle="tooltip"
title="Email sent to alias"></i>
{{ email_log.created_at | dt }}
{% endif %}
<br>
<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> forwards,
<span class="alias-activity">{{ contact_info.nb_reply }}</span> sents
in the last 14 days.
</div>
</div>
<a href="{{ url_for('dashboard.contact_detail_route', contact_id=contact.id) }}">Edit ➡</a>
<form method="post">
<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>
<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">
@ -202,23 +203,23 @@
<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>
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>
Next
</a>
</li>
</ul>
</nav>
</div>
</div>
{% endif %}
{% endblock %}
{% block script %}
<script>
$(".delete-forward-email").on("click", function (e) {
let that = $(this);
@ -282,4 +283,4 @@
}
}
</script>
{% endblock %}
{% endblock %}

View File

@ -1,7 +1,8 @@
{% extends 'default.html' %}
{% extends "default.html" %}
{% set active_page = "dashboard" %}
{% block head %}
<style>
{# https://bootsnipp.com/snippets/rljEW#}
.card-counter {
@ -61,108 +62,102 @@
display: block;
font-size: 18px;
}
</style>
</style>
{% endblock %}
{% block title %}
Alias Activity
{% endblock %}
{% block title %}Alias Activity{% endblock %}
{% block default_content %}
<h1 class="h3">
{{ alias.email }}
</h1>
<div class="row">
<div class="col-md-3 col-sm-6">
<div class="card-counter primary">
<i class="fa fa-at"></i>
<span class="count-numbers">{{ total }}</span>
<span class="count-name">Email Handled</span>
</div>
</div>
<div class="col-md-3 col-sm-6">
<div class="card-counter primary">
<i class="fa fa-paper-plane"></i>
<span class="count-numbers">{{ email_forwarded }}</span>
<span class="count-name">Email Forwarded</span>
</div>
</div>
<div class="col-md-3 col-sm-6">
<div class="card-counter primary">
<i class="fa fa-reply"></i>
<span class="count-numbers">{{ email_replied }}</span>
<span class="count-name">Email Replied</span>
</div>
</div>
<div class="col-md-3 col-sm-6">
<div class="card-counter danger">
<i class="fa fa-ban"></i>
<span class="count-numbers">{{ email_blocked }}</span>
<span class="count-name">Email Blocked</span>
</div>
</div>
</div>
<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>
<h1 class="h3">{{ alias.email }}</h1>
<div class="row">
<div class="col-md-3 col-sm-6">
<div class="card-counter primary">
<i class="fa fa-at"></i>
<span class="count-numbers">{{ total }}</span>
<span class="count-name">Email Handled</span>
</div>
</div>
<div class="col-md-3 col-sm-6">
<div class="card-counter primary">
<i class="fa fa-paper-plane"></i>
<span class="count-numbers">{{ email_forwarded }}</span>
<span class="count-name">Email Forwarded</span>
</div>
</div>
<div class="col-md-3 col-sm-6">
<div class="card-counter primary">
<i class="fa fa-reply"></i>
<span class="count-numbers">{{ email_replied }}</span>
<span class="count-name">Email Replied</span>
</div>
</div>
<div class="col-md-3 col-sm-6">
<div class="card-counter danger">
<i class="fa fa-ban"></i>
<span class="count-numbers">{{ email_blocked }}</span>
<span class="count-name">Email Blocked</span>
</div>
</div>
</div>
<div class="row mt-4">
{% for log in logs %}
{% 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 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 %}
</div>
</div>
{% endfor %}
</div>
<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 %}
<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 %}
<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 %}

View File

@ -1,46 +1,31 @@
{% extends 'default.html' %}
{% extends "default.html" %}
{% set active_page = "dashboard" %}
{% block title %}
Receive {{ alias.email }}
{% endblock %}
{% 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">
<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 %}>
<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 %}
{% block script %}<script>$('.mailbox-select').multipleSelect();</script>{% endblock %}

View File

@ -1,47 +1,38 @@
{% extends 'default.html' %}
{% extends "default.html" %}
{% set active_page = "dashboard" %}
{% block title %}
Send {{ alias.email }}
{% endblock %}
{% 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.
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.
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">
<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>
<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 any more, please delete the link.
</p>
@ -51,8 +42,8 @@
</form>
{% else %}
<p>
In order to transfer ownership,
please create the <b>Share URL</b> 👇 and send it to the other person.
In order to transfer ownership,
please create the <b>Share URL</b> 👇 and send it to the other person.
</p>
<form method="post">
<input type="hidden" name="form-name" value="create">
@ -60,17 +51,10 @@
</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 class="alert alert-danger">After the confirmation, you can no longer use this alias.</div>
</div>
</div>
{% endblock %}

View File

@ -1,103 +1,84 @@
{% extends 'default.html' %}
{% block title %}
API Key
{% endblock %}
{% extends "default.html" %}
{% block title %}API Key{% endblock %}
{% set active_page = "api_key" %}
{% block head %}
{% endblock %}
{% block head %}{% endblock %}
{% block default_content %}
<div class="row">
<div class="col">
<h1 class="h3"> API Keys </h1>
<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="**********">
<input class="form-control"
id="apikey-{{ api_key.id }}"
readonly
value="**********">
</div>
<br>
<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>
<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 &nbsp; &nbsp; <i class="fe fe-trash"></i>
Delete All &nbsp; &nbsp; <i class="fe fe-trash"></i>
</span>
</form>
<br>
<br />
{% endif %}
<hr>
<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);

View File

@ -1,81 +1,69 @@
{% extends 'default.html' %}
{% extends "default.html" %}
{% set active_page = "app" %}
{% block title %}
Sign in with SimpleLogin apps
{% endblock %}
{% block title %}Sign in with SimpleLogin apps{% endblock %}
{% block default_content %}
<div class="row">
<div class="col">
<h1 class="h3"> Apps </h1>
<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>
<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>
{% for client_user in client_users %}
<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>
<tr>
<td>{{ client_user.client.name }}</td>
<td>
{% for scope, val in client_user.get_user_info().items() %}
<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 %}
<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>

View File

@ -1,63 +1,71 @@
{% extends 'default.html' %}
{% set active_page = "setting" %}
{% block title %}
Alias Batch Import
{% endblock %}
{% 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>
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>
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="/static/batch_import_template.csv" download>Download CSV Template</a>
<hr>
<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">
<input required type="file"
<input required
type="file"
name="alias-file"
accept=".csv"
class="form-control-file">
<label>Only <b>.csv</b> file is supported. </label> <br>
<label>
Only <b>.csv</b> file is supported.
</label>
<br />
<button class="btn btn-success mt-2">Upload</button>
</form>
{% if batch_imports %}
<hr>
<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>
<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 %}
{% 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 %}

View File

@ -1,92 +1,86 @@
{% extends 'default.html' %}
{% block title %}
Billing
{% endblock %}
{% block head %}
{% endblock %}
{% 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>
<h1 class="h3 mb-5">Billing</h1>
{% if sub.cancelled %}
<p>
You are on the <b>{{ sub.plan_name() }}</b> plan. <br>
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>
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>
<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>
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>
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>
<hr />
<div class="mt-6">
<h4>Change Plan</h4>
You can change the plan at any moment. <br>
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>
<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');">
<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');">
<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>
<hr />
<div>
<h4>Cancel subscription</h4>
Don't want to protect your inbox anymore? <br>
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>
Cancel subscription <i class="fe fe-alert-triangle text-danger"></i>
</span>
</form>
</div>
{% endif %}
</div>
</div>
{% endblock %}
{% block script %}
<script>
<script>
$(".cancel").on("click", function (e) {
let that = $(this);
@ -111,4 +105,4 @@
});
</script>
{% endblock %}
{% endblock %}

View File

@ -1,28 +1,19 @@
{% extends 'default.html' %}
{% extends "default.html" %}
{% set active_page = "dashboard" %}
{% block title %}
Block a sender
{% endblock %}
{% block title %}Block a sender{% endblock %}
{% block default_content %}
<div class="card">
<div class="card-body">
<h1 class="h3">
Block sender
</h1>
<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 %}

View File

@ -1,11 +1,7 @@
{% extends 'default.html' %}
{% extends "default.html" %}
{% set active_page = "dashboard" %}
{% block title %}
Contact {{ contact.email }} - Alias {{ alias.email }}
{% endblock %}
{% block title %}Contact {{ contact.email }} - Alias {{ alias.email }}{% endblock %}
{% block default_content %}
<div class="row">
@ -13,21 +9,24 @@
<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 }}
<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>
<span class="cursor"
data-toggle="tooltip"
data-original-title="PGP Enabled">🗝</span>
{% endif %}
</li>
</ol>
</nav>
</h1>
<div class="card">
<form method="post">
<input type="hidden" name="form-name" value="pgp">
<div class="card-body">
<div class="card-title">
Pretty Good Privacy (PGP)
@ -37,44 +36,30 @@
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="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>
<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 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>
<script src="/static/js/utils/drag-drop-into-text.js"></script>
<script>enableDragDropForPGPKeys('#pgp-public-key');</script>
{% endblock %}

View File

@ -1,70 +1,53 @@
{% extends 'default.html' %}
{% 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">
</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>
</style>
{% endblock %}
{% block title %}
Coupon
{% 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>
{% 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 %}
{% block script %}<script type="text/javascript">Paddle.Setup({vendor: {{PADDLE_VENDOR_ID } }});</script>{% endblock %}

View File

@ -1,34 +1,33 @@
{% extends 'default.html' %}
{% extends "default.html" %}
{% set active_page = "dashboard" %}
{% block title %}
Custom Alias
{% endblock %}
{% 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>
<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"
<input name="prefix"
class="form-control"
id="prefix"
type="text"
data-parsley-pattern="[0-9a-z-_.]{1,}"
@ -36,30 +35,26 @@
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>
autofocus
required>
</div>
<div class="col-sm-6 p-1">
<select class="form-control" name="signed-alias-suffix">
{% for suffix_info in alias_suffixes_with_signature %}
{% set alias_suffix = suffix_info[0] %}
<option value="{{ suffix_info[1] }}"
{% 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 %}
>
<option value="{{ suffix_info[1] }}" {% 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)
@ -70,24 +65,25 @@
</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>
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 %}>
<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 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"
@ -96,7 +92,6 @@
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>
@ -105,10 +100,9 @@
</form>
</div>
</div>
{% endblock %}
{% block script %}
<script>
$('.mailbox-select').multipleSelect();
@ -137,6 +131,5 @@
that.closest("form").submit();
})
</script>
</script>
{% endblock %}

View File

@ -1,24 +1,26 @@
{% extends 'default.html' %}
{% extends "default.html" %}
{% set active_page = "custom_domain" %}
{% block title %}
Custom Domains
{% endblock %}
{% block head %}
{% endblock %}
{% 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">
<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">
@ -26,86 +28,74 @@
</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>
<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">
<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">
<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>
Created {{ custom_domain.created_at | dt }}
<br />
<span class="font-weight-bold">{{ custom_domain.nb_alias() }}</span> aliases.
<br>
<br />
</h6>
<a href="{{ url_for('dashboard.domain_detail', custom_domain_id=custom_domain.id) }}" class="mt-3">
Details ➡
</a>
<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 %}
{% block script %}<script>$('.mailbox-select').multipleSelect();</script>{% endblock %}

View File

@ -1,11 +1,9 @@
{% extends 'default.html' %}
{% extends "default.html" %}
{% set active_page = "setting" %}
{% block title %}
Delete account
{% endblock %}
{% block title %}Delete account{% endblock %}
{% block default_content %}
<div class="card">
<div class="card-body">
<div class="h2">Account Deletion</div>
@ -13,7 +11,6 @@
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">
<span class="delete-account btn btn-outline-danger">Delete account</span>
@ -21,8 +18,8 @@
</div>
</div>
{% endblock %}
{% block script %}
<script>
$(".delete-account").on("click", function (e) {
let that = $(this);
@ -50,4 +47,4 @@
});
</script>
{% endblock %}
{% endblock %}

View File

@ -1,198 +1,176 @@
{% extends 'default.html' %}
{% extends "default.html" %}
{% set active_page = "directory" %}
{% block title %}
Directory
{% endblock %}
{% 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">
<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 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>
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>
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="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">
<input type="hidden" name="form-name" value="toggle-directory">
<input type="hidden" name="dir-id" 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 %}
>
<input type="checkbox" class="custom-switch-input" name="dir-status"
{{ "" if dir.disabled else "checked" }}>
<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">
<input type="hidden" name="form-name" value="update">
<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">
<input type="hidden" name="form-name" value="toggle-directory">
<input type="hidden" name="dir-id" 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>
<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 %}>
<input type="checkbox" class="custom-switch-input" name="dir-status" {{ "" if dir.disabled else "checked" }}>
<span class="custom-switch-indicator"></span>
</label>
</form>
</h6>
</div>
<div class="card-footer p-0">
<div class="row">
<div class="col">
<form method="post">
<input type="hidden" name="form-name" value="delete">
<input type="hidden" class="dir-name" value="{{ dir.name }}">
<input type="hidden" name="dir-id" value="{{ dir.id }}">
<span class="card-link btn btn-link float-right text-danger delete-dir">
Delete
</span>
</form>
</div>
</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">
<input type="hidden" name="form-name" value="update">
<input type="hidden" name="dir-id" 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">
<input type="hidden" name="form-name" value="delete">
<input type="hidden" class="dir-name" value="{{ dir.name }}">
<input type="hidden" name="dir-id" value="{{ dir.id }}">
<span class="card-link btn btn-link float-right text-danger delete-dir">Delete</span>
</form>
</div>
</div>
</div>
</div>
{% endfor %}
</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 %}
<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>
<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 = $(this).parent().find(".dir-name").val();

View File

@ -1,55 +1,46 @@
{% extends 'dashboard/domain_detail/base.html' %}
{% extends "dashboard/domain_detail/base.html" %}
{% set domain_detail_page = "auto_create" %}
{% block title %}
{{ custom_domain.domain }} Auto Create Rules
{% endblock %}
{% 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>
<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="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>
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>
<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>
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 }}">
@ -60,112 +51,90 @@
{% endfor %}
</div>
{% endif %}
<div class="mt-2" id="new-rule">
<hr>
<h3>New rule </h3>
<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.*"
) }}
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>
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>
<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">
<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 %}>
<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>
<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>
<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>
</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 %}">
<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 %}
{% block script %}<script>$('.mailbox-select').multipleSelect();</script>{% endblock %}

View File

@ -1,12 +1,13 @@
{% extends 'default.html' %}
{% 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">
@ -14,39 +15,31 @@
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 %}
{% block domain_detail_content %}{% endblock %}
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -1,136 +1,134 @@
{% extends 'dashboard/domain_detail/base.html' %}
{% extends "dashboard/domain_detail/base.html" %}
{% set domain_detail_page = "dns" %}
{% block title %}
{{ custom_domain.domain }} DNS
{% endblock %}
{% 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 class="">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>
<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
<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>
<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>
<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>
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>
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">
<input type="hidden" name="form-name" value="check-ownership">
<button type="submit" class="btn btn-primary"> Verify</button>
<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 %}
{% if not ownership_errors %}(Empty){% endif %}
{% for r in ownership_errors %}
{{ r }} <br>
{{ 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 %}
<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
<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>
<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>
<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>
<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>
<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>
Record: MX
<br />
Domain: {{ custom_domain.domain }} or
<b>@</b> <br>
Priority: {{ priority }} <br>
<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>
title="Click to copy"
class="clipboard"
data-clipboard-text="{{ email_server }}">{{ email_server }}</em>
</div>
{% endfor %}
<form method="post" action="#mx-form">
<input type="hidden" name="form-name" value="check-mx">
{% if custom_domain.verified %}
<button type="submit" class="btn btn-outline-primary">
Re-verify
</button>
<button type="submit" class="btn btn-outline-primary">Re-verify</button>
{% else %}
<button type="submit" class="btn btn-primary">
Verify
</button>
<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 %}
{% if not mx_errors %}(Empty){% endif %}
{% for r in mx_errors %}
{{ r }} <br>
{{ 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.
@ -139,33 +137,40 @@
</div>
{% endif %}
</div>
<hr>
<hr />
<div id="spf-form">
<div class="font-weight-bold">2. SPF (Optional)
<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>
<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>
<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
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>
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>
Record: TXT
<br />
Domain: {{ custom_domain.domain }} or
<b>@</b> <br>
<b>@</b>
<br />
Value:
<em data-toggle="tooltip"
title="Click to copy"
@ -174,92 +179,100 @@
{{ spf_record }}
</em>
</div>
<form method="post" action="#spf-form">
<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>
<button type="submit" class="btn btn-outline-primary">Re-verify</button>
{% else %}
<button type="submit" class="btn btn-primary">
Verify
</button>
<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 %}
{% if not spf_errors %}(Empty){% endif %}
{% for r in spf_errors %}
{{ r }} <br>
{{ 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>
<hr />
<div id="dkim-form">
<div class="font-weight-bold">3. DKIM (Optional)
<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>
<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>
<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>
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>
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 record to your domain.</div>
<div class="mb-2">
Add the following CNAME DNS record to your domain.
</div>
<div class="mb-2 p-3 dns-record">
Record: CNAME <br>
Record: CNAME
<br />
Domain: <em data-toggle="tooltip"
title="Click to copy"
class="clipboard"
data-clipboard-text="dkim._domainkey">dkim._domainkey</em> <br>
title="Click to copy"
class="clipboard"
data-clipboard-text="dkim._domainkey">dkim._domainkey</em>
<br />
Value:
<em data-toggle="tooltip"
title="Click to copy"
class="clipboard"
data-clipboard-text="{{ dkim_cname + '.' }}" style="overflow-wrap: break-word">
data-clipboard-text="{{ dkim_cname + '.' }}"
style="overflow-wrap: break-word">
{{ dkim_cname }}.
</em>
</div>
<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>
<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>
<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>
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">
<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>
@ -269,54 +282,67 @@
</button>
{% endif %}
</form>
{% if not dkim_ok %}
<div class="text-danger mt-4">
Your DNS is not correctly set.
{% if dkim_errors %}
The CNAME record we obtain for
<em>dkim._domainkey.{{ custom_domain.domain }}</em> is:
<div class="mb-3 p-3 dns-record">
{% for r in dkim_errors %}
{{ r }} <br>
{{ r }}
<br />
{% endfor %}
</div>
{% endif %}
{% if custom_domain.dkim_verified %}
Without DKIM setup, emails you sent from your alias might end up in Spam/Junk folder.
{% endif %}
</div>
{% endif %}
</div>
<hr>
<hr />
<div id="dmarc-form">
<div class="font-weight-bold">4. DMARC (Optional)
<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>
<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>
<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>
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">
Add the following TXT DNS record to your domain.
</div>
<div class="mb-2 p-3 dns-record">
Record: TXT <br>
Record: TXT
<br />
Domain: <em data-toggle="tooltip"
title="Click to copy"
class="clipboard"
data-clipboard-text="_dmarc">_dmarc</em> <br>
title="Click to copy"
class="clipboard"
data-clipboard-text="_dmarc">_dmarc</em>
<br />
Value:
<em data-toggle="tooltip"
title="Click to copy"
@ -325,18 +351,18 @@
{{ 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>
<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>
<br />
</div>
<form method="post" action="#dmarc-form">
<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>
@ -346,27 +372,26 @@
</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 %}
{% if not dmarc_errors %}(Empty){% endif %}
{% for r in dmarc_errors %}
{{ r }} <br>
{{ 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 %}

View File

@ -1,71 +1,59 @@
{% extends 'dashboard/domain_detail/base.html' %}
{% extends "dashboard/domain_detail/base.html" %}
{% set domain_detail_page = "info" %}
{% block title %}
{{ custom_domain.domain }} Info
{% endblock %}
{% block title %}{{ custom_domain.domain }} Info{% endblock %}
{% block domain_detail_content %}
<h1 class="h2 mb-1"> {{ custom_domain.domain }} </h1>
<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>
<hr />
<h3 class="mb-1">Auto create/on the fly alias</h3>
<div>
<form method="post">
<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 "" }}>
<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
Catch All
</spam>
</label>
</form>
<div class="">
<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
<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>.
<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></a>.
<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">
<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">
<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 %}>
<option value="{{ mailbox.id }}"
{% if mailbox in domain_mailboxes %} selected{% endif %}>
{{ mailbox.email }}
</option>
{% endfor %}
@ -77,15 +65,12 @@
</div>
</form>
</div>
<hr>
<hr />
<h3 class="mb-1">Default Display Name</h3>
<div class="">
<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">
<input type="hidden" name="form-name" value="set-name">
@ -97,47 +82,40 @@
</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>
<button class="btn btn-outline-danger float-right ml-2"
name="action"
value="remove">Remove</button>
{% endif %}
</form>
</div>
<hr>
<hr />
<h3 class="mb-1">Random Prefix Generation</h3>
<div class="">
Add a random prefix for this domain when creating a new alias.
</div>
<div>Add a random prefix for this domain when creating a new alias.</div>
<div>
<form method="post">
<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 "" }}>
<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>
<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.
@ -155,20 +133,13 @@
</div>
{% endif %}
</div>
<form method="post">
<input type="hidden" name="form-name" value="delete">
<span
class="delete-custom-domain btn btn-danger">
Delete {{ custom_domain.domain }}
</span>
<span class="delete-custom-domain btn btn-danger">Delete {{ custom_domain.domain }}</span>
</form>
{% endblock %}
{% block script %}
<script>
$('.mailbox-select').multipleSelect();
@ -180,7 +151,7 @@
let that = $(this);
bootbox.confirm({
message: "All aliases associated with <b>{{ custom_domain.domain }}</b> will be also deleted. <br>" +
message: "All aliases associated with <b>{{ custom_domain.domain }}</b> will be also deleted. <br />" +
"This operation is not reversible, please confirm.",
buttons: {
confirm: {
@ -201,4 +172,3 @@
});
</script>
{% endblock %}

View File

@ -1,27 +1,30 @@
{% extends 'dashboard/domain_detail/base.html' %}
{% extends "dashboard/domain_detail/base.html" %}
{% set domain_detail_page = "trash" %}
{% block title %}
{{ custom_domain.domain }} deleted aliases
{% endblock %}
{% 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">
<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>
<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">
<input type="hidden" name="form-name" value="empty-all">
<button class="btn btn-outline-danger">Empty Trash</button>
@ -33,10 +36,9 @@
{% else %}
There's no deleted alias recorded for this domain.
{% endif %}
{% for deleted_alias in domain_deleted_aliases %}
<hr>
<hr />
<b>{{ deleted_alias.email }}</b> -
deleted {{ deleted_alias.created_at | dt }}
<form method="post">
@ -45,6 +47,4 @@
<button class="btn btn-sm btn-outline-warning">Remove from trash</button>
</form>
{% endfor %}
{% endblock %}

View File

@ -1,11 +1,9 @@
{% extends 'default.html' %}
{% extends "default.html" %}
{% set active_page = "setting" %}
{% block title %}
SUDO MODE
{% endblock %}
{% block title %}SUDO MODE{% endblock %}
{% block default_content %}
<div class="card">
<div class="card-body">
<h1 class="h2">Entering Sudo Mode</h1>
@ -15,17 +13,13 @@
<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>
</div>
</div>
{% endblock %}
{% endblock %}

View File

@ -1,36 +1,26 @@
{% extends 'default.html' %}
{% extends "default.html" %}
{% set active_page = "dashboard" %}
{% block title %}
Extend Subscription
{% endblock %}
{% 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>
<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>
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 %}

View File

@ -1,24 +1,23 @@
{% extends 'default.html' %}
{% set active_page = "setting" %}
{% block title %}
Manage Security Key
{% endblock %}
{% 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>
<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>
@ -29,25 +28,32 @@
</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%}
{% 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>
<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 %}

View File

@ -1,33 +1,32 @@
{% extends 'default.html' %}
{% set active_page = "setting" %}
{% block title %}
Security Key Setup
{% endblock %}
{% 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>
<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>
<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) {
@ -71,7 +70,6 @@
}
</script>
</div>
</div>
{% endblock %}

File diff suppressed because it is too large Load Diff

View File

@ -1,28 +1,22 @@
{% extends 'default.html' %}
{% extends "default.html" %}
{% set active_page = "dashboard" %}
{% block title %}
Lifetime Licence
{% endblock %}
{% 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>
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 %}
{% endblock %}

View File

@ -1,131 +1,130 @@
{% extends 'default.html' %}
{% extends "default.html" %}
{% set active_page = "mailbox" %}
{% block title %}
Mailboxes
{% endblock %}
{% 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">
<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>
<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">
<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>
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>
<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>
<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>
<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 %}
<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 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>
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">
<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 %}"
>
<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">
<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>
<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>
<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();
@ -154,4 +153,4 @@
})
});
</script>
{% endblock %}
{% endblock %}

View File

@ -1,12 +1,9 @@
{% extends 'default.html' %}
{% extends "default.html" %}
{% set active_page = "mailbox" %}
{% block title %}
Mailbox {{ mailbox.email }}
{% endblock %}
{% block title %}Mailbox {{ mailbox.email }}{% endblock %}
{% block head %}
<style>
div[disabled]
{
@ -15,51 +12,51 @@
}
</style>
{% endblock %}
{% block default_content %}
<div class="row">
<div class="col">
<h1 class="h3">{{ mailbox.email }}
<h1 class="h3">
{{ mailbox.email }}
{% if mailbox.verified %}
<span class="cursor" data-toggle="tooltip" data-original-title="Mailbox Verified"></span>
<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>
<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>
<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>
<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="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) }}"
@ -74,202 +71,163 @@
</form>
</div>
<!-- END Change email -->
<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">
<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" }}>
<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 %}
<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 %}
<div class="alert alert-danger" role="alert">This feature is only available in premium plan.</div>
{% endif %}
<form method="post">
<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>
<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 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 %}>
<div class="card" {% if not mailbox.pgp_enabled() %}
disabled {% endif %}>
<form method="post">
<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>
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="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">
<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>
<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>
<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>
<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>
{% endif %}
<hr />
<h2 class="h4">Advanced Options</h2>
{% if spf_available %}
<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 class="card" id="spf">
<form method="post">
<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>
{% if mailbox.authorized_addresses | length == 0 %}
{% 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">
<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 %}
{% else %}
<ul>
{% for authorized_address in mailbox.authorized_addresses %}
<form method="post" action="#authorized-address" class="form-inline">
<input type="hidden" name="form-name" value="add-authorized-address">
<input type="email" name="email" size="50" class="form-control">
<input type="submit" class="btn btn-primary" value="Add">
</form>
<li>
{{ authorized_address.email }}
<form method="post" action="#authorized-address" style="display: inline">
<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">
<input type="hidden" name="form-name" value="add-authorized-address">
<input type="email" name="email" size="50" class="form-control">
<input type="submit" class="btn btn-primary" value="Add">
</form>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block script %}
<script src="/static/js/utils/drag-drop-into-text.js"></script>
<script>
{% 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>
</script>
{% endblock %}

View File

@ -1,19 +1,14 @@
{% extends "base.html" %}
{% block title %}
Mailbox Validation
{% endblock %}
{% 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 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 %}
{% endblock %}

View File

@ -1,24 +1,19 @@
{% extends 'default.html' %}
{% extends "default.html" %}
{% set active_page = "setting" %}
{% block title %}
Cancel MFA
{% endblock %}
{% 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 %}

View File

@ -1,22 +1,20 @@
{% extends 'default.html' %}
{% set active_page = "setting" %}
{% block title %}
MFA Setup
{% endblock %}
{% 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>
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({
@ -25,25 +23,16 @@
});
})();
</script>
<div class="mt-3 mb-2">
Or you can manually enter the following secret key:
</div>
<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 %}

View File

@ -1,44 +1,38 @@
{% extends 'default.html' %}
{% block title %}
API Key
{% endblock %}
{% 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>
<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="**********">
<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>
<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"
<button class="clipboard btn btn-primary"
data-clipboard-action="copy"
data-clipboard-text="{{ api_key.code }}"
data-clipboard-target="#apikey-{{ api_key.id }}">
Copy &nbsp; &nbsp; <i class="fe fe-clipboard"></i>
</button>
</div>
</div>
{% endblock %}
{% block script %}
<script>
<script>
$(".toggle-api-key").on('click', function (event) {
let that = $(this);
let apiInput = that.parent().parent().parent().find("input");

View File

@ -1,29 +1,18 @@
{% extends 'default.html' %}
{% extends "default.html" %}
{% set active_page = "dashboard" %}
{% block title %}
{{ notification.title }}
{% endblock %}
{% 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"
<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 %}

View File

@ -1,42 +1,36 @@
{% extends 'default.html' %}
{% extends "default.html" %}
{% set active_page = "dashboard" %}
{% block title %}
Notifications
{% endblock %}
{% block title %}Notifications{% endblock %}
{% block default_content %}
<div class="row">
<div class="col">
<h1 class="h3"> Notifications </h1>
<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;">
<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>
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">
@ -44,21 +38,20 @@
<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>
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>
Next
</a>
</li>
</ul>
</nav>
</div>
</div>
{% endif %}
</div>
</div>
{% endblock %}

View File

@ -1,21 +1,17 @@
{% extends 'default.html' %}
{% extends "default.html" %}
{% set active_page = "dashboard" %}
{% block title %}
Pricing
{% endblock %}
{% 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">
</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;
}
@ -29,175 +25,177 @@
[data-toggle="collapse"]:not(.collapsed) .if-collapsed {
display: none;
}
</style>
</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>#}
{# 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="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="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="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 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 %}
<div id="proton-upgrade">
<h4>Proton Unlimited and Mail Plus 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 $3.99/month (billed yearly), starting with 15GB of storage, 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 %}
{% if proton_upgrade %}
<div id="proton-upgrade" class="">
<h4 class="">Proton Unlimited and Mail Plus 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 $3.99/month (billed yearly), starting with 15GB of storage, encrypted
calendar & file storage and more. </p>
<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="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">
<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>
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,
@ -218,6 +216,5 @@
});
}
</script>
</script>
{% endblock %}

View File

@ -1,10 +1,9 @@
{% extends 'default.html' %}
{% set active_page = "setting" %}
{% block title %}
Recovery Codes
{% endblock %}
{% 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>
@ -13,34 +12,29 @@
your account again.
Each code can only be used once, make sure to store them in a safe place.
</p>
<ul>
{% for recovery_code in recovery_codes %}
{% if recovery_code.used %}
<li>
<span style="text-decoration: line-through">{{ recovery_code.code }}</span>.
Used {{ recovery_code.used_at | dt }}.
</li>
{% else %}
<li>
{{ recovery_code.code }}
</li>
<li>{{ recovery_code.code }}</li>
{% endif %}
{% endfor %}
</ul>
<form method="post" class="mt-6">
<input type="submit" class="btn btn-outline-primary" value="Generate New Codes">
<input type="submit"
class="btn btn-outline-primary"
value="Generate New Codes">
</form>
<div class="small-text">
Warning: Generating new codes will invalidate the older ones.
</div>
<hr>
<a href="{{ url_for('dashboard.index') }}" class="btn btn-primary btn-lg"> Back to the home page </a>
<div class="small-text">Warning: Generating new codes will invalidate the older ones.</div>
<hr />
<a href="{{ url_for('dashboard.index') }}"
class="btn btn-primary btn-lg">Back to the home page</a>
</div>
</div>
{% endblock %}

View File

@ -1,76 +1,92 @@
{% extends 'default.html' %}
{% block title %}
Referral
{% endblock %}
{% 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">
<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>
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.
<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>
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 %}">
<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 '' }}">
<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>
{% 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 %}
{% 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>
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
<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"
<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>
@ -78,19 +94,19 @@
</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>
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">
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">
@ -98,15 +114,13 @@
<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"
<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.">
@ -115,47 +129,45 @@
dashes (-) and underscores (_) are currently supported.
</div>
</div>
<div class="form-group">
<input name="name" class="form-control" required
<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>
<th>Sent at</th>
<th>Amount</th>
<th>Payment Method</th>
<th>Number of upgraded accounts</th>
</tr>
{% endfor %}
</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);
@ -182,4 +194,4 @@
});
</script>
{% endblock %}
{% endblock %}

View File

@ -1,69 +1,55 @@
{% extends 'default.html' %}
{% block title %}
Quarantine
{% endblock %}
{% extends "default.html" %}
{% block title %}Quarantine{% endblock %}
{% set active_page = "setting" %}
{% block default_content %}
<div class="col">
<h1 class="h3 mb-5"> Quarantine </h1>
<div class="col">
<h1 class="h3 mb-5">Quarantine</h1>
<div class="alert alert-info">
This page shows all emails that are potentially spams or malicious.
Usually these emails have been <b>refused</b> (or bounced) by your mailbox. <br>
Usually these emails have been <b>refused</b> (or bounced) by your mailbox.
<br />
- If an email is indeed spam, this means the alias is now in the hands of a spammer,
in this case you should <b>disable</b> this alias. <br>
- Otherwise, you should create a <b>filter</b> to avoid your email provider from blocking these emails. <br>
<a href="mailto:hi@simplelogin.io">Contact us↗</a> if you need any help.
in this case you should <b>disable</b> this alias.
<br />
- Otherwise, you should create a <b>filter</b> to avoid your email provider from blocking these emails.
<br />
<a href="mailto:hi@simplelogin.io">Contact us↗</a>
if you need any help.
</div>
{% if email_logs|length == 0 %}
<div class="my-4 p-4 card">
You don't have any emails in Quarantine.
</div>
{% endif %}
{% if email_logs|length == 0 %}<div class="my-4 p-4 card">You don't have any emails in Quarantine.</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 }}
</div>
<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 }}</div>
{% if email_log.is_reply %}
From: {{ alias.email }} <br>
From: {{ alias.email }}
<br />
To: {{ contact.website_email }}
{% else %}
From: {{ contact.website_email }} <br>
From: {{ contact.website_email }}
<br />
<span>
To: {{ alias.email }}
<a href='{{ url_for("dashboard.index", highlight_alias_id=alias.id) }}'
class="btn btn-sm btn-outline-danger">Disable Alias</a>
</span>
To: {{ alias.email }}
<a href='{{ url_for("dashboard.index", highlight_alias_id=alias.id) }}'
class="btn btn-sm btn-outline-danger">
Disable Alias
</a>
</span>
{% endif %}
{% if refused_email.deleted %}
<div>
Email deleted {{ refused_email.delete_at | dt }}
</div>
<div>Email deleted {{ refused_email.delete_at | dt }}</div>
{% else %}
<a href="{{ refused_email.get_url() }}" download
class="mt-4">Download →</a>
<a href="{{ refused_email.get_url() }}" download class="mt-4">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 %}
{% endblock %}

File diff suppressed because it is too large Load Diff

View File

@ -1,78 +1,78 @@
{% extends 'default.html' %}
{% extends "default.html" %}
{% set active_page = "subdomain" %}
{% block title %}
Subdomains
{% endblock %}
{% block head %}
{% endblock %}
{% 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">
<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">
<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>
<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>
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>
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>
<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>
Created {{ subdomain.created_at | dt }}
<br />
<span class="font-weight-bold">{{ subdomain.nb_alias() }}</span> aliases.
<br>
<br />
</h6>
<a href="{{ url_for('dashboard.domain_detail', custom_domain_id=subdomain.id) }}" class="mt-3">
Details ➡
</a>
<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="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"
@ -80,31 +80,31 @@
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">
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>
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>
@ -112,14 +112,11 @@
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block script %}
<script>
new Vue({
el: '#new-subdomain',

View File

@ -1,55 +1,63 @@
{% extends 'default.html' %}
{% extends "default.html" %}
{% set active_page = 'dashboard' %}
{% block title %}
Support
{% endblock %}
{% 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="/dashboard/notifications">your
notifications</a> for error messages. <br>
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
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
<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">
<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="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">
<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">
<button class="btn btn-outline-primary"
type="button"
@click="generateRandomAlias"
id="button-addon2">
Generate a random alias
</button>
</div>
@ -58,15 +66,12 @@
<button class="btn btn-primary">Create support ticket</button>
</div>
</form>
</div>
</div>
</div>
{% endblock %}
{% block script %}
<script>
new Vue({
el: '#alias-group',
@ -95,6 +100,5 @@
}
$(this).next('.custom-file-label').html(files.join(', '));
});
</script>
</script>
{% endblock %}

View File

@ -1,30 +1,22 @@
{% extends 'default.html' %}
{% extends "default.html" %}
{% set active_page = "dashboard" %}
{% block title %}
Block an alias
{% endblock %}
{% block title %}Block an alias{% endblock %}
{% block default_content %}
<div class="card">
<div class="card-body">
<h1 class="h3">
Block alias
</h1>
<h1 class="h3">Block alias</h1>
<p>
You are about to block the alias <a href="mailto:{{ alias }}">{{ alias }}</a>
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 %}

View File

@ -1,18 +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 %}
{% block default_content %}{% endblock %}
</div>
</div>
</div>
{% include "footer.html" %}
{% endblock %}
{% endblock %}

View File

@ -1,13 +1,14 @@
{% extends 'developer/client_details/base.html' %}
{% 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>
<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 %}
{% endblock %}

View File

@ -1,64 +1,50 @@
{% extends 'default.html' %}
{% extends "default.html" %}
{% set active_page = "developer" %}
{% block title %}
Developer - App {{ client.name }}
{% endblock %}
{% 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_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"
<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 %}
{% block client_details_content %}{% endblock %}
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -1,83 +1,78 @@
{% extends 'developer/client_details/base.html' %}
{% 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">
<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="http://docs.simplelogin.io" target="_blank" rel="noopener">
documentation <i class="fe fe-external-link"></i></a>
<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"
<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
<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 %}
{% 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 %}
{# <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 %}

View File

@ -1,68 +1,71 @@
{% extends 'developer/client_details/base.html' %}
{% extends "developer/client_details/base.html" %}
{% set client_details_page = "oauth_endpoint" %}
{% block client_details_content %}
<h1 class="h2">OAuth2 endpoints</h1>
<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">
<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>
<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">
<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>
<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">
<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>
<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">
<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>
<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 %}

View File

@ -1,54 +1,59 @@
{% extends 'developer/client_details/base.html' %}
{% 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">
<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">
<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">
<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">
<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>
<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>
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">
<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>,
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>
@ -66,49 +71,47 @@
</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">
<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:\/\/.*"
<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
@ -124,4 +127,4 @@
});
</script>
{% endblock %}
{% endblock %}

View File

@ -1,11 +1,13 @@
{% extends 'developer/client_details/base.html' %}
{% extends "developer/client_details/base.html" %}
{% set client_details_page = "referral" %}
{% block client_details_content %}
<h1 class="h2">Referral</h1>
<div class="">
If you are in the <a href="{{ url_for('dashboard.referral_route') }}">referral</a> program, you can attach a
<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>
@ -14,15 +16,20 @@
<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>
{% 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>
<option value="" selected>
No referral selected
</option>
{% endif %}
</select>
</div>
<input type="submit" class="btn btn-primary" value="Update">
</form>
{% endblock %}
{% endblock %}

View File

@ -1,92 +1,91 @@
{% extends 'default.html' %}
{% extends "default.html" %}
{% set active_page = "developer" %}
{% block title %}
Sign in with SimpleLogin
{% endblock %}
{% 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">
<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">
<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>
<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">
<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">
<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">
<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"
<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>
<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>
Created {{ client.created_at | dt }}
<br />
<span class="font-weight-bold">{{ client.nb_user() }}</span> users
<br>
<br />
</h6>
<a href="{{ url_for('developer.client_detail', client_id=client.id) }}" class="mt-3">
Details ➡
</a>
<a href="{{ url_for('developer.client_detail', client_id=client.id) }}"
class="mt-3">Details ➡</a>
</div>
</div>
</div>
{% endfor %}
</div>
{% endblock %}

View File

@ -1,34 +1,26 @@
{% extends 'default.html' %}
{% extends "default.html" %}
{% set active_page = "developer" %}
{% block title %}
Developer - Create new website
{% endblock %}
{% 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 %}

View File

@ -1,41 +1,29 @@
{% extends 'default.html' %}
{% extends "default.html" %}
{% set active_page = "discover" %}
{% block title %}
Discover
{% endblock %}
{% 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>
<h4>
<a href="{{ client.home_url }}">{{ client.name }}</a>
</h4>
<div class="text-muted">{{ client.home_url }}</div>
</div>
</div>
</div>
{% endfor %}
</div>
{% endblock %}

View File

@ -1,26 +1,67 @@
{% macro render_text(text) %}
<p style="font-size: 16px; line-height: 1.625; color: #51545E; margin: .4em 0 1.1875em;">{{ text }}</p>
<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>
<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;">
<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;">
<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">
<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;">
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>
@ -30,44 +71,71 @@ https://litmus.com/blog/a-guide-to-bulletproof-buttons-in-email-design -->
</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;">
<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 youre having trouble with the button above, copy and paste the URL below into your web browser.</p>
<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;">{{ link }}</p>
style="font-size: 13px;
line-height: 1.625;
color: #51545E;
margin: .4em 0 1.1875em;">
If youre 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 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 %}
</table>
</td>
</tr>
</table>
{% endmacro %}
<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 %}

View File

@ -1,13 +1,11 @@
{% from "_emailhelpers.html" import render_text, text, render_button, raw_url, grey_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">
<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" />
<title></title>
<style type="text/css" rel="stylesheet" media="all">
<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;
@ -414,15 +412,15 @@
text-shadow: none !important;
}
}
</style>
<!--[if mso]>
</style>
<!--[if mso]>
<style type="text/css">
.f-fallback {
font-family: Arial, sans-serif;
}
</style>
<![endif]-->
<style type="text/css" rel="stylesheet" media="all">
<style type="text/css" rel="stylesheet" media="all">
body {
width: 100% !important;
height: 100%;
@ -438,59 +436,173 @@
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. All rights reserved.
<br />
{% block footer %}
{% endblock %}
</p>
<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>
</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. All rights reserved.
<br />
{% block footer %}{% endblock %}
</p>
<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>

View File

@ -1,35 +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!") }}
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 Protonmail">
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 Protonmail">
{{ 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("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>
<a href="{{ URL }}/dashboard/setting#notification">Settings</a>
{% endblock %}

View File

@ -1,114 +1,125 @@
{% 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="http://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/simple_login">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 %}
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/simple_login">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>
<a href="{{ URL }}/dashboard/setting#notification">Settings</a>
{% endblock %}

View File

@ -1,40 +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%">
<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%">
<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('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>
<a href="{{ URL }}/dashboard/setting#notification">Settings</a>
{% endblock %}

View File

@ -1,26 +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">
<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>
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('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 %}

View File

@ -1,34 +1,32 @@
{% 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%;">
<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 %}

View File

@ -1,38 +1,33 @@
{% 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 ProtonMail,
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") }}
<h1>Add other mailboxes to SimpleLogin.</h1>
{% endcall %}
{% call text() %}
If you have several email inboxes, say Gmail and ProtonMail,
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 %}

View File

@ -1,35 +1,33 @@
{% 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) }}
<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 %}

View File

@ -1,48 +1,45 @@
{% 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 %}
<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 %}

View File

@ -1,57 +1,55 @@
{% extends "base.html" %}
{% block logo %}<img src="{{ URL }}/static/logo-sl-proton.svg" style="width: 150px; margin: auto">{% endblock %}
{% block logo %}<img src="{{ URL }}/static/logo-sl-proton.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 %}
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 %}

View File

@ -1,65 +1,76 @@
{% extends "base.html" %}
{% block greeting %}
<h1 style="margin-top: 0; color: #333333; font-size: 22px; font-weight: bold; text-align: left;" align="left">
<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 %}
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() %}
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 15 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/simple_login">Twitter</a>.
{% endcall %}
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 15 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/simple_login">Twitter</a>
.
{% endcall %}
{% endblock %}

View File

@ -1,15 +1,14 @@
{% 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.') }}
<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 %}

View File

@ -1,10 +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.') }}
{{ render_text('Thanks,
<br />
SimpleLogin Team.') }}
{{ raw_url(activation_link) }}
{% endblock %}

View File

@ -1,21 +1,16 @@
{% 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 %}
<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 %}

View File

@ -1,25 +1,21 @@
{% 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) }}
<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 %}

View File

@ -1,30 +1,25 @@
{% 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 %}
<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 %}

View File

@ -1,40 +1,41 @@
{% extends "base.html" %}
{% block content %}
{% call text() %}
<h1>
An email sent to your alias {{ alias.email }} from {{ website_email }} was <b>bounced</b> by your mailbox
{{ mailbox_email }}
</h1>
{% endcall %}
{% call text() %}
This is usually because your mailbox service thinks the email is <b>spam</b>.
{% endcall %}
{{ render_button("View the bounced email", refused_email_url) }}
{{ render_text('The email is automatically deleted in 7 days.') }}
{% call text() %}
Please consider the following options: <br>
<ol>
<li>If the email is not spam, you can create a
<a href="https://simplelogin.io/docs/getting-started/troubleshooting/">filter</a>
to explicitly allow all emails from SimpleLogin. <br>
</li>
<li>
If this email is indeed spam, it means your alias {{ alias.email }} is now in the hands of a spammer.
You can either <a href="{{ disable_alias_link }}">disable the alias</a>
or <a href="{{ block_sender_link }}">block the sender</a> if they send too many spams.
</li>
</ol>
<br>
Please note that the alias can be automatically disabled if too many emails sent to it are bounced.
{% endcall %}
{{ render_text('Thanks, <br />SimpleLogin Team.') }}
<h1>
An email sent to your alias {{ alias.email }} from {{ website_email }} was <b>bounced</b> by your mailbox
{{ mailbox_email }}
</h1>
{% endcall %}
{% call text() %}
This is usually because your mailbox service thinks the email is <b>spam</b>.
{% endcall %}
{{ render_button("View the bounced email", refused_email_url) }}
{{ render_text('The email is automatically deleted in 7 days.') }}
{% call text() %}
Please consider the following options:
<br />
<ol>
<li>
If the email is not spam, you can create a
<a href="https://simplelogin.io/docs/getting-started/troubleshooting/">filter</a>
to explicitly allow all emails from SimpleLogin.
<br />
</li>
<li>
If this email is indeed spam, it means your alias {{ alias.email }} is now in the hands of a spammer.
You can either
<a href="{{ disable_alias_link }}">disable the alias</a>
or
<a href="{{ block_sender_link }}">block the sender</a>
if they send too many spams.
</li>
</ol>
<br />
Please note that the alias can be automatically disabled if too many emails sent to it are bounced.
{% endcall %}
{{ render_text('Thanks,
<br />
SimpleLogin Team.') }}
{% endblock %}

View File

@ -1,24 +1,20 @@
{% extends "base.html" %}
{% block content %}
{% call text() %}
<h1>
Cannot create {{ alias }} on-the-fly
</h1>
{% endcall %}
{% call text() %}
An email has been sent to the alias <b>{{ alias }}</b> that would be created automatically as you own the directory <b>{{ directory }}</b>
{% endcall %}
{% call text() %}
As <b>on-the-fly alias creation is disabled</b> on this directory, the alias isn't created.
{% endcall %}
{% call text() %}
If you aren't aware of this alias, that probably means someone has discovered about your directory and is abusing it.
{% endcall %}
{{ render_text('Thanks, <br />SimpleLogin Team.') }}
<h1>Cannot create {{ alias }} on-the-fly</h1>
{% endcall %}
{% call text() %}
An email has been sent to the alias <b>{{ alias }}</b> that would be created automatically as you own the directory <b>{{ directory }}</b>
{% endcall %}
{% call text() %}
As <b>on-the-fly alias creation is disabled</b> on this directory, the alias isn't created.
{% endcall %}
{% call text() %}
If you aren't aware of this alias, that probably means someone has discovered about your directory and is abusing it.
{% endcall %}
{{ render_text('Thanks,
<br />
SimpleLogin Team.') }}
{% endblock %}

View File

@ -1,17 +1,14 @@
{% extends "base.html" %}
{% block content %}
{% call text() %}
<h1>
Cannot create alias {{ alias }} on-the-fly
</h1>
{% endcall %}
{{ render_text("An email has been sent to the alias <b>" + alias + "</b> that would be created automatically as you own the directory <b>" + directory + "</b>.") }}
{{ render_text("However you have reached the alias limit in your current plan, this creation cannot happen.") }}
{{ render_text('Please upgrade to premium plan in order to use this feature.') }}
{{ render_text('Thanks, <br />SimpleLogin Team.') }}
<h1>Cannot create alias {{ alias }} on-the-fly</h1>
{% endcall %}
{{ render_text("An email has been sent to the alias <b>" + alias + "</b> that would be created automatically as you own the directory <b>" + directory + "</b>.") }}
{{ render_text("However you have reached the alias limit in your current plan, this creation cannot happen.") }}
{{ render_text('Please upgrade to premium plan in order to use this feature.') }}
{{ render_text('Thanks,
<br />
SimpleLogin Team.') }}
{% endblock %}

View File

@ -1,17 +1,14 @@
{% extends "base.html" %}
{% block content %}
{% call text() %}
<h1>
Cannot create {{ alias }} on-the-fly
</h1>
{% endcall %}
{{ render_text("An email has been sent to the alias <b>" + alias + "</b> that would be created automatically as you own the domain <b>" + domain + "</b>.") }}
{{ render_text("However you have reached the alias limit in your current plan, this creation cannot happen.") }}
{{ render_text('Please upgrade to premium plan in order to use this feature.') }}
{{ render_text('Thanks, <br />SimpleLogin Team.') }}
<h1>Cannot create {{ alias }} on-the-fly</h1>
{% endcall %}
{{ render_text("An email has been sent to the alias <b>" + alias + "</b> that would be created automatically as you own the domain <b>" + domain + "</b>.") }}
{{ render_text("However you have reached the alias limit in your current plan, this creation cannot happen.") }}
{{ render_text('Please upgrade to premium plan in order to use this feature.') }}
{{ render_text('Thanks,
<br />
SimpleLogin Team.') }}
{% endblock %}

View File

@ -1,11 +1,13 @@
{% extends "base.html" %}
{% block content %}
{{ render_text("You recently requested to change your email on SimpleLogin to <b>"+ new_email +"</b>.") }}
{{ render_text("Your current email is " + current_email + ".") }}
{{ render_text("Use the button below to confirm within the next 12 hours.") }}
{{ render_button("Change email", link) }}
{{ render_text('Thanks, <br />SimpleLogin Team.') }}
{{ render_text('Thanks,
<br />
SimpleLogin Team.') }}
{{ raw_url(link) }}
{% endblock %}

View File

@ -1,10 +1,14 @@
{% extends "base.html" %}
{% block content %}
{{ render_text("Hi") }}
{{ render_text("Thank you for choosing SimpleLogin.") }}
{{ render_text("To get started, please activate your account by entering the following code into the application:") }}
{{ render_text("<h1>" + code + "</h1>")}}
{{ render_text('Thanks, <br />SimpleLogin Team.') }}
{{ render_text("
<h1>" + code + "</h1>
")}}
{{ render_text('Thanks,
<br />
SimpleLogin Team.') }}
{% endblock %}

View File

@ -1,23 +1,20 @@
{% extends "base.html" %}
{% block content %}
{% call text() %}
<h1>
Your subscription has been extended!
</h1>
{% endcall %}
{% call text() %}
Your payment with cryptocurrency has been successfully processed. <br>
Your subscription has been extended to
<b>{{ coinbase_subscription.end_at.format("YYYY-MM-DD") }}</b>
{% endcall %}
{% call text() %}
Thank you a lot for your support!
{% endcall %}
{{ render_text('Best, <br />SimpleLogin Team.') }}
<h1>Your subscription has been extended!</h1>
{% endcall %}
{% call text() %}
Your payment with cryptocurrency has been successfully processed.
<br />
Your subscription has been extended to
<b>{{ coinbase_subscription.end_at.format("YYYY-MM-DD") }}</b>
{% endcall %}
{% call text() %}
Thank you a lot for your support!
{% endcall %}
{{ render_text('Best,
<br />
SimpleLogin Team.') }}
{% endblock %}

View File

@ -1,22 +1,20 @@
{% extends "base.html" %}
{% block content %}
{% call text() %}
<h1>
Your account has been upgraded!
</h1>
{% endcall %}
{% call text() %}
Your payment with cryptocurrency has been successfully processed. <br>
Your account has been upgraded to the premium plan until
<b>{{ coinbase_subscription.end_at.format("YYYY-MM-DD") }}</b>
{% endcall %}
{% call text() %}
Thank you a lot for your support!
{% endcall %}
{{ render_text('Best, <br />SimpleLogin Team.') }}
<h1>Your account has been upgraded!</h1>
{% endcall %}
{% call text() %}
Your payment with cryptocurrency has been successfully processed.
<br />
Your account has been upgraded to the premium plan until
<b>{{ coinbase_subscription.end_at.format("YYYY-MM-DD") }}</b>
{% endcall %}
{% call text() %}
Thank you a lot for your support!
{% endcall %}
{{ render_text('Best,
<br />
SimpleLogin Team.') }}
{% endblock %}

View File

@ -1,18 +1,16 @@
{% extends "base.html" %}
{% block content %}
{% call text() %}
<h1>
Your subscription is ending soon.
</h1>
{% endcall %}
{% call text() %}
Your subscription ends on
<b>{{ coinbase_subscription.end_at.format("YYYY-MM-DD") }}</b>
{% endcall %}
{{ render_button("Extend your subscription", extend_subscription_url) }}
{{ render_text('Best, <br />SimpleLogin Team.') }}
<h1>Your subscription is ending soon.</h1>
{% endcall %}
{% call text() %}
Your subscription ends on
<b>{{ coinbase_subscription.end_at.format("YYYY-MM-DD") }}</b>
{% endcall %}
{{ render_button("Extend your subscription", extend_subscription_url) }}
{{ render_text('Best,
<br />
SimpleLogin Team.') }}
{% endblock %}

View File

@ -1,23 +1,23 @@
{% extends "base.html" %}
{% block content %}
{% call text() %}
<h1>
An email was sent to your alias <b>{{ alias.email }}</b> from its own mailbox
<b>{{ from_addr }}</b>.
</h1>
{% endcall %}
{% call text() %}
SimpleLogin doesn't send this email <b>back</b> to your mailbox as it would be refused or hidden anyway by your email service. <br>
So no worries, there's nothing you need to do :).
{% endcall %}
{{ render_button("View the email", refused_email_url) }}
{% call text() %}
The email is automatically deleted in 7 days.
{% endcall %}
{{ render_text('Thanks, <br />SimpleLogin Team.') }}
<h1>
An email was sent to your alias <b>{{ alias.email }}</b> from its own mailbox
<b>{{ from_addr }}</b>.
</h1>
{% endcall %}
{% call text() %}
SimpleLogin doesn't send this email <b>back</b> to your mailbox as it would be refused or hidden anyway by your email service.
<br />
So no worries, there's nothing you need to do :).
{% endcall %}
{{ render_button("View the email", refused_email_url) }}
{% call text() %}
The email is automatically deleted in 7 days.
{% endcall %}
{{ render_text('Thanks,
<br />
SimpleLogin Team.') }}
{% endblock %}

View File

@ -1,31 +1,24 @@
{% extends "base.html" %}
{% block content %}
{% call text() %}
<h1>
Issues with {{ mailbox.email }}.
</h1>
{% endcall %}
{% call text() %}
We have detected that your mailbox can't reliably receive emails from SimpleLogin for a consecutive period of time. <br>
The mailbox will be disabled in 5 days if the problem isn't solved by then.
{% endcall %}
{% call text() %}
Please note that a mailbox can't be a disposable or forwarding email address.
{% endcall %}
{% call text() %}
If you have any question, you can reach out to us by replying to this email.
{% endcall %}
{% call text() %}
Best, <br/>
SimpleLogin Team.
{% endcall %}
<h1>Issues with {{ mailbox.email }}.</h1>
{% endcall %}
{% call text() %}
We have detected that your mailbox can't reliably receive emails from SimpleLogin for a consecutive period of time.
<br />
The mailbox will be disabled in 5 days if the problem isn't solved by then.
{% endcall %}
{% call text() %}
Please note that a mailbox can't be a disposable or forwarding email address.
{% endcall %}
{% call text() %}
If you have any question, you can reach out to us by replying to this email.
{% endcall %}
{% call text() %}
Best,
<br />
SimpleLogin Team.
{% endcall %}
{% endblock %}

View File

@ -1,31 +1,24 @@
{% extends "base.html" %}
{% block content %}
{% call text() %}
<h1>
{{ mailbox.email }} is disabled.
</h1>
{% endcall %}
{% call text() %}
We have detected that your mailbox can't reliably receive emails from SimpleLogin for a consecutive period of time. <br>
The mailbox has been therefore disabled.
{% endcall %}
{% call text() %}
Please note that a mailbox can't be a disposable or forwarding email address.
{% endcall %}
{% call text() %}
If you have any question, you can reach out to us by replying to this email.
{% endcall %}
{% call text() %}
Best, <br/>
SimpleLogin Team.
{% endcall %}
<h1>{{ mailbox.email }} is disabled.</h1>
{% endcall %}
{% call text() %}
We have detected that your mailbox can't reliably receive emails from SimpleLogin for a consecutive period of time.
<br />
The mailbox has been therefore disabled.
{% endcall %}
{% call text() %}
Please note that a mailbox can't be a disposable or forwarding email address.
{% endcall %}
{% call text() %}
If you have any question, you can reach out to us by replying to this email.
{% endcall %}
{% call text() %}
Best,
<br />
SimpleLogin Team.
{% endcall %}
{% endblock %}

View File

@ -1,46 +1,43 @@
{% extends "base.html" %}
{% block content %}
{% call text() %}
<h1>
{{ breached_aliases|count }} of your aliases are found in data breaches.
</h1>
{% endcall %}
<ol>
{%- for alias in breached_aliases[:10] %}
<li>{% call text() %}
<b>{{ alias.email }}</b> was found in {{ alias.hibp_breaches|count }} data breaches. <br>
<ul>
{% set breaches = alias.hibp_breaches|sort(attribute='date', reverse=True) %}
{%- for breach in breaches[:4] %}
<li>
<b>{{ breach.name }}</b> {% if breach.date %}({{ breach.date.format('YYYY-MM-DD') }}){% endif %}
{{ breach.description }}
</li>
{%- endfor %}
</ul>
{% if breaches|length > 4 %}
And {{ breaches|length - 4 }} more data breaches...
{% endif %}
{% endcall %}</li>
{%- endfor %}
</ol>
{% if breached_aliases|length > 10 %}
{% call text() %}
And {{ breached_aliases|length - 10 }} more aliases...
<h1>{{ breached_aliases|count }} of your aliases are found in data breaches.</h1>
{% endcall %}
<ol>
{%- for alias in breached_aliases[:10] %}
<li>
{% call text() %}
<b>{{ alias.email }}</b> was found in {{ alias.hibp_breaches|count }} data breaches.
<br />
<ul>
{% set breaches = alias.hibp_breaches|sort(attribute='date', reverse=True) %}
{%- for breach in breaches[:4] %}
<li>
<b>{{ breach.name }}</b>
{% if breach.date %}({{ breach.date.format('YYYY-MM-DD') }}){% endif %}
{{ breach.description }}
</li>
{%- endfor %}
</ul>
{% if breaches|length > 4 %}And {{ breaches|length - 4 }} more data breaches...{% endif %}
{% endcall %}
{% endif %}
</li>
{%- endfor %}
</ol>
{% if breached_aliases|length > 10 %}
{% call text() %}
For more information, check <a href='https://haveibeenpwned.com/'>HaveIBeenPwned.com</a>.
{% endcall %}
{{ render_text('Best, <br />SimpleLogin Team.') }}
{% endblock %}
And {{ breached_aliases|length - 10 }} more aliases...
{% endcall %}
{% endif %}
{% call text() %}
For more information, check
<a href='https://haveibeenpwned.com/'>HaveIBeenPwned.com</a>
.
{% endcall %}
{{ render_text('Best,
<br />
SimpleLogin Team.') }}
{% endblock %}

Some files were not shown because too many files have changed in this diff Show More