@ -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 django import forms | ||||
from hc.api.models import Channel | |||||
class TimeoutForm(forms.Form): | class TimeoutForm(forms.Form): | ||||
timeout = forms.IntegerField(min_value=60, max_value=604800) | timeout = forms.IntegerField(min_value=60, max_value=604800) | ||||
grace = 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 %} |