Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a status dashboard #31

Merged
merged 8 commits into from
Jun 30, 2015
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion runtests.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,4 @@ def run_tests(*test_args):


if __name__ == '__main__':
run_tests(*sys.argv[1:])
run_tests(*sys.argv[1:])
7 changes: 7 additions & 0 deletions tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,3 +118,10 @@ def test_response_404_when_none_specified(self):

def tearDown(self):
pass


class TestWatchmanDashboard(unittest.TestCase):
def test_dashboard_response_code(self):
request = RequestFactory().get('/')
response = views.dashboard(request)
self.assertEqual(response.status_code, 200)
87 changes: 87 additions & 0 deletions watchman/templates/watchman/dashboard.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
{% load i18n %}

<!DOCTYPE html>
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
<script src="//code.jquery.com/jquery-2.1.4.min.js"></script>
<script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apparently we should probably just hardcode these to https://: http://www.paulirish.com/2010/the-protocol-relative-url/

Now that SSL is encouraged for everyone and doesn’t have performance concerns, this technique is now an anti-pattern. If the asset you need is available on SSL, then always use the https:// asset.

Allowing the snippet to request over HTTP opens the door for attacks like the recent Github Man-on-the-side attack. It’s always safe to request HTTPS assets even if your site is on HTTP, however the reverse is not true.

</head>
<body>
{% block content %}
<div class="container">
<h2>{% trans 'System Status' %}</h2>
<h3 class="{% if overall_status %}text-success{% else %}text-danger{% endif %}">
{% if overall_status %}
<span class="glyphicon glyphicon-ok"></span> {% trans 'All systems up and running!' %}
{% else %}
<span class="glyphicon glyphicon-alert"></span> {% trans 'WARNING - Some systems down!' %}
{% endif %}{# overall_status #}
</h3>
<small></small>

<table class="table table-bordered table-hover">
<thead>
<tr>
<th>{% trans 'Type' %}</th>
<th>{% trans 'Name' %}</th>
<th>{% trans 'Status' %}</th>
</tr>
</thead>
<tbody>
{% for type in checks %}
{% for thing in type.statuses %}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe: {% for status in type.statuses %}?

<tr class="{% if type.ok %}success{% else %}danger{% endif %}">
<td>{{ type.type|title }}</td>
<td>{{ thing.name|title }}</td>

{% if thing.ok %}
<td class="success">{% trans 'OK' %}</td>

{% else %}
<td class="danger">
<button type="button" class="btn btn-danger" data-toggle="modal" data-target="#{{ type.type }}{% if thing.name %}-{{ thing.name }}{% endif %}">{% trans 'ERROR!' %}</button>
</td>
{% endif %}{# thing.ok #}

</tr>
{% endfor %}{# for thing in type.statuses #}
{% empty %}
<tr>
<td colspan="3">{% trans 'No checks indicated.' %}</td>
</tr>
{% endfor %}{# for type in checks #}
</tbody>
</table>

{% if not overall_status %}<span class="pull-right"><small>{% trans 'Click an error button to see the full traceback' %}</small></span>{% endif %}{# overall_status #}
</div>
{% endblock %}{# status_content #}


{% for type in checks %}
{% for thing in type.statuses %}
{% if not thing.ok %}
<div class="modal fade" id="{{ type.type }}{% if thing.name %}-{{ thing.name }}{% endif %}" tabindex="-1" role="dialog" aria-labelledby="{{ type.type }}{% if thing.name %}-{{ thing.name }}{% endif %}-title">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title" id="{{ type.type }}{% if thing.name %}-{{ thing.name }}{% endif %}-title">{{ type.type|title }}{% if thing.name %} - {{ thing.name|title }}{% endif %}</h4>
</div><!-- class="modal-header" -->
<div class="modal-body">
<h4><pre>{{ thing.error }}</pre></h4>
<pre>{{ thing.traceback }}</pre>
</div><!-- class="model-body" -->
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</div><!-- class="modal-footer" -->
</div><!-- class="modal-content" -->
</div><!-- class="modal-dialog" -->
</div><!-- class="modal fade" -->
{% endif %}{# not thing.ok #}
{% endfor %}{# for type in checks #}
{% endfor %}{# for thing in type.statuses #}
</body>
</html>
1 change: 1 addition & 0 deletions watchman/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@
urlpatterns = patterns(
'',
url(r'^$', 'watchman.views.status', name="status"),
url(r'^dashboard/$', 'watchman.views.dashboard', name="dashboard"),
)
85 changes: 84 additions & 1 deletion watchman/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
from __future__ import unicode_literals

from django.http import Http404
from django.shortcuts import render
from django.utils.translation import ugettext as _

from jsonview.decorators import json_view
from watchman.decorators import token_required
Expand All @@ -28,6 +30,87 @@ def status(request):
response.update(check())

if len(response) == 0:
raise Http404('No checks found')
raise Http404(_('No checks found'))

return response


@token_required
def dashboard(request):
check_types = []

for check in get_checks(None, None):
if callable(check):
_check = check()

for _type in _check:
# For other systems (eg: email, storage) _check[_type] is a
# dictionary of status
#
# Example:
# {
# 'ok': True, # Status
# }
#
# Example:
# {
# 'ok': False, # Status
# 'error': "RuntimeError",
# 'stacktrace': "...",
# }
#
# For some systems (eg: cache, database) _check[_type] is a
# list of dictionaries of dictionaries of statuses
#
# Example:
# [
# {
# 'default': { # Cache/database name
# 'ok': True, # Status
# }
# },
# {
# 'non-default': { # Cache/database name
# 'ok': False, # Status
# 'error': "RuntimeError",
# 'stacktrace': "...",
# }
# },
# ]
#
statuses = []

if type(_check[_type]) == dict:
result = _check[_type]
statuses = [{
'name': '',
'ok': result['ok'],
'error': '' if result['ok'] else result['error'],
'stacktrace': '' if result['ok'] else result['stacktrace'],
}]
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice. 👍


type_overall_status = _check[_type]['ok']

elif type(_check[_type]) == list:
for result in _check[_type]:
for name in result:
statuses.append({
'name': name,
'ok': result[name]['ok'],
'error': '' if result[name]['ok'] else result[name]['error'],
'stacktrace': '' if result[name]['ok'] else result[name]['stacktrace'],
})

type_overall_status = all([s['ok'] for s in statuses])

check_types.append({
'type': _type,
'ok': type_overall_status,
'statuses': statuses})

overall_status = all([type_status['ok'] for type_status in check_types])

return render(request, 'watchman/dashboard.html', {
'checks': check_types,
'overall_status': overall_status
})