diff --git a/hc/api/models.py b/hc/api/models.py index 32313aa3..dd2f9245 100644 --- a/hc/api/models.py +++ b/hc/api/models.py @@ -41,6 +41,7 @@ CHANNEL_KINDS = ( ("trello", "Trello"), ("matrix", "Matrix"), ("whatsapp", "WhatsApp"), + ("apprise", "Apprise"), ) PO_PRIORITIES = {-2: "lowest", -1: "low", 0: "normal", 1: "high", 2: "emergency"} @@ -392,6 +393,8 @@ class Channel(models.Model): return transports.Matrix(self) elif self.kind == "whatsapp": return transports.WhatsApp(self) + elif self.kind == "apprise": + return transports.Apprise(self) else: raise NotImplementedError("Unknown channel kind: %s" % self.kind) diff --git a/hc/api/transports.py b/hc/api/transports.py index 9475a89f..41e54ced 100644 --- a/hc/api/transports.py +++ b/hc/api/transports.py @@ -3,6 +3,7 @@ from django.template.loader import render_to_string from django.utils import timezone import json import requests +import apprise from urllib.parse import quote, urlencode from hc.accounts.models import Profile @@ -273,7 +274,7 @@ class PagerTree(HttpTransport): class PagerTeam(HttpTransport): def notify(self, check): url = self.channel.value - headers = {"Conent-Type": "application/json"} + headers = {"Content-Type": "application/json"} payload = { "incident_key": str(check.code), "event_type": "trigger" if check.status == "down" else "resolve", @@ -461,3 +462,17 @@ class Trello(HttpTransport): } return self.post(self.URL, params=params) + +class Apprise(HttpTransport): + def notify(self, check): + a = apprise.Apprise() + title = tmpl("apprise_title.html", check=check) + body = tmpl("apprise_description.html", check=check) + + a.add(self.channel.value) + + notify_type = apprise.NotifyType.SUCCESS \ + if check.status == "up" else apprise.NotifyType.FAILURE + + return "Failed" if not \ + a.notify(body=body, title=title, notify_type=notify_type) else None diff --git a/hc/front/forms.py b/hc/front/forms.py index 30494ade..4e01e49a 100644 --- a/hc/front/forms.py +++ b/hc/front/forms.py @@ -159,3 +159,8 @@ class AddMatrixForm(forms.Form): self.cleaned_data["room_id"] = doc["room_id"] return v + + +class AddAppriseForm(forms.Form): + error_css_class = "has-error" + url = forms.CharField(max_length=512) diff --git a/hc/front/tests/test_add_apprise.py b/hc/front/tests/test_add_apprise.py new file mode 100644 index 00000000..cd1cb30b --- /dev/null +++ b/hc/front/tests/test_add_apprise.py @@ -0,0 +1,21 @@ +from hc.api.models import Channel +from hc.test import BaseTestCase + + +class AddSlackTestCase(BaseTestCase): + def test_instructions_work(self): + self.client.login(username="alice@example.org", password="password") + r = self.client.get("/integrations/add_apprise/") + self.assertContains(r, "Integration Settings", status_code=200) + + def test_it_works(self): + form = {"url": "json://example.org"} + + self.client.login(username="alice@example.org", password="password") + r = self.client.post("/integrations/add_apprise/", form) + self.assertRedirects(r, "/integrations/") + + c = Channel.objects.get() + self.assertEqual(c.kind, "apprise") + self.assertEqual(c.value, "json://example.org") + self.assertEqual(c.project, self.project) diff --git a/hc/front/urls.py b/hc/front/urls.py index d3643d4f..e23ba45a 100644 --- a/hc/front/urls.py +++ b/hc/front/urls.py @@ -43,6 +43,7 @@ channel_urls = [ path("add_trello/", views.add_trello, name="hc-add-trello"), path("add_trello/settings/", views.trello_settings, name="hc-trello-settings"), path("add_matrix/", views.add_matrix, name="hc-add-matrix"), + path("add_apprise/", views.add_apprise, name="hc-add-apprise"), path("/checks/", views.channel_checks, name="hc-channel-checks"), path("/name/", views.update_channel_name, name="hc-channel-name"), path("/test/", views.send_test_notification, name="hc-channel-test"), diff --git a/hc/front/views.py b/hc/front/views.py index 398cc1db..c02025d1 100644 --- a/hc/front/views.py +++ b/hc/front/views.py @@ -44,6 +44,7 @@ from hc.front.forms import ( ChannelNameForm, EmailSettingsForm, AddMatrixForm, + AddAppriseForm, ) from hc.front.schemas import telegram_callback from hc.front.templatetags.hc_extras import num_down_title, down_title, sortchecks @@ -1325,6 +1326,29 @@ def add_matrix(request): return render(request, "integrations/add_matrix.html", ctx) +@login_required +def add_apprise(request): + if request.method == "POST": + form = AddAppriseForm(request.POST) + if form.is_valid(): + channel = Channel(project=request.project, kind="apprise") + channel.value = form.cleaned_data["url"] + channel.save() + + channel.assign_all_checks() + messages.success(request, "The Apprise integration has been added!") + return redirect("hc-channels") + else: + form = AddAppriseForm() + + ctx = { + "page": "channels", + "project": request.project, + "form": form, + } + return render(request, "integrations/add_apprise.html", ctx) + + @login_required @require_POST def trello_settings(request): diff --git a/hc/settings.py b/hc/settings.py index 612811f1..6f31a3bf 100644 --- a/hc/settings.py +++ b/hc/settings.py @@ -204,6 +204,7 @@ MATRIX_HOMESERVER = os.getenv("MATRIX_HOMESERVER") MATRIX_USER_ID = os.getenv("MATRIX_USER_ID") MATRIX_ACCESS_TOKEN = os.getenv("MATRIX_ACCESS_TOKEN") + if os.path.exists(os.path.join(BASE_DIR, "hc/local_settings.py")): from .local_settings import * else: diff --git a/requirements.txt b/requirements.txt index 88ba406c..027adb1a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,3 +4,4 @@ django_compressor==2.2 psycopg2==2.7.5 pytz==2019.1 requests==2.22.0 +apprise==0.7.9 diff --git a/static/img/integrations/apprise.png b/static/img/integrations/apprise.png new file mode 100644 index 00000000..46533dc8 Binary files /dev/null and b/static/img/integrations/apprise.png differ diff --git a/templates/front/channels.html b/templates/front/channels.html index 22400afe..5f398e6f 100644 --- a/templates/front/channels.html +++ b/templates/front/channels.html @@ -69,6 +69,8 @@ {% endif %} {% elif ch.kind == "webhook" %} Webhook + {% elif ch.kind == "apprise" %} + Apprise {% elif ch.kind == "pushbullet" %} Pushbullet {% elif ch.kind == "discord" %} @@ -211,6 +213,15 @@ Add Integration +
  • + Pushover icon + +

    Apprise

    +

    Receive instant push notifications using Apprise; see all of the supported services here.

    + + Add Integration +
  • {% if enable_pushover %}
  • {% endif %} +
    +
    + Apprise icon +

    WhatsApp
    Chat

    +
    +
    diff --git a/templates/integrations/add_apprise.html b/templates/integrations/add_apprise.html new file mode 100644 index 00000000..e079ccfa --- /dev/null +++ b/templates/integrations/add_apprise.html @@ -0,0 +1,49 @@ +{% extends "base.html" %} +{% load humanize static hc_extras %} + +{% block title %}Add Apprise - {% site_name %}{% endblock %} + +{% block content %} +
    +
    +

    Apprise

    + +

    + Identify as many Apprise URLs as you wish. You can use a comma (,) to identify + more than on URL if you wish to. + + For a detailed list of all supported Apprise Notification URLs simply + click here. +

    + +

    Integration Settings

    + +
    + {% csrf_token %} + +
    + +
    + + + {% if form.url.errors %} +
    + {{ form.url.errors|join:"" }} +
    + {% endif %} +
    +
    +
    +
    + +
    +
    +
    +
    +
    +{% endblock %} diff --git a/templates/integrations/apprise_description.html b/templates/integrations/apprise_description.html new file mode 100644 index 00000000..22c9e800 --- /dev/null +++ b/templates/integrations/apprise_description.html @@ -0,0 +1,5 @@ +{% load humanize %} +{{ check.name_then_code }} is {{ check.status|upper }}. +{% if check.status == "down" %} +Last ping was {{ check.last_ping|naturaltime }}. +{% endif %} diff --git a/templates/integrations/apprise_title.html b/templates/integrations/apprise_title.html new file mode 100644 index 00000000..29274284 --- /dev/null +++ b/templates/integrations/apprise_title.html @@ -0,0 +1 @@ +{{ check.name_then_code }} is {{ check.status|upper }}