From 3862cd6b0658ef96a21619219d5fb84a6b1e7568 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C4=93teris=20Caune?= Date: Tue, 9 May 2017 13:47:23 +0300 Subject: [PATCH] Adding Check.last_ping_body field, and an UI to show it (#116) --- .../migrations/0030_check_last_ping_body.py | 20 +++++++++++ hc/api/models.py | 4 +++ hc/api/tests/test_ping.py | 9 ++++- hc/api/views.py | 1 + hc/front/tests/test_last_ping.py | 21 ++++++++++++ hc/front/urls.py | 1 + hc/front/views.py | 20 ++++++++++- static/css/last_ping.css | 26 +++++++++++++++ static/css/my_checks.css | 9 ++++- static/css/my_checks_desktop.css | 14 +++++++- static/js/checks.js | 28 ++++++++++++++-- templates/base.html | 1 + templates/front/last_ping.html | 33 +++++++++++++++++++ templates/front/my_checks.html | 11 +++++++ templates/front/my_checks_desktop.html | 11 ++++--- 15 files changed, 197 insertions(+), 12 deletions(-) create mode 100644 hc/api/migrations/0030_check_last_ping_body.py create mode 100644 hc/front/tests/test_last_ping.py create mode 100644 static/css/last_ping.css create mode 100644 templates/front/last_ping.html diff --git a/hc/api/migrations/0030_check_last_ping_body.py b/hc/api/migrations/0030_check_last_ping_body.py new file mode 100644 index 00000000..0979bffe --- /dev/null +++ b/hc/api/migrations/0030_check_last_ping_body.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.5 on 2017-05-08 18:31 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0029_auto_20170507_1251'), + ] + + operations = [ + migrations.AddField( + model_name='check', + name='last_ping_body', + field=models.CharField(blank=True, max_length=1000), + ), + ] diff --git a/hc/api/models.py b/hc/api/models.py index 78b0d2b8..8a703827 100644 --- a/hc/api/models.py +++ b/hc/api/models.py @@ -65,6 +65,7 @@ class Check(models.Model): tz = models.CharField(max_length=36, default="UTC") n_pings = models.IntegerField(default=0) last_ping = models.DateTimeField(null=True, blank=True) + last_ping_body = models.CharField(max_length=1000, blank=True) alert_after = models.DateTimeField(null=True, blank=True, editable=False) status = models.CharField(max_length=6, choices=STATUSES, default="new") @@ -173,6 +174,9 @@ class Check(models.Model): return result + def has_confirmation_link(self): + return "confirm" in self.last_ping_body.lower() + @classmethod def check(cls, **kwargs): errors = super(Check, cls).check(**kwargs) diff --git a/hc/api/tests/test_ping.py b/hc/api/tests/test_ping.py index 3285e157..fc5b1724 100644 --- a/hc/api/tests/test_ping.py +++ b/hc/api/tests/test_ping.py @@ -32,9 +32,16 @@ class PingTestCase(TestCase): def test_post_works(self): csrf_client = Client(enforce_csrf_checks=True) - r = csrf_client.post("/ping/%s/" % self.check.code) + r = csrf_client.post("/ping/%s/" % self.check.code, "hello world", + content_type="text/plain") assert r.status_code == 200 + self.check.refresh_from_db() + self.assertEqual(self.check.last_ping_body, "hello world") + + ping = Ping.objects.latest("id") + self.assertEqual(ping.method, "POST") + def test_head_works(self): csrf_client = Client(enforce_csrf_checks=True) r = csrf_client.head("/ping/%s/" % self.check.code) diff --git a/hc/api/views.py b/hc/api/views.py index 0e272024..2bec2a30 100644 --- a/hc/api/views.py +++ b/hc/api/views.py @@ -24,6 +24,7 @@ def ping(request, code): check.n_pings = F("n_pings") + 1 check.last_ping = timezone.now() + check.last_ping_body = request.body[:1000] check.alert_after = check.get_alert_after() if check.status in ("new", "paused"): check.status = "up" diff --git a/hc/front/tests/test_last_ping.py b/hc/front/tests/test_last_ping.py new file mode 100644 index 00000000..ce96be08 --- /dev/null +++ b/hc/front/tests/test_last_ping.py @@ -0,0 +1,21 @@ +from hc.api.models import Check, Ping +from hc.test import BaseTestCase + + +class LastPingTestCase(BaseTestCase): + + def test_it_works(self): + check = Check(user=self.alice) + check.last_ping_body = "this is body" + check.save() + + Ping.objects.create(owner=check) + + self.client.login(username="alice@example.org", password="password") + r = self.client.post("/checks/%s/last_ping/" % check.code) + self.assertContains(r, "this is body", status_code=200) + + def test_it_requires_user(self): + check = Check.objects.create() + r = self.client.post("/checks/%s/last_ping/" % check.code) + self.assertEqual(r.status_code, 403) diff --git a/hc/front/urls.py b/hc/front/urls.py index 244b1917..609a266b 100644 --- a/hc/front/urls.py +++ b/hc/front/urls.py @@ -8,6 +8,7 @@ check_urls = [ url(r'^pause/$', views.pause, name="hc-pause"), url(r'^remove/$', views.remove_check, name="hc-remove-check"), url(r'^log/$', views.log, name="hc-log"), + url(r'^last_ping/$', views.last_ping, name="hc-last-ping"), ] channel_urls = [ diff --git a/hc/front/views.py b/hc/front/views.py index 1c76567b..59368afe 100644 --- a/hc/front/views.py +++ b/hc/front/views.py @@ -201,7 +201,6 @@ def update_timeout(request, code): return redirect("hc-checks") -@csrf_exempt @require_POST def cron_preview(request): schedule = request.POST.get("schedule") @@ -223,6 +222,25 @@ def cron_preview(request): return render(request, "front/cron_preview.html", ctx) +@require_POST +def last_ping(request, code): + if not request.user.is_authenticated: + return HttpResponseForbidden() + + check = get_object_or_404(Check, code=code) + if check.user_id != request.team.user.id: + return HttpResponseForbidden() + + ping = Ping.objects.filter(owner=check).latest("created") + + ctx = { + "check": check, + "ping": ping + } + + return render(request, "front/last_ping.html", ctx) + + @require_POST @login_required @uuid_or_400 diff --git a/static/css/last_ping.css b/static/css/last_ping.css new file mode 100644 index 00000000..fdf51c44 --- /dev/null +++ b/static/css/last_ping.css @@ -0,0 +1,26 @@ +#last-ping-body .modal-body { + padding: 40px; +} + +#last-ping-body h3 { + font-size: 18px; + margin: 0 0 24px 0; +} + +#last-ping-body .ua { + font-size: 11px; + font-family: monospace; +} + +#last-ping-body p strong { + display: block; +} + +#last-ping-body h4 { + margin-top: 24px; +} + +#last-ping-body pre { + padding: 16px; + margin: 0; +} diff --git a/static/css/my_checks.css b/static/css/my_checks.css index 76a698ca..95e79098 100644 --- a/static/css/my_checks.css +++ b/static/css/my_checks.css @@ -1,5 +1,5 @@ @media (min-width: 992px) { - #update-timeout-modal .modal-dialog { + #update-timeout-modal .modal-dialog, #last-ping-modal .modal-dialog { width: 800px; } } @@ -115,6 +115,13 @@ font-weight: 500; } +.label-confirmation { + background-color: #22bc66; + color: #fff; + font-style: normal; + font-weight: 500; +} + #show-usage-modal .modal-dialog { width: 1100px; } diff --git a/static/css/my_checks_desktop.css b/static/css/my_checks_desktop.css index 2c82b80d..daa32016 100644 --- a/static/css/my_checks_desktop.css +++ b/static/css/my_checks_desktop.css @@ -36,9 +36,10 @@ table.table tr > th.th-name { #checks-table tr:hover .my-checks-name { border: 1px dotted #AAA; + cursor: pointer; } -#checks-table > tbody > tr > th.th-period { +#checks-table > tbody > tr > th.th-period, #checks-table > tbody > tr > th.th-last-ping { padding-left: 15px; } @@ -58,6 +59,17 @@ table.table tr > th.th-name { #checks-table tr:hover .timeout-grace { border: 1px dotted #AAA; + cursor: pointer; +} + +#checks-table .last-ping { + border: 1px solid rgba(0, 0, 0, 0); + padding: 6px; +} + +#checks-table tr:hover .last-ping { + border: 1px dotted #AAA; + cursor: pointer; } .checks-subline { diff --git a/static/js/checks.js b/static/js/checks.js index ec3baa3f..1973fa3f 100644 --- a/static/js/checks.js +++ b/static/js/checks.js @@ -119,8 +119,14 @@ $(function () { // OK, we're good currentPreviewHash = hash; $("#cron-preview-title").text("Updating..."); - $.post("/checks/cron_preview/", {schedule: schedule, tz: tz}, - function(data) { + + var token = $('input[name=csrfmiddlewaretoken]').val(); + $.ajax({ + url: "/checks/cron_preview/", + type: "post", + headers: {"X-CSRFToken": token}, + data: {schedule: schedule, tz: tz}, + success: function(data) { if (hash != currentPreviewHash) { return; // ignore stale results } @@ -129,7 +135,7 @@ $(function () { var haveError = $("#invalid-arguments").size() > 0; $("#update-cron-submit").prop("disabled", haveError); } - ); + }); } $(".timeout-grace").click(function() { @@ -169,6 +175,22 @@ $(function () { return false; }); + $(".last-ping").click(function() { + $("#last-ping-body").text("Updating..."); + $('#last-ping-modal').modal("show"); + + var token = $('input[name=csrfmiddlewaretoken]').val(); + $.ajax({ + url: this.dataset.url, + type: "post", + headers: {"X-CSRFToken": token}, + success: function(data) { + $("#last-ping-body" ).html(data); + } + }); + + return false; + }); $("#my-checks-tags button").click(function() { // .active has not been updated yet by bootstrap code, diff --git a/templates/base.html b/templates/base.html index 254dec61..cc0a2294 100644 --- a/templates/base.html +++ b/templates/base.html @@ -31,6 +31,7 @@ + {% endcompress %} diff --git a/templates/front/last_ping.html b/templates/front/last_ping.html new file mode 100644 index 00000000..04de2829 --- /dev/null +++ b/templates/front/last_ping.html @@ -0,0 +1,33 @@ + \ No newline at end of file diff --git a/templates/front/my_checks.html b/templates/front/my_checks.html index 5d0e5da2..9b8b5232 100644 --- a/templates/front/my_checks.html +++ b/templates/front/my_checks.html @@ -350,6 +350,17 @@ + +
{% csrf_token %}
diff --git a/templates/front/my_checks_desktop.html b/templates/front/my_checks_desktop.html index 76ef658c..d81e410d 100644 --- a/templates/front/my_checks_desktop.html +++ b/templates/front/my_checks_desktop.html @@ -8,7 +8,7 @@ Period
Grace - Last Ping + Last Ping {% for check in checks %} @@ -71,11 +71,12 @@ {% if check.last_ping %} - +
{{ check.last_ping|naturaltime }} - + {% if check.has_confirmation_link %} +
confirmation link + {% endif %} +
{% else %} Never {% endif %}