Browse Source

Alerts to SMS, work in progress.

pull/133/head
Pēteris Caune 7 years ago
parent
commit
25fb11bb3e
20 changed files with 177 additions and 73 deletions
  1. +4
    -1
      hc/api/models.py
  2. +21
    -0
      hc/api/transports.py
  3. +10
    -0
      hc/front/forms.py
  4. +46
    -0
      hc/front/tests/test_add_sms.py
  5. +1
    -0
      hc/front/urls.py
  6. +25
    -2
      hc/front/views.py
  7. +5
    -0
      hc/settings.py
  8. BIN
      static/img/integrations/sms.png
  9. +11
    -0
      templates/front/channels.html
  10. +9
    -0
      templates/front/welcome.html
  11. +0
    -7
      templates/integrations/add_discord.html
  12. +0
    -9
      templates/integrations/add_email.html
  13. +0
    -9
      templates/integrations/add_hipchat.html
  14. +0
    -9
      templates/integrations/add_opsgenie.html
  15. +0
    -9
      templates/integrations/add_pd.html
  16. +44
    -0
      templates/integrations/add_sms.html
  17. +0
    -9
      templates/integrations/add_telegram.html
  18. +0
    -9
      templates/integrations/add_victorops.html
  19. +0
    -9
      templates/integrations/add_webhook.html
  20. +1
    -0
      templates/integrations/sms_message.html

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

@ -36,7 +36,8 @@ CHANNEL_KINDS = (("email", "Email"),
("opsgenie", "OpsGenie"), ("opsgenie", "OpsGenie"),
("victorops", "VictorOps"), ("victorops", "VictorOps"),
("discord", "Discord"), ("discord", "Discord"),
("telegram", "Telegram"))
("telegram", "Telegram"),
("sms", "SMS"))
PO_PRIORITIES = { PO_PRIORITIES = {
-2: "lowest", -2: "lowest",
@ -270,6 +271,8 @@ class Channel(models.Model):
return transports.Discord(self) return transports.Discord(self)
elif self.kind == "telegram": elif self.kind == "telegram":
return transports.Telegram(self) return transports.Telegram(self)
elif self.kind == "sms":
return transports.Sms(self)
else: else:
raise NotImplementedError("Unknown channel kind: %s" % self.kind) raise NotImplementedError("Unknown channel kind: %s" % self.kind)


+ 21
- 0
hc/api/transports.py View File

@ -296,3 +296,24 @@ class Telegram(HttpTransport):
def notify(self, check): def notify(self, check):
text = tmpl("telegram_message.html", check=check) text = tmpl("telegram_message.html", check=check)
return self.send(self.channel.telegram_id, text) 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)

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

@ -1,4 +1,5 @@
from django import forms from django import forms
from django.core.validators import RegexValidator
from hc.front.validators import (CronExpressionValidator, TimezoneValidator, from hc.front.validators import (CronExpressionValidator, TimezoneValidator,
WebhookValidator) WebhookValidator)
@ -64,3 +65,12 @@ class AddWebhookForm(forms.Form):
def get_value(self): def get_value(self):
d = self.cleaned_data d = self.cleaned_data
return "\n".join((d["value_down"], d["value_up"], d["post_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
- 0
hc/front/tests/test_add_sms.py View 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="[email protected]", 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="[email protected]", 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="[email protected]", 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="[email protected]", 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="[email protected]", password="password")
r = self.client.get("/integrations/add_sms/")
self.assertEqual(r.status_code, 404)

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

@ -26,6 +26,7 @@ channel_urls = [
url(r'^add_victorops/$', views.add_victorops, name="hc-add-victorops"), url(r'^add_victorops/$', views.add_victorops, name="hc-add-victorops"),
url(r'^telegram/bot/$', views.telegram_bot), url(r'^telegram/bot/$', views.telegram_bot),
url(r'^add_telegram/$', views.add_telegram, name="hc-add-telegram"), 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-]+)/checks/$', views.channel_checks, name="hc-channel-checks"),
url(r'^([\w-]+)/remove/$', views.remove_channel, name="hc-remove-channel"), url(r'^([\w-]+)/remove/$', views.remove_channel, name="hc-remove-channel"),
url(r'^([\w-]+)/verify/([\w-]+)/$', views.verify_email, url(r'^([\w-]+)/verify/([\w-]+)/$', views.verify_email,


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

@ -25,7 +25,7 @@ from hc.api.models import (DEFAULT_GRACE, DEFAULT_TIMEOUT, Channel, Check,
from hc.api.transports import Telegram from hc.api.transports import Telegram
from hc.front.forms import (AddWebhookForm, NameTagsForm, from hc.front.forms import (AddWebhookForm, NameTagsForm,
TimeoutForm, AddUrlForm, AddPdForm, AddEmailForm, TimeoutForm, AddUrlForm, AddPdForm, AddEmailForm,
AddOpsGenieForm, CronForm)
AddOpsGenieForm, CronForm, AddSmsForm)
from hc.front.schemas import telegram_callback from hc.front.schemas import telegram_callback
from hc.lib import jsonschema from hc.lib import jsonschema
from pytz import all_timezones from pytz import all_timezones
@ -106,6 +106,7 @@ def index(request):
"enable_pushover": settings.PUSHOVER_API_TOKEN is not None, "enable_pushover": settings.PUSHOVER_API_TOKEN is not None,
"enable_discord": settings.DISCORD_CLIENT_ID 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,
"registration_open": settings.REGISTRATION_OPEN "registration_open": settings.REGISTRATION_OPEN
} }
@ -350,7 +351,8 @@ def channels(request):
"enable_pushbullet": settings.PUSHBULLET_CLIENT_ID is not None, "enable_pushbullet": settings.PUSHBULLET_CLIENT_ID is not None,
"enable_pushover": settings.PUSHOVER_API_TOKEN is not None, "enable_pushover": settings.PUSHOVER_API_TOKEN is not None,
"enable_discord": settings.DISCORD_CLIENT_ID 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) return render(request, "front/channels.html", ctx)
@ -811,6 +813,27 @@ def add_telegram(request):
return render(request, "integrations/add_telegram.html", ctx) 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): def privacy(request):
return render(request, "front/privacy.html", {}) return render(request, "front/privacy.html", {})


+ 5
- 0
hc/settings.py View File

@ -157,6 +157,11 @@ PUSHBULLET_CLIENT_SECRET = None
TELEGRAM_BOT_NAME = "ExampleBot" TELEGRAM_BOT_NAME = "ExampleBot"
TELEGRAM_TOKEN = None 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")): 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/sms.png View File

Before After
Width: 128  |  Height: 128  |  Size: 3.9 KiB

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

@ -152,6 +152,17 @@
<a href="{% url 'hc-add-email' %}" class="btn btn-primary">Add Integration</a> <a href="{% url 'hc-add-email' %}" class="btn btn-primary">Add Integration</a>
</li> </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> <li>
<img src="{% static 'img/integrations/webhook.png' %}" <img src="{% static 'img/integrations/webhook.png' %}"
class="icon" alt="Webhook icon" /> class="icon" alt="Webhook icon" />


+ 9
- 0
templates/front/welcome.html View File

@ -236,6 +236,15 @@
<td>Good old email messages.</td> <td>Good old email messages.</td>
</tr> </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> <tr>
<td> <td>
<img width="22" height="22" alt="Webhook icon" src="data:image/gif;base64,R0lGODdhLAAsAOMQAEBCQEpMS8s8aF5gXmxubtRkhoWIhp2gnuGRqLi6uOm3yfHJ1tja2PHZ4+/s6/3//CwAAAAALAAsAAAE/vDJSau9OOvNu/+gtyBFWSBLGDqF4L5u0aidAt8vQmv23eK6ncUBKygoi5/LIawgXgXM0xVsPogxCcMQCBgYEuXM2hMcHQGAGhAAl6tC8cOwXg+uUOtDKRnU10x5VkpMfn8AgVlWUwIzdH93e4pNbw8MaXUHSGNWMDMMBJhsehdljRMMawakFkoCCGMEa2CsE1gvKVeqtRQNghIHs1YJtBOEFA6GAwwHBs4HxRsJaZoTlRXBh3bRF48ABBR8F4baaqsY5AADTA+MnBWpa12i5t2ZtL5UGnQEDAwODrZkqoBmTQJbcjIwu5CNDTtgBiUw0sfBgYEBAwgcfNCwmgRMtuAk3fCQDkA1WaMkOFijaQEOMxwzEnBG0xnKP0wasosH4KCpPAnKCe35IKiajTx9voxidOihg003rlSjKd+NFI8CHNjKtdmhNhyFfVQTaeIrXWpCWih5cg2FjhKSlDgyhyUGi2wGbGzq8YqovhMaBrjgj7C8hxDXaGTiIMFNANwspfmCiotdC96GnqPAU02AAfTUZWhYDrDKzNo2332sGLGFaeU2cmD2LIFrhTadyebFu7dvGhEAADs=" /> <img width="22" height="22" alt="Webhook icon" src="data:image/gif;base64,R0lGODdhLAAsAOMQAEBCQEpMS8s8aF5gXmxubtRkhoWIhp2gnuGRqLi6uOm3yfHJ1tja2PHZ4+/s6/3//CwAAAAALAAsAAAE/vDJSau9OOvNu/+gtyBFWSBLGDqF4L5u0aidAt8vQmv23eK6ncUBKygoi5/LIawgXgXM0xVsPogxCcMQCBgYEuXM2hMcHQGAGhAAl6tC8cOwXg+uUOtDKRnU10x5VkpMfn8AgVlWUwIzdH93e4pNbw8MaXUHSGNWMDMMBJhsehdljRMMawakFkoCCGMEa2CsE1gvKVeqtRQNghIHs1YJtBOEFA6GAwwHBs4HxRsJaZoTlRXBh3bRF48ABBR8F4baaqsY5AADTA+MnBWpa12i5t2ZtL5UGnQEDAwODrZkqoBmTQJbcjIwu5CNDTtgBiUw0sfBgYEBAwgcfNCwmgRMtuAk3fCQDkA1WaMkOFijaQEOMxwzEnBG0xnKP0wasosH4KCpPAnKCe35IKiajTx9voxidOihg003rlSjKd+NFI8CHNjKtdmhNhyFfVQTaeIrXWpCWih5cg2FjhKSlDgyhyUGi2wGbGzq8YqovhMaBrjgj7C8hxDXaGTiIMFNANwspfmCiotdC96GnqPAU02AAfTUZWhYDrDKzNo2332sGLGFaeU2cmD2LIFrhTadyebFu7dvGhEAADs=" />


+ 0
- 7
templates/integrations/add_discord.html View File

@ -28,10 +28,3 @@
</div> </div>
</div> </div>
{% endblock %} {% 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 %}

+ 0
- 9
templates/integrations/add_email.html View File

@ -52,13 +52,4 @@
</form> </form>
</div> </div>
</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 %} {% endblock %}

+ 0
- 9
templates/integrations/add_hipchat.html View File

@ -92,13 +92,4 @@
</form> </form>
</div> </div>
</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 %} {% endblock %}

+ 0
- 9
templates/integrations/add_opsgenie.html View File

@ -90,13 +90,4 @@
</form> </form>
</div> </div>
</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 %} {% endblock %}

+ 0
- 9
templates/integrations/add_pd.html View File

@ -91,13 +91,4 @@
</form> </form>
</div> </div>
</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 %} {% endblock %}

+ 44
- 0
templates/integrations/add_sms.html View 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 %}

+ 0
- 9
templates/integrations/add_telegram.html View File

@ -89,16 +89,7 @@
src="{% static 'img/integrations/setup_telegram_3.png' %}"> src="{% static 'img/integrations/setup_telegram_3.png' %}">
</div> </div>
</div> </div>
{% endif %} {% endif %}
</div> </div>
</div> </div>
{% endblock %} {% 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 %}

+ 0
- 9
templates/integrations/add_victorops.html View File

@ -105,13 +105,4 @@
</form> </form>
</div> </div>
</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 %} {% endblock %}

+ 0
- 9
templates/integrations/add_webhook.html View File

@ -113,13 +113,4 @@
</form> </form>
</div> </div>
</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 %} {% endblock %}

+ 1
- 0
templates/integrations/sms_message.html View File

@ -0,0 +1 @@
{% load humanize %}{{ site_name }}: The check "{{ check.name_then_code }}" is DOWN. Last ping was {{ check.last_ping|naturaltime }}.

Loading…
Cancel
Save