forked from GithubBackups/healthchecks
Save request body for every ping, not just the last one.
This commit is contained in:
parent
166115ebfb
commit
02f6853d4c
25
hc/api/migrations/0038_auto_20180318_1216.py
Normal file
25
hc/api/migrations/0038_auto_20180318_1216.py
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11.6 on 2018-03-18 12:16
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('api', '0037_auto_20180127_1215'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='check',
|
||||||
|
name='has_confirmation_link',
|
||||||
|
field=models.BooleanField(default=False),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='ping',
|
||||||
|
name='body',
|
||||||
|
field=models.CharField(blank=True, max_length=10000),
|
||||||
|
),
|
||||||
|
]
|
@ -71,6 +71,7 @@ class Check(models.Model):
|
|||||||
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=10000, blank=True)
|
last_ping_body = models.CharField(max_length=10000, blank=True)
|
||||||
|
has_confirmation_link = models.BooleanField(default=False)
|
||||||
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")
|
||||||
|
|
||||||
@ -182,13 +183,10 @@ class Check(models.Model):
|
|||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def has_confirmation_link(self):
|
|
||||||
return "confirm" in self.last_ping_body.lower()
|
|
||||||
|
|
||||||
def ping(self, remote_addr, scheme, method, ua, body):
|
def ping(self, remote_addr, scheme, method, ua, body):
|
||||||
self.n_pings = models.F("n_pings") + 1
|
self.n_pings = models.F("n_pings") + 1
|
||||||
self.last_ping = timezone.now()
|
self.last_ping = timezone.now()
|
||||||
self.last_ping_body = body[:10000]
|
self.has_confirmation_link = "confirm" in str(body).lower()
|
||||||
self.alert_after = self.get_alert_after()
|
self.alert_after = self.get_alert_after()
|
||||||
if self.status in ("new", "paused"):
|
if self.status in ("new", "paused"):
|
||||||
self.status = "up"
|
self.status = "up"
|
||||||
@ -203,6 +201,7 @@ class Check(models.Model):
|
|||||||
ping.method = method
|
ping.method = method
|
||||||
# If User-Agent is longer than 200 characters, truncate it:
|
# If User-Agent is longer than 200 characters, truncate it:
|
||||||
ping.ua = ua[:200]
|
ping.ua = ua[:200]
|
||||||
|
ping.body = body[:10000]
|
||||||
ping.save()
|
ping.save()
|
||||||
|
|
||||||
|
|
||||||
@ -215,6 +214,7 @@ class Ping(models.Model):
|
|||||||
remote_addr = models.GenericIPAddressField(blank=True, null=True)
|
remote_addr = models.GenericIPAddressField(blank=True, null=True)
|
||||||
method = models.CharField(max_length=10, blank=True)
|
method = models.CharField(max_length=10, blank=True)
|
||||||
ua = models.CharField(max_length=200, blank=True)
|
ua = models.CharField(max_length=200, blank=True)
|
||||||
|
body = models.CharField(max_length=10000, blank=True)
|
||||||
|
|
||||||
|
|
||||||
class Channel(models.Model):
|
class Channel(models.Model):
|
||||||
|
@ -36,11 +36,9 @@ class PingTestCase(TestCase):
|
|||||||
content_type="text/plain")
|
content_type="text/plain")
|
||||||
self.assertEqual(r.status_code, 200)
|
self.assertEqual(r.status_code, 200)
|
||||||
|
|
||||||
self.check.refresh_from_db()
|
|
||||||
self.assertEqual(self.check.last_ping_body, "hello world")
|
|
||||||
|
|
||||||
ping = Ping.objects.latest("id")
|
ping = Ping.objects.latest("id")
|
||||||
self.assertEqual(ping.method, "POST")
|
self.assertEqual(ping.method, "POST")
|
||||||
|
self.assertEqual(ping.body, "hello world")
|
||||||
|
|
||||||
def test_head_works(self):
|
def test_head_works(self):
|
||||||
csrf_client = Client(enforce_csrf_checks=True)
|
csrf_client = Client(enforce_csrf_checks=True)
|
||||||
@ -107,3 +105,12 @@ class PingTestCase(TestCase):
|
|||||||
def test_it_never_caches(self):
|
def test_it_never_caches(self):
|
||||||
r = self.client.get("/ping/%s/" % self.check.code)
|
r = self.client.get("/ping/%s/" % self.check.code)
|
||||||
assert "no-cache" in r.get("Cache-Control")
|
assert "no-cache" in r.get("Cache-Control")
|
||||||
|
|
||||||
|
def test_it_updates_confirmation_flag(self):
|
||||||
|
payload = "Please Confirm ..."
|
||||||
|
r = self.client.post("/ping/%s/" % self.check.code, data=payload,
|
||||||
|
content_type="text/plain")
|
||||||
|
self.assertEqual(r.status_code, 200)
|
||||||
|
|
||||||
|
self.check.refresh_from_db()
|
||||||
|
self.assertTrue(self.check.has_confirmation_link)
|
||||||
|
@ -28,9 +28,8 @@ def ping(request, code):
|
|||||||
scheme = headers.get("HTTP_X_FORWARDED_PROTO", "http")
|
scheme = headers.get("HTTP_X_FORWARDED_PROTO", "http")
|
||||||
method = headers["REQUEST_METHOD"]
|
method = headers["REQUEST_METHOD"]
|
||||||
ua = headers.get("HTTP_USER_AGENT", "")
|
ua = headers.get("HTTP_USER_AGENT", "")
|
||||||
body = request.body[:10000]
|
|
||||||
|
|
||||||
check.ping(remote_addr, scheme, method, ua, body)
|
check.ping(remote_addr, scheme, method, ua, request.body)
|
||||||
|
|
||||||
response = HttpResponse("OK")
|
response = HttpResponse("OK")
|
||||||
response["Access-Control-Allow-Origin"] = "*"
|
response["Access-Control-Allow-Origin"] = "*"
|
||||||
|
@ -1,21 +0,0 @@
|
|||||||
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)
|
|
37
hc/front/tests/test_ping_details.py
Normal file
37
hc/front/tests/test_ping_details.py
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
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.save()
|
||||||
|
|
||||||
|
Ping.objects.create(owner=check, body="this is body")
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
def test_it_accepts_n(self):
|
||||||
|
check = Check(user=self.alice)
|
||||||
|
check.last_ping_body = "this is body"
|
||||||
|
check.save()
|
||||||
|
|
||||||
|
# remote_addr, scheme, method, ua, body):
|
||||||
|
check.ping("1.2.3.4", "http", "post", "tester", "foo-123")
|
||||||
|
check.ping("1.2.3.4", "http", "post", "tester", "bar-456")
|
||||||
|
|
||||||
|
self.client.login(username="alice@example.org", password="password")
|
||||||
|
|
||||||
|
r = self.client.post("/checks/%s/pings/1/" % check.code)
|
||||||
|
self.assertContains(r, "foo-123", status_code=200)
|
||||||
|
|
||||||
|
r = self.client.post("/checks/%s/pings/2/" % check.code)
|
||||||
|
self.assertContains(r, "bar-456", status_code=200)
|
@ -8,7 +8,8 @@ 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"),
|
url(r'^last_ping/$', views.ping_details, name="hc-last-ping"),
|
||||||
|
url(r'^pings/([\d]+)/$', views.ping_details, name="hc-ping-details"),
|
||||||
]
|
]
|
||||||
|
|
||||||
channel_urls = [
|
channel_urls = [
|
||||||
|
@ -249,7 +249,7 @@ def cron_preview(request):
|
|||||||
|
|
||||||
|
|
||||||
@require_POST
|
@require_POST
|
||||||
def last_ping(request, code):
|
def ping_details(request, code, n=None):
|
||||||
if not request.user.is_authenticated:
|
if not request.user.is_authenticated:
|
||||||
return HttpResponseForbidden()
|
return HttpResponseForbidden()
|
||||||
|
|
||||||
@ -257,14 +257,18 @@ def last_ping(request, code):
|
|||||||
if check.user_id != request.team.user.id:
|
if check.user_id != request.team.user.id:
|
||||||
return HttpResponseForbidden()
|
return HttpResponseForbidden()
|
||||||
|
|
||||||
ping = Ping.objects.filter(owner=check).latest("created")
|
q = Ping.objects.filter(owner=check)
|
||||||
|
if n:
|
||||||
|
q = q.filter(n=n)
|
||||||
|
|
||||||
|
ping = q.latest("created")
|
||||||
|
|
||||||
ctx = {
|
ctx = {
|
||||||
"check": check,
|
"check": check,
|
||||||
"ping": ping
|
"ping": ping
|
||||||
}
|
}
|
||||||
|
|
||||||
return render(request, "front/last_ping.html", ctx)
|
return render(request, "front/ping_details.html", ctx)
|
||||||
|
|
||||||
|
|
||||||
@require_POST
|
@require_POST
|
||||||
|
@ -1,26 +1,26 @@
|
|||||||
#last-ping-body .modal-body {
|
#ping-details-body .modal-body {
|
||||||
padding: 40px;
|
padding: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#last-ping-body h3 {
|
#ping-details-body h3 {
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
margin: 0 0 24px 0;
|
margin: 0 0 24px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#last-ping-body .ua {
|
#ping-details-body .ua {
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
font-family: monospace;
|
font-family: monospace;
|
||||||
}
|
}
|
||||||
|
|
||||||
#last-ping-body p strong {
|
#ping-details-body p strong {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
#last-ping-body h4 {
|
#ping-details-body h4 {
|
||||||
margin-top: 24px;
|
margin-top: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#last-ping-body pre {
|
#ping-details-body pre {
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
@ -176,8 +176,8 @@ $(function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
$(".last-ping-cell").on("click", ".last-ping", function() {
|
$(".last-ping-cell").on("click", ".last-ping", function() {
|
||||||
$("#last-ping-body").text("Updating...");
|
$("#ping-details-body").text("Updating...");
|
||||||
$('#last-ping-modal').modal("show");
|
$('#ping-details-modal').modal("show");
|
||||||
|
|
||||||
var token = $('input[name=csrfmiddlewaretoken]').val();
|
var token = $('input[name=csrfmiddlewaretoken]').val();
|
||||||
$.ajax({
|
$.ajax({
|
||||||
@ -185,7 +185,7 @@ $(function () {
|
|||||||
type: "post",
|
type: "post",
|
||||||
headers: {"X-CSRFToken": token},
|
headers: {"X-CSRFToken": token},
|
||||||
success: function(data) {
|
success: function(data) {
|
||||||
$("#last-ping-body" ).html(data);
|
$("#ping-details-body" ).html(data);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,4 +1,21 @@
|
|||||||
$(function () {
|
$(function () {
|
||||||
|
$(".details-btn").on("click", function() {
|
||||||
|
$("#ping-details-body").text("Updating...");
|
||||||
|
$('#ping-details-modal').modal("show");
|
||||||
|
|
||||||
|
var token = $('input[name=csrfmiddlewaretoken]').val();
|
||||||
|
$.ajax({
|
||||||
|
url: this.dataset.url,
|
||||||
|
type: "post",
|
||||||
|
headers: {"X-CSRFToken": token},
|
||||||
|
success: function(data) {
|
||||||
|
$("#ping-details-body" ).html(data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
function switchDateFormat(format) {
|
function switchDateFormat(format) {
|
||||||
$("#log td.datetime").each(function(index, cell) {
|
$("#log td.datetime").each(function(index, cell) {
|
||||||
var dt = moment(cell.getAttribute("data-raw"));
|
var dt = moment(cell.getAttribute("data-raw"));
|
||||||
|
@ -36,7 +36,7 @@
|
|||||||
<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/add_webhook.css' %}" type="text/css">
|
<link rel="stylesheet" href="{% static 'css/add_webhook.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">
|
<link rel="stylesheet" href="{% static 'css/ping_details.css' %}" type="text/css">
|
||||||
<link rel="stylesheet" href="{% static 'css/profile.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/checkbox.css' %}" type="text/css">
|
||||||
<link rel="stylesheet" href="{% static 'css/radio.css' %}" type="text/css">
|
<link rel="stylesheet" href="{% static 'css/radio.css' %}" type="text/css">
|
||||||
|
@ -40,6 +40,7 @@
|
|||||||
<th class="ip">IP</th>
|
<th class="ip">IP</th>
|
||||||
<th class="protocol">Protocol</th>
|
<th class="protocol">Protocol</th>
|
||||||
<th class="ua">User Agent</th>
|
<th class="ua">User Agent</th>
|
||||||
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
{% for event in events %}
|
{% for event in events %}
|
||||||
{% if event.n %}
|
{% if event.n %}
|
||||||
@ -66,6 +67,12 @@
|
|||||||
<td class="ua">
|
<td class="ua">
|
||||||
{{ event.ua }}
|
{{ event.ua }}
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
{% if event.body %}
|
||||||
|
<a href="#" class="details-btn" data-url="{% url 'hc-ping-details' check.code event.n %}">show body</a>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if event.check_status %}
|
{% if event.check_status %}
|
||||||
@ -112,7 +119,7 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
{% if show_limit_notice and limit < 10000 %}
|
{% if show_limit_notice and limit < 1000 %}
|
||||||
<p class="alert alert-info">
|
<p class="alert alert-info">
|
||||||
<strong>Showing last {{ limit }} pings.</strong>
|
<strong>Showing last {{ limit }} pings.</strong>
|
||||||
Want to see more?
|
Want to see more?
|
||||||
@ -128,6 +135,21 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="ping-details-modal" class="modal">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div id="ping-details-body">Loading</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-default" data-dismiss="modal">Got It!</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<form>
|
||||||
|
{% csrf_token %}
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block scripts %}
|
{% block scripts %}
|
||||||
|
@ -354,10 +354,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="last-ping-modal" class="modal">
|
<div id="ping-details-modal" class="modal">
|
||||||
<div class="modal-dialog">
|
<div class="modal-dialog">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div id="last-ping-body">Loading</div>
|
<div id="ping-details-body">Loading</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-default" data-dismiss="modal">Got It!</button>
|
<button type="button" class="btn btn-default" data-dismiss="modal">Got It!</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -26,8 +26,8 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% if check.last_ping_body %}
|
{% if ping.body %}
|
||||||
<h4>Request Body</h4>
|
<h4>Request Body</h4>
|
||||||
<pre>{{ check.last_ping_body }}</pre>
|
<pre>{{ ping.body }}</pre>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
Loading…
x
Reference in New Issue
Block a user