forked from GithubBackups/healthchecks
Implement PagerDuty Simple Install Flow
This commit is contained in:
parent
2cd2bfed6f
commit
fd7ab5e767
@ -9,6 +9,7 @@ All notable changes to this project will be documented in this file.
|
|||||||
- Improve the handling of unknown email addresses in the Sign In form
|
- Improve the handling of unknown email addresses in the Sign In form
|
||||||
- Add support for "... is UP" SMS notifications
|
- Add support for "... is UP" SMS notifications
|
||||||
- Add an option for weekly reports (in addition to monthly)
|
- Add an option for weekly reports (in addition to monthly)
|
||||||
|
- Implement PagerDuty Simple Install Flow
|
||||||
|
|
||||||
## v1.20.0 - 2020-04-22
|
## v1.20.0 - 2020-04-22
|
||||||
|
|
||||||
|
10
README.md
10
README.md
@ -371,6 +371,16 @@ MATRIX_USER_ID=@mychecks:matrix.org
|
|||||||
MATRIX_ACCESS_TOKEN=[a long string of characters returned by the login call]
|
MATRIX_ACCESS_TOKEN=[a long string of characters returned by the login call]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### PagerDuty Simple Install Flow
|
||||||
|
|
||||||
|
To enable PagerDuty [Simple Install Flow](https://developer.pagerduty.com/docs/app-integration-development/events-integration/),
|
||||||
|
|
||||||
|
* Register a PagerDuty app at [PagerDuty](https://pagerduty.com/) › Developer Mode › My Apps
|
||||||
|
* In the newly created app, add the "Events Integration" functionality
|
||||||
|
* Specify a Redirect URL: `https://your-domain.com/integrations/add_pagerduty/`
|
||||||
|
* Copy the displayed app_id value (PXXXXX) and put it in the `PD_APP_ID` environment
|
||||||
|
variable
|
||||||
|
|
||||||
## Running in Production
|
## Running in Production
|
||||||
|
|
||||||
Here is a non-exhaustive list of pointers and things to check before launching a Healthchecks instance
|
Here is a non-exhaustive list of pointers and things to check before launching a Healthchecks instance
|
||||||
|
@ -29,6 +29,7 @@ MATTERMOST_ENABLED=True
|
|||||||
MSTEAMS_ENABLED=True
|
MSTEAMS_ENABLED=True
|
||||||
OPSGENIE_ENABLED=True
|
OPSGENIE_ENABLED=True
|
||||||
PAGERTREE_ENABLED=True
|
PAGERTREE_ENABLED=True
|
||||||
|
PD_APP_ID=
|
||||||
PD_ENABLED=True
|
PD_ENABLED=True
|
||||||
PD_VENDOR_KEY=
|
PD_VENDOR_KEY=
|
||||||
PING_BODY_LIMIT=10000
|
PING_BODY_LIMIT=10000
|
||||||
|
@ -650,12 +650,19 @@ class Channel(models.Model):
|
|||||||
doc = json.loads(self.value)
|
doc = json.loads(self.value)
|
||||||
return doc["service_key"]
|
return doc["service_key"]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def pd_service_name(self):
|
||||||
|
assert self.kind == "pd"
|
||||||
|
if self.value.startswith("{"):
|
||||||
|
doc = json.loads(self.value)
|
||||||
|
return doc.get("name")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def pd_account(self):
|
def pd_account(self):
|
||||||
assert self.kind == "pd"
|
assert self.kind == "pd"
|
||||||
if self.value.startswith("{"):
|
if self.value.startswith("{"):
|
||||||
doc = json.loads(self.value)
|
doc = json.loads(self.value)
|
||||||
return doc["account"]
|
return doc.get("account")
|
||||||
|
|
||||||
def latest_notification(self):
|
def latest_notification(self):
|
||||||
return Notification.objects.filter(channel=self).latest()
|
return Notification.objects.filter(channel=self).latest()
|
||||||
|
63
hc/front/tests/test_add_pagerduty_complete.py
Normal file
63
hc/front/tests/test_add_pagerduty_complete.py
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
import json
|
||||||
|
|
||||||
|
from django.test.utils import override_settings
|
||||||
|
from hc.api.models import Channel
|
||||||
|
from hc.test import BaseTestCase
|
||||||
|
from urllib.parse import urlencode
|
||||||
|
|
||||||
|
|
||||||
|
@override_settings(PD_APP_ID="FOOBAR")
|
||||||
|
class AddPagerDutyCompleteTestCase(BaseTestCase):
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
|
||||||
|
session = self.client.session
|
||||||
|
session["pagerduty"] = ("ABC", str(self.project.code))
|
||||||
|
session.save()
|
||||||
|
|
||||||
|
def _url(self, state="ABC"):
|
||||||
|
config = {
|
||||||
|
"account": {"name": "Foo"},
|
||||||
|
"integration_keys": [{"integration_key": "foo", "name": "bar"}],
|
||||||
|
}
|
||||||
|
|
||||||
|
url = "/integrations/add_pagerduty/?"
|
||||||
|
url += urlencode({"state": state, "config": json.dumps(config)})
|
||||||
|
return url
|
||||||
|
|
||||||
|
def test_it_adds_channel(self):
|
||||||
|
self.client.login(username="alice@example.org", password="password")
|
||||||
|
r = self.client.get(self._url())
|
||||||
|
self.assertRedirects(r, self.channels_url)
|
||||||
|
|
||||||
|
channel = Channel.objects.get()
|
||||||
|
self.assertEqual(channel.kind, "pd")
|
||||||
|
self.assertEqual(channel.pd_service_key, "foo")
|
||||||
|
self.assertEqual(channel.pd_account, "Foo")
|
||||||
|
|
||||||
|
def test_it_validates_state(self):
|
||||||
|
self.client.login(username="alice@example.org", password="password")
|
||||||
|
r = self.client.get(self._url(state="XYZ"))
|
||||||
|
self.assertEqual(r.status_code, 403)
|
||||||
|
|
||||||
|
@override_settings(PD_APP_ID=None)
|
||||||
|
def test_it_requires_app_id(self):
|
||||||
|
self.client.login(username="alice@example.org", password="password")
|
||||||
|
|
||||||
|
r = self.client.get(self._url())
|
||||||
|
self.assertEqual(r.status_code, 404)
|
||||||
|
|
||||||
|
@override_settings(PD_ENABLED=False)
|
||||||
|
def test_it_requires_pd_enabled(self):
|
||||||
|
self.client.login(username="alice@example.org", 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="bob@example.org", password="password")
|
||||||
|
r = self.client.get(self._url())
|
||||||
|
self.assertEqual(r.status_code, 403)
|
@ -3,6 +3,7 @@ from hc.api.models import Channel
|
|||||||
from hc.test import BaseTestCase
|
from hc.test import BaseTestCase
|
||||||
|
|
||||||
|
|
||||||
|
@override_settings(PD_APP_ID=None)
|
||||||
class AddPdTestCase(BaseTestCase):
|
class AddPdTestCase(BaseTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super().setUp()
|
super().setUp()
|
||||||
@ -47,3 +48,10 @@ class AddPdTestCase(BaseTestCase):
|
|||||||
self.client.login(username="alice@example.org", password="password")
|
self.client.login(username="alice@example.org", password="password")
|
||||||
r = self.client.get(self.url)
|
r = self.client.get(self.url)
|
||||||
self.assertEqual(r.status_code, 404)
|
self.assertEqual(r.status_code, 404)
|
||||||
|
|
||||||
|
@override_settings(PD_APP_ID="FOOBAR")
|
||||||
|
def test_it_handles_pd_app_id(self):
|
||||||
|
self.client.login(username="alice@example.org", password="password")
|
||||||
|
r = self.client.get(self.url)
|
||||||
|
self.assertContains(r, "app_id=FOOBAR")
|
||||||
|
self.assertIn("pagerduty", self.client.session)
|
||||||
|
@ -27,6 +27,7 @@ channel_urls = [
|
|||||||
path("add_pushbullet/", views.add_pushbullet_complete),
|
path("add_pushbullet/", views.add_pushbullet_complete),
|
||||||
path("add_discord/", views.add_discord_complete),
|
path("add_discord/", views.add_discord_complete),
|
||||||
path("add_linenotify/", views.add_linenotify_complete),
|
path("add_linenotify/", views.add_linenotify_complete),
|
||||||
|
path("add_pagerduty/", views.add_pd_complete, name="hc-add-pd-complete"),
|
||||||
path("add_pushover/", views.pushover_help, name="hc-pushover-help"),
|
path("add_pushover/", views.pushover_help, name="hc-pushover-help"),
|
||||||
path("telegram/", views.telegram_help, name="hc-telegram-help"),
|
path("telegram/", views.telegram_help, name="hc-telegram-help"),
|
||||||
path("telegram/bot/", views.telegram_bot, name="hc-telegram-webhook"),
|
path("telegram/bot/", views.telegram_bot, name="hc-telegram-webhook"),
|
||||||
|
@ -1089,6 +1089,21 @@ def add_shell(request, code):
|
|||||||
def add_pd(request, code):
|
def add_pd(request, code):
|
||||||
project = _get_rw_project_for_user(request, code)
|
project = _get_rw_project_for_user(request, code)
|
||||||
|
|
||||||
|
# Simple Install Flow
|
||||||
|
if settings.PD_APP_ID:
|
||||||
|
state = token_urlsafe()
|
||||||
|
|
||||||
|
redirect_url = settings.SITE_ROOT + reverse("hc-add-pd-complete")
|
||||||
|
redirect_url += "?" + urlencode({"state": state})
|
||||||
|
|
||||||
|
install_url = "https://app.pagerduty.com/install/integration?" + urlencode(
|
||||||
|
{"app_id": settings.PD_APP_ID, "redirect_url": redirect_url, "version": "2"}
|
||||||
|
)
|
||||||
|
|
||||||
|
ctx = {"page": "channels", "project": project, "install_url": install_url}
|
||||||
|
request.session["pagerduty"] = (state, str(project.code))
|
||||||
|
return render(request, "integrations/add_pd_simple.html", ctx)
|
||||||
|
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
form = forms.AddPdForm(request.POST)
|
form = forms.AddPdForm(request.POST)
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
@ -1101,10 +1116,37 @@ def add_pd(request, code):
|
|||||||
else:
|
else:
|
||||||
form = forms.AddPdForm()
|
form = forms.AddPdForm()
|
||||||
|
|
||||||
ctx = {"page": "channels", "form": form}
|
ctx = {"page": "channels", "project": project, "form": form}
|
||||||
return render(request, "integrations/add_pd.html", ctx)
|
return render(request, "integrations/add_pd.html", ctx)
|
||||||
|
|
||||||
|
|
||||||
|
@require_setting("PD_ENABLED")
|
||||||
|
@require_setting("PD_APP_ID")
|
||||||
|
@login_required
|
||||||
|
def add_pd_complete(request):
|
||||||
|
if "pagerduty" not in request.session:
|
||||||
|
return HttpResponseBadRequest()
|
||||||
|
|
||||||
|
state, code = request.session.pop("pagerduty")
|
||||||
|
if request.GET.get("state") != state:
|
||||||
|
return HttpResponseForbidden()
|
||||||
|
|
||||||
|
project = _get_rw_project_for_user(request, code)
|
||||||
|
|
||||||
|
doc = json.loads(request.GET["config"])
|
||||||
|
for item in doc["integration_keys"]:
|
||||||
|
channel = Channel(kind="pd", project=project)
|
||||||
|
channel.name = item["name"]
|
||||||
|
channel.value = json.dumps(
|
||||||
|
{"service_key": item["integration_key"], "account": doc["account"]["name"]}
|
||||||
|
)
|
||||||
|
channel.save()
|
||||||
|
channel.assign_all_checks()
|
||||||
|
|
||||||
|
messages.success(request, "The PagerDuty integration has been added!")
|
||||||
|
return redirect("hc-channels", project.code)
|
||||||
|
|
||||||
|
|
||||||
@require_setting("PD_ENABLED")
|
@require_setting("PD_ENABLED")
|
||||||
@require_setting("PD_VENDOR_KEY")
|
@require_setting("PD_VENDOR_KEY")
|
||||||
def pdc_help(request):
|
def pdc_help(request):
|
||||||
|
@ -215,6 +215,7 @@ PAGERTREE_ENABLED = envbool("PAGERTREE_ENABLED", "True")
|
|||||||
# PagerDuty
|
# PagerDuty
|
||||||
PD_ENABLED = envbool("PD_ENABLED", "True")
|
PD_ENABLED = envbool("PD_ENABLED", "True")
|
||||||
PD_VENDOR_KEY = os.getenv("PD_VENDOR_KEY")
|
PD_VENDOR_KEY = os.getenv("PD_VENDOR_KEY")
|
||||||
|
PD_APP_ID = os.getenv("PD_APP_ID")
|
||||||
|
|
||||||
# Prometheus
|
# Prometheus
|
||||||
PROMETHEUS_ENABLED = envbool("PROMETHEUS_ENABLED", "True")
|
PROMETHEUS_ENABLED = envbool("PROMETHEUS_ENABLED", "True")
|
||||||
|
BIN
static/img/integrations/setup_pd_simple_0.png
Normal file
BIN
static/img/integrations/setup_pd_simple_0.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 92 KiB |
BIN
static/img/integrations/setup_pd_simple_1.png
Normal file
BIN
static/img/integrations/setup_pd_simple_1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 44 KiB |
BIN
static/img/integrations/setup_pd_simple_2.png
Normal file
BIN
static/img/integrations/setup_pd_simple_2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 49 KiB |
BIN
static/img/integrations/setup_pd_simple_3.png
Normal file
BIN
static/img/integrations/setup_pd_simple_3.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 46 KiB |
@ -152,12 +152,23 @@ integration.</p>
|
|||||||
<h2 id="PAGERTREE_ENABLED"><code>PAGERTREE_ENABLED</code></h2>
|
<h2 id="PAGERTREE_ENABLED"><code>PAGERTREE_ENABLED</code></h2>
|
||||||
<p>Default: <code>True</code></p>
|
<p>Default: <code>True</code></p>
|
||||||
<p>A boolean that turns on/off the PagerTree integration. Enabled by default.</p>
|
<p>A boolean that turns on/off the PagerTree integration. Enabled by default.</p>
|
||||||
|
<h2 id="PD_APP_ID"><code>PD_APP_ID</code></h2>
|
||||||
|
<p>Default: <code>None</code></p>
|
||||||
|
<p>PagerDuty application ID. If set, enables the PagerDuty
|
||||||
|
<a href="https://developer.pagerduty.com/docs/app-integration-development/events-integration/">Simple Install Flow</a>.
|
||||||
|
If <code>None</code>, Healthchecks will fall back to the even simpler flow where users manually
|
||||||
|
copy integration keys from PagerDuty and paste them in Healthchecks.</p>
|
||||||
|
<p>To set up:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Register a PagerDuty app at <a href="https://pagerduty.com/">PagerDuty</a> › Developer Mode › My Apps</li>
|
||||||
|
<li>In the newly created app, add the "Events Integration" functionality</li>
|
||||||
|
<li>Specify a Redirect URL: <code>https://your-domain.com/integrations/add_pagerduty/</code></li>
|
||||||
|
<li>Copy the displayed app_id value (PXXXXX) and put it in the <code>PD_APP_ID</code> environment
|
||||||
|
variable</li>
|
||||||
|
</ul>
|
||||||
<h2 id="PD_ENABLED"><code>PD_ENABLED</code></h2>
|
<h2 id="PD_ENABLED"><code>PD_ENABLED</code></h2>
|
||||||
<p>Default: <code>True</code></p>
|
<p>Default: <code>True</code></p>
|
||||||
<p>A boolean that turns on/off the PagerDuty integration. Enabled by default.</p>
|
<p>A boolean that turns on/off the PagerDuty integration. Enabled by default.</p>
|
||||||
<h2 id="PD_VENDOR_KEY"><code>PD_VENDOR_KEY</code></h2>
|
|
||||||
<p>Default: <code>None</code></p>
|
|
||||||
<p><a href="https://www.pagerduty.com/">PagerDuty</a> vendor key, used by the PagerDuty integration.</p>
|
|
||||||
<h2 id="PING_BODY_LIMIT"><code>PING_BODY_LIMIT</code></h2>
|
<h2 id="PING_BODY_LIMIT"><code>PING_BODY_LIMIT</code></h2>
|
||||||
<p>Default: <code>10000</code></p>
|
<p>Default: <code>10000</code></p>
|
||||||
<p>The upper size limit in bytes for logged ping request bodies.
|
<p>The upper size limit in bytes for logged ping request bodies.
|
||||||
|
@ -254,18 +254,29 @@ Default: `True`
|
|||||||
|
|
||||||
A boolean that turns on/off the PagerTree integration. Enabled by default.
|
A boolean that turns on/off the PagerTree integration. Enabled by default.
|
||||||
|
|
||||||
|
## `PD_APP_ID` {: #PD_APP_ID }
|
||||||
|
|
||||||
|
Default: `None`
|
||||||
|
|
||||||
|
PagerDuty application ID. If set, enables the PagerDuty
|
||||||
|
[Simple Install Flow](https://developer.pagerduty.com/docs/app-integration-development/events-integration/).
|
||||||
|
If `None`, Healthchecks will fall back to the even simpler flow where users manually
|
||||||
|
copy integration keys from PagerDuty and paste them in Healthchecks.
|
||||||
|
|
||||||
|
To set up:
|
||||||
|
|
||||||
|
* Register a PagerDuty app at [PagerDuty](https://pagerduty.com/) › Developer Mode › My Apps
|
||||||
|
* In the newly created app, add the "Events Integration" functionality
|
||||||
|
* Specify a Redirect URL: `https://your-domain.com/integrations/add_pagerduty/`
|
||||||
|
* Copy the displayed app_id value (PXXXXX) and put it in the `PD_APP_ID` environment
|
||||||
|
variable
|
||||||
|
|
||||||
## `PD_ENABLED` {: #PD_ENABLED }
|
## `PD_ENABLED` {: #PD_ENABLED }
|
||||||
|
|
||||||
Default: `True`
|
Default: `True`
|
||||||
|
|
||||||
A boolean that turns on/off the PagerDuty integration. Enabled by default.
|
A boolean that turns on/off the PagerDuty integration. Enabled by default.
|
||||||
|
|
||||||
## `PD_VENDOR_KEY` {: #PD_VENDOR_KEY }
|
|
||||||
|
|
||||||
Default: `None`
|
|
||||||
|
|
||||||
[PagerDuty](https://www.pagerduty.com/) vendor key, used by the PagerDuty integration.
|
|
||||||
|
|
||||||
## `PING_BODY_LIMIT` {: #PING_BODY_LIMIT }
|
## `PING_BODY_LIMIT` {: #PING_BODY_LIMIT }
|
||||||
|
|
||||||
Default: `10000`
|
Default: `10000`
|
||||||
|
124
templates/integrations/add_pd_simple.html
Normal file
124
templates/integrations/add_pd_simple.html
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% load humanize static hc_extras %}
|
||||||
|
|
||||||
|
{% block title %}PagerDuty Integration for {{ site_name }}{% endblock %}
|
||||||
|
|
||||||
|
{% block description %}
|
||||||
|
<meta name="description" content="Use {{ site_name }} with PagerDuty: configure {{ site_name }} to create a PagerDuty incident when a check goes down, and resolve it when a check goes back up.">
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-12">
|
||||||
|
<h1>PagerDuty</h1>
|
||||||
|
|
||||||
|
<div class="jumbotron">
|
||||||
|
{% if request.user.is_authenticated %}
|
||||||
|
<p>If your team uses <a href="https://www.pagerduty.com">PagerDuty</a>,
|
||||||
|
you can set up {{ site_name }} to create a PagerDuty incident when
|
||||||
|
a check goes down, and resolve it when a check goes back up.</p>
|
||||||
|
|
||||||
|
{% if install_url %}
|
||||||
|
<div class="text-center">
|
||||||
|
<div class="text-center">
|
||||||
|
<a href="{{ install_url|safe }}" class="btn btn-lg btn-default">
|
||||||
|
<img class="ai-icon" src="{% static 'img/integrations/pd.png' %}" alt="PagerDuty" />
|
||||||
|
Connect PagerDuty
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% else %}
|
||||||
|
<p>
|
||||||
|
{{ site_name }} is a <strong>free</strong> and
|
||||||
|
<a href="https://github.com/healthchecks/healthchecks">open source</a>
|
||||||
|
service for monitoring your cron jobs, background processes and
|
||||||
|
scheduled tasks. Before adding PagerDuty integration, please log into
|
||||||
|
{{ site_name }}:</p>
|
||||||
|
|
||||||
|
<div class="text-center">
|
||||||
|
<a href="{% url 'hc-login' %}"
|
||||||
|
class="btn btn-primary btn-lg">Sign In</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Setup Guide</h2>
|
||||||
|
|
||||||
|
{% if not connect_url %}
|
||||||
|
<div class="row ai-step">
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<span class="step-no"></span>
|
||||||
|
<p>
|
||||||
|
{% if request.user.is_authenticated %}
|
||||||
|
Go
|
||||||
|
{% else %}
|
||||||
|
After logging in, go
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
to the <strong>Integrations</strong> page,
|
||||||
|
and click on <strong>Add Integration</strong> next to the
|
||||||
|
PagerDuty integration.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<img
|
||||||
|
class="ai-guide-screenshot"
|
||||||
|
alt="Screenshot"
|
||||||
|
src="{% static 'img/integrations/setup_pd_simple_0.png' %}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="row ai-step">
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<span class="step-no"></span>
|
||||||
|
<p>
|
||||||
|
Click on "Connect PagerDuty", and you will be
|
||||||
|
asked to log into your PagerDuty account.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<img
|
||||||
|
class="ai-guide-screenshot"
|
||||||
|
alt="Screenshot"
|
||||||
|
src="{% static 'img/integrations/setup_pd_simple_1.png' %}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row ai-step">
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<span class="step-no"></span>
|
||||||
|
<p>
|
||||||
|
Next, PagerDuty will let set the services
|
||||||
|
for this integration.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<img
|
||||||
|
class="ai-guide-screenshot"
|
||||||
|
alt="Screenshot"
|
||||||
|
src="{% static 'img/integrations/setup_pd_simple_2.png' %}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row ai-step">
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<span class="step-no"></span>
|
||||||
|
<p>
|
||||||
|
And that is all! You will then be redirected back to
|
||||||
|
"Integrations" page on {{ site_name }} and see
|
||||||
|
the new integration!
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<img
|
||||||
|
class="ai-guide-screenshot"
|
||||||
|
alt="Screenshot"
|
||||||
|
src="{% static 'img/integrations/setup_pd_simple_3.png' %}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
Loading…
x
Reference in New Issue
Block a user