From f249338eb97ac953312206e21e01b16d7ae9d7db Mon Sep 17 00:00:00 2001 From: BlkPh0x Date: Wed, 4 Oct 2023 13:45:04 +1100 Subject: [PATCH] Update campaign_results.min.js --- static/js/dist/app/campaign_results.min.js | 942 ++++++++++++++++++++- 1 file changed, 940 insertions(+), 2 deletions(-) diff --git a/static/js/dist/app/campaign_results.min.js b/static/js/dist/app/campaign_results.min.js index e341ece1..5f45ce59 100644 --- a/static/js/dist/app/campaign_results.min.js +++ b/static/js/dist/app/campaign_results.min.js @@ -1,4 +1,206 @@ -var map=null;var doPoll=true;var statuses={"Email Sent":{color:"#1abc9c",label:"label-success",icon:"fa-envelope",point:"ct-point-sent"},"Emails Sent":{color:"#1abc9c",label:"label-success",icon:"fa-envelope",point:"ct-point-sent"},"In progress":{label:"label-primary"},Queued:{label:"label-info"},Completed:{label:"label-success"},"Email Opened":{color:"#f9bf3b",label:"label-warning",icon:"fa-envelope-open",point:"ct-point-opened"},"Clicked Link":{color:"#F39C12",label:"label-clicked",icon:"fa-mouse-pointer",point:"ct-point-clicked"},Success:{color:"#f05b4f",label:"label-danger",icon:"fa-exclamation",point:"ct-point-clicked"},"Email Reported":{color:"#45d6ef",label:"label-info",icon:"fa-bullhorn",point:"ct-point-reported"},Error:{color:"#6c7a89",label:"label-default",icon:"fa-times",point:"ct-point-error"},"Error Sending Email":{color:"#6c7a89",label:"label-default",icon:"fa-times",point:"ct-point-error"},"Submitted Data":{color:"#f05b4f",label:"label-danger",icon:"fa-exclamation",point:"ct-point-clicked"},Unknown:{color:"#6c7a89",label:"label-default",icon:"fa-question",point:"ct-point-error"},Sending:{color:"#428bca",label:"label-primary",icon:"fa-spinner",point:"ct-point-sending"},Retrying:{color:"#6c7a89",label:"label-default",icon:"fa-clock-o",point:"ct-point-error"},Scheduled:{color:"#428bca",label:"label-primary",icon:"fa-clock-o",point:"ct-point-sending"},"Campaign Created":{label:"label-success",icon:"fa-rocket"}};var statusMapping={"Email Sent":"sent","Email Opened":"opened","Clicked Link":"clicked","Submitted Data":"submitted_data","Email Reported":"reported"};var progressListing=["Email Sent","Email Opened","Clicked Link","Submitted Data"];var campaign={};var bubbles=[];function dismiss(){$("#modal\\.flashes").empty();$("#modal").modal("hide");$("#resultsTable").dataTable().DataTable().clear().draw()}function deleteCampaign(){Swal.fire({title:"Are you sure?",text:"This will delete the campaign. This can't be undone!",type:"warning",animation:false,showCancelButton:true,confirmButtonText:"Delete Campaign",confirmButtonColor:"#428bca",reverseButtons:true,allowOutsideClick:false,showLoaderOnConfirm:true,preConfirm:function(){return new Promise(function(resolve,reject){api.campaignId.delete(campaign.id).success(function(msg){resolve()}).error(function(data){reject(data.responseJSON.message)})})}}).then(function(result){if(result.value){Swal.fire("Campaign Deleted!","This campaign has been deleted!","success")}$('button:contains("OK")').on("click",function(){location.href="/campaigns"})})}function completeCampaign(){Swal.fire({title:"Are you sure?",text:"Gophish will stop processing events for this campaign",type:"warning",animation:false,showCancelButton:true,confirmButtonText:"Complete Campaign",confirmButtonColor:"#428bca",reverseButtons:true,allowOutsideClick:false,showLoaderOnConfirm:true,preConfirm:function(){return new Promise(function(resolve,reject){api.campaignId.complete(campaign.id).success(function(msg){resolve()}).error(function(data){reject(data.responseJSON.message)})})}}).then(function(result){if(result.value){Swal.fire("Campaign Completed!","This campaign has been completed!","success");$("#complete_button")[0].disabled=true;$("#complete_button").text("Completed!");doPoll=false}})}function function exportAsCSV(scope) { +var map = null +var doPoll = true; + +// statuses is a helper map to point result statuses to ui classes +var statuses = { + "Email Sent": { + color: "#1abc9c", + label: "label-success", + icon: "fa-envelope", + point: "ct-point-sent" + }, + "Emails Sent": { + color: "#1abc9c", + label: "label-success", + icon: "fa-envelope", + point: "ct-point-sent" + }, + "In progress": { + label: "label-primary" + }, + "Queued": { + label: "label-info" + }, + "Completed": { + label: "label-success" + }, + "Email Opened": { + color: "#f9bf3b", + label: "label-warning", + icon: "fa-envelope-open", + point: "ct-point-opened" + }, + "Clicked Link": { + color: "#F39C12", + label: "label-clicked", + icon: "fa-mouse-pointer", + point: "ct-point-clicked" + }, + "Success": { + color: "#f05b4f", + label: "label-danger", + icon: "fa-exclamation", + point: "ct-point-clicked" + }, + //not a status, but is used for the campaign timeline and user timeline + "Email Reported": { + color: "#45d6ef", + label: "label-info", + icon: "fa-bullhorn", + point: "ct-point-reported" + }, + "Error": { + color: "#6c7a89", + label: "label-default", + icon: "fa-times", + point: "ct-point-error" + }, + "Error Sending Email": { + color: "#6c7a89", + label: "label-default", + icon: "fa-times", + point: "ct-point-error" + }, + "Submitted Data": { + color: "#f05b4f", + label: "label-danger", + icon: "fa-exclamation", + point: "ct-point-clicked" + }, + "Unknown": { + color: "#6c7a89", + label: "label-default", + icon: "fa-question", + point: "ct-point-error" + }, + "Sending": { + color: "#428bca", + label: "label-primary", + icon: "fa-spinner", + point: "ct-point-sending" + }, + "Retrying": { + color: "#6c7a89", + label: "label-default", + icon: "fa-clock-o", + point: "ct-point-error" + }, + "Scheduled": { + color: "#428bca", + label: "label-primary", + icon: "fa-clock-o", + point: "ct-point-sending" + }, + "Campaign Created": { + label: "label-success", + icon: "fa-rocket" + } +} + +var statusMapping = { + "Email Sent": "sent", + "Email Opened": "opened", + "Clicked Link": "clicked", + "Submitted Data": "submitted_data", + "Email Reported": "reported", +} + +// This is an underwhelming attempt at an enum +// until I have time to refactor this appropriately. +var progressListing = [ + "Email Sent", + "Email Opened", + "Clicked Link", + "Submitted Data" +] + +var campaign = {} +var bubbles = [] + +function dismiss() { + $("#modal\\.flashes").empty() + $("#modal").modal('hide') + $("#resultsTable").dataTable().DataTable().clear().draw() +} + +// Deletes a campaign after prompting the user +function deleteCampaign() { + Swal.fire({ + title: "Are you sure?", + text: "This will delete the campaign. This can't be undone!", + type: "warning", + animation: false, + showCancelButton: true, + confirmButtonText: "Delete Campaign", + confirmButtonColor: "#428bca", + reverseButtons: true, + allowOutsideClick: false, + showLoaderOnConfirm: true, + preConfirm: function () { + return new Promise(function (resolve, reject) { + api.campaignId.delete(campaign.id) + .success(function (msg) { + resolve() + }) + .error(function (data) { + reject(data.responseJSON.message) + }) + }) + } + }).then(function (result) { + if(result.value){ + Swal.fire( + 'Campaign Deleted!', + 'This campaign has been deleted!', + 'success' + ); + } + $('button:contains("OK")').on('click', function () { + location.href = '/campaigns' + }) + }) +} + +// Completes a campaign after prompting the user +function completeCampaign() { + Swal.fire({ + title: "Are you sure?", + text: "Gophish will stop processing events for this campaign", + type: "warning", + animation: false, + showCancelButton: true, + confirmButtonText: "Complete Campaign", + confirmButtonColor: "#428bca", + reverseButtons: true, + allowOutsideClick: false, + showLoaderOnConfirm: true, + preConfirm: function () { + return new Promise(function (resolve, reject) { + api.campaignId.complete(campaign.id) + .success(function (msg) { + resolve() + }) + .error(function (data) { + reject(data.responseJSON.message) + }) + }) + } + }).then(function (result) { + if (result.value){ + Swal.fire( + 'Campaign Completed!', + 'This campaign has been completed!', + 'success' + ); + $('#complete_button')[0].disabled = true; + $('#complete_button').text('Completed!') + doPoll = false; + } + }) +} + +// Exports campaign results as a CSV file +function exportAsCSV(scope) { var csvScope = null; var filename = campaign.name + ' - ' + capitalize(scope) + '.csv'; @@ -79,4 +281,740 @@ var map=null;var doPoll=true;var statuses={"Email Sent":{color:"#1abc9c",label:" // Clean up window.URL.revokeObjectURL(csvURL); -}function replay(event_idx){request=campaign.timeline[event_idx];details=JSON.parse(request.details);url=null;form=$("
").attr({method:"POST",target:"_blank"});$.each(Object.keys(details.payload),function(i,param){if(param=="rid"){return true}if(param=="__original_url"){url=details.payload[param];return true}$("").attr({name:param}).val(details.payload[param]).appendTo(form)});Swal.fire({title:"Where do you want the credentials submitted to?",input:"text",showCancelButton:true,inputPlaceholder:"http://example.com/login",inputValue:url||"",inputValidator:function(value){return new Promise(function(resolve,reject){if(value){resolve()}else{reject("Invalid URL.")}})}}).then(function(result){if(result.value){url=result.value;submitForm()}});return;submitForm();function submitForm(){form.attr({action:url});form.appendTo("body").submit().remove()}}var renderDevice=function(event_details){var ua=UAParser(details.browser["user-agent"]);var detailsString='
';var deviceIcon="laptop";if(ua.device.type){if(ua.device.type=="tablet"||ua.device.type=="mobile"){deviceIcon=ua.device.type}}var deviceVendor="";if(ua.device.vendor){deviceVendor=ua.device.vendor.toLowerCase();if(deviceVendor=="microsoft")deviceVendor="windows"}var deviceName="Unknown";if(ua.os.name){deviceName=ua.os.name;if(deviceName=="Mac OS"){deviceVendor="apple"}else if(deviceName=="Windows"){deviceVendor="windows"}if(ua.device.vendor&&ua.device.model){deviceName=ua.device.vendor+" "+ua.device.model}}if(ua.os.version){deviceName=deviceName+" (OS Version: "+ua.os.version+")"}deviceString='
'+''+''+" "+escapeHtml(deviceName)+"
";detailsString+=deviceString;var deviceBrowser="Unknown";var browserIcon="info-circle";var browserVersion="";if(ua.browser&&ua.browser.name){deviceBrowser=ua.browser.name;deviceBrowser=deviceBrowser.replace("Mobile ","");if(deviceBrowser){browserIcon=deviceBrowser.toLowerCase();if(browserIcon=="ie")browserIcon="internet-explorer"}browserVersion="(Version: "+ua.browser.version+")"}var browserString='
'+' '+deviceBrowser+" "+browserVersion+"
";detailsString+=browserString;detailsString+="
";return detailsString};function renderTimeline(data){record={id:data[0],first_name:data[2],last_name:data[3],email:data[4],position:data[5],status:data[6],reported:data[7],send_date:data[8]};results='
'+"
Timeline for "+escapeHtml(record.first_name)+" "+escapeHtml(record.last_name)+'
Email: '+escapeHtml(record.email)+"
Result ID: "+escapeHtml(record.id)+"
"+'
';$.each(campaign.timeline,function(i,event){if(!event.email||event.email==record.email){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+=" ";$.each(Object.keys(details.payload),function(i,param){if(param=="rid"){return true}results+=" ";results+=" ";results+=" ";results+=" "});results+="
ParameterValue(s)
"+escapeHtml(param)+""+escapeHtml(details.payload[param])+"
";results+="
"}if(details.error){results+='
View Details
';results+='
';results+='Error '+details.error;results+="
"}}results+="
"}});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}]})};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''+this.point.name+": "+this.y+"%
"}},series:[{data:chartopts["data"],colors:chartopts["colors"]}]})};var updateMap=function(results){if(!map){return}bubbles=[];$.each(campaign.results,function(i,result){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)};function createStatusLabel(status,send_date){var label=statuses[status].label||"label-default";var statusColumn=''+status+"";if(status=="Scheduled"||status=="Retrying"){var sendDateMessage="Scheduled to send at "+send_date;statusColumn=''+status+""}return statusColumn}function poll(){api.campaignId.results(campaign.id).success(function(c){campaign=c;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});var email_series_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"]++}var step=progressListing.indexOf(result.status);for(var i=0;i"}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"]++}var step=progressListing.indexOf(result.status);for(var i=0;i{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();setRefresh=setTimeout(refresh,6e4)}); +} + +function replay(event_idx) { + request = campaign.timeline[event_idx] + details = JSON.parse(request.details) + url = null + form = $('').attr({ + method: 'POST', + target: '_blank', + }) + /* Create a form object and submit it */ + $.each(Object.keys(details.payload), function (i, param) { + if (param == "rid") { + return true; + } + if (param == "__original_url") { + url = details.payload[param]; + return true; + } + $('').attr({ + name: param, + }).val(details.payload[param]).appendTo(form); + }) + /* Ensure we know where to send the user */ + // Prompt for the URL + Swal.fire({ + title: 'Where do you want the credentials submitted to?', + input: 'text', + showCancelButton: true, + inputPlaceholder: "http://example.com/login", + inputValue: url || "", + inputValidator: function (value) { + return new Promise(function (resolve, reject) { + if (value) { + resolve(); + } else { + reject('Invalid URL.'); + } + }); + } + }).then(function (result) { + if (result.value){ + url = result.value + submitForm() + } + }) + return + submitForm() + + function submitForm() { + form.attr({ + action: url + }) + form.appendTo('body').submit().remove() + } +} + +/** + * Returns an HTML string that displays the OS and browser that clicked the link + * or submitted credentials. + * + * @param {object} event_details - The "details" parameter for a campaign + * timeline event + * + */ +var renderDevice = function (event_details) { + var ua = UAParser(details.browser['user-agent']) + var detailsString = '
' + + var deviceIcon = 'laptop' + if (ua.device.type) { + if (ua.device.type == 'tablet' || ua.device.type == 'mobile') { + deviceIcon = ua.device.type + } + } + + var deviceVendor = '' + if (ua.device.vendor) { + deviceVendor = ua.device.vendor.toLowerCase() + if (deviceVendor == 'microsoft') deviceVendor = 'windows' + } + + var deviceName = 'Unknown' + if (ua.os.name) { + deviceName = ua.os.name + if (deviceName == "Mac OS") { + deviceVendor = 'apple' + } else if (deviceName == "Windows") { + deviceVendor = 'windows' + } + if (ua.device.vendor && ua.device.model) { + deviceName = ua.device.vendor + ' ' + ua.device.model + } + } + + if (ua.os.version) { + deviceName = deviceName + ' (OS Version: ' + ua.os.version + ')' + } + + deviceString = '
' + + '' + + '' + + ' ' + escapeHtml(deviceName) + '
' + + detailsString += deviceString + + var deviceBrowser = 'Unknown' + var browserIcon = 'info-circle' + var browserVersion = '' + + if (ua.browser && ua.browser.name) { + deviceBrowser = ua.browser.name + // Handle the "mobile safari" case + deviceBrowser = deviceBrowser.replace('Mobile ', '') + if (deviceBrowser) { + browserIcon = deviceBrowser.toLowerCase() + if (browserIcon == 'ie') browserIcon = 'internet-explorer' + } + browserVersion = '(Version: ' + ua.browser.version + ')' + } + + var browserString = '
' + + ' ' + + deviceBrowser + ' ' + browserVersion + '
' + + detailsString += browserString + detailsString += '
' + return detailsString +} + +function renderTimeline(data) { + record = { + "id": data[0], + "first_name": data[2], + "last_name": data[3], + "email": data[4], + "position": data[5], + "status": data[6], + "reported": data[7], + "send_date": data[8] + } + results = '
' + + '
Timeline for ' + escapeHtml(record.first_name) + ' ' + escapeHtml(record.last_name) + + '
Email: ' + escapeHtml(record.email) + + '
Result ID: ' + escapeHtml(record.id) + '
' + + '
' + $.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 += ' ' + $.each(Object.keys(details.payload), function (i, param) { + if (param == "rid") { + return true; + } + results += ' ' + results += ' ' + results += ' ' + results += ' ' + }) + results += '
ParameterValue(s)
' + escapeHtml(param) + '' + escapeHtml(details.payload[param]) + '
' + 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) +})