forked from GithubBackups/healthchecks
Alerts to SMS, work in progress.
This commit is contained in:
parent
dec006890c
commit
25fb11bb3e
@ -36,7 +36,8 @@ CHANNEL_KINDS = (("email", "Email"),
|
||||
("opsgenie", "OpsGenie"),
|
||||
("victorops", "VictorOps"),
|
||||
("discord", "Discord"),
|
||||
("telegram", "Telegram"))
|
||||
("telegram", "Telegram"),
|
||||
("sms", "SMS"))
|
||||
|
||||
PO_PRIORITIES = {
|
||||
-2: "lowest",
|
||||
@ -270,6 +271,8 @@ class Channel(models.Model):
|
||||
return transports.Discord(self)
|
||||
elif self.kind == "telegram":
|
||||
return transports.Telegram(self)
|
||||
elif self.kind == "sms":
|
||||
return transports.Sms(self)
|
||||
else:
|
||||
raise NotImplementedError("Unknown channel kind: %s" % self.kind)
|
||||
|
||||
|
@ -296,3 +296,24 @@ class Telegram(HttpTransport):
|
||||
def notify(self, check):
|
||||
text = tmpl("telegram_message.html", check=check)
|
||||
return self.send(self.channel.telegram_id, text)
|
||||
|
||||
|
||||
class Sms(HttpTransport):
|
||||
URL = 'https://api.twilio.com/2010-04-01/Accounts/%s/Messages.json'
|
||||
|
||||
def is_noop(self, check):
|
||||
return check.status != "down"
|
||||
|
||||
def notify(self, check):
|
||||
url = self.URL % settings.TWILIO_ACCOUNT
|
||||
auth = (settings.TWILIO_ACCOUNT, settings.TWILIO_AUTH)
|
||||
text = tmpl("sms_message.html", check=check,
|
||||
site_name=settings.SITE_NAME)
|
||||
|
||||
data = {
|
||||
'From': settings.TWILIO_FROM,
|
||||
'To': self.channel.value,
|
||||
'Body': text,
|
||||
}
|
||||
|
||||
return self.post(url, data=data, auth=auth)
|
||||
|
@ -1,4 +1,5 @@
|
||||
from django import forms
|
||||
from django.core.validators import RegexValidator
|
||||
from hc.front.validators import (CronExpressionValidator, TimezoneValidator,
|
||||
WebhookValidator)
|
||||
|
||||
@ -64,3 +65,12 @@ class AddWebhookForm(forms.Form):
|
||||
def get_value(self):
|
||||
d = self.cleaned_data
|
||||
return "\n".join((d["value_down"], d["value_up"], d["post_data"]))
|
||||
|
||||
|
||||
phone_validator = RegexValidator(regex='^\+\d{5,15}$',
|
||||
message="Invalid phone number format.")
|
||||
|
||||
|
||||
class AddSmsForm(forms.Form):
|
||||
error_css_class = "has-error"
|
||||
value = forms.CharField(max_length=16, validators=[phone_validator])
|
||||
|
46
hc/front/tests/test_add_sms.py
Normal file
46
hc/front/tests/test_add_sms.py
Normal file
@ -0,0 +1,46 @@
|
||||
from django.test.utils import override_settings
|
||||
from hc.api.models import Channel
|
||||
from hc.test import BaseTestCase
|
||||
|
||||
|
||||
@override_settings(TWILIO_ACCOUNT="foo", TWILIO_AUTH="foo", TWILIO_FROM="123")
|
||||
class AddSmsTestCase(BaseTestCase):
|
||||
url = "/integrations/add_sms/"
|
||||
|
||||
def test_instructions_work(self):
|
||||
self.client.login(username="alice@example.org", password="password")
|
||||
r = self.client.get(self.url)
|
||||
self.assertContains(r, "Get a SMS message")
|
||||
|
||||
def test_it_creates_channel(self):
|
||||
form = {"value": "+1234567890"}
|
||||
|
||||
self.client.login(username="alice@example.org", password="password")
|
||||
r = self.client.post(self.url, form)
|
||||
self.assertRedirects(r, "/integrations/")
|
||||
|
||||
c = Channel.objects.get()
|
||||
self.assertEqual(c.kind, "sms")
|
||||
self.assertEqual(c.value, "+1234567890")
|
||||
|
||||
def test_it_rejects_bad_number(self):
|
||||
form = {"value": "not a phone number address"}
|
||||
|
||||
self.client.login(username="alice@example.org", password="password")
|
||||
r = self.client.post(self.url, form)
|
||||
self.assertContains(r, "Invalid phone number format.")
|
||||
|
||||
def test_it_trims_whitespace(self):
|
||||
form = {"value": " +1234567890 "}
|
||||
|
||||
self.client.login(username="alice@example.org", password="password")
|
||||
self.client.post(self.url, form)
|
||||
|
||||
c = Channel.objects.get()
|
||||
self.assertEqual(c.value, "+1234567890")
|
||||
|
||||
@override_settings(TWILIO_AUTH=None)
|
||||
def test_it_requires_credentials(self):
|
||||
self.client.login(username="alice@example.org", password="password")
|
||||
r = self.client.get("/integrations/add_sms/")
|
||||
self.assertEqual(r.status_code, 404)
|
@ -26,6 +26,7 @@ channel_urls = [
|
||||
url(r'^add_victorops/$', views.add_victorops, name="hc-add-victorops"),
|
||||
url(r'^telegram/bot/$', views.telegram_bot),
|
||||
url(r'^add_telegram/$', views.add_telegram, name="hc-add-telegram"),
|
||||
url(r'^add_sms/$', views.add_sms, name="hc-add-sms"),
|
||||
url(r'^([\w-]+)/checks/$', views.channel_checks, name="hc-channel-checks"),
|
||||
url(r'^([\w-]+)/remove/$', views.remove_channel, name="hc-remove-channel"),
|
||||
url(r'^([\w-]+)/verify/([\w-]+)/$', views.verify_email,
|
||||
|
@ -25,7 +25,7 @@ from hc.api.models import (DEFAULT_GRACE, DEFAULT_TIMEOUT, Channel, Check,
|
||||
from hc.api.transports import Telegram
|
||||
from hc.front.forms import (AddWebhookForm, NameTagsForm,
|
||||
TimeoutForm, AddUrlForm, AddPdForm, AddEmailForm,
|
||||
AddOpsGenieForm, CronForm)
|
||||
AddOpsGenieForm, CronForm, AddSmsForm)
|
||||
from hc.front.schemas import telegram_callback
|
||||
from hc.lib import jsonschema
|
||||
from pytz import all_timezones
|
||||
@ -106,6 +106,7 @@ def index(request):
|
||||
"enable_pushover": settings.PUSHOVER_API_TOKEN is not None,
|
||||
"enable_discord": settings.DISCORD_CLIENT_ID is not None,
|
||||
"enable_telegram": settings.TELEGRAM_TOKEN is not None,
|
||||
"enable_sms": settings.TWILIO_AUTH is not None,
|
||||
"registration_open": settings.REGISTRATION_OPEN
|
||||
}
|
||||
|
||||
@ -350,7 +351,8 @@ def channels(request):
|
||||
"enable_pushbullet": settings.PUSHBULLET_CLIENT_ID is not None,
|
||||
"enable_pushover": settings.PUSHOVER_API_TOKEN is not None,
|
||||
"enable_discord": settings.DISCORD_CLIENT_ID is not None,
|
||||
"enable_telegram": settings.TELEGRAM_TOKEN is not None
|
||||
"enable_telegram": settings.TELEGRAM_TOKEN is not None,
|
||||
"enable_sms": settings.TWILIO_AUTH is not None
|
||||
}
|
||||
return render(request, "front/channels.html", ctx)
|
||||
|
||||
@ -811,6 +813,27 @@ def add_telegram(request):
|
||||
return render(request, "integrations/add_telegram.html", ctx)
|
||||
|
||||
|
||||
@login_required
|
||||
def add_sms(request):
|
||||
if settings.TWILIO_AUTH is None:
|
||||
raise Http404("sms integration is not available")
|
||||
|
||||
if request.method == "POST":
|
||||
form = AddSmsForm(request.POST)
|
||||
if form.is_valid():
|
||||
channel = Channel(user=request.team.user, kind="sms")
|
||||
channel.value = form.cleaned_data["value"]
|
||||
channel.save()
|
||||
|
||||
channel.assign_all_checks()
|
||||
return redirect("hc-channels")
|
||||
else:
|
||||
form = AddSmsForm()
|
||||
|
||||
ctx = {"page": "channels", "form": form}
|
||||
return render(request, "integrations/add_sms.html", ctx)
|
||||
|
||||
|
||||
def privacy(request):
|
||||
return render(request, "front/privacy.html", {})
|
||||
|
||||
|
@ -157,6 +157,11 @@ PUSHBULLET_CLIENT_SECRET = None
|
||||
TELEGRAM_BOT_NAME = "ExampleBot"
|
||||
TELEGRAM_TOKEN = None
|
||||
|
||||
# SMS (Twilio) integration -- override in local_settings.py
|
||||
TWILIO_ACCOUNT = None
|
||||
TWILIO_AUTH = None
|
||||
TWILIO_FROM = None
|
||||
|
||||
if os.path.exists(os.path.join(BASE_DIR, "hc/local_settings.py")):
|
||||
from .local_settings import *
|
||||
else:
|
||||
|
BIN
static/img/integrations/sms.png
Normal file
BIN
static/img/integrations/sms.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.9 KiB |
@ -152,6 +152,17 @@
|
||||
|
||||
<a href="{% url 'hc-add-email' %}" class="btn btn-primary">Add Integration</a>
|
||||
</li>
|
||||
{% if enable_sms %}
|
||||
<li>
|
||||
<img src="{% static 'img/integrations/sms.png' %}"
|
||||
class="icon" alt="SMS icon" />
|
||||
|
||||
<h2>SMS</h2>
|
||||
<p>Get a text message to your phone when check goes down.</p>
|
||||
|
||||
<a href="{% url 'hc-add-sms' %}" class="btn btn-primary">Add Integration</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
<li>
|
||||
<img src="{% static 'img/integrations/webhook.png' %}"
|
||||
class="icon" alt="Webhook icon" />
|
||||
|
@ -236,6 +236,15 @@
|
||||
<td>Good old email messages.</td>
|
||||
</tr>
|
||||
|
||||
{% if enable_sms %}
|
||||
<tr>
|
||||
<td>
|
||||
<img width="22" height="22" alt="Email icon" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACwAAAAsCAYAAAAehFoBAAAFHElEQVRYw+2Ya2xURRSAv5m72+32wbbYgpSUElqUCPgAohJgoSgQpZHERIlK4gNisEL4IyEm/uCHYoIxhoeERKLiO8QEJBhJoVBKmhAoUihQ5WlTlmK3pbuw7733jj/AS7fbUkqV7iacP3cy99yTb86cOefcgfvy/4roafKz/bPyHDbb0MEEc4iwd/H0uuu9An+1f1ZmWNpXKNTbwJgUcegfCD73Gvrm1eU1ugW8/tBzQ7RofDcwNUUjoSrL1Be8WV4TkQBaLL4lhWEB5oak7VMAsf7gvMc002hIg/NmKN0sldIw56dJgtCkTc6TQqjidElpCkZJoZQtfZKwsN8RbLajgPEjKsjPKsah5RDVA1wN/cWxlm3oZhR32XKc9jwADjd/TWeoxfo2KyOfGaXLbnpIUdX0IQAZtmzGj6igILsUp91FzAjhD3s44dlOMNbRK0ufwEWuR6mYuAabdCTMl+Km6cpuAlEvJUOfIjdzOAD+iIdDF7+8pVcwk9JCt7WpVU3gchbx4uPrrEV2lUu+Y7cFln0BTypemABrKv22+mMLZycU0IeGzU7SmVC0IAG2L5v98vDQ7NHW+JcT7+HxHceuOXlwyCNE9WCSfm7mcEa4xtPqP4nLWcTwIeOSbWaVWOMDZ9dxqnUXmsygMKeMQNR7W54+PSzFrTWFYp0AxI0wLZ1HiRuhXrxc3uUperCpJdk0zBhXrp0eOPD1yN/WeM649ynIKetVNxK/BkBZ4UyksDH2ZjhE4v5Em9E2azyjrJJR+VPuOCT6BG649PONDAgU5JTx0qRNzH54JVkZyc2cP+whqgfItLuYVLyQ/KxRmMrAGziXoNfo2YFhxgHIcQyjYuLHzJ/wEflZxQMHPt9eS1XTGmvrBIJxw+fyypQtSd522HO50H4QgCkli26c+s6jCWEF4A2cZWfjKvxhjzVXMvRJXp60mdEPTB0YMMA5bw3fH3mdI83fopvRG3C2XJ4e/VaCnl1zcqZtX0Lsn2nbh11zJtls9TfyY/0Sas9tsEJGkxnMKH134MD/HrQjzd/w68kPrLn8rFGJxV7Yuew7bh2cuBHhYkcdmrT3aNNUOicv72Tb75WYyrCyTPec3++05g97rJgLx31dKqXood4rqv9ci8s5klCsg7gRSdLJcxYTjHVYWSZmBDGVnpA97hp42piljHBNIBBtQzdiCQejawbpKh5fAx5f7x3rxKIXGF9UwfVIGzEjiMs50vJqOO6zwu6ugAFs0kGes7jbdhocbfnprvsYKWy4nEVJ8/XN3w3Mwxc76rDJDLIdBdikg2Csg/bAeRo922kPXgCgI3SRiH6NWA+VD6Az1IypdJRSN/uFBvKzS8h1DMOuOQnH/XSGmjnVuguP7/jtG7ZNNc98oYRYkibt5VqppFBp08ArpaRSXE2jS5R2KZSsSxdgKcxa6VWx34AzqfgD1032v+Ped1iuLq/RTVO8BgRS+BLtrKmJRVZpXl6+p96Q2nSgPsWcHFOKDaGIfGL59D2Xk9ahFGJjzZzJUpqTBTLvZql1A893360uHzYJxNb/GtaU5qlwSNu/cl5VsM/by66ysfbZlUKxtpfXHqWb05Y9s6/5Xnl9IHcSHZpQc5feQ9h+tZfdJGwKsWCpu/r0vY7ru/GwIYR6dbl776Dk7/56WAnU4kp39Y5BKx79pF1VObN6K4Mo/QHeuMy995NBL893mMB/8Lqnr0iJfqIvBQ3zgD27843VYrXJfbkvqSf/AF8f89OScY4/AAAAAElFTkSuQmCC" />
|
||||
</td>
|
||||
<td>SMS text messages.</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
<img width="22" height="22" alt="Webhook icon" src="data:image/gif;base64,R0lGODdhLAAsAOMQAEBCQEpMS8s8aF5gXmxubtRkhoWIhp2gnuGRqLi6uOm3yfHJ1tja2PHZ4+/s6/3//CwAAAAALAAsAAAE/vDJSau9OOvNu/+gtyBFWSBLGDqF4L5u0aidAt8vQmv23eK6ncUBKygoi5/LIawgXgXM0xVsPogxCcMQCBgYEuXM2hMcHQGAGhAAl6tC8cOwXg+uUOtDKRnU10x5VkpMfn8AgVlWUwIzdH93e4pNbw8MaXUHSGNWMDMMBJhsehdljRMMawakFkoCCGMEa2CsE1gvKVeqtRQNghIHs1YJtBOEFA6GAwwHBs4HxRsJaZoTlRXBh3bRF48ABBR8F4baaqsY5AADTA+MnBWpa12i5t2ZtL5UGnQEDAwODrZkqoBmTQJbcjIwu5CNDTtgBiUw0sfBgYEBAwgcfNCwmgRMtuAk3fCQDkA1WaMkOFijaQEOMxwzEnBG0xnKP0wasosH4KCpPAnKCe35IKiajTx9voxidOihg003rlSjKd+NFI8CHNjKtdmhNhyFfVQTaeIrXWpCWih5cg2FjhKSlDgyhyUGi2wGbGzq8YqovhMaBrjgj7C8hxDXaGTiIMFNANwspfmCiotdC96GnqPAU02AAfTUZWhYDrDKzNo2332sGLGFaeU2cmD2LIFrhTadyebFu7dvGhEAADs=" />
|
||||
|
@ -28,10 +28,3 @@
|
||||
</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 %}
|
||||
|
@ -52,13 +52,4 @@
|
||||
</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 %}
|
||||
|
@ -92,13 +92,4 @@
|
||||
</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 %}
|
||||
|
@ -90,13 +90,4 @@
|
||||
</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 %}
|
||||
|
@ -91,13 +91,4 @@
|
||||
</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 %}
|
||||
|
44
templates/integrations/add_sms.html
Normal file
44
templates/integrations/add_sms.html
Normal file
@ -0,0 +1,44 @@
|
||||
{% extends "base.html" %}
|
||||
{% load compress humanize staticfiles hc_extras %}
|
||||
|
||||
{% block title %}Notification Channels - {% site_name %}{% endblock %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<h1>SMS</h1>
|
||||
|
||||
<p>Get a SMS message to your specified number when check goes down.</p>
|
||||
|
||||
<h2>Integration Settings</h2>
|
||||
|
||||
<form method="post" class="form-horizontal" action="{% url 'hc-add-sms' %}">
|
||||
{% csrf_token %}
|
||||
<div class="form-group {{ form.value.css_classes }}">
|
||||
<label for="id_number" class="col-sm-2 control-label">Phone Number</label>
|
||||
<div class="col-sm-3">
|
||||
<input
|
||||
id="id_number"
|
||||
type="tel"
|
||||
class="form-control"
|
||||
name="value"
|
||||
placeholder="+1234567890"
|
||||
value="{{ form.value.value|default:"" }}">
|
||||
|
||||
{% if form.value.errors %}
|
||||
<div class="help-block">
|
||||
{{ form.value.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 %}
|
@ -89,16 +89,7 @@
|
||||
src="{% static 'img/integrations/setup_telegram_3.png' %}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{% endif %}
|
||||
</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 %}
|
||||
|
@ -105,13 +105,4 @@
|
||||
</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 %}
|
||||
|
@ -113,13 +113,4 @@
|
||||
</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 %}
|
||||
|
1
templates/integrations/sms_message.html
Normal file
1
templates/integrations/sms_message.html
Normal file
@ -0,0 +1 @@
|
||||
{% load humanize %}{{ site_name }}: The check "{{ check.name_then_code }}" is DOWN. Last ping was {{ check.last_ping|naturaltime }}.
|
Loading…
x
Reference in New Issue
Block a user