forked from GithubBackups/healthchecks
Add the "Email Settings..." dialog and the "Subject Must Contain" setting
This commit is contained in:
parent
4f2930bb05
commit
5edcd42033
@ -8,6 +8,7 @@ All notable changes to this project will be documented in this file.
|
|||||||
- Database schema: add uniqueness constraint to Check.code
|
- Database schema: add uniqueness constraint to Check.code
|
||||||
- Database schema: add Ping.kind field
|
- Database schema: add Ping.kind field
|
||||||
- Database schema: remove Ping.start and Ping.fail fields
|
- Database schema: remove Ping.start and Ping.fail fields
|
||||||
|
- Add "Email Settings..." dialog and "Subject Must Contain" setting
|
||||||
|
|
||||||
|
|
||||||
## 1.4.0 - 2018-12-25
|
## 1.4.0 - 2018-12-25
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import asyncore
|
import asyncore
|
||||||
|
import email
|
||||||
import re
|
import re
|
||||||
from smtpd import SMTPServer
|
from smtpd import SMTPServer
|
||||||
|
|
||||||
@ -17,6 +18,11 @@ class Listener(SMTPServer):
|
|||||||
to_parts = rcpttos[0].split("@")
|
to_parts = rcpttos[0].split("@")
|
||||||
code = to_parts[0]
|
code = to_parts[0]
|
||||||
|
|
||||||
|
try:
|
||||||
|
data = data.decode()
|
||||||
|
except UnicodeError:
|
||||||
|
data = "[binary data]"
|
||||||
|
|
||||||
if not RE_UUID.match(code):
|
if not RE_UUID.match(code):
|
||||||
self.stdout.write("Not an UUID: %s" % code)
|
self.stdout.write("Not an UUID: %s" % code)
|
||||||
return
|
return
|
||||||
@ -27,8 +33,15 @@ class Listener(SMTPServer):
|
|||||||
self.stdout.write("Check not found: %s" % code)
|
self.stdout.write("Check not found: %s" % code)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
action = "success"
|
||||||
|
if check.subject:
|
||||||
|
parsed = email.message_from_string(data)
|
||||||
|
received_subject = parsed.get("subject", "")
|
||||||
|
if check.subject not in received_subject:
|
||||||
|
action = "ign"
|
||||||
|
|
||||||
ua = "Email from %s" % mailfrom
|
ua = "Email from %s" % mailfrom
|
||||||
check.ping(peer[0], "email", "", ua, data)
|
check.ping(peer[0], "email", "", ua, data, action)
|
||||||
self.stdout.write("Processed ping for %s" % code)
|
self.stdout.write("Processed ping for %s" % code)
|
||||||
|
|
||||||
|
|
||||||
|
18
hc/api/migrations/0053_check_subject.py
Normal file
18
hc/api/migrations/0053_check_subject.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 2.1.4 on 2019-01-04 12:49
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('api', '0052_auto_20190104_1122'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='check',
|
||||||
|
name='subject',
|
||||||
|
field=models.CharField(blank=True, max_length=100),
|
||||||
|
),
|
||||||
|
]
|
@ -74,6 +74,7 @@ class Check(models.Model):
|
|||||||
grace = models.DurationField(default=DEFAULT_GRACE)
|
grace = models.DurationField(default=DEFAULT_GRACE)
|
||||||
schedule = models.CharField(max_length=100, default="* * * * *")
|
schedule = models.CharField(max_length=100, default="* * * * *")
|
||||||
tz = models.CharField(max_length=36, default="UTC")
|
tz = models.CharField(max_length=36, default="UTC")
|
||||||
|
subject = models.CharField(max_length=100, blank=True)
|
||||||
n_pings = models.IntegerField(default=0)
|
n_pings = models.IntegerField(default=0)
|
||||||
last_ping = models.DateTimeField(null=True, blank=True)
|
last_ping = models.DateTimeField(null=True, blank=True)
|
||||||
last_start = models.DateTimeField(null=True, blank=True)
|
last_start = models.DateTimeField(null=True, blank=True)
|
||||||
@ -207,7 +208,9 @@ class Check(models.Model):
|
|||||||
def ping(self, remote_addr, scheme, method, ua, body, action):
|
def ping(self, remote_addr, scheme, method, ua, body, action):
|
||||||
if action == "start":
|
if action == "start":
|
||||||
self.last_start = timezone.now()
|
self.last_start = timezone.now()
|
||||||
# DOn't update "last_ping" field.
|
# Don't update "last_ping" field.
|
||||||
|
elif action == "ign":
|
||||||
|
pass
|
||||||
else:
|
else:
|
||||||
self.last_start = None
|
self.last_start = None
|
||||||
self.last_ping = timezone.now()
|
self.last_ping = timezone.now()
|
||||||
@ -230,7 +233,7 @@ class Check(models.Model):
|
|||||||
|
|
||||||
ping = Ping(owner=self)
|
ping = Ping(owner=self)
|
||||||
ping.n = self.n_pings
|
ping.n = self.n_pings
|
||||||
if action in ("start", "fail"):
|
if action in ("start", "fail", "ign"):
|
||||||
ping.kind = action
|
ping.kind = action
|
||||||
|
|
||||||
ping.remote_addr = remote_addr
|
ping.remote_addr = remote_addr
|
||||||
|
@ -24,6 +24,10 @@ class NameTagsForm(forms.Form):
|
|||||||
return " ".join(result)
|
return " ".join(result)
|
||||||
|
|
||||||
|
|
||||||
|
class EmailSettingsForm(forms.Form):
|
||||||
|
subject = forms.CharField(max_length=100)
|
||||||
|
|
||||||
|
|
||||||
class TimeoutForm(forms.Form):
|
class TimeoutForm(forms.Form):
|
||||||
timeout = forms.IntegerField(min_value=60, max_value=2592000)
|
timeout = forms.IntegerField(min_value=60, max_value=2592000)
|
||||||
grace = forms.IntegerField(min_value=60, max_value=2592000)
|
grace = forms.IntegerField(min_value=60, max_value=2592000)
|
||||||
|
@ -5,6 +5,7 @@ from hc.front import views
|
|||||||
check_urls = [
|
check_urls = [
|
||||||
path('name/', views.update_name, name="hc-update-name"),
|
path('name/', views.update_name, name="hc-update-name"),
|
||||||
path('details/', views.details, name="hc-details"),
|
path('details/', views.details, name="hc-details"),
|
||||||
|
path('email_settings/', views.email_settings, name="hc-email-settings"),
|
||||||
path('timeout/', views.update_timeout, name="hc-update-timeout"),
|
path('timeout/', views.update_timeout, name="hc-update-timeout"),
|
||||||
path('pause/', views.pause, name="hc-pause"),
|
path('pause/', views.pause, name="hc-pause"),
|
||||||
path('remove/', views.remove_check, name="hc-remove-check"),
|
path('remove/', views.remove_check, name="hc-remove-check"),
|
||||||
|
@ -23,7 +23,7 @@ from hc.api.transports import Telegram
|
|||||||
from hc.front.forms import (AddWebhookForm, NameTagsForm,
|
from hc.front.forms import (AddWebhookForm, NameTagsForm,
|
||||||
TimeoutForm, AddUrlForm, AddEmailForm,
|
TimeoutForm, AddUrlForm, AddEmailForm,
|
||||||
AddOpsGenieForm, CronForm, AddSmsForm,
|
AddOpsGenieForm, CronForm, AddSmsForm,
|
||||||
ChannelNameForm)
|
ChannelNameForm, EmailSettingsForm)
|
||||||
from hc.front.schemas import telegram_callback
|
from hc.front.schemas import telegram_callback
|
||||||
from hc.front.templatetags.hc_extras import (num_down_title, down_title,
|
from hc.front.templatetags.hc_extras import (num_down_title, down_title,
|
||||||
sortchecks)
|
sortchecks)
|
||||||
@ -274,6 +274,18 @@ def update_name(request, code):
|
|||||||
return redirect("hc-checks")
|
return redirect("hc-checks")
|
||||||
|
|
||||||
|
|
||||||
|
@require_POST
|
||||||
|
@login_required
|
||||||
|
def email_settings(request, code):
|
||||||
|
check = _get_check_for_user(request, code)
|
||||||
|
form = EmailSettingsForm(request.POST)
|
||||||
|
if form.is_valid():
|
||||||
|
check.subject = form.cleaned_data["subject"]
|
||||||
|
check.save()
|
||||||
|
|
||||||
|
return redirect("hc-details", code)
|
||||||
|
|
||||||
|
|
||||||
@require_POST
|
@require_POST
|
||||||
@login_required
|
@login_required
|
||||||
def update_timeout(request, code):
|
def update_timeout(request, code):
|
||||||
|
@ -74,6 +74,11 @@ body {
|
|||||||
border: 1px solid #117a3f;
|
border: 1px solid #117a3f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.label-ign {
|
||||||
|
background: #e0e0e0;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
.hc-dialog {
|
.hc-dialog {
|
||||||
background: #FFF;
|
background: #FFF;
|
||||||
padding: 2em;
|
padding: 2em;
|
||||||
|
@ -33,7 +33,14 @@
|
|||||||
<div>
|
<div>
|
||||||
<p>Keep this check up by making HTTP requests to this URL:</p>
|
<p>Keep this check up by making HTTP requests to this URL:</p>
|
||||||
<code>{{ check.url }}</code>
|
<code>{{ check.url }}</code>
|
||||||
<p>Or by sending emails to this address:</p>
|
<p>
|
||||||
|
{% if check.subject %}
|
||||||
|
Or by sending emails with "{{ check.subject }}"
|
||||||
|
in the subject line to this address:
|
||||||
|
{% else %}
|
||||||
|
Or by sending emails to this address:
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
||||||
<code>{{ check.email }}</code>
|
<code>{{ check.email }}</code>
|
||||||
|
|
||||||
<p>You can also explictly
|
<p>You can also explictly
|
||||||
@ -46,17 +53,17 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="text-right">
|
<div class="text-right">
|
||||||
<button
|
<button
|
||||||
data-label="Copy URL"
|
data-toggle="modal"
|
||||||
data-clipboard-text="{{ check.url }}"
|
data-target="#email-settings-modal"
|
||||||
class="btn btn-sm btn-default copy-btn">Copy URL</button>
|
class="btn btn-sm btn-default">Email Settings…</button>
|
||||||
<button
|
|
||||||
data-label="Copy Email"
|
|
||||||
data-clipboard-text="{{ check.email }}"
|
|
||||||
class="btn btn-sm btn-default copy-btn">Copy Email</button>
|
|
||||||
<button
|
<button
|
||||||
data-toggle="modal"
|
data-toggle="modal"
|
||||||
data-target="#show-usage-modal"
|
data-target="#show-usage-modal"
|
||||||
class="btn btn-sm btn-default">Usage Examples</button>
|
class="btn btn-sm btn-default">Usage Examples</button>
|
||||||
|
<button
|
||||||
|
data-label="Copy URL"
|
||||||
|
data-clipboard-text="{{ check.url }}"
|
||||||
|
class="btn btn-sm btn-default copy-btn">Copy URL</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -207,6 +214,7 @@
|
|||||||
{% include "front/update_timeout_modal.html" %}
|
{% include "front/update_timeout_modal.html" %}
|
||||||
{% include "front/show_usage_modal.html" %}
|
{% include "front/show_usage_modal.html" %}
|
||||||
{% include "front/remove_check_modal.html" %}
|
{% include "front/remove_check_modal.html" %}
|
||||||
|
{% include "front/email_settings_modal.html" %}
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
@ -14,6 +14,8 @@
|
|||||||
<span class="label label-danger">Failure</span>
|
<span class="label label-danger">Failure</span>
|
||||||
{% elif event.kind == "start" %}
|
{% elif event.kind == "start" %}
|
||||||
<span class="label label-start">Started</span>
|
<span class="label label-start">Started</span>
|
||||||
|
{% elif event.kind == "ign" %}
|
||||||
|
<span class="label label-ign">Ignored</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="label label-success">OK</span>
|
<span class="label label-success">OK</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
41
templates/front/email_settings_modal.html
Normal file
41
templates/front/email_settings_modal.html
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
{% load hc_extras %}
|
||||||
|
|
||||||
|
<div id="email-settings-modal" class="modal">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<form
|
||||||
|
action="{% url 'hc-email-settings' check.code %}"
|
||||||
|
class="form-horizontal"
|
||||||
|
method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||||
|
<h4 class="update-timeout-title">Inbound Email Settings</h4>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="update-name-input" class="col-sm-4 control-label">
|
||||||
|
Subject Must Contain
|
||||||
|
</label>
|
||||||
|
<div class="col-sm-7">
|
||||||
|
<input
|
||||||
|
name="subject"
|
||||||
|
type="text"
|
||||||
|
value="{{ check.subject }}"
|
||||||
|
class="input-name form-control" />
|
||||||
|
|
||||||
|
<span class="help-block">
|
||||||
|
If set, {% site_name %} will ignore emails
|
||||||
|
without this value in the Subject line.
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
|
||||||
|
<button type="submit" class="btn btn-primary">Save</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -48,6 +48,8 @@
|
|||||||
<span class="label label-danger">Failure</span>
|
<span class="label label-danger">Failure</span>
|
||||||
{% elif event.kind == "start" %}
|
{% elif event.kind == "start" %}
|
||||||
<span class="label label-start">Started</span>
|
<span class="label label-start">Started</span>
|
||||||
|
{% elif event.kind == "ign" %}
|
||||||
|
<span class="label label-ign">Ignored</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="label label-success">OK</span>
|
<span class="label label-success">OK</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -4,6 +4,8 @@
|
|||||||
<span class="text-danger">(received via the <code>/fail</code> endpoint)</span>
|
<span class="text-danger">(received via the <code>/fail</code> endpoint)</span>
|
||||||
{% elif ping.kind == "start" %}
|
{% elif ping.kind == "start" %}
|
||||||
<span class="text-success">(received via the <code>/start</code> endpoint)</span>
|
<span class="text-success">(received via the <code>/start</code> endpoint)</span>
|
||||||
|
{% elif ping.kind == "ign" %}
|
||||||
|
<span class="text-muted">(ignored)</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user