From 3550218129c6bcde23dada9abec9060cf69b3d4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C4=93teris=20Caune?= Date: Mon, 27 Jul 2015 19:46:38 +0300 Subject: [PATCH] Keep a log of pings --- hc/api/admin.py | 11 +++++++-- hc/api/migrations/0007_ping.py | 26 +++++++++++++++++++++ hc/api/models.py | 14 ++++++++++++ hc/api/views.py | 10 +++++++- hc/front/urls.py | 17 +++++++------- hc/front/views.py | 20 +++++++++++++++- static/css/style.css | 10 ++++++-- templates/front/log.html | 42 ++++++++++++++++++++++++++++++++++ templates/front/my_checks.html | 7 ++++++ 9 files changed, 143 insertions(+), 14 deletions(-) create mode 100644 hc/api/migrations/0007_ping.py create mode 100644 templates/front/log.html diff --git a/hc/api/admin.py b/hc/api/admin.py index e7c3294f..db7e14e3 100644 --- a/hc/api/admin.py +++ b/hc/api/admin.py @@ -1,16 +1,18 @@ from django.contrib import admin -from hc.api.models import Check +from hc.api.models import Check, Ping @admin.register(Check) class ChecksAdmin(admin.ModelAdmin): + class Media: css = { 'all': ('css/admin/checks.css',) } - list_display = ("id", "name", "created", "code", "status", "email", "last_ping") + list_display = ("id", "name", "created", "code", "status", "email", + "last_ping") list_select_related = ("user", ) actions = ["send_alert"] @@ -24,3 +26,8 @@ class ChecksAdmin(admin.ModelAdmin): self.message_user(request, "%d alert(s) sent" % qs.count()) send_alert.short_description = "Send Alert" + + +@admin.register(Ping) +class PingsAdmin(admin.ModelAdmin): + list_display = ("id", "created", "owner", "method", "ua") diff --git a/hc/api/migrations/0007_ping.py b/hc/api/migrations/0007_ping.py new file mode 100644 index 00000000..b70f4722 --- /dev/null +++ b/hc/api/migrations/0007_ping.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0006_check_grace'), + ] + + operations = [ + migrations.CreateModel( + name='Ping', + fields=[ + ('id', models.AutoField(primary_key=True, serialize=False, auto_created=True, verbose_name='ID')), + ('created', models.DateTimeField(auto_now_add=True)), + ('remote_addr', models.GenericIPAddressField()), + ('method', models.CharField(max_length=10)), + ('ua', models.CharField(max_length=100, blank=True)), + ('body', models.TextField(blank=True)), + ('owner', models.ForeignKey(to='api.Check')), + ], + ), + ] diff --git a/hc/api/models.py b/hc/api/models.py index 6e10e6ff..d463e537 100644 --- a/hc/api/models.py +++ b/hc/api/models.py @@ -1,3 +1,5 @@ +# coding: utf-8 + from datetime import timedelta as td import uuid @@ -24,6 +26,9 @@ class Check(models.Model): alert_after = models.DateTimeField(null=True, blank=True, editable=False) status = models.CharField(max_length=6, choices=STATUSES, default="new") + def __str__(self): + return "Check(%s)" % self.code + def url(self): return settings.PING_ENDPOINT + str(self.code) @@ -53,3 +58,12 @@ class Check(models.Model): return "grace" return "down" + + +class Ping(models.Model): + owner = models.ForeignKey(Check) + created = models.DateTimeField(auto_now_add=True) + remote_addr = models.GenericIPAddressField() + method = models.CharField(max_length=10) + ua = models.CharField(max_length=100, blank=True) + body = models.TextField(blank=True) diff --git a/hc/api/views.py b/hc/api/views.py index 0d6ed46f..b73936a6 100644 --- a/hc/api/views.py +++ b/hc/api/views.py @@ -5,7 +5,7 @@ from django.http import HttpResponse, HttpResponseBadRequest from django.utils import timezone from django.views.decorators.csrf import csrf_exempt -from hc.api.models import Check +from hc.api.models import Check, Ping @csrf_exempt @@ -21,6 +21,14 @@ def ping(request, code): check.save() + ping = Ping(owner=check) + headers = request.META + ping.remote_addr = headers.get("X_REAL_IP", headers["REMOTE_ADDR"]) + ping.method = headers["REQUEST_METHOD"] + ping.ua = headers.get("HTTP_USER_AGENT", "") + ping.body = request.body + ping.save() + response = HttpResponse("OK") response["Access-Control-Allow-Origin"] = "*" return response diff --git a/hc/front/urls.py b/hc/front/urls.py index 56c4c030..0a059843 100644 --- a/hc/front/urls.py +++ b/hc/front/urls.py @@ -3,13 +3,14 @@ from django.conf.urls import url from hc.front import views urlpatterns = [ - url(r'^$', views.index, name="hc-index"), - url(r'^checks/add/$', views.add_check, name="hc-add-check"), - url(r'^checks/([\w-]+)/name/$', views.update_name, name="hc-update-name"), + url(r'^$', views.index, name="hc-index"), + url(r'^checks/add/$', views.add_check, name="hc-add-check"), + url(r'^checks/([\w-]+)/name/$', views.update_name, name="hc-update-name"), url(r'^checks/([\w-]+)/timeout/$', views.update_timeout, name="hc-update-timeout"), - url(r'^checks/([\w-]+)/email/$', views.email_preview), - url(r'^checks/([\w-]+)/remove/$', views.remove, name="hc-remove-check"), - url(r'^pricing/$', views.pricing, name="hc-pricing"), - url(r'^docs/$', views.docs, name="hc-docs"), - url(r'^about/$', views.about, name="hc-about"), + url(r'^checks/([\w-]+)/email/$', views.email_preview), + url(r'^checks/([\w-]+)/remove/$', views.remove, name="hc-remove-check"), + url(r'^checks/([\w-]+)/log/$', views.log, name="hc-log"), + url(r'^pricing/$', views.pricing, name="hc-pricing"), + url(r'^docs/$', views.docs, name="hc-docs"), + url(r'^about/$', views.about, name="hc-about"), ] diff --git a/hc/front/views.py b/hc/front/views.py index 9b0ea69b..3cc190d6 100644 --- a/hc/front/views.py +++ b/hc/front/views.py @@ -6,7 +6,7 @@ from django.http import HttpResponseForbidden from django.shortcuts import redirect, render from django.utils import timezone -from hc.api.models import Check +from hc.api.models import Check, Ping from hc.front.forms import TimeoutForm @@ -147,3 +147,21 @@ def remove(request, code): check.delete() return redirect("hc-index") + + +@login_required +def log(request, code): + + check = Check.objects.get(code=code) + if check.user != request.user: + return HttpResponseForbidden() + + pings = Ping.objects.filter(owner=check).order_by("-created")[:100] + + ctx = { + "check": check, + "pings": pings + + } + + return render(request, "front/log.html", ctx) diff --git a/static/css/style.css b/static/css/style.css index 02317def..a450d6ad 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -131,7 +131,7 @@ table.table tr > th.th-name { display: block; } -.my-checks-name:hover { +#checks-table tr:hover .my-checks-name { border: 1px dotted #AAA; } @@ -142,6 +142,7 @@ table.table tr > th.th-name { .url-cell { font-size: small; + position: relative; } td.inactive .popover { @@ -167,7 +168,7 @@ td.inactive .popover { display: block; } -.timeout_grace:hover { +#checks-table tr:hover .timeout_grace { border: 1px dotted #AAA; } @@ -243,3 +244,8 @@ tr:hover .check-menu { font-weight: bold; } +/* Log */ + +.log-table .remote-addr, .log-table .ua { + font-family: monospace; +} \ No newline at end of file diff --git a/templates/front/log.html b/templates/front/log.html new file mode 100644 index 00000000..8b945fdd --- /dev/null +++ b/templates/front/log.html @@ -0,0 +1,42 @@ +{% extends "base.html" %} +{% load compress humanize staticfiles hc_extras %} + +{% block title %}My Checks - healthchecks.io{% endblock %} + + +{% block content %} +
+
+

Log for {{ check.name|default:check.code }}

+ {% if pings %} + + + + + + + + {% for ping in pings %} + + + + + + + {% endfor %} +
TimeRemote IPMethodUser Agent
+ + {{ ping.created }} + + {{ ping.remote_addr }} + {{ ping.method }} + {{ ping.ua }}
+ {% else %} +
Log is empty. This check has not received any pings yet.
+ {% endif %} +
+
+{% endblock %} + diff --git a/templates/front/my_checks.html b/templates/front/my_checks.html index 9fd1f95b..0f4bb8dd 100644 --- a/templates/front/my_checks.html +++ b/templates/front/my_checks.html @@ -75,6 +75,13 @@