Toggle integrations on/off on "My Checks" page.

This commit is contained in:
Pēteris Caune 2018-06-10 23:19:25 +03:00
parent 0b3030311c
commit 83a2ff17e6
No known key found for this signature in database
GPG Key ID: E28D7679E9A9EDE2
13 changed files with 123 additions and 65 deletions

View File

@ -240,6 +240,23 @@ class Channel(models.Model):
email_verified = models.BooleanField(default=False)
checks = models.ManyToManyField(Check)
def __str__(self):
if self.kind == "email":
return "Email to %s" % self.value
elif self.kind == "sms":
if self.sms_label:
return "SMS to %s" % self.sms_label
return "SMS to %s" % self.sms_number
elif self.kind == "slack":
return "Slack %s" % self.slack.channel
elif self.kind == "telegram":
return "Telegram %s" % self.telegram_name
return self.get_kind_display()
def icon_url(self):
return settings.STATIC_URL + "img/integrations/%s.png" % self.kind
def assign_all_checks(self):
checks = Check.objects.filter(user=self.user)
self.checks.add(*checks)

View File

@ -15,7 +15,7 @@ class ChannelChecksTestCase(BaseTestCase):
self.client.login(username="alice@example.org", password="password")
r = self.client.get(url)
self.assertContains(r, "Assign Checks to Channel", status_code=200)
self.assertContains(r, "Assign Checks to Integration", status_code=200)
def test_team_access_works(self):
url = "/integrations/%s/checks/" % self.channel.code
@ -24,7 +24,7 @@ class ChannelChecksTestCase(BaseTestCase):
# should work.
self.client.login(username="bob@example.org", password="password")
r = self.client.get(url)
self.assertContains(r, "Assign Checks to Channel", status_code=200)
self.assertContains(r, "Assign Checks to Integration", status_code=200)
def test_it_checks_owner(self):
# channel does not belong to mallory so this should come back

View File

@ -9,6 +9,7 @@ check_urls = [
path('remove/', views.remove_check, name="hc-remove-check"),
path('log/', views.log, name="hc-log"),
path('last_ping/', views.ping_details, name="hc-last-ping"),
path('channels/<uuid:channel_code>/enabled', views.switch_channel, name="hc-switch-channel"),
path('pings/<int:n>/', views.ping_details, name="hc-ping-details"),
]

View File

@ -59,16 +59,20 @@ def my_checks(request):
request.profile.sort = request.GET["sort"]
request.profile.save()
checks = list(Check.objects.filter(user=request.team.user))
checks = list(Check.objects.filter(user=request.team.user).prefetch_related("channel_set"))
sortchecks(checks, request.profile.sort)
tags_statuses, num_down = _tags_statuses(checks)
pairs = list(tags_statuses.items())
pairs.sort(key=lambda pair: pair[0].lower())
channels = Channel.objects.filter(user=request.team.user)
channels = list(channels.order_by("created"))
ctx = {
"page": "checks",
"checks": checks,
"channels": channels,
"num_down": num_down,
"now": timezone.now(),
"tags": pairs,
@ -104,6 +108,25 @@ def status(request):
})
@login_required
@require_POST
def switch_channel(request, code, channel_code):
check = get_object_or_404(Check, code=code)
if check.user_id != request.team.user.id:
return HttpResponseForbidden()
channel = get_object_or_404(Channel, code=channel_code)
if channel.user_id != request.team.user.id:
return HttpResponseForbidden()
if request.POST.get("state") == "on":
channel.checks.add(check)
else:
channel.checks.remove(check)
return HttpResponse()
def _welcome_check(request):
check = None
if "welcome_code" in request.session:

View File

@ -48,11 +48,15 @@ body {
}
}
.navbar-text {
font-size: small;
}
.page-checks .container-fluid {
/* Fluid below 1320px, but max width capped to 1320px ... */
max-width: 1320px;
}
.status {
font-size: 24px;
}
@ -86,4 +90,4 @@ pre {
.jumbotron p {
font-weight: 300;
}
}

View File

@ -11,11 +11,6 @@
font-style: italic;
}
table.table tr > th.th-name {
padding-left: 21px;
}
#checks-table .indicator-cell {
text-align: center;
}
@ -24,10 +19,6 @@ table.table tr > th.th-name {
border-top: 0;
}
#checks-table th {
border-top: 0;
}
#checks-table a.default {
color: #333;
}
@ -41,26 +32,33 @@ table.table tr > th.th-name {
border-top: 1px solid #f1f1f1;
}
#checks-table .my-checks-name {
#checks-table .my-checks-name,
#checks-table .integrations,
#checks-table .timeout-grace,
#checks-table .last-ping,
#checks-table .last-ping-never {
border: 1px solid rgba(0, 0, 0, 0);
padding: 6px;
display: block;
}
#checks-table tr:hover .my-checks-name {
#checks-table tr:hover .my-checks-name,
#checks-table tr:hover .integrations,
#checks-table tr:hover .timeout-grace,
#checks-table tr:hover .last-ping {
border: 1px dotted #AAA;
cursor: pointer;
}
#checks-table > tbody > tr > th.th-name,
#checks-table > tbody > tr > th.th-integrations,
#checks-table > tbody > tr > th.th-period,
#checks-table > tbody > tr > th.th-last-ping {
padding-left: 15px;
}
#checks-table .timeout-grace {
border: 1px solid rgba(0, 0, 0, 0);
padding: 6px;
display: block;
#checks-table .integrations img {
padding: 10px 2px;
width: 24px;
}
.timeout-grace .cron-expression {
@ -71,26 +69,6 @@ table.table tr > th.th-name {
max-width: 120px;
}
#checks-table tr:hover .timeout-grace {
border: 1px dotted #AAA;
cursor: pointer;
}
#checks-table .last-ping {
border: 1px solid rgba(0, 0, 0, 0);
padding: 6px;
}
#checks-table .last-ping-never {
padding: 7px;
}
#checks-table tr:hover .last-ping {
border: 1px dotted #AAA;
cursor: pointer;
}
.checks-subline {
color: #888;
white-space: nowrap;
@ -139,7 +117,9 @@ tr:hover .copy-link {
opacity: 1
}
#checks-table .url-cell {
#checks-table .url-cell,
#checks-table .integrations-cell,
#checks-table .timeout-cell {
white-space: nowrap;
}
@ -147,7 +127,6 @@ tr:hover .copy-link {
color: #888;
}
.my-checks-url {
font-family: "Lucida Console", Monaco, monospace;
font-size: 11.7px;
@ -156,3 +135,7 @@ tr:hover .copy-link {
color: #333;
}
.integrations-cell .off {
filter: grayscale(100%);
opacity: 0.3;
}

View File

@ -56,4 +56,8 @@
.label-down {
background-color: #d9534f;
}
}
#checks-list .base {
color: #888;
}

View File

@ -175,6 +175,19 @@ $(function () {
return false;
});
$(".integrations img").click(function() {
var isOff = $(this).toggleClass("off").hasClass("off");
var token = $('input[name=csrfmiddlewaretoken]').val();
$.ajax({
url: this.dataset.url,
type: "post",
headers: {"X-CSRFToken": token},
data: {"state": isOff ? "off" : "on"}
});
return false;
});
$(".last-ping-cell").on("click", ".last-ping", function() {
$("#ping-details-body").text("Updating...");
$('#ping-details-modal').modal("show");
@ -192,6 +205,7 @@ $(function () {
return false;
});
// Filtering by tags
$("#my-checks-tags button").click(function() {
// .active has not been updated yet by bootstrap code,

View File

@ -49,7 +49,7 @@
</head>
<body class="page-{{ page }}">
<nav class="navbar navbar-default">
<div class="container">
<div class="container{% if page == "checks" %}-fluid{% endif %}">
<div class="navbar-header">
<button
type="button"
@ -158,13 +158,13 @@
</nav>
{% block containers %}
<div class="container">
<div class="container{% if page == "checks" %}-fluid{% endif %}">
{% block content %}{% endblock %}
</div>
{% endblock %}
<footer class="footer">
<div class="container">
<div class="container{% if page == "checks" %}-fluid{% endif %}">
<ul>
<li>
Powered by Healthchecks open-source project

View File

@ -5,7 +5,7 @@
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">&times;</button>
<h4 class="update-timeout-title">Assign Checks to Channel</h4>
<h4 class="update-timeout-title">Assign Checks to Integration</h4>
</div>
<input type="hidden" name="channel" value="{{ channel.code }}" />

View File

@ -5,15 +5,6 @@
{% block content %}
<div class="row">
<div class="col-sm-12">
<h1>
{% if request.team == request.user.profile %}
My Checks
{% else %}
{{ request.team.team_name }}
{% endif %}
</h1>
</div>
{% if tags %}
<div id="my-checks-tags" class="col-sm-12">
{% for tag, status in tags %}
@ -99,7 +90,7 @@
class="form-control" />
<span class="help-block">
Optionally, assign tags for easy filtering.
Use tags for easy filtering and for status badges.
Separate multiple tags with spaces.
</span>
</div>

View File

@ -1,5 +1,5 @@
{% load hc_extras %}
<table id="checks-table" class="table hidden-xs">
{% load hc_extras staticfiles %}
<table id="checks-table" class="table hidden-xs hidden-sm">
<tr>
<th></th>
<th class="th-name">
@ -18,6 +18,7 @@
{% endif %}
</th>
<th>Ping URL</th>
<th class="th-integrations">Integrations</th>
<th class="th-period">
Period <br />
<span class="checks-subline">Grace</span>
@ -69,8 +70,26 @@
copy
</button>
</td>
<td class="integrations-cell">
{% if channels|length < 8 %}
<div class="integrations">
{% spaceless %}
{% for channel in channels %}
<img
data-toggle="tooltip"
data-url="{% url 'hc-switch-channel' check.code channel.code %}"
title="{{ channel }}"
src="{{ channel.icon_url }}"
{% if channel in check.channel_set.all %}{% else %}class="off"{% endif %} />
{% endfor %}
{% endspaceless %}
</div>
{% else %}
{{ check.channel_set.all|length }} of {{ channels|length }}
{% endif %}
</td>
<td class="timeout-cell">
<span
<div
data-url="{% url 'hc-update-timeout' check.code %}"
data-kind="{{ check.kind }}"
data-timeout="{{ check.timeout.total_seconds }}"
@ -87,7 +106,7 @@
<span class="checks-subline">
{{ check.grace|hc_duration }}
</span>
</span>
</div>
</td>
<td id="lpd-{{ check.code}}" class="last-ping-cell">
{% include "front/last_ping_cell.html" with check=check %}

View File

@ -1,6 +1,6 @@
{% load hc_extras humanize %}
<ul id="checks-list" class="visible-xs">
<ul id="checks-list" class="visible-xs visible-sm">
{% for check in checks %}
<li>
<h2>
@ -8,7 +8,9 @@
{{ check.name|default:"unnamed" }}
</span>
<code>{{ check.code }}</code>
<code>
<span class="base hidden-xs">{{ ping_endpoint }}</span>{{ check.code }}
</code>
</h2>
<a
@ -42,7 +44,7 @@
</td>
</tr>
{% endif %}
{% if check.kind == "simple " %}
{% if check.kind == "simple" %}
<tr>
<th>Period</th>
<td>{{ check.timeout|hc_duration }}</td>