forked from GithubBackups/healthchecks
Zendesk integration (experimental and hidden from Integrations page for now)
This commit is contained in:
parent
d6b920551b
commit
a869906fde
20
hc/api/migrations/0035_auto_20171229_2008.py
Normal file
20
hc/api/migrations/0035_auto_20171229_2008.py
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11.6 on 2017-12-29 20:08
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('api', '0034_auto_20171227_1530'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='channel',
|
||||||
|
name='kind',
|
||||||
|
field=models.CharField(choices=[('email', 'Email'), ('webhook', 'Webhook'), ('hipchat', 'HipChat'), ('slack', 'Slack'), ('pd', 'PagerDuty'), ('pagertree', 'PagerTree'), ('po', 'Pushover'), ('pushbullet', 'Pushbullet'), ('opsgenie', 'OpsGenie'), ('victorops', 'VictorOps'), ('discord', 'Discord'), ('telegram', 'Telegram'), ('sms', 'SMS'), ('zendesk', 'Zendesk')], max_length=20),
|
||||||
|
),
|
||||||
|
]
|
@ -39,7 +39,8 @@ CHANNEL_KINDS = (("email", "Email"),
|
|||||||
("victorops", "VictorOps"),
|
("victorops", "VictorOps"),
|
||||||
("discord", "Discord"),
|
("discord", "Discord"),
|
||||||
("telegram", "Telegram"),
|
("telegram", "Telegram"),
|
||||||
("sms", "SMS"))
|
("sms", "SMS"),
|
||||||
|
("zendesk", "Zendesk"))
|
||||||
|
|
||||||
PO_PRIORITIES = {
|
PO_PRIORITIES = {
|
||||||
-2: "lowest",
|
-2: "lowest",
|
||||||
@ -277,6 +278,8 @@ class Channel(models.Model):
|
|||||||
return transports.Telegram(self)
|
return transports.Telegram(self)
|
||||||
elif self.kind == "sms":
|
elif self.kind == "sms":
|
||||||
return transports.Sms(self)
|
return transports.Sms(self)
|
||||||
|
elif self.kind == "zendesk":
|
||||||
|
return transports.Zendesk(self)
|
||||||
else:
|
else:
|
||||||
raise NotImplementedError("Unknown channel kind: %s" % self.kind)
|
raise NotImplementedError("Unknown channel kind: %s" % self.kind)
|
||||||
|
|
||||||
@ -316,7 +319,6 @@ class Channel(models.Model):
|
|||||||
doc = json.loads(self.value)
|
doc = json.loads(self.value)
|
||||||
return doc.get("url_down")
|
return doc.get("url_down")
|
||||||
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def url_up(self):
|
def url_up(self):
|
||||||
assert self.kind == "webhook"
|
assert self.kind == "webhook"
|
||||||
@ -450,6 +452,18 @@ class Channel(models.Model):
|
|||||||
doc = json.loads(self.value)
|
doc = json.loads(self.value)
|
||||||
return doc["account"]
|
return doc["account"]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def zendesk_token(self):
|
||||||
|
assert self.kind == "zendesk"
|
||||||
|
doc = json.loads(self.value)
|
||||||
|
return doc["access_token"]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def zendesk_subdomain(self):
|
||||||
|
assert self.kind == "zendesk"
|
||||||
|
doc = json.loads(self.value)
|
||||||
|
return doc["subdomain"]
|
||||||
|
|
||||||
def latest_notification(self):
|
def latest_notification(self):
|
||||||
return Notification.objects.filter(channel=self).latest()
|
return Notification.objects.filter(channel=self).latest()
|
||||||
|
|
||||||
|
@ -489,3 +489,67 @@ class NotifyTestCase(BaseTestCase):
|
|||||||
|
|
||||||
self.channel.notify(self.check)
|
self.channel.notify(self.check)
|
||||||
self.assertTrue(mock_post.called)
|
self.assertTrue(mock_post.called)
|
||||||
|
|
||||||
|
@patch("hc.api.transports.requests.request")
|
||||||
|
def test_zendesk_down(self, mock_post):
|
||||||
|
v = json.dumps({"access_token": "fake-token", "subdomain": "foo"})
|
||||||
|
self._setup_data("zendesk", v)
|
||||||
|
mock_post.return_value.status_code = 200
|
||||||
|
|
||||||
|
self.channel.notify(self.check)
|
||||||
|
assert Notification.objects.count() == 1
|
||||||
|
|
||||||
|
args, kwargs = mock_post.call_args
|
||||||
|
method, url = args
|
||||||
|
self.assertEqual(method, "post")
|
||||||
|
self.assertTrue("foo.zendesk.com" in url)
|
||||||
|
|
||||||
|
payload = kwargs["json"]
|
||||||
|
self.assertEqual(payload["request"]["type"], "incident")
|
||||||
|
self.assertTrue("down" in payload["request"]["subject"])
|
||||||
|
|
||||||
|
headers = kwargs["headers"]
|
||||||
|
self.assertEqual(headers["Authorization"], "Bearer fake-token")
|
||||||
|
|
||||||
|
@patch("hc.api.transports.requests.request")
|
||||||
|
@patch("hc.api.transports.requests.get")
|
||||||
|
def test_zendesk_up(self, mock_get, mock_post):
|
||||||
|
v = json.dumps({"access_token": "fake-token", "subdomain": "foo"})
|
||||||
|
self._setup_data("zendesk", v, status="up")
|
||||||
|
|
||||||
|
mock_post.return_value.status_code = 200
|
||||||
|
mock_get.return_value.status_code = 200
|
||||||
|
mock_get.return_value.json.return_value = {
|
||||||
|
"requests": [{
|
||||||
|
"url": "https://foo.example.org/comment",
|
||||||
|
"description": "code is %s" % self.check.code
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
|
||||||
|
self.channel.notify(self.check)
|
||||||
|
assert Notification.objects.count() == 1
|
||||||
|
|
||||||
|
args, kwargs = mock_post.call_args
|
||||||
|
self.assertTrue("foo.example.org" in args[1])
|
||||||
|
|
||||||
|
payload = kwargs["json"]
|
||||||
|
self.assertEqual(payload["request"]["type"], "incident")
|
||||||
|
self.assertTrue("UP" in payload["request"]["subject"])
|
||||||
|
|
||||||
|
headers = kwargs["headers"]
|
||||||
|
self.assertEqual(headers["Authorization"], "Bearer fake-token")
|
||||||
|
|
||||||
|
@patch("hc.api.transports.requests.request")
|
||||||
|
@patch("hc.api.transports.requests.get")
|
||||||
|
def test_zendesk_up_with_no_existing_ticket(self, mock_get, mock_post):
|
||||||
|
v = json.dumps({"access_token": "fake-token", "subdomain": "foo"})
|
||||||
|
self._setup_data("zendesk", v, status="up")
|
||||||
|
|
||||||
|
mock_get.return_value.status_code = 200
|
||||||
|
mock_get.return_value.json.return_value = {"requests": []}
|
||||||
|
|
||||||
|
self.channel.notify(self.check)
|
||||||
|
n = Notification.objects.get()
|
||||||
|
self.assertEqual(n.error, "Could not find a ticket to update")
|
||||||
|
|
||||||
|
self.assertFalse(mock_post.called)
|
||||||
|
@ -115,6 +115,16 @@ class HttpTransport(Transport):
|
|||||||
|
|
||||||
return error
|
return error
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def put(cls, url, **kwargs):
|
||||||
|
# Make 3 attempts--
|
||||||
|
for x in range(0, 3):
|
||||||
|
error = cls._request("put", url, **kwargs)
|
||||||
|
if error is None:
|
||||||
|
break
|
||||||
|
|
||||||
|
return error
|
||||||
|
|
||||||
|
|
||||||
class Webhook(HttpTransport):
|
class Webhook(HttpTransport):
|
||||||
def prepare(self, template, check, urlencode=False):
|
def prepare(self, template, check, urlencode=False):
|
||||||
@ -357,3 +367,48 @@ class Sms(HttpTransport):
|
|||||||
}
|
}
|
||||||
|
|
||||||
return self.post(url, data=data, auth=auth)
|
return self.post(url, data=data, auth=auth)
|
||||||
|
|
||||||
|
|
||||||
|
class Zendesk(HttpTransport):
|
||||||
|
TMPL = "https://%s.zendesk.com/api/v2/requests.json"
|
||||||
|
|
||||||
|
def get_payload(self, check):
|
||||||
|
return {
|
||||||
|
"request": {
|
||||||
|
"subject": tmpl("zendesk_title.html", check=check),
|
||||||
|
"type": "incident",
|
||||||
|
"comment": {
|
||||||
|
"body": tmpl("zendesk_description.html", check=check)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def notify_down(self, check):
|
||||||
|
headers = {"Authorization": "Bearer %s" % self.channel.zendesk_token}
|
||||||
|
url = self.TMPL % self.channel.zendesk_subdomain
|
||||||
|
return self.post(url, headers=headers, json=self.get_payload(check))
|
||||||
|
|
||||||
|
def notify_up(self, check):
|
||||||
|
# Get the list of requests made by us, in newest-to-oldest order
|
||||||
|
url = self.TMPL % self.channel.zendesk_subdomain
|
||||||
|
url += "?sort_by=created_at&sort_order=desc"
|
||||||
|
headers = {"Authorization": "Bearer %s" % self.channel.zendesk_token}
|
||||||
|
r = requests.get(url, headers=headers, timeout=10)
|
||||||
|
if r.status_code != 200:
|
||||||
|
return "Received status code %d" % r.status_code
|
||||||
|
|
||||||
|
# Update the first request that has check.code in its description
|
||||||
|
doc = r.json()
|
||||||
|
if "requests" in doc:
|
||||||
|
for obj in doc["requests"]:
|
||||||
|
if str(check.code) in obj["description"]:
|
||||||
|
payload = self.get_payload(check)
|
||||||
|
return self.put(obj["url"], headers=headers, json=payload)
|
||||||
|
|
||||||
|
return "Could not find a ticket to update"
|
||||||
|
|
||||||
|
def notify(self, check):
|
||||||
|
if check.status == "down":
|
||||||
|
return self.notify_down(check)
|
||||||
|
if check.status == "up":
|
||||||
|
return self.notify_up(check)
|
||||||
|
68
hc/front/tests/test_add_zendesk.py
Normal file
68
hc/front/tests/test_add_zendesk.py
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
import json
|
||||||
|
|
||||||
|
from django.test.utils import override_settings
|
||||||
|
from hc.api.models import Channel
|
||||||
|
from hc.test import BaseTestCase
|
||||||
|
from mock import patch
|
||||||
|
|
||||||
|
|
||||||
|
@override_settings(ZENDESK_CLIENT_ID="t1", ZENDESK_CLIENT_SECRET="s1")
|
||||||
|
class AddZendeskTestCase(BaseTestCase):
|
||||||
|
url = "/integrations/add_zendesk/"
|
||||||
|
|
||||||
|
def test_instructions_work(self):
|
||||||
|
self.client.login(username="alice@example.org", password="password")
|
||||||
|
r = self.client.get(self.url)
|
||||||
|
self.assertContains(r, "Connect Zendesk Support", status_code=200)
|
||||||
|
|
||||||
|
def test_post_works(self):
|
||||||
|
self.client.login(username="alice@example.org", password="password")
|
||||||
|
r = self.client.post(self.url, {"subdomain": "foo"})
|
||||||
|
self.assertEqual(r.status_code, 302)
|
||||||
|
self.assertTrue("foo.zendesk.com" in r["Location"])
|
||||||
|
|
||||||
|
# There should now be a key in session
|
||||||
|
self.assertTrue("zendesk" in self.client.session)
|
||||||
|
|
||||||
|
@override_settings(ZENDESK_CLIENT_ID=None)
|
||||||
|
def test_it_requires_client_id(self):
|
||||||
|
self.client.login(username="alice@example.org", password="password")
|
||||||
|
r = self.client.get(self.url)
|
||||||
|
self.assertEqual(r.status_code, 404)
|
||||||
|
|
||||||
|
@patch("hc.front.views.requests.post")
|
||||||
|
def test_it_handles_oauth_response(self, mock_post):
|
||||||
|
session = self.client.session
|
||||||
|
session["zendesk"] = "foo"
|
||||||
|
session["subdomain"] = "foodomain"
|
||||||
|
session.save()
|
||||||
|
|
||||||
|
oauth_response = {"access_token": "test-token"}
|
||||||
|
mock_post.return_value.text = json.dumps(oauth_response)
|
||||||
|
mock_post.return_value.json.return_value = oauth_response
|
||||||
|
|
||||||
|
url = self.url + "?code=12345678&state=foo"
|
||||||
|
|
||||||
|
self.client.login(username="alice@example.org", password="password")
|
||||||
|
r = self.client.get(url, follow=True)
|
||||||
|
self.assertRedirects(r, "/integrations/")
|
||||||
|
self.assertContains(r, "The Zendesk integration has been added!")
|
||||||
|
|
||||||
|
ch = Channel.objects.get()
|
||||||
|
self.assertEqual(ch.zendesk_token, "test-token")
|
||||||
|
self.assertEqual(ch.zendesk_subdomain, "foodomain")
|
||||||
|
|
||||||
|
# Session should now be clean
|
||||||
|
self.assertFalse("zendesk" in self.client.session)
|
||||||
|
self.assertFalse("subdomain" in self.client.session)
|
||||||
|
|
||||||
|
def test_it_avoids_csrf(self):
|
||||||
|
session = self.client.session
|
||||||
|
session["zendesk"] = "foo"
|
||||||
|
session.save()
|
||||||
|
|
||||||
|
url = self.url + "?code=12345678&state=bar"
|
||||||
|
|
||||||
|
self.client.login(username="alice@example.org", password="password")
|
||||||
|
r = self.client.get(url)
|
||||||
|
self.assertEqual(r.status_code, 400)
|
@ -30,6 +30,7 @@ channel_urls = [
|
|||||||
url(r'^telegram/bot/$', views.telegram_bot, name="hc-telegram-webhook"),
|
url(r'^telegram/bot/$', views.telegram_bot, name="hc-telegram-webhook"),
|
||||||
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'^add_sms/$', views.add_sms, name="hc-add-sms"),
|
||||||
|
url(r'^add_zendesk/$', views.add_zendesk, name="hc-add-zendesk"),
|
||||||
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,
|
||||||
|
@ -352,6 +352,7 @@ def channels(request):
|
|||||||
"enable_telegram": settings.TELEGRAM_TOKEN is not None,
|
"enable_telegram": settings.TELEGRAM_TOKEN is not None,
|
||||||
"enable_sms": settings.TWILIO_AUTH is not None,
|
"enable_sms": settings.TWILIO_AUTH is not None,
|
||||||
"enable_pd": settings.PD_VENDOR_KEY is not None,
|
"enable_pd": settings.PD_VENDOR_KEY is not None,
|
||||||
|
"enable_zendesk": settings.ZENDESK_CLIENT_ID is not None,
|
||||||
"use_payments": settings.USE_PAYMENTS
|
"use_payments": settings.USE_PAYMENTS
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -895,6 +896,64 @@ def add_sms(request):
|
|||||||
return render(request, "integrations/add_sms.html", ctx)
|
return render(request, "integrations/add_sms.html", ctx)
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def add_zendesk(request):
|
||||||
|
if settings.ZENDESK_CLIENT_ID is None:
|
||||||
|
raise Http404("zendesk integration is not available")
|
||||||
|
|
||||||
|
if request.method == "POST":
|
||||||
|
domain = request.POST.get("subdomain")
|
||||||
|
request.session["subdomain"] = domain
|
||||||
|
redirect_uri = settings.SITE_ROOT + reverse("hc-add-zendesk")
|
||||||
|
auth_url = "https://%s.zendesk.com/oauth/authorizations/new?" % domain
|
||||||
|
auth_url += urlencode({
|
||||||
|
"client_id": settings.ZENDESK_CLIENT_ID,
|
||||||
|
"redirect_uri": redirect_uri,
|
||||||
|
"response_type": "code",
|
||||||
|
"scope": "requests:read requests:write",
|
||||||
|
"state": _prepare_state(request, "zendesk")
|
||||||
|
})
|
||||||
|
|
||||||
|
return redirect(auth_url)
|
||||||
|
|
||||||
|
if "code" in request.GET:
|
||||||
|
code = _get_validated_code(request, "zendesk")
|
||||||
|
if code is None:
|
||||||
|
return HttpResponseBadRequest()
|
||||||
|
|
||||||
|
domain = request.session.pop("subdomain")
|
||||||
|
url = "https://%s.zendesk.com/oauth/tokens" % domain
|
||||||
|
|
||||||
|
redirect_uri = settings.SITE_ROOT + reverse("hc-add-zendesk")
|
||||||
|
result = requests.post(url, {
|
||||||
|
"client_id": settings.ZENDESK_CLIENT_ID,
|
||||||
|
"client_secret": settings.ZENDESK_CLIENT_SECRET,
|
||||||
|
"code": code,
|
||||||
|
"grant_type": "authorization_code",
|
||||||
|
"redirect_uri": redirect_uri,
|
||||||
|
"scope": "read"
|
||||||
|
})
|
||||||
|
|
||||||
|
doc = result.json()
|
||||||
|
if "access_token" in doc:
|
||||||
|
doc["subdomain"] = domain
|
||||||
|
|
||||||
|
channel = Channel(kind="zendesk")
|
||||||
|
channel.user = request.team.user
|
||||||
|
channel.value = json.dumps(doc)
|
||||||
|
channel.save()
|
||||||
|
channel.assign_all_checks()
|
||||||
|
messages.success(request,
|
||||||
|
"The Zendesk integration has been added!")
|
||||||
|
else:
|
||||||
|
messages.warning(request, "Something went wrong")
|
||||||
|
|
||||||
|
return redirect("hc-channels")
|
||||||
|
|
||||||
|
ctx = {"page": "channels"}
|
||||||
|
return render(request, "integrations/add_zendesk.html", ctx)
|
||||||
|
|
||||||
|
|
||||||
def privacy(request):
|
def privacy(request):
|
||||||
return render(request, "front/privacy.html", {})
|
return render(request, "front/privacy.html", {})
|
||||||
|
|
||||||
|
@ -165,6 +165,10 @@ TWILIO_FROM = None
|
|||||||
# PagerDuty
|
# PagerDuty
|
||||||
PD_VENDOR_KEY = None
|
PD_VENDOR_KEY = None
|
||||||
|
|
||||||
|
# Zendesk
|
||||||
|
ZENDESK_CLIENT_ID = None
|
||||||
|
ZENDESK_CLIENT_SECRET = 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:
|
||||||
|
@ -221,4 +221,13 @@ table.channels-table > tbody > tr > th {
|
|||||||
|
|
||||||
.webhook-header {
|
.webhook-header {
|
||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add Zendesk */
|
||||||
|
.zendesk-subdomain {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.zendesk-subdomain input {
|
||||||
|
border-right: 0;
|
||||||
}
|
}
|
BIN
static/img/integrations/zendesk.png
Normal file
BIN
static/img/integrations/zendesk.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.9 KiB |
@ -100,6 +100,8 @@
|
|||||||
{{ ch.telegram_name }}
|
{{ ch.telegram_name }}
|
||||||
{% elif ch.kind == "hipchat" %}
|
{% elif ch.kind == "hipchat" %}
|
||||||
{{ ch.hipchat_webhook_url }}
|
{{ ch.hipchat_webhook_url }}
|
||||||
|
{% elif ch.kind == "zendesk" %}
|
||||||
|
{{ ch.zendesk_subdomain }}.zendesk.com
|
||||||
{% else %}
|
{% else %}
|
||||||
{{ ch.value }}
|
{{ ch.value }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -277,6 +279,17 @@
|
|||||||
|
|
||||||
<a href="{% url 'hc-add-opsgenie' %}" class="btn btn-primary">Add Integration</a>
|
<a href="{% url 'hc-add-opsgenie' %}" class="btn btn-primary">Add Integration</a>
|
||||||
</li>
|
</li>
|
||||||
|
{% if enable_zendesk and false %}
|
||||||
|
<li>
|
||||||
|
<img src="{% static 'img/integrations/zendesk.png' %}"
|
||||||
|
class="icon" alt="Discord icon" />
|
||||||
|
|
||||||
|
<h2>Zendesk Support</h2>
|
||||||
|
<p>Create a Zendesk support ticket when a check goes down.</p>
|
||||||
|
|
||||||
|
<a href="{% url 'hc-add-zendesk' %}" class="btn btn-primary">Add Integration</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
<li class="link-to-github">
|
<li class="link-to-github">
|
||||||
<img src="{% static 'img/integrations/missing.png' %}"
|
<img src="{% static 'img/integrations/missing.png' %}"
|
||||||
class="icon" alt="Suggest New Integration" />
|
class="icon" alt="Suggest New Integration" />
|
||||||
|
43
templates/integrations/add_zendesk.html
Normal file
43
templates/integrations/add_zendesk.html
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% load compress humanize staticfiles hc_extras %}
|
||||||
|
|
||||||
|
{% block title %}Add Zendesk - {% site_name %}{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-12">
|
||||||
|
<h1>Zendesk Support</h1>
|
||||||
|
|
||||||
|
<div class="jumbotron">
|
||||||
|
<p>
|
||||||
|
If your team uses <a href="http://zendesk.com/">Zendesk</a>,
|
||||||
|
you can set up {% site_name %} to create Zendesk support tickets
|
||||||
|
when checks go <strong>down</strong>, and comment on them when
|
||||||
|
checks go back <strong>up</strong>.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<form method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-offset-3 col-sm-6">
|
||||||
|
<div class="input-group input-group-lg zendesk-subdomain">
|
||||||
|
<input
|
||||||
|
name="subdomain"
|
||||||
|
placeholder="Subdomain"
|
||||||
|
type="text"
|
||||||
|
class="form-control">
|
||||||
|
<span class="input-group-addon">.zendesk.com</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit" class="btn btn-lg btn-default btn-block">
|
||||||
|
<img class="ai-icon" src="{% static 'img/integrations/zendesk.png' %}" alt="Zendesk" />
|
||||||
|
Connect Zendesk Support
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
8
templates/integrations/zendesk_description.html
Normal file
8
templates/integrations/zendesk_description.html
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{% load humanize %}
|
||||||
|
{% if check.status == "down" %}
|
||||||
|
{{ check.name_then_code }} is down.
|
||||||
|
Last ping was {{ check.last_ping|naturaltime }}.
|
||||||
|
Log: {{ check.log_url }}
|
||||||
|
{% else %}
|
||||||
|
{{ check.name_then_code }} received a ping and is now UP
|
||||||
|
{% endif %}
|
5
templates/integrations/zendesk_title.html
Normal file
5
templates/integrations/zendesk_title.html
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{% if check.status == "down" %}
|
||||||
|
{{ check.name_then_code }} is down
|
||||||
|
{% else %}
|
||||||
|
{{ check.name_then_code }} is now UP
|
||||||
|
{% endif %}
|
Loading…
x
Reference in New Issue
Block a user