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 OpsGenie instructions (#450)
- Update the email notification template to include more check and last ping details - Update the email notification template to include more check and last ping details
- Improve the crontab snippet in the "Check Details" page (#465) - Improve the crontab snippet in the "Check Details" page (#465)
- Add Signal integration (#428)
## v1.18.0 - 2020-12-09 ## 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` | PUSHOVER_SUBSCRIPTION_URL | `None`
| REMOTE_USER_HEADER | `None` | See [External Authentication](#external-authentication) for details. | REMOTE_USER_HEADER | `None` | See [External Authentication](#external-authentication) for details.
| SHELL_ENABLED | `"False"` | SHELL_ENABLED | `"False"`
| SIGNAL_CLI_USERNAME | `None`
| SIGNAL_CLI_CMD | `signal-cli` | Path to the signal-cli executable
| SLACK_CLIENT_ID | `None` | SLACK_CLIENT_ID | `None`
| SLACK_CLIENT_SECRET | `None` | SLACK_CLIENT_SECRET | `None`
| TELEGRAM_BOT_NAME | `"ExampleBot"` | 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 variables. The Pushover subscription URL should look similar to
`https://pushover.net/subscribe/yourAppName-randomAlphaNumericData`. `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 ### Telegram
* Create a Telegram bot by talking to the * Create a Telegram bot by talking to the


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

@ -52,6 +52,7 @@ CHANNEL_KINDS = (
("spike", "Spike"), ("spike", "Spike"),
("call", "Phone Call"), ("call", "Phone Call"),
("linenotify", "LINE Notify"), ("linenotify", "LINE Notify"),
("signal", "Signal"),
) )
PO_PRIORITIES = {-2: "lowest", -1: "low", 0: "normal", 1: "high", 2: "emergency"} PO_PRIORITIES = {-2: "lowest", -1: "low", 0: "normal", 1: "high", 2: "emergency"}
@ -471,6 +472,8 @@ class Channel(models.Model):
return transports.Call(self) return transports.Call(self)
elif self.kind == "linenotify": elif self.kind == "linenotify":
return transports.LineNotify(self) return transports.LineNotify(self)
elif self.kind == "signal":
return transports.Signal(self)
else: else:
raise NotImplementedError("Unknown channel kind: %s" % self.kind) raise NotImplementedError("Unknown channel kind: %s" % self.kind)
@ -649,7 +652,7 @@ class Channel(models.Model):
@property @property
def phone_number(self): def phone_number(self):
assert self.kind in ("call", "sms", "whatsapp")
assert self.kind in ("call", "sms", "whatsapp", "signal")
if self.value.startswith("{"): if self.value.startswith("{"):
doc = json.loads(self.value) doc = json.loads(self.value)
return doc["value"] return doc["value"]
@ -714,6 +717,18 @@ class Channel(models.Model):
doc = json.loads(self.value) doc = json.loads(self.value)
return doc["down"] 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 @property
def opsgenie_key(self): def opsgenie_key(self):
assert self.kind == "opsgenie" 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 from hc.test import BaseTestCase
class NotifyTestCase(BaseTestCase):
class NotifyEmailTestCase(BaseTestCase):
def setUp(self): def setUp(self):
super().setUp() 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 from django.utils.html import escape
import json import json
import requests import requests
import subprocess
from urllib.parse import quote, urlencode from urllib.parse import quote, urlencode
from hc.accounts.models import Profile from hc.accounts.models import Profile
@ -659,3 +660,27 @@ class LineNotify(HttpTransport):
} }
payload = {"message": tmpl("linenotify_message.html", check=check)} payload = {"message": tmpl("linenotify_message.html", check=check)}
return self.post(self.URL, headers=headers, params=payload) 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_zulip/", views.add_zulip, name="hc-add-zulip"),
path("add_spike/", views.add_spike, name="hc-add-spike"), path("add_spike/", views.add_spike, name="hc-add-spike"),
path("add_linenotify/", views.add_linenotify, name="hc-add-linenotify"), 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("badges/", views.badges, name="hc-badges"),
path("checks/", views.my_checks, name="hc-checks"), path("checks/", views.my_checks, name="hc-checks"),
path("checks/add/", views.add_check, name="hc-add-check"), 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_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_shell": settings.SHELL_ENABLED is True, "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_slack_btn": settings.SLACK_CLIENT_ID is not None,
"enable_sms": settings.TWILIO_AUTH is not None, "enable_sms": settings.TWILIO_AUTH is not None,
"enable_telegram": settings.TELEGRAM_TOKEN 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_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_shell": settings.SHELL_ENABLED is True, "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_slack_btn": settings.SLACK_CLIENT_ID is not None,
"enable_sms": settings.TWILIO_AUTH is not None, "enable_sms": settings.TWILIO_AUTH is not None,
"enable_telegram": settings.TELEGRAM_TOKEN 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) 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") @require_setting("TRELLO_APP_KEY")
@login_required @login_required
def add_trello(request, code): 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_ID = os.getenv("LINENOTIFY_CLIENT_ID")
LINENOTIFY_CLIENT_SECRET = os.getenv("LINENOTIFY_CLIENT_SECRET") 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")): if os.path.exists(os.path.join(BASE_DIR, "hc/local_settings.py")):
from .local_settings import * from .local_settings import *
else: else:


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

@ -1,10 +1,10 @@
@font-face { @font-face {
font-family: 'icomoon'; 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-weight: normal;
font-style: normal; font-style: normal;
} }
@ -24,6 +24,10 @@
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
.icon-signal:before {
content: "\e91c";
color: #2592e9;
}
.icon-linenotify:before { .icon-linenotify:before {
content: "\e91b"; content: "\e91b";
color: #00c300; 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="&#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="&#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="&#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> </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" %} {% elif ch.zulip_type == "private" %}
user <span>{{ ch.zulip_to}}</span> user <span>{{ ch.zulip_to}}</span>
{% endif %} {% 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 %} {% else %}
{{ ch.get_kind_display }} {{ ch.get_kind_display }}
{% endif %} {% endif %}
@ -355,6 +363,17 @@
</li> </li>
{% endif %} {% 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 %} {% if enable_sms %}
<li> <li>
<img src="{% static 'img/integrations/sms.png' %}" <img src="{% static 'img/integrations/sms.png' %}"


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

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


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

@ -574,6 +574,18 @@
</div> </div>
{% endif %} {% 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 %} {% if enable_sms %}
<div class="col-lg-2 col-md-3 col-sm-4 col-xs-6"> <div class="col-lg-2 col-md-3 col-sm-4 col-xs-6">
<div class="integration"> <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