Browse Source

Matrix integration WIP. cc: #175

pull/226/head
Pēteris Caune 6 years ago
parent
commit
f539e99652
No known key found for this signature in database GPG Key ID: E28D7679E9A9EDE2
11 changed files with 163 additions and 4 deletions
  1. +4
    -1
      hc/api/models.py
  2. +23
    -1
      hc/api/transports.py
  3. +24
    -0
      hc/front/forms.py
  4. +1
    -0
      hc/front/urls.py
  5. +34
    -2
      hc/front/views.py
  6. +4
    -0
      hc/settings.py
  7. BIN
      static/img/integrations/matrix.png
  8. +13
    -0
      templates/front/channels.html
  9. +48
    -0
      templates/integrations/add_matrix.html
  10. +6
    -0
      templates/integrations/matrix_description.html
  11. +6
    -0
      templates/integrations/matrix_description_formatted.html

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

@ -43,7 +43,8 @@ CHANNEL_KINDS = (("email", "Email"),
("telegram", "Telegram"), ("telegram", "Telegram"),
("sms", "SMS"), ("sms", "SMS"),
("zendesk", "Zendesk"), ("zendesk", "Zendesk"),
("trello", "Trello"))
("trello", "Trello"),
("matrix", "Matrix"))
PO_PRIORITIES = { PO_PRIORITIES = {
-2: "lowest", -2: "lowest",
@ -339,6 +340,8 @@ class Channel(models.Model):
return transports.Sms(self) return transports.Sms(self)
elif self.kind == "trello": elif self.kind == "trello":
return transports.Trello(self) return transports.Trello(self)
elif self.kind == "matrix":
return transports.Matrix(self)
else: else:
raise NotImplementedError("Unknown channel kind: %s" % self.kind) raise NotImplementedError("Unknown channel kind: %s" % self.kind)


+ 23
- 1
hc/api/transports.py View File

@ -3,7 +3,7 @@ from django.template.loader import render_to_string
from django.utils import timezone from django.utils import timezone
import json import json
import requests import requests
from urllib.parse import quote
from urllib.parse import quote, urlencode
from hc.accounts.models import Profile from hc.accounts.models import Profile
from hc.lib import emails from hc.lib import emails
@ -343,6 +343,28 @@ class VictorOps(HttpTransport):
return self.post(self.channel.value, json=payload) return self.post(self.channel.value, json=payload)
class Matrix(HttpTransport):
def get_url(self):
s = quote(self.channel.value)
url = settings.MATRIX_HOMESERVER
url += "/_matrix/client/r0/rooms/%s/send/m.room.message?" % s
url += urlencode({"access_token": settings.MATRIX_ACCESS_TOKEN})
return url
def notify(self, check):
plain = tmpl("matrix_description.html", check=check)
formatted = tmpl("matrix_description_formatted.html", check=check)
payload = {
"msgtype": "m.text",
"body": plain,
"format": "org.matrix.custom.html",
"formatted_body": formatted
}
return self.post(self.get_url(), json=payload)
class Discord(HttpTransport): class Discord(HttpTransport):
def notify(self, check): def notify(self, check):
text = tmpl("slack_message.json", check=check) text = tmpl("slack_message.json", check=check)


+ 24
- 0
hc/front/forms.py View File

@ -1,11 +1,14 @@
from datetime import timedelta as td from datetime import timedelta as td
import json import json
import re import re
from urllib.parse import quote, urlencode
from django import forms from django import forms
from django.conf import settings
from django.core.validators import RegexValidator from django.core.validators import RegexValidator
from hc.front.validators import (CronExpressionValidator, TimezoneValidator, from hc.front.validators import (CronExpressionValidator, TimezoneValidator,
WebhookValidator) WebhookValidator)
import requests
class NameTagsForm(forms.Form): class NameTagsForm(forms.Form):
@ -116,3 +119,24 @@ class AddSmsForm(forms.Form):
class ChannelNameForm(forms.Form): class ChannelNameForm(forms.Form):
name = forms.CharField(max_length=100, required=False) name = forms.CharField(max_length=100, required=False)
class AddMatrixForm(forms.Form):
error_css_class = "has-error"
alias = forms.CharField(max_length=40)
def clean_alias(self):
v = self.cleaned_data["alias"]
# validate it by trying to join
url = settings.MATRIX_HOMESERVER
url += "/_matrix/client/r0/join/%s?" % quote(v)
url += urlencode({"access_token": settings.MATRIX_ACCESS_TOKEN})
doc = requests.post(url, {}).json()
if "error" in doc:
raise forms.ValidationError(
"Response from Matrix: %s" % doc["error"])
self.cleaned_data["room_id"] = doc["room_id"]
return v

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

@ -38,6 +38,7 @@ channel_urls = [
path('add_sms/', views.add_sms, name="hc-add-sms"), path('add_sms/', views.add_sms, name="hc-add-sms"),
path('add_trello/', views.add_trello, name="hc-add-trello"), path('add_trello/', views.add_trello, name="hc-add-trello"),
path('add_trello/settings/', views.trello_settings, name="hc-trello-settings"), path('add_trello/settings/', views.trello_settings, name="hc-trello-settings"),
path('add_matrix/', views.add_matrix, name="hc-add-matrix"),
path('<uuid:code>/checks/', views.channel_checks, name="hc-channel-checks"), path('<uuid:code>/checks/', views.channel_checks, name="hc-channel-checks"),
path('<uuid:code>/name/', views.update_channel_name, name="hc-channel-name"), path('<uuid:code>/name/', views.update_channel_name, name="hc-channel-name"),
path('<uuid:code>/remove/', views.remove_channel, name="hc-remove-channel"), path('<uuid:code>/remove/', views.remove_channel, name="hc-remove-channel"),


+ 34
- 2
hc/front/views.py View File

@ -1,6 +1,6 @@
from datetime import datetime, timedelta as td from datetime import datetime, timedelta as td
import json import json
from urllib.parse import urlencode
from urllib.parse import urlencode, quote
from croniter import croniter from croniter import croniter
from django.conf import settings from django.conf import settings
@ -24,7 +24,7 @@ from hc.api.transports import Telegram
from hc.front.forms import (AddWebhookForm, NameTagsForm, from hc.front.forms import (AddWebhookForm, NameTagsForm,
TimeoutForm, AddUrlForm, AddEmailForm, TimeoutForm, AddUrlForm, AddEmailForm,
AddOpsGenieForm, CronForm, AddSmsForm, AddOpsGenieForm, CronForm, AddSmsForm,
ChannelNameForm, EmailSettingsForm)
ChannelNameForm, EmailSettingsForm, AddMatrixForm)
from hc.front.schemas import telegram_callback from hc.front.schemas import telegram_callback
from hc.front.templatetags.hc_extras import (num_down_title, down_title, from hc.front.templatetags.hc_extras import (num_down_title, down_title,
sortchecks) sortchecks)
@ -550,6 +550,7 @@ def channels(request):
"enable_sms": settings.TWILIO_AUTH is not None, "enable_sms": settings.TWILIO_AUTH is not None,
"enable_pd": settings.PD_VENDOR_KEY is not None, "enable_pd": settings.PD_VENDOR_KEY is not None,
"enable_trello": settings.TRELLO_APP_KEY is not None, "enable_trello": settings.TRELLO_APP_KEY is not None,
"enable_matrix": settings.MATRIX_ACCESS_TOKEN is not None,
"use_payments": settings.USE_PAYMENTS "use_payments": settings.USE_PAYMENTS
} }
@ -1144,6 +1145,37 @@ def add_trello(request):
return render(request, "integrations/add_trello.html", ctx) return render(request, "integrations/add_trello.html", ctx)
@login_required
def add_matrix(request):
if settings.MATRIX_ACCESS_TOKEN is None:
raise Http404("matrix integration is not available")
if request.method == "POST":
form = AddMatrixForm(request.POST)
if form.is_valid():
channel = Channel(project=request.project, kind="matrix")
channel.value = form.cleaned_data["room_id"]
# If user supplied room alias instead of ID, use it as channel name
alias = form.cleaned_data["alias"]
if not alias.startswith("!"):
channel.name = alias
channel.save()
channel.assign_all_checks()
messages.success(request, "The Matrix integration has been added!")
return redirect("hc-channels")
else:
form = AddMatrixForm()
ctx = {
"page": "channels",
"form": form
}
return render(request, "integrations/add_matrix.html", ctx)
@login_required @login_required
@require_POST @require_POST
def trello_settings(request): def trello_settings(request):


+ 4
- 0
hc/settings.py View File

@ -196,6 +196,10 @@ PD_VENDOR_KEY = os.getenv("PD_VENDOR_KEY")
# Trello # Trello
TRELLO_APP_KEY = os.getenv("TRELLO_APP_KEY") TRELLO_APP_KEY = os.getenv("TRELLO_APP_KEY")
# Matrix
MATRIX_HOMESERVER = os.getenv("MATRIX_HOMESERVER")
MATRIX_ACCESS_TOKEN = os.getenv("MATRIX_ACCESS_TOKEN")
if os.path.exists(os.path.join(BASE_DIR, "hc/local_settings.py")): if os.path.exists(os.path.join(BASE_DIR, "hc/local_settings.py")):
from .local_settings import * from .local_settings import *
else: else:


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

Before After
Width: 56  |  Height: 56  |  Size: 1.0 KiB

+ 13
- 0
templates/front/channels.html View File

@ -80,6 +80,8 @@
Trello Trello
board <span>{{ ch.trello_board_list|first }}</span>, board <span>{{ ch.trello_board_list|first }}</span>,
list <span>{{ ch.trello_board_list|last }}</span> list <span>{{ ch.trello_board_list|last }}</span>
{% elif ch.kind == "matrix" %}
Matrix <span>{{ ch.value }}</span>
{% else %} {% else %}
{{ ch.kind }} {{ ch.kind }}
{% endif %} {% endif %}
@ -279,6 +281,17 @@
<a href="{% url 'hc-add-trello' %}" class="btn btn-primary">Add Integration</a> <a href="{% url 'hc-add-trello' %}" class="btn btn-primary">Add Integration</a>
</li> </li>
{% endif %} {% endif %}
{% if enable_matrix %}
<li>
<img src="{% static 'img/integrations/matrix.png' %}"
class="icon" alt="Matrix icon" />
<h2>Matrix</h2>
<p>Post notifications to a Matrix room.</p>
<a href="{% url 'hc-add-matrix' %}" class="btn btn-primary">Add Integration</a>
</li>
{% endif %}
<li class="link-to-github"> <li class="link-to-github">
<img src="{% static 'img/integrations/missing.png' %}" <img src="{% static 'img/integrations/missing.png' %}"
class="icon" alt="Suggest New Integration" /> class="icon" alt="Suggest New Integration" />


+ 48
- 0
templates/integrations/add_matrix.html View File

@ -0,0 +1,48 @@
{% extends "base.html" %}
{% load humanize static hc_extras %}
{% block title %}Add Matrix - {% site_name %}{% endblock %}
{% block content %}
<div class="row">
<div class="col-sm-12">
<h1>Matrix</h1>
<p>
If your team uses <a href="https://matrix.org/">Matrix</a>,
you can set up {% site_name %} to post notifications
to an appropriate Matrix room.
</p>
<h2>Integration Settings</h2>
<form method="post" class="form-horizontal">
{% csrf_token %}
<div class="form-group {{ form.room_id.css_classes }}">
<label for="alias" class="col-sm-2 control-label">Room Alias or ID</label>
<div class="col-sm-6">
<input
id="alias"
type="text"
class="form-control"
name="alias"
placeholder="!abc:matrix.org"
value="{{ form.alias.value|default:"" }}">
{% if form.alias.errors %}
<div class="help-block">
{{ form.alias.errors|join:"" }}
</div>
{% endif %}
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-primary">Save Integration</button>
</div>
</div>
</form>
</div>
</div>
{% endblock %}

+ 6
- 0
templates/integrations/matrix_description.html View File

@ -0,0 +1,6 @@
{% load humanize %}
{% if check.status == "down" %}
{{ check.name_then_code }} is DOWN. Last ping was {{ check.last_ping|naturaltime }}.
{% else %}
{{ check.name_then_code }} is now UP.
{% endif %}

+ 6
- 0
templates/integrations/matrix_description_formatted.html View File

@ -0,0 +1,6 @@
{% load humanize %}
{% if check.status == "down" %}
<b>{{ check.name_then_code }}</b> is <b>DOWN</b>. Last ping was {{ check.last_ping|naturaltime }}.
{% else %}
<b>{{ check.name_then_code }}</b> is now <b>UP</b>.
{% endif %}

Loading…
Cancel
Save