Browse Source

Adding Check.last_ping_body field, and an UI to show it (#116)

pull/114/merge
Pēteris Caune 8 years ago
parent
commit
3862cd6b06
15 changed files with 197 additions and 12 deletions
  1. +20
    -0
      hc/api/migrations/0030_check_last_ping_body.py
  2. +4
    -0
      hc/api/models.py
  3. +8
    -1
      hc/api/tests/test_ping.py
  4. +1
    -0
      hc/api/views.py
  5. +21
    -0
      hc/front/tests/test_last_ping.py
  6. +1
    -0
      hc/front/urls.py
  7. +19
    -1
      hc/front/views.py
  8. +26
    -0
      static/css/last_ping.css
  9. +8
    -1
      static/css/my_checks.css
  10. +13
    -1
      static/css/my_checks_desktop.css
  11. +25
    -3
      static/js/checks.js
  12. +1
    -0
      templates/base.html
  13. +33
    -0
      templates/front/last_ping.html
  14. +11
    -0
      templates/front/my_checks.html
  15. +6
    -5
      templates/front/my_checks_desktop.html

+ 20
- 0
hc/api/migrations/0030_check_last_ping_body.py View File

@ -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),
),
]

+ 4
- 0
hc/api/models.py View File

@ -65,6 +65,7 @@ class Check(models.Model):
tz = models.CharField(max_length=36, default="UTC") tz = models.CharField(max_length=36, default="UTC")
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_ping_body = models.CharField(max_length=1000, blank=True)
alert_after = models.DateTimeField(null=True, blank=True, editable=False) alert_after = models.DateTimeField(null=True, blank=True, editable=False)
status = models.CharField(max_length=6, choices=STATUSES, default="new") status = models.CharField(max_length=6, choices=STATUSES, default="new")
@ -173,6 +174,9 @@ class Check(models.Model):
return result return result
def has_confirmation_link(self):
return "confirm" in self.last_ping_body.lower()
@classmethod @classmethod
def check(cls, **kwargs): def check(cls, **kwargs):
errors = super(Check, cls).check(**kwargs) errors = super(Check, cls).check(**kwargs)


+ 8
- 1
hc/api/tests/test_ping.py View File

@ -32,9 +32,16 @@ class PingTestCase(TestCase):
def test_post_works(self): def test_post_works(self):
csrf_client = Client(enforce_csrf_checks=True) 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 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): def test_head_works(self):
csrf_client = Client(enforce_csrf_checks=True) csrf_client = Client(enforce_csrf_checks=True)
r = csrf_client.head("/ping/%s/" % self.check.code) r = csrf_client.head("/ping/%s/" % self.check.code)


+ 1
- 0
hc/api/views.py View File

@ -24,6 +24,7 @@ def ping(request, code):
check.n_pings = F("n_pings") + 1 check.n_pings = F("n_pings") + 1
check.last_ping = timezone.now() check.last_ping = timezone.now()
check.last_ping_body = request.body[:1000]
check.alert_after = check.get_alert_after() check.alert_after = check.get_alert_after()
if check.status in ("new", "paused"): if check.status in ("new", "paused"):
check.status = "up" check.status = "up"


+ 21
- 0
hc/front/tests/test_last_ping.py View File

@ -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="[email protected]", 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)

+ 1
- 0
hc/front/urls.py View File

@ -8,6 +8,7 @@ check_urls = [
url(r'^pause/$', views.pause, name="hc-pause"), url(r'^pause/$', views.pause, name="hc-pause"),
url(r'^remove/$', views.remove_check, name="hc-remove-check"), url(r'^remove/$', views.remove_check, name="hc-remove-check"),
url(r'^log/$', views.log, name="hc-log"), url(r'^log/$', views.log, name="hc-log"),
url(r'^last_ping/$', views.last_ping, name="hc-last-ping"),
] ]
channel_urls = [ channel_urls = [


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

@ -201,7 +201,6 @@ def update_timeout(request, code):
return redirect("hc-checks") return redirect("hc-checks")
@csrf_exempt
@require_POST @require_POST
def cron_preview(request): def cron_preview(request):
schedule = request.POST.get("schedule") schedule = request.POST.get("schedule")
@ -223,6 +222,25 @@ def cron_preview(request):
return render(request, "front/cron_preview.html", ctx) 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 @require_POST
@login_required @login_required
@uuid_or_400 @uuid_or_400


+ 26
- 0
static/css/last_ping.css View File

@ -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;
}

+ 8
- 1
static/css/my_checks.css View File

@ -1,5 +1,5 @@
@media (min-width: 992px) { @media (min-width: 992px) {
#update-timeout-modal .modal-dialog {
#update-timeout-modal .modal-dialog, #last-ping-modal .modal-dialog {
width: 800px; width: 800px;
} }
} }
@ -115,6 +115,13 @@
font-weight: 500; font-weight: 500;
} }
.label-confirmation {
background-color: #22bc66;
color: #fff;
font-style: normal;
font-weight: 500;
}
#show-usage-modal .modal-dialog { #show-usage-modal .modal-dialog {
width: 1100px; width: 1100px;
} }


+ 13
- 1
static/css/my_checks_desktop.css View File

@ -36,9 +36,10 @@ table.table tr > th.th-name {
#checks-table tr:hover .my-checks-name { #checks-table tr:hover .my-checks-name {
border: 1px dotted #AAA; 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; padding-left: 15px;
} }
@ -58,6 +59,17 @@ table.table tr > th.th-name {
#checks-table tr:hover .timeout-grace { #checks-table tr:hover .timeout-grace {
border: 1px dotted #AAA; 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 { .checks-subline {


+ 25
- 3
static/js/checks.js View File

@ -119,8 +119,14 @@ $(function () {
// OK, we're good // OK, we're good
currentPreviewHash = hash; currentPreviewHash = hash;
$("#cron-preview-title").text("Updating..."); $("#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) { if (hash != currentPreviewHash) {
return; // ignore stale results return; // ignore stale results
} }
@ -129,7 +135,7 @@ $(function () {
var haveError = $("#invalid-arguments").size() > 0; var haveError = $("#invalid-arguments").size() > 0;
$("#update-cron-submit").prop("disabled", haveError); $("#update-cron-submit").prop("disabled", haveError);
} }
);
});
} }
$(".timeout-grace").click(function() { $(".timeout-grace").click(function() {
@ -169,6 +175,22 @@ $(function () {
return false; 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() { $("#my-checks-tags button").click(function() {
// .active has not been updated yet by bootstrap code, // .active has not been updated yet by bootstrap code,


+ 1
- 0
templates/base.html View File

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


+ 33
- 0
templates/front/last_ping.html View File

@ -0,0 +1,33 @@
<div class="modal-body">
<h3>Ping #{{ ping.n }}</h3>
<div class="row">
<div class="col-sm-6">
<p>
<strong>Time Received</strong>
<code>{{ ping.created.isoformat }}</code>
</p>
<p>
<strong>Source</strong>
{% if ping.scheme == "email" %}
{{ ping.ua }}
{% else %}
{{ ping.scheme|upper }} {{ ping.method }} from {{ ping.remote_addr }}
{% endif %}
</p>
</div>
{% if ping.scheme != "email" %}
<div class="col-sm-6">
<p>
<strong>User Agent</strong>
<span class="ua">{{ ping.ua }}</span>
</p>
</div>
{% endif %}
</div>
{% if check.last_ping_body %}
<h4>Request Body</h4>
<pre>{{ check.last_ping_body }}</pre>
{% endif %}
</div>

+ 11
- 0
templates/front/my_checks.html View File

@ -350,6 +350,17 @@
</div> </div>
</div> </div>
<div id="last-ping-modal" class="modal">
<div class="modal-dialog">
<div class="modal-content">
<div id="last-ping-body">Loading</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Got It!</button>
</div>
</div>
</div>
</div>
<form id="pause-form" method="post"> <form id="pause-form" method="post">
{% csrf_token %} {% csrf_token %}
</form> </form>


+ 6
- 5
templates/front/my_checks_desktop.html View File

@ -8,7 +8,7 @@
Period <br /> Period <br />
<span class="checks-subline">Grace</span> <span class="checks-subline">Grace</span>
</th> </th>
<th>Last Ping</th>
<th class="th-last-ping">Last Ping</th>
<th></th> <th></th>
</tr> </tr>
{% for check in checks %} {% for check in checks %}
@ -71,11 +71,12 @@
</td> </td>
<td> <td>
{% if check.last_ping %} {% if check.last_ping %}
<span
data-toggle="tooltip"
title="{{ check.last_ping|date:'N j, Y, P e' }}">
<div class="last-ping" data-url="{% url 'hc-last-ping' check.code %}">
{{ check.last_ping|naturaltime }} {{ check.last_ping|naturaltime }}
</span>
{% if check.has_confirmation_link %}
<br /><span class="label label-confirmation">confirmation link</span>
{% endif %}
</div>
{% else %} {% else %}
Never Never
{% endif %} {% endif %}


Loading…
Cancel
Save