Moved all charts from Chartist to Highcharts. Closes #680.

This commit is contained in:
Jordan Wright 2017-08-05 21:11:50 -05:00
parent 972c40fd87
commit 75600f5812
20 changed files with 3444 additions and 1262 deletions

View file

@ -5,11 +5,9 @@
*/
var gulp = require('gulp'),
jshint = require('gulp-jshint'),
rename = require('gulp-rename'),
concat = require('gulp-concat'),
uglify = require('gulp-uglify'),
wrap = require('gulp-wrap'),
cleanCSS = require('gulp-clean-css'),
js_directory = 'static/js/src/',
@ -19,72 +17,69 @@ var gulp = require('gulp'),
dest_js_directory = 'static/js/dist/',
dest_css_directory = 'static/css/dist/';
gulp.task('default', ['watch']);
gulp.task('jshint', function() {
return gulp.src(js_directory)
.pipe(jshint())
.pipe(jshint.reporter('jshint-stylish'));
});
gulp.task('build', function() {
gulp.task('vendorjs', function () {
// Vendor minifying / concat
gulp.src([
vendor_directory + 'jquery.js',
vendor_directory + 'bootstrap.min.js',
vendor_directory + 'moment.min.js',
vendor_directory + 'chartist.min.js',
vendor_directory + 'papaparse.min.js',
vendor_directory + 'd3.min.js',
vendor_directory + 'topojson.min.js',
vendor_directory + 'datamaps.min.js',
vendor_directory + 'jquery.dataTables.min.js',
vendor_directory + 'dataTables.bootstrap.js',
vendor_directory + 'datetime-moment.js',
vendor_directory + 'jquery.ui.widget.js',
vendor_directory + 'jquery.fileupload.js',
vendor_directory + 'jquery.iframe-transport.js',
vendor_directory + 'sweetalert2.min.js',
vendor_directory + 'bootstrap-datetime.js',
vendor_directory + 'select2.min.js',
vendor_directory + 'core.min.js'
])
return gulp.src([
vendor_directory + 'jquery.js',
vendor_directory + 'bootstrap.min.js',
vendor_directory + 'moment.min.js',
vendor_directory + 'chartist.min.js',
vendor_directory + 'papaparse.min.js',
vendor_directory + 'd3.min.js',
vendor_directory + 'topojson.min.js',
vendor_directory + 'datamaps.min.js',
vendor_directory + 'jquery.dataTables.min.js',
vendor_directory + 'dataTables.bootstrap.js',
vendor_directory + 'datetime-moment.js',
vendor_directory + 'jquery.ui.widget.js',
vendor_directory + 'jquery.fileupload.js',
vendor_directory + 'jquery.iframe-transport.js',
vendor_directory + 'sweetalert2.min.js',
vendor_directory + 'bootstrap-datetime.js',
vendor_directory + 'select2.min.js',
vendor_directory + 'core.min.js',
vendor_directory + 'highcharts.js'
])
.pipe(concat('vendor.js'))
.pipe(rename({
suffix: '.min'
}))
.pipe(uglify())
.pipe(gulp.dest(dest_js_directory));
})
gulp.task('scripts', function () {
// Gophish app files
gulp.src(app_directory)
.pipe(rename({
suffix: '.min'
}))
.pipe(uglify().on('error', function(e){
.pipe(uglify().on('error', function (e) {
console.log(e);
}))
.pipe(gulp.dest(dest_js_directory + 'app/'));
})
gulp.task('styles', function() {
return gulp.src([
css_directory + 'bootstrap.min.css',
css_directory + 'main.css',
css_directory + 'dashboard.css',
css_directory + 'flat-ui.css',
css_directory + 'dataTables.bootstrap.css',
css_directory + 'font-awesome.min.css',
css_directory + 'chartist.min.css',
css_directory + 'bootstrap-datetime.css',
css_directory + 'checkbox.css',
css_directory + 'sweetalert2.min.css',
css_directory + 'select2.min.css',
css_directory + 'select2-bootstrap.min.css'
])
.pipe(cleanCSS({compatibilty: 'ie9'}))
css_directory + 'bootstrap.min.css',
css_directory + 'main.css',
css_directory + 'dashboard.css',
css_directory + 'flat-ui.css',
css_directory + 'dataTables.bootstrap.css',
css_directory + 'font-awesome.min.css',
css_directory + 'chartist.min.css',
css_directory + 'bootstrap-datetime.css',
css_directory + 'checkbox.css',
css_directory + 'sweetalert2.min.css',
css_directory + 'select2.min.css',
css_directory + 'select2-bootstrap.min.css',
])
.pipe(cleanCSS({ compatibilty: 'ie9' }))
.pipe(concat('gophish.css'))
.pipe(gulp.dest(dest_css_directory));
});
})
gulp.task('watch', function() {
gulp.watch('static/js/src/app/**/*.js', ['jshint']);
});
gulp.task('build', ['vendorjs', 'scripts', 'styles']);
gulp.task('default', ['build']);

View file

@ -198,6 +198,7 @@ func (c *Campaign) getDetails() error {
}
// getCampaignStats returns a CampaignStats object for the campaign with the given campaign ID.
// It also backfills numbers as appropriate with a running total, so that the values are aggregated.
func getCampaignStats(cid int64) (CampaignStats, error) {
s := CampaignStats{}
query := db.Table("results").Where("campaign_id = ?", cid)
@ -205,11 +206,7 @@ func getCampaignStats(cid int64) (CampaignStats, error) {
if err != nil {
return s, err
}
err = query.Where("status=?", EVENT_SENT).Count(&s.EmailsSent).Error
if err != nil {
return s, err
}
err = query.Where("status=?", EVENT_OPENED).Count(&s.OpenedEmail).Error
query.Where("status=?", EVENT_DATA_SUBMIT).Count(&s.SubmittedData)
if err != nil {
return s, err
}
@ -217,10 +214,20 @@ func getCampaignStats(cid int64) (CampaignStats, error) {
if err != nil {
return s, err
}
query.Where("status=?", EVENT_DATA_SUBMIT).Count(&s.SubmittedData)
// Every submitted data event implies they clicked the link
s.ClickedLink += s.SubmittedData
err = query.Where("status=?", EVENT_OPENED).Count(&s.OpenedEmail).Error
if err != nil {
return s, err
}
// Every clicked link event implies they opened the email
s.OpenedEmail += s.ClickedLink
err = query.Where("status=?", EVENT_SENT).Count(&s.EmailsSent).Error
if err != nil {
return s, err
}
// Every opened email event implies the email was sent
s.EmailsSent += s.OpenedEmail
err = query.Where("status=?", ERROR).Count(&s.Error).Error
return s, err
}

View file

@ -1,6 +1,6 @@
{
"name": "gophish",
"version": "0.3.0-dev",
"version": "0.4.0-dev",
"repository": {
"type": "git",
"url": "git+https://github.com/gophish/gophish.git"

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

22
static/css/main.css vendored
View file

@ -541,3 +541,25 @@ table.dataTable{
.input-group-btn .btn {
line-height:20px !important;
}
.highcharts-title {
font-family: "Source Sans Pro",Helvetica,Arial,sans-serif;
}
.color-success {
font-weight: bold;
color: #f05b4f;
}
.color-sent {
font-weight: bold;
color: #1abc9c;
}
.color-opened {
font-weight: bold;
color: #f9bf3b;
}
.color-clicked {
font-weight: bold;
color: #f39c12;
}
.color-success {
color: #f05b4f;
}

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load diff

Before

Width:  |  Height:  |  Size: 357 KiB

After

Width:  |  Height:  |  Size: 434 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -4,64 +4,70 @@ var doPoll = true;
// statuses is a helper map to point result statuses to ui classes
var statuses = {
"Email Sent": {
slice: "ct-slice-donut-sent",
legend: "ct-legend-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": {
slice: "ct-slice-donut-opened",
legend: "ct-legend-opened",
color: "#f9bf3b",
label: "label-warning",
icon: "fa-envelope",
point: "ct-point-opened"
},
"Clicked Link": {
slice: "ct-slice-donut-clicked",
legend: "ct-legend-clicked",
color: "#F39C12",
label: "label-clicked",
icon: "fa-mouse-pointer",
point: "ct-point-clicked"
},
"Success": {
slice: "ct-slice-donut-success",
legend: "ct-legend-success",
color: "#f05b4f",
label: "label-danger",
icon: "fa-exclamation",
point: "ct-point-clicked"
},
"Error": {
slice: "ct-slice-donut-error",
legend: "ct-legend-error",
color: "#6c7a89",
label: "label-default",
icon: "fa-times",
point: "ct-point-error"
},
"Error Sending Email": {
slice: "ct-slice-donut-error",
legend: "ct-legend-error",
color: "#6c7a89",
label: "label-default",
icon: "fa-times",
point: "ct-point-error"
},
"Submitted Data": {
slice: "ct-slice-donut-success",
legend: "ct-legend-success",
color: "#f05b4f",
label: "label-danger",
icon: "fa-exclamation",
point: "ct-point-clicked"
},
"Unknown": {
slice: "ct-slice-donut-error",
legend: "ct-legend-error",
color: "#6c7a89",
label: "label-default",
icon: "fa-question",
point: "ct-point-error"
},
"Sending": {
slice: "ct-slice-donut-sending",
legend: "ct-legend-sending",
color: "#428bca",
label: "label-primary",
icon: "fa-spinner",
point: "ct-point-sending"
@ -72,6 +78,22 @@ var statuses = {
}
}
var statusMapping = {
"Email Sent": "sent",
"Email Opened": "opened",
"Clicked Link": "clicked",
"Submitted Data": "submitted_data",
}
// 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 = []
@ -93,24 +115,25 @@ function deleteCampaign() {
confirmButtonColor: "#428bca",
reverseButtons: true,
allowOutsideClick: false,
preConfirm: function() {
return new Promise(function(resolve, reject) {
showLoaderOnConfirm: true,
preConfirm: function () {
return new Promise(function (resolve, reject) {
api.campaignId.delete(campaign.id)
.success(function(msg) {
.success(function (msg) {
resolve()
})
.error(function(data) {
.error(function (data) {
reject(data.responseJSON.message)
})
})
}
}).then(function() {
}).then(function () {
swal(
'Campaign Deleted!',
'This campaign has been deleted!',
'success'
);
$('button:contains("OK")').on('click', function() {
$('button:contains("OK")').on('click', function () {
location.href = '/campaigns'
})
})
@ -128,18 +151,19 @@ function completeCampaign() {
confirmButtonColor: "#428bca",
reverseButtons: true,
allowOutsideClick: false,
preConfirm: function() {
return new Promise(function(resolve, reject) {
showLoaderOnConfirm: true,
preConfirm: function () {
return new Promise(function (resolve, reject) {
api.campaignId.complete(campaign.id)
.success(function(msg) {
.success(function (msg) {
resolve()
})
.error(function(data) {
.error(function (data) {
reject(data.responseJSON.message)
})
})
}
}).then(function() {
}).then(function () {
swal(
'Campaign Completed!',
'This campaign has been completed!',
@ -190,32 +214,32 @@ function replay(event_idx) {
details = JSON.parse(request.details)
url = null
form = $('<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;
}
$('<input>').attr({
name: param,
}).val(details.payload[param]).appendTo(form);
})
/* Ensure we know where to send the user */
// Prompt for the URL
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;
}
$('<input>').attr({
name: param,
}).val(details.payload[param]).appendTo(form);
})
/* Ensure we know where to send the user */
// Prompt for the URL
swal({
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) {
inputValidator: function (value) {
return new Promise(function (resolve, reject) {
if (value) {
resolve();
} else {
@ -223,7 +247,7 @@ function replay(event_idx) {
}
});
}
}).then(function(result) {
}).then(function (result) {
url = result
submitForm()
})
@ -249,7 +273,7 @@ function renderTimeline(data) {
'<h6>Timeline for ' + escapeHtml(record.first_name) + ' ' + escapeHtml(record.last_name) +
'</h6><span class="subtitle">Email: ' + escapeHtml(record.email) + '</span>' +
'<div class="timeline-graph col-sm-6">'
$.each(campaign.timeline, function(i, event) {
$.each(campaign.timeline, function (i, event) {
if (!event.email || event.email == record.email) {
// Add the event
results += '<div class="timeline-entry">' +
@ -270,7 +294,7 @@ function renderTimeline(data) {
results += '<div class="timeline-event-results">'
results += ' <table class="table table-condensed table-bordered table-striped">'
results += ' <thead><tr><th>Parameter</th><th>Value(s)</tr></thead><tbody>'
$.each(Object.keys(details.payload), function(i, param) {
$.each(Object.keys(details.payload), function (i, param) {
if (param == "rid") {
return true;
}
@ -296,6 +320,131 @@ function renderTimeline(data) {
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)) +
'<br>Event: ' + this.point.message + '<br>Email: <b>' + this.point.email + '</b>'
}
},
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
}]
})
}
/* 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].y, 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].y })
}
}
},
title: {
text: chartopts['title']
},
plotOptions: {
pie: {
innerSize: '80%',
dataLabels: {
enabled: false
}
}
},
credits: {
enabled: false
},
tooltip: {
formatter: function () {
if (this.key == undefined) {
return false
}
return '<span style="color:' + this.color + '">\u25CF</span>' + this.point.name + ': <b>' + this.y + '</b><br/>'
}
},
series: [{
data: chartopts['data'],
colors: chartopts['colors'],
}]
})
}
/* poll - Queries the API and updates the UI with the results
*
@ -307,81 +456,94 @@ function renderTimeline(data) {
*/
function poll() {
api.campaignId.results(campaign.id)
.success(function(c) {
.success(function (c) {
campaign = c
/* Update the timeline */
var timeline_data = {
series: [{
name: "Events",
data: []
}]
}
$.each(campaign.timeline, function(i, event) {
timeline_data.series[0].data.push({
meta: i,
x: new Date(event.time),
/* Update the timeline */
var timeline_series_data = []
$.each(campaign.timeline, function (i, event) {
var event_date = moment(event.time)
timeline_series_data.push({
email: event.email,
x: event_date.valueOf(),
y: 1
})
})
var timeline_chart = $("#timeline_chart")
if (timeline_chart.get(0).__chartist__) {
timeline_chart.get(0).__chartist__.update(timeline_data)
}
var timeline_series_data = []
$.each(campaign.timeline, function (i, event) {
var event_date = moment(event.time)
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_data = {
series: []
}
var email_series_data = {}
$.each(campaign.results, function(i, result) {
if (!email_series_data[result.status]) {
email_series_data[result.status] = 1
} else {
email_series_data[result.status]++;
// 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]++;
// Backfill status values
var step = progressListing.indexOf(result.status)
for (var i = 0; i < step; i++) {
email_series_data[progressListing[i]]++
}
})
$("#email_chart_legend").html("")
$.each(email_series_data, function(status, count) {
email_data.series.push({
meta: status,
value: count
$.each(email_series_data, function (status, count) {
var email_data = []
if (!(status in statusMapping)) {
return true
}
email_data.push({
name: status,
y: count
})
email_data.push({
name: '',
y: campaign.results.length - count
})
var chart = $("#" + statusMapping[status] + "_chart").highcharts()
chart.series[0].update({
data: email_data
})
$("#email_chart_legend").append('<li><span class="' + statuses[status].legend + '"></span>' + status + '</li>')
})
var email_chart = $("#email_chart")
if (email_chart.get(0).__chartist__) {
email_chart.get(0).__chartist__.on('draw', function(data) {
data.element.addClass(statuses[data.meta].slice)
})
// Update with the latest data
email_chart.get(0).__chartist__.update(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) {
var label = statuses[result.status].label || "label-default";
rowData[6] = "<span class=\"label " + label + "\">" + result.status + "</span>"
resultsTable.row(i).data(rowData).draw(false)
if (row.child.isShown()) {
row.child(renderTimeline(row.data()))
}
return false
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) {
var label = statuses[result.status].label || "label-default";
rowData[6] = "<span class=\"label " + label + "\">" + result.status + "</span>"
resultsTable.row(i).data(rowData).draw(false)
if (row.child.isShown()) {
row.child(renderTimeline(row.data()))
}
})
return false
}
})
/* Update the map information */
})
/* Update the map information */
bubbles = []
$.each(campaign.results, function(i, result) {
$.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) {
$.each(bubbles, function (i, bubble) {
if (bubble.ip == result.ip) {
bubbles[i].radius += 1
newIP = false
@ -407,13 +569,13 @@ function poll() {
function load() {
campaign.id = window.location.pathname.split('/').slice(-1)[0]
api.campaignId.results(campaign.id)
.success(function(c) {
.success(function (c) {
campaign = c
if (campaign) {
$("title").text(c.name + " - Gophish")
$("#loading").hide()
$("#campaignResults").show()
// Set the title
// Set the title
$("#page-title").text("Results for " + c.name)
if (c.status == "Completed") {
$('#complete_button')[0].disabled = true;
@ -422,57 +584,21 @@ function load() {
}
// Setup tooltips
$('[data-toggle="tooltip"]').tooltip()
// 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 our graphs
var timeline_data = {
series: [{
name: "Events",
data: []
}]
}
var email_data = {
series: []
}
var email_legend = {}
var email_series_data = {}
var timeline_opts = {
axisX: {
showGrid: false,
type: Chartist.FixedScaleAxis,
divisor: 5,
labelInterpolationFnc: function(value) {
return moment(value).format('MMMM Do YYYY h:mm a')
}
},
axisY: {
type: Chartist.FixedScaleAxis,
ticks: [0, 1, 2],
low: 0,
showLabel: false
},
showArea: false,
plugins: []
}
var email_opts = {
donut: true,
donutWidth: 40,
chartPadding: 0,
showLabel: 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
})
// Setup the results table
resultsTable = $("#resultsTable").DataTable({
destroy: true,
"order": [
@ -490,25 +616,31 @@ function load() {
}]
});
resultsTable.clear();
$.each(campaign.results, function(i, result) {
label = statuses[result.status].label || "label-default";
resultsTable.row.add([
result.id,
"<i class=\"fa fa-caret-right\"></i>",
escapeHtml(result.first_name) || "",
escapeHtml(result.last_name) || "",
escapeHtml(result.email) || "",
escapeHtml(result.position) || "",
"<span class=\"label " + label + "\">" + result.status + "</span>"
]).draw()
if (!email_series_data[result.status]) {
email_series_data[result.status] = 1
} else {
email_series_data[result.status]++;
}
})
// Setup the individual timelines
$('#resultsTable tbody').on('click', 'td.details-control', function() {
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) {
label = statuses[result.status].label || "label-default";
resultsTable.row.add([
result.id,
"<i class=\"fa fa-caret-right\"></i>",
escapeHtml(result.first_name) || "",
escapeHtml(result.last_name) || "",
escapeHtml(result.email) || "",
escapeHtml(result.position) || "",
"<span class=\"label " + label + "\">" + result.status + "</span>"
]).draw()
email_series_data[result.status]++;
// Backfill status values
var step = progressListing.indexOf(result.status)
for (var i = 0; i < step; i++) {
email_series_data[progressListing[i]]++
}
})
// 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()) {
@ -528,88 +660,42 @@ function load() {
}
});
// Setup the graphs
$.each(campaign.timeline, function(i, event) {
timeline_data.series[0].data.push({
meta: i,
x: new Date(event.time),
y: 1
})
})
$("#email_chart_legend").html("")
$.each(email_series_data, function(status, count) {
email_data.series.push({
meta: status,
value: count
})
$("#email_chart_legend").append('<li><span class="' + statuses[status].legend + '"></span>' + status + '</li>')
})
var timeline_chart = new Chartist.Line('#timeline_chart', timeline_data, timeline_opts)
timeline_chart.on('draw', function(data) {
if (data.type === "point") {
var point_style = statuses[campaign.timeline[data.meta].message].point
var circle = new Chartist.Svg("circle", {
cx: [data.x],
cy: [data.y],
r: 5,
fill: "#283F50",
meta: data.meta,
value: 1,
}, point_style + ' ct-timeline-point')
data.element.replace(circle)
$.each(campaign.timeline, function (i, event) {
var event_date = moment(event.time)
timeline_series_data.push({
email: event.email,
message: event.message,
x: event_date.valueOf(),
y: 1,
marker: {
fillColor: statuses[event.message].color
}
})
// Setup the overview chart listeners
$chart = $("#timeline_chart")
var $toolTip = $chart
.append('<div class="chartist-tooltip"></div>')
.find('.chartist-tooltip')
.hide();
$chart.on('mouseenter', '.ct-timeline-point', function() {
var $point = $(this)
cidx = $point.attr('meta')
html = "Event: " + campaign.timeline[cidx].message
if (campaign.timeline[cidx].email) {
html += '<br>' + "Email: " + escapeHtml(campaign.timeline[cidx].email)
})
renderTimelineChart({
data: timeline_series_data
})
$.each(email_series_data, function (status, count) {
var email_data = []
if (!(status in statusMapping)) {
return true
}
$toolTip.html(html).show()
});
$chart.on('mouseleave', '.ct-timeline-point', function() {
$toolTip.hide();
});
$chart.on('mousemove', function(event) {
$toolTip.css({
left: (event.offsetX || event.originalEvent.layerX) - $toolTip.width() / 2 - 10,
top: (event.offsetY + 70 || event.originalEvent.layerY) - $toolTip.height() - 40
});
});
var email_chart = new Chartist.Pie("#email_chart", email_data, email_opts)
email_chart.on('draw', function(data) {
data.element.addClass(statuses[data.meta].slice)
email_data.push({
name: status,
y: count
})
// Setup the average chart listeners
$piechart = $("#email_chart")
var $pietoolTip = $piechart
.append('<div class="chartist-tooltip"></div>')
.find('.chartist-tooltip')
.hide();
$piechart.on('mouseenter', '.ct-slice-donut', function() {
var $point = $(this)
value = $point.attr('ct:value')
label = $point.attr('ct:meta')
$pietoolTip.html(label + ': ' + value.toString()).show();
});
$piechart.on('mouseleave', '.ct-slice-donut', function() {
$pietoolTip.hide();
});
$piechart.on('mousemove', function(event) {
$pietoolTip.css({
left: (event.offsetX || event.originalEvent.layerX) - $pietoolTip.width() / 2 - 10,
top: (event.offsetY + 40 || event.originalEvent.layerY) - $pietoolTip.height() - 80
});
});
email_data.push({
name: '',
y: campaign.results.length - count
})
var chart = renderPieChart({
elemId: statusMapping[status] + '_chart',
title: status,
name: status,
data: email_data,
colors: [statuses[status].color, '#dddddd']
})
})
if (!map) {
map = new Datamap({
element: document.getElementById("resultsMap"),
@ -627,13 +713,13 @@ function load() {
}
});
}
$.each(campaign.results, function(i, result) {
$.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) {
$.each(bubbles, function (i, bubble) {
if (bubble.ip == result.ip) {
bubbles[i].radius += 1
newIP = false
@ -653,7 +739,7 @@ function load() {
map.bubbles(bubbles)
}
// Load up the map data (only once!)
$('a[data-toggle="tab"]').on('shown.bs.tab', function(e) {
$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
if ($(e.target).attr('href') == "#overview") {
if (!map) {
map = new Datamap({
@ -671,7 +757,7 @@ function load() {
}
})
})
.error(function() {
.error(function () {
$("#loading").hide()
errorFlash(" Campaign not found!")
})
@ -691,9 +777,13 @@ function refresh() {
$(document).ready(function() {
$(document).ready(function () {
Highcharts.setOptions({
global: {
useUTC: false
}
})
load();
// Start the polling loop
// Start the polling loop
setRefresh = setTimeout(refresh, 60000)

View file

@ -3,15 +3,13 @@ var campaigns = []
// statuses is a helper map to point result statuses to ui classes
var statuses = {
"Email Sent": {
slice: "ct-slice-donut-sent",
legend: "ct-legend-sent",
color: "#1abc9c",
label: "label-success",
icon: "fa-envelope",
point: "ct-point-sent"
},
"Emails Sent": {
slice: "ct-slice-donut-sent",
legend: "ct-legend-sent",
color: "#1abc9c",
label: "label-success",
icon: "fa-envelope",
point: "ct-point-sent"
@ -26,57 +24,49 @@ var statuses = {
label: "label-success"
},
"Email Opened": {
slice: "ct-slice-donut-opened",
legend: "ct-legend-opened",
color: "#f9bf3b",
label: "label-warning",
icon: "fa-envelope",
point: "ct-point-opened"
},
"Clicked Link": {
slice: "ct-slice-donut-clicked",
legend: "ct-legend-clicked",
color: "#F39C12",
label: "label-clicked",
icon: "fa-mouse-pointer",
point: "ct-point-clicked"
},
"Success": {
slice: "ct-slice-donut-success",
legend: "ct-legend-success",
color: "#f05b4f",
label: "label-danger",
icon: "fa-exclamation",
point: "ct-point-clicked"
},
"Error": {
slice: "ct-slice-donut-error",
legend: "ct-legend-error",
color: "#6c7a89",
label: "label-default",
icon: "fa-times",
point: "ct-point-error"
},
"Error Sending Email": {
slice: "ct-slice-donut-error",
legend: "ct-legend-error",
color: "#6c7a89",
label: "label-default",
icon: "fa-times",
point: "ct-point-error"
},
"Submitted Data": {
slice: "ct-slice-donut-success",
legend: "ct-legend-success",
color: "#f05b4f",
label: "label-danger",
icon: "fa-exclamation",
point: "ct-point-clicked"
},
"Unknown": {
slice: "ct-slice-donut-error",
legend: "ct-legend-error",
color: "#6c7a89",
label: "label-default",
icon: "fa-question",
point: "ct-point-error"
},
"Sending": {
slice: "ct-slice-donut-sending",
legend: "ct-legend-sending",
color: "#428bca",
label: "label-primary",
icon: "fa-spinner",
point: "ct-point-sending"
@ -92,34 +82,80 @@ var statsMapping = {
"opened": "Email Opened",
"clicked": "Clicked Link",
"submitted_data": "Submitted Data",
"error": "Error"
}
function deleteCampaign(idx) {
if (confirm("Delete " + campaigns[idx].name + "?")) {
api.campaignId.delete(campaigns[idx].id)
.success(function(data) {
.success(function (data) {
successFlash(data.message)
location.reload()
})
}
}
function generateStatsPieChart(campaigns) {
var stats_opts = {
donut: true,
donutWidth: 40,
chartPadding: 0,
showLabel: false
}
/* Renders a pie chart using the provided chartops */
function renderPieChart(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 '<span style="color:' + this.color + '">\u25CF</span>' + this.point.name + ': <b>' + this.y + '%</b><br/>'
}
},
series: [{
data: chartopts['data'],
colors: chartopts['colors'],
}]
})
}
function generateStatsPieCharts(campaigns) {
var stats_data = []
var stats_series_data = {}
var stats_data = {
series: []
}
var total = 0
$.each(campaigns, function(i, campaign) {
$.each(campaign.stats, function(status, count) {
$.each(campaigns, function (i, campaign) {
$.each(campaign.stats, function (status, count) {
if (status == "total") {
total += count
return true
@ -131,153 +167,172 @@ function generateStatsPieChart(campaigns) {
}
})
})
$.each(stats_series_data, function(status, count) {
$.each(stats_series_data, function (status, count) {
// I don't like this, but I guess it'll have to work.
// Turns submitted_data into Submitted Data
if (!(status in statsMapping)) {
return true
}
status_label = statsMapping[status]
stats_data.series.push({
meta: status_label,
value: Math.floor((count / total) * 100)
stats_data.push({
name: status_label,
y: Math.floor((count / total) * 100),
count: count
})
$("#stats_chart_legend").append('<li><span class="' + statuses[status_label].legend + '"></span>' + status_label + '</li>')
})
var stats_chart = new Chartist.Pie("#stats_chart", stats_data, stats_opts)
$piechart = $("#stats_chart")
var $pietoolTip = $piechart
.append('<div class="chartist-tooltip"></div>')
.find('.chartist-tooltip')
.hide();
$piechart.get(0).__chartist__.on('draw', function(data) {
data.element.addClass(statuses[data.meta].slice)
stats_data.push({
name: '',
y: 100 - Math.floor((count / total) * 100)
})
// Update with the latest data
$piechart.get(0).__chartist__.update(stats_data)
$piechart.on('mouseenter', '.ct-slice-donut', function() {
var $point = $(this)
value = $point.attr('ct:value')
label = $point.attr('ct:meta')
$pietoolTip.html(label + ': ' + value.toString() + "%").show();
});
$piechart.on('mouseleave', '.ct-slice-donut', function() {
$pietoolTip.hide();
});
$piechart.on('mousemove', function(event) {
$pietoolTip.css({
left: (event.offsetX || event.originalEvent.layerX) - $pietoolTip.width() / 2 - 10,
top: (event.offsetY + 40 || event.originalEvent.layerY) - $pietoolTip.height() - 80
});
var stats_chart = renderPieChart({
elemId: status + '_chart',
title: status_label,
name: status,
data: stats_data,
colors: [statuses[status_label].color, "#dddddd"]
})
stats_data = []
});
}
$(document).ready(function() {
function generateTimelineChart(campaigns) {
var overview_data = []
$.each(campaigns, function (i, campaign) {
var campaign_date = moment(campaign.created_date)
// Add it to the chart data
campaign.y = 0
// Clicked events also contain our data submitted events
campaign.y += campaign.stats.clicked
campaign.y = Math.floor((campaign.y / campaign.stats.total) * 100)
// Add the data to the overview chart
overview_data.push({
campaign_id: campaign.id,
name: campaign.name,
x: campaign_date.valueOf(),
y: campaign.y
})
})
Highcharts.chart('overview_chart', {
chart: {
zoomType: 'x',
type: 'areaspline'
},
title: {
text: 'Phishing Success Overview'
},
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: 100,
title: {
text: "% of Success"
}
},
tooltip: {
formatter: function () {
return Highcharts.dateFormat('%A, %b %d %l:%M:%S %P', new Date(this.x)) +
'<br>' + this.point.name + '<br>% Success: <b>' + this.y + '%</b>'
}
},
legend: {
enabled: false
},
plotOptions: {
series: {
marker: {
enabled: true,
symbol: 'circle',
radius: 3
},
cursor: 'pointer',
point: {
events: {
click: function (e) {
window.location.href = "/campaigns/" + this.campaign_id
}
}
}
}
},
credits: {
enabled: false
},
series: [{
data: overview_data,
color: "#f05b4f",
fillOpacity: 0.5
}]
})
}
$(document).ready(function () {
api.campaigns.summary()
.success(function(data) {
.success(function (data) {
$("#loading").hide()
campaigns = data.campaigns
if (campaigns.length > 0) {
$("#dashboard").show()
// Create the overview chart data
var overview_data = {
labels: [],
series: [
[]
]
}
var overview_opts = {
axisX: {
showGrid: false
},
showArea: true,
plugins: [],
low: 0,
high: 100
}
// Create the overview chart data
campaignTable = $("#campaignTable").DataTable({
columnDefs: [{
orderable: false,
targets: "no-sort"
}],
},
{ className: "color-sent", targets: [2] },
{ className: "color-opened", targets: [3] },
{ className: "color-clicked", targets: [4] },
{ className: "color-success", targets: [5] }],
order: [
[1, "desc"]
]
});
$.each(campaigns, function(i, campaign) {
var campaign_date = moment(campaign.created_date).format('MMMM Do YYYY, h:mm:ss a')
var label = statuses[campaign.status].label || "label-default";
//section for tooltips on the status of a campaign to show some quick stats
var launchDate;
if (moment(campaign.launch_date).isAfter(moment())) {
launchDate = "Scheduled to start: " + moment(campaign.launch_date).format('MMMM Do YYYY, h:mm:ss a')
var quickStats = launchDate + "<br><br>" + "Number of recipients: " + campaign.stats.total
} else {
launchDate = "Launch Date: " + moment(campaign.launch_date).format('MMMM Do YYYY, h:mm:ss a')
var quickStats = launchDate + "<br><br>" + "Number of recipients: " + campaign.stats.total + "<br><br>" + "Emails opened: " + campaign.stats.opened + "<br><br>" + "Emails clicked: " + campaign.stats.clicked + "<br><br>" + "Submitted Credentials: " + campaign.stats.submitted_data + "<br><br>" + "Errors : " + campaign.stats.error
}
// Add it to the table
campaignTable.row.add([
escapeHtml(campaign.name),
campaign_date,
"<span class=\"label " + label + "\" data-toggle=\"tooltip\" data-placement=\"right\" data-html=\"true\" title=\"" + quickStats + "\">" + campaign.status + "</span>",
"<div class='pull-right'><a class='btn btn-primary' href='/campaigns/" + campaign.id + "' data-toggle='tooltip' data-placement='left' title='View Results'>\
$.each(campaigns, function (i, campaign) {
var campaign_date = moment(campaign.created_date).format('MMMM Do YYYY, h:mm:ss a')
var label = statuses[campaign.status].label || "label-default";
//section for tooltips on the status of a campaign to show some quick stats
var launchDate;
if (moment(campaign.launch_date).isAfter(moment())) {
launchDate = "Scheduled to start: " + moment(campaign.launch_date).format('MMMM Do YYYY, h:mm:ss a')
var quickStats = launchDate + "<br><br>" + "Number of recipients: " + campaign.stats.total
} else {
launchDate = "Launch Date: " + moment(campaign.launch_date).format('MMMM Do YYYY, h:mm:ss a')
var quickStats = launchDate + "<br><br>" + "Number of recipients: " + campaign.stats.total + "<br><br>" + "Emails opened: " + campaign.stats.opened + "<br><br>" + "Emails clicked: " + campaign.stats.clicked + "<br><br>" + "Submitted Credentials: " + campaign.stats.submitted_data + "<br><br>" + "Errors : " + campaign.stats.error
}
// Add it to the table
campaignTable.row.add([
escapeHtml(campaign.name),
campaign_date,
campaign.stats.sent,
campaign.stats.opened,
campaign.stats.clicked,
campaign.stats.submitted_data,
"<span class=\"label " + label + "\" data-toggle=\"tooltip\" data-placement=\"right\" data-html=\"true\" title=\"" + quickStats + "\">" + campaign.status + "</span>",
"<div class='pull-right'><a class='btn btn-primary' href='/campaigns/" + campaign.id + "' data-toggle='tooltip' data-placement='left' title='View Results'>\
<i class='fa fa-bar-chart'></i>\
</a>\
<button class='btn btn-danger' onclick='deleteCampaign(" + i + ")' data-toggle='tooltip' data-placement='left' title='Delete Campaign'>\
<i class='fa fa-trash-o'></i>\
</button></div>"
]).draw()
$('[data-toggle="tooltip"]').tooltip()
// Add it to the chart data
campaign.y = 0
campaign.y += campaign.stats.clicked + campaign.stats.submitted_data
campaign.y = Math.floor((campaign.y / campaign.stats.total) * 100)
// Add the data to the overview chart
overview_data.labels.push(campaign_date)
overview_data.series[0].push({
meta: i,
value: campaign.y
})
})
// Build the charts
generateStatsPieChart(campaigns)
var overview_chart = new Chartist.Line('#overview_chart', overview_data, overview_opts)
// Setup the overview chart listeners
$chart = $("#overview_chart")
var $toolTip = $chart
.append('<div class="chartist-tooltip"></div>')
.find('.chartist-tooltip')
.hide();
$chart.on('mouseenter', '.ct-point', function() {
var $point = $(this)
value = $point.attr('ct:value') || 0
cidx = $point.attr('ct:meta')
$toolTip.html(campaigns[cidx].name + '<br>' + "Successes: " + value.toString() + "%").show();
});
$chart.on('mouseleave', '.ct-point', function() {
$toolTip.hide();
});
$chart.on('mousemove', function(event) {
$toolTip.css({
left: (event.offsetX || event.originalEvent.layerX) - $toolTip.width() / 2 - 10,
top: (event.offsetY + 40 || event.originalEvent.layerY) - $toolTip.height() - 40
});
});
$("#overview_chart").on("click", ".ct-point", function(e) {
var $cidx = $(this).attr('ct:meta');
window.location.href = "/campaigns/" + campaigns[cidx].id
});
]).draw()
$('[data-toggle="tooltip"]').tooltip()
})
// Build the charts
generateStatsPieCharts(campaigns)
generateTimelineChart(campaigns)
} else {
$("#emptyMessage").show()
}
})
.error(function() {
.error(function () {
errorFlash("Error fetching campaigns")
})
})

View file

@ -41,165 +41,165 @@ var api = {
// campaigns contains the endpoints for /campaigns
campaigns: {
// get() - Queries the API for GET /campaigns
get: function() {
get: function () {
return query("/campaigns/", "GET", {}, false)
},
// post() - Posts a campaign to POST /campaigns
post: function(data) {
post: function (data) {
return query("/campaigns/", "POST", data, false)
},
// summary() - Queries the API for GET /campaigns/summary
summary: function() {
summary: function () {
return query("/campaigns/summary", "GET", {}, false)
}
},
// campaignId contains the endpoints for /campaigns/:id
campaignId: {
// get() - Queries the API for GET /campaigns/:id
get: function(id) {
get: function (id) {
return query("/campaigns/" + id, "GET", {}, true)
},
// delete() - Deletes a campaign at DELETE /campaigns/:id
delete: function(id) {
delete: function (id) {
return query("/campaigns/" + id, "DELETE", {}, false)
},
// results() - Queries the API for GET /campaigns/:id/results
results: function(id) {
results: function (id) {
return query("/campaigns/" + id + "/results", "GET", {}, true)
},
// complete() - Completes a campaign at POST /campaigns/:id/complete
complete: function(id) {
complete: function (id) {
return query("/campaigns/" + id + "/complete", "GET", {}, true)
},
// summary() - Queries the API for GET /campaigns/summary
summary: function(id) {
summary: function (id) {
return query("/campaigns/" + id + "/summary", "GET", {}, true)
}
},
// groups contains the endpoints for /groups
groups: {
// get() - Queries the API for GET /groups
get: function() {
get: function () {
return query("/groups/", "GET", {}, false)
},
// post() - Posts a group to POST /groups
post: function(group) {
post: function (group) {
return query("/groups/", "POST", group, false)
},
// summary() - Queries the API for GET /groups/summary
summary: function() {
summary: function () {
return query("/groups/summary", "GET", {}, true)
}
},
// groupId contains the endpoints for /groups/:id
groupId: {
// get() - Queries the API for GET /groups/:id
get: function(id) {
get: function (id) {
return query("/groups/" + id, "GET", {}, false)
},
// put() - Puts a group to PUT /groups/:id
put: function(group) {
put: function (group) {
return query("/groups/" + group.id, "PUT", group, false)
},
// delete() - Deletes a group at DELETE /groups/:id
delete: function(id) {
delete: function (id) {
return query("/groups/" + id, "DELETE", {}, false)
}
},
// templates contains the endpoints for /templates
templates: {
// get() - Queries the API for GET /templates
get: function() {
get: function () {
return query("/templates/", "GET", {}, false)
},
// post() - Posts a template to POST /templates
post: function(template) {
post: function (template) {
return query("/templates/", "POST", template, false)
}
},
// templateId contains the endpoints for /templates/:id
templateId: {
// get() - Queries the API for GET /templates/:id
get: function(id) {
get: function (id) {
return query("/templates/" + id, "GET", {}, false)
},
// put() - Puts a template to PUT /templates/:id
put: function(template) {
put: function (template) {
return query("/templates/" + template.id, "PUT", template, false)
},
// delete() - Deletes a template at DELETE /templates/:id
delete: function(id) {
delete: function (id) {
return query("/templates/" + id, "DELETE", {}, false)
}
},
// pages contains the endpoints for /pages
pages: {
// get() - Queries the API for GET /pages
get: function() {
get: function () {
return query("/pages/", "GET", {}, false)
},
// post() - Posts a page to POST /pages
post: function(page) {
post: function (page) {
return query("/pages/", "POST", page, false)
}
},
// pageId contains the endpoints for /pages/:id
pageId: {
// get() - Queries the API for GET /pages/:id
get: function(id) {
get: function (id) {
return query("/pages/" + id, "GET", {}, false)
},
// put() - Puts a page to PUT /pages/:id
put: function(page) {
put: function (page) {
return query("/pages/" + page.id, "PUT", page, false)
},
// delete() - Deletes a page at DELETE /pages/:id
delete: function(id) {
delete: function (id) {
return query("/pages/" + id, "DELETE", {}, false)
}
},
// SMTP contains the endpoints for /smtp
SMTP: {
// get() - Queries the API for GET /smtp
get: function() {
get: function () {
return query("/smtp/", "GET", {}, false)
},
// post() - Posts a SMTP to POST /smtp
post: function(smtp) {
post: function (smtp) {
return query("/smtp/", "POST", smtp, false)
}
},
// SMTPId contains the endpoints for /smtp/:id
SMTPId: {
// get() - Queries the API for GET /smtp/:id
get: function(id) {
get: function (id) {
return query("/smtp/" + id, "GET", {}, false)
},
// put() - Puts a SMTP to PUT /smtp/:id
put: function(smtp) {
put: function (smtp) {
return query("/smtp/" + smtp.id, "PUT", smtp, false)
},
// delete() - Deletes a SMTP at DELETE /smtp/:id
delete: function(id) {
delete: function (id) {
return query("/smtp/" + id, "DELETE", {}, false)
}
},
// import handles all of the "import" functions in the api
import_email: function(raw) {
import_email: function (raw) {
return query("/import/email", "POST", {}, false)
},
// clone_site handles importing a site by url
clone_site: function(req) {
clone_site: function (req) {
return query("/import/site", "POST", req, false)
},
// send_test_email sends an email to the specified email address
send_test_email: function(req) {
send_test_email: function (req) {
return query("/util/send_test_email", "POST", req, true)
}
}
// Register our moment.js datatables listeners
$(document).ready(function() {
$(document).ready(function () {
$.fn.dataTable.moment('MMMM Do YYYY, h:mm:ss a');
// Setup tooltips
$('[data-toggle="tooltip"]').tooltip()

View file

@ -17,7 +17,9 @@
</li>
<li><a href="/settings">Settings</a>
</li>
<li><hr></li>
<li>
<hr>
</li>
<li><a href="https://gophish.gitbooks.io/user-guide/content/">User Guide</a>
</li>
<li><a href="/api/">API Documentation</a>
@ -38,16 +40,17 @@
<a href="/campaigns" class="btn btn-default">
<i class="fa fa-arrow-circle-o-left fa-lg"></i> Back
</a>
<div class="btn-group">
<button type="button" id="exportButton" class="btn btn-primary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
<i class="fa fa-file-excel-o"></i> Export CSV
<i class="fa fa-caret-down"></i>
</button>
<ul class="dropdown-menu" aria-labelledby="exportButton">
<li><a href="#" onclick="exportAsCSV('results')">Results</a></li>
<li><a href="#" onclick="exportAsCSV('events')">Raw Events</a></li>
</ul>
</div>
<div class="btn-group">
<button type="button" id="exportButton" class="btn btn-primary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true"
aria-expanded="true">
<i class="fa fa-file-excel-o"></i> Export CSV
<i class="fa fa-caret-down"></i>
</button>
<ul class="dropdown-menu" aria-labelledby="exportButton">
<li><a href="#" onclick="exportAsCSV('results')">Results</a></li>
<li><a href="#" onclick="exportAsCSV('events')">Raw Events</a></li>
</ul>
</div>
<button id="complete_button" type="button" class="btn btn-blue" data-toggle="tooltip" onclick="completeCampaign()">
<i class="fa fa-flag-checkered"></i> Complete
</button>
@ -57,72 +60,60 @@
<button id="refresh_btn" type="button" class="btn btn-blue" data-toggle="tooltip" onclick="refresh()">
<i class="fa fa-refresh fa-lg"></i> Refresh
</button>
<span id="refresh_message">
<i class="fa fa-spin fa-spinner"></i> Refreshing
<span id="refresh_message">
<i class="fa fa-spin fa-spinner"></i> Refreshing
</span>
</div>
<br />
<div class="row">
<ul class="nav nav-tabs" role="tablist">
<li class="active"><a href="#overview" aria-controls="home" role="tab" data-toggle="tab">Overview</a></li>
<!--<li><a href="#plugins" aria-controls="profile" role="tab" data-toggle="tab">Plugins</a></li>
<li><a href="#demographics" aria-controls="settings" role="tab" data-toggle="tab">Demographics</a></li>-->
</ul>
<div class="tab-content">
<div role="tabpanel" class="tab-pane active" id="overview">
<br/>
<div class="row">
<div class="col-lg-6 col-md-6 col-sm-12 col-xs-12">
<p style="text-align:center;">Campaign Timeline</p>
<br/>
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
<div id="timeline_chart"></div>
</div>
<div class="col-lg-6 col-md-6 col-sm-12 col-xs-12">
<p style="text-align:center;">Email Status</p>
<div id="email_chart" class="col-lg-7 col-md-7"></div>
<div class="col-lg-5 col-md-5">
<ul id="email_chart_legend" class="chartist-legend">
</ul>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<p style="text-align:center;">Targets Map</p>
<div id="resultsMap"></div>
</div>
</div>
<div class="row">
<div id="sent_chart" style="height:200px;" class="col-lg-3 col-md-3"></div>
<div id="opened_chart" style="height:200px;" class="col-lg-3 col-md-3"></div>
<div id="clicked_chart" style="height:200px;" class="col-lg-3 col-md-3"></div>
<div id="submitted_data_chart" style="height:200px;" class="col-lg-3 col-md-3"></div>
</div>
</div>
<!--
<div role="tabpanel" class="tab-pane" id="plugins">
<div class="row">
<div class="col-md-6">
<p style="text-align:center;">Targets Map</p>
<div id="resultsMap"></div>
</div>
</div>
<div role="tabpanel" class="tab-pane" id="demographics">
Demographics here
</div> -->
</div>
</div>
<div class="row">
<h2>Details</h2>
<table id="resultsTable" class="table">
<thead>
<tr>
<th>Result ID</th>
<th class="no-sort"></th>
<th>First Name</th>
<th>Last Name</th>
<th>Email</th>
<th>Position</th>
<th>Status</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
<div id="flashes" class="row"></div>
<div class="row">
<h2>Details</h2>
<table id="resultsTable" class="table">
<thead>
<tr>
<th>Result ID</th>
<th class="no-sort"></th>
<th>First Name</th>
<th>Last Name</th>
<th>Email</th>
<th>Position</th>
<th>Status</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
{{end}}
{{define "scripts"}}
<div id="flashes" class="row"></div>
</div>
{{end}} {{define "scripts"}}
<script src="/js/dist/app/campaign_results.min.js"></script>
{{end}}

View file

@ -38,19 +38,15 @@
No campaigns created yet. Let's create one!
</div>
</div>
<div id="dashboard" style="display:none;">
<div id="dashboard">
<div class="row">
<div id="overview_chart" class="col-lg-6 col-md-6 col-sm-12 col-xs-12">
<p style="text-align:center;">Phishing Success Overview</p>
</div>
<div class="col-lg-6 col-md-6 col-sm-12 col-xs-12">
<p style="text-align:center;">Average Phishing Results</p>
<div id="stats_chart" class="col-lg-7 col-md-7"></div>
<div class="col-lg-5 col-md-5">
<ul id="stats_chart_legend" class="chartist-legend">
</ul>
</div>
</div>
<div id="overview_chart" style="height:200px;" class="col-lg-12 col-md-12 col-sm-12 col-xs-12"></div>
</div>
<div class="row">
<div id="sent_chart" style="height:200px;" class="col-lg-3 col-md-3"></div>
<div id="opened_chart" style="height:200px;" class="col-lg-3 col-md-3"></div>
<div id="clicked_chart" style="height:200px;" class="col-lg-3 col-md-3"></div>
<div id="submitted_data_chart" style="height:200px;" class="col-lg-3 col-md-3"></div>
</div>
<div class="row">
<h2>Recent Campaigns</h2>
@ -63,10 +59,14 @@
<table id="campaignTable" class="table">
<thead>
<tr>
<th>Name</th>
<th>Created Date</th>
<th>Status</th>
<th class="col-md-2 col-sm-2 no-sort"></th>
<th class="col-md-2 col-sm-2">Name</th>
<th class="col-md-2 col-sm-2">Created Date</th>
<th class="col-md-1 col-sm-1"><i class="fa fa-envelope-o"></i></th>
<th class="col-md-1 col-sm-1"><i class="fa fa-envelope-open-o"></i></th>
<th class="col-md-1 col-sm-1"><i class="fa fa-mouse-pointer"></i></th>
<th class="col-md-1 col-sm-1"><i class="fa fa-exclamation-circle"></i></th>
<th class="col-md-1 col-sm-1">Status</th>
<th class="col-md-2 col-sm-2 no-sort"></i></th>
</tr>
</thead>
<tbody>