@ -0,0 +1,30 @@ | |||
# -*- coding: utf-8 -*- | |||
from __future__ import unicode_literals | |||
from django.db import models, migrations | |||
from django.conf import settings | |||
import uuid | |||
class Migration(migrations.Migration): | |||
dependencies = [ | |||
migrations.swappable_dependency(settings.AUTH_USER_MODEL), | |||
('api', '0009_auto_20150801_1250'), | |||
] | |||
operations = [ | |||
migrations.CreateModel( | |||
name='Channel', | |||
fields=[ | |||
('id', models.AutoField(primary_key=True, auto_created=True, verbose_name='ID', serialize=False)), | |||
('code', models.UUIDField(editable=False, default=uuid.uuid4)), | |||
('created', models.DateTimeField(auto_now_add=True)), | |||
('kind', models.CharField(choices=[('email', 'Email'), ('webhook', 'Webhook'), ('pd', 'PagerDuty')], max_length=20)), | |||
('value', models.CharField(max_length=200, blank=True)), | |||
('email_verified', models.BooleanField(default=False)), | |||
('checks', models.ManyToManyField(to='api.Check')), | |||
('user', models.ForeignKey(to=settings.AUTH_USER_MODEL)), | |||
], | |||
), | |||
] |
@ -1,6 +1,15 @@ | |||
from django import forms | |||
from hc.api.models import Channel | |||
class TimeoutForm(forms.Form): | |||
timeout = forms.IntegerField(min_value=60, max_value=604800) | |||
grace = forms.IntegerField(min_value=60, max_value=604800) | |||
class AddChannelForm(forms.ModelForm): | |||
class Meta: | |||
model = Channel | |||
fields = ['kind', 'value'] |
@ -0,0 +1,26 @@ | |||
.channel-checks-table tr:first-child td { | |||
border-top: 0; | |||
} | |||
.channel-checks-table td:first-child, .channel-checks-table th:first-child { | |||
padding-left: 16px; | |||
} | |||
.channel-checks-table .check-all-cell { | |||
background: #EEE; | |||
} | |||
.channel-checks-table .check-all-cell .cbx-container { | |||
background: #FFF; | |||
} | |||
.channel-checks-table input[type=checkbox]:checked + label:after { | |||
font-family: 'Glyphicons Halflings'; | |||
content: "\e013"; | |||
} | |||
.channel-checks-table label:after { | |||
padding-left: 4px; | |||
padding-top: 2px; | |||
font-size: 9px; | |||
} |
@ -0,0 +1,44 @@ | |||
.channels-table { | |||
margin-top: 36px; | |||
} | |||
table.channels-table > tbody > tr > th { | |||
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 { | |||
color: #5cb85c; | |||
font-weight: bold | |||
} | |||
.word-down { | |||
color: #d9534f; | |||
font-weight: bold | |||
} | |||
.preposition { | |||
color: #888; | |||
} | |||
.channel-unconfirmed { | |||
font-size: small; | |||
} | |||
.channels-help-hidden { | |||
display: none; | |||
} | |||
.channels-table .channels-num-checks { | |||
padding-left: 40px; | |||
} | |||
@ -0,0 +1,38 @@ | |||
$(function() { | |||
var placeholders = { | |||
email: "[email protected]", | |||
webhook: "http://", | |||
pd: "service key" | |||
} | |||
$("#add-check-kind").change(function() { | |||
$(".channels-add-help p").hide(); | |||
var v = $("#add-check-kind").val(); | |||
$(".channels-add-help p." + v).show(); | |||
$("#add-check-value").attr("placeholder", placeholders[v]); | |||
}); | |||
$(".edit-checks").click(function() { | |||
$("#checks-modal").modal("show"); | |||
var url = $(this).attr("href"); | |||
$.ajax(url).done(function(data) { | |||
$("#checks-modal .modal-content").html(data); | |||
}) | |||
return false; | |||
}); | |||
var $cm = $("#checks-modal"); | |||
$cm.on("click", "#toggle-all", function() { | |||
var value = $(this).prop("checked"); | |||
$cm.find(".toggle").prop("checked", value); | |||
console.log("aaa", value); | |||
}); | |||
}); |
@ -0,0 +1,47 @@ | |||
{% load compress humanize staticfiles hc_extras %} | |||
<form method="post"> | |||
{% csrf_token %} | |||
<div class="modal-header"> | |||
<button type="button" class="close" data-dismiss="modal">×</span></button> | |||
<h4 class="update-timeout-title">Assign Checks to Channel {{ channel.value }}</h4> | |||
</div> | |||
<input type="hidden" name="channel" value="{{ channel.code }}" /> | |||
<table class="table channel-checks-table"> | |||
<tr> | |||
<th class="check-all-cell"> | |||
<input | |||
id="toggle-all" | |||
type="checkbox" | |||
class="toggle" /> | |||
</th> | |||
<th class="check-all-cell"> | |||
Check / Uncheck All | |||
</th> | |||
</tr> | |||
{% for check in checks %} | |||
<tr> | |||
<td> | |||
<input | |||
type="checkbox" | |||
class="toggle" | |||
data-toggle="checkbox-x" | |||
{% if check.code in assigned %} checked {% endif %} | |||
name="check-{{ check.code }}"> | |||
</td> | |||
<td> | |||
{{ check.name|default:check.code }} | |||
</td> | |||
</tr> | |||
{% endfor %} | |||
</table> | |||
<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> | |||
</form> | |||
@ -0,0 +1,117 @@ | |||
{% extends "base.html" %} | |||
{% load compress humanize staticfiles hc_extras %} | |||
{% block title %}Notification Channels - healthchecks.io{% endblock %} | |||
{% block content %} | |||
<div class="row"> | |||
<div class="col-sm-12"> | |||
<h1>Notification Channels</h1> | |||
<table class="table channels-table"> | |||
<tr> | |||
<th>Type</th> | |||
<th>Value</th> | |||
<th>Assigned Checks</th> | |||
<th></th> | |||
</tr> | |||
{% for ch in channels %} | |||
<tr> | |||
<td> | |||
{% if ch.kind == "email" %} Email {% endif %} | |||
{% if ch.kind == "webhook" %} Webhook {% endif %} | |||
{% if ch.kind == "pd" %} PagerDuty {% endif %} | |||
</td> | |||
<td> | |||
<span class="preposition"> | |||
{% if ch.kind == "email" %} to {% endif %} | |||
{% if ch.kind == "pd" %} service key {% endif %} | |||
</span> | |||
{{ ch.value }} | |||
{% if ch.kind == "email" and not ch.email_verified %} | |||
<span class="channel-unconfirmed">(unconfirmed) | |||
{% endif %} | |||
</td> | |||
<td class="channels-num-checks"> | |||
<a | |||
class="edit-checks" | |||
href="{% url 'hc-channel-checks' ch.code %}"> | |||
{{ ch.checks.count }} of {{ num_checks }} | |||
</a> | |||
</td> | |||
</tr> | |||
{% endfor %} | |||
<tr> | |||
<th colspan="2" class="channels-add-title"> | |||
Add Notification Channel | |||
</th> | |||
</tr> | |||
<tr> | |||
<form method="post" action="{% url 'hc-add-channel' %}"> | |||
<td> | |||
<select id="add-check-kind" class="form-control" name="kind"> | |||
<option value="email">Email</option> | |||
<option value="webhook">Webhook</option> | |||
<option value="pd">PagerDuty</option> | |||
</select> | |||
</td> | |||
<td class="form-inline"> | |||
{% csrf_token %} | |||
<input | |||
id="add-check-value" | |||
name="value" | |||
class="form-control" | |||
type="text" | |||
placeholder="[email protected]" /> | |||
<button type="submit" class="btn btn-success">Add</button> | |||
</td> | |||
<td> | |||
</td> | |||
</form> | |||
</tr> | |||
<tr> | |||
<td colspan="3" class="channels-add-help"> | |||
<p class="email"> | |||
Healthchecks.io will send an email to the specified | |||
address when a check goes | |||
<span class="word-up">up</span> or <span class="word-down">down</span>. | |||
</p> | |||
<p class="channels-help-hidden webhook"> | |||
Healthchecks.io will request the specified URL when | |||
a check goes | |||
<span class="word-down">down</span>. | |||
</p> | |||
<p class="channels-help-hidden pd"> | |||
Healthchecks.io will create an incident on PagerDuty when | |||
a check goes | |||
<span class="word-down">down</span> and will resolve it | |||
when same check goes <span class="word-up">up</span> | |||
</p> | |||
</td> | |||
</tr> | |||
</table> | |||
</div> | |||
</div> | |||
<div id="checks-modal" class="modal"> | |||
<div class="modal-dialog"> | |||
<div class="modal-content"> | |||
</div> | |||
</form> | |||
</div> | |||
</div> | |||
{% endblock %} | |||
{% block scripts %} | |||
{% compress js %} | |||
<script src="{% static 'js/jquery-2.1.4.min.js' %}"></script> | |||
<script src="{% static 'js/bootstrap.min.js' %}"></script> | |||
<script src="{% static 'js/channels.js' %}"></script> | |||
{% endcompress %} | |||
{% endblock %} |