Add hiccup statistic page
diff --git a/crashreports/serializers.py b/crashreports/serializers.py
new file mode 100644
index 0000000..c551198
--- /dev/null
+++ b/crashreports/serializers.py
@@ -0,0 +1,10 @@
+from rest_framework import serializers
+from models import Crashreport
+from rest_framework import permissions
+
+class CrashReportSerializer(serializers.ModelSerializer):
+ permission_classes = (permissions.IsAuthenticated)
+ class Meta:
+ model = Crashreport
+ fields = ('pk','uuid', 'uptime', 'build_fingerprint', 'boot_reason',
+ 'power_on_reason', 'power_off_reason', 'aux_data', 'date')
diff --git a/crashreports/static/crashreports/style.css b/crashreports/static/crashreports/style.css
new file mode 100644
index 0000000..8d4af5f
--- /dev/null
+++ b/crashreports/static/crashreports/style.css
@@ -0,0 +1,28 @@
+h2 {
+ margin-top:60px;
+}
+h1 {
+ margin-top:50px;
+}
+
+#container {
+ margin: auto;
+ width: 90%;
+
+}
+
+div.chart {
+ margin: 5px;
+ width: 500px;
+ height: 500px;
+ float: left;
+ -webkit-box-shadow: 7px 10px 26px 0px rgba(0,0,0,0.15);
+ -moz-box-shadow: 7px 10px 26px 0px rgba(0,0,0,0.15);
+ box-shadow: 7px 10px 26px 0px rgba(0,0,0,0.15);
+}
+
+body {
+ font-family: sans-serif;
+ text-align: center;
+ background-color: #eee;
+}
diff --git a/crashreports/templates/crashreports/hiccup_stats.html b/crashreports/templates/crashreports/hiccup_stats.html
new file mode 100644
index 0000000..d390324
--- /dev/null
+++ b/crashreports/templates/crashreports/hiccup_stats.html
@@ -0,0 +1,159 @@
+<html>
+<head>
+ <meta charset="utf-8">
+ <META HTTP-EQUIV="refresh" CONTENT="300">
+ <link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/nvd3/1.8.2/nv.d3.min.css" charset="utf-8" >
+ <link rel="stylesheet" type="text/css" href="/static/crashreports/style.css" charset="utf-8" >
+
+ <script src="http://code.jquery.com/jquery-latest.min.js"></script>
+ <script src="https://d3js.org/d3.v3.min.js" charset="utf-8"></script>
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/nvd3/1.8.2/nv.d3.min.js" charset="utf-8"></script>
+ <script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
+
+ </head>
+
+ <body>
+ <h1> Hiccup Stats </h1>
+ <div id="container">
+ <div id = "all_fingerprints" class="chart">
+ </div>
+ <div id = "probable_crashes" class="chart">
+ </div >
+ <div id = "Crash_Distribution" class="chart">
+ </div >
+ <div id = "Crash_Distribution_crashes" class="chart">
+ </div >
+ <div id = "SMPL_chart" class="chart">
+ </div >
+ </div>
+ <script>
+
+ console.log("test");
+
+ // Load the Visualization API and the corechart package.
+ google.charts.load('current', {'packages':['corechart']});
+
+ // Set a callback to run when the Google Visualization API is loaded.
+ google.charts.setOnLoadCallback(drawChart);
+
+ function aggregate_build_fingerprints(data) {
+ totals = {}
+ data = clear_fake_crashes(data)
+ for (var entry in data) {
+ key = ""
+ if (data[entry].build_fingerprint.includes("a:user")) {
+ key = "DDRFIX";
+ } else {
+ key = "Normal"
+ }
+ if (!totals[key]) {
+ console.log(data[entry].build_fingerprint);
+ totals[key] = 0;
+ }
+ totals[key] += 1;
+ }
+ ret = []
+ for (var entry in totals) {
+ ret.push([entry, totals[entry]])
+ }
+ return ret;
+ }
+
+ function aggregate_uuid(data) {
+ data = clear_fake_crashes(data)
+ totals = {}
+ for (var entry in data) {
+ if (!totals[data[entry].uuid]) {
+ console.log(data[entry].uuid);
+ totals[data[entry].uuid] = 0;
+ }
+ totals[data[entry].uuid] += 1;
+ }
+ ret = []
+ for (var entry in totals) {
+ ret.push([entry, totals[entry]])
+ }
+ return ret;
+ }
+
+
+ function clear_fake_crashes(data) {
+ ret = {}
+ for (var entry in data) {
+ if (data[entry].boot_reason=="FAKECRASH") {
+ continue;
+ }
+ ret[entry]=data[entry];
+ }
+ return ret;
+ }
+
+ function aggregate_SMPL(data) {
+ totals = {}
+ data = clear_fake_crashes(data)
+ for (var entry in data) {
+ key = ""
+ if (data[entry].power_on_reason.includes("SMPL")) {
+ key = "SMPL";
+ } else if (data[entry].power_off_reason.includes("SOFT")) {
+ key = "Crash";
+ } else {
+ continue;
+ }
+ if (!totals[key]) {
+ console.log(data[entry].build_fingerprint);
+ totals[key] = 0;
+ }
+ totals[key] += 1;
+ }
+ ret = []
+ for (var entry in totals) {
+ ret.push([entry, totals[entry]])
+ }
+ return ret;
+ }
+
+ function drawChart() {
+ drawChartFromDate("2016-09-09","");
+ }
+
+ function drawChartFromDate(startDate,endDate) {
+ drawChartFingerprints("/hiccup/crashreports/?format=json&start_date="+startDate+"&end_date="+endDate, "All Crashreports", 'all_fingerprints',aggregate_build_fingerprints);
+ drawChartFingerprints("/hiccup/crashreports/?format=json&start_date="+startDate+"&end_date="+endDate+"&boot_reason=keyboard+power+on", "Probable Crashes", 'probable_crashes', aggregate_build_fingerprints);
+ drawChartFingerprints("/hiccup/crashreports/?format=json&start_date="+startDate+"&end_date="+endDate, "SMPL or Crash", 'SMPL_chart', aggregate_SMPL);
+ drawUuidHistogram("/hiccup/crashreports/?format=json&start_date="+startDate+"&end_date="+endDate, "Crash Distribution", 'Crash_Distribution');
+ drawUuidHistogram("/hiccup/crashreports/?format=json&start_date="+startDate+"&end_date="+endDate+"&boot_reason=keyboard+power+on", "Crash Distribution (probable Crashes)", 'Crash_Distribution_crashes');
+ }
+
+ function drawChartFingerprints(url, title, element, aggregate_function) { $.getJSON( url, function( data ) {
+ var totals_fingerprint = aggregate_function(data);
+ var options = {
+ title: title,
+ pieHole: 0.4,
+ };
+ var chart = new google.visualization.PieChart(document.getElementById(element));
+ var data = new google.visualization.DataTable();
+ data.addColumn('string', 'Build Fingerprint');
+ data.addColumn('number', 'Number of Crashreports');
+ data.addRows(totals_fingerprint);
+ chart.draw(data, options);
+ });
+ }
+
+ function drawUuidHistogram(url, title, element) { $.getJSON( url, function( data ) {
+ var totals_fingerprint = aggregate_uuid(data);
+ var options = {
+ title: title,
+ pieHole: 0.4,
+ };
+ var chart = new google.visualization.Histogram(document.getElementById(element));
+ var data = new google.visualization.DataTable();
+ data.addColumn('string', 'Build Fingerprint');
+ data.addColumn('number', 'Number of Crashreports');
+ data.addRows(totals_fingerprint);
+ chart.draw(data, options);
+ });
+ }
+</script>
+</body>
+</html>
diff --git a/crashreports/templates/registration/login.html b/crashreports/templates/registration/login.html
new file mode 100644
index 0000000..397eadf
--- /dev/null
+++ b/crashreports/templates/registration/login.html
@@ -0,0 +1,66 @@
+{% extends "admin/base_site.html" %}
+{% load i18n static %}
+
+{% block extrastyle %}{{ block.super }}<link rel="stylesheet" type="text/css" href="{% static "admin/css/login.css" %}" />
+{{ form.media }}
+{% endblock %}
+
+{% block bodyclass %}{{ block.super }} login{% endblock %}
+
+{% block usertools %}{% endblock %}
+
+{% block nav-global %}{% endblock %}
+
+{% block content_title %}{% endblock %}
+
+{% block breadcrumbs %}{% endblock %}
+
+{% block content %}
+{% if form.errors and not form.non_field_errors %}
+<p class="errornote">
+{% if form.errors.items|length == 1 %}{% trans "Please correct the error below." %}{% else %}{% trans "Please correct the errors below." %}{% endif %}
+</p>
+{% endif %}
+
+{% if form.non_field_errors %}
+{% for error in form.non_field_errors %}
+<p class="errornote">
+ {{ error }}
+</p>
+{% endfor %}
+{% endif %}
+
+<div id="content-main">
+
+{% if user.is_authenticated %}
+<p class="errornote">
+{% blocktrans trimmed %}
+ You are authenticated as {{ username }}, but are not authorized to
+ access this page. Would you like to login to a different account?
+{% endblocktrans %}
+</p>
+{% endif %}
+
+<form action="{{ app_path }}" method="post" id="login-form">{% csrf_token %}
+ <div class="form-row">
+ {{ form.username.errors }}
+ {{ form.username.label_tag }} {{ form.username }}
+ </div>
+ <div class="form-row">
+ {{ form.password.errors }}
+ {{ form.password.label_tag }} {{ form.password }}
+ <input type="hidden" name="next" value="{{ next }}" />
+ </div>
+ {% url 'admin_password_reset' as password_reset_url %}
+ {% if password_reset_url %}
+ <div class="password-reset-link">
+ <a href="{{ password_reset_url }}">{% trans 'Forgotten your password or username?' %}</a>
+ </div>
+ {% endif %}
+ <div class="submit-row">
+ <label> </label><input type="submit" value="{% trans 'Log in' %}" />
+ </div>
+</form>
+
+</div>
+{% endblock %}
diff --git a/crashreports/urls.py b/crashreports/urls.py
index 66ad6e1..e121032 100644
--- a/crashreports/urls.py
+++ b/crashreports/urls.py
@@ -1,7 +1,13 @@
-from django.conf.urls import url
+from django.conf.urls import url, include
from . import views
+from rest_framework import routers
+from rest_framework import filters
+
+router = routers.DefaultRouter()
+router.register(r'crashreports', views.CrashreportViewSet)
urlpatterns = [
url(r'^crashreport/', views.index, name='index'),
- url(r'^get_crash_statistic', views.get_crash_statistic, name='get_crash_statistic'),
- url(r'', views.empty, name='empty'),]
+ url(r'^crashreports/hiccup_stats/', views.hiccup_stats, name='home'),
+ url(r'^', include(router.urls)),
+]
diff --git a/crashreports/views.py b/crashreports/views.py
index 5beff69..54a09f0 100644
--- a/crashreports/views.py
+++ b/crashreports/views.py
@@ -12,10 +12,21 @@
from django.contrib.auth.decorators import login_required
from django.db.models import Count
+from rest_framework import viewsets
+from serializers import CrashReportSerializer
+from rest_framework.permissions import BasePermission
+from rest_framework import filters
+from rest_framework import generics
+import django_filters
+from django.template import loader
+
import datetime
import time
+from ratelimit.decorators import ratelimit
+
+@ratelimit( key='ip', rate='100/h')
@csrf_exempt
def index(request):
# Handle file upload`
@@ -40,19 +51,36 @@
else:
return HttpResponse(status=400)
else:
- return HttpResponse(status=400)
+ return HttpResponse(status=400)
+
+class IsCreationOrIsAuthenticated(BasePermission):
+ def has_permission(self, request, view):
+ if not request.user.is_authenticated():
+ if view.action == 'create':
+ return True
+ else:
+ return False
+ else:
+ return True
+
+class ListFilter(django_filters.Filter):
+ def filter(self, qs, value):
+ value_list = value.split(u',')
+ return super(ListFilter, self).filter(qs, django_filters.fields.Lookup(value_list, 'in'))
-def empty(request):
- return HttpResponse(status=204)
+class CrashreportFilter(filters.FilterSet):
+ start_date = django_filters.DateTimeFilter(name="date", lookup_expr='gte')
+ end_date = django_filters.DateTimeFilter(name="date", lookup_expr='lte')
+ boot_reason = ListFilter(name='boot_reason')
+ class Meta:
+ model = Crashreport
+ fields = ['build_fingerprint','boot_reason', 'power_on_reason', 'power_off_reason']
-@login_required
-def get_crash_statistic(request):
- from_date = request.GET.get('from_date', "2016-01-01")
- to_date = request.GET.get('to_date', "2017-01-01")
- entries = Crashreport.objects.filter(date__range=[from_date, to_date]).extra({'date_created' : "date(date)"}).values('date_created').annotate(created_count=Count('id'))
- for entry in entries:
- entry['date_created']=time.mktime(datetime.datetime.strptime(entry['date_created'], "%Y-%m-%d").timetuple())*1000
- return render_to_response('crashreports/json/crashreport_by_day.html/', {
- 'entries' : entries
- })
+
+class CrashreportViewSet(viewsets.ModelViewSet):
+ queryset = Crashreport.objects.all()
+ serializer_class = CrashReportSerializer
+ permission_classes = [IsCreationOrIsAuthenticated]
+ filter_backends = (filters.DjangoFilterBackend,)
+ filter_class = CrashreportFilter
diff --git a/hiccup/settings.py b/hiccup/settings.py
index 7727902..e5deb5b 100644
--- a/hiccup/settings.py
+++ b/hiccup/settings.py
@@ -40,6 +40,7 @@
'rest_framework',
'crashreports',
'psensor',
+ 'crispy_forms',
]
MIDDLEWARE_CLASSES = [
@@ -117,7 +118,8 @@
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
- )
+ ),
+ 'DEFAULT_FILTER_BACKENDS': ('rest_framework.filters.DjangoFilterBackend',)
}
# Static files (CSS, JavaScript, Images)
diff --git a/hiccup/urls.py b/hiccup/urls.py
index ad8e0c0..c2dbf41 100644
--- a/hiccup/urls.py
+++ b/hiccup/urls.py
@@ -1,9 +1,12 @@
from django.conf.urls import include, url
from django.contrib import admin
-
+from django.views.generic.base import RedirectView
+from django.contrib.auth import views as auth_views
urlpatterns = [
url(r'^hiccup/admin/', admin.site.urls),
url(r'^psensor/', include('psensor.urls')),
url(r'^hiccup/', include('crashreports.urls')),
+ url(r'^accounts/login/$', auth_views.login),
+ url(r'^.*$', RedirectView.as_view(url='https://fairphone.com', permanent=False), name='index')
]
diff --git a/hiccup/views.py b/hiccup/views.py
new file mode 100644
index 0000000..f5f4673
--- /dev/null
+++ b/hiccup/views.py
@@ -0,0 +1,74 @@
+# -*- coding: utf-8 -*-
+from django.shortcuts import render_to_response
+from django.shortcuts import render
+from django.http import HttpResponseRedirect
+from django.http import HttpResponse
+from django.core.urlresolvers import reverse
+
+from crashreports.models import Crashreport
+from crashreports.forms import CrashreportForm
+from django.views.decorators.csrf import csrf_exempt
+from django.http import Http404
+
+from django.contrib.auth.decorators import login_required
+from django.db.models import Count
+
+import datetime
+import time
+
+@csrf_exempt
+def index(request):
+ # Handle file upload`
+ if request.method == 'POST':
+ form = CrashreportForm(request.POST, request.FILES)
+ if form.is_valid():
+ print form.cleaned_data['uuid'];
+ print form.cleaned_data['aux_data'];
+ print form.cleaned_data['boot_reason'];
+ print form.cleaned_data['build_fingerprint'];
+ print form.cleaned_data['date'];
+ new_cr = Crashreport(uuid=form.cleaned_data['uuid'],
+ aux_data=form.cleaned_data['aux_data'],
+ uptime=form.cleaned_data['uptime'],
+ boot_reason=form.cleaned_data['boot_reason'],
+ power_on_reason=form.cleaned_data['power_on_reason'],
+ power_off_reason=form.cleaned_data['power_off_reason'],
+ build_fingerprint=form.cleaned_data['build_fingerprint'],
+ date= form.cleaned_data['date'])
+ try:
+ new_cr.crashreport_file = request.FILES['crashreport']
+ except:
+ new_cr.crashreport_file = None
+ new_cr.save()
+ # Redirect to the document list after POST
+ return HttpResponse(status=204)
+ else:
+ return HttpResponse(status=204)
+ else:
+ raise HttpResponse(status=204)
+
+
+# @login_required
+# def list_crash_reports(request):
+# context = {
+# 'request':request,
+# 'crashreport_count': Crashreport.objects.count(),
+# 'unique_devices': Crashreport.objects.values("uuid").distinct().count(),
+# 'log_count': Crashreport.objects.exclude(crashreport_file='').count(),
+# 'crashreport_hist' : Crashreport.objects.all().order_by('-date')[:5]
+# }
+# return render_to_response('crashreports/list.html',context)
+
+
+@login_required
+def get_crash_statistic(request):
+ from_date = request.GET.get('from_date', "2016-01-01")
+ to_date = request.GET.get('to_date', "2017-01-01")
+ print from_date
+ print to_date
+ entries = Crashreport.objects.filter(date__range=[from_date, to_date]).extra({'date_created' : "date(date)"}).values('date_created').annotate(created_count=Count('id'))
+ for entry in entries:
+ entry['date_created']=time.mktime(datetime.datetime.strptime(entry['date_created'], "%Y-%m-%d").timetuple())*1000
+ return render_to_response('crashreports/json/crashreport_by_day.html/', {
+ 'entries' : entries
+ })
diff --git a/psensor/views.py b/psensor/views.py
index 80c8f8f..553c598 100644
--- a/psensor/views.py
+++ b/psensor/views.py
@@ -1,7 +1,6 @@
from models import PSensorSetting
from rest_framework import viewsets
from serializers import PSensorSettingSerializer
-from rest_framework.decorators import detail_route, list_route
from rest_framework.permissions import BasePermission
class IsCreationOrIsAuthenticated(BasePermission):
@@ -16,7 +15,6 @@
return True
-
class PSensorSettingViewSet(viewsets.ModelViewSet):
queryset = PSensorSetting.objects.all()
serializer_class = PSensorSettingSerializer
diff --git a/requirements.txt b/requirements.txt
index 71d6b04..b83ecca 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,2 +1,11 @@
Django==1.9.7
+django-appconf==1.0.2
+django-bootstrap-form==3.2.1
+django-crispy-forms==1.6.0
+django-filter==0.14.0
+django-ratelimit==1.0.0
+django-user-accounts==1.3.1
+djangorestframework==3.4.6
+pinax-theme-bootstrap==7.10.2
+pytz==2016.6.1
wheel==0.24.0