Browse Source

Add Channel.name field, users can now name integrations.

pull/211/head
Pēteris Caune 6 years ago
parent
commit
21de50d84e
No known key found for this signature in database GPG Key ID: E28D7679E9A9EDE2
16 changed files with 385 additions and 276 deletions
  1. +1
    -0
      CHANGELOG.md
  2. +18
    -0
      hc/api/migrations/0043_channel_name.py
  3. +25
    -0
      hc/api/migrations/0044_auto_20181120_2004.py
  4. +6
    -2
      hc/api/models.py
  5. +4
    -0
      hc/front/forms.py
  6. +1
    -1
      hc/front/tests/test_add_sms.py
  7. +6
    -7
      hc/front/tests/test_channels.py
  8. +54
    -0
      hc/front/tests/test_update_channel_name.py
  9. +15
    -18
      hc/front/tests/test_update_name.py
  10. +1
    -0
      hc/front/urls.py
  11. +18
    -2
      hc/front/views.py
  12. +51
    -26
      static/css/channels.css
  13. +1
    -1
      static/css/my_checks_desktop.css
  14. +1
    -19
      static/js/channels.js
  15. +138
    -155
      templates/front/channels.html
  16. +45
    -45
      templates/front/update_name_modal.html

+ 1
- 0
CHANGELOG.md View File

@ -14,6 +14,7 @@ All notable changes to this project will be documented in this file.
- Add "channels" attribute to the Check API resource - Add "channels" attribute to the Check API resource
- Can specify channel codes when updating a check via API - Can specify channel codes when updating a check via API
- Added a workaround for email agents automatically opening "Unsubscribe" links - Added a workaround for email agents automatically opening "Unsubscribe" links
- Add Channel.name field, users can now name integrations
### Bug Fixes ### Bug Fixes
- During DST transition, handle ambiguous dates as pre-transition - During DST transition, handle ambiguous dates as pre-transition


+ 18
- 0
hc/api/migrations/0043_channel_name.py View File

@ -0,0 +1,18 @@
# Generated by Django 2.1.3 on 2018-11-20 16:50
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('api', '0042_auto_20181029_1522'),
]
operations = [
migrations.AddField(
model_name='channel',
name='name',
field=models.CharField(blank=True, max_length=100),
),
]

+ 25
- 0
hc/api/migrations/0044_auto_20181120_2004.py View File

@ -0,0 +1,25 @@
# Generated by Django 2.1.3 on 2018-11-20 20:04
import json
from django.db import migrations
def combine_channel_names(apps, schema_editor):
Channel = apps.get_model('api', 'Channel')
for channel in Channel.objects.filter(kind="sms"):
if channel.value.startswith("{"):
doc = json.loads(channel.value)
channel.name = doc.get("label", "")
channel.save()
class Migration(migrations.Migration):
dependencies = [
('api', '0043_channel_name'),
]
operations = [
migrations.RunPython(combine_channel_names),
]

+ 6
- 2
hc/api/models.py View File

@ -231,6 +231,7 @@ class Ping(models.Model):
class Channel(models.Model): class Channel(models.Model):
name = models.CharField(max_length=100, blank=True)
code = models.UUIDField(default=uuid.uuid4, editable=False) code = models.UUIDField(default=uuid.uuid4, editable=False)
user = models.ForeignKey(User, models.CASCADE) user = models.ForeignKey(User, models.CASCADE)
created = models.DateTimeField(auto_now_add=True) created = models.DateTimeField(auto_now_add=True)
@ -240,11 +241,11 @@ class Channel(models.Model):
checks = models.ManyToManyField(Check) checks = models.ManyToManyField(Check)
def __str__(self): def __str__(self):
if self.name:
return self.name
if self.kind == "email": if self.kind == "email":
return "Email to %s" % self.value return "Email to %s" % self.value
elif self.kind == "sms": elif self.kind == "sms":
if self.sms_label:
return "SMS to %s" % self.sms_label
return "SMS to %s" % self.sms_number return "SMS to %s" % self.sms_number
elif self.kind == "slack": elif self.kind == "slack":
return "Slack %s" % self.slack_channel return "Slack %s" % self.slack_channel
@ -327,6 +328,9 @@ class Channel(models.Model):
return error return error
def icon_path(self):
return 'img/integrations/%s.png' % self.kind
@property @property
def po_value(self): def po_value(self):
assert self.kind == "po" assert self.kind == "po"


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

@ -108,3 +108,7 @@ class AddSmsForm(forms.Form):
error_css_class = "has-error" error_css_class = "has-error"
label = forms.CharField(max_length=100, required=False) label = forms.CharField(max_length=100, required=False)
value = forms.CharField(max_length=16, validators=[phone_validator]) value = forms.CharField(max_length=16, validators=[phone_validator])
class ChannelNameForm(forms.Form):
name = forms.CharField(max_length=100, required=False)

+ 1
- 1
hc/front/tests/test_add_sms.py View File

@ -31,7 +31,7 @@ class AddSmsTestCase(BaseTestCase):
c = Channel.objects.get() c = Channel.objects.get()
self.assertEqual(c.kind, "sms") self.assertEqual(c.kind, "sms")
self.assertEqual(c.sms_number, "+1234567890") self.assertEqual(c.sms_number, "+1234567890")
self.assertEqual(c.sms_label, "My Phone")
self.assertEqual(c.name, "My Phone")
def test_it_rejects_bad_number(self): def test_it_rejects_bad_number(self):
form = {"value": "not a phone number address"} form = {"value": "not a phone number address"}


+ 6
- 7
hc/front/tests/test_channels.py View File

@ -33,9 +33,9 @@ class ChannelsTestCase(BaseTestCase):
self.assertEqual(r.status_code, 200) self.assertEqual(r.status_code, 200)
# These are inside a modal: # These are inside a modal:
self.assertContains(r, "<code>http://down.example.com</code>")
self.assertContains(r, "<code>http://up.example.com</code>")
self.assertContains(r, "<code>foobar</code>")
self.assertContains(r, "http://down.example.com")
self.assertContains(r, "http://up.example.com")
self.assertContains(r, "foobar")
def test_it_shows_pushover_details(self): def test_it_shows_pushover_details(self):
ch = Channel(kind="po", user=self.alice) ch = Channel(kind="po", user=self.alice)
@ -46,7 +46,6 @@ class ChannelsTestCase(BaseTestCase):
r = self.client.get("/integrations/") r = self.client.get("/integrations/")
self.assertEqual(r.status_code, 200) self.assertEqual(r.status_code, 200)
self.assertContains(r, "fake-key")
self.assertContains(r, "(normal priority)") self.assertContains(r, "(normal priority)")
def test_it_shows_disabled_email(self): def test_it_shows_disabled_email(self):
@ -63,7 +62,7 @@ class ChannelsTestCase(BaseTestCase):
self.client.login(username="[email protected]", password="password") self.client.login(username="[email protected]", password="password")
r = self.client.get("/integrations/") r = self.client.get("/integrations/")
self.assertEqual(r.status_code, 200) self.assertEqual(r.status_code, 200)
self.assertContains(r, "(bounced, disabled)")
self.assertContains(r, "Disabled")
def test_it_shows_unconfirmed_email(self): def test_it_shows_unconfirmed_email(self):
channel = Channel(user=self.alice, kind="email") channel = Channel(user=self.alice, kind="email")
@ -73,7 +72,7 @@ class ChannelsTestCase(BaseTestCase):
self.client.login(username="[email protected]", password="password") self.client.login(username="[email protected]", password="password")
r = self.client.get("/integrations/") r = self.client.get("/integrations/")
self.assertEqual(r.status_code, 200) self.assertEqual(r.status_code, 200)
self.assertContains(r, "(unconfirmed)")
self.assertContains(r, "Unconfirmed")
def test_it_shows_sms_label(self): def test_it_shows_sms_label(self):
ch = Channel(kind="sms", user=self.alice) ch = Channel(kind="sms", user=self.alice)
@ -84,4 +83,4 @@ class ChannelsTestCase(BaseTestCase):
r = self.client.get("/integrations/") r = self.client.get("/integrations/")
self.assertEqual(r.status_code, 200) self.assertEqual(r.status_code, 200)
self.assertContains(r, "My Phone (+123)")
self.assertContains(r, "SMS to +123")

+ 54
- 0
hc/front/tests/test_update_channel_name.py View File

@ -0,0 +1,54 @@
from hc.api.models import Channel
from hc.test import BaseTestCase
class UpdateChannelNameTestCase(BaseTestCase):
def setUp(self):
super(UpdateChannelNameTestCase, self).setUp()
self.channel = Channel(kind="email", user=self.alice)
self.channel.save()
self.url = "/integrations/%s/name/" % self.channel.code
def test_it_works(self):
payload = {"name": "My work email"}
self.client.login(username="[email protected]", password="password")
r = self.client.post(self.url, data=payload)
self.assertRedirects(r, "/integrations/")
self.channel.refresh_from_db()
self.assertEqual(self.channel.name, "My work email")
def test_team_access_works(self):
payload = {"name": "Bob was here"}
# Logging in as bob, not alice. Bob has team access so this
# should work.
self.client.login(username="[email protected]", password="password")
self.client.post(self.url, data=payload)
self.channel.refresh_from_db()
self.assertEqual(self.channel.name, "Bob was here")
def test_it_checks_ownership(self):
payload = {"name": "Charlie Sent This"}
self.client.login(username="[email protected]", password="password")
r = self.client.post(self.url, data=payload)
self.assertEqual(r.status_code, 403)
def test_it_handles_missing_uuid(self):
# Valid UUID but there is no check for it:
url = "/integrations/6837d6ec-fc08-4da5-a67f-08a9ed1ccf62/name/"
payload = {"name": "Alice Was Here"}
self.client.login(username="[email protected]", password="password")
r = self.client.post(url, data=payload)
self.assertEqual(r.status_code, 404)
def test_it_rejects_get(self):
self.client.login(username="[email protected]", password="password")
r = self.client.get(self.url)
self.assertEqual(r.status_code, 405)

+ 15
- 18
hc/front/tests/test_update_name.py View File

@ -9,36 +9,35 @@ class UpdateNameTestCase(BaseTestCase):
self.check = Check(user=self.alice) self.check = Check(user=self.alice)
self.check.save() self.check.save()
self.url = "/checks/%s/name/" % self.check.code
def test_it_works(self): def test_it_works(self):
url = "/checks/%s/name/" % self.check.code
payload = {"name": "Alice Was Here"} payload = {"name": "Alice Was Here"}
self.client.login(username="[email protected]", password="password") self.client.login(username="[email protected]", password="password")
r = self.client.post(url, data=payload)
r = self.client.post(self.url, data=payload)
self.assertRedirects(r, "/checks/") self.assertRedirects(r, "/checks/")
check = Check.objects.get(code=self.check.code)
assert check.name == "Alice Was Here"
self.check.refresh_from_db()
self.assertEqual(self.check.name, "Alice Was Here")
def test_team_access_works(self): def test_team_access_works(self):
url = "/checks/%s/name/" % self.check.code
payload = {"name": "Bob Was Here"} payload = {"name": "Bob Was Here"}
# Logging in as bob, not alice. Bob has team access so this # Logging in as bob, not alice. Bob has team access so this
# should work. # should work.
self.client.login(username="[email protected]", password="password") self.client.login(username="[email protected]", password="password")
self.client.post(url, data=payload)
self.client.post(self.url, data=payload)
check = Check.objects.get(code=self.check.code)
assert check.name == "Bob Was Here"
self.check.refresh_from_db()
self.assertEqual(self.check.name, "Bob Was Here")
def test_it_checks_ownership(self): def test_it_checks_ownership(self):
url = "/checks/%s/name/" % self.check.code
payload = {"name": "Charlie Sent This"} payload = {"name": "Charlie Sent This"}
self.client.login(username="[email protected]", password="password") self.client.login(username="[email protected]", password="password")
r = self.client.post(url, data=payload)
assert r.status_code == 403
r = self.client.post(self.url, data=payload)
self.assertEqual(r.status_code, 403)
def test_it_handles_bad_uuid(self): def test_it_handles_bad_uuid(self):
url = "/checks/not-uuid/name/" url = "/checks/not-uuid/name/"
@ -55,20 +54,18 @@ class UpdateNameTestCase(BaseTestCase):
self.client.login(username="[email protected]", password="password") self.client.login(username="[email protected]", password="password")
r = self.client.post(url, data=payload) r = self.client.post(url, data=payload)
assert r.status_code == 404
self.assertEqual(r.status_code, 404)
def test_it_sanitizes_tags(self): def test_it_sanitizes_tags(self):
url = "/checks/%s/name/" % self.check.code
payload = {"tags": " foo bar\r\t \n baz \n"} payload = {"tags": " foo bar\r\t \n baz \n"}
self.client.login(username="[email protected]", password="password") self.client.login(username="[email protected]", password="password")
self.client.post(url, data=payload)
self.client.post(self.url, data=payload)
check = Check.objects.get(id=self.check.id)
self.assertEqual(check.tags, "foo bar baz")
self.check.refresh_from_db()
self.assertEqual(self.check.tags, "foo bar baz")
def test_it_rejects_get(self): def test_it_rejects_get(self):
url = "/checks/%s/name/" % self.check.code
self.client.login(username="[email protected]", password="password") self.client.login(username="[email protected]", password="password")
r = self.client.get(url)
r = self.client.get(self.url)
self.assertEqual(r.status_code, 405) self.assertEqual(r.status_code, 405)

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

@ -38,6 +38,7 @@ channel_urls = [
path('add_trello/', views.add_trello, name="hc-add-trello"), path('add_trello/', views.add_trello, name="hc-add-trello"),
path('add_trello/settings/', views.trello_settings, name="hc-trello-settings"), path('add_trello/settings/', views.trello_settings, name="hc-trello-settings"),
path('<uuid:code>/checks/', views.channel_checks, name="hc-channel-checks"), path('<uuid:code>/checks/', views.channel_checks, name="hc-channel-checks"),
path('<uuid:code>/name/', views.update_channel_name, name="hc-channel-name"),
path('<uuid:code>/remove/', views.remove_channel, name="hc-remove-channel"), path('<uuid:code>/remove/', views.remove_channel, name="hc-remove-channel"),
path('<uuid:code>/verify/<slug:token>/', views.verify_email, path('<uuid:code>/verify/<slug:token>/', views.verify_email,
name="hc-verify-email"), name="hc-verify-email"),


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

@ -22,7 +22,8 @@ from hc.api.models import (DEFAULT_GRACE, DEFAULT_TIMEOUT, Channel, Check,
from hc.api.transports import Telegram from hc.api.transports import Telegram
from hc.front.forms import (AddWebhookForm, NameTagsForm, from hc.front.forms import (AddWebhookForm, NameTagsForm,
TimeoutForm, AddUrlForm, AddEmailForm, TimeoutForm, AddUrlForm, AddEmailForm,
AddOpsGenieForm, CronForm, AddSmsForm)
AddOpsGenieForm, CronForm, AddSmsForm,
ChannelNameForm)
from hc.front.schemas import telegram_callback from hc.front.schemas import telegram_callback
from hc.front.templatetags.hc_extras import (num_down_title, down_title, from hc.front.templatetags.hc_extras import (num_down_title, down_title,
sortchecks) sortchecks)
@ -498,6 +499,21 @@ def channel_checks(request, code):
return render(request, "front/channel_checks.html", ctx) return render(request, "front/channel_checks.html", ctx)
@require_POST
@login_required
def update_channel_name(request, code):
channel = get_object_or_404(Channel, code=code)
if channel.user_id != request.team.user.id:
return HttpResponseForbidden()
form = ChannelNameForm(request.POST)
if form.is_valid():
channel.name = form.cleaned_data["name"]
channel.save()
return redirect("hc-channels")
def verify_email(request, code, token): def verify_email(request, code, token):
channel = get_object_or_404(Channel, code=code) channel = get_object_or_404(Channel, code=code)
if channel.make_token() == token: if channel.make_token() == token:
@ -1001,8 +1017,8 @@ def add_sms(request):
form = AddSmsForm(request.POST) form = AddSmsForm(request.POST)
if form.is_valid(): if form.is_valid():
channel = Channel(user=request.team.user, kind="sms") channel = Channel(user=request.team.user, kind="sms")
channel.name = form.cleaned_data["label"]
channel.value = json.dumps({ channel.value = json.dumps({
"label": form.cleaned_data["label"],
"value": form.cleaned_data["value"] "value": form.cleaned_data["value"]
}) })
channel.save() channel.save()


+ 51
- 26
static/css/channels.css View File

@ -9,6 +9,26 @@
border-top: 1px solid #f1f1f1; border-top: 1px solid #f1f1f1;
} }
.channels-table .icon-cell {
width: 40px;
}
.channels-table .icon-cell img {
margin-left: 16px;
height: 40px;
}
.channels-table .th-name,
.channels-table .th-checks {
padding-left: 15px;
}
.channels-table .unnamed {
font-style: italic;
color: #999;
}
.channels-table .value-cell { .channels-table .value-cell {
max-width: 400px; max-width: 400px;
overflow: hidden; overflow: hidden;
@ -20,22 +40,10 @@
background: #f5f5f5; background: #f5f5f5;
} }
table.channels-table > tbody > tr > th { table.channels-table > tbody > tr > th {
border-top: 0; border-top: 0;
} }
.channels-table .channels-add-title {
border-top: 0;
padding-top: 20px
}
.channels-table .channels-add-help {
color: #888;
border-top: 0;
}
.word-up { .word-up {
color: #5cb85c; color: #5cb85c;
font-weight: bold font-weight: bold
@ -58,20 +66,32 @@ table.channels-table > tbody > tr > th {
font-size: small; font-size: small;
} }
.edit-name, .edit-checks {
padding: 12px 6px;
border: 1px solid #FFF;
cursor: pointer;
}
.channels-help-hidden {
display: none;
.edit-name .channel-details-mini {
font-size: 11.7px;
color: #888;
max-width: 500px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.channel-details-mini span {
color: #111;
}
.channel-row:hover .edit-name,
.channel-row:hover .edit-checks {
border: 1px dotted #AAA;
} }
.edit-checks { .edit-checks {
display: inline-block;
width: 120px;
padding: 6px;
text-align: center;
line-height: 28px;
border: 1px solid #FFF;
color: #333; color: #333;
} }
.edit-checks:hover { .edit-checks:hover {
@ -79,10 +99,6 @@ table.channels-table > tbody > tr > th {
color: #000; color: #000;
} }
.channel-row:hover .edit-checks {
border: 1px dotted #AAA;
}
.channel-remove { .channel-remove {
visibility: hidden; visibility: hidden;
} }
@ -91,6 +107,11 @@ table.channels-table > tbody > tr > th {
visibility: visible; visibility: visible;
} }
.webhook-details td {
max-width: 300px;
}
.ai-title { .ai-title {
margin-top: 2em; margin-top: 2em;
} }
@ -212,6 +233,10 @@ table.channels-table > tbody > tr > th {
font-style: italic; font-style: italic;
} }
.channel-modal .modal-body {
padding: 15px 40px;
}
/* Add Webhook */ /* Add Webhook */
@ -230,4 +255,4 @@ table.channels-table > tbody > tr > th {
.zendesk-subdomain input { .zendesk-subdomain input {
border-right: 0; border-right: 0;
}
}

+ 1
- 1
static/css/my_checks_desktop.css View File

@ -36,7 +36,7 @@
#checks-table .integrations, #checks-table .integrations,
#checks-table .timeout-grace, #checks-table .timeout-grace,
#checks-table .last-ping { #checks-table .last-ping {
border: 1px solid rgba(0, 0, 0, 0);
border: 1px solid #FFF;
padding: 6px; padding: 6px;
} }


+ 1
- 19
static/js/channels.js View File

@ -1,30 +1,12 @@
$(function() { $(function() {
var placeholders = {
email: "[email protected]",
webhook: "http://",
slack: "https://hooks.slack.com/...",
hipchat: "https://api.hipchat.com/...",
pd: "service key"
}
$("#add-channel-kind").change(function() {
$(".channels-add-help p").hide();
var v = $("#add-channel-kind").val();
$(".channels-add-help p." + v).show();
$("#add-channel-value").attr("placeholder", placeholders[v]);
});
$(".edit-checks").click(function() { $(".edit-checks").click(function() {
$("#checks-modal").modal("show"); $("#checks-modal").modal("show");
var url = $(this).attr("href");
$.ajax(url).done(function(data) {
$.ajax(this.dataset.url).done(function(data) {
$("#checks-modal .modal-content").html(data); $("#checks-modal .modal-content").html(data);
}) })
return false; return false;
}); });


+ 138
- 155
templates/front/channels.html View File

@ -18,114 +18,94 @@
<table class="table channels-table"> <table class="table channels-table">
{% if channels %} {% if channels %}
<tr> <tr>
<th>Type</th>
<th>Value</th>
<th>Assigned Checks</th>
<th></th>
<th class="th-name">Name, Details</th>
<th class="th-checks">Assigned Checks</th>
<th>Status</th>
<th>Last Notification</th> <th>Last Notification</th>
<th></th> <th></th>
</tr> </tr>
{% for ch in channels %} {% for ch in channels %}
{% with n=ch.latest_notification %}
<tr class="channel-row"> <tr class="channel-row">
<td>{{ ch.get_kind_display }}</td>
<td class="value-cell">
{% if ch.kind == "email" %}
<span class="preposition">to</span>
{{ ch.value }}
{% if not ch.email_verified %}
{% if ch.latest_notification and ch.latest_notification.error %}
<span class="channel-disabled">
(bounced, disabled)
</span>
<td class="icon-cell">
<img src="{% static ch.icon_path %}"
class="icon"
alt="Slack icon"
data-toggle="tooltip"
title="Slack" />
</td>
<td>
<div class="edit-name" data-toggle="modal" data-target="#name-{{ ch.code }}">
{% if ch.name %}
{{ ch.name }}
{% else %}
<div class="unnamed">unnamed</div>
{% endif %}
<div class="channel-details-mini">
{% if ch.kind == "email" %}
Email to <span>{{ ch.value }}</span>
{% elif ch.kind == "pd" %}
PagerDuty account <span>{{ ch.pd_account }}</span>
{% elif ch.kind == "pagertree" %}
PagerTree
{% elif ch.kind == "opsgenie" %}
OpsGenie
{% elif ch.kind == "victorops" %}
VictorOps
{% elif ch.kind == "po" %}
Pushover ({{ ch.po_value|last }} priority)
{% elif ch.kind == "slack" %}
Slack
{% if ch.slack_team %}
team <span>{{ ch.slack_team }}</span>,
channel <span>{{ ch.slack_channel }}</span>
{% endif %}
{% elif ch.kind == "webhook" %}
Webhook
{% elif ch.kind == "pushbullet" %}
Pushbullet
{% elif ch.kind == "discord" %}
Discord
{% elif ch.kind == "telegram" %}
Telegram
{% if ch.telegram_type == "group" %}
chat <span>{{ ch.telegram_name }}</span>
{% elif ch.telegram_type == "private" %}
user <span>{{ ch.telegram_name }}</span>
{% endif %}
{% elif ch.kind == "hipchat" %}
HipChat
{% elif ch.kind == "sms" %}
SMS to <span>{{ ch.sms_number }}</span>
{% elif ch.kind == "trello" %}
Trello
board <span>{{ ch.trello_board_list|first }}</span>,
list <span>{{ ch.trello_board_list|last }}</span>
{% else %} {% else %}
<span class="channel-unconfirmed">
(unconfirmed)
</span>
{{ ch.kind }}
{% endif %} {% endif %}
</span>
{% endif %}
{% elif ch.kind == "pd" %}
{% if ch.pd_account %}
<span class="preposition">account</span>
{{ ch.pd_account}},
{% endif %}
<span class="preposition">service key</span>
{{ ch.pd_service_key }}
{% elif ch.kind == "pagertree" %}
<span class="preposition">URL</span>
{{ ch.value }}
{% elif ch.kind == "opsgenie" %}
<span class="preposition">API key</span>
{{ ch.value }}
{% elif ch.kind == "victorops" %}
<span class="preposition">Post URL</span>
{{ ch.value }}
{% elif ch.kind == "po" %}
<span class="preposition">user key</span>
{{ ch.po_value|first }}
({{ ch.po_value|last }} priority)
{% elif ch.kind == "slack" %}
{% if ch.slack_team %}
<span class="preposition">team</span>
{{ ch.slack_team }},
<span class="preposition">channel</span>
{{ ch.slack_channel }}
{% else %}
{{ ch.value }}
{% endif %}
{% elif ch.kind == "webhook" %}
{% if ch.url_down %}
<span class="preposition">down</span> {{ ch.url_down }}
{% endif %}
{% if ch.url_up and not ch.url_down %}
<span class="preposition">up</span> {{ ch.url_up }}
{% endif %}
{% if ch.url_up or ch.post_data or ch.headers %}
<a href="#"
data-toggle="modal"
data-target="#channel-details-{{ ch.code }}">(details)</a>
{% endif %}
{% elif ch.kind == "pushbullet" %}
<span class="preposition">API key</span>
{{ ch.value }}
{% elif ch.kind == "discord" %}
{{ ch.discord_webhook_id }}
{% elif ch.kind == "telegram" %}
{% if ch.telegram_type == "group" %}
<span class="preposition">chat</span>
{% elif ch.telegram_type == "private" %}
<span class="preposition">user</span>
{% endif %}
{{ ch.telegram_name }}
{% elif ch.kind == "hipchat" %}
{{ ch.hipchat_webhook_url }}
{% elif ch.kind == "zendesk" %}
{{ ch.zendesk_subdomain }}.zendesk.com
{% elif ch.kind == "sms" %}
{% if ch.sms_label %}
{{ ch.sms_label }} ({{ ch.sms_number }})
</div>
</div>
</td>
<td>
<div class="edit-checks"
data-url="{% url 'hc-channel-checks' ch.code %}">
{{ ch.n_checks }} checks
</div>
</td>
<td>
{% if ch.kind == "email" and not ch.email_verified %}
{% if n and n.error %}
<span class="label label-danger">Disabled</span>
{% else %} {% else %}
{{ ch.sms_number }}
<span class="label label-default">Unconfirmed</span>
{% endif %} {% endif %}
{% elif ch.kind == "trello" %}
<span class="preposition">board</span>
{{ ch.trello_board_list|first }},
<span class="preposition">list</span>
{{ ch.trello_board_list|last }}
{% else %} {% else %}
{{ ch.value }}
Ready to deliver
{% endif %} {% endif %}
</td> </td>
<td class="channels-num-checks">
<a
class="edit-checks"
href="{% url 'hc-channel-checks' ch.code %}">
{{ ch.n_checks }} of {{ num_checks }}
</a>
</td>
<td> <td>
{% with n=ch.latest_notification %}
{% if n %} {% if n %}
{% if n.error %} {% if n.error %}
<span class="text-danger" data-toggle="tooltip" title="{{ n.error }}"> <span class="text-danger" data-toggle="tooltip" title="{{ n.error }}">
@ -140,7 +120,6 @@
{% if ch.kind == "sms" %} {% if ch.kind == "sms" %}
<p>Used {{ profile.sms_sent_this_month }} of {{ profile.sms_limit }} sends this month.</p> <p>Used {{ profile.sms_sent_this_month }} of {{ profile.sms_limit }} sends this month.</p>
{% endif %} {% endif %}
{% endwith %}
</td> </td>
<td> <td>
<button <button
@ -155,6 +134,7 @@
</td> </td>
</tr> </tr>
{% endwith %}
{% endfor %} {% endfor %}
{% endif %} {% endif %}
</table> </table>
@ -364,69 +344,72 @@
</div> </div>
{% for ch in channels %} {% for ch in channels %}
{% if ch.kind == "webhook" %}
<div id="channel-details-{{ ch.code }}" class="modal channel-details">
<div id="name-{{ ch.code }}" class="modal channel-modal">
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">&times;</button>
<h4>Integration Details</h4>
</div>
<div class="modal-body">
<table>
<tr>
<th>Request Method</th>
<td>
{% if ch.post_data %}
POST
{% else %}
GET
{% endif %}
</td>
</tr>
<tr>
<th>URL for "down" events</th>
<td>
{% if ch.url_down %}
<code>{{ ch.url_down }}</code>
{% else %}
<span class="missing">(not set)</span>
{% endif %}
</td>
</tr>
{% if ch.url_up %}
<tr>
<th>URL for "up" events</th>
<td>
{% if ch.url_up %}
<code>{{ ch.url_up }}</code>
{% else %}
<span class="missing">(not set)</span>
{% endif %}
</td>
</tr>
{% endif %}
{% if ch.post_data %}
<tr>
<th>POST data</th>
<td><code>{{ ch.post_data }}</code></td>
</tr>
{% endif %}
{% for k, v in ch.headers.items %}
<tr>
<th>Header <code>{{ k }}</code></th>
<td><code>{{ v }}</code></td>
</tr>
{% endfor %}
</table>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
<form
action="{% url 'hc-channel-name' ch.code %}"
class="form-horizontal"
method="post">
{% csrf_token %}
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">&times;</button>
<h4 class="update-timeout-title">Integration Details</h4>
</div>
<div class="modal-body">
<div class="form-group">
<label for="update-name-input" class="col-sm-2 control-label">
Name
</label>
<div class="col-sm-10">
<input
name="name"
type="text"
value="{{ ch.name }}"
placeholder="{{ ch }}"
class="input-name form-control" />
<span class="help-block">
Give this integration a human-friendly name,
so you can easily recognize it later.
</span>
</div>
</div>
{% if ch.kind == "webhook" %}
{% if ch.url_down %}
<p><strong>URL for "down" events</strong></p>
<pre>{{ ch.url_down }}</pre>
{% endif %}
{% if ch.url_up %}
<p><strong>URL for "up" events</strong></p>
<pre>{{ ch.url_up }}</pre>
{% endif %}
{% if ch.post_data %}
<p><strong>POST data</strong></p>
<pre>{{ ch.post_data }}</pre>
{% endif %}
{% for k, v in ch.headers.items %}
<p><strong>Header <code>{{ k }}</code></strong></p>
<pre>{{ v }}</pre>
{% endfor %}
{% endif %}
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-primary">Save</button>
</div>
</div> </div>
</div>
</form>
</div> </div>
</div> </div>
{% endif %}
{% endfor %} {% endfor %}
{% endblock %} {% endblock %}


+ 45
- 45
templates/front/update_name_modal.html View File

@ -12,58 +12,58 @@
<h4 class="update-timeout-title">Name and Tags</h4> <h4 class="update-timeout-title">Name and Tags</h4>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<div class="form-group">
<label for="update-name-input" class="col-sm-2 control-label">
Name
</label>
<div class="col-sm-10">
<input
id="update-name-input"
name="name"
type="text"
value="{{ check.name }}"
placeholder="unnamed"
class="input-name form-control" />
<div class="form-group">
<label for="update-name-input" class="col-sm-2 control-label">
Name
</label>
<div class="col-sm-10">
<input
id="update-name-input"
name="name"
type="text"
value="{{ check.name }}"
placeholder="unnamed"
class="input-name form-control" />
<span class="help-block">
Give this check a human-friendly name,
so you can easily recognize it later.
</span>
</div>
<span class="help-block">
Give this check a human-friendly name,
so you can easily recognize it later.
</span>
</div> </div>
</div>
<div class="form-group">
<label for="update-tags-input" class="col-sm-2 control-label">
Tags
</label>
<div class="col-sm-10">
<input
id="update-tags-input"
name="tags"
type="text"
value="{{ check.tags }}"
placeholder="production www"
class="form-control" />
<div class="form-group">
<label for="update-tags-input" class="col-sm-2 control-label">
Tags
</label>
<div class="col-sm-10">
<input
id="update-tags-input"
name="tags"
type="text"
value="{{ check.tags }}"
placeholder="production www"
class="form-control" />
<span class="help-block">
Use tags for easy filtering and for status badges.
Separate multiple tags with spaces.
</span>
</div>
<span class="help-block">
Use tags for easy filtering and for status badges.
Separate multiple tags with spaces.
</span>
</div> </div>
</div>
<div class="form-group">
<label for="update-desc-input" class="col-sm-2 control-label">
Description
</label>
<div class="col-sm-10">
<textarea
id="update-desc-input"
class="form-control"
rows="5"
name="desc">{{ check.desc }}</textarea>
</div>
<div class="form-group">
<label for="update-desc-input" class="col-sm-2 control-label">
Description
</label>
<div class="col-sm-10">
<textarea
id="update-desc-input"
class="form-control"
rows="5"
name="desc">{{ check.desc }}</textarea>
</div> </div>
</div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button> <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>


Loading…
Cancel
Save