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 re
from django.contrib import messages
from django.contrib.auth import login as auth_login
@ -15,6 +16,7 @@ from hc.accounts.forms import (EmailPasswordForm, InviteTeamMemberForm,
SetPasswordForm, TeamNameForm)
from hc.accounts.models import Profile, Member
from hc.api.models import Channel, Check
from hc.lib.badges import get_badge_url
def _make_user(email):
@ -186,7 +188,20 @@ def profile(request):
profile.save()
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 = {
"badge_urls": badge_urls,
"profile": profile,
"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"),
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.decorators import check_api_key, uuid_or_400, validate_json
from hc.api.models import Check, Ping
from hc.lib.badges import check_signature, get_badge_svg
@csrf_exempt
@ -84,3 +85,25 @@ def create_check(request):
return HttpResponse(status=405)
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;
}
@ -9,4 +9,8 @@
.settings-block h2 {
margin: 0;
padding-bottom: 24px;
}
#badges-description {
margin-bottom: 24px;
}

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

@ -7,7 +7,7 @@
{% block content %}
<div class="row">
<div class="col-sm-12">
<h1 id="settings-title">Settings</h1>
<h1 class="settings-title">Settings</h1>
</div>
{% if messages %}
<div class="col-sm-12">
@ -57,45 +57,6 @@
</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="panel panel-default">
<div class="panel-body settings-block">
@ -154,7 +115,76 @@
</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>
{% endif %}
<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