kopia lustrzana https://github.com/OpenDroneMap/WebODM
Created Task Notification plugin
rodzic
8005fcdc21
commit
18d3c7827c
|
@ -0,0 +1 @@
|
|||
.conf
|
|
@ -0,0 +1,2 @@
|
|||
from .plugin import *
|
||||
from . import signals
|
|
@ -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)
|
|
@ -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
|
||||
)
|
|
@ -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
|
||||
}
|
|
@ -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),
|
||||
]
|
|
@ -0,0 +1,11 @@
|
|||
.errorlist {
|
||||
color: red;
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.errorlist li {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
|
@ -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
|
|
@ -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 %}
|
Ładowanie…
Reference in New Issue