From 3a44890803bcfe5c4f4bc8482a7994d3ba254243 Mon Sep 17 00:00:00 2001 From: Yingtong Li Date: Thu, 4 Jan 2018 11:36:07 +0800 Subject: [PATCH] Email admins about task failures --- eos/core/tasks/__init__.py | 10 ++++- eos/core/tasks/direct.py | 6 ++- eosweb/core/main.py | 14 +++---- eosweb/core/tasks.py | 60 +++++++++++++++++++++++++++ eosweb/core/templates/email/base.html | 4 +- eosweb/core/templates/email/base.scss | 2 +- eosweb/core/templates/email/base.txt | 26 ++++++++++++ local_settings.example.py | 6 +-- requirements.txt | 2 + 9 files changed, 113 insertions(+), 17 deletions(-) create mode 100644 eosweb/core/tasks.py create mode 100644 eosweb/core/templates/email/base.txt diff --git a/eos/core/tasks/__init__.py b/eos/core/tasks/__init__.py index 96cb0ea..3e7d3a3 100644 --- a/eos/core/tasks/__init__.py +++ b/eos/core/tasks/__init__.py @@ -1,5 +1,5 @@ # Eos - Verifiable elections -# Copyright © 2017 RunasSudo (Yingtong Li) +# Copyright © 2017-18 RunasSudo (Yingtong Li) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by @@ -27,6 +27,8 @@ class Task(TopLevelObject): FAILED = -10 TIMEOUT = -20 + label = 'Unknown task' + _id = UUIDField() run_strategy = EmbeddedObjectField() @@ -43,6 +45,12 @@ class Task(TopLevelObject): def _run(self): pass + + def complete(self): + pass + + def error(self): + pass class DummyTask(Task): _db_name = Task._db_name diff --git a/eos/core/tasks/direct.py b/eos/core/tasks/direct.py index d0e883a..086f0ae 100644 --- a/eos/core/tasks/direct.py +++ b/eos/core/tasks/direct.py @@ -1,5 +1,5 @@ # Eos - Verifiable elections -# Copyright © 2017 RunasSudo (Yingtong Li) +# Copyright © 2017-18 RunasSudo (Yingtong Li) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by @@ -28,6 +28,8 @@ class DirectRunStrategy(RunStrategy): task.status = Task.Status.COMPLETE task.completed_at = DateTimeField.now() task.save() + + task.complete() except Exception as e: task.status = Task.Status.FAILED task.completed_at = DateTimeField.now() @@ -39,3 +41,5 @@ class DirectRunStrategy(RunStrategy): else: task.messages.append(repr(e)) task.save() + + task.error() diff --git a/eosweb/core/main.py b/eosweb/core/main.py index 5da1b46..dea2ce4 100644 --- a/eosweb/core/main.py +++ b/eosweb/core/main.py @@ -1,5 +1,5 @@ # Eos - Verifiable elections -# Copyright © 2017 RunasSudo (Yingtong Li) +# Copyright © 2017-18 RunasSudo (Yingtong Li) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by @@ -28,6 +28,8 @@ from eos.psr.election import * from eos.psr.mixnet import * from eos.psr.workflow import * +from eosweb.core.tasks import * + import eos.core.hashing import eosweb @@ -259,7 +261,7 @@ def election_admin_enter_task(election): if workflow_task.status != WorkflowTask.Status.READY: return flask.Response('Task is not yet ready or has already exited', 409) - task = WorkflowTaskEntryTask( + task = WorkflowTaskEntryWebTask( election_id=election._id, workflow_task=workflow_task._name, status=Task.Status.READY, @@ -275,7 +277,7 @@ def election_admin_enter_task(election): def election_admin_schedule_task(election): workflow_task = election.workflow.get_task(flask.request.form['task_name']) - task = WorkflowTaskEntryTask( + task = WorkflowTaskEntryWebTask( election_id=election._id, workflow_task=workflow_task._name, run_at=DateTimeField().deserialise(flask.request.form['datetime']), @@ -387,12 +389,6 @@ def email_authenticate(): return flask.redirect(flask.url_for('login_complete')) -@app.route('/email') -def tmp(): - import sass - css = sass.compile(string=flask.render_template('email/base.scss')) - return flask.render_template('email/base.html', title='Hello World', text='

Dear voter,

You are registered to vote in the election Election Name.

', css=css) - # === Apps === for app_name in app.config['APPS']: diff --git a/eosweb/core/tasks.py b/eosweb/core/tasks.py new file mode 100644 index 0000000..8cac44b --- /dev/null +++ b/eosweb/core/tasks.py @@ -0,0 +1,60 @@ +# Eos - Verifiable elections +# Copyright © 2017-18 RunasSudo (Yingtong Li) +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import logging +import premailer +import sass + +import flask +import flask_mail + +from eos.core.tasks import * +from eos.base.election import * + +class WebTask(Task): + def error(self): + import eosweb + + # Prepare email + title = 'Task failed: {}'.format(self.label) + + css = sass.compile(string=flask.render_template('email/base.scss')) + html = flask.render_template( + 'email/base.html', + title=title, + css=css, + text='

The task {} failed execution. The output was:

{}
'.format(self.label, '\n'.join(self.messages)) + ) + html = premailer.Premailer(html).transform() + + body = flask.render_template( + 'email/base.txt', + title=title, + text='The task "{}" failed execution. The output was:\n\n{}'.format(self.label, '\n'.join(self.messages)) + ) + + # Send email + mail = flask_mail.Mail(eosweb.app) + msg = flask_mail.Message( + title, + recipients=[admin.email for admin in eosweb.app.config['ADMINS'] if isinstance(admin, EmailUser)], + body=body, + html=html + ) + mail.send(msg) + +class WorkflowTaskEntryWebTask(WorkflowTaskEntryTask, WebTask): + pass diff --git a/eosweb/core/templates/email/base.html b/eosweb/core/templates/email/base.html index a8dcf4b..fc944fb 100644 --- a/eosweb/core/templates/email/base.html +++ b/eosweb/core/templates/email/base.html @@ -1,6 +1,6 @@ {# Eos - Verifiable elections - Copyright © 2017 RunasSudo (Yingtong Li) + Copyright © 2017-18 RunasSudo (Yingtong Li) This file adapted from https://github.com/leemunroe/responsive-html-email-template, licensed under the MIT licence. @@ -25,7 +25,7 @@ {{ title }} diff --git a/eosweb/core/templates/email/base.scss b/eosweb/core/templates/email/base.scss index 310835c..f91377e 100644 --- a/eosweb/core/templates/email/base.scss +++ b/eosweb/core/templates/email/base.scss @@ -1,6 +1,6 @@ {# Eos - Verifiable elections - Copyright © 2017 RunasSudo (Yingtong Li) + Copyright © 2017-18 RunasSudo (Yingtong Li) This file adapted from https://github.com/leemunroe/responsive-html-email-template, licensed under the MIT licence. diff --git a/eosweb/core/templates/email/base.txt b/eosweb/core/templates/email/base.txt new file mode 100644 index 0000000..a5e778a --- /dev/null +++ b/eosweb/core/templates/email/base.txt @@ -0,0 +1,26 @@ +{# + Eos - Verifiable elections + Copyright © 2017-18 RunasSudo (Yingtong Li) + + This file adapted from https://github.com/leemunroe/responsive-html-email-template, licensed under the MIT licence. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +#} + +{{ text|safe }} + +--- + +Eos Voting for {{ eosweb.app.config['ORG_NAME'] }} +{{ eosweb.app.config['BASE_URI'] }} diff --git a/local_settings.example.py b/local_settings.example.py index 22ecb5e..3bc4e61 100644 --- a/local_settings.example.py +++ b/local_settings.example.py @@ -32,9 +32,9 @@ DB_NAME = 'eos' # Email -SMTP_HOST, SMTP_PORT = 'localhost', 25 -SMTP_USER, SMTP_PASS = None, None -SMTP_FROM = 'eos@localhost' +MAIL_SERVER, MAIL_PORT = 'localhost', 25 +MAIL_USERNAME, MAIL_PASSWORD = None, None +MAIL_DEFAULT_SENDER = 'eos@localhost' # Reddit diff --git a/requirements.txt b/requirements.txt index 29decd8..4c536b1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,8 +1,10 @@ coverage==4.4.1 Flask==0.12.2 +Flask-Mail==0.9.1 Flask-OAuthlib==0.9.4 gunicorn==19.7.1 libsass==0.13.4 +premailer==3.1.1 psycopg2==2.7.3.2 PyExecJS==1.4.1 pymongo==3.5.1