'
$.each(campaign.timeline, function (i, event) {
if (!event.email || event.email == record.email) {
// Add the event
results += '
' +
'
'
results +=
'
' +
'
' +
'
' + escapeHtml(event.message) +
'
' + moment.utc(event.time).local().format('MMMM Do YYYY h:mm:ss a') + ''
if (event.details) {
details = JSON.parse(event.details)
if (event.message == "Clicked Link" || event.message == "Submitted Data") {
deviceView = renderDevice(details)
if (deviceView) {
results += deviceView
}
}
if (event.message == "Submitted Data") {
results += '
'
results += '
View Details
'
}
if (details.payload) {
results += '
'
results += '
'
results += ' Parameter | Value(s) |
'
$.each(Object.keys(details.payload), function (i, param) {
if (param == "rid") {
return true;
}
results += ' '
results += ' ' + escapeHtml(param) + ' | '
results += ' ' + escapeHtml(details.payload[param]) + ' | '
results += '
'
})
results += '
'
results += '
'
}
if (details.error) {
results += '
View Details
'
results += '
'
results += 'Error ' + details.error
results += '
'
}
}
results += '
'
}
})
// Add the scheduled send event at the bottom
if (record.status == "Scheduled" || record.status == "Retrying") {
results += '
' +
'
'
results +=
'
' +
'
' +
'
' + "Scheduled to send at " + record.send_date + ''
}
results += '
'
return results
}
var renderTimelineChart = function (chartopts) {
return Highcharts.chart('timeline_chart', {
chart: {
zoomType: 'x',
type: 'line',
height: "200px"
},
title: {
text: 'Campaign Timeline'
},
xAxis: {
type: 'datetime',
dateTimeLabelFormats: {
second: '%l:%M:%S',
minute: '%l:%M',
hour: '%l:%M',
day: '%b %d, %Y',
week: '%b %d, %Y',
month: '%b %Y'
}
},
yAxis: {
min: 0,
max: 2,
visible: false,
tickInterval: 1,
labels: {
enabled: false
},
title: {
text: ""
}
},
tooltip: {
formatter: function () {
return Highcharts.dateFormat('%A, %b %d %l:%M:%S %P', new Date(this.x)) +
'
Event: ' + this.point.message + '
Email:
' + this.point.email + ''
}
},
legend: {
enabled: false
},
plotOptions: {
series: {
marker: {
enabled: true,
symbol: 'circle',
radius: 3
},
cursor: 'pointer',
},
line: {
states: {
hover: {
lineWidth: 1
}
}
}
},
credits: {
enabled: false
},
series: [{
data: chartopts['data'],
dashStyle: "shortdash",
color: "#cccccc",
lineWidth: 1,
turboThreshold: 0
}]
})
}
/* Renders a pie chart using the provided chartops */
var renderPieChart = function (chartopts) {
return Highcharts.chart(chartopts['elemId'], {
chart: {
type: 'pie',
events: {
load: function () {
var chart = this,
rend = chart.renderer,
pie = chart.series[0],
left = chart.plotLeft + pie.center[0],
top = chart.plotTop + pie.center[1];
this.innerText = rend.text(chartopts['data'][0].count, left, top).
attr({
'text-anchor': 'middle',
'font-size': '24px',
'font-weight': 'bold',
'fill': chartopts['colors'][0],
'font-family': 'Helvetica,Arial,sans-serif'
}).add();
},
render: function () {
this.innerText.attr({
text: chartopts['data'][0].count
})
}
}
},
title: {
text: chartopts['title']
},
plotOptions: {
pie: {
innerSize: '80%',
dataLabels: {
enabled: false
}
}
},
credits: {
enabled: false
},
tooltip: {
formatter: function () {
if (this.key == undefined) {
return false
}
return '
\u25CF' + this.point.name + ':
' + this.y + '%'
}
},
series: [{
data: chartopts['data'],
colors: chartopts['colors'],
}]
})
}
/* Updates the bubbles on the map
@param {campaign.result[]} results - The campaign results to process
*/
var updateMap = function (results) {
if (!map) {
return
}
bubbles = []
$.each(campaign.results, function (i, result) {
// Check that it wasn't an internal IP
if (result.latitude == 0 && result.longitude == 0) {
return true;
}
newIP = true
$.each(bubbles, function (i, bubble) {
if (bubble.ip == result.ip) {
bubbles[i].radius += 1
newIP = false
return false
}
})
if (newIP) {
bubbles.push({
latitude: result.latitude,
longitude: result.longitude,
name: result.ip,
fillKey: "point",
radius: 2
})
}
})
map.bubbles(bubbles)
}
/**
* Creates a status label for use in the results datatable
* @param {string} status
* @param {moment(datetime)} send_date
*/
function createStatusLabel(status, send_date) {
var label = statuses[status].label || "label-default";
var statusColumn = "
" + status + ""
// Add the tooltip if the email is scheduled to be sent
if (status == "Scheduled" || status == "Retrying") {
var sendDateMessage = "Scheduled to send at " + send_date
statusColumn = "
" + status + ""
}
return statusColumn
}
/* poll - Queries the API and updates the UI with the results
*
* Updates:
* * Timeline Chart
* * Email (Donut) Chart
* * Map Bubbles
* * Datatables
*/
function poll() {
api.campaignId.results(campaign.id)
.success(function (c) {
campaign = c
/* Update the timeline */
var timeline_series_data = []
$.each(campaign.timeline, function (i, event) {
var event_date = moment.utc(event.time).local()
timeline_series_data.push({
email: event.email,
message: event.message,
x: event_date.valueOf(),
y: 1,
marker: {
fillColor: statuses[event.message].color
}
})
})
var timeline_chart = $("#timeline_chart").highcharts()
timeline_chart.series[0].update({
data: timeline_series_data
})
/* Update the results donut chart */
var email_series_data = {}
// Load the initial data
Object.keys(statusMapping).forEach(function (k) {
email_series_data[k] = 0
});
$.each(campaign.results, function (i, result) {
email_series_data[result.status]++;
if (result.reported) {
email_series_data['Email Reported']++
}
// Backfill status values
var step = progressListing.indexOf(result.status)
for (var i = 0; i < step; i++) {
email_series_data[progressListing[i]]++
}
})
$.each(email_series_data, function (status, count) {
var email_data = []
if (!(status in statusMapping)) {
return true
}
email_data.push({
name: status,
y: Math.floor((count / campaign.results.length) * 100),
count: count
})
email_data.push({
name: '',
y: 100 - Math.floor((count / campaign.results.length) * 100)
})
var chart = $("#" + statusMapping[status] + "_chart").highcharts()
chart.series[0].update({
data: email_data
})
})
/* Update the datatable */
resultsTable = $("#resultsTable").DataTable()
resultsTable.rows().every(function (i, tableLoop, rowLoop) {
var row = this.row(i)
var rowData = row.data()
var rid = rowData[0]
$.each(campaign.results, function (j, result) {
if (result.id == rid) {
rowData[8] = moment(result.send_date).format('MMMM Do YYYY, h:mm:ss a')
rowData[7] = result.reported
rowData[6] = result.status
resultsTable.row(i).data(rowData)
if (row.child.isShown()) {
$(row.node()).find("#caret").removeClass("fa-caret-right")
$(row.node()).find("#caret").addClass("fa-caret-down")
row.child(renderTimeline(row.data()))
}
return false
}
})
})
resultsTable.draw(false)
/* Update the map information */
updateMap(campaign.results)
$('[data-toggle="tooltip"]').tooltip()
$("#refresh_message").hide()
$("#refresh_btn").show()
})
}
function load() {
campaign.id = window.location.pathname.split('/').slice(-1)[0]
var use_map = JSON.parse(localStorage.getItem('gophish.use_map'))
api.campaignId.results(campaign.id)
.success(function (c) {
campaign = c
if (campaign) {
$("title").text(c.name + " - Gophish")
$("#loading").hide()
$("#campaignResults").show()
// Set the title
$("#page-title").text("Results for " + c.name)
if (c.status == "Completed") {
$('#complete_button')[0].disabled = true;
$('#complete_button').text('Completed!');
doPoll = false;
}
// Setup viewing the details of a result
$("#resultsTable").on("click", ".timeline-event-details", function () {
// Show the parameters
payloadResults = $(this).parent().find(".timeline-event-results")
if (payloadResults.is(":visible")) {
$(this).find("i").removeClass("fa-caret-down")
$(this).find("i").addClass("fa-caret-right")
payloadResults.hide()
} else {
$(this).find("i").removeClass("fa-caret-right")
$(this).find("i").addClass("fa-caret-down")
payloadResults.show()
}
})
// Setup the results table
resultsTable = $("#resultsTable").DataTable({
destroy: true,
"order": [
[2, "asc"]
],
columnDefs: [{
orderable: false,
targets: "no-sort"
}, {
className: "details-control",
"targets": [1]
}, {
"visible": false,
"targets": [0, 8]
},
{
"render": function (data, type, row) {
return createStatusLabel(data, row[8])
},
"targets": [6]
},
{
className: "text-center",
"render": function (reported, type, row) {
if (type == "display") {
if (reported) {
return "
"
}
return "
"
}
return reported
},
"targets": [7]
}
]
});
resultsTable.clear();
var email_series_data = {}
var timeline_series_data = []
Object.keys(statusMapping).forEach(function (k) {
email_series_data[k] = 0
});
$.each(campaign.results, function (i, result) {
resultsTable.row.add([
result.id,
"
",
escapeHtml(result.first_name) || "",
escapeHtml(result.last_name) || "",
escapeHtml(result.email) || "",
escapeHtml(result.position) || "",
result.status,
result.reported,
moment(result.send_date).format('MMMM Do YYYY, h:mm:ss a')
])
email_series_data[result.status]++;
if (result.reported) {
email_series_data['Email Reported']++
}
// Backfill status values
var step = progressListing.indexOf(result.status)
for (var i = 0; i < step; i++) {
email_series_data[progressListing[i]]++
}
})
resultsTable.draw();
// Setup tooltips
$('[data-toggle="tooltip"]').tooltip()
// Setup the individual timelines
$('#resultsTable tbody').on('click', 'td.details-control', function () {
var tr = $(this).closest('tr');
var row = resultsTable.row(tr);
if (row.child.isShown()) {
// This row is already open - close it
row.child.hide();
tr.removeClass('shown');
$(this).find("i").removeClass("fa-caret-down")
$(this).find("i").addClass("fa-caret-right")
} else {
// Open this row
$(this).find("i").removeClass("fa-caret-right")
$(this).find("i").addClass("fa-caret-down")
row.child(renderTimeline(row.data())).show();
tr.addClass('shown');
}
});
// Setup the graphs
$.each(campaign.timeline, function (i, event) {
if (event.message == "Campaign Created") {
return true
}
var event_date = moment.utc(event.time).local()
timeline_series_data.push({
email: event.email,
message: event.message,
x: event_date.valueOf(),
y: 1,
marker: {
fillColor: statuses[event.message].color
}
})
})
renderTimelineChart({
data: timeline_series_data
})
$.each(email_series_data, function (status, count) {
var email_data = []
if (!(status in statusMapping)) {
return true
}
email_data.push({
name: status,
y: Math.floor((count / campaign.results.length) * 100),
count: count
})
email_data.push({
name: '',
y: 100 - Math.floor((count / campaign.results.length) * 100)
})
var chart = renderPieChart({
elemId: statusMapping[status] + '_chart',
title: status,
name: status,
data: email_data,
colors: [statuses[status].color, '#dddddd']
})
})
if (use_map) {
$("#resultsMapContainer").show()
map = new Datamap({
element: document.getElementById("resultsMap"),
responsive: true,
fills: {
defaultFill: "#ffffff",
point: "#283F50"
},
geographyConfig: {
highlightFillColor: "#1abc9c",
borderColor: "#283F50"
},
bubblesConfig: {
borderColor: "#283F50"
}
});
}
updateMap(campaign.results)
}
})
.error(function () {
$("#loading").hide()
errorFlash(" Campaign not found!")
})
}
var setRefresh
function refresh() {
if (!doPoll) {
return;
}
$("#refresh_message").show()
$("#refresh_btn").hide()
poll()
clearTimeout(setRefresh)
setRefresh = setTimeout(refresh, 60000)
};
function report_mail(rid, cid) {
Swal.fire({
title: "Are you sure?",
text: "This result will be flagged as reported (RID: " + rid + ")",
type: "question",
animation: false,
showCancelButton: true,
confirmButtonText: "Continue",
confirmButtonColor: "#428bca",
reverseButtons: true,
allowOutsideClick: false,
showLoaderOnConfirm: true
}).then(function (result) {
if (result.value){
api.campaignId.get(cid).success((function(c) {
report_url = new URL(c.url)
report_url.pathname = '/report'
report_url.search = "?rid=" + rid
fetch(report_url)
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
refresh();
})
.catch(error => {
let errorMessage = error.message;
if (error.message === "Failed to fetch") {
errorMessage = "This might be due to Mixed Content issues or network problems.";
}
Swal.fire({
title: 'Error',
text: errorMessage,
type: 'error',
confirmButtonText: 'Close'
});
});
}));
}
})
}
$(document).ready(function () {
Highcharts.setOptions({
global: {
useUTC: false
}
})
load();
// Start the polling loop
setRefresh = setTimeout(refresh, 60000)
})