Browse Source

Add Signal integration

Fixes: #428
pull/468/head
Pēteris Caune 4 years ago
parent
commit
cd99af14ba
No known key found for this signature in database GPG Key ID: E28D7679E9A9EDE2
21 changed files with 410 additions and 7 deletions
  1. +1
    -0
      CHANGELOG.md
  2. +35
    -0
      README.md
  3. +16
    -1
      hc/api/models.py
  4. +1
    -1
      hc/api/tests/test_notify_email.py
  5. +82
    -0
      hc/api/tests/test_notify_signal.py
  6. +25
    -0
      hc/api/transports.py
  7. +60
    -0
      hc/front/tests/test_add_signal.py
  8. +1
    -0
      hc/front/urls.py
  9. +34
    -0
      hc/front/views.py
  10. +4
    -0
      hc/settings.py
  11. +9
    -5
      static/css/icomoon.css
  12. BIN
      static/fonts/icomoon.eot
  13. +1
    -0
      static/fonts/icomoon.svg
  14. BIN
      static/fonts/icomoon.ttf
  15. BIN
      static/fonts/icomoon.woff
  16. BIN
      static/img/integrations/signal.png
  17. +19
    -0
      templates/front/channels.html
  18. +5
    -0
      templates/front/event_summary.html
  19. +12
    -0
      templates/front/welcome.html
  20. +98
    -0
      templates/integrations/add_signal.html
  21. +7
    -0
      templates/integrations/signal_message.html

+ 1
- 0
CHANGELOG.md View File

@ -8,6 +8,7 @@ All notable changes to this project will be documented in this file.
- Update OpsGenie instructions (#450)
- Update the email notification template to include more check and last ping details
- Improve the crontab snippet in the "Check Details" page (#465)
- Add Signal integration (#428)
## v1.18.0 - 2020-12-09


+ 35
- 0
README.md View File

@ -136,6 +136,8 @@ Healthchecks reads configuration from the following environment variables:
| PUSHOVER_SUBSCRIPTION_URL | `None`
| REMOTE_USER_HEADER | `None` | See [External Authentication](#external-authentication) for details.
| SHELL_ENABLED | `"False"`
| SIGNAL_CLI_USERNAME | `None`
| SIGNAL_CLI_CMD | `signal-cli` | Path to the signal-cli executable
| SLACK_CLIENT_ID | `None`
| SLACK_CLIENT_SECRET | `None`
| TELEGRAM_BOT_NAME | `"ExampleBot"`
@ -407,6 +409,39 @@ To enable the Pushover integration, you will need to:
variables. The Pushover subscription URL should look similar to
`https://pushover.net/subscribe/yourAppName-randomAlphaNumericData`.
### Signal
Healthchecks uses [signal-cli](https://github.com/AsamK/signal-cli) to send Signal
notifications. It requires the `signal-cli` program to be installed and available on
the local machine.
To send notifications, healthchecks executes "signal-cli send" calls.
It does not handle phone number registration and verification. You must do that
manually, before using the integration.
To enable the Signal integration:
* Download and install signal-cli in your preferred location
(for example, in `/srv/signal-cli-0.7.2/`).
* Register and verify phone number, or [link it](https://github.com/AsamK/signal-cli/wiki/Linking-other-devices-(Provisioning))
to an existing registration.
* Test your signal-cli configuration by sending a message manually from command line.
* Put the sender phone number in the `SIGNAL_CLI_USERNAME` environment variable.
Example: `SIGNAL_CLI_USERNAME=+123456789`.
* If `signal-cli` is not in the system path, specify its path in `SIGNAL_CLI_CMD`.
Example: `SIGNAL_CLI_CMD=/srv/signal-cli-0.7.2/bin/signal-cli`
It is possible to use a separate system user for running signal-cli:
* Create a separate system user, (for example, "signal-user").
* Configure signal-cli while logged in as signal-user.
* Change `SIGNAL_CLI_CMD` to run signal-cli through sudo:
`sudo -u signal-user /srv/signal-cli-0.7.2/bin/signal-cli`.
* Configure sudo to not require password. For example, if healthchecks
runs under the www-data system user, the sudoers rule would be:
`www-data ALL=(signal-user) NOPASSWD: /srv/signal-cli-0.7.2/bin/signal-cli`.
### Telegram
* Create a Telegram bot by talking to the


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

@ -52,6 +52,7 @@ CHANNEL_KINDS = (
("spike", "Spike"),
("call", "Phone Call"),
("linenotify", "LINE Notify"),
("signal", "Signal"),
)
PO_PRIORITIES = {-2: "lowest", -1: "low", 0: "normal", 1: "high", 2: "emergency"}
@ -471,6 +472,8 @@ class Channel(models.Model):
return transports.Call(self)
elif self.kind == "linenotify":
return transports.LineNotify(self)
elif self.kind == "signal":
return transports.Signal(self)
else:
raise NotImplementedError("Unknown channel kind: %s" % self.kind)
@ -649,7 +652,7 @@ class Channel(models.Model):
@property
def phone_number(self):
assert self.kind in ("call", "sms", "whatsapp")
assert self.kind in ("call", "sms", "whatsapp", "signal")
if self.value.startswith("{"):
doc = json.loads(self.value)
return doc["value"]
@ -714,6 +717,18 @@ class Channel(models.Model):
doc = json.loads(self.value)
return doc["down"]
@property
def signal_notify_up(self):
assert self.kind == "signal"
doc = json.loads(self.value)
return doc["up"]
@property
def signal_notify_down(self):
assert self.kind == "signal"
doc = json.loads(self.value)
return doc["down"]
@property
def opsgenie_key(self):
assert self.kind == "opsgenie"


+ 1
- 1
hc/api/tests/test_notify_email.py View File

@ -9,7 +9,7 @@ from hc.api.models import Channel, Check, Notification, Ping
from hc.test import BaseTestCase
class NotifyTestCase(BaseTestCase):
class NotifyEmailTestCase(BaseTestCase):
def setUp(self):
super().setUp()


+ 82
- 0
hc/api/tests/test_notify_signal.py View File

@ -0,0 +1,82 @@
# coding: utf-8
from datetime import timedelta as td
import json
from unittest.mock import patch
from django.utils.timezone import now
from django.test.utils import override_settings
from hc.api.models import Channel, Check, Notification
from hc.test import BaseTestCase
@override_settings(SIGNAL_CLI_USERNAME="+987654321")
class NotifySignalTestCase(BaseTestCase):
def setUp(self):
super().setUp()
self.check = Check(project=self.project)
self.check.name = "Daily Backup"
self.check.status = "down"
self.check.last_ping = now() - td(minutes=61)
self.check.save()
payload = {"value": "+123456789", "up": True, "down": True}
self.channel = Channel(project=self.project)
self.channel.kind = "signal"
self.channel.value = json.dumps(payload)
self.channel.save()
self.channel.checks.add(self.check)
@patch("hc.api.transports.subprocess.run")
def test_it_works(self, mock_run):
mock_run.return_value.returncode = 0
self.channel.notify(self.check)
n = Notification.objects.get()
self.assertEqual(n.error, "")
self.assertTrue(mock_run.called)
args, kwargs = mock_run.call_args
cmd = " ".join(args[0])
self.assertIn("-u +987654321", cmd)
self.assertIn("send +123456789", cmd)
@patch("hc.api.transports.subprocess.run")
def test_it_obeys_down_flag(self, mock_run):
payload = {"value": "+123456789", "up": True, "down": False}
self.channel.value = json.dumps(payload)
self.channel.save()
self.channel.notify(self.check)
# This channel should not notify on "down" events:
self.assertEqual(Notification.objects.count(), 0)
self.assertFalse(mock_run.called)
@patch("hc.api.transports.subprocess.run")
def test_it_requires_signal_cli_username(self, mock_run):
with override_settings(SIGNAL_CLI_USERNAME=None):
self.channel.notify(self.check)
n = Notification.objects.get()
self.assertEqual(n.error, "Signal notifications are not enabled")
self.assertFalse(mock_run.called)
@patch("hc.api.transports.subprocess.run")
def test_it_does_not_escape_special_characters(self, mock_run):
self.check.name = "Foo & Bar"
self.check.save()
mock_run.return_value.returncode = 0
self.channel.notify(self.check)
self.assertTrue(mock_run.called)
args, kwargs = mock_run.call_args
cmd = " ".join(args[0])
self.assertIn("Foo & Bar", cmd)

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

@ -6,6 +6,7 @@ from django.utils import timezone
from django.utils.html import escape
import json
import requests
import subprocess
from urllib.parse import quote, urlencode
from hc.accounts.models import Profile
@ -659,3 +660,27 @@ class LineNotify(HttpTransport):
}
payload = {"message": tmpl("linenotify_message.html", check=check)}
return self.post(self.URL, headers=headers, params=payload)
class Signal(Transport):
def is_noop(self, check):
if check.status == "down":
return not self.channel.signal_notify_down
else:
return not self.channel.signal_notify_up
def notify(self, check):
if not settings.SIGNAL_CLI_USERNAME:
return "Signal notifications are not enabled"
text = tmpl("signal_message.html", check=check, site_name=settings.SITE_NAME)
args = settings.SIGNAL_CLI_CMD.split()
args.extend(["-u", settings.SIGNAL_CLI_USERNAME])
args.extend(["send", self.channel.phone_number])
args.extend(["-m", text])
result = subprocess.run(args, timeout=10)
if result.returncode != 0:
return "signal-cli returned exit code %d" % result.returncode

+ 60
- 0
hc/front/tests/test_add_signal.py View File

@ -0,0 +1,60 @@
from django.test.utils import override_settings
from hc.api.models import Channel
from hc.test import BaseTestCase
@override_settings(SIGNAL_CLI_USERNAME="+123456789")
class AddSignalTestCase(BaseTestCase):
def setUp(self):
super().setUp()
self.url = "/projects/%s/add_signal/" % self.project.code
def test_instructions_work(self):
self.client.login(username="[email protected]", password="password")
r = self.client.get(self.url)
self.assertContains(r, "Get a Signal message")
def test_it_creates_channel(self):
form = {
"label": "My Phone",
"value": "+1234567890",
"down": "true",
"up": "true",
}
self.client.login(username="[email protected]", password="password")
r = self.client.post(self.url, form)
self.assertRedirects(r, self.channels_url)
c = Channel.objects.get()
self.assertEqual(c.kind, "signal")
self.assertEqual(c.phone_number, "+1234567890")
self.assertEqual(c.name, "My Phone")
self.assertTrue(c.signal_notify_down)
self.assertTrue(c.signal_notify_up)
self.assertEqual(c.project, self.project)
def test_it_obeys_up_down_flags(self):
form = {"label": "My Phone", "value": "+1234567890"}
self.client.login(username="[email protected]", password="password")
r = self.client.post(self.url, form)
self.assertRedirects(r, self.channels_url)
c = Channel.objects.get()
self.assertFalse(c.signal_notify_down)
self.assertFalse(c.signal_notify_up)
@override_settings(SIGNAL_CLI_USERNAME=None)
def test_it_handles_unset_username(self):
self.client.login(username="[email protected]", password="password")
r = self.client.get(self.url)
self.assertEqual(r.status_code, 404)
def test_it_requires_rw_access(self):
self.bobs_membership.rw = False
self.bobs_membership.save()
self.client.login(username="[email protected]", password="password")
r = self.client.get(self.url)
self.assertEqual(r.status_code, 403)

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

@ -77,6 +77,7 @@ project_urls = [
path("add_zulip/", views.add_zulip, name="hc-add-zulip"),
path("add_spike/", views.add_spike, name="hc-add-spike"),
path("add_linenotify/", views.add_linenotify, name="hc-add-linenotify"),
path("add_signal/", views.add_signal, name="hc-add-signal"),
path("badges/", views.badges, name="hc-badges"),
path("checks/", views.my_checks, name="hc-checks"),
path("checks/add/", views.add_check, name="hc-add-check"),


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

@ -299,6 +299,7 @@ def index(request):
"enable_pushbullet": settings.PUSHBULLET_CLIENT_ID is not None,
"enable_pushover": settings.PUSHOVER_API_TOKEN is not None,
"enable_shell": settings.SHELL_ENABLED is True,
"enable_signal": settings.SIGNAL_CLI_USERNAME is not None,
"enable_slack_btn": settings.SLACK_CLIENT_ID is not None,
"enable_sms": settings.TWILIO_AUTH is not None,
"enable_telegram": settings.TELEGRAM_TOKEN is not None,
@ -762,6 +763,7 @@ def channels(request, code):
"enable_pushbullet": settings.PUSHBULLET_CLIENT_ID is not None,
"enable_pushover": settings.PUSHOVER_API_TOKEN is not None,
"enable_shell": settings.SHELL_ENABLED is True,
"enable_signal": settings.SIGNAL_CLI_USERNAME is not None,
"enable_slack_btn": settings.SLACK_CLIENT_ID is not None,
"enable_sms": settings.TWILIO_AUTH is not None,
"enable_telegram": settings.TELEGRAM_TOKEN is not None,
@ -1632,6 +1634,38 @@ def add_whatsapp(request, code):
return render(request, "integrations/add_whatsapp.html", ctx)
@require_setting("SIGNAL_CLI_USERNAME")
@login_required
def add_signal(request, code):
project = _get_rw_project_for_user(request, code)
if request.method == "POST":
form = forms.AddSmsForm(request.POST)
if form.is_valid():
channel = Channel(project=project, kind="signal")
channel.name = form.cleaned_data["label"]
channel.value = json.dumps(
{
"value": form.cleaned_data["value"],
"up": form.cleaned_data["up"],
"down": form.cleaned_data["down"],
}
)
channel.save()
channel.assign_all_checks()
return redirect("hc-channels", project.code)
else:
form = forms.AddSmsForm()
ctx = {
"page": "channels",
"project": project,
"form": form,
"profile": project.owner_profile,
}
return render(request, "integrations/add_signal.html", ctx)
@require_setting("TRELLO_APP_KEY")
@login_required
def add_trello(request, code):


+ 4
- 0
hc/settings.py View File

@ -230,6 +230,10 @@ SHELL_ENABLED = envbool("SHELL_ENABLED", "False")
LINENOTIFY_CLIENT_ID = os.getenv("LINENOTIFY_CLIENT_ID")
LINENOTIFY_CLIENT_SECRET = os.getenv("LINENOTIFY_CLIENT_SECRET")
# Signal
SIGNAL_CLI_USERNAME = os.getenv("SIGNAL_CLI_USERNAME")
SIGNAL_CLI_CMD = os.getenv("SIGNAL_CLI_CMD", "signal-cli")
if os.path.exists(os.path.join(BASE_DIR, "hc/local_settings.py")):
from .local_settings import *
else:


+ 9
- 5
static/css/icomoon.css View File

@ -1,10 +1,10 @@
@font-face {
font-family: 'icomoon';
src: url('../fonts/icomoon.eot?qka09c');
src: url('../fonts/icomoon.eot?qka09c#iefix') format('embedded-opentype'),
url('../fonts/icomoon.ttf?qka09c') format('truetype'),
url('../fonts/icomoon.woff?qka09c') format('woff'),
url('../fonts/icomoon.svg?qka09c#icomoon') format('svg');
src: url('../fonts/icomoon.eot?y9u69e');
src: url('../fonts/icomoon.eot?y9u69e#iefix') format('embedded-opentype'),
url('../fonts/icomoon.ttf?y9u69e') format('truetype'),
url('../fonts/icomoon.woff?y9u69e') format('woff'),
url('../fonts/icomoon.svg?y9u69e#icomoon') format('svg');
font-weight: normal;
font-style: normal;
}
@ -24,6 +24,10 @@
-moz-osx-font-smoothing: grayscale;
}
.icon-signal:before {
content: "\e91c";
color: #2592e9;
}
.icon-linenotify:before {
content: "\e91b";
color: #00c300;


BIN
static/fonts/icomoon.eot View File


+ 1
- 0
static/fonts/icomoon.svg View File

@ -46,4 +46,5 @@
<glyph unicode="&#xe919;" glyph-name="spike" d="M109.229 960c-60.512 0-109.229-48.717-109.229-109.229v-805.546c0-60.512 48.717-109.223 109.229-109.223h805.546c60.512 0 109.223 48.712 109.223 109.223v805.546c0 60.512-48.712 109.229-109.223 109.229h-805.546zM519.994 785.921c26.731 0 48.899-9.736 66.504-29.225 18.255-19.486 27.384-43.496 27.384-72.027s-9.132-52.538-27.384-72.022c-17.605-19.486-39.773-29.231-66.504-29.231-27.386 0-50.206 9.74-68.461 29.231-17.605 19.484-26.403 43.49-26.403 72.022s8.804 52.541 26.403 72.027c18.255 19.484 41.075 29.225 68.461 29.225zM520.32 507.623c6.096-0.139 12.331-0.933 18.708-2.381l0.011 0.006c28.061-6.363 48.535-22.392 61.416-48.083 13.515-25.828 16.278-58.825 8.279-98.987-15.724-78.957-55.879-141.702-120.468-188.229-11.595-8.035-23.78-10.623-36.535-7.728s-22.963 10.188-30.631 21.894c-7.802 11.022-10.558 22.314-8.252 33.889 2.167 10.883 8.281 19.813 18.328 26.784 27.336 20.121 48.064 43.881 62.183 71.267-23.827 7.539-41.767 21.209-53.821 41.021-11.92 20.489-15.293 43.669-10.153 69.536 5.558 27.906 18.846 49.43 39.858 64.586 15.763 11.367 32.786 16.844 51.076 16.426z" />
<glyph unicode="&#xe91a;" glyph-name="call" d="M511.999 960c-282.768-0.001-511.998-229.229-511.999-511.998v-0.001c0.001-282.768 229.229-511.998 511.998-511.999h0.001c282.768 0.001 511.998 229.229 511.999 511.998v0.001c-0.001 282.768-229.229 511.998-511.998 511.999h-0.001zM523.135 718.551c0.212 0.008 0.461 0.012 0.71 0.012 1.228 0 2.431-0.105 3.601-0.307l-0.125 0.018c63.598-9.762 111.38-29.198 148.064-60.881s61.269-74.421 82.321-127.903c0.921-2.278 1.455-4.919 1.455-7.686 0-11.609-9.411-21.019-21.019-21.019-8.842 0-16.409 5.46-19.514 13.192l-0.050 0.141c-19.763 50.209-41.118 85.954-70.69 111.495s-68.379 42.13-126.95 51.12c-10.294 1.472-18.121 10.229-18.121 20.815 0 11.361 9.015 20.616 20.281 21.002l0.035 0.001zM335.388 700.417c28.506 0 56.475-2.279 61.81-8.072 8.866-33.089 17.681-87.508 30.79-150.924-1.22-23.476-66.153-77.434-66.153-77.434 17.073-28.659 66.462-80.495 88.414-97.873s83.536-60.363 120.121-67.070c0 0 44.507 80.799 54.873 83.238 14.634 0 129.576 4.573 143.6-11.281s34.151-114.946 28.969-134.153c-5.183-19.207-24.828-41.032-43.121-51.398-19.512-9.147-126.82-15.236-202.43 9.459-37.805 12.347-128.683 52.31-196.289 119.12s-111.52 160.031-117.925 196.301c-12.811 72.538-31.148 153.47 32.323 184.453 7.47 3.354 36.513 5.634 65.019 5.634zM516.113 634.010c0.212 0.008 0.461 0.012 0.71 0.012 1.228 0 2.431-0.105 3.601-0.307l-0.125 0.018c42.287-6.491 74.931-19.61 100.057-41.311s41.701-50.751 55.633-86.146c1.134-2.501 1.795-5.423 1.795-8.499 0-11.606-9.409-21.015-21.015-21.015-9.145 0-16.925 5.841-19.814 13.996l-0.045 0.147c-12.644 32.122-26 54.166-44.014 69.725s-41.719 25.843-78.979 31.563c-10.294 1.472-18.121 10.229-18.121 20.815 0 11.361 9.015 20.616 20.281 21.002l0.035 0.001z" />
<glyph unicode="&#xe91b;" glyph-name="linenotify" d="M826.24 539.179c14.891 0 26.88-12.16 26.88-26.923 0-14.72-11.989-26.88-26.88-26.88h-74.88v-48h74.88c14.891 0 26.88-12.075 26.88-26.88 0-14.677-11.989-26.837-26.88-26.837h-101.803c-14.72 0-26.752 12.16-26.752 26.837v203.563c0 14.72 12.032 26.88 26.88 26.88h101.803c14.763 0 26.752-12.16 26.752-26.88 0-14.891-11.989-26.88-26.88-26.88h-74.88v-48h74.88zM661.76 410.496c0-11.52-7.424-21.76-18.432-25.429-2.731-0.896-5.675-1.323-8.491-1.323-9.003 0-16.683 3.84-21.76 10.667l-104.235 141.525v-125.44c0-14.677-11.904-26.837-26.923-26.837-14.763 0-26.709 12.16-26.709 26.837v203.563c0 11.52 7.381 21.76 18.347 25.387 2.56 0.981 5.803 1.408 8.277 1.408 8.32 0 16-4.437 21.12-10.837l105.045-142.080v126.123c0 14.72 12.032 26.88 26.88 26.88 14.72 0 26.88-12.16 26.88-26.88v-203.563zM416.811 410.496c0-14.677-12.032-26.837-26.923-26.837-14.72 0-26.752 12.16-26.752 26.837v203.563c0 14.72 12.032 26.88 26.88 26.88 14.763 0 26.795-12.16 26.795-26.88v-203.563zM311.595 383.659h-101.803c-14.72 0-26.88 12.16-26.88 26.837v203.563c0 14.72 12.16 26.88 26.88 26.88 14.848 0 26.88-12.16 26.88-26.88v-176.683h74.923c14.848 0 26.837-12.075 26.837-26.88 0-14.677-12.032-26.837-26.837-26.837zM1024 519.936c0 229.163-229.76 415.659-512 415.659s-512-186.496-512-415.659c0-205.269 182.187-377.259 428.16-409.941 16.683-3.499 39.381-11.008 45.141-25.173 5.12-12.843 3.371-32.683 1.621-46.080l-6.997-43.52c-1.92-12.843-10.24-50.603 44.757-27.52 55.083 22.997 295.083 173.995 402.603 297.6 73.557 80.597 108.715 163.157 108.715 254.635z" />
<glyph unicode="&#xe91c;" glyph-name="signal" d="M512.512 849.152c-222.72 0-403.285-166.4-403.285-371.584 0-128.683 52.011-226.176 134.613-289.024h-0.213l9.984-129.792v-0.171c0-0.725 0-1.323 0.171-1.963v-0.256c1.024-6.144 6.4-10.88 12.843-10.88 2.261 0 4.267 0.64 6.144 1.493l0.469 0.341 123.563 71.424c36.437-8.363 75.392-12.757 115.627-12.757 222.72 0 403.285 166.357 403.285 371.627 0 205.227-180.608 371.584-403.371 371.584l0.171-0.043zM443.307 929.28c22.4 2.773 45.269 4.437 68.48 4.437 16.213 0 32-0.853 47.744-2.219l2.432 26.197c-16.64 1.493-33.237 2.304-50.091 2.304-24.32 0-48.427-1.536-72.149-4.651l3.541-26.112 0.043 0.043zM605.312 925.269c39.040-7.040 76.16-18.603 111.36-33.92l10.752 24.192c-5.632 2.389-11.221 4.779-16.939 7.040-32.512 12.8-66.176 22.357-100.48 28.629l-4.693-25.941zM996.523 474.88c-0.64-39.040-6.613-77.056-17.28-113.28l25.173-7.467c11.392 38.528 17.749 79.104 18.389 120.789l-26.283-0.043zM287.787 882.603c34.603 16.597 71.595 29.781 110.336 38.485l-5.76 25.771c-26.923-6.016-53.376-14.037-79.061-24.277-12.672-4.992-25.003-10.453-36.992-16.256l11.477-23.723zM870.187 822.912c-30.123 27.691-63.36 51.499-99.371 71.381l-13.269-23.083c34.603-18.987 66.603-41.856 94.891-68.096l17.749 19.712v0.085zM646.443 19.157c40.32 10.24 78.72 25.216 114.603 43.947l-13.44 22.741c-33.536-17.621-69.589-31.488-107.563-41.259l6.4-25.429zM156.885 789.12c26.88 27.221 57.472 51.2 90.88 71.467l-13.952 22.571c-29.781-18.005-57.6-38.997-82.987-62.848-4.352-4.011-8.533-8.149-12.8-12.331l18.731-18.816 0.128-0.043zM988.032 307.84l-24.235 10.069c-14.891-35.541-34.261-68.864-57.771-99.499l21.12-15.744c24.619 32.341 45.269 67.669 60.757 105.173h0.128zM884.309 770.859c25.856-28.8 47.872-60.8 65.451-95.061l23.68 11.733c-18.56 36.352-41.771 70.187-69.419 100.949l-19.84-17.621h0.128zM993.28 642.731l-24.235-10.411c13.355-35.328 22.229-72.661 25.771-111.36l26.24 2.603c-3.755 40.789-13.013 80.725-27.776 119.168zM272.469-14.891l-41.344-18.133-14.165 43.733-24.96-8.107 18.688-57.472c1.237-3.541 3.84-6.443 7.125-8.021 1.749-0.725 3.541-1.109 5.376-1.109s3.584 0.384 5.248 1.109l54.741 24.021-10.709 23.979zM435.584 36.011c-3.328 0.683-6.699 1.152-10.112 1.749l-19.84 3.371c-2.603 0.384-5.12 0-7.339-0.981l-83.243-36.395 11.563-23.637 78.72 34.261c5.333-0.896 10.667-1.749 15.829-2.517 2.901-0.725 5.931-1.109 8.917-1.621l5.504 25.771zM594.645 35.157c-27.008-4.437-54.656-6.656-82.859-6.656-11.648 0-22.4 0.64-32.939 1.621l-0.811-26.24c10.709-1.024 21.803-1.707 33.749-1.707 29.781 0 59.008 2.475 87.381 7.083l-4.437 25.856-0.085 0.043zM64.725 656.384c15.787 35.2 36.352 68.181 60.8 98.219l-20.053 17.365c-26.027-31.787-47.616-66.432-64.341-103.552l-0.427-0.896 24.021-11.136zM876.032 183.040c-26.112-27.776-55.893-52.437-88.32-73.728l14.464-21.973c34.389 22.357 65.749 48.64 93.44 78.037l-19.584 17.664zM41.387 339.157c-8.021 33.963-12.501 71.509-13.781 113.963h-26.283c1.28-45.696 6.187-85.547 14.72-121.301l25.344 7.339zM22.955 621.867c-12.672-38.827-19.968-79.061-21.76-120.021l26.24-1.28c1.792 39.381 8.917 77.483 20.779 113.579l-25.259 7.723zM202.112 56.107l-25.173 77.44c-0.853 2.56-2.389 4.651-4.48 6.272-9.899 7.765-19.157 15.616-28.16 23.808l-18.005-19.029c8.277-7.851 17.408-15.445 26.837-23.083l23.936-73.259 25.045 7.851zM112.299 196.565c-25.216 29.483-44.245 61.824-57.813 98.773l-24.448-9.771c1.749-4.565 3.541-9.216 5.376-13.739 14.080-33.963 32.896-64.384 57.259-92.8l19.627 17.536z" />
</font></defs></svg>

BIN
static/fonts/icomoon.ttf View File


BIN
static/fonts/icomoon.woff View File


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

Before After
Width: 142  |  Height: 142  |  Size: 2.1 KiB

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

@ -91,6 +91,14 @@
{% elif ch.zulip_type == "private" %}
user <span>{{ ch.zulip_to}}</span>
{% endif %}
{% elif ch.kind == "signal" %}
Signal to <span>{{ ch.phone_number }}</span>
{% if ch.signal_notify_down and not ch.signal_notify_up %}
(down only)
{% endif %}
{% if ch.signal_notify_up and not ch.signal_notify_down %}
(up only)
{% endif %}
{% else %}
{{ ch.get_kind_display }}
{% endif %}
@ -355,6 +363,17 @@
</li>
{% endif %}
{% if enable_signal %}
<li>
<img src="{% static 'img/integrations/signal.png' %}"
class="icon" alt="Signal icon" />
<h2>Signal</h2>
<p>Get a Signal message when a check goes up or down.</p>
<a href="{% url 'hc-add-signal' project.code %}" class="btn btn-primary">Add Integration</a>
</li>
{% endif %}
{% if enable_sms %}
<li>
<img src="{% static 'img/integrations/sms.png' %}"


+ 5
- 0
templates/front/event_summary.html View File

@ -40,6 +40,11 @@
{% if event.channel.name %}
({{ event.channel.name }})
{% endif %}
{% elif event.channel.kind == "signal" %}
Sent Signal message to {{ event.channel.phone_number }}
{% if event.channel.name %}
({{ event.channel.name }})
{% endif %}
{% else %}
Sent alert to {{ event.channel.get_kind_display }}
{% if event.channel.name %}


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

@ -574,6 +574,18 @@
</div>
{% endif %}
{% if enable_signal %}
<div class="col-lg-2 col-md-3 col-sm-4 col-xs-6">
<div class="integration">
<img src="{% static 'img/integrations/signal.png' %}" class="icon" alt="" />
<h3>
{% trans "Signal" %}<br>
<small>{% trans "Chat" %}</small>
</h3>
</div>
</div>
{% endif %}
{% if enable_sms %}
<div class="col-lg-2 col-md-3 col-sm-4 col-xs-6">
<div class="integration">


+ 98
- 0
templates/integrations/add_signal.html View File

@ -0,0 +1,98 @@
{% extends "base.html" %}
{% load humanize static hc_extras %}
{% block title %}Add Signal Integration - {{ site_name }}{% endblock %}
{% block content %}
<div class="row">
<div class="col-sm-12">
<h1>Signal</h1>
<p>Get a Signal message when a check goes up or down.</p>
<h2>Integration Settings</h2>
<form method="post" class="form-horizontal">
{% csrf_token %}
<div class="form-group {{ form.label.css_classes }}">
<label for="id_label" class="col-sm-2 control-label">Label</label>
<div class="col-sm-6">
<input
id="id_label"
type="text"
class="form-control"
name="label"
placeholder="Alice's Phone"
value="{{ form.label.value|default:"" }}">
{% if form.label.errors %}
<div class="help-block">
{{ form.label.errors|join:"" }}
</div>
{% else %}
<span class="help-block">
Optional. If you add multiple phone numbers,
the labels will help you tell them apart.
</span>
{% endif %}
</div>
</div>
<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>
{% else %}
<span class="help-block">
Make sure the phone number starts with "+" and has the
country code.
</span>
{% endif %}
</div>
</div>
<div id="add-email-notify-group" class="form-group">
<label class="col-sm-2 control-label">Notify When</label>
<div class="col-sm-10">
<label class="checkbox-container">
<input
type="checkbox"
name="down"
value="true"
{% if form.down.value %} checked {% endif %}>
<span class="checkmark"></span>
A check goes <strong>down</strong>
</label>
<label class="checkbox-container">
<input
type="checkbox"
name="up"
value="true"
{% if form.up.value %} checked {% endif %}>
<span class="checkmark"></span>
A check goes <strong>up</strong>
</label>
</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 %}

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

@ -0,0 +1,7 @@
{% load humanize %}{% spaceless %}
{% if check.status == "down" %}
The check “{{ check.name_then_code|safe }}” is DOWN. Last ping was {{ check.last_ping|naturaltime }}.
{% else %}
The check “{{ check.name_then_code|safe }}” is now UP.
{% endif %}
{% endspaceless %}

Loading…
Cancel
Save