forked from GithubBackups/healthchecks
Can configure the email integration to only report the "down" events. Fixes #231
This commit is contained in:
parent
499720a156
commit
a4fde44e3a
@ -7,6 +7,7 @@ All notable changes to this project will be documented in this file.
|
|||||||
- Add the EMAIL_USE_VERIFICATION configuration setting (#232)
|
- Add the EMAIL_USE_VERIFICATION configuration setting (#232)
|
||||||
- Show "Badges" and "Settings" in top navigation (#234)
|
- Show "Badges" and "Settings" in top navigation (#234)
|
||||||
- Upgrade to Django 2.2
|
- Upgrade to Django 2.2
|
||||||
|
- Can configure the email integration to only report the "down" events (#231)
|
||||||
|
|
||||||
|
|
||||||
## 1.6.0 - 2019-04-01
|
## 1.6.0 - 2019-04-01
|
||||||
|
@ -272,7 +272,7 @@ class Channel(models.Model):
|
|||||||
if self.name:
|
if self.name:
|
||||||
return self.name
|
return self.name
|
||||||
if self.kind == "email":
|
if self.kind == "email":
|
||||||
return "Email to %s" % self.value
|
return "Email to %s" % self.email_value
|
||||||
elif self.kind == "sms":
|
elif self.kind == "sms":
|
||||||
return "SMS to %s" % self.sms_number
|
return "SMS to %s" % self.sms_number
|
||||||
elif self.kind == "slack":
|
elif self.kind == "slack":
|
||||||
@ -302,7 +302,7 @@ class Channel(models.Model):
|
|||||||
args = [self.code, self.make_token()]
|
args = [self.code, self.make_token()]
|
||||||
verify_link = reverse("hc-verify-email", args=args)
|
verify_link = reverse("hc-verify-email", args=args)
|
||||||
verify_link = settings.SITE_ROOT + verify_link
|
verify_link = settings.SITE_ROOT + verify_link
|
||||||
emails.verify_email(self.value, {"verify_link": verify_link})
|
emails.verify_email(self.email_value, {"verify_link": verify_link})
|
||||||
|
|
||||||
def get_unsub_link(self):
|
def get_unsub_link(self):
|
||||||
args = [self.code, self.make_token()]
|
args = [self.code, self.make_token()]
|
||||||
@ -526,6 +526,33 @@ class Channel(models.Model):
|
|||||||
doc = json.loads(self.value)
|
doc = json.loads(self.value)
|
||||||
return doc["list_id"]
|
return doc["list_id"]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def email_value(self):
|
||||||
|
assert self.kind == "email"
|
||||||
|
if not self.value.startswith("{"):
|
||||||
|
return self.value
|
||||||
|
|
||||||
|
doc = json.loads(self.value)
|
||||||
|
return doc.get("value")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def email_notify_up(self):
|
||||||
|
assert self.kind == "email"
|
||||||
|
if not self.value.startswith("{"):
|
||||||
|
return True
|
||||||
|
|
||||||
|
doc = json.loads(self.value)
|
||||||
|
return doc.get("up")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def email_notify_down(self):
|
||||||
|
assert self.kind == "email"
|
||||||
|
if not self.value.startswith("{"):
|
||||||
|
return True
|
||||||
|
|
||||||
|
doc = json.loads(self.value)
|
||||||
|
return doc.get("down")
|
||||||
|
|
||||||
|
|
||||||
class Notification(models.Model):
|
class Notification(models.Model):
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -244,9 +244,21 @@ class NotifyTestCase(BaseTestCase):
|
|||||||
self.assertEqual(len(mail.outbox), 1)
|
self.assertEqual(len(mail.outbox), 1)
|
||||||
|
|
||||||
email = mail.outbox[0]
|
email = mail.outbox[0]
|
||||||
|
self.assertEqual(email.to[0], "alice@example.org")
|
||||||
self.assertTrue("X-Bounce-Url" in email.extra_headers)
|
self.assertTrue("X-Bounce-Url" in email.extra_headers)
|
||||||
self.assertTrue("List-Unsubscribe" in email.extra_headers)
|
self.assertTrue("List-Unsubscribe" in email.extra_headers)
|
||||||
|
|
||||||
|
def test_email_transport_handles_json_value(self):
|
||||||
|
payload = {"value": "alice@example.org", "up": True, "down": True}
|
||||||
|
self._setup_data("email", json.dumps(payload))
|
||||||
|
self.channel.notify(self.check)
|
||||||
|
|
||||||
|
# And email should have been sent
|
||||||
|
self.assertEqual(len(mail.outbox), 1)
|
||||||
|
|
||||||
|
email = mail.outbox[0]
|
||||||
|
self.assertEqual(email.to[0], "alice@example.org")
|
||||||
|
|
||||||
def test_it_skips_unverified_email(self):
|
def test_it_skips_unverified_email(self):
|
||||||
self._setup_data("email", "alice@example.org", email_verified=False)
|
self._setup_data("email", "alice@example.org", email_verified=False)
|
||||||
self.channel.notify(self.check)
|
self.channel.notify(self.check)
|
||||||
@ -256,6 +268,15 @@ class NotifyTestCase(BaseTestCase):
|
|||||||
self.assertEqual(Notification.objects.count(), 0)
|
self.assertEqual(Notification.objects.count(), 0)
|
||||||
self.assertEqual(len(mail.outbox), 0)
|
self.assertEqual(len(mail.outbox), 0)
|
||||||
|
|
||||||
|
def test_email_checks_up_down_flags(self):
|
||||||
|
payload = {"value": "alice@example.org", "up": True, "down": False}
|
||||||
|
self._setup_data("email", json.dumps(payload))
|
||||||
|
self.channel.notify(self.check)
|
||||||
|
|
||||||
|
# This channel should not notify on "down" events:
|
||||||
|
self.assertEqual(Notification.objects.count(), 0)
|
||||||
|
self.assertEqual(len(mail.outbox), 0)
|
||||||
|
|
||||||
@patch("hc.api.transports.requests.request")
|
@patch("hc.api.transports.requests.request")
|
||||||
def test_pd(self, mock_post):
|
def test_pd(self, mock_post):
|
||||||
self._setup_data("pd", "123")
|
self._setup_data("pd", "123")
|
||||||
|
@ -59,7 +59,7 @@ class Email(Transport):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
# Look up the sorting preference for this email address
|
# Look up the sorting preference for this email address
|
||||||
p = Profile.objects.get(user__email=self.channel.value)
|
p = Profile.objects.get(user__email=self.channel.email_value)
|
||||||
sort = p.sort
|
sort = p.sort
|
||||||
except Profile.DoesNotExist:
|
except Profile.DoesNotExist:
|
||||||
# Default sort order is by check's creation time
|
# Default sort order is by check's creation time
|
||||||
@ -75,10 +75,16 @@ class Email(Transport):
|
|||||||
"unsub_link": unsub_link
|
"unsub_link": unsub_link
|
||||||
}
|
}
|
||||||
|
|
||||||
emails.alert(self.channel.value, ctx, headers)
|
emails.alert(self.channel.email_value, ctx, headers)
|
||||||
|
|
||||||
def is_noop(self, check):
|
def is_noop(self, check):
|
||||||
return not self.channel.email_verified
|
if not self.channel.email_verified:
|
||||||
|
return True
|
||||||
|
|
||||||
|
if check.status == "down":
|
||||||
|
return not self.channel.email_notify_down
|
||||||
|
else:
|
||||||
|
return not self.channel.email_notify_up
|
||||||
|
|
||||||
|
|
||||||
class HttpTransport(Transport):
|
class HttpTransport(Transport):
|
||||||
|
@ -57,6 +57,8 @@ class AddOpsGenieForm(forms.Form):
|
|||||||
class AddEmailForm(forms.Form):
|
class AddEmailForm(forms.Form):
|
||||||
error_css_class = "has-error"
|
error_css_class = "has-error"
|
||||||
value = forms.EmailField(max_length=100)
|
value = forms.EmailField(max_length=100)
|
||||||
|
down = forms.BooleanField(required=False, initial=True)
|
||||||
|
up = forms.BooleanField(required=False, initial=True)
|
||||||
|
|
||||||
|
|
||||||
class AddUrlForm(forms.Form):
|
class AddUrlForm(forms.Form):
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import json
|
||||||
|
|
||||||
from django.core import mail
|
from django.core import mail
|
||||||
from django.test.utils import override_settings
|
from django.test.utils import override_settings
|
||||||
|
|
||||||
@ -12,7 +14,7 @@ class AddEmailTestCase(BaseTestCase):
|
|||||||
self.client.login(username="alice@example.org", password="password")
|
self.client.login(username="alice@example.org", password="password")
|
||||||
r = self.client.get(self.url)
|
r = self.client.get(self.url)
|
||||||
self.assertContains(r, "Get an email message")
|
self.assertContains(r, "Get an email message")
|
||||||
self.assertContains(r, "Confirmation needed")
|
self.assertContains(r, "Requires confirmation")
|
||||||
|
|
||||||
def test_it_creates_channel(self):
|
def test_it_creates_channel(self):
|
||||||
form = {"value": "alice@example.org"}
|
form = {"value": "alice@example.org"}
|
||||||
@ -22,8 +24,9 @@ class AddEmailTestCase(BaseTestCase):
|
|||||||
self.assertRedirects(r, "/integrations/")
|
self.assertRedirects(r, "/integrations/")
|
||||||
|
|
||||||
c = Channel.objects.get()
|
c = Channel.objects.get()
|
||||||
|
doc = json.loads(c.value)
|
||||||
self.assertEqual(c.kind, "email")
|
self.assertEqual(c.kind, "email")
|
||||||
self.assertEqual(c.value, "alice@example.org")
|
self.assertEqual(doc["value"], "alice@example.org")
|
||||||
self.assertFalse(c.email_verified)
|
self.assertFalse(c.email_verified)
|
||||||
self.assertEqual(c.project, self.project)
|
self.assertEqual(c.project, self.project)
|
||||||
|
|
||||||
@ -32,6 +35,8 @@ class AddEmailTestCase(BaseTestCase):
|
|||||||
|
|
||||||
email = mail.outbox[0]
|
email = mail.outbox[0]
|
||||||
self.assertTrue(email.subject.startswith("Verify email address on"))
|
self.assertTrue(email.subject.startswith("Verify email address on"))
|
||||||
|
# Make sure we're sending to an email address, not a JSON string:
|
||||||
|
self.assertEqual(email.to[0], "alice@example.org")
|
||||||
|
|
||||||
def test_team_access_works(self):
|
def test_team_access_works(self):
|
||||||
form = {"value": "bob@example.org"}
|
form = {"value": "bob@example.org"}
|
||||||
@ -57,13 +62,14 @@ class AddEmailTestCase(BaseTestCase):
|
|||||||
self.client.post(self.url, form)
|
self.client.post(self.url, form)
|
||||||
|
|
||||||
c = Channel.objects.get()
|
c = Channel.objects.get()
|
||||||
self.assertEqual(c.value, "alice@example.org")
|
doc = json.loads(c.value)
|
||||||
|
self.assertEqual(doc["value"], "alice@example.org")
|
||||||
|
|
||||||
@override_settings(EMAIL_USE_VERIFICATION=False)
|
@override_settings(EMAIL_USE_VERIFICATION=False)
|
||||||
def test_it_hides_confirmation_needed_notice(self):
|
def test_it_hides_confirmation_needed_notice(self):
|
||||||
self.client.login(username="alice@example.org", password="password")
|
self.client.login(username="alice@example.org", password="password")
|
||||||
r = self.client.get(self.url)
|
r = self.client.get(self.url)
|
||||||
self.assertNotContains(r, "Confirmation needed")
|
self.assertNotContains(r, "Requires confirmation")
|
||||||
|
|
||||||
@override_settings(EMAIL_USE_VERIFICATION=False)
|
@override_settings(EMAIL_USE_VERIFICATION=False)
|
||||||
def test_it_auto_verifies_email(self):
|
def test_it_auto_verifies_email(self):
|
||||||
@ -74,8 +80,9 @@ class AddEmailTestCase(BaseTestCase):
|
|||||||
self.assertRedirects(r, "/integrations/")
|
self.assertRedirects(r, "/integrations/")
|
||||||
|
|
||||||
c = Channel.objects.get()
|
c = Channel.objects.get()
|
||||||
|
doc = json.loads(c.value)
|
||||||
self.assertEqual(c.kind, "email")
|
self.assertEqual(c.kind, "email")
|
||||||
self.assertEqual(c.value, "alice@example.org")
|
self.assertEqual(doc["value"], "alice@example.org")
|
||||||
self.assertTrue(c.email_verified)
|
self.assertTrue(c.email_verified)
|
||||||
|
|
||||||
# Email should *not* have been sent
|
# Email should *not* have been sent
|
||||||
|
@ -74,6 +74,34 @@ class ChannelsTestCase(BaseTestCase):
|
|||||||
self.assertEqual(r.status_code, 200)
|
self.assertEqual(r.status_code, 200)
|
||||||
self.assertContains(r, "Unconfirmed")
|
self.assertContains(r, "Unconfirmed")
|
||||||
|
|
||||||
|
def test_it_shows_down_only_note_for_email(self):
|
||||||
|
channel = Channel(project=self.project, kind="email")
|
||||||
|
channel.value = json.dumps({
|
||||||
|
"value": "alice@example.org",
|
||||||
|
"up": False,
|
||||||
|
"down": True
|
||||||
|
})
|
||||||
|
channel.save()
|
||||||
|
|
||||||
|
self.client.login(username="alice@example.org", password="password")
|
||||||
|
r = self.client.get("/integrations/")
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
self.assertContains(r, "(down only)")
|
||||||
|
|
||||||
|
def test_it_shows_up_only_note_for_email(self):
|
||||||
|
channel = Channel(project=self.project, kind="email")
|
||||||
|
channel.value = json.dumps({
|
||||||
|
"value": "alice@example.org",
|
||||||
|
"up": True,
|
||||||
|
"down": False
|
||||||
|
})
|
||||||
|
channel.save()
|
||||||
|
|
||||||
|
self.client.login(username="alice@example.org", password="password")
|
||||||
|
r = self.client.get("/integrations/")
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
self.assertContains(r, "(up only)")
|
||||||
|
|
||||||
def test_it_shows_sms_label(self):
|
def test_it_shows_sms_label(self):
|
||||||
ch = Channel(kind="sms", project=self.project)
|
ch = Channel(kind="sms", project=self.project)
|
||||||
ch.value = json.dumps({"value": "+123", "label": "My Phone"})
|
ch.value = json.dumps({"value": "+123", "label": "My Phone"})
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import json
|
||||||
|
|
||||||
from hc.api.models import Channel, Check, Notification, Ping
|
from hc.api.models import Channel, Check, Notification, Ping
|
||||||
from hc.test import BaseTestCase
|
from hc.test import BaseTestCase
|
||||||
|
|
||||||
@ -15,20 +17,20 @@ class LogTestCase(BaseTestCase):
|
|||||||
ping.created = "2000-01-01T00:00:00+00:00"
|
ping.created = "2000-01-01T00:00:00+00:00"
|
||||||
ping.save()
|
ping.save()
|
||||||
|
|
||||||
|
self.url = "/checks/%s/log/" % self.check.code
|
||||||
|
|
||||||
def test_it_works(self):
|
def test_it_works(self):
|
||||||
url = "/checks/%s/log/" % self.check.code
|
|
||||||
|
|
||||||
self.client.login(username="alice@example.org", password="password")
|
self.client.login(username="alice@example.org", password="password")
|
||||||
r = self.client.get(url)
|
r = self.client.get(self.url)
|
||||||
self.assertContains(r, "Local Time", status_code=200)
|
self.assertContains(r, "Local Time", status_code=200)
|
||||||
|
|
||||||
def test_team_access_works(self):
|
def test_team_access_works(self):
|
||||||
url = "/checks/%s/log/" % self.check.code
|
|
||||||
|
|
||||||
# Logging in as bob, not alice. Bob has team access so this
|
# Logging in as bob, not alice. Bob has team access so this
|
||||||
# should work.
|
# should work.
|
||||||
self.client.login(username="bob@example.org", password="password")
|
self.client.login(username="bob@example.org", password="password")
|
||||||
r = self.client.get(url)
|
r = self.client.get(self.url)
|
||||||
self.assertEqual(r.status_code, 200)
|
self.assertEqual(r.status_code, 200)
|
||||||
|
|
||||||
def test_it_handles_bad_uuid(self):
|
def test_it_handles_bad_uuid(self):
|
||||||
@ -47,40 +49,50 @@ class LogTestCase(BaseTestCase):
|
|||||||
self.assertEqual(r.status_code, 404)
|
self.assertEqual(r.status_code, 404)
|
||||||
|
|
||||||
def test_it_checks_ownership(self):
|
def test_it_checks_ownership(self):
|
||||||
url = "/checks/%s/log/" % self.check.code
|
|
||||||
self.client.login(username="charlie@example.org", password="password")
|
self.client.login(username="charlie@example.org", password="password")
|
||||||
r = self.client.get(url)
|
r = self.client.get(self.url)
|
||||||
self.assertEqual(r.status_code, 404)
|
self.assertEqual(r.status_code, 404)
|
||||||
|
|
||||||
def test_it_shows_pushover_notifications(self):
|
def test_it_shows_email_notification(self):
|
||||||
|
ch = Channel(kind="email", project=self.project)
|
||||||
|
ch.value = json.dumps({
|
||||||
|
"value": "alice@example.org",
|
||||||
|
"up": True,
|
||||||
|
"down": True
|
||||||
|
})
|
||||||
|
ch.save()
|
||||||
|
|
||||||
|
Notification(owner=self.check, channel=ch, check_status="down").save()
|
||||||
|
|
||||||
|
self.client.login(username="alice@example.org", password="password")
|
||||||
|
r = self.client.get(self.url)
|
||||||
|
self.assertContains(r, "Sent email alert to alice@example.org",
|
||||||
|
status_code=200)
|
||||||
|
|
||||||
|
def test_it_shows_pushover_notification(self):
|
||||||
ch = Channel.objects.create(kind="po", project=self.project)
|
ch = Channel.objects.create(kind="po", project=self.project)
|
||||||
|
|
||||||
Notification(owner=self.check, channel=ch, check_status="down").save()
|
Notification(owner=self.check, channel=ch, check_status="down").save()
|
||||||
|
|
||||||
url = "/checks/%s/log/" % self.check.code
|
|
||||||
|
|
||||||
self.client.login(username="alice@example.org", password="password")
|
self.client.login(username="alice@example.org", password="password")
|
||||||
r = self.client.get(url)
|
r = self.client.get(self.url)
|
||||||
self.assertContains(r, "Sent a Pushover notification", status_code=200)
|
self.assertContains(r, "Sent a Pushover notification", status_code=200)
|
||||||
|
|
||||||
def test_it_shows_webhook_notifications(self):
|
def test_it_shows_webhook_notification(self):
|
||||||
ch = Channel(kind="webhook", project=self.project)
|
ch = Channel(kind="webhook", project=self.project)
|
||||||
ch.value = "foo/$NAME"
|
ch.value = "foo/$NAME"
|
||||||
ch.save()
|
ch.save()
|
||||||
|
|
||||||
Notification(owner=self.check, channel=ch, check_status="down").save()
|
Notification(owner=self.check, channel=ch, check_status="down").save()
|
||||||
|
|
||||||
url = "/checks/%s/log/" % self.check.code
|
|
||||||
|
|
||||||
self.client.login(username="alice@example.org", password="password")
|
self.client.login(username="alice@example.org", password="password")
|
||||||
r = self.client.get(url)
|
r = self.client.get(self.url)
|
||||||
self.assertContains(r, "Called webhook foo/$NAME", status_code=200)
|
self.assertContains(r, "Called webhook foo/$NAME", status_code=200)
|
||||||
|
|
||||||
def test_it_allows_cross_team_access(self):
|
def test_it_allows_cross_team_access(self):
|
||||||
self.bobs_profile.current_project = None
|
self.bobs_profile.current_project = None
|
||||||
self.bobs_profile.save()
|
self.bobs_profile.save()
|
||||||
|
|
||||||
url = "/checks/%s/log/" % self.check.code
|
|
||||||
self.client.login(username="bob@example.org", password="password")
|
self.client.login(username="bob@example.org", password="password")
|
||||||
r = self.client.get(url)
|
r = self.client.get(self.url)
|
||||||
self.assertEqual(r.status_code, 200)
|
self.assertEqual(r.status_code, 200)
|
||||||
|
@ -678,7 +678,11 @@ def add_email(request):
|
|||||||
form = AddEmailForm(request.POST)
|
form = AddEmailForm(request.POST)
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
channel = Channel(project=request.project, kind="email")
|
channel = Channel(project=request.project, kind="email")
|
||||||
channel.value = form.cleaned_data["value"]
|
channel.value = json.dumps({
|
||||||
|
"value": form.cleaned_data["value"],
|
||||||
|
"up": form.cleaned_data["up"],
|
||||||
|
"down": form.cleaned_data["down"]
|
||||||
|
})
|
||||||
channel.save()
|
channel.save()
|
||||||
|
|
||||||
channel.assign_all_checks()
|
channel.assign_all_checks()
|
||||||
|
@ -77,13 +77,13 @@ body {
|
|||||||
.status.icon-grace { color: #f0ad4e; }
|
.status.icon-grace { color: #f0ad4e; }
|
||||||
.status.icon-down { color: #d9534f; }
|
.status.icon-down { color: #d9534f; }
|
||||||
|
|
||||||
.label-start {
|
.label.label-start {
|
||||||
background-color: #FFF;
|
background-color: #FFF;
|
||||||
color: #117a3f;;
|
color: #117a3f;;
|
||||||
border: 1px solid #117a3f;
|
border: 1px solid #117a3f;
|
||||||
}
|
}
|
||||||
|
|
||||||
.label-ign {
|
.label.label-ign {
|
||||||
background: #e0e0e0;
|
background: #e0e0e0;
|
||||||
color: #333;
|
color: #333;
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,11 @@
|
|||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.form-horizontal .radio-container, .form-horizontal .checkbox-container {
|
||||||
|
margin-top: 7px;
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* Hide the browser's default radio button */
|
/* Hide the browser's default radio button */
|
||||||
.radio-container input {
|
.radio-container input {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
@ -73,7 +73,7 @@
|
|||||||
</label>
|
</label>
|
||||||
|
|
||||||
<br />
|
<br />
|
||||||
<p style="color: #888">
|
<p class="text-muted">
|
||||||
Reports will be delivered to {{ profile.user.email }}. <br />
|
Reports will be delivered to {{ profile.user.email }}. <br />
|
||||||
{% if profile.next_report_date %}
|
{% if profile.next_report_date %}
|
||||||
Next monthly report date is
|
Next monthly report date is
|
||||||
|
@ -16,35 +16,35 @@
|
|||||||
<link rel="apple-touch-icon" sizes="180x180" href="{% static 'img/apple-touch-180.png' %}">
|
<link rel="apple-touch-icon" sizes="180x180" href="{% static 'img/apple-touch-180.png' %}">
|
||||||
|
|
||||||
{% compress css %}
|
{% compress css %}
|
||||||
<link rel="stylesheet" href="{% static 'css/bootstrap.css' %}" type="text/css">
|
<link rel="stylesheet" href="{% static 'css/add_project_modal.css' %}" type="text/css">
|
||||||
<link rel="stylesheet" href="{% static 'css/icomoon.css' %}" type="text/css">
|
|
||||||
<link rel="stylesheet" href="{% static 'css/nouislider.min.css' %}" type="text/css">
|
|
||||||
<link rel="stylesheet" href="{% static 'css/nouislider.pips.css' %}" type="text/css">
|
|
||||||
<link rel="stylesheet" href="{% static 'css/bootstrap-select.min.css' %}" type="text/css">
|
|
||||||
<link rel="stylesheet" href="{% static 'css/snippet-copy.css' %}" type="text/css">
|
|
||||||
<link rel="stylesheet" href="{% static 'css/base.css' %}" type="text/css">
|
|
||||||
<link rel="stylesheet" href="{% static 'css/docs.css' %}" type="text/css">
|
|
||||||
<link rel="stylesheet" href="{% static 'css/docs_cron.css' %}" type="text/css">
|
|
||||||
<link rel="stylesheet" href="{% static 'css/welcome.css' %}" type="text/css">
|
|
||||||
<link rel="stylesheet" href="{% static 'css/my_checks.css' %}" type="text/css">
|
|
||||||
<link rel="stylesheet" href="{% static 'css/my_checks_desktop.css' %}" type="text/css">
|
|
||||||
<link rel="stylesheet" href="{% static 'css/pricing.css' %}" type="text/css">
|
|
||||||
<link rel="stylesheet" href="{% static 'css/syntax.css' %}" type="text/css">
|
|
||||||
<link rel="stylesheet" href="{% static 'css/channels.css' %}" type="text/css">
|
|
||||||
<link rel="stylesheet" href="{% static 'css/channel_checks.css' %}" type="text/css">
|
|
||||||
<link rel="stylesheet" href="{% static 'css/details.css' %}" type="text/css">
|
|
||||||
<link rel="stylesheet" href="{% static 'css/log.css' %}" type="text/css">
|
|
||||||
<link rel="stylesheet" href="{% static 'css/add_pushover.css' %}" type="text/css">
|
<link rel="stylesheet" href="{% static 'css/add_pushover.css' %}" type="text/css">
|
||||||
<link rel="stylesheet" href="{% static 'css/add_webhook.css' %}" type="text/css">
|
<link rel="stylesheet" href="{% static 'css/add_webhook.css' %}" type="text/css">
|
||||||
<link rel="stylesheet" href="{% static 'css/settings.css' %}" type="text/css">
|
<link rel="stylesheet" href="{% static 'css/base.css' %}" type="text/css">
|
||||||
<link rel="stylesheet" href="{% static 'css/ping_details.css' %}" type="text/css">
|
|
||||||
<link rel="stylesheet" href="{% static 'css/profile.css' %}" type="text/css">
|
|
||||||
<link rel="stylesheet" href="{% static 'css/checkbox.css' %}" type="text/css">
|
|
||||||
<link rel="stylesheet" href="{% static 'css/radio.css' %}" type="text/css">
|
|
||||||
<link rel="stylesheet" href="{% static 'css/billing.css' %}" type="text/css">
|
<link rel="stylesheet" href="{% static 'css/billing.css' %}" type="text/css">
|
||||||
|
<link rel="stylesheet" href="{% static 'css/bootstrap-select.min.css' %}" type="text/css">
|
||||||
|
<link rel="stylesheet" href="{% static 'css/bootstrap.css' %}" type="text/css">
|
||||||
|
<link rel="stylesheet" href="{% static 'css/channel_checks.css' %}" type="text/css">
|
||||||
|
<link rel="stylesheet" href="{% static 'css/channels.css' %}" type="text/css">
|
||||||
|
<link rel="stylesheet" href="{% static 'css/checkbox.css' %}" type="text/css">
|
||||||
|
<link rel="stylesheet" href="{% static 'css/details.css' %}" type="text/css">
|
||||||
|
<link rel="stylesheet" href="{% static 'css/docs.css' %}" type="text/css">
|
||||||
|
<link rel="stylesheet" href="{% static 'css/docs_cron.css' %}" type="text/css">
|
||||||
|
<link rel="stylesheet" href="{% static 'css/icomoon.css' %}" type="text/css">
|
||||||
|
<link rel="stylesheet" href="{% static 'css/log.css' %}" type="text/css">
|
||||||
<link rel="stylesheet" href="{% static 'css/login.css' %}" type="text/css">
|
<link rel="stylesheet" href="{% static 'css/login.css' %}" type="text/css">
|
||||||
|
<link rel="stylesheet" href="{% static 'css/my_checks.css' %}" type="text/css">
|
||||||
|
<link rel="stylesheet" href="{% static 'css/my_checks_desktop.css' %}" type="text/css">
|
||||||
|
<link rel="stylesheet" href="{% static 'css/nouislider.min.css' %}" type="text/css">
|
||||||
|
<link rel="stylesheet" href="{% static 'css/nouislider.pips.css' %}" type="text/css">
|
||||||
|
<link rel="stylesheet" href="{% static 'css/ping_details.css' %}" type="text/css">
|
||||||
|
<link rel="stylesheet" href="{% static 'css/pricing.css' %}" type="text/css">
|
||||||
|
<link rel="stylesheet" href="{% static 'css/profile.css' %}" type="text/css">
|
||||||
<link rel="stylesheet" href="{% static 'css/projects.css' %}" type="text/css">
|
<link rel="stylesheet" href="{% static 'css/projects.css' %}" type="text/css">
|
||||||
<link rel="stylesheet" href="{% static 'css/add_project_modal.css' %}" type="text/css">
|
<link rel="stylesheet" href="{% static 'css/radio.css' %}" type="text/css">
|
||||||
|
<link rel="stylesheet" href="{% static 'css/settings.css' %}" type="text/css">
|
||||||
|
<link rel="stylesheet" href="{% static 'css/snippet-copy.css' %}" type="text/css">
|
||||||
|
<link rel="stylesheet" href="{% static 'css/syntax.css' %}" type="text/css">
|
||||||
|
<link rel="stylesheet" href="{% static 'css/welcome.css' %}" type="text/css">
|
||||||
{% endcompress %}
|
{% endcompress %}
|
||||||
</head>
|
</head>
|
||||||
<body class="page-{{ page }}">
|
<body class="page-{{ page }}">
|
||||||
|
@ -42,7 +42,13 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="channel-details-mini">
|
<div class="channel-details-mini">
|
||||||
{% if ch.kind == "email" %}
|
{% if ch.kind == "email" %}
|
||||||
Email to <span>{{ ch.value }}</span>
|
Email to <span>{{ ch.email_value }}</span>
|
||||||
|
{% if ch.email_notify_down and not ch.email_notify_up %}
|
||||||
|
(down only)
|
||||||
|
{% endif %}
|
||||||
|
{% if ch.email_notify_up and not ch.email_notify_down %}
|
||||||
|
(up only)
|
||||||
|
{% endif %}
|
||||||
{% elif ch.kind == "pd" %}
|
{% elif ch.kind == "pd" %}
|
||||||
PagerDuty account <span>{{ ch.pd_account }}</span>
|
PagerDuty account <span>{{ ch.pd_account }}</span>
|
||||||
{% elif ch.kind == "pagertree" %}
|
{% elif ch.kind == "pagertree" %}
|
||||||
|
@ -54,7 +54,7 @@
|
|||||||
<td class="time"></td>
|
<td class="time"></td>
|
||||||
<td class="alert-info" colspan="2">
|
<td class="alert-info" colspan="2">
|
||||||
{% if event.channel.kind == "email" %}
|
{% if event.channel.kind == "email" %}
|
||||||
Sent email alert to {{ event.channel.value }}
|
Sent email alert to {{ event.channel.email_value }}
|
||||||
{% elif event.channel.kind == "slack" %}
|
{% elif event.channel.kind == "slack" %}
|
||||||
Sent Slack alert
|
Sent Slack alert
|
||||||
{% if event.channel.slack_channel %}
|
{% if event.channel.slack_channel %}
|
||||||
|
@ -97,7 +97,7 @@
|
|||||||
<td class="time"></td>
|
<td class="time"></td>
|
||||||
<td class="alert-info" colspan="2">
|
<td class="alert-info" colspan="2">
|
||||||
{% if event.channel.kind == "email" %}
|
{% if event.channel.kind == "email" %}
|
||||||
Sent email alert to {{ event.channel.value }}
|
Sent email alert to {{ event.channel.email_value }}
|
||||||
{% elif event.channel.kind == "slack" %}
|
{% elif event.channel.kind == "slack" %}
|
||||||
Sent Slack alert
|
Sent Slack alert
|
||||||
{% if event.channel.slack_channel %}
|
{% if event.channel.slack_channel %}
|
||||||
|
@ -1,26 +1,20 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% load humanize static hc_extras %}
|
{% load humanize static hc_extras %}
|
||||||
|
|
||||||
{% block title %}Notification Channels - {% site_name %}{% endblock %}
|
{% block title %}Set Up Email Notifications - {% site_name %}{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-12">
|
<div class="col-sm-12">
|
||||||
<h1>Email</h1>
|
<h1>Email</h1>
|
||||||
|
|
||||||
<p>Get an email message when check goes up or down.</p>
|
<p>Get an email message when check goes up or down.</p>
|
||||||
|
|
||||||
<p>
|
|
||||||
<strong>Tip:</strong>
|
|
||||||
Add multiple email addresses, to notify multiple team members.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
{% if use_verification %}
|
{% if use_verification %}
|
||||||
<p>
|
<p class="alert alert-info">
|
||||||
<strong>Confirmation needed.</strong>
|
<strong>Requires confirmation.</strong>
|
||||||
After entering an email address, {% site_name %} will send out a confirmation link.
|
After entering an email address, {% site_name %} will send out a confirmation link.
|
||||||
Only confirmed addresses will receive notifications.
|
Only confirmed addresses receive notifications.
|
||||||
</p>
|
</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
@ -37,6 +31,7 @@
|
|||||||
class="form-control"
|
class="form-control"
|
||||||
name="value"
|
name="value"
|
||||||
placeholder="you@example.org"
|
placeholder="you@example.org"
|
||||||
|
required
|
||||||
value="{{ form.value.value|default:"" }}">
|
value="{{ form.value.value|default:"" }}">
|
||||||
|
|
||||||
{% if form.value.errors %}
|
{% if form.value.errors %}
|
||||||
@ -46,6 +41,36 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="add-email-notify-group" class="form-group">
|
||||||
|
<label class="col-sm-2 control-label">Notify When</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<label class="checkbox-container">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
name="down"
|
||||||
|
value="true"
|
||||||
|
{% if form.down.value %} checked {% endif %}>
|
||||||
|
<span class="checkmark"></span>
|
||||||
|
A check goes <strong>down</strong>
|
||||||
|
</label>
|
||||||
|
<label class="checkbox-container">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
name="up"
|
||||||
|
value="true"
|
||||||
|
{% if form.up.value %} checked {% endif %}>
|
||||||
|
<span class="checkmark"></span>
|
||||||
|
A check goes <strong>up</strong>
|
||||||
|
<br />
|
||||||
|
<span class="text-muted">
|
||||||
|
Ticketing system integrations: untick this and avoid creating new tickets
|
||||||
|
when checks come back up.
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="col-sm-offset-2 col-sm-10">
|
<div class="col-sm-offset-2 col-sm-10">
|
||||||
<button type="submit" class="btn btn-primary">Save Integration</button>
|
<button type="submit" class="btn btn-primary">Save Integration</button>
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-12">
|
<div class="col-sm-12">
|
||||||
|
<h1>Pushbover</h1>
|
||||||
<div class="jumbotron">
|
<div class="jumbotron">
|
||||||
{% if request.user.is_authenticated %}
|
{% if request.user.is_authenticated %}
|
||||||
<p>
|
<p>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user