diff --git a/hc/api/admin.py b/hc/api/admin.py index 8e27cd91..10579deb 100644 --- a/hc/api/admin.py +++ b/hc/api/admin.py @@ -28,7 +28,7 @@ class ChecksAdmin(admin.ModelAdmin): } search_fields = ["name", "user__email"] - list_display = ("id", "name", "created", "code", "status", "email", + list_display = ("id", "name_tags", "created", "code", "status", "email", "last_ping") list_select_related = ("user", ) list_filter = ("status", OwnershipListFilter, "last_ping") @@ -38,6 +38,13 @@ class ChecksAdmin(admin.ModelAdmin): def email(self, obj): return obj.user.email if obj.user else None + def name_tags(self, obj): + if not obj.tags: + return obj.name + + return "%s [%s]" % (obj.name, obj.tags) + + def send_alert(self, request, qs): for check in qs: check.send_alert() diff --git a/hc/api/migrations/0019_check_tags.py b/hc/api/migrations/0019_check_tags.py new file mode 100644 index 00000000..9359954a --- /dev/null +++ b/hc/api/migrations/0019_check_tags.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0018_remove_ping_body'), + ] + + operations = [ + migrations.AddField( + model_name='check', + name='tags', + field=models.CharField(max_length=500, blank=True), + ), + ] diff --git a/hc/api/models.py b/hc/api/models.py index d604870a..aa216d7e 100644 --- a/hc/api/models.py +++ b/hc/api/models.py @@ -42,6 +42,7 @@ class Check(models.Model): index_together = ["status", "user", "alert_after"] name = models.CharField(max_length=100, blank=True) + tags = models.CharField(max_length=500, blank=True) code = models.UUIDField(default=uuid.uuid4, editable=False, db_index=True) user = models.ForeignKey(User, blank=True, null=True) created = models.DateTimeField(auto_now_add=True) @@ -89,6 +90,9 @@ class Check(models.Model): channels = Channel.objects.filter(user=self.user) self.channel_set.add(*channels) + def tags_list(self): + return self.tags.split(" ") + class Ping(models.Model): owner = models.ForeignKey(Check) diff --git a/hc/front/forms.py b/hc/front/forms.py index 5cea3fc8..ca0c2ed1 100644 --- a/hc/front/forms.py +++ b/hc/front/forms.py @@ -2,6 +2,21 @@ from django import forms from hc.api.models import Channel +class NameTagsForm(forms.Form): + name = forms.CharField(max_length=100, required=False) + tags = forms.CharField(max_length=500, required=False) + + def clean_tags(self): + l = [] + + for part in self.cleaned_data["tags"].split(" "): + part = part.strip() + if part != "": + l.append(part) + + return " ".join(l) + + class TimeoutForm(forms.Form): timeout = forms.IntegerField(min_value=60, max_value=604800) grace = forms.IntegerField(min_value=60, max_value=604800) diff --git a/hc/front/tests/test_update_name.py b/hc/front/tests/test_update_name.py index 0316f471..3781fe4e 100644 --- a/hc/front/tests/test_update_name.py +++ b/hc/front/tests/test_update_name.py @@ -53,3 +53,13 @@ class UpdateNameTestCase(TestCase): self.client.login(username="alice", password="password") r = self.client.post(url, data=payload) assert r.status_code == 404 + + def test_it_sanitizes_tags(self): + url = "/checks/%s/name/" % self.check.code + payload = {"tags": " foo bar\r\t \n baz \n"} + + self.client.login(username="alice", password="password") + self.client.post(url, data=payload) + + check = Check.objects.get(id=self.check.id) + self.assertEqual(check.tags, "foo bar baz") diff --git a/hc/front/views.py b/hc/front/views.py index 66fbee40..dd5f4048 100644 --- a/hc/front/views.py +++ b/hc/front/views.py @@ -1,3 +1,4 @@ +from collections import Counter from datetime import timedelta as td from django.conf import settings @@ -10,17 +11,35 @@ from django.utils.six.moves.urllib.parse import urlencode from django.utils.crypto import get_random_string from hc.api.decorators import uuid_or_400 from hc.api.models import Channel, Check, Ping -from hc.front.forms import AddChannelForm, TimeoutForm +from hc.front.forms import AddChannelForm, NameTagsForm, TimeoutForm @login_required def my_checks(request): checks = Check.objects.filter(user=request.user).order_by("created") + counter = Counter() + down_tags, grace_tags = set(), set() + for check in checks: + status = check.get_status() + for tag in check.tags_list(): + if tag == "": + continue + + counter[tag] += 1 + + if status == "down": + down_tags.add(tag) + elif status == "grace": + grace_tags.add(tag) + ctx = { "page": "checks", "checks": checks, - "now": timezone.now() + "now": timezone.now(), + "tags": counter.most_common(), + "down_tags": down_tags, + "grace_tags": grace_tags } return render(request, "front/my_checks.html", ctx) @@ -89,11 +108,14 @@ def update_name(request, code): assert request.method == "POST" check = get_object_or_404(Check, code=code) - if check.user != request.user: + if check.user_id != request.user.id: return HttpResponseForbidden() - check.name = request.POST["name"] - check.save() + form = NameTagsForm(request.POST) + if form.is_valid(): + check.name = form.cleaned_data["name"] + check.tags = form.cleaned_data["tags"] + check.save() return redirect("hc-checks") diff --git a/static/css/bootstrap.css b/static/css/bootstrap.css index add848f8..ea9bcc6e 100644 --- a/static/css/bootstrap.css +++ b/static/css/bootstrap.css @@ -1517,7 +1517,7 @@ code, kbd, pre, samp { - font-family: Menlo, Monaco, Consolas, "Courier New", monospace; + font-family: "Lucida Console", Monaco, monospace; } code { padding: 2px 4px; diff --git a/static/css/my_checks.css b/static/css/my_checks.css index 1b056701..6f0827ab 100644 --- a/static/css/my_checks.css +++ b/static/css/my_checks.css @@ -56,3 +56,8 @@ .update-timeout-terms span { font-weight: bold; } + +.label-tag { + background-color: #eee; + color: #555; +} diff --git a/static/css/my_checks_desktop.css b/static/css/my_checks_desktop.css index 1aa8eb82..b1f0f6d1 100644 --- a/static/css/my_checks_desktop.css +++ b/static/css/my_checks_desktop.css @@ -2,6 +2,11 @@ margin-top: 36px; } +#checks-table .checks-row:hover { + background-color: #f5f5f5; +} + + .my-checks-name.unnamed { color: #999; font-style: italic; diff --git a/static/js/checks.js b/static/js/checks.js index 0abbb548..e5dd286a 100644 --- a/static/js/checks.js +++ b/static/js/checks.js @@ -79,6 +79,7 @@ $(function () { $("#update-name-form").attr("action", $this.data("url")); $("#update-name-input").val($this.data("name")); + $("#update-tags-input").val($this.data("tags")); $('#update-name-modal').modal("show"); $("#update-name-input").focus(); @@ -122,5 +123,45 @@ $(function () { $(".my-checks-email").show(); }); + $("#my-checks-tags button").click(function() { + // .active has not been updated yet by bootstrap code, + // so cannot use it + $(this).toggleClass('checked'); + + // Make a list of currently checked tags: + var checked = []; + $("#my-checks-tags button.checked").each(function(index, el) { + checked.push(el.textContent); + }); + + // No checked tags: show all + if (checked.length == 0) { + $("#checks-table tr.checks-row").show(); + $("#checks-list > li").show(); + return; + } + + function applyFilters(index, element) { + var tags = $(".my-checks-name", element).data("tags").split(" "); + for (var i=0, tag; tag=checked[i]; i++) { + if (tags.indexOf(tag) == -1) { + $(element).hide(); + return; + } + } + + $(element).show(); + } + + // Desktop: for each row, see if it needs to be shown or hidden + $("#checks-table tr.checks-row").each(applyFilters); + // Mobile: for each list item, see if it needs to be shown or hidden + $("#checks-list > li").each(applyFilters); + + }); + + + + }); \ No newline at end of file diff --git a/stuff/bootstrap/variables.less b/stuff/bootstrap/variables.less index 2b3d9033..1be8320a 100755 --- a/stuff/bootstrap/variables.less +++ b/stuff/bootstrap/variables.less @@ -45,7 +45,8 @@ @font-family-sans-serif: "Open Sans", Arial, sans-serif; @font-family-serif: Georgia, "Times New Roman", Times, serif; //** Default monospace fonts for ``, ``, and `
`.
-@font-family-monospace:   Menlo, Monaco, Consolas, "Courier New", monospace;
+@font-family-monospace:   "Lucida Console", Monaco, monospace;
+
 @font-family-base:        @font-family-sans-serif;
 
 @font-size-base:          14px;
diff --git a/templates/front/my_checks.html b/templates/front/my_checks.html
index 7b840e1a..a157cdf3 100644
--- a/templates/front/my_checks.html
+++ b/templates/front/my_checks.html
@@ -7,7 +7,27 @@
 {% block content %}
 
-

My Checks

+

My Checks

+
+ {% if tags %} +
+ {% for tag, count in tags %} + {% if tag in down_tags %} + + {% elif tag in grace_tags %} + + {% else %} + + {% endif %} + {% endfor %} +
+ {% endif %} + +
+
+
+ + {% if checks %} {% include "front/my_checks_mobile.html" %} {% include "front/my_checks_desktop.html" %} @@ -28,23 +48,53 @@