Created Task Notification plugin

pull/1343/head
Ronald 2023-05-18 18:34:11 +02:00
rodzic 8005fcdc21
commit 18d3c7827c
10 zmienionych plików z 394 dodań i 0 usunięć

Wyświetl plik

@ -0,0 +1 @@
.conf

Wyświetl plik

@ -0,0 +1,2 @@
from .plugin import *
from . import signals

Wyświetl plik

@ -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)

Wyświetl plik

@ -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
)

Wyświetl plik

@ -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": "ronadlwilsonmachado@gmail.com",
"repository": "https://github.com/OpenDroneMap/WebODM",
"tags": [
"notification",
"email",
"smtp"
],
"homepage": "https://github.com/OpenDroneMap/WebODM",
"experimental": false,
"deprecated": false
}

Wyświetl plik

@ -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),
]

Wyświetl plik

@ -0,0 +1,11 @@
.errorlist {
color: red;
list-style: none;
margin: 0;
padding: 0;
}
.errorlist li {
margin: 0;
padding: 0;
}

Wyświetl plik

@ -0,0 +1,88 @@
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 . 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):
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):
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):
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

Wyświetl plik

@ -0,0 +1,92 @@
{% extends "app/plugins/templates/base.html" %}
{% load i18n %}
{% block content %}
<h2>{% trans 'Tasks Notification' %}</h2>
<p>Get notified when a task has finished processing, has been removed or has failed</p>
<hr>
<form action="/plugins/tasknotification/" method="post" class="mt-5">
{% csrf_token %}
<div class="row">
<div class="col-sm-6">
<div class="form-group mb-3">
<label for="notification_app_name">App name</label>
<input name="notification_app_name" value="{{ form.notification_app_name.value }}" type="text" class="form-control" />
{{form.notification_app_name.errors}}
</div>
</div>
<div class="col-sm-6">
<div class="form-group mb-3">
<label for="smtp_to_address">Send Notification to Address</label>
<input name="smtp_to_address" value="{{ form.smtp_to_address.value }}" type="text" class="form-control" placeholder="user@example.com"/>
{{ form.smtp_to_address.errors }}
</div>
</div>
</div>
<p><b>Allowed Notifications</b></p>
<div class="checkbox mb-3">
<label>
<input name="notify_task_completed" {% if form.notify_task_completed.value %} checked {% endif %} type="checkbox"> Task Completed
</label>
{{form.notify_task_completed.errors}}
</div>
<div class="checkbox mb-3">
<label>
<input name="notify_task_failed" {% if form.notify_task_failed.value %} checked {% endif %} type="checkbox"> Task Failed
</label>
{{form.notify_task_failed.errors}}
</div>
<div class="checkbox mb-3">
<label>
<input name="notify_task_removed" {% if form.notify_task_removed.value %} checked {% endif %} type="checkbox"> Task Removed
</label>
{{form.notify_task_removed.errors}}
</div>
<br>
<h3>Smtp Settings</h3>
<br>
<div class="row">
<div class="col-sm-6">
<div class="form-group mb-3">
<label for="smtp_from_address">From Address</label>
<input name="smtp_from_address" value="{{ form.smtp_from_address.value }}" type="text" class="form-control" placeholder="admin@webodm.com" />
{{ form.smtp_from_address.errors }}
</div>
</div>
</div>
<div class="form-group mb-3">
<label for="smtp_server">SMTP Server</label>
<input name="smtp_server" value="{{ form.smtp_server.value }}" type="text" class="form-control" placeholder="smtp.server.com" />
{{form.smtp_server.errors}}
</div>
<div class="form-group mb-3">
<label for="smtp_port">Port</label>
<input name="smtp_port" value="{{ form.smtp_port.value }}" type="number" class="form-control" placeholder="587" />
{{form.smtp_port.errors}}
</div>
<div class="form-group mb-3">
<label for="smtp_username">Username</label>
<input name="smtp_username" value="{{ form.smtp_username.value }}" type="text" class="form-control" />
{{form.smtp_username.errors}}
</div>
<div class="form-group mb-3">
<label for="smtp_password">Password</label>
<input name="smtp_password" value="{{ form.smtp_password.value }}" type="password" class="form-control" />
{{form.smtp_password.errors}}
</div>
<div class="checkbox mb-3">
<label>
<input name="smtp_use_tls" {% if form.smtp_use_tls.value %} checked {% endif %} type="checkbox"> Use Transport Layer Security (TLS)
</label>
{{form.smtp_use_tls.errors}}
</div>
<hr>
<p>
{{ form.non_field_errors }}
</p>
<div>
<button name="apply_configuration" value="yes" class="btn btn-primary">Apply Settings</button>
<button name="test_configuration" value="yes" class="btn btn-info">Send Test Email</button>
</div>
</form>
{% endblock %}