forked from GithubBackups/healthchecks
Adding Check.last_ping_body field, and an UI to show it (#116)
This commit is contained in:
parent
c552fe9ce2
commit
3862cd6b06
20
hc/api/migrations/0030_check_last_ping_body.py
Normal file
20
hc/api/migrations/0030_check_last_ping_body.py
Normal 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),
|
||||||
|
),
|
||||||
|
]
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
@ -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
hc/front/tests/test_last_ping.py
Normal file
21
hc/front/tests/test_last_ping.py
Normal 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="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)
|
@ -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 = [
|
||||||
|
@ -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
static/css/last_ping.css
Normal file
26
static/css/last_ping.css
Normal 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;
|
||||||
|
}
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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,
|
||||||
|
@ -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
templates/front/last_ping.html
Normal file
33
templates/front/last_ping.html
Normal 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>
|
@ -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>
|
||||||
|
@ -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
|
<div class="last-ping" data-url="{% url 'hc-last-ping' check.code %}">
|
||||||
data-toggle="tooltip"
|
|
||||||
title="{{ check.last_ping|date:'N j, Y, P e' }}">
|
|
||||||
{{ 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…
x
Reference in New Issue
Block a user