diff --git a/CHANGELOG.md b/CHANGELOG.md index ca881c46..b5eb9b99 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ All notable changes to this project will be documented in this file. - Add a "Transfer Ownership" feature in Project Settings - In checks list, the pause button asks for confirmation (#356) - Added /api/v1/metrics/ endpoint, useful for monitoring the service itself +- Added "When paused, ignore pings" option in the Filtering Rules dialog (#369) ### Bug Fixes - "Get a single check" API call now supports read-only API keys (#346) diff --git a/hc/api/migrations/0071_check_manual_resume.py b/hc/api/migrations/0071_check_manual_resume.py new file mode 100644 index 00000000..82b212ad --- /dev/null +++ b/hc/api/migrations/0071_check_manual_resume.py @@ -0,0 +1,18 @@ +# Generated by Django 3.0.4 on 2020-06-02 07:35 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0070_auto_20200411_1310'), + ] + + operations = [ + migrations.AddField( + model_name='check', + name='manual_resume', + field=models.NullBooleanField(default=False), + ), + ] diff --git a/hc/api/models.py b/hc/api/models.py index 7089b5da..4f31ad8d 100644 --- a/hc/api/models.py +++ b/hc/api/models.py @@ -75,6 +75,7 @@ class Check(models.Model): tz = models.CharField(max_length=36, default="UTC") subject = models.CharField(max_length=100, blank=True) methods = models.CharField(max_length=30, blank=True) + manual_resume = models.NullBooleanField(default=False) n_pings = models.IntegerField(default=0) last_ping = models.DateTimeField(null=True, blank=True) @@ -243,6 +244,9 @@ class Check(models.Model): def ping(self, remote_addr, scheme, method, ua, body, action): now = timezone.now() + if self.status == "paused" and self.manual_resume: + action = "ign" + if action == "start": self.last_start = now # Don't update "last_ping" field. diff --git a/hc/api/tests/test_ping.py b/hc/api/tests/test_ping.py index 45349607..023a141c 100644 --- a/hc/api/tests/test_ping.py +++ b/hc/api/tests/test_ping.py @@ -209,3 +209,18 @@ class PingTestCase(BaseTestCase): ping = Ping.objects.latest("id") self.assertEqual(len(ping.body), 20000) + + def test_it_handles_manual_resume_flag(self): + self.check.status = "paused" + self.check.manual_resume = True + self.check.save() + + r = self.client.get(self.url) + self.assertEqual(r.status_code, 200) + + self.check.refresh_from_db() + self.assertEqual(self.check.status, "paused") + + ping = Ping.objects.latest("id") + self.assertEqual(ping.scheme, "http") + self.assertEqual(ping.kind, "ign") diff --git a/hc/front/forms.py b/hc/front/forms.py index 7e68d36c..bb35decb 100644 --- a/hc/front/forms.py +++ b/hc/front/forms.py @@ -63,8 +63,9 @@ class NameTagsForm(forms.Form): class FilteringRulesForm(forms.Form): - subject = forms.CharField(max_length=100) + subject = forms.CharField(required=False, max_length=100) methods = forms.ChoiceField(required=False, choices=(("", "Any"), ("POST", "POST"))) + manual_resume = forms.BooleanField(required=False) class TimeoutForm(forms.Form): diff --git a/hc/front/tests/test_filtering_rules.py b/hc/front/tests/test_filtering_rules.py index 2ed4f956..c0417c74 100644 --- a/hc/front/tests/test_filtering_rules.py +++ b/hc/front/tests/test_filtering_rules.py @@ -12,12 +12,16 @@ class FilteringRulesTestCase(BaseTestCase): def test_it_works(self): self.client.login(username="alice@example.org", password="password") - r = self.client.post(self.url, data={"subject": "SUCCESS", "methods": "POST"}) + r = self.client.post( + self.url, + data={"subject": "SUCCESS", "methods": "POST", "manual_resume": "1"}, + ) self.assertRedirects(r, self.redirect_url) self.check.refresh_from_db() self.assertEqual(self.check.subject, "SUCCESS") self.assertEqual(self.check.methods, "POST") + self.assertTrue(self.check.manual_resume) def test_it_clears_method(self): self.check.method = "POST" @@ -29,3 +33,25 @@ class FilteringRulesTestCase(BaseTestCase): self.check.refresh_from_db() self.assertEqual(self.check.methods, "") + + def test_it_clears_subject(self): + self.check.subject = "SUCCESS" + self.check.save() + + self.client.login(username="alice@example.org", password="password") + r = self.client.post(self.url, data={"methods": ""}) + self.assertRedirects(r, self.redirect_url) + + self.check.refresh_from_db() + self.assertEqual(self.check.subject, "") + + def test_it_clears_manual_resume_flag(self): + self.check.manual_resume = True + self.check.save() + + self.client.login(username="alice@example.org", password="password") + r = self.client.post(self.url, data={}) + self.assertRedirects(r, self.redirect_url) + + self.check.refresh_from_db() + self.assertFalse(self.check.manual_resume) diff --git a/hc/front/tests/test_pause.py b/hc/front/tests/test_pause.py index 4452f6f2..ca170d64 100644 --- a/hc/front/tests/test_pause.py +++ b/hc/front/tests/test_pause.py @@ -10,7 +10,7 @@ class PauseTestCase(BaseTestCase): super(PauseTestCase, self).setUp() self.check = Check.objects.create(project=self.project, status="up") self.url = "/checks/%s/pause/" % self.check.code - self.redirect_url = "/projects/%s/checks/" % self.project.code + self.redirect_url = "/checks/%s/details/" % self.check.code def test_it_pauses(self): self.client.login(username="alice@example.org", password="password") diff --git a/hc/front/tests/test_resume.py b/hc/front/tests/test_resume.py new file mode 100644 index 00000000..ec2bc539 --- /dev/null +++ b/hc/front/tests/test_resume.py @@ -0,0 +1,28 @@ +from hc.api.models import Check +from hc.test import BaseTestCase + + +class ResumeTestCase(BaseTestCase): + def setUp(self): + super(ResumeTestCase, self).setUp() + self.check = Check.objects.create(project=self.project, status="paused") + self.url = "/checks/%s/resume/" % self.check.code + self.redirect_url = "/checks/%s/details/" % self.check.code + + def test_it_resumes(self): + self.client.login(username="alice@example.org", password="password") + r = self.client.post(self.url) + self.assertRedirects(r, self.redirect_url) + + self.check.refresh_from_db() + self.assertEqual(self.check.status, "new") + + def test_it_rejects_get(self): + self.client.login(username="alice@example.org", password="password") + r = self.client.get(self.url) + self.assertEqual(r.status_code, 405) + + def test_it_allows_cross_team_access(self): + self.client.login(username="bob@example.org", password="password") + r = self.client.post(self.url) + self.assertRedirects(r, self.redirect_url) diff --git a/hc/front/tests/test_status_single.py b/hc/front/tests/test_status_single.py index 85832520..18ca23ac 100644 --- a/hc/front/tests/test_status_single.py +++ b/hc/front/tests/test_status_single.py @@ -50,3 +50,15 @@ class StatusSingleTestCase(BaseTestCase): self.client.login(username="bob@example.org", password="password") r = self.client.get("/checks/%s/status/" % self.check.code) self.assertEqual(r.status_code, 200) + + def test_it_handles_manual_resume(self): + self.check.status = "paused" + self.check.manual_resume = True + self.check.save() + + self.client.login(username="alice@example.org", password="password") + r = self.client.get("/checks/%s/status/" % self.check.code) + doc = r.json() + + self.assertEqual(doc["status"], "paused") + self.assertTrue("will ignore pings until resumed" in doc["status_text"]) diff --git a/hc/front/urls.py b/hc/front/urls.py index 53ba4bfe..08b547e4 100644 --- a/hc/front/urls.py +++ b/hc/front/urls.py @@ -8,6 +8,7 @@ check_urls = [ path("filtering_rules/", views.filtering_rules, name="hc-filtering-rules"), path("timeout/", views.update_timeout, name="hc-update-timeout"), path("pause/", views.pause, name="hc-pause"), + path("resume/", views.resume, name="hc-resume"), path("remove/", views.remove_check, name="hc-remove-check"), path("log/", views.log, name="hc-log"), path("status/", views.status_single, name="hc-status-single"), diff --git a/hc/front/views.py b/hc/front/views.py index 8027c428..44780d55 100644 --- a/hc/front/views.py +++ b/hc/front/views.py @@ -344,6 +344,7 @@ def filtering_rules(request, code): if form.is_valid(): check.subject = form.cleaned_data["subject"] check.methods = form.cleaned_data["methods"] + check.manual_resume = form.cleaned_data["manual_resume"] check.save() return redirect("hc-details", code) @@ -442,14 +443,25 @@ def pause(request, code): check.alert_after = None check.save() - if "/details/" in request.META.get("HTTP_REFERER", ""): - return redirect("hc-details", code) - # Don't redirect after an AJAX request: if request.META.get("HTTP_X_REQUESTED_WITH") == "XMLHttpRequest": return HttpResponse() - return redirect("hc-checks", check.project.code) + return redirect("hc-details", code) + + +@require_POST +@login_required +def resume(request, code): + check = _get_check_for_user(request, code) + + check.status = "new" + check.last_start = None + check.last_ping = None + check.alert_after = None + check.save() + + return redirect("hc-details", code) @require_POST diff --git a/static/js/details.js b/static/js/details.js index 37789f73..6351be17 100644 --- a/static/js/details.js +++ b/static/js/details.js @@ -36,6 +36,11 @@ $(function () { return false; }); + $("#log-status-text").on("click", "#resume-btn", function() { + $("#resume-form").submit(); + return false; + }); + $("#pause").click(function(e) { $("#pause-form").submit(); return false; @@ -79,7 +84,9 @@ $(function () { if (data.status_text != lastStatusText) { lastStatusText = data.status_text; $("#log-status-icon").attr("class", "status icon-" + data.status); - $("#log-status-text").text(data.status_text); + $("#log-status-text").html(data.status_text); + + $('#pause-btn').prop('disabled', data.status == "paused"); } if (data.events) { diff --git a/templates/front/details.html b/templates/front/details.html index cc9e32db..bc4d9c58 100644 --- a/templates/front/details.html +++ b/templates/front/details.html @@ -127,7 +127,11 @@
{% csrf_token %} - +
- + + +
+ {% csrf_token %} +
+ {% include "front/update_name_modal.html" %} {% include "front/update_timeout_modal.html" %} diff --git a/templates/front/filtering_rules_modal.html b/templates/front/filtering_rules_modal.html index d751c9dc..82263692 100644 --- a/templates/front/filtering_rules_modal.html +++ b/templates/front/filtering_rules_modal.html @@ -58,6 +58,29 @@ + + +