Browse Source

Adding an option to send daily or hourly reminders if any check is down. Fixes #48

pull/140/head
Pēteris Caune 7 years ago
parent
commit
d520706c27
21 changed files with 549 additions and 77 deletions
  1. +2
    -1
      hc/accounts/admin.py
  2. +10
    -0
      hc/accounts/forms.py
  3. +26
    -0
      hc/accounts/migrations/0012_auto_20171014_1002.py
  4. +38
    -8
      hc/accounts/models.py
  5. +44
    -8
      hc/accounts/tests/test_notifications.py
  6. +49
    -13
      hc/accounts/tests/test_profile.py
  7. +6
    -0
      hc/accounts/tests/test_unsubscribe_reports.py
  8. +19
    -1
      hc/accounts/views.py
  9. +6
    -1
      hc/api/management/commands/sendalerts.py
  10. +54
    -23
      hc/api/management/commands/sendreports.py
  11. +30
    -2
      hc/api/tests/test_sendalerts.py
  12. +48
    -9
      hc/api/tests/test_sendreports.py
  13. +67
    -0
      static/css/checkbox.css
  14. +64
    -0
      static/css/radio.css
  15. +1
    -1
      templates/accounts/badges.html
  16. +51
    -5
      templates/accounts/notifications.html
  17. +1
    -1
      templates/accounts/profile.html
  18. +2
    -0
      templates/base.html
  19. +22
    -2
      templates/emails/report-body-html.html
  20. +4
    -1
      templates/emails/report-body-text.html
  21. +5
    -1
      templates/emails/report-subject.html

+ 2
- 1
hc/accounts/admin.py View File

@ -21,7 +21,8 @@ class Fieldset:
class ProfileFieldset(Fieldset):
name = "User Profile"
fields = ("email", "api_key", "current_team", "reports_allowed",
"next_report_date", "token", "sort")
"next_report_date", "nag_period", "next_nag_date",
"token", "sort")
class TeamFieldset(Fieldset):


+ 10
- 0
hc/accounts/forms.py View File

@ -1,3 +1,4 @@
from datetime import timedelta as td
from django import forms
from django.contrib.auth.models import User
@ -16,6 +17,15 @@ class EmailPasswordForm(forms.Form):
class ReportSettingsForm(forms.Form):
reports_allowed = forms.BooleanField(required=False)
nag_period = forms.IntegerField(min_value=0, max_value=86400)
def clean_nag_period(self):
seconds = self.cleaned_data["nag_period"]
if seconds not in (0, 3600, 86400):
raise forms.ValidationError("Bad nag_period: %d" % seconds)
return td(seconds=seconds)
class SetPasswordForm(forms.Form):


+ 26
- 0
hc/accounts/migrations/0012_auto_20171014_1002.py View File

@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.6 on 2017-10-14 10:02
from __future__ import unicode_literals
import datetime
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('accounts', '0011_profile_sort'),
]
operations = [
migrations.AddField(
model_name='profile',
name='nag_period',
field=models.DurationField(choices=[(datetime.timedelta(0), 'Disabled'), (datetime.timedelta(0, 3600), 'Hourly'), (datetime.timedelta(1), 'Daily')], default=datetime.timedelta(0)),
),
migrations.AddField(
model_name='profile',
name='next_nag_date',
field=models.DateTimeField(blank=True, null=True),
),
]

+ 38
- 8
hc/accounts/models.py View File

@ -13,6 +13,12 @@ from django.utils import timezone
from hc.lib import emails
NO_NAG = timedelta()
NAG_PERIODS = ((NO_NAG, "Disabled"),
(timedelta(hours=1), "Hourly"),
(timedelta(days=1), "Daily"))
def month(dt):
""" For a given datetime, return the matching first-day-of-month date. """
return dt.date().replace(day=1)
@ -23,7 +29,7 @@ class ProfileManager(models.Manager):
try:
return user.profile
except Profile.DoesNotExist:
profile = Profile(user=user, team_access_allowed=user.is_superuser)
profile = Profile(user=user)
if not settings.USE_PAYMENTS:
# If not using payments, set high limits
profile.check_limit = 500
@ -41,6 +47,8 @@ class Profile(models.Model):
team_access_allowed = models.BooleanField(default=False)
next_report_date = models.DateTimeField(null=True, blank=True)
reports_allowed = models.BooleanField(default=True)
nag_period = models.DurationField(default=NO_NAG, choices=NAG_PERIODS)
next_nag_date = models.DateTimeField(null=True, blank=True)
ping_log_limit = models.IntegerField(default=100)
check_limit = models.IntegerField(default=20)
token = models.CharField(max_length=128, blank=True)
@ -58,6 +66,9 @@ class Profile(models.Model):
def __str__(self):
return self.team_name or self.user.email
def notifications_url(self):
return settings.SITE_ROOT + reverse("hc-notifications")
def team(self):
# compare ids to avoid SQL queries
if self.current_team_id and self.current_team_id != self.id:
@ -106,11 +117,15 @@ class Profile(models.Model):
self.api_key = base64.urlsafe_b64encode(os.urandom(24))
self.save()
def send_report(self):
# reset next report date first:
now = timezone.now()
self.next_report_date = now + timedelta(days=30)
self.save()
def send_report(self, nag=False):
# Are there any non-new checks in the account?
q = self.user.check_set.filter(last_ping__isnull=False)
if not q.exists():
return False
num_down = q.filter(status="down").count()
if nag and num_down == 0:
return False
token = signing.Signer().sign(uuid.uuid4())
path = reverse("hc-unsubscribe-reports", args=[self.user.username])
@ -118,11 +133,16 @@ class Profile(models.Model):
ctx = {
"checks": self.user.check_set.order_by("created"),
"now": now,
"unsub_link": unsub_link
"now": timezone.now(),
"unsub_link": unsub_link,
"notifications_url": self.notifications_url,
"nag": nag,
"nag_period": self.nag_period.total_seconds(),
"num_down": num_down
}
emails.report(self.user.email, ctx)
return True
def can_invite(self):
return self.member_set.count() < self.team_limit
@ -161,6 +181,16 @@ class Profile(models.Model):
self.save()
return True
def set_next_nag_date(self):
""" Set next_nag_date for all members of this team. """
is_owner = models.Q(id=self.id)
is_member = models.Q(user__member__team=self)
q = Profile.objects.filter(is_owner | is_member)
q = q.exclude(nag_period=NO_NAG)
q.update(next_nag_date=timezone.now() + models.F("nag_period"))
class Member(models.Model):
team = models.ForeignKey(Profile, models.CASCADE)


+ 44
- 8
hc/accounts/tests/test_notifications.py View File

@ -1,24 +1,60 @@
from datetime import timedelta as td
from django.utils.timezone import now
from hc.test import BaseTestCase
class NotificationsTestCase(BaseTestCase):
def test_it_saves_reports_allowed_true(self):
self.profile.reports_allowed = False
self.profile.save()
self.client.login(username="[email protected]", password="password")
form = {"reports_allowed": "on"}
form = {"reports_allowed": "on", "nag_period": "0"}
r = self.client.post("/accounts/profile/notifications/", form)
assert r.status_code == 200
self.assertEqual(r.status_code, 200)
self.alice.profile.refresh_from_db()
self.assertTrue(self.alice.profile.reports_allowed)
self.profile.refresh_from_db()
self.assertTrue(self.profile.reports_allowed)
self.assertIsNotNone(self.profile.next_report_date)
def test_it_saves_reports_allowed_false(self):
self.profile.reports_allowed = True
self.profile.next_report_date = now()
self.profile.save()
self.client.login(username="[email protected]", password="password")
form = {"nag_period": "0"}
r = self.client.post("/accounts/profile/notifications/", form)
self.assertEqual(r.status_code, 200)
self.profile.refresh_from_db()
self.assertFalse(self.profile.reports_allowed)
self.assertIsNone(self.profile.next_report_date)
def test_it_saves_hourly_nag_period(self):
self.client.login(username="[email protected]", password="password")
form = {"nag_period": "3600"}
r = self.client.post("/accounts/profile/notifications/", form)
self.assertEqual(r.status_code, 200)
self.profile.refresh_from_db()
self.assertEqual(self.profile.nag_period.total_seconds(), 3600)
self.assertIsNotNone(self.profile.next_nag_date)
def test_it_does_not_save_nonstandard_nag_period(self):
self.profile.nag_period = td(seconds=3600)
self.profile.save()
self.client.login(username="[email protected]", password="password")
form = {}
form = {"nag_period": "1234"}
r = self.client.post("/accounts/profile/notifications/", form)
assert r.status_code == 200
self.assertEqual(r.status_code, 200)
self.alice.profile.refresh_from_db()
self.assertFalse(self.alice.profile.reports_allowed)
self.profile.refresh_from_db()
self.assertEqual(self.profile.nag_period.total_seconds(), 3600)

+ 49
- 13
hc/accounts/tests/test_profile.py View File

@ -1,9 +1,11 @@
from datetime import timedelta as td
from django.core import mail
from django.conf import settings
from django.utils.timezone import now
from hc.test import BaseTestCase
from hc.accounts.models import Member
from hc.api.models import Check
from django.conf import settings
class ProfileTestCase(BaseTestCase):
@ -16,8 +18,8 @@ class ProfileTestCase(BaseTestCase):
assert r.status_code == 302
# profile.token should be set now
self.alice.profile.refresh_from_db()
token = self.alice.profile.token
self.profile.refresh_from_db()
token = self.profile.token
self.assertTrue(len(token) > 10)
# And an email should have been sent
@ -32,8 +34,8 @@ class ProfileTestCase(BaseTestCase):
r = self.client.post("/accounts/profile/", form)
self.assertEqual(r.status_code, 200)
self.alice.profile.refresh_from_db()
api_key = self.alice.profile.api_key
self.profile.refresh_from_db()
api_key = self.profile.api_key
self.assertTrue(len(api_key) > 10)
def test_it_revokes_api_key(self):
@ -43,14 +45,16 @@ class ProfileTestCase(BaseTestCase):
r = self.client.post("/accounts/profile/", form)
assert r.status_code == 200
self.alice.profile.refresh_from_db()
self.assertEqual(self.alice.profile.api_key, "")
self.profile.refresh_from_db()
self.assertEqual(self.profile.api_key, "")
def test_it_sends_report(self):
check = Check(name="Test Check", user=self.alice)
check.last_ping = now()
check.save()
self.alice.profile.send_report()
sent = self.profile.send_report()
self.assertTrue(sent)
# And an email should have been sent
self.assertEqual(len(mail.outbox), 1)
@ -59,6 +63,38 @@ class ProfileTestCase(BaseTestCase):
self.assertEqual(message.subject, 'Monthly Report')
self.assertIn("Test Check", message.body)
def test_it_sends_nag(self):
check = Check(name="Test Check", user=self.alice)
check.status = "down"
check.last_ping = now()
check.save()
self.profile.nag_period = td(hours=1)
self.profile.save()
sent = self.profile.send_report(nag=True)
self.assertTrue(sent)
# And an email should have been sent
self.assertEqual(len(mail.outbox), 1)
message = mail.outbox[0]
self.assertEqual(message.subject, 'Reminder: 1 check still down')
self.assertIn("Test Check", message.body)
def test_it_skips_nag_if_none_down(self):
check = Check(name="Test Check", user=self.alice)
check.last_ping = now()
check.save()
self.profile.nag_period = td(hours=1)
self.profile.save()
sent = self.profile.send_report(nag=True)
self.assertFalse(sent)
self.assertEqual(len(mail.outbox), 0)
def test_it_adds_team_member(self):
self.client.login(username="[email protected]", password="password")
@ -67,7 +103,7 @@ class ProfileTestCase(BaseTestCase):
self.assertEqual(r.status_code, 200)
member_emails = set()
for member in self.alice.profile.member_set.all():
for member in self.profile.member_set.all():
member_emails.add(member.user.email)
self.assertEqual(len(member_emails), 2)
@ -107,8 +143,8 @@ class ProfileTestCase(BaseTestCase):
r = self.client.post("/accounts/profile/", form)
self.assertEqual(r.status_code, 200)
self.alice.profile.refresh_from_db()
self.assertEqual(self.alice.profile.team_name, "Alpha Team")
self.profile.refresh_from_db()
self.assertEqual(self.profile.team_name, "Alpha Team")
def test_it_switches_to_own_team(self):
self.client.login(username="[email protected]", password="password")
@ -128,8 +164,8 @@ class ProfileTestCase(BaseTestCase):
assert r.status_code == 302
# profile.token should be set now
self.alice.profile.refresh_from_db()
token = self.alice.profile.token
self.profile.refresh_from_db()
token = self.profile.token
self.assertTrue(len(token) > 10)
# And an email should have been sent


+ 6
- 0
hc/accounts/tests/test_unsubscribe_reports.py View File

@ -1,3 +1,5 @@
from datetime import timedelta as td
from django.core import signing
from hc.test import BaseTestCase
@ -5,6 +7,9 @@ from hc.test import BaseTestCase
class UnsubscribeReportsTestCase(BaseTestCase):
def test_it_works(self):
self.profile.nag_period = td(hours=1)
self.profile.save()
token = signing.Signer().sign("foo")
url = "/accounts/unsubscribe_reports/alice/?token=%s" % token
r = self.client.get(url)
@ -12,3 +17,4 @@ class UnsubscribeReportsTestCase(BaseTestCase):
self.profile.refresh_from_db()
self.assertFalse(self.profile.reports_allowed)
self.assertEqual(self.profile.nag_period.total_seconds(), 0)

+ 19
- 1
hc/accounts/views.py View File

@ -1,3 +1,4 @@
from datetime import timedelta as td
import uuid
import re
@ -11,6 +12,7 @@ from django.contrib.auth.models import User
from django.core import signing
from django.http import HttpResponseForbidden, HttpResponseBadRequest
from django.shortcuts import redirect, render
from django.utils.timezone import now
from django.views.decorators.http import require_POST
from hc.accounts.forms import (ChangeEmailForm, EmailPasswordForm,
InviteTeamMemberForm, RemoveTeamMemberForm,
@ -238,7 +240,22 @@ def notifications(request):
if request.method == "POST":
form = ReportSettingsForm(request.POST)
if form.is_valid():
profile.reports_allowed = form.cleaned_data["reports_allowed"]
if profile.reports_allowed != form.cleaned_data["reports_allowed"]:
profile.reports_allowed = form.cleaned_data["reports_allowed"]
if profile.reports_allowed:
profile.next_report_date = now() + td(days=30)
else:
profile.next_report_date = None
if profile.nag_period != form.cleaned_data["nag_period"]:
# Set the new nag period
profile.nag_period = form.cleaned_data["nag_period"]
# and schedule next_nag_date:
if profile.nag_period:
profile.next_nag_date = now() + profile.nag_period
else:
profile.next_nag_date = None
profile.save()
messages.success(request, "Your settings have been updated!")
@ -338,6 +355,7 @@ def unsubscribe_reports(request, username):
user = User.objects.get(username=username)
profile = Profile.objects.for_user(user)
profile.reports_allowed = False
profile.nag_period = td()
profile.save()
return render(request, "accounts/unsubscribed.html")


+ 6
- 1
hc/api/management/commands/sendalerts.py View File

@ -8,9 +8,14 @@ from hc.api.models import Check
def notify(check_id, stdout):
check = Check.objects.get(id=check_id)
tmpl = "Sending alert, status=%s, code=%s\n"
stdout.write(tmpl % (check.status, check.code))
# Set dates for followup nags
if check.status == "down" and check.user.profile:
check.user.profile.set_next_nag_date()
# Send notifications
errors = check.send_alert()
for ch, error in errors:
stdout.write("ERROR: %s %s %s\n" % (ch.kind, ch.value, error))


+ 54
- 23
hc/api/management/commands/sendreports.py View File

@ -15,8 +15,8 @@ def num_pinged_checks(profile):
class Command(BaseCommand):
help = 'Send due monthly reports'
tmpl = "Sending monthly report to %s"
help = 'Send due monthly reports and nags'
tmpl = "Sent monthly report to %s"
def add_arguments(self, parser):
parser.add_argument(
@ -27,7 +27,7 @@ class Command(BaseCommand):
help='Keep running indefinitely in a 300 second wait loop',
)
def handle_one_run(self):
def handle_one_monthly_report(self):
now = timezone.now()
month_before = now - timedelta(days=30)
month_after = now + timedelta(days=30)
@ -38,39 +38,70 @@ class Command(BaseCommand):
q = Profile.objects.filter(report_due | report_not_scheduled)
q = q.filter(reports_allowed=True)
q = q.filter(user__date_joined__lt=month_before)
profiles = list(q)
profile = q.first()
sent = 0
for profile in profiles:
qq = Profile.objects
qq = qq.filter(id=profile.id,
next_report_date=profile.next_report_date)
if profile is None:
return False
num_updated = qq.update(next_report_date=month_after)
if num_updated != 1:
# Was updated elsewhere, skipping
continue
# A sort of optimistic lock. Try to update next_report_date,
# and if does get modified, we're in drivers seat:
qq = Profile.objects.filter(id=profile.id,
next_report_date=profile.next_report_date)
if num_pinged_checks(profile) == 0:
continue
num_updated = qq.update(next_report_date=month_after)
if num_updated != 1:
# next_report_date was already updated elsewhere, skipping
return True
if profile.send_report():
self.stdout.write(self.tmpl % profile.user.email)
profile.send_report()
# Pause before next report to avoid hitting sending quota
time.sleep(1)
sent += 1
return sent
return True
def handle(self, *args, **options):
if not options["loop"]:
return "Sent %d reports" % self.handle_one_run()
def handle_one_nag(self):
now = timezone.now()
q = Profile.objects.filter(next_nag_date__lt=now)
profile = q.first()
if profile is None:
return False
qq = Profile.objects.filter(id=profile.id,
next_nag_date=profile.next_nag_date)
num_updated = qq.update(next_nag_date=now + profile.nag_period)
if num_updated != 1:
# next_rag_date was already updated elsewhere, skipping
return True
if profile.send_report(nag=True):
self.stdout.write("Sent nag to %s" % profile.user.email)
# Pause before next report to avoid hitting sending quota
time.sleep(1)
else:
profile.next_nag_date = None
profile.save()
return True
def handle(self, *args, **options):
self.stdout.write("sendreports is now running")
while True:
self.handle_one_run()
# Monthly reports
while self.handle_one_monthly_report():
pass
# Daily and hourly nags
while self.handle_one_nag():
pass
if not options["loop"]:
break
formatted = timezone.now().isoformat()
self.stdout.write("-- MARK %s --" % formatted)
time.sleep(300)
# Sleep for 1 minute before looking for more work
time.sleep(60)

+ 30
- 2
hc/api/tests/test_sendalerts.py View File

@ -1,9 +1,9 @@
from datetime import timedelta
from mock import patch
from mock import Mock, patch
from django.core.management import call_command
from django.utils import timezone
from hc.api.management.commands.sendalerts import Command
from hc.api.management.commands.sendalerts import Command, notify
from hc.api.models import Check
from hc.test import BaseTestCase
@ -93,3 +93,31 @@ class SendAlertsTestCase(BaseTestCase):
# It should call `notify` instead of `notify_on_thread`
self.assertTrue(mock_notify.called)
def test_it_updates_owners_next_nag_date(self):
self.profile.nag_period = timedelta(hours=1)
self.profile.save()
check = Check(user=self.alice, status="down")
check.last_ping = timezone.now() - timedelta(days=2)
check.alert_after = check.get_alert_after()
check.save()
notify(check.id, Mock())
self.profile.refresh_from_db()
self.assertIsNotNone(self.profile.next_nag_date)
def test_it_updates_members_next_nag_date(self):
self.bobs_profile.nag_period = timedelta(hours=1)
self.bobs_profile.save()
check = Check(user=self.alice, status="down")
check.last_ping = timezone.now() - timedelta(days=2)
check.alert_after = check.get_alert_after()
check.save()
notify(check.id, Mock())
self.bobs_profile.refresh_from_db()
self.assertIsNotNone(self.bobs_profile.next_nag_date)

+ 48
- 9
hc/api/tests/test_sendreports.py View File

@ -1,5 +1,6 @@
from datetime import timedelta as td
from django.core import mail
from django.utils.timezone import now
from hc.api.management.commands.sendreports import Command
from hc.api.models import Check
@ -16,34 +17,72 @@ class SendAlertsTestCase(BaseTestCase):
self.alice.date_joined = now() - td(days=365)
self.alice.save()
# Make alice eligible for nags:
self.profile.nag_period = td(hours=1)
self.profile.next_nag_date = now() - td(seconds=10)
self.profile.save()
# And it needs at least one check that has been pinged.
self.check = Check(user=self.alice, last_ping=now())
self.check.status = "down"
self.check.save()
def test_it_sends_report(self):
sent = Command().handle_one_run()
self.assertEqual(sent, 1)
found = Command().handle_one_monthly_report()
self.assertTrue(found)
# Alice's profile should have been updated
self.profile.refresh_from_db()
self.assertTrue(self.profile.next_report_date > now())
self.assertEqual(len(mail.outbox), 1)
def test_it_obeys_next_report_date(self):
self.profile.next_report_date = now() + td(days=1)
self.profile.save()
sent = Command().handle_one_run()
self.assertEqual(sent, 0)
found = Command().handle_one_monthly_report()
self.assertFalse(found)
def test_it_obeys_reports_allowed_flag(self):
self.profile.reports_allowed = False
self.profile.save()
sent = Command().handle_one_run()
self.assertEqual(sent, 0)
found = Command().handle_one_monthly_report()
self.assertFalse(found)
def test_it_requires_pinged_checks(self):
self.check.delete()
sent = Command().handle_one_run()
self.assertEqual(sent, 0)
found = Command().handle_one_monthly_report()
self.assertTrue(found)
# No email should have been sent:
self.assertEqual(len(mail.outbox), 0)
def test_it_sends_nag(self):
found = Command().handle_one_nag()
self.assertTrue(found)
self.profile.refresh_from_db()
self.assertTrue(self.profile.next_nag_date > now())
self.assertEqual(len(mail.outbox), 1)
def test_it_obeys_next_nag_date(self):
self.profile.next_nag_date = now() + td(days=1)
self.profile.save()
found = Command().handle_one_nag()
self.assertFalse(found)
def test_nags_require_down_checks(self):
self.check.status = "up"
self.check.save()
found = Command().handle_one_nag()
self.assertTrue(found)
# No email should have been sent:
self.assertEqual(len(mail.outbox), 0)
# next_nag_date should now be unset
self.profile.refresh_from_db()
self.assertIsNone(self.profile.next_nag_date)

+ 67
- 0
static/css/checkbox.css View File

@ -0,0 +1,67 @@
/* Customize the label (the container) */
.checkbox-container {
display: block;
position: relative;
padding-left: 30px;
margin-left: 20px;
margin-bottom: 12px;
cursor: pointer;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
font-weight: normal;
}
/* Hide the browser's default checkbox */
.checkbox-container input {
position: absolute;
opacity: 0;
}
/* Create a custom checkbox */
.checkmark {
position: absolute;
top: 0;
left: 0;
height: 20px;
width: 20px;
border-radius: 3px;
border: 1px solid #DDD;
}
/* On mouse-over tint the border */
.checkmark:hover {
border-color: #5db4ea;
}
/* When the checkbox is checked, add a colored background */
.checkbox-container input:checked ~ .checkmark {
border-color: #0091EA;
background-color: #0091EA;
}
/* Create the checkmark/indicator (hidden when not checked) */
.checkmark:after {
content: "";
position: absolute;
display: none;
}
/* Show the checkmark when checked */
.checkbox-container input:checked ~ .checkmark:after {
display: block;
}
/* Style the checkmark/indicator */
.checkbox-container .checkmark:after {
left: 7px;
top: 3px;
width: 5px;
height: 10px;
border: solid white;
border-width: 0 2px 2px 0;
-webkit-transform: rotate(45deg);
-ms-transform: rotate(45deg);
transform: rotate(45deg);
}

+ 64
- 0
static/css/radio.css View File

@ -0,0 +1,64 @@
/* Customize the label (the container) */
.radio-container {
display: block;
position: relative;
padding-left: 30px;
margin-bottom: 12px;
margin-left: 20px;
cursor: pointer;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
font-weight: normal;
}
/* Hide the browser's default radio button */
.radio-container input {
position: absolute;
opacity: 0;
}
/* Create a custom radio button */
.radiomark {
position: absolute;
top: 0;
left: 0;
height: 20px;
width: 20px;
border-radius: 50%;
border: 1px solid #DDD;
}
/* On mouse-over, tint the border */
.radiomark:hover {
border-color: #5db4ea;
}
/* When the radio button is checked, add a colored background */
.radio-container input:checked ~ .radiomark {
border-color: #0091EA;
background-color: #0091EA;
}
/* Create the indicator (the dot/circle - hidden when not checked) */
.radiomark:after {
content: "";
position: absolute;
display: none;
}
/* Show the indicator (dot/circle) when checked */
.radio-container input:checked ~ .radiomark:after {
display: block;
}
/* Style the indicator (dot/circle) */
.radio-container .radiomark:after {
top: 6px;
left: 6px;
width: 6px;
height: 6px;
border-radius: 50%;
background: white;
}

+ 1
- 1
templates/accounts/badges.html View File

@ -14,7 +14,7 @@
<div class="col-sm-2">
<ul class="nav nav-pills nav-stacked">
<li><a href="{% url 'hc-profile' %}">Security</a></li>
<li><a href="{% url 'hc-notifications' %}">Notifications</a></li>
<li><a href="{% url 'hc-notifications' %}">Email Reports</a></li>
<li class="active"><a href="{% url 'hc-badges' %}">Badges</a></li>
</ul>
</div>


+ 51
- 5
templates/accounts/notifications.html View File

@ -21,7 +21,7 @@
<div class="col-sm-3">
<ul class="nav nav-pills nav-stacked">
<li><a href="{% url 'hc-profile' %}">Account</a></li>
<li class="active"><a href="{% url 'hc-notifications' %}">Notifications</a></li>
<li class="active"><a href="{% url 'hc-notifications' %}">Email Reports</a></li>
<li><a href="{% url 'hc-badges' %}">Badges</a></li>
</ul>
</div>
@ -29,20 +29,66 @@
<div class="col-sm-9 col-md-6">
<div class="panel panel-default">
<div class="panel-body settings-block">
<h2>Monthly Reports</h2>
<form method="post">
{% csrf_token %}
<label>
<h2>Email Reports</h2>
<p>Send me monthly emails about:</p>
<label class="checkbox-container">
<input
name="reports_allowed"
type="checkbox"
{% if profile.reports_allowed %} checked {% endif %}>
Each month send me a summary of my checks
<span class="checkmark"></span>
The status of checks my checks
</label>
<br>
<p>If any checks are down:</p>
<label class="radio-container">
<input
type="radio"
name="nag_period"
value="0"
{% if profile.nag_period.total_seconds == 0 %} checked {% endif %}>
<span class="radiomark"></span>
Do not remind me
</label>
<label class="radio-container">
<input
type="radio"
name="nag_period"
value="86400"
{% if profile.nag_period.total_seconds == 86400 %} checked {% endif %}>
<span class="radiomark"></span>
Remind me daily
</label>
<label class="radio-container">
<input
type="radio"
name="nag_period"
value="3600"
{% if profile.nag_period.total_seconds == 3600 %} checked {% endif %}>
<span class="radiomark"></span>
Remind me hourly
</label>
<br />
<p style="color: #888">
Reports will be delivered to {{ profile.user.email }}. <br />
{% if profile.next_report_date %}
Next monthly report date is
{{ profile.next_report_date.date }}.
{% endif %}
</p>
<br />
<button
name="update_reports_allowed"
type="submit"
class="btn btn-default pull-right">Save</button>
class="btn btn-default pull-right">Save Changes</button>
</form>
</div>
</div>


+ 1
- 1
templates/accounts/profile.html View File

@ -22,7 +22,7 @@
<div class="col-sm-3">
<ul class="nav nav-pills nav-stacked">
<li class="active"><a href="{% url 'hc-profile' %}">Account</a></li>
<li><a href="{% url 'hc-notifications' %}">Notifications</a></li>
<li><a href="{% url 'hc-notifications' %}">Email Reports</a></li>
<li><a href="{% url 'hc-badges' %}">Badges</a></li>
</ul>
</div>


+ 2
- 0
templates/base.html View File

@ -37,6 +37,8 @@
<link rel="stylesheet" href="{% static 'css/settings.css' %}" type="text/css">
<link rel="stylesheet" href="{% static 'css/last_ping.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">
{% endcompress %}
</head>
<body class="page-{{ page }}">


+ 22
- 2
templates/emails/report-body-html.html View File

@ -3,15 +3,35 @@
{% block content %}
Hello,<br />
This is a monthly report sent by <a href="{% site_root %}">{% site_name %}</a>.
{% if nag %}
This is a
{% if nag_period == 3600 %}hourly{% endif %}
{% if nag_period == 86400 %}daily{% endif %}
reminder sent by <a href="{% site_root %}">{% site_name %}</a>.<br />
{% if num_down == 1%}
One check is currently <strong>DOWN</strong>.
{% else %}
{{ num_down }} checks are currently <strong>DOWN</strong>.
{% endif %}
{% else %}
This is a monthly report sent by <a href="{% site_root %}">{% site_name %}</a>.
{% endif %}
<br />
{% include "emails/summary-html.html" %}
{% if nag %}
<strong>Too many notifications?</strong>
Visit the <a href="{{ notifications_url }}">Email Reports</a>
page on {% site_name %} to set your notification preferences.
{% else %}
<strong>Just one more thing to check:</strong>
Do you have more cron jobs,
not yet on this list, that would benefit from monitoring?
Get the ball rolling by adding one more!
{% endif %}
<br /><br />
Cheers,<br>
@ -22,6 +42,6 @@ The {% escaped_site_name %} Team
{% block unsub %}
<br>
<a href="{{ unsub_link }}" target="_blank" style="color: #666666; text-decoration: underline;">
Unsubscribe from Monthly Reports
Unsubscribe
</a>
{% endblock %}

+ 4
- 1
templates/emails/report-body-text.html View File

@ -1,7 +1,10 @@
{% load hc_extras %}
Hello,
This is a monthly report sent by {% site_name %}.
{% if nag %}This is a {% if nag_period == 3600 %}hourly {% endif %}{% if nag_period == 86400 %}daily {% endif %}reminder sent by {% site_name %}.
{% if num_down == 1%}One check is currently DOWN.{% else %}{{ num_down }} checks are currently DOWN.{% endif %}{% else %}This is a monthly report sent by {% site_name %}.{% endif %}
{% include 'emails/summary-text.html' %}


+ 5
- 1
templates/emails/report-subject.html View File

@ -1,2 +1,6 @@
Monthly Report
{% if nag %}
Reminder: {{ num_down }} check{{ num_down|pluralize }} still down
{% else %}
Monthly Report
{% endif %}

Loading…
Cancel
Save