forked from GithubBackups/healthchecks
Add Pushover integration
This commit is contained in:
parent
f08221a1db
commit
85c1f65887
11
README.md
11
README.md
@ -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`
|
||||
|
@ -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
hc/api/migrations/0017_auto_20151117_1032.py
Normal file
19
hc/api/migrations/0017_auto_20151117_1032.py
Normal 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),
|
||||
),
|
||||
]
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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-]+)/$',
|
||||
|
@ -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)
|
||||
|
@ -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:
|
||||
|
@ -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
Normal file
BIN
static/img/integrations/pushover.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
@ -5,7 +5,7 @@
|
||||
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal">×</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 }}" />
|
||||
|
@ -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
templates/integrations/add_pushover.html
Normal file
50
templates/integrations/add_pushover.html
Normal 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
templates/integrations/pushover_message.html
Normal file
14
templates/integrations/pushover_message.html
Normal 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…
x
Reference in New Issue
Block a user