diff --git a/hc/api/models.py b/hc/api/models.py index 414d2824..0b38354f 100644 --- a/hc/api/models.py +++ b/hc/api/models.py @@ -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) diff --git a/hc/front/tests/test_channel_checks.py b/hc/front/tests/test_channel_checks.py index 63474c48..a9f09726 100644 --- a/hc/front/tests/test_channel_checks.py +++ b/hc/front/tests/test_channel_checks.py @@ -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 diff --git a/hc/front/urls.py b/hc/front/urls.py index dfd2779a..819fc821 100644 --- a/hc/front/urls.py +++ b/hc/front/urls.py @@ -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//enabled', views.switch_channel, name="hc-switch-channel"), path('pings//', views.ping_details, name="hc-ping-details"), ] diff --git a/hc/front/views.py b/hc/front/views.py index 3fc212f2..9b491fa1 100644 --- a/hc/front/views.py +++ b/hc/front/views.py @@ -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: diff --git a/static/css/base.css b/static/css/base.css index a8e277ac..e19b5cc9 100644 --- a/static/css/base.css +++ b/static/css/base.css @@ -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; -} \ No newline at end of file +} diff --git a/static/css/my_checks_desktop.css b/static/css/my_checks_desktop.css index 37fd4e18..58ff8031 100644 --- a/static/css/my_checks_desktop.css +++ b/static/css/my_checks_desktop.css @@ -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; +} diff --git a/static/css/my_checks_mobile.css b/static/css/my_checks_mobile.css index 95a82d79..6ca7e0fa 100644 --- a/static/css/my_checks_mobile.css +++ b/static/css/my_checks_mobile.css @@ -56,4 +56,8 @@ .label-down { background-color: #d9534f; -} \ No newline at end of file +} + +#checks-list .base { + color: #888; +} diff --git a/static/js/checks.js b/static/js/checks.js index 2974ef68..a2e6b5eb 100644 --- a/static/js/checks.js +++ b/static/js/checks.js @@ -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, diff --git a/templates/base.html b/templates/base.html index 1f71e787..6567b73d 100644 --- a/templates/base.html +++ b/templates/base.html @@ -49,7 +49,7 @@