mirror of
https://github.com/gophish/gophish
synced 2024-11-14 16:27:23 +00:00
Moved all charts from Chartist to Highcharts. Closes #680.
This commit is contained in:
parent
972c40fd87
commit
75600f5812
20 changed files with 3444 additions and 1262 deletions
95
gulpfile.js
95
gulpfile.js
|
@ -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']);
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
6
static/css/dist/gophish.css
vendored
6
static/css/dist/gophish.css
vendored
File diff suppressed because one or more lines are too long
4
static/css/font-awesome.min.css
vendored
4
static/css/font-awesome.min.css
vendored
File diff suppressed because one or more lines are too long
22
static/css/main.css
vendored
22
static/css/main.css
vendored
|
@ -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;
|
||||
}
|
||||
|
|
BIN
static/font/FontAwesome.otf
vendored
BIN
static/font/FontAwesome.otf
vendored
Binary file not shown.
BIN
static/font/fontawesome-webfont.eot
vendored
BIN
static/font/fontawesome-webfont.eot
vendored
Binary file not shown.
3318
static/font/fontawesome-webfont.svg
vendored
3318
static/font/fontawesome-webfont.svg
vendored
File diff suppressed because it is too large
Load diff
Before Width: | Height: | Size: 357 KiB After Width: | Height: | Size: 434 KiB |
BIN
static/font/fontawesome-webfont.ttf
vendored
BIN
static/font/fontawesome-webfont.ttf
vendored
Binary file not shown.
BIN
static/font/fontawesome-webfont.woff
vendored
BIN
static/font/fontawesome-webfont.woff
vendored
Binary file not shown.
BIN
static/font/fontawesome-webfont.woff2
vendored
BIN
static/font/fontawesome-webfont.woff2
vendored
Binary file not shown.
2
static/js/dist/app/campaign_results.min.js
vendored
2
static/js/dist/app/campaign_results.min.js
vendored
File diff suppressed because one or more lines are too long
2
static/js/dist/app/dashboard.min.js
vendored
2
static/js/dist/app/dashboard.min.js
vendored
File diff suppressed because one or more lines are too long
50
static/js/dist/vendor.min.js
vendored
50
static/js/dist/vendor.min.js
vendored
File diff suppressed because one or more lines are too long
|
@ -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)
|
||||
|
|
|
@ -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")
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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}}
|
|
@ -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>
|
||||
|
|
Loading…
Reference in a new issue