diff --git a/writehat/lib/statistic.py b/writehat/lib/statistic.py
new file mode 100644
index 0000000..ffcae63
--- /dev/null
+++ b/writehat/lib/statistic.py
@@ -0,0 +1,112 @@
+import logging
+import json
+
+log = logging.getLogger(__name__)
+
+from writehat.lib.engagement import *
+from writehat.lib.findingCategory import *
+from writehat.lib.cvss import *
+from writehat.lib.dread import *
+
+def severity_statistics(startDate, endDate):
+ log.debug('Getting severity statistics')
+ labels = ['Informational', 'Low', 'Medium', 'High', 'Critical', 'Total']
+ informational, low, medium, high, critical, total = 0,0,0,0,0,0
+ cvss_findings = CVSSEngagementFinding.objects.filter(createdDate__range=[startDate, endDate])
+ dread_findings = DREADEngagementFinding.objects.filter(createdDate__range=[startDate, endDate])
+ proactive_findings = ProactiveEngagementFinding.objects.filter(createdDate__range=[startDate, endDate]).count()
+
+ for finding in cvss_findings:
+ total+=1
+ cvss = CVSS(finding.vector)
+ if cvss.severity == "Informational":
+ informational+=1
+ elif cvss.severity == "Low":
+ low+=1
+ elif cvss.severity == "Medium":
+ medium+=1
+ elif cvss.severity == "High":
+ high+=1
+ else:
+ critical+=1
+ cvss_data = [informational, low, medium, high, critical, total]
+ informational, low, medium, high, critical, total = 0,0,0,0,0,0
+ for finding in dread_findings:
+ total+=1
+ dread = DREAD(finding.vector)
+ if dread.severity == "Informational":
+ informational+=1
+ elif dread.severity == "Low":
+ low+=1
+ elif dread.severity == "Medium":
+ medium+=1
+ elif dread.severity == "High":
+ high+=1
+ else:
+ critical+=1
+ dread_data = [informational, low, medium, high, critical, total]
+
+ proactive_data = [0, 0, 0, 0, 0, proactive_findings]
+ return labels, cvss_data, dread_data, proactive_data
+
+def category_statistics(startDate, endDate):
+ log.debug('Getting category statistics')
+ labels, data, category_uuids = [], [], []
+ categories = DatabaseFindingCategory.objects.all()
+ for category in categories:
+ if category.name != "root":
+ labels.append(category.name)
+ category_uuids.append(category.id)
+ for uuid in category_uuids:
+ categoryCount = DREADEngagementFinding.objects.filter(createdDate__range=[startDate, endDate]).filter(categoryID=uuid).count()
+ categoryCount += CVSSEngagementFinding.objects.filter(createdDate__range=[startDate, endDate]).filter(categoryID=uuid).count()
+ categoryCount += ProactiveEngagementFinding.objects.filter(createdDate__range=[startDate, endDate]).filter(categoryID=uuid).count()
+ data.append(categoryCount)
+
+ return labels, data
+
+def customer_statistics(startDate, endDate):
+ labels, data, engagements = [], [], []
+ log.debug('Getting customer statistics')
+ customers = Customer.objects.all()
+ for customer in customers:
+ findingCount=0
+ engagements = Engagement.objects.filter(customerID=customer.id)
+ for engagement in engagements:
+ findingGroups = BaseFindingGroup.objects.filter(engagementParent=engagement.id)
+ for findingGroup in findingGroups:
+ fc = CVSSEngagementFinding.objects.filter(createdDate__range=[startDate, endDate]).filter(findingGroup=findingGroup.id).count()
+ fc += DREADEngagementFinding.objects.filter(createdDate__range=[startDate, endDate]).filter(findingGroup=findingGroup.id).count()
+ fc += ProactiveEngagementFinding.objects.filter(createdDate__range=[startDate, endDate]).filter(findingGroup=findingGroup.id).count()
+ findingCount+=fc
+
+ # don't append customers to the list which have no vulnerabilities
+ if findingCount != 0:
+ labels.append(customer.name)
+ data.append(findingCount)
+ labels = [x for _, x in sorted(zip(data, labels), reverse=True)]
+ data = sorted(data, reverse=True)
+ x = labels[:9]
+ y = data[:9]
+ if sum(data[9:]) != 0:
+ x.append("Other")
+ y.append(sum(data[9:]))
+ labels,data = x,y
+
+ return labels, data
+
+def engagement_statistics(startDate, endDate):
+ log.debug('Getting engagement statistics (count)')
+ data = 0
+ data = Engagement.objects.filter(createdDate__range=[startDate, endDate]).count()
+ return data
+
+def engagement_name_statistics(startDate, endDate):
+ log.debug('Getting engagement statistics (by name)')
+ data = []
+ engagements = Engagement.objects.filter(createdDate__range=[startDate, endDate])
+ for engagement in engagements:
+ data.append(engagement.name)
+ if not data:
+ data.append("No engagements started in the specified time window")
+ return data
\ No newline at end of file
diff --git a/writehat/static/js/calendar.js b/writehat/static/js/calendar.js
new file mode 100644
index 0000000..34bb77a
--- /dev/null
+++ b/writehat/static/js/calendar.js
@@ -0,0 +1,25 @@
+var p = new URLSearchParams(window.location.search);
+if (p.get("startDate") || p.get("endDate")) {
+ var startDate = p.get("startDate")
+ var endDate = p.get("endDate")
+} else {
+ const date = new Date();
+ var startDate = `1-${date.getMonth() + 1}-${date.getFullYear()}`
+ var endDate = `${new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate()}-${date.getMonth() + 1}-${date.getFullYear()}`
+}
+
+const picker = new Litepicker({
+ element: document.getElementById('litepicker'),
+ format: 'D-M-YYYY',
+ singleMode: false,
+ lang: 'en-GB',
+ startDate: startDate,
+ endDate: endDate,
+ setup: (picker) => {
+ picker.on('selected', (date1, date2) => {
+ document.getElementById("startDate").value = `${date1.getDate()}-${(date1.getMonth() + 1)}-${date1.getFullYear()}`;
+ document.getElementById("endDate").value = `${date2.getDate()}-${(date2.getMonth() + 1)}-${date2.getFullYear()}`;
+ document.getElementById("statisticsForm").submit();
+ });
+ },
+});
\ No newline at end of file
diff --git a/writehat/static/js/statistics.js b/writehat/static/js/statistics.js
new file mode 100644
index 0000000..f8412ba
--- /dev/null
+++ b/writehat/static/js/statistics.js
@@ -0,0 +1,230 @@
+$(function() {
+ var $severityStatistics = $("#severity_statistics");
+ var $categoryStatistics = $("#category_statistics");
+ var $customerStatistics = $("#customer_statistics");
+ var $p = new URLSearchParams(window.location.search);
+ var dreadBackgroundColors = [
+ pattern.draw('diagonal-right-left', 'rgba(0, 98, 255, 0.7)'),
+ pattern.draw('diagonal-right-left', 'rgba(255, 165, 0, 1)'),
+ pattern.draw('diagonal-right-left', 'rgba(255, 58, 0, 0.8)'),
+ pattern.draw('diagonal-right-left', 'rgba(255, 0, 0, 1)'),
+ pattern.draw('diagonal-right-left', 'rgba(195, 0, 255, 1)'),
+ pattern.draw('diagonal-right-left', 'rgba(60, 180, 108, 1)'),
+ ];
+ var proactiveBackgroundColors = [
+ pattern.draw('line', 'rgba(0, 98, 255, 0.7)'),
+ pattern.draw('line', 'rgba(255, 165, 0, 1)'),
+ pattern.draw('line', 'rgba(255, 58, 0, 0.8)'),
+ pattern.draw('line', 'rgba(255, 0, 0, 1)'),
+ pattern.draw('line', 'rgba(195, 0, 255, 1)'),
+ pattern.draw('line', 'rgba(60, 180, 108, 1)'),
+ ];
+ $.ajax({
+ url: "get_statistics?startDate=" + $p.get("startDate") + "&endDate=" + $p.get("endDate"),
+ type: "get",
+ success: function(data) {
+ $("h1").text(data.engagement_data);
+ $.each(data.engagement_name_data, function(index, value) {
+ $('
'+ value + '').appendTo('#engagement_names');
+ });
+ var severityCtx = $severityStatistics[0].getContext("2d");
+ var categoryCtx = $categoryStatistics[0].getContext("2d");
+ var customerCtx = $customerStatistics[0].getContext("2d");
+ Chart.register(ChartDataLabels);
+ new Chart(severityCtx, {
+ type: 'bar',
+ data: {
+ labels: data.cvss_severity_labels,
+ datasets: [{
+ label: 'CVSS',
+ backgroundColor: ['rgba(0, 98, 255, 0.7)', 'rgba(255, 165, 0, 1)', 'rgba(255, 58, 0, 0.8)', 'rgba(255, 0, 0, 1)', 'rgba(195, 0, 255, 1)', 'rgba(60, 180, 108, 1)'],
+ data: data.cvss_severity_data,
+ },
+ {
+ label: 'DREAD',
+ backgroundColor: dreadBackgroundColors,
+ data: data.dread_severity_data
+ },
+ {
+ label: 'PROACTIVE',
+ backgroundColor: proactiveBackgroundColors,
+ data: data.proactive_severity_data
+ }],
+ },
+ options: {
+ layout: {
+ padding: {
+ left: 5,
+ right: 5,
+ top: 50,
+ bottom: 5
+ }
+ },
+ responsive: true,
+ scales: {
+ x: {
+ ticks: {
+ color: 'white',
+ font: {
+ size: 14
+ }
+ }
+ },
+ y: {
+ stepSize: 1,
+ ticks: {
+ color: 'white',
+ callback: function(value) {if (value % 1 === 0) {return value;}},
+ font: {
+ size: 14
+ }
+ }
+ }
+ },
+ plugins: {
+ legend: {
+ position: 'bottom'
+ },
+ tooltip: {
+ enabled: false
+ },
+ datalabels: {
+ anchor: 'end',
+ align: 'end',
+ offset: 5,
+ color: 'white',
+ formatter: function (value, context) { return value || null; },
+ font: {
+ weight: 'bold',
+ size: 16
+ }
+ }
+ }
+ }
+ }),
+ new Chart(categoryCtx, {
+ type: 'bar',
+ data: {
+ labels: data.category_labels,
+ datasets: [{
+ label: 'Total',
+ backgroundColor: ['rgba(0, 98, 255, 0.7)'],
+ data: data.category_data
+ }]
+ },
+ options: {
+ indexAxis: 'y',
+ layout: {
+ padding: {
+ left: 5,
+ right: 50,
+ top: 5,
+ bottom: 5
+ }
+ },
+ responsive: true,
+ scales: {
+ x: {
+ ticks: {
+ color: 'white',
+ callback: function(value) {if (value % 1 === 0) {return value;}},
+ font: {
+ size: 14
+ }
+ }
+ },
+ y: {
+ ticks: {
+ color: 'white',
+ font: {
+ size: 14
+ }
+ }
+ }
+ },
+ plugins: {
+ legend: {
+ display: false
+ },
+ tooltip: {
+ enabled: false
+ },
+ datalabels: {
+ anchor: 'end',
+ align: 'end',
+ offset: 5,
+ color: 'white',
+ formatter: function (value, context) { return value || null; },
+ font: {
+ weight: 'bold',
+ size: 16
+ }
+ }
+ }
+ }
+ }
+ ),new Chart(customerCtx, {
+ type: 'bar',
+ data: {
+ labels: data.customer_labels,
+ datasets: [{
+ label: 'Total',
+ backgroundColor: ['rgba(0, 98, 255, 0.7)'],
+ data: data.customer_data
+ }]
+ },
+ options: {
+ indexAxis: 'y',
+ layout: {
+ padding: {
+ left: 5,
+ right: 50,
+ top: 5,
+ bottom: 5
+ }
+ },
+ responsive: true,
+ scales: {
+ x: {
+ ticks: {
+ color: 'white',
+ callback: function(value) {if (value % 1 === 0) {return value;}},
+ font: {
+ size: 14
+ }
+ }
+ },
+ y: {
+ ticks: {
+ color: 'white',
+ font: {
+ size: 14
+ }
+ }
+ }
+ },
+ plugins: {
+ legend: {
+ display: false
+ },
+ tooltip: {
+ enabled: false
+ },
+ datalabels: {
+ anchor: 'end',
+ align: 'end',
+ offset: 5,
+ color: 'white',
+ formatter: function (value, context) { return value || null; },
+ font: {
+ weight: 'bold',
+ size: 16
+ }
+ }
+ }
+ }
+ }
+ );
+ }
+ });
+});
\ No newline at end of file
diff --git a/writehat/templates/layouts/nav.html b/writehat/templates/layouts/nav.html
index 98f3a70..d154690 100644
--- a/writehat/templates/layouts/nav.html
+++ b/writehat/templates/layouts/nav.html
@@ -45,6 +45,10 @@
{% endif %}
+
+ {% include 'snippets/smallButton.html' with href='/statistics' type='chart-bar' class='text-green' tooltip='Statistics' %}
+
+
\ No newline at end of file
diff --git a/writehat/templates/pages/statistics.html b/writehat/templates/pages/statistics.html
new file mode 100644
index 0000000..838194c
--- /dev/null
+++ b/writehat/templates/pages/statistics.html
@@ -0,0 +1,33 @@
+{% extends 'layouts/base.html' %}
+
+{% block title %}Statistics{% endblock %}
+
+{% block headerLeft %}
+ Statistics
+{% endblock %}
+
+{% block headerRight %}
+
+
+
+
+
+
+
+
+{% endblock %}
+
+{% block fullPane %}
+
+
+
+
+
+
+
+{% include 'panes/statistics.html' %}
+
+{% endblock %}
\ No newline at end of file
diff --git a/writehat/templates/panes/statistics.html b/writehat/templates/panes/statistics.html
new file mode 100644
index 0000000..3b37c34
--- /dev/null
+++ b/writehat/templates/panes/statistics.html
@@ -0,0 +1,37 @@
+
+
+
+
Engagements Started (Count)
+
+
+
+
+
+
Engagements Started (By Name)
+
+
+
+
+
+
Vulnerabilities by Severity
+
+
+
+
+
+
+
+
Vulnerabilities by Category
+
+
+
+
+
+
Vulnerabilities by Requestor
+
+
+
+
\ No newline at end of file
diff --git a/writehat/urls.py b/writehat/urls.py
index 3419dc2..14ad59d 100644
--- a/writehat/urls.py
+++ b/writehat/urls.py
@@ -36,6 +36,10 @@
#url(rf'^images/test/{uuid}$', views.imageTest),
+ # Statistics URLs
+ path('statistics', views.statistics, name='statistics'),
+ path('get_statistics', views.getStatistics, name='get_statistics'),
+
# findings urls
path('findings', views.findingsList),
diff --git a/writehat/views.py b/writehat/views.py
index 9781f89..c42a7b7 100644
--- a/writehat/views.py
+++ b/writehat/views.py
@@ -4,6 +4,7 @@
import base64
import logging
import uuid as uuidlib
+import calendar
# django
from django.conf import settings
@@ -42,6 +43,7 @@
from writehat.lib.findingCategory import *
from writehat.lib.engagementFinding import *
from writehat.lib.excel import generateExcel
+from writehat.lib.statistic import *
# Selenium
@@ -71,6 +73,46 @@ def index(request):
return HttpResponseRedirect('/engagements')
+@require_http_methods(['GET'])
+@csrf_exempt
+def statistics(request):
+ return render(request,"pages/statistics.html", {})
+
+# stats dashboard
+@require_http_methods(['GET'])
+@csrf_exempt
+def getStatistics(request):
+ try:
+ startDate = datetime.strptime(request.GET.get("startDate"), '%d-%m-%Y')
+ endDate = datetime.strptime(request.GET.get("endDate"), '%d-%m-%Y')
+ except ValueError:
+ log.error("invalid or null timestamp provided, defaulting to current months statistics")
+ today = datetime.now()
+ startDate = datetime.strptime(f'1-{today.month}-{today.year}', '%d-%m-%Y')
+ endDate = datetime.strptime(f'{calendar.monthrange(today.year, today.month)[1]}-{today.month}-{today.year}', '%d-%m-%Y')
+
+ severity_labels, cvss_severity_data, dread_severity_data, proactive_severity_data = severity_statistics(startDate, endDate)
+ category_labels, category_data = category_statistics(startDate, endDate)
+ customer_labels, customer_data = customer_statistics(startDate, endDate)
+ engagement_data = engagement_statistics(startDate, endDate)
+ engagement_name_data = engagement_name_statistics(startDate, endDate)
+
+ return JsonResponse(data={
+ 'cvss_severity_labels': severity_labels,
+ 'cvss_severity_data': cvss_severity_data,
+ 'dread_severity_labels': severity_labels,
+ 'dread_severity_data': dread_severity_data,
+ 'proactive_severity_labels': severity_labels,
+ 'proactive_severity_data': proactive_severity_data,
+ 'category_labels': category_labels,
+ 'category_data': category_data,
+ 'customer_labels': customer_labels,
+ 'customer_data':customer_data,
+ 'engagement_labels':'Engagements',
+ 'engagement_data':engagement_data,
+ 'engagement_name_label':'Engagement Names',
+ 'engagement_name_data':engagement_name_data,
+ })
# Validation - returns allowed characters
@require_http_methods(['GET'])