Browse Source

Badges

pull/72/head
Pēteris Caune 8 years ago
parent
commit
c15a4871c2
7 changed files with 192 additions and 41 deletions
  1. +15
    -0
      hc/accounts/views.py
  2. +1
    -0
      hc/api/urls.py
  3. +23
    -0
      hc/api/views.py
  4. +55
    -0
      hc/lib/badges.py
  5. +5
    -1
      static/css/settings.css
  6. +70
    -40
      templates/accounts/profile.html
  7. +23
    -0
      templates/badge.svg

+ 15
- 0
hc/accounts/views.py View File

@ -1,4 +1,5 @@
import uuid import uuid
import re
from django.contrib import messages from django.contrib import messages
from django.contrib.auth import login as auth_login from django.contrib.auth import login as auth_login
@ -15,6 +16,7 @@ from hc.accounts.forms import (EmailPasswordForm, InviteTeamMemberForm,
SetPasswordForm, TeamNameForm) SetPasswordForm, TeamNameForm)
from hc.accounts.models import Profile, Member from hc.accounts.models import Profile, Member
from hc.api.models import Channel, Check from hc.api.models import Channel, Check
from hc.lib.badges import get_badge_url
def _make_user(email): def _make_user(email):
@ -186,7 +188,20 @@ def profile(request):
profile.save() profile.save()
messages.info(request, "Team Name updated!") messages.info(request, "Team Name updated!")
tags = set()
for check in Check.objects.filter(user=request.team.user):
tags.update(check.tags_list())
username = request.team.user.username
badge_urls = []
for tag in sorted(tags, key=lambda s: s.lower()):
if not re.match("^[\w-]+$", tag):
continue
badge_urls.append(get_badge_url(username, tag))
ctx = { ctx = {
"badge_urls": badge_urls,
"profile": profile, "profile": profile,
"show_api_key": show_api_key "show_api_key": show_api_key
} }


+ 1
- 0
hc/api/urls.py View File

@ -6,4 +6,5 @@ urlpatterns = [
url(r'^ping/([\w-]+)/$', views.ping, name="hc-ping-slash"), url(r'^ping/([\w-]+)/$', views.ping, name="hc-ping-slash"),
url(r'^ping/([\w-]+)$', views.ping, name="hc-ping"), url(r'^ping/([\w-]+)$', views.ping, name="hc-ping"),
url(r'^api/v1/checks/$', views.create_check), url(r'^api/v1/checks/$', views.create_check),
url(r'^badge/([\w-]+)/([\w-]{8})/([\w-]+).svg$', views.badge, name="hc-badge"),
] ]

+ 23
- 0
hc/api/views.py View File

@ -9,6 +9,7 @@ from django.views.decorators.csrf import csrf_exempt
from hc.api import schemas from hc.api import schemas
from hc.api.decorators import check_api_key, uuid_or_400, validate_json from hc.api.decorators import check_api_key, uuid_or_400, validate_json
from hc.api.models import Check, Ping from hc.api.models import Check, Ping
from hc.lib.badges import check_signature, get_badge_svg
@csrf_exempt @csrf_exempt
@ -84,3 +85,25 @@ def create_check(request):
return HttpResponse(status=405) return HttpResponse(status=405)
return JsonResponse(response, status=code) return JsonResponse(response, status=code)
@never_cache
def badge(request, username, signature, tag):
if not check_signature(username, tag, signature):
return HttpResponseBadRequest()
status = "up"
q = Check.objects.filter(user__username=username, tags__contains=tag)
for check in q:
if tag not in check.tags_list():
continue
if status == "up" and check.in_grace_period():
status = "late"
if check.get_status() == "down":
status = "down"
break
svg = get_badge_svg(tag, status)
return HttpResponse(svg, content_type="image/svg+xml")

+ 55
- 0
hc/lib/badges.py View File

@ -0,0 +1,55 @@
from django.conf import settings
from django.core.signing import base64_hmac
from django.template.loader import render_to_string
from django.core.urlresolvers import reverse
WIDTHS = {"a": 7, "b": 7, "c": 6, "d": 7, "e": 6, "f": 4, "g": 7, "h": 7,
"i": 3, "j": 3, "k": 7, "l": 3, "m": 10, "n": 7, "o": 7, "p": 7,
"q": 7, "r": 4, "s": 6, "t": 5, "u": 7, "v": 7, "w": 9, "x": 6,
"y": 7, "z": 7, "0": 7, "1": 6, "2": 7, "3": 7, "4": 7, "5": 7,
"6": 7, "7": 7, "8": 7, "9": 7, "A": 8, "B": 7, "C": 8, "D": 8,
"E": 7, "F": 6, "G": 9, "H": 8, "I": 3, "J": 4, "K": 7, "L": 6,
"M": 10, "N": 8, "O": 9, "P": 6, "Q": 9, "R": 7, "S": 7, "T": 7,
"U": 8, "V": 8, "W": 11, "X": 7, "Y": 7, "Z": 7, "-": 4, "_": 6}
COLORS = {
"up": "#4c1",
"late": "#fe7d37",
"down": "#e05d44"
}
def get_width(s):
total = 0
for c in s:
total += WIDTHS.get(c, 7)
return total
def get_badge_svg(tag, status):
w1 = get_width(tag) + 10
w2 = get_width(status) + 10
ctx = {
"width": w1 + w2,
"tag_width": w1,
"status_width": w2,
"tag_center_x": w1 / 2,
"status_center_x": w1 + w2 / 2,
"tag": tag,
"status": status,
"color": COLORS[status]
}
return render_to_string("badge.svg", ctx)
def check_signature(username, tag, sig):
ours = base64_hmac(str(username), tag, settings.SECRET_KEY)
ours = ours[:8].decode("utf-8")
return ours == sig
def get_badge_url(username, tag):
sig = base64_hmac(str(username), tag, settings.SECRET_KEY)
url = reverse("hc-badge", args=[username, sig[:8], tag])
return settings.SITE_ROOT + url

+ 5
- 1
static/css/settings.css View File

@ -1,4 +1,4 @@
#settings-title {
.settings-title {
padding-bottom: 24px; padding-bottom: 24px;
} }
@ -9,4 +9,8 @@
.settings-block h2 { .settings-block h2 {
margin: 0; margin: 0;
padding-bottom: 24px; padding-bottom: 24px;
}
#badges-description {
margin-bottom: 24px;
} }

+ 70
- 40
templates/accounts/profile.html View File

@ -7,7 +7,7 @@
{% block content %} {% block content %}
<div class="row"> <div class="row">
<div class="col-sm-12"> <div class="col-sm-12">
<h1 id="settings-title">Settings</h1>
<h1 class="settings-title">Settings</h1>
</div> </div>
{% if messages %} {% if messages %}
<div class="col-sm-12"> <div class="col-sm-12">
@ -57,45 +57,6 @@
</div> </div>
</div> </div>
<div class="col-sm-6">
<div class="panel panel-default">
<div class="panel-body settings-block">
<h2>API Access</h2>
{% if profile.api_key %}
{% if show_api_key %}
API key: <code>{{ profile.api_key }}</code>
<button
data-toggle="modal"
data-target="#revoke-api-key-modal"
class="btn btn-danger pull-right">Revoke</button>
{% else %}
<span class="text-success glyphicon glyphicon-ok"></span>
API access is enabled.
<form method="post">
{% csrf_token %}
<button
type="submit"
name="show_api_key"
class="btn btn-default pull-right">Show API key</button>
</form>
{% endif %}
{% else %}
<span class="glyphicon glyphicon-remove"></span>
API access is disabled.
<form method="post">
{% csrf_token %}
<button
type="submit"
name="create_api_key"
class="btn btn-default pull-right">Create API key</button>
</form>
{% endif %}
</div>
</div>
</div>
<div class="col-sm-6"> <div class="col-sm-6">
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-body settings-block"> <div class="panel-body settings-block">
@ -154,7 +115,76 @@
</div> </div>
</div> </div>
</div> </div>
<div class="col-sm-6">
<div class="panel panel-default">
<div class="panel-body settings-block">
<h2>API Access</h2>
{% if profile.api_key %}
{% if show_api_key %}
API key: <code>{{ profile.api_key }}</code>
<button
data-toggle="modal"
data-target="#revoke-api-key-modal"
class="btn btn-danger pull-right">Revoke</button>
{% else %}
<span class="text-success glyphicon glyphicon-ok"></span>
API access is enabled.
<form method="post">
{% csrf_token %}
<button
type="submit"
name="show_api_key"
class="btn btn-default pull-right">Show API key</button>
</form>
{% endif %}
{% else %}
<span class="glyphicon glyphicon-remove"></span>
API access is disabled.
<form method="post">
{% csrf_token %}
<button
type="submit"
name="create_api_key"
class="btn btn-default pull-right">Create API key</button>
</form>
{% endif %}
</div>
</div>
</div>
</div>
{% if badge_urls %}
<div class="row">
<div class="col-sm-12">
<div class="panel panel-default">
<div class="panel-body settings-block">
<h2 class="settings-title">Status Badges</h2>
<p id="badges-description">
Here are status badges for each of the tags you have used. The
badges have public, but hard-to-guess URLs. If you wish, you can
add them to your READMEs, dashboards or status pages.
</p>
<table class="badges table">
{% for badge_url in badge_urls %}
<tr>
<td>
<img src="{{ badge_url }}" alt="" />
</td>
<td>
<code>{{ badge_url }}</code>
</td>
</tr>
{% endfor %}
</table>
</div>
</div>
</div>
</div> </div>
{% endif %}
<div id="revoke-api-key-modal" class="modal"> <div id="revoke-api-key-modal" class="modal">


+ 23
- 0
templates/badge.svg View File

@ -0,0 +1,23 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="{{ width }}" height="20">
<linearGradient id="smooth" x2="0" y2="100%">
<stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
<stop offset="1" stop-opacity=".1"/>
</linearGradient>
<mask id="round">
<rect width="{{ width }}" height="20" rx="3" fill="#fff"/>
</mask>
<g mask="url(#round)">
<rect width="{{ tag_width }}" height="20" fill="#555"/>
<rect x="{{ tag_width }}" width="{{ status_width }}" height="20" fill="{{ color }}"/>
<rect width="{{ width }}" height="20" fill="url(#smooth)"/>
</g>
<g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11">
<text x="{{ tag_center_x }}" y="15" fill="#010101" fill-opacity=".3">{{ tag }}</text>
<text x="{{ tag_center_x }}" y="14">{{ tag }}</text>
<text x="{{ status_center_x }}" y="15" fill="#010101" fill-opacity=".3">{{ status }}</text>
<text x="{{ status_center_x }}" y="14">{{ status }}</text>
</g>
</svg>

Loading…
Cancel
Save