Browse Source

Add Pushover integration

pull/12/head
Thomas Jost 9 years ago
parent
commit
85c1f65887
14 changed files with 224 additions and 10 deletions
  1. +11
    -0
      README.md
  2. +2
    -0
      hc/api/admin.py
  3. +19
    -0
      hc/api/migrations/0017_auto_20151117_1032.py
  4. +36
    -1
      hc/api/models.py
  5. +1
    -1
      hc/front/tests/test_add_channel.py
  6. +1
    -0
      hc/front/urls.py
  7. +63
    -6
      hc/front/views.py
  8. +4
    -0
      hc/settings.py
  9. +4
    -0
      static/css/channels.css
  10. BIN
      static/img/integrations/pushover.png
  11. +1
    -1
      templates/front/channel_checks.html
  12. +18
    -1
      templates/front/channels.html
  13. +50
    -0
      templates/integrations/add_pushover.html
  14. +14
    -0
      templates/integrations/pushover_message.html

+ 11
- 0
README.md View File

@ -63,3 +63,14 @@ in development environment.
$ ./manage.py runserver
## Integrations
### Pushover
To enable Pushover integration, you will need to:
* register a new application on https://pushover.net/apps/build
* enable subscriptions in your application and make sure to enable the URL
subscription type
* add the application token and subscription URL to `hc/local_settings.py`, as
`PUSHOVER_API_TOKEN` and `PUSHOVER_SUBSCRIPTION_URL`

+ 2
- 0
hc/api/admin.py View File

@ -146,6 +146,8 @@ class ChannelsAdmin(admin.ModelAdmin):
def formatted_kind(self, obj):
if obj.kind == "pd":
return "PagerDuty"
elif obj.kind == "po":
return "Pushover"
elif obj.kind == "webhook":
return "Webhook"
elif obj.kind == "slack":


+ 19
- 0
hc/api/migrations/0017_auto_20151117_1032.py View File

@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('api', '0016_auto_20151030_1107'),
]
operations = [
migrations.AlterField(
model_name='channel',
name='kind',
field=models.CharField(choices=[('email', 'Email'), ('webhook', 'Webhook'), ('hipchat', 'HipChat'), ('slack', 'Slack'), ('pd', 'PagerDuty'), ('po', 'Pushover')], max_length=20),
),
]

+ 36
- 1
hc/api/models.py View File

@ -24,7 +24,7 @@ DEFAULT_TIMEOUT = td(days=1)
DEFAULT_GRACE = td(hours=1)
CHANNEL_KINDS = (("email", "Email"), ("webhook", "Webhook"),
("hipchat", "HipChat"),
("slack", "Slack"), ("pd", "PagerDuty"))
("slack", "Slack"), ("pd", "PagerDuty"), ("po", "Pushover"))
class Check(models.Model):
@ -183,6 +183,41 @@ class Channel(models.Model):
n.status = r.status_code
n.save()
elif self.kind == "po":
tmpl = "integrations/pushover_message.html"
ctx = {
"check": check,
"down_checks": self.user.check_set.filter(status="down").exclude(code=check.code).order_by("created"),
}
text = render_to_string(tmpl, ctx).strip()
if check.status == "down":
title = "%s is DOWN" % check.name_then_code()
else:
title = "%s is now UP" % check.name_then_code()
user_key, priority = self.po_value
payload = {
"token": settings.PUSHOVER_API_TOKEN,
"user": user_key,
"message": text,
"title": title,
"html": 1,
"priority": priority,
}
url = "https://api.pushover.net/1/messages.json"
r = requests.post(url, data=payload, timeout=5)
n.status = r.status_code
n.save()
@property
def po_value(self):
assert self.kind == "po"
user_key, prio = self.value.split("|")
prio = int(prio)
return user_key, prio
class Notification(models.Model):
owner = models.ForeignKey(Check)


+ 1
- 1
hc/front/tests/test_add_channel.py View File

@ -31,7 +31,7 @@ class AddChannelTestCase(TestCase):
def test_instructions_work(self):
self.client.login(username="alice", password="password")
for frag in ("email", "webhook", "pd", "slack", "hipchat"):
for frag in ("email", "webhook", "pd", "pushover", "slack", "hipchat"):
url = "/integrations/add_%s/" % frag
r = self.client.get(url)
self.assertContains(r, "Integration Settings", status_code=200)

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

@ -21,6 +21,7 @@ urlpatterns = [
url(r'^integrations/add_pd/$', views.add_pd, name="hc-add-pd"),
url(r'^integrations/add_slack/$', views.add_slack, name="hc-add-slack"),
url(r'^integrations/add_hipchat/$', views.add_hipchat, name="hc-add-hipchat"),
url(r'^integrations/add_pushover/$', views.add_pushover, name="hc-add-pushover"),
url(r'^integrations/([\w-]+)/checks/$', views.channel_checks, name="hc-channel-checks"),
url(r'^integrations/([\w-]+)/remove/$', views.remove_channel, name="hc-remove-channel"),
url(r'^integrations/([\w-]+)/verify/([\w-]+)/$',


+ 63
- 6
hc/front/views.py View File

@ -1,10 +1,13 @@
from datetime import timedelta as td
from django.conf import settings
from django.core.urlresolvers import reverse
from django.contrib.auth.decorators import login_required
from django.http import HttpResponseBadRequest, HttpResponseForbidden
from django.shortcuts import get_object_or_404, redirect, render
from django.utils import timezone
from django.utils.six.moves.urllib.parse import urlencode
from django.utils.crypto import get_random_string
from hc.api.decorators import uuid_or_400
from hc.api.models import Channel, Check, Ping
from hc.front.forms import AddChannelForm, TimeoutForm
@ -224,16 +227,14 @@ def channels(request):
ctx = {
"page": "channels",
"channels": channels,
"num_checks": num_checks
"num_checks": num_checks,
"enable_pushover": settings.PUSHOVER_API_TOKEN is not None,
}
return render(request, "front/channels.html", ctx)
@login_required
def add_channel(request):
assert request.method == "POST"
form = AddChannelForm(request.POST)
def do_add_channel(request, data):
form = AddChannelForm(data)
if form.is_valid():
channel = form.save(commit=False)
channel.user = request.user
@ -250,6 +251,11 @@ def add_channel(request):
return HttpResponseBadRequest()
@login_required
def add_channel(request):
assert request.method == "POST"
return do_add_channel(request, request.POST)
@login_required
@uuid_or_400
def channel_checks(request, code):
@ -322,3 +328,54 @@ def add_slack(request):
def add_hipchat(request):
ctx = {"page": "channels"}
return render(request, "integrations/add_hipchat.html", ctx)
@login_required
def add_pushover(request):
if settings.PUSHOVER_API_TOKEN is None or settings.PUSHOVER_SUBSCRIPTION_URL is None:
return HttpResponseForbidden()
if request.method == "POST":
# Initiate the subscription
nonce = get_random_string()
request.session["po_nonce"] = nonce
failure_url = request.build_absolute_uri(reverse("hc-channels"))
success_url = request.build_absolute_uri(reverse("hc-add-pushover")) + "?" + urlencode({
"nonce": nonce,
"prio": request.POST.get("po_priority", "0"),
})
subscription_url = settings.PUSHOVER_SUBSCRIPTION_URL + "?" + urlencode({
"success": success_url,
"failure": failure_url,
})
return redirect(subscription_url)
# Handle successful subscriptions
if "pushover_user_key" in request.GET and "nonce" in request.GET and "prio" in request.GET:
# Validate nonce
if request.GET["nonce"] != request.session.get("po_nonce", None):
return HttpResponseForbidden()
del request.session["po_nonce"]
if request.GET.get("pushover_unsubscribed", "0") == "1":
# Unsubscription: delete all Pushover channels for this user
for channel in Channel.objects.filter(user=request.user, kind="po"):
channel.delete()
return redirect("hc-channels")
else:
# Subscription
user_key = request.GET["pushover_user_key"]
priority = int(request.GET["prio"])
return do_add_channel(request, {
"kind": "po",
"value": "%s|%d" % (user_key, priority),
})
else:
ctx = {"page": "channels"}
return render(request, "integrations/add_pushover.html", ctx)

+ 4
- 0
hc/settings.py View File

@ -125,6 +125,10 @@ COMPRESS_OFFLINE = True
EMAIL_BACKEND = "djmail.backends.default.EmailBackend"
# Pushover integration -- override these in local_settings
PUSHOVER_API_TOKEN = None
PUSHOVER_SUBSCRIPTION_URL = None
try:
from .local_settings import *
except ImportError as e:


+ 4
- 0
static/css/channels.css View File

@ -126,6 +126,10 @@ table.channels-table > tbody > tr > th {
height: 48px;
}
.btn img.ai-icon {
height: 1.4em;
}
.add-integration h2 {
margin-top: 0;
}


BIN
static/img/integrations/pushover.png View File

Before After
Width: 310  |  Height: 310  |  Size: 12 KiB

+ 1
- 1
templates/front/channel_checks.html View File

@ -5,7 +5,7 @@
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">&times;</span></button>
<h4 class="update-timeout-title">Assign Checks to Channel {{ channel.value }}</h4>
<h4 class="update-timeout-title">Assign Checks to Channel {% if channel.kind == "po" %}{{ channel.po_value|join:" / " }}{% else %}{{ channel.value }}{% endif %}</h4>
</div>
<input type="hidden" name="channel" value="{{ channel.code }}" />


+ 18
- 1
templates/front/channels.html View File

@ -23,14 +23,20 @@
{% if ch.kind == "slack" %} Slack {% endif %}
{% if ch.kind == "hipchat" %} HipChat {% endif %}
{% if ch.kind == "pd" %} PagerDuty {% endif %}
{% if ch.kind == "po" %} Pushover {% endif %}
</td>
<td>
<span class="preposition">
{% if ch.kind == "email" %} to {% endif %}
{% if ch.kind == "pd" %} API key {% endif %}
{% if ch.kind == "po" %} User key / priority {% endif %}
</span>
{{ ch.value }}
{% if ch.kind == "po" %}
{{ ch.po_value|join:" / " }}
{% else %}
{{ ch.value }}
{% endif %}
{% if ch.kind == "email" and not ch.email_verified %}
<span class="channel-unconfirmed">(unconfirmed)
@ -108,6 +114,17 @@
<a href="{% url 'hc-add-hipchat' %}" class="btn btn-primary">Add Integration</a>
</li>
{% if enable_pushover %}
<li>
<img src="{% static 'img/integrations/pushover.png' %}"
alt="Pushover icon" />
<h2>Pushover</h2>
<p>Receive instant push notifications on your phone or tablet.</p>
<a href="{% url 'hc-add-pushover' %}" class="btn btn-primary">Add Integration</a>
</li>
{% endif %}
</ul>


+ 50
- 0
templates/integrations/add_pushover.html View File

@ -0,0 +1,50 @@
{% extends "base.html" %}
{% load compress humanize staticfiles hc_extras %}
{% block title %}Add Pushover - healthchecks.io{% endblock %}
{% block content %}
<div class="row">
<div class="col-sm-12">
<h1>Pushover</h1>
<p><a href="https://www.pushover.net/">Pushover</a> is a service to receive
instant push notifications on your phone or tablet from a variety of
sources. If you bought the app on your mobile device, you can integrate it
with your healthchecks.io account in a few simple steps.</p>
<h2>Integration Settings</h2>
<form method="post" class="form-horizontal" action="{% url 'hc-add-pushover' %}">
{% csrf_token %}
<div class="form-group">
<label for="po_priority" class="col-sm-2 control-label">Notification priority</label>
<div class="col-sm-3">
<select class="form-control" id="po_priority" name="po_priority">
<option value="-2">Lowest</option>
<option value="-1">Low</option>
<option value="0" selected>Normal</option>
<option value="1">High</option>
</select>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-default">
<img class="ai-icon" src="{% static 'img/integrations/pushover.png' %}" alt="Pushover" />
Subscribe with Pushover
</button>
</div>
</div>
</form>
</div>
</div>
{% endblock %}
{% block scripts %}
{% compress js %}
<script src="{% static 'js/jquery-2.1.4.min.js' %}"></script>
<script src="{% static 'js/bootstrap.min.js' %}"></script>
{% endcompress %}
{% endblock %}

+ 14
- 0
templates/integrations/pushover_message.html View File

@ -0,0 +1,14 @@
{% load humanize %}
{% if check.status == "down" %}
The check "{{ check.name_then_code }}" is <b>DOWN</b>.
Last ping was {{ check.last_ping|naturaltime }}.
{% else %}
The check "{{ check.name_then_code }}" received a ping and is now <b>UP</b>.
{% endif %}{% if down_checks %}
The following checks are {% if check.status == "down" %}also{% else %}still{% endif %} down:
{% for down_check in down_checks %}- "{{ down_check.name_then_code }}" (last ping: {{ down_check.last_ping|naturaltime }})
{% endfor %}
{% else %}
All the other checks are up.
{% endif %}

Loading…
Cancel
Save