-
-
Notifications
You must be signed in to change notification settings - Fork 942
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1343 from Kathenae/kathenae-task_notification_plugin
Task notification plugin
- Loading branch information
Showing
13 changed files
with
412 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
.conf |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
from .plugin import * | ||
from . import signals |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import os | ||
import configparser | ||
|
||
script_dir = os.path.dirname(os.path.abspath(__file__)) | ||
|
||
def load(): | ||
config = configparser.ConfigParser() | ||
config.read(f"{script_dir}/.conf") | ||
smtp_configuration = { | ||
'smtp_server': config.get('SETTINGS', 'smtp_server', fallback=""), | ||
'smtp_port': config.getint('SETTINGS', 'smtp_port', fallback=587), | ||
'smtp_username': config.get('SETTINGS', 'smtp_username', fallback=""), | ||
'smtp_password': config.get('SETTINGS', 'smtp_password', fallback=""), | ||
'smtp_use_tls': config.getboolean('SETTINGS', 'smtp_use_tls', fallback=False), | ||
'smtp_from_address': config.get('SETTINGS', 'smtp_from_address', fallback=""), | ||
'smtp_to_address': config.get('SETTINGS', 'smtp_to_address', fallback=""), | ||
'notification_app_name': config.get('SETTINGS', 'notification_app_name', fallback=""), | ||
'notify_task_completed': config.getboolean('SETTINGS', 'notify_task_completed', fallback=False), | ||
'notify_task_failed': config.getboolean('SETTINGS', 'notify_task_failed', fallback=False), | ||
'notify_task_removed': config.getboolean('SETTINGS', 'notify_task_removed', fallback=False) | ||
} | ||
return smtp_configuration | ||
|
||
def save(data : dict): | ||
config = configparser.ConfigParser() | ||
config['SETTINGS'] = { | ||
'smtp_server': str(data.get('smtp_server')), | ||
'smtp_port': str(data.get('smtp_port')), | ||
'smtp_username': str(data.get('smtp_username')), | ||
'smtp_password': str(data.get('smtp_password')), | ||
'smtp_use_tls': str(data.get('smtp_use_tls')), | ||
'smtp_from_address': str(data.get('smtp_from_address')), | ||
'smtp_to_address': str(data.get('smtp_to_address')), | ||
'notification_app_name': str(data.get('notification_app_name')), | ||
'notify_task_completed': str(data.get('notify_task_completed')), | ||
'notify_task_failed': str(data.get('notify_task_failed')), | ||
'notify_task_removed': str(data.get('notify_task_removed')) | ||
} | ||
with open(f"{script_dir}/.conf", 'w') as configFile: | ||
config.write(configFile) |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
from django.core.mail import send_mail | ||
from django.core.mail.backends.smtp import EmailBackend | ||
from . import config | ||
|
||
|
||
def send(subject : str, message : str, smtp_config : dict = None): | ||
|
||
if not smtp_config: | ||
smtp_config = config.load() | ||
|
||
email_backend = EmailBackend( | ||
smtp_config.get('smtp_server'), | ||
smtp_config.get('smtp_port'), | ||
smtp_config.get('smtp_username'), | ||
smtp_config.get('smtp_password'), | ||
smtp_config.get('smtp_use_tls'), | ||
timeout=10 | ||
) | ||
|
||
result = send_mail( | ||
subject, | ||
message, | ||
smtp_config.get('smtp_from_address'), | ||
[smtp_config.get('smtp_to_address')], | ||
connection=email_backend, | ||
auth_user = smtp_config.get('smtp_username'), | ||
auth_password = smtp_config.get('smtp_password'), | ||
fail_silently = False | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
{ | ||
"name": "Task Notification", | ||
"webodmMinVersion": "0.6.2", | ||
"description": "Get notified when a task has finished processing, has been removed or has failed", | ||
"version": "0.1.0", | ||
"author": "Ronald W. Machado", | ||
"email": "[email protected]", | ||
"repository": "https://github.com/OpenDroneMap/WebODM", | ||
"tags": [ | ||
"notification", | ||
"email", | ||
"smtp" | ||
], | ||
"homepage": "https://github.com/OpenDroneMap/WebODM", | ||
"experimental": false, | ||
"deprecated": false | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
from app.plugins import PluginBase, Menu, MountPoint | ||
from app.models import Setting | ||
from django.utils.translation import gettext as _ | ||
from django.shortcuts import render | ||
from django.contrib import messages | ||
from django.contrib.auth.decorators import login_required | ||
from django import forms | ||
from smtplib import SMTPAuthenticationError, SMTPConnectError, SMTPDataError | ||
from . import email | ||
from . import config | ||
|
||
class ConfigurationForm(forms.Form): | ||
notification_app_name = forms.CharField( | ||
label='App name', | ||
max_length=100, | ||
required=True, | ||
) | ||
smtp_to_address = forms.EmailField( | ||
label='Send Notification to Address', | ||
max_length=100, | ||
required=True | ||
) | ||
smtp_from_address = forms.EmailField( | ||
label='From Address', | ||
max_length=100, | ||
required=True | ||
) | ||
smtp_server = forms.CharField( | ||
label='SMTP Server', | ||
max_length=100, | ||
required=True | ||
) | ||
smtp_port = forms.IntegerField( | ||
label='Port', | ||
required=True | ||
) | ||
smtp_username = forms.CharField( | ||
label='Username', | ||
max_length=100, | ||
required=True | ||
) | ||
smtp_password = forms.CharField( | ||
label='Password', | ||
max_length=100, | ||
required=True | ||
) | ||
smtp_use_tls = forms.BooleanField( | ||
label='Use Transport Layer Security (TLS)', | ||
required=False, | ||
) | ||
|
||
notify_task_completed = forms.BooleanField( | ||
label='Notify Task Completed', | ||
required=False, | ||
) | ||
notify_task_failed = forms.BooleanField( | ||
label='Notify Task Failed', | ||
required=False, | ||
) | ||
notify_task_removed = forms.BooleanField( | ||
label='Notify Task Removed', | ||
required=False, | ||
) | ||
|
||
def test_settings(self, request): | ||
try: | ||
settings = Setting.objects.first() | ||
email.send(f'{self.cleaned_data["notification_app_name"]} - Testing Notification', 'Hi, just testing if notification is working', self.cleaned_data) | ||
messages.success(request, f"Email sent successfully, check your inbox at {self.cleaned_data.get('smtp_to_address')}") | ||
except SMTPAuthenticationError as e: | ||
messages.error(request, 'Invalid SMTP username or password') | ||
except SMTPConnectError as e: | ||
messages.error(request, 'Could not connect to the SMTP server') | ||
except SMTPDataError as e: | ||
messages.error(request, 'Error sending email. Please try again later') | ||
except Exception as e: | ||
messages.error(request, f'An error occured: {e}') | ||
|
||
def save_settings(self): | ||
config.save(self.cleaned_data) | ||
|
||
class Plugin(PluginBase): | ||
def main_menu(self): | ||
return [Menu(_("Task Notification"), self.public_url(""), "fa fa-envelope fa-fw")] | ||
|
||
def include_css_files(self): | ||
return ['style.css'] | ||
|
||
def app_mount_points(self): | ||
|
||
@login_required | ||
def index(request): | ||
if request.method == "POST": | ||
|
||
form = ConfigurationForm(request.POST) | ||
test_configuration = request.POST.get("test_configuration") | ||
if form.is_valid() and test_configuration: | ||
form.test_settings(request) | ||
elif form.is_valid() and not test_configuration: | ||
form.save_settings() | ||
messages.success(request, "Notification settings applied successfully!") | ||
else: | ||
config_data = config.load() | ||
|
||
# notification_app_name initial value should be whatever is defined in the settings | ||
settings = Setting.objects.first() | ||
config_data['notification_app_name'] = config_data['notification_app_name'] or settings.app_name | ||
form = ConfigurationForm(initial=config_data) | ||
|
||
return render(request, self.template_path('index.html'), {'form' : form, 'title' : 'Task Notification'}) | ||
|
||
return [ | ||
MountPoint('$', index), | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
.errorlist { | ||
color: red; | ||
list-style: none; | ||
margin: 0; | ||
padding: 0; | ||
} | ||
|
||
.errorlist li { | ||
margin: 0; | ||
padding: 0; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
import logging | ||
from django.dispatch import receiver | ||
from django.core.mail import send_mail | ||
from app.plugins.signals import task_completed, task_failed, task_removed | ||
from app.plugins.functions import get_current_plugin | ||
from . import email as notification | ||
from . import config | ||
from app.models import Task, Setting | ||
|
||
logger = logging.getLogger('app.logger') | ||
|
||
@receiver(task_completed) | ||
def handle_task_completed(sender, task_id, **kwargs): | ||
if get_current_plugin(only_active=True) is None: | ||
return | ||
|
||
logger.info("TaskNotification: Task Completed") | ||
|
||
config_data = config.load() | ||
if config_data.get("notify_task_completed") == True: | ||
task = Task.objects.get(id=task_id) | ||
setting = Setting.objects.first() | ||
notification_app_name = config_data['notification_app_name'] or settings.app_name | ||
|
||
console_output = reverse_output(task.console_output) | ||
notification.send( | ||
f"{notification_app_name} - {task.project.name} Task Completed", | ||
f"{task.project.name}\n{task.name} Completed\nProcessing time:{hours_minutes_secs(task.processing_time)}\n\nConsole Output:{console_output}", | ||
config_data | ||
) | ||
|
||
@receiver(task_removed) | ||
def handle_task_removed(sender, task_id, **kwargs): | ||
if get_current_plugin(only_active=True) is None: | ||
return | ||
|
||
logger.info("TaskNotification: Task Removed") | ||
|
||
config_data = config.load() | ||
if config_data.get("notify_task_removed") == True: | ||
task = Task.objects.get(id=task_id) | ||
setting = Setting.objects.first() | ||
notification_app_name = config_data['notification_app_name'] or settings.app_name | ||
console_output = reverse_output(task.console_output) | ||
notification.send( | ||
f"{notification_app_name} - {task.project.name} Task removed", | ||
f"{task.project.name}\n{task.name} was removed\nProcessing time:{hours_minutes_secs(task.processing_time)}\n\nConsole Output:{console_output}", | ||
config_data | ||
) | ||
|
||
@receiver(task_failed) | ||
def handle_task_failed(sender, task_id, **kwargs): | ||
if get_current_plugin(only_active=True) is None: | ||
return | ||
|
||
logger.info("TaskNotification: Task Failed") | ||
|
||
config_data = config.load() | ||
if config_data.get("notify_task_failed") == True: | ||
task = Task.objects.get(id=task_id) | ||
setting = Setting.objects.first() | ||
notification_app_name = config_data['notification_app_name'] or settings.app_name | ||
console_output = reverse_output(task.console_output) | ||
notification.send( | ||
f"{notification_app_name} - {task.project.name} Task Failed", | ||
f"{task.project.name}\n{task.name} Failed with error: {task.last_error}\nProcessing time:{hours_minutes_secs(task.processing_time)}\n\nConsole Output:{console_output}", | ||
config_data | ||
) | ||
|
||
def hours_minutes_secs(milliseconds): | ||
if milliseconds == 0 or milliseconds == -1: | ||
return "-- : -- : --" | ||
|
||
ch = 60 * 60 * 1000 | ||
cm = 60 * 1000 | ||
h = milliseconds // ch | ||
m = (milliseconds - h * ch) // cm | ||
s = round((milliseconds - h * ch - m * cm) / 1000) | ||
pad = lambda n: '0' + str(n) if n < 10 else str(n) | ||
|
||
if s == 60: | ||
m += 1 | ||
s = 0 | ||
if m == 60: | ||
h += 1 | ||
m = 0 | ||
|
||
return ':'.join([pad(h), pad(m), pad(s)]) | ||
|
||
def reverse_output(output_string): | ||
# Split the output string into lines, then reverse the order | ||
lines = output_string.split('\n') | ||
lines.reverse() | ||
|
||
# Join the reversed lines back into a single string with newlines | ||
reversed_string = '\n'.join(lines) | ||
|
||
return reversed_string |
Oops, something went wrong.