mirror of https://github.com/gophish/gophish
Added support for pie charts for arbitrary events
parent
f212c13375
commit
f5509a56c2
|
@ -1,5 +1,7 @@
|
||||||
var map = null
|
var map = null
|
||||||
var doPoll = true;
|
var doPoll = true;
|
||||||
|
var arbEventsPieCharts = true; // Include pie charts for arbitary events or not
|
||||||
|
// Setting to true will add the arb events to statusMapping as wel as adding HTML chart elements.
|
||||||
|
|
||||||
// statuses is a helper map to point result statuses to ui classes
|
// statuses is a helper map to point result statuses to ui classes
|
||||||
var statuses = {
|
var statuses = {
|
||||||
|
@ -104,6 +106,7 @@ var statusMapping = {
|
||||||
"Clicked Link": "clicked",
|
"Clicked Link": "clicked",
|
||||||
"Submitted Data": "submitted_data",
|
"Submitted Data": "submitted_data",
|
||||||
"Email Reported": "reported",
|
"Email Reported": "reported",
|
||||||
|
//"Opened Word Document" : "opened_word_document"
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is an underwhelming attempt at an enum
|
// This is an underwhelming attempt at an enum
|
||||||
|
@ -693,7 +696,7 @@ function poll() {
|
||||||
.success(function (c) {
|
.success(function (c) {
|
||||||
campaign = c
|
campaign = c
|
||||||
|
|
||||||
updateArbitraryEventData(campaign) // Update data structures with new arbitrary event specifications
|
updateArbitraryEventData(campaign, false) // Update data structures with new arbitrary event specifications
|
||||||
|
|
||||||
/* Update the timeline */
|
/* Update the timeline */
|
||||||
var timeline_series_data = []
|
var timeline_series_data = []
|
||||||
|
@ -725,13 +728,20 @@ function poll() {
|
||||||
data: timeline_series_data
|
data: timeline_series_data
|
||||||
})
|
})
|
||||||
/* Update the results donut chart */
|
/* Update the results donut chart */
|
||||||
var email_series_data = {}
|
//var email_series_data = {}
|
||||||
// Load the initial data
|
// Load the initial data
|
||||||
Object.keys(statusMapping).forEach(function (k) {
|
//Object.keys(statusMapping).forEach(function (k) {
|
||||||
email_series_data[k] = 0
|
// email_series_data[k] = 0
|
||||||
});
|
//});
|
||||||
|
|
||||||
|
/*
|
||||||
$.each(campaign.results, function (i, result) {
|
$.each(campaign.results, function (i, result) {
|
||||||
email_series_data[result.status]++;
|
|
||||||
|
// Don't count arbitrary events, we do this independently to avoid backfill logic.
|
||||||
|
if (progressListing.includes(result.status)) {
|
||||||
|
email_series_data[result.status]++;
|
||||||
|
}
|
||||||
|
|
||||||
if (result.reported) {
|
if (result.reported) {
|
||||||
email_series_data['Email Reported']++
|
email_series_data['Email Reported']++
|
||||||
}
|
}
|
||||||
|
@ -740,7 +750,12 @@ function poll() {
|
||||||
for (var i = 0; i < step; i++) {
|
for (var i = 0; i < step; i++) {
|
||||||
email_series_data[progressListing[i]]++
|
email_series_data[progressListing[i]]++
|
||||||
}
|
}
|
||||||
})
|
})*/
|
||||||
|
|
||||||
|
// New function for counting events. Doesn't handle backfill, yet.
|
||||||
|
email_series_data = countCampaignEvents(campaign)
|
||||||
|
|
||||||
|
|
||||||
$.each(email_series_data, function (status, count) {
|
$.each(email_series_data, function (status, count) {
|
||||||
var email_data = []
|
var email_data = []
|
||||||
if (!(status in statusMapping)) {
|
if (!(status in statusMapping)) {
|
||||||
|
@ -801,7 +816,7 @@ function load() {
|
||||||
campaign = c
|
campaign = c
|
||||||
if (campaign) {
|
if (campaign) {
|
||||||
|
|
||||||
updateArbitraryEventData(campaign) // Update data structures with new arbitrary event specifications
|
updateArbitraryEventData(campaign, true) // Update data structures with new arbitrary event specifications
|
||||||
|
|
||||||
$("title").text(c.name + " - Gophish")
|
$("title").text(c.name + " - Gophish")
|
||||||
$("#loading").hide()
|
$("#loading").hide()
|
||||||
|
@ -865,11 +880,12 @@ function load() {
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
resultsTable.clear();
|
resultsTable.clear();
|
||||||
var email_series_data = {}
|
//var email_series_data = {}
|
||||||
var timeline_series_data = []
|
var timeline_series_data = []
|
||||||
Object.keys(statusMapping).forEach(function (k) {
|
//Object.keys(statusMapping).forEach(function (k) {
|
||||||
email_series_data[k] = 0
|
// email_series_data[k] = 0
|
||||||
});
|
//});
|
||||||
|
|
||||||
$.each(campaign.results, function (i, result) {
|
$.each(campaign.results, function (i, result) {
|
||||||
|
|
||||||
resultsTable.row.add([
|
resultsTable.row.add([
|
||||||
|
@ -883,16 +899,34 @@ function load() {
|
||||||
result.reported,
|
result.reported,
|
||||||
moment(result.send_date).format('MMMM Do YYYY, h:mm:ss a')
|
moment(result.send_date).format('MMMM Do YYYY, h:mm:ss a')
|
||||||
])
|
])
|
||||||
email_series_data[result.status]++;
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Don't count arbitrary events, we do this independently to avoid backfill logic.
|
||||||
|
if (progressListing.includes(result.status)) {
|
||||||
|
email_series_data[result.status]++;
|
||||||
|
}
|
||||||
|
|
||||||
if (result.reported) {
|
if (result.reported) {
|
||||||
email_series_data['Email Reported']++
|
email_series_data['Email Reported']++
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//TODO: At some point need to figure out backfilling with arbitrary events
|
||||||
|
// Possibly just backfill Email sent and Email Opened before getting into more complex
|
||||||
|
// data structures
|
||||||
|
|
||||||
// Backfill status values
|
// Backfill status values
|
||||||
var step = progressListing.indexOf(result.status)
|
var step = progressListing.indexOf(result.status)
|
||||||
for (var i = 0; i < step; i++) {
|
for (var i = 0; i < step; i++) {
|
||||||
email_series_data[progressListing[i]]++
|
email_series_data[progressListing[i]]++
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// New function for counting events. Doesn't handle backfill, yet.
|
||||||
|
email_series_data = countCampaignEvents(campaign)
|
||||||
|
|
||||||
resultsTable.draw();
|
resultsTable.draw();
|
||||||
// Setup tooltips
|
// Setup tooltips
|
||||||
$('[data-toggle="tooltip"]').tooltip()
|
$('[data-toggle="tooltip"]').tooltip()
|
||||||
|
@ -1041,16 +1075,27 @@ function report_mail(rid, cid) {
|
||||||
|
|
||||||
/* updateArbitraryData will go through the supplied campaign and add arbitrary event data to three data structure:
|
/* updateArbitraryData will go through the supplied campaign and add arbitrary event data to three data structure:
|
||||||
statuses
|
statuses
|
||||||
statusMapping //TODO
|
statusMapping
|
||||||
progressListing
|
progressListing // Todo, needs more consideration on backfill
|
||||||
*/
|
|
||||||
function updateArbitraryEventData(campaign){
|
|
||||||
|
|
||||||
// We add arbitrary events to the statuses dict and arbitrary event names the progressListing array.
|
The createPies boolean is used to allow us to create the pies on load() but not re-create them from calling poll(), as the
|
||||||
campaign.timeline.forEach(function(event) {
|
highchart info gets overwritten. The problem with this is that if a new arbitrary event comes in while the page is loaded
|
||||||
|
the poll() won't add the pie. Need to investigate this. TODO
|
||||||
|
|
||||||
|
*/
|
||||||
|
function updateArbitraryEventData(campaign, createPies){
|
||||||
|
|
||||||
|
|
||||||
|
var arbEventNames = [] // Hold unique arb event names. Used to create HTML pie charts if arbEventPieCharts set to true
|
||||||
|
|
||||||
|
campaign.timeline.forEach(function(event) { // Step over each event
|
||||||
|
|
||||||
|
|
||||||
if (event.message == "Arbitrary Event") {
|
if (event.message == "Arbitrary Event") {
|
||||||
|
|
||||||
details = JSON.parse(event.details) // TODO Validate this exists
|
details = JSON.parse(event.details) // TODO Validate this exists
|
||||||
|
|
||||||
|
// 1. Add title, color, icon, and label properties to statuses dict
|
||||||
title = "Arbitrary Event"
|
title = "Arbitrary Event"
|
||||||
if ("title" in details.payload){
|
if ("title" in details.payload){
|
||||||
title = String(details.payload.title)
|
title = String(details.payload.title)
|
||||||
|
@ -1058,7 +1103,6 @@ function updateArbitraryEventData(campaign){
|
||||||
}
|
}
|
||||||
statuses[title] = {"arbitrary event" : 1} // Set true to be arbitrary event, just so we can discern if we need to
|
statuses[title] = {"arbitrary event" : 1} // Set true to be arbitrary event, just so we can discern if we need to
|
||||||
|
|
||||||
|
|
||||||
statuses[title]["color"] = "#00FFFF" // Default
|
statuses[title]["color"] = "#00FFFF" // Default
|
||||||
if ("color" in details.payload ){
|
if ("color" in details.payload ){
|
||||||
color = String(details.payload.color)
|
color = String(details.payload.color)
|
||||||
|
@ -1080,19 +1124,87 @@ function updateArbitraryEventData(campaign){
|
||||||
statuses[title]["label"] = label
|
statuses[title]["label"] = label
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the title to the progressListing array (if it's not already in there)
|
if (!arbEventNames.includes(title)){
|
||||||
if (!progressListing.includes(title)) {
|
arbEventNames.push(title)
|
||||||
progressListing.push(title)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* How to handle progressListing needs more thought, and probably */
|
||||||
|
// Add the title to the progressListing array (if it's not already in there)
|
||||||
|
//if (!progressListing.includes(title)) {
|
||||||
|
// progressListing.push(title)
|
||||||
|
//}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// We add the arbitary event titles to the progress listing
|
// 2.0 If arbEventsPieChart is enabled we add to statusMapping and add HTML charts for the event
|
||||||
// Small problem here is that the ordering will assume anything appended is more serious than 'data submitted'
|
if (arbEventsPieCharts == true && createPies == true) {
|
||||||
//progressListing = progressListing.concat(Object.keys(tmpTitles))
|
|
||||||
|
|
||||||
|
//2.1 Create HTML elements
|
||||||
|
|
||||||
|
// Split the array into multiple arrays, each of size 5. This let's us create pie chart rows of five
|
||||||
|
arbEventNames.sort()
|
||||||
|
chunkedArbEvents = Array.from({ length: Math.ceil(arbEventNames.length / 5) }, (v, i) => arbEventNames.slice(i * 5, i * 5 + 5) );
|
||||||
|
|
||||||
|
$("#arbpie").html('') // i. Clear the div class
|
||||||
|
html = ''
|
||||||
|
chunkedArbEvents.forEach(function(chunk){
|
||||||
|
|
||||||
|
rowhtml = '<div class="row">\n\t<div style="height:200px;" class="col-lg-1 col-md-1"></div>\n'
|
||||||
|
chunk.forEach(function(title){
|
||||||
|
sanitizedEventName = title.toLowerCase().replace(/ /g, "_") // Convert Opened Word Document to opened_word_document.
|
||||||
|
sanitizedEventName = escapeHtml(sanitizedEventName) // Should maybe do more tests on this. Or even use a short random string rather than the name. e.g {"Opened Word Document" : "7a2f87"}
|
||||||
|
//i. Add the HTML element
|
||||||
|
rowhtml += '\t<div id="' + sanitizedEventName + '_chart" style="height:200px;" class="col-lg-2 col-md-2"></div>\n'
|
||||||
|
|
||||||
|
//ii. Add to statusMapping
|
||||||
|
statusMapping[title] = sanitizedEventName
|
||||||
|
|
||||||
|
|
||||||
|
})
|
||||||
|
rowhtml += '\t<div style="height:200px;" class="col-lg-1 col-md-1"></div>\n</div>\n'
|
||||||
|
|
||||||
|
html += rowhtml
|
||||||
|
|
||||||
|
})
|
||||||
|
$("#arbpie").html(html)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// countCampaignEvents will return a dict of title:count of arbitrary and regular events from a campaign
|
||||||
|
// Todo: Need to implement backfill logic
|
||||||
|
function countCampaignEvents(campaign) {
|
||||||
|
|
||||||
|
// Add all the default events to a counter dict
|
||||||
|
eventsCounter = {}
|
||||||
|
Object.keys(statusMapping).forEach(function (k) {
|
||||||
|
eventsCounter[k] = 0
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
campaign.timeline.forEach(function(event){
|
||||||
|
if (event.message == "Arbitrary Event"){
|
||||||
|
details = JSON.parse(event.details)
|
||||||
|
title = details.payload.title[0]
|
||||||
|
} else {
|
||||||
|
title = event.message
|
||||||
|
// Backfill logic for non arbitrary events. Todo
|
||||||
|
|
||||||
|
}
|
||||||
|
if (title in eventsCounter) {
|
||||||
|
eventsCounter[title] += 1
|
||||||
|
} else {
|
||||||
|
eventsCounter[title] = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Backfill logic here for arb?
|
||||||
|
|
||||||
|
})
|
||||||
|
return eventsCounter
|
||||||
}
|
}
|
||||||
|
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
|
|
|
@ -54,8 +54,14 @@
|
||||||
<div id="clicked_chart" style="height:200px;" class="col-lg-2 col-md-2"></div>
|
<div id="clicked_chart" style="height:200px;" class="col-lg-2 col-md-2"></div>
|
||||||
<div id="submitted_data_chart" style="height:200px;" class="col-lg-2 col-md-2"></div>
|
<div id="submitted_data_chart" style="height:200px;" class="col-lg-2 col-md-2"></div>
|
||||||
<div id="reported_chart" style="height:200px;" class="col-lg-2 col-md-2"></div>
|
<div id="reported_chart" style="height:200px;" class="col-lg-2 col-md-2"></div>
|
||||||
|
<!--<div id="opened_word_document_chart" style="height:200px;" class="col-lg-2 col-md-2" hidden></div>-->
|
||||||
<div style="height:200px;" class="col-lg-1 col-md-1"></div>
|
<div style="height:200px;" class="col-lg-1 col-md-1"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Add additional rows for arb events here: -->
|
||||||
|
<div id="arbpie"></div>
|
||||||
|
|
||||||
|
|
||||||
<div class="row" id="resultsMapContainer">
|
<div class="row" id="resultsMapContainer">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<p style="text-align:center;">Targets Map</p>
|
<p style="text-align:center;">Targets Map</p>
|
||||||
|
|
Loading…
Reference in New Issue