mirror of https://github.com/gophish/gophish
Moved all charts from Chartist to Highcharts. Closes #680.
parent
972c40fd87
commit
75600f5812
95
gulpfile.js
95
gulpfile.js
|
@ -5,11 +5,9 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var gulp = require('gulp'),
|
var gulp = require('gulp'),
|
||||||
jshint = require('gulp-jshint'),
|
|
||||||
rename = require('gulp-rename'),
|
rename = require('gulp-rename'),
|
||||||
concat = require('gulp-concat'),
|
concat = require('gulp-concat'),
|
||||||
uglify = require('gulp-uglify'),
|
uglify = require('gulp-uglify'),
|
||||||
wrap = require('gulp-wrap'),
|
|
||||||
cleanCSS = require('gulp-clean-css'),
|
cleanCSS = require('gulp-clean-css'),
|
||||||
|
|
||||||
js_directory = 'static/js/src/',
|
js_directory = 'static/js/src/',
|
||||||
|
@ -19,72 +17,69 @@ var gulp = require('gulp'),
|
||||||
dest_js_directory = 'static/js/dist/',
|
dest_js_directory = 'static/js/dist/',
|
||||||
dest_css_directory = 'static/css/dist/';
|
dest_css_directory = 'static/css/dist/';
|
||||||
|
|
||||||
gulp.task('default', ['watch']);
|
gulp.task('vendorjs', function () {
|
||||||
|
|
||||||
gulp.task('jshint', function() {
|
|
||||||
return gulp.src(js_directory)
|
|
||||||
.pipe(jshint())
|
|
||||||
.pipe(jshint.reporter('jshint-stylish'));
|
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task('build', function() {
|
|
||||||
// Vendor minifying / concat
|
// Vendor minifying / concat
|
||||||
gulp.src([
|
return gulp.src([
|
||||||
vendor_directory + 'jquery.js',
|
vendor_directory + 'jquery.js',
|
||||||
vendor_directory + 'bootstrap.min.js',
|
vendor_directory + 'bootstrap.min.js',
|
||||||
vendor_directory + 'moment.min.js',
|
vendor_directory + 'moment.min.js',
|
||||||
vendor_directory + 'chartist.min.js',
|
vendor_directory + 'chartist.min.js',
|
||||||
vendor_directory + 'papaparse.min.js',
|
vendor_directory + 'papaparse.min.js',
|
||||||
vendor_directory + 'd3.min.js',
|
vendor_directory + 'd3.min.js',
|
||||||
vendor_directory + 'topojson.min.js',
|
vendor_directory + 'topojson.min.js',
|
||||||
vendor_directory + 'datamaps.min.js',
|
vendor_directory + 'datamaps.min.js',
|
||||||
vendor_directory + 'jquery.dataTables.min.js',
|
vendor_directory + 'jquery.dataTables.min.js',
|
||||||
vendor_directory + 'dataTables.bootstrap.js',
|
vendor_directory + 'dataTables.bootstrap.js',
|
||||||
vendor_directory + 'datetime-moment.js',
|
vendor_directory + 'datetime-moment.js',
|
||||||
vendor_directory + 'jquery.ui.widget.js',
|
vendor_directory + 'jquery.ui.widget.js',
|
||||||
vendor_directory + 'jquery.fileupload.js',
|
vendor_directory + 'jquery.fileupload.js',
|
||||||
vendor_directory + 'jquery.iframe-transport.js',
|
vendor_directory + 'jquery.iframe-transport.js',
|
||||||
vendor_directory + 'sweetalert2.min.js',
|
vendor_directory + 'sweetalert2.min.js',
|
||||||
vendor_directory + 'bootstrap-datetime.js',
|
vendor_directory + 'bootstrap-datetime.js',
|
||||||
vendor_directory + 'select2.min.js',
|
vendor_directory + 'select2.min.js',
|
||||||
vendor_directory + 'core.min.js'
|
vendor_directory + 'core.min.js',
|
||||||
])
|
vendor_directory + 'highcharts.js'
|
||||||
|
])
|
||||||
.pipe(concat('vendor.js'))
|
.pipe(concat('vendor.js'))
|
||||||
.pipe(rename({
|
.pipe(rename({
|
||||||
suffix: '.min'
|
suffix: '.min'
|
||||||
}))
|
}))
|
||||||
.pipe(uglify())
|
.pipe(uglify())
|
||||||
.pipe(gulp.dest(dest_js_directory));
|
.pipe(gulp.dest(dest_js_directory));
|
||||||
|
})
|
||||||
|
|
||||||
|
gulp.task('scripts', function () {
|
||||||
// Gophish app files
|
// Gophish app files
|
||||||
gulp.src(app_directory)
|
gulp.src(app_directory)
|
||||||
.pipe(rename({
|
.pipe(rename({
|
||||||
suffix: '.min'
|
suffix: '.min'
|
||||||
}))
|
}))
|
||||||
.pipe(uglify().on('error', function(e){
|
.pipe(uglify().on('error', function (e) {
|
||||||
console.log(e);
|
console.log(e);
|
||||||
}))
|
}))
|
||||||
.pipe(gulp.dest(dest_js_directory + 'app/'));
|
.pipe(gulp.dest(dest_js_directory + 'app/'));
|
||||||
|
})
|
||||||
|
|
||||||
|
gulp.task('styles', function() {
|
||||||
return gulp.src([
|
return gulp.src([
|
||||||
css_directory + 'bootstrap.min.css',
|
css_directory + 'bootstrap.min.css',
|
||||||
css_directory + 'main.css',
|
css_directory + 'main.css',
|
||||||
css_directory + 'dashboard.css',
|
css_directory + 'dashboard.css',
|
||||||
css_directory + 'flat-ui.css',
|
css_directory + 'flat-ui.css',
|
||||||
css_directory + 'dataTables.bootstrap.css',
|
css_directory + 'dataTables.bootstrap.css',
|
||||||
css_directory + 'font-awesome.min.css',
|
css_directory + 'font-awesome.min.css',
|
||||||
css_directory + 'chartist.min.css',
|
css_directory + 'chartist.min.css',
|
||||||
css_directory + 'bootstrap-datetime.css',
|
css_directory + 'bootstrap-datetime.css',
|
||||||
css_directory + 'checkbox.css',
|
css_directory + 'checkbox.css',
|
||||||
css_directory + 'sweetalert2.min.css',
|
css_directory + 'sweetalert2.min.css',
|
||||||
css_directory + 'select2.min.css',
|
css_directory + 'select2.min.css',
|
||||||
css_directory + 'select2-bootstrap.min.css'
|
css_directory + 'select2-bootstrap.min.css',
|
||||||
])
|
])
|
||||||
.pipe(cleanCSS({compatibilty: 'ie9'}))
|
.pipe(cleanCSS({ compatibilty: 'ie9' }))
|
||||||
.pipe(concat('gophish.css'))
|
.pipe(concat('gophish.css'))
|
||||||
.pipe(gulp.dest(dest_css_directory));
|
.pipe(gulp.dest(dest_css_directory));
|
||||||
});
|
})
|
||||||
|
|
||||||
gulp.task('watch', function() {
|
gulp.task('build', ['vendorjs', 'scripts', 'styles']);
|
||||||
gulp.watch('static/js/src/app/**/*.js', ['jshint']);
|
|
||||||
});
|
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.
|
// 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) {
|
func getCampaignStats(cid int64) (CampaignStats, error) {
|
||||||
s := CampaignStats{}
|
s := CampaignStats{}
|
||||||
query := db.Table("results").Where("campaign_id = ?", cid)
|
query := db.Table("results").Where("campaign_id = ?", cid)
|
||||||
|
@ -205,11 +206,7 @@ func getCampaignStats(cid int64) (CampaignStats, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return s, err
|
return s, err
|
||||||
}
|
}
|
||||||
err = query.Where("status=?", EVENT_SENT).Count(&s.EmailsSent).Error
|
query.Where("status=?", EVENT_DATA_SUBMIT).Count(&s.SubmittedData)
|
||||||
if err != nil {
|
|
||||||
return s, err
|
|
||||||
}
|
|
||||||
err = query.Where("status=?", EVENT_OPENED).Count(&s.OpenedEmail).Error
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return s, err
|
return s, err
|
||||||
}
|
}
|
||||||
|
@ -217,10 +214,20 @@ func getCampaignStats(cid int64) (CampaignStats, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return s, err
|
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 {
|
if err != nil {
|
||||||
return s, err
|
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
|
err = query.Where("status=?", ERROR).Count(&s.Error).Error
|
||||||
return s, err
|
return s, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "gophish",
|
"name": "gophish",
|
||||||
"version": "0.3.0-dev",
|
"version": "0.4.0-dev",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+https://github.com/gophish/gophish.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
|
@ -541,3 +541,25 @@ table.dataTable{
|
||||||
.input-group-btn .btn {
|
.input-group-btn .btn {
|
||||||
line-height:20px !important;
|
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
|
@ -4,64 +4,70 @@ var doPoll = true;
|
||||||
// statuses is a helper map to point result statuses to ui classes
|
// statuses is a helper map to point result statuses to ui classes
|
||||||
var statuses = {
|
var statuses = {
|
||||||
"Email Sent": {
|
"Email Sent": {
|
||||||
slice: "ct-slice-donut-sent",
|
color: "#1abc9c",
|
||||||
legend: "ct-legend-sent",
|
|
||||||
label: "label-success",
|
label: "label-success",
|
||||||
icon: "fa-envelope",
|
icon: "fa-envelope",
|
||||||
point: "ct-point-sent"
|
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": {
|
"Email Opened": {
|
||||||
slice: "ct-slice-donut-opened",
|
color: "#f9bf3b",
|
||||||
legend: "ct-legend-opened",
|
|
||||||
label: "label-warning",
|
label: "label-warning",
|
||||||
icon: "fa-envelope",
|
icon: "fa-envelope",
|
||||||
point: "ct-point-opened"
|
point: "ct-point-opened"
|
||||||
},
|
},
|
||||||
"Clicked Link": {
|
"Clicked Link": {
|
||||||
slice: "ct-slice-donut-clicked",
|
color: "#F39C12",
|
||||||
legend: "ct-legend-clicked",
|
|
||||||
label: "label-clicked",
|
label: "label-clicked",
|
||||||
icon: "fa-mouse-pointer",
|
icon: "fa-mouse-pointer",
|
||||||
point: "ct-point-clicked"
|
point: "ct-point-clicked"
|
||||||
},
|
},
|
||||||
"Success": {
|
"Success": {
|
||||||
slice: "ct-slice-donut-success",
|
color: "#f05b4f",
|
||||||
legend: "ct-legend-success",
|
|
||||||
label: "label-danger",
|
label: "label-danger",
|
||||||
icon: "fa-exclamation",
|
icon: "fa-exclamation",
|
||||||
point: "ct-point-clicked"
|
point: "ct-point-clicked"
|
||||||
},
|
},
|
||||||
"Error": {
|
"Error": {
|
||||||
slice: "ct-slice-donut-error",
|
color: "#6c7a89",
|
||||||
legend: "ct-legend-error",
|
|
||||||
label: "label-default",
|
label: "label-default",
|
||||||
icon: "fa-times",
|
icon: "fa-times",
|
||||||
point: "ct-point-error"
|
point: "ct-point-error"
|
||||||
},
|
},
|
||||||
"Error Sending Email": {
|
"Error Sending Email": {
|
||||||
slice: "ct-slice-donut-error",
|
color: "#6c7a89",
|
||||||
legend: "ct-legend-error",
|
|
||||||
label: "label-default",
|
label: "label-default",
|
||||||
icon: "fa-times",
|
icon: "fa-times",
|
||||||
point: "ct-point-error"
|
point: "ct-point-error"
|
||||||
},
|
},
|
||||||
"Submitted Data": {
|
"Submitted Data": {
|
||||||
slice: "ct-slice-donut-success",
|
color: "#f05b4f",
|
||||||
legend: "ct-legend-success",
|
|
||||||
label: "label-danger",
|
label: "label-danger",
|
||||||
icon: "fa-exclamation",
|
icon: "fa-exclamation",
|
||||||
point: "ct-point-clicked"
|
point: "ct-point-clicked"
|
||||||
},
|
},
|
||||||
"Unknown": {
|
"Unknown": {
|
||||||
slice: "ct-slice-donut-error",
|
color: "#6c7a89",
|
||||||
legend: "ct-legend-error",
|
|
||||||
label: "label-default",
|
label: "label-default",
|
||||||
icon: "fa-question",
|
icon: "fa-question",
|
||||||
point: "ct-point-error"
|
point: "ct-point-error"
|
||||||
},
|
},
|
||||||
"Sending": {
|
"Sending": {
|
||||||
slice: "ct-slice-donut-sending",
|
color: "#428bca",
|
||||||
legend: "ct-legend-sending",
|
|
||||||
label: "label-primary",
|
label: "label-primary",
|
||||||
icon: "fa-spinner",
|
icon: "fa-spinner",
|
||||||
point: "ct-point-sending"
|
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 campaign = {}
|
||||||
var bubbles = []
|
var bubbles = []
|
||||||
|
|
||||||
|
@ -93,24 +115,25 @@ function deleteCampaign() {
|
||||||
confirmButtonColor: "#428bca",
|
confirmButtonColor: "#428bca",
|
||||||
reverseButtons: true,
|
reverseButtons: true,
|
||||||
allowOutsideClick: false,
|
allowOutsideClick: false,
|
||||||
preConfirm: function() {
|
showLoaderOnConfirm: true,
|
||||||
return new Promise(function(resolve, reject) {
|
preConfirm: function () {
|
||||||
|
return new Promise(function (resolve, reject) {
|
||||||
api.campaignId.delete(campaign.id)
|
api.campaignId.delete(campaign.id)
|
||||||
.success(function(msg) {
|
.success(function (msg) {
|
||||||
resolve()
|
resolve()
|
||||||
})
|
})
|
||||||
.error(function(data) {
|
.error(function (data) {
|
||||||
reject(data.responseJSON.message)
|
reject(data.responseJSON.message)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}).then(function() {
|
}).then(function () {
|
||||||
swal(
|
swal(
|
||||||
'Campaign Deleted!',
|
'Campaign Deleted!',
|
||||||
'This campaign has been deleted!',
|
'This campaign has been deleted!',
|
||||||
'success'
|
'success'
|
||||||
);
|
);
|
||||||
$('button:contains("OK")').on('click', function() {
|
$('button:contains("OK")').on('click', function () {
|
||||||
location.href = '/campaigns'
|
location.href = '/campaigns'
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -128,18 +151,19 @@ function completeCampaign() {
|
||||||
confirmButtonColor: "#428bca",
|
confirmButtonColor: "#428bca",
|
||||||
reverseButtons: true,
|
reverseButtons: true,
|
||||||
allowOutsideClick: false,
|
allowOutsideClick: false,
|
||||||
preConfirm: function() {
|
showLoaderOnConfirm: true,
|
||||||
return new Promise(function(resolve, reject) {
|
preConfirm: function () {
|
||||||
|
return new Promise(function (resolve, reject) {
|
||||||
api.campaignId.complete(campaign.id)
|
api.campaignId.complete(campaign.id)
|
||||||
.success(function(msg) {
|
.success(function (msg) {
|
||||||
resolve()
|
resolve()
|
||||||
})
|
})
|
||||||
.error(function(data) {
|
.error(function (data) {
|
||||||
reject(data.responseJSON.message)
|
reject(data.responseJSON.message)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}).then(function() {
|
}).then(function () {
|
||||||
swal(
|
swal(
|
||||||
'Campaign Completed!',
|
'Campaign Completed!',
|
||||||
'This campaign has been completed!',
|
'This campaign has been completed!',
|
||||||
|
@ -190,32 +214,32 @@ function replay(event_idx) {
|
||||||
details = JSON.parse(request.details)
|
details = JSON.parse(request.details)
|
||||||
url = null
|
url = null
|
||||||
form = $('<form>').attr({
|
form = $('<form>').attr({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
target: '_blank',
|
target: '_blank',
|
||||||
})
|
})
|
||||||
/* Create a form object and submit it */
|
/* Create a form object and submit it */
|
||||||
$.each(Object.keys(details.payload), function(i, param) {
|
$.each(Object.keys(details.payload), function (i, param) {
|
||||||
if (param == "rid") {
|
if (param == "rid") {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (param == "__original_url") {
|
if (param == "__original_url") {
|
||||||
url = details.payload[param];
|
url = details.payload[param];
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
$('<input>').attr({
|
$('<input>').attr({
|
||||||
name: param,
|
name: param,
|
||||||
}).val(details.payload[param]).appendTo(form);
|
}).val(details.payload[param]).appendTo(form);
|
||||||
})
|
})
|
||||||
/* Ensure we know where to send the user */
|
/* Ensure we know where to send the user */
|
||||||
// Prompt for the URL
|
// Prompt for the URL
|
||||||
swal({
|
swal({
|
||||||
title: 'Where do you want the credentials submitted to?',
|
title: 'Where do you want the credentials submitted to?',
|
||||||
input: 'text',
|
input: 'text',
|
||||||
showCancelButton: true,
|
showCancelButton: true,
|
||||||
inputPlaceholder: "http://example.com/login",
|
inputPlaceholder: "http://example.com/login",
|
||||||
inputValue: url || "",
|
inputValue: url || "",
|
||||||
inputValidator: function(value) {
|
inputValidator: function (value) {
|
||||||
return new Promise(function(resolve, reject) {
|
return new Promise(function (resolve, reject) {
|
||||||
if (value) {
|
if (value) {
|
||||||
resolve();
|
resolve();
|
||||||
} else {
|
} else {
|
||||||
|
@ -223,7 +247,7 @@ function replay(event_idx) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}).then(function(result) {
|
}).then(function (result) {
|
||||||
url = result
|
url = result
|
||||||
submitForm()
|
submitForm()
|
||||||
})
|
})
|
||||||
|
@ -249,7 +273,7 @@ function renderTimeline(data) {
|
||||||
'<h6>Timeline for ' + escapeHtml(record.first_name) + ' ' + escapeHtml(record.last_name) +
|
'<h6>Timeline for ' + escapeHtml(record.first_name) + ' ' + escapeHtml(record.last_name) +
|
||||||
'</h6><span class="subtitle">Email: ' + escapeHtml(record.email) + '</span>' +
|
'</h6><span class="subtitle">Email: ' + escapeHtml(record.email) + '</span>' +
|
||||||
'<div class="timeline-graph col-sm-6">'
|
'<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) {
|
if (!event.email || event.email == record.email) {
|
||||||
// Add the event
|
// Add the event
|
||||||
results += '<div class="timeline-entry">' +
|
results += '<div class="timeline-entry">' +
|
||||||
|
@ -270,7 +294,7 @@ function renderTimeline(data) {
|
||||||
results += '<div class="timeline-event-results">'
|
results += '<div class="timeline-event-results">'
|
||||||
results += ' <table class="table table-condensed table-bordered table-striped">'
|
results += ' <table class="table table-condensed table-bordered table-striped">'
|
||||||
results += ' <thead><tr><th>Parameter</th><th>Value(s)</tr></thead><tbody>'
|
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") {
|
if (param == "rid") {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -296,6 +320,131 @@ function renderTimeline(data) {
|
||||||
return results
|
return results
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var renderTimelineChart = function (chartopts) {
|
||||||
|
return Highcharts.chart('timeline_chart', {
|
||||||
|
chart: {
|
||||||
|
zoomType: 'x',
|
||||||
|
type: 'line',
|
||||||
|
height: "200px"
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
text: 'Campaign Timeline'
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
type: 'datetime',
|
||||||
|
dateTimeLabelFormats: {
|
||||||
|
second: '%l:%M:%S',
|
||||||
|
minute: '%l:%M',
|
||||||
|
hour: '%l:%M',
|
||||||
|
day: '%b %d, %Y',
|
||||||
|
week: '%b %d, %Y',
|
||||||
|
month: '%b %Y'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
min: 0,
|
||||||
|
max: 2,
|
||||||
|
visible: false,
|
||||||
|
tickInterval: 1,
|
||||||
|
labels: {
|
||||||
|
enabled: false
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
text: ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
formatter: function () {
|
||||||
|
return Highcharts.dateFormat('%A, %b %d %l:%M:%S %P', new Date(this.x)) +
|
||||||
|
'<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
|
/* poll - Queries the API and updates the UI with the results
|
||||||
*
|
*
|
||||||
|
@ -307,81 +456,94 @@ function renderTimeline(data) {
|
||||||
*/
|
*/
|
||||||
function poll() {
|
function poll() {
|
||||||
api.campaignId.results(campaign.id)
|
api.campaignId.results(campaign.id)
|
||||||
.success(function(c) {
|
.success(function (c) {
|
||||||
campaign = c
|
campaign = c
|
||||||
/* Update the timeline */
|
/* Update the timeline */
|
||||||
var timeline_data = {
|
var timeline_series_data = []
|
||||||
series: [{
|
$.each(campaign.timeline, function (i, event) {
|
||||||
name: "Events",
|
var event_date = moment(event.time)
|
||||||
data: []
|
timeline_series_data.push({
|
||||||
}]
|
email: event.email,
|
||||||
}
|
x: event_date.valueOf(),
|
||||||
$.each(campaign.timeline, function(i, event) {
|
|
||||||
timeline_data.series[0].data.push({
|
|
||||||
meta: i,
|
|
||||||
x: new Date(event.time),
|
|
||||||
y: 1
|
y: 1
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
var timeline_chart = $("#timeline_chart")
|
var timeline_series_data = []
|
||||||
if (timeline_chart.get(0).__chartist__) {
|
$.each(campaign.timeline, function (i, event) {
|
||||||
timeline_chart.get(0).__chartist__.update(timeline_data)
|
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 */
|
/* Update the results donut chart */
|
||||||
var email_data = {
|
|
||||||
series: []
|
|
||||||
}
|
|
||||||
var email_series_data = {}
|
var email_series_data = {}
|
||||||
$.each(campaign.results, function(i, result) {
|
// Load the initial data
|
||||||
if (!email_series_data[result.status]) {
|
Object.keys(statusMapping).forEach(function (k) {
|
||||||
email_series_data[result.status] = 1
|
email_series_data[k] = 0
|
||||||
} else {
|
});
|
||||||
email_series_data[result.status]++;
|
$.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) {
|
||||||
$.each(email_series_data, function(status, count) {
|
var email_data = []
|
||||||
email_data.series.push({
|
if (!(status in statusMapping)) {
|
||||||
meta: status,
|
return true
|
||||||
value: count
|
}
|
||||||
|
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 */
|
/* Update the datatable */
|
||||||
resultsTable = $("#resultsTable").DataTable()
|
resultsTable = $("#resultsTable").DataTable()
|
||||||
resultsTable.rows().every(function(i, tableLoop, rowLoop) {
|
resultsTable.rows().every(function (i, tableLoop, rowLoop) {
|
||||||
var row = this.row(i)
|
var row = this.row(i)
|
||||||
var rowData = row.data()
|
var rowData = row.data()
|
||||||
var rid = rowData[0]
|
var rid = rowData[0]
|
||||||
$.each(campaign.results, function(j, result) {
|
$.each(campaign.results, function (j, result) {
|
||||||
if (result.id == rid) {
|
if (result.id == rid) {
|
||||||
var label = statuses[result.status].label || "label-default";
|
var label = statuses[result.status].label || "label-default";
|
||||||
rowData[6] = "<span class=\"label " + label + "\">" + result.status + "</span>"
|
rowData[6] = "<span class=\"label " + label + "\">" + result.status + "</span>"
|
||||||
resultsTable.row(i).data(rowData).draw(false)
|
resultsTable.row(i).data(rowData).draw(false)
|
||||||
if (row.child.isShown()) {
|
if (row.child.isShown()) {
|
||||||
row.child(renderTimeline(row.data()))
|
row.child(renderTimeline(row.data()))
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
})
|
return false
|
||||||
|
}
|
||||||
})
|
})
|
||||||
/* Update the map information */
|
})
|
||||||
|
/* Update the map information */
|
||||||
bubbles = []
|
bubbles = []
|
||||||
$.each(campaign.results, function(i, result) {
|
$.each(campaign.results, function (i, result) {
|
||||||
// Check that it wasn't an internal IP
|
// Check that it wasn't an internal IP
|
||||||
if (result.latitude == 0 && result.longitude == 0) {
|
if (result.latitude == 0 && result.longitude == 0) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
newIP = true
|
newIP = true
|
||||||
$.each(bubbles, function(i, bubble) {
|
$.each(bubbles, function (i, bubble) {
|
||||||
if (bubble.ip == result.ip) {
|
if (bubble.ip == result.ip) {
|
||||||
bubbles[i].radius += 1
|
bubbles[i].radius += 1
|
||||||
newIP = false
|
newIP = false
|
||||||
|
@ -407,13 +569,13 @@ function poll() {
|
||||||
function load() {
|
function load() {
|
||||||
campaign.id = window.location.pathname.split('/').slice(-1)[0]
|
campaign.id = window.location.pathname.split('/').slice(-1)[0]
|
||||||
api.campaignId.results(campaign.id)
|
api.campaignId.results(campaign.id)
|
||||||
.success(function(c) {
|
.success(function (c) {
|
||||||
campaign = c
|
campaign = c
|
||||||
if (campaign) {
|
if (campaign) {
|
||||||
$("title").text(c.name + " - Gophish")
|
$("title").text(c.name + " - Gophish")
|
||||||
$("#loading").hide()
|
$("#loading").hide()
|
||||||
$("#campaignResults").show()
|
$("#campaignResults").show()
|
||||||
// Set the title
|
// Set the title
|
||||||
$("#page-title").text("Results for " + c.name)
|
$("#page-title").text("Results for " + c.name)
|
||||||
if (c.status == "Completed") {
|
if (c.status == "Completed") {
|
||||||
$('#complete_button')[0].disabled = true;
|
$('#complete_button')[0].disabled = true;
|
||||||
|
@ -422,57 +584,21 @@ function load() {
|
||||||
}
|
}
|
||||||
// Setup tooltips
|
// Setup tooltips
|
||||||
$('[data-toggle="tooltip"]').tooltip()
|
$('[data-toggle="tooltip"]').tooltip()
|
||||||
// Setup viewing the details of a result
|
// Setup viewing the details of a result
|
||||||
$("#resultsTable").on("click", ".timeline-event-details", function() {
|
$("#resultsTable").on("click", ".timeline-event-details", function () {
|
||||||
// Show the parameters
|
// Show the parameters
|
||||||
payloadResults = $(this).parent().find(".timeline-event-results")
|
payloadResults = $(this).parent().find(".timeline-event-results")
|
||||||
if (payloadResults.is(":visible")) {
|
if (payloadResults.is(":visible")) {
|
||||||
$(this).find("i").removeClass("fa-caret-down")
|
$(this).find("i").removeClass("fa-caret-down")
|
||||||
$(this).find("i").addClass("fa-caret-right")
|
$(this).find("i").addClass("fa-caret-right")
|
||||||
payloadResults.hide()
|
payloadResults.hide()
|
||||||
} else {
|
} else {
|
||||||
$(this).find("i").removeClass("fa-caret-right")
|
$(this).find("i").removeClass("fa-caret-right")
|
||||||
$(this).find("i").addClass("fa-caret-down")
|
$(this).find("i").addClass("fa-caret-down")
|
||||||
payloadResults.show()
|
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 the results table
|
})
|
||||||
|
// Setup the results table
|
||||||
resultsTable = $("#resultsTable").DataTable({
|
resultsTable = $("#resultsTable").DataTable({
|
||||||
destroy: true,
|
destroy: true,
|
||||||
"order": [
|
"order": [
|
||||||
|
@ -490,25 +616,31 @@ function load() {
|
||||||
}]
|
}]
|
||||||
});
|
});
|
||||||
resultsTable.clear();
|
resultsTable.clear();
|
||||||
$.each(campaign.results, function(i, result) {
|
var email_series_data = {}
|
||||||
label = statuses[result.status].label || "label-default";
|
var timeline_series_data = []
|
||||||
resultsTable.row.add([
|
Object.keys(statusMapping).forEach(function (k) {
|
||||||
result.id,
|
email_series_data[k] = 0
|
||||||
"<i class=\"fa fa-caret-right\"></i>",
|
});
|
||||||
escapeHtml(result.first_name) || "",
|
$.each(campaign.results, function (i, result) {
|
||||||
escapeHtml(result.last_name) || "",
|
label = statuses[result.status].label || "label-default";
|
||||||
escapeHtml(result.email) || "",
|
resultsTable.row.add([
|
||||||
escapeHtml(result.position) || "",
|
result.id,
|
||||||
"<span class=\"label " + label + "\">" + result.status + "</span>"
|
"<i class=\"fa fa-caret-right\"></i>",
|
||||||
]).draw()
|
escapeHtml(result.first_name) || "",
|
||||||
if (!email_series_data[result.status]) {
|
escapeHtml(result.last_name) || "",
|
||||||
email_series_data[result.status] = 1
|
escapeHtml(result.email) || "",
|
||||||
} else {
|
escapeHtml(result.position) || "",
|
||||||
email_series_data[result.status]++;
|
"<span class=\"label " + label + "\">" + result.status + "</span>"
|
||||||
}
|
]).draw()
|
||||||
})
|
email_series_data[result.status]++;
|
||||||
// Setup the individual timelines
|
// Backfill status values
|
||||||
$('#resultsTable tbody').on('click', 'td.details-control', function() {
|
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 tr = $(this).closest('tr');
|
||||||
var row = resultsTable.row(tr);
|
var row = resultsTable.row(tr);
|
||||||
if (row.child.isShown()) {
|
if (row.child.isShown()) {
|
||||||
|
@ -528,88 +660,42 @@ function load() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// Setup the graphs
|
// Setup the graphs
|
||||||
$.each(campaign.timeline, function(i, event) {
|
$.each(campaign.timeline, function (i, event) {
|
||||||
timeline_data.series[0].data.push({
|
var event_date = moment(event.time)
|
||||||
meta: i,
|
timeline_series_data.push({
|
||||||
x: new Date(event.time),
|
email: event.email,
|
||||||
y: 1
|
message: event.message,
|
||||||
})
|
x: event_date.valueOf(),
|
||||||
})
|
y: 1,
|
||||||
$("#email_chart_legend").html("")
|
marker: {
|
||||||
$.each(email_series_data, function(status, count) {
|
fillColor: statuses[event.message].color
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
// Setup the overview chart listeners
|
})
|
||||||
$chart = $("#timeline_chart")
|
renderTimelineChart({
|
||||||
var $toolTip = $chart
|
data: timeline_series_data
|
||||||
.append('<div class="chartist-tooltip"></div>')
|
})
|
||||||
.find('.chartist-tooltip')
|
$.each(email_series_data, function (status, count) {
|
||||||
.hide();
|
var email_data = []
|
||||||
$chart.on('mouseenter', '.ct-timeline-point', function() {
|
if (!(status in statusMapping)) {
|
||||||
var $point = $(this)
|
return true
|
||||||
cidx = $point.attr('meta')
|
|
||||||
html = "Event: " + campaign.timeline[cidx].message
|
|
||||||
if (campaign.timeline[cidx].email) {
|
|
||||||
html += '<br>' + "Email: " + escapeHtml(campaign.timeline[cidx].email)
|
|
||||||
}
|
}
|
||||||
$toolTip.html(html).show()
|
email_data.push({
|
||||||
});
|
name: status,
|
||||||
$chart.on('mouseleave', '.ct-timeline-point', function() {
|
y: count
|
||||||
$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)
|
|
||||||
})
|
})
|
||||||
// Setup the average chart listeners
|
email_data.push({
|
||||||
$piechart = $("#email_chart")
|
name: '',
|
||||||
var $pietoolTip = $piechart
|
y: campaign.results.length - count
|
||||||
.append('<div class="chartist-tooltip"></div>')
|
})
|
||||||
.find('.chartist-tooltip')
|
var chart = renderPieChart({
|
||||||
.hide();
|
elemId: statusMapping[status] + '_chart',
|
||||||
|
title: status,
|
||||||
$piechart.on('mouseenter', '.ct-slice-donut', function() {
|
name: status,
|
||||||
var $point = $(this)
|
data: email_data,
|
||||||
value = $point.attr('ct:value')
|
colors: [statuses[status].color, '#dddddd']
|
||||||
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
|
|
||||||
});
|
|
||||||
});
|
|
||||||
if (!map) {
|
if (!map) {
|
||||||
map = new Datamap({
|
map = new Datamap({
|
||||||
element: document.getElementById("resultsMap"),
|
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
|
// Check that it wasn't an internal IP
|
||||||
if (result.latitude == 0 && result.longitude == 0) {
|
if (result.latitude == 0 && result.longitude == 0) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
newIP = true
|
newIP = true
|
||||||
$.each(bubbles, function(i, bubble) {
|
$.each(bubbles, function (i, bubble) {
|
||||||
if (bubble.ip == result.ip) {
|
if (bubble.ip == result.ip) {
|
||||||
bubbles[i].radius += 1
|
bubbles[i].radius += 1
|
||||||
newIP = false
|
newIP = false
|
||||||
|
@ -653,7 +739,7 @@ function load() {
|
||||||
map.bubbles(bubbles)
|
map.bubbles(bubbles)
|
||||||
}
|
}
|
||||||
// Load up the map data (only once!)
|
// 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 ($(e.target).attr('href') == "#overview") {
|
||||||
if (!map) {
|
if (!map) {
|
||||||
map = new Datamap({
|
map = new Datamap({
|
||||||
|
@ -671,7 +757,7 @@ function load() {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.error(function() {
|
.error(function () {
|
||||||
$("#loading").hide()
|
$("#loading").hide()
|
||||||
errorFlash(" Campaign not found!")
|
errorFlash(" Campaign not found!")
|
||||||
})
|
})
|
||||||
|
@ -691,9 +777,13 @@ function refresh() {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
$(document).ready(function() {
|
$(document).ready(function () {
|
||||||
|
Highcharts.setOptions({
|
||||||
|
global: {
|
||||||
|
useUTC: false
|
||||||
|
}
|
||||||
|
})
|
||||||
load();
|
load();
|
||||||
// Start the polling loop
|
|
||||||
|
|
||||||
// Start the polling loop
|
// Start the polling loop
|
||||||
setRefresh = setTimeout(refresh, 60000)
|
setRefresh = setTimeout(refresh, 60000)
|
||||||
|
|
|
@ -3,15 +3,13 @@ var campaigns = []
|
||||||
// statuses is a helper map to point result statuses to ui classes
|
// statuses is a helper map to point result statuses to ui classes
|
||||||
var statuses = {
|
var statuses = {
|
||||||
"Email Sent": {
|
"Email Sent": {
|
||||||
slice: "ct-slice-donut-sent",
|
color: "#1abc9c",
|
||||||
legend: "ct-legend-sent",
|
|
||||||
label: "label-success",
|
label: "label-success",
|
||||||
icon: "fa-envelope",
|
icon: "fa-envelope",
|
||||||
point: "ct-point-sent"
|
point: "ct-point-sent"
|
||||||
},
|
},
|
||||||
"Emails Sent": {
|
"Emails Sent": {
|
||||||
slice: "ct-slice-donut-sent",
|
color: "#1abc9c",
|
||||||
legend: "ct-legend-sent",
|
|
||||||
label: "label-success",
|
label: "label-success",
|
||||||
icon: "fa-envelope",
|
icon: "fa-envelope",
|
||||||
point: "ct-point-sent"
|
point: "ct-point-sent"
|
||||||
|
@ -26,57 +24,49 @@ var statuses = {
|
||||||
label: "label-success"
|
label: "label-success"
|
||||||
},
|
},
|
||||||
"Email Opened": {
|
"Email Opened": {
|
||||||
slice: "ct-slice-donut-opened",
|
color: "#f9bf3b",
|
||||||
legend: "ct-legend-opened",
|
|
||||||
label: "label-warning",
|
label: "label-warning",
|
||||||
icon: "fa-envelope",
|
icon: "fa-envelope",
|
||||||
point: "ct-point-opened"
|
point: "ct-point-opened"
|
||||||
},
|
},
|
||||||
"Clicked Link": {
|
"Clicked Link": {
|
||||||
slice: "ct-slice-donut-clicked",
|
color: "#F39C12",
|
||||||
legend: "ct-legend-clicked",
|
|
||||||
label: "label-clicked",
|
label: "label-clicked",
|
||||||
icon: "fa-mouse-pointer",
|
icon: "fa-mouse-pointer",
|
||||||
point: "ct-point-clicked"
|
point: "ct-point-clicked"
|
||||||
},
|
},
|
||||||
"Success": {
|
"Success": {
|
||||||
slice: "ct-slice-donut-success",
|
color: "#f05b4f",
|
||||||
legend: "ct-legend-success",
|
|
||||||
label: "label-danger",
|
label: "label-danger",
|
||||||
icon: "fa-exclamation",
|
icon: "fa-exclamation",
|
||||||
point: "ct-point-clicked"
|
point: "ct-point-clicked"
|
||||||
},
|
},
|
||||||
"Error": {
|
"Error": {
|
||||||
slice: "ct-slice-donut-error",
|
color: "#6c7a89",
|
||||||
legend: "ct-legend-error",
|
|
||||||
label: "label-default",
|
label: "label-default",
|
||||||
icon: "fa-times",
|
icon: "fa-times",
|
||||||
point: "ct-point-error"
|
point: "ct-point-error"
|
||||||
},
|
},
|
||||||
"Error Sending Email": {
|
"Error Sending Email": {
|
||||||
slice: "ct-slice-donut-error",
|
color: "#6c7a89",
|
||||||
legend: "ct-legend-error",
|
|
||||||
label: "label-default",
|
label: "label-default",
|
||||||
icon: "fa-times",
|
icon: "fa-times",
|
||||||
point: "ct-point-error"
|
point: "ct-point-error"
|
||||||
},
|
},
|
||||||
"Submitted Data": {
|
"Submitted Data": {
|
||||||
slice: "ct-slice-donut-success",
|
color: "#f05b4f",
|
||||||
legend: "ct-legend-success",
|
|
||||||
label: "label-danger",
|
label: "label-danger",
|
||||||
icon: "fa-exclamation",
|
icon: "fa-exclamation",
|
||||||
point: "ct-point-clicked"
|
point: "ct-point-clicked"
|
||||||
},
|
},
|
||||||
"Unknown": {
|
"Unknown": {
|
||||||
slice: "ct-slice-donut-error",
|
color: "#6c7a89",
|
||||||
legend: "ct-legend-error",
|
|
||||||
label: "label-default",
|
label: "label-default",
|
||||||
icon: "fa-question",
|
icon: "fa-question",
|
||||||
point: "ct-point-error"
|
point: "ct-point-error"
|
||||||
},
|
},
|
||||||
"Sending": {
|
"Sending": {
|
||||||
slice: "ct-slice-donut-sending",
|
color: "#428bca",
|
||||||
legend: "ct-legend-sending",
|
|
||||||
label: "label-primary",
|
label: "label-primary",
|
||||||
icon: "fa-spinner",
|
icon: "fa-spinner",
|
||||||
point: "ct-point-sending"
|
point: "ct-point-sending"
|
||||||
|
@ -92,34 +82,80 @@ var statsMapping = {
|
||||||
"opened": "Email Opened",
|
"opened": "Email Opened",
|
||||||
"clicked": "Clicked Link",
|
"clicked": "Clicked Link",
|
||||||
"submitted_data": "Submitted Data",
|
"submitted_data": "Submitted Data",
|
||||||
"error": "Error"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function deleteCampaign(idx) {
|
function deleteCampaign(idx) {
|
||||||
if (confirm("Delete " + campaigns[idx].name + "?")) {
|
if (confirm("Delete " + campaigns[idx].name + "?")) {
|
||||||
api.campaignId.delete(campaigns[idx].id)
|
api.campaignId.delete(campaigns[idx].id)
|
||||||
.success(function(data) {
|
.success(function (data) {
|
||||||
successFlash(data.message)
|
successFlash(data.message)
|
||||||
location.reload()
|
location.reload()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateStatsPieChart(campaigns) {
|
/* Renders a pie chart using the provided chartops */
|
||||||
var stats_opts = {
|
function renderPieChart(chartopts) {
|
||||||
donut: true,
|
return Highcharts.chart(chartopts['elemId'], {
|
||||||
donutWidth: 40,
|
chart: {
|
||||||
chartPadding: 0,
|
type: 'pie',
|
||||||
showLabel: false
|
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_series_data = {}
|
||||||
var stats_data = {
|
|
||||||
series: []
|
|
||||||
}
|
|
||||||
var total = 0
|
var total = 0
|
||||||
|
|
||||||
$.each(campaigns, function(i, campaign) {
|
$.each(campaigns, function (i, campaign) {
|
||||||
$.each(campaign.stats, function(status, count) {
|
$.each(campaign.stats, function (status, count) {
|
||||||
if (status == "total") {
|
if (status == "total") {
|
||||||
total += count
|
total += count
|
||||||
return true
|
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.
|
// I don't like this, but I guess it'll have to work.
|
||||||
// Turns submitted_data into Submitted Data
|
// Turns submitted_data into Submitted Data
|
||||||
|
if (!(status in statsMapping)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
status_label = statsMapping[status]
|
status_label = statsMapping[status]
|
||||||
stats_data.series.push({
|
stats_data.push({
|
||||||
meta: status_label,
|
name: status_label,
|
||||||
value: Math.floor((count / total) * 100)
|
y: Math.floor((count / total) * 100),
|
||||||
|
count: count
|
||||||
})
|
})
|
||||||
$("#stats_chart_legend").append('<li><span class="' + statuses[status_label].legend + '"></span>' + status_label + '</li>')
|
stats_data.push({
|
||||||
})
|
name: '',
|
||||||
|
y: 100 - Math.floor((count / total) * 100)
|
||||||
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)
|
|
||||||
})
|
})
|
||||||
// Update with the latest data
|
var stats_chart = renderPieChart({
|
||||||
$piechart.get(0).__chartist__.update(stats_data)
|
elemId: status + '_chart',
|
||||||
|
title: status_label,
|
||||||
$piechart.on('mouseenter', '.ct-slice-donut', function() {
|
name: status,
|
||||||
var $point = $(this)
|
data: stats_data,
|
||||||
value = $point.attr('ct:value')
|
colors: [statuses[status_label].color, "#dddddd"]
|
||||||
label = $point.attr('ct:meta')
|
})
|
||||||
$pietoolTip.html(label + ': ' + value.toString() + "%").show();
|
stats_data = []
|
||||||
});
|
|
||||||
|
|
||||||
$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
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
$(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()
|
api.campaigns.summary()
|
||||||
.success(function(data) {
|
.success(function (data) {
|
||||||
$("#loading").hide()
|
$("#loading").hide()
|
||||||
campaigns = data.campaigns
|
campaigns = data.campaigns
|
||||||
if (campaigns.length > 0) {
|
if (campaigns.length > 0) {
|
||||||
$("#dashboard").show()
|
$("#dashboard").show()
|
||||||
// Create the overview chart data
|
// Create the overview chart data
|
||||||
var overview_data = {
|
|
||||||
labels: [],
|
|
||||||
series: [
|
|
||||||
[]
|
|
||||||
]
|
|
||||||
}
|
|
||||||
var overview_opts = {
|
|
||||||
axisX: {
|
|
||||||
showGrid: false
|
|
||||||
},
|
|
||||||
showArea: true,
|
|
||||||
plugins: [],
|
|
||||||
low: 0,
|
|
||||||
high: 100
|
|
||||||
}
|
|
||||||
campaignTable = $("#campaignTable").DataTable({
|
campaignTable = $("#campaignTable").DataTable({
|
||||||
columnDefs: [{
|
columnDefs: [{
|
||||||
orderable: false,
|
orderable: false,
|
||||||
targets: "no-sort"
|
targets: "no-sort"
|
||||||
}],
|
},
|
||||||
|
{ className: "color-sent", targets: [2] },
|
||||||
|
{ className: "color-opened", targets: [3] },
|
||||||
|
{ className: "color-clicked", targets: [4] },
|
||||||
|
{ className: "color-success", targets: [5] }],
|
||||||
order: [
|
order: [
|
||||||
[1, "desc"]
|
[1, "desc"]
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
$.each(campaigns, function(i, campaign) {
|
$.each(campaigns, function (i, campaign) {
|
||||||
var campaign_date = moment(campaign.created_date).format('MMMM Do YYYY, h:mm:ss a')
|
var campaign_date = moment(campaign.created_date).format('MMMM Do YYYY, h:mm:ss a')
|
||||||
var label = statuses[campaign.status].label || "label-default";
|
var label = statuses[campaign.status].label || "label-default";
|
||||||
//section for tooltips on the status of a campaign to show some quick stats
|
//section for tooltips on the status of a campaign to show some quick stats
|
||||||
var launchDate;
|
var launchDate;
|
||||||
if (moment(campaign.launch_date).isAfter(moment())) {
|
if (moment(campaign.launch_date).isAfter(moment())) {
|
||||||
launchDate = "Scheduled to start: " + moment(campaign.launch_date).format('MMMM Do YYYY, h:mm:ss a')
|
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
|
var quickStats = launchDate + "<br><br>" + "Number of recipients: " + campaign.stats.total
|
||||||
} else {
|
} else {
|
||||||
launchDate = "Launch Date: " + moment(campaign.launch_date).format('MMMM Do YYYY, h:mm:ss a')
|
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
|
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
|
// Add it to the table
|
||||||
campaignTable.row.add([
|
campaignTable.row.add([
|
||||||
escapeHtml(campaign.name),
|
escapeHtml(campaign.name),
|
||||||
campaign_date,
|
campaign_date,
|
||||||
"<span class=\"label " + label + "\" data-toggle=\"tooltip\" data-placement=\"right\" data-html=\"true\" title=\"" + quickStats + "\">" + campaign.status + "</span>",
|
campaign.stats.sent,
|
||||||
"<div class='pull-right'><a class='btn btn-primary' href='/campaigns/" + campaign.id + "' data-toggle='tooltip' data-placement='left' title='View Results'>\
|
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>\
|
<i class='fa fa-bar-chart'></i>\
|
||||||
</a>\
|
</a>\
|
||||||
<button class='btn btn-danger' onclick='deleteCampaign(" + i + ")' data-toggle='tooltip' data-placement='left' title='Delete Campaign'>\
|
<button class='btn btn-danger' onclick='deleteCampaign(" + i + ")' data-toggle='tooltip' data-placement='left' title='Delete Campaign'>\
|
||||||
<i class='fa fa-trash-o'></i>\
|
<i class='fa fa-trash-o'></i>\
|
||||||
</button></div>"
|
</button></div>"
|
||||||
]).draw()
|
]).draw()
|
||||||
$('[data-toggle="tooltip"]').tooltip()
|
$('[data-toggle="tooltip"]').tooltip()
|
||||||
// Add it to the chart data
|
})
|
||||||
campaign.y = 0
|
// Build the charts
|
||||||
campaign.y += campaign.stats.clicked + campaign.stats.submitted_data
|
generateStatsPieCharts(campaigns)
|
||||||
campaign.y = Math.floor((campaign.y / campaign.stats.total) * 100)
|
generateTimelineChart(campaigns)
|
||||||
// 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
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
$("#emptyMessage").show()
|
$("#emptyMessage").show()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.error(function() {
|
.error(function () {
|
||||||
errorFlash("Error fetching campaigns")
|
errorFlash("Error fetching campaigns")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -41,165 +41,165 @@ var api = {
|
||||||
// campaigns contains the endpoints for /campaigns
|
// campaigns contains the endpoints for /campaigns
|
||||||
campaigns: {
|
campaigns: {
|
||||||
// get() - Queries the API for GET /campaigns
|
// get() - Queries the API for GET /campaigns
|
||||||
get: function() {
|
get: function () {
|
||||||
return query("/campaigns/", "GET", {}, false)
|
return query("/campaigns/", "GET", {}, false)
|
||||||
},
|
},
|
||||||
// post() - Posts a campaign to POST /campaigns
|
// post() - Posts a campaign to POST /campaigns
|
||||||
post: function(data) {
|
post: function (data) {
|
||||||
return query("/campaigns/", "POST", data, false)
|
return query("/campaigns/", "POST", data, false)
|
||||||
},
|
},
|
||||||
// summary() - Queries the API for GET /campaigns/summary
|
// summary() - Queries the API for GET /campaigns/summary
|
||||||
summary: function() {
|
summary: function () {
|
||||||
return query("/campaigns/summary", "GET", {}, false)
|
return query("/campaigns/summary", "GET", {}, false)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// campaignId contains the endpoints for /campaigns/:id
|
// campaignId contains the endpoints for /campaigns/:id
|
||||||
campaignId: {
|
campaignId: {
|
||||||
// get() - Queries the API for GET /campaigns/:id
|
// get() - Queries the API for GET /campaigns/:id
|
||||||
get: function(id) {
|
get: function (id) {
|
||||||
return query("/campaigns/" + id, "GET", {}, true)
|
return query("/campaigns/" + id, "GET", {}, true)
|
||||||
},
|
},
|
||||||
// delete() - Deletes a campaign at DELETE /campaigns/:id
|
// delete() - Deletes a campaign at DELETE /campaigns/:id
|
||||||
delete: function(id) {
|
delete: function (id) {
|
||||||
return query("/campaigns/" + id, "DELETE", {}, false)
|
return query("/campaigns/" + id, "DELETE", {}, false)
|
||||||
},
|
},
|
||||||
// results() - Queries the API for GET /campaigns/:id/results
|
// results() - Queries the API for GET /campaigns/:id/results
|
||||||
results: function(id) {
|
results: function (id) {
|
||||||
return query("/campaigns/" + id + "/results", "GET", {}, true)
|
return query("/campaigns/" + id + "/results", "GET", {}, true)
|
||||||
},
|
},
|
||||||
// complete() - Completes a campaign at POST /campaigns/:id/complete
|
// complete() - Completes a campaign at POST /campaigns/:id/complete
|
||||||
complete: function(id) {
|
complete: function (id) {
|
||||||
return query("/campaigns/" + id + "/complete", "GET", {}, true)
|
return query("/campaigns/" + id + "/complete", "GET", {}, true)
|
||||||
},
|
},
|
||||||
// summary() - Queries the API for GET /campaigns/summary
|
// summary() - Queries the API for GET /campaigns/summary
|
||||||
summary: function(id) {
|
summary: function (id) {
|
||||||
return query("/campaigns/" + id + "/summary", "GET", {}, true)
|
return query("/campaigns/" + id + "/summary", "GET", {}, true)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// groups contains the endpoints for /groups
|
// groups contains the endpoints for /groups
|
||||||
groups: {
|
groups: {
|
||||||
// get() - Queries the API for GET /groups
|
// get() - Queries the API for GET /groups
|
||||||
get: function() {
|
get: function () {
|
||||||
return query("/groups/", "GET", {}, false)
|
return query("/groups/", "GET", {}, false)
|
||||||
},
|
},
|
||||||
// post() - Posts a group to POST /groups
|
// post() - Posts a group to POST /groups
|
||||||
post: function(group) {
|
post: function (group) {
|
||||||
return query("/groups/", "POST", group, false)
|
return query("/groups/", "POST", group, false)
|
||||||
},
|
},
|
||||||
// summary() - Queries the API for GET /groups/summary
|
// summary() - Queries the API for GET /groups/summary
|
||||||
summary: function() {
|
summary: function () {
|
||||||
return query("/groups/summary", "GET", {}, true)
|
return query("/groups/summary", "GET", {}, true)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// groupId contains the endpoints for /groups/:id
|
// groupId contains the endpoints for /groups/:id
|
||||||
groupId: {
|
groupId: {
|
||||||
// get() - Queries the API for GET /groups/:id
|
// get() - Queries the API for GET /groups/:id
|
||||||
get: function(id) {
|
get: function (id) {
|
||||||
return query("/groups/" + id, "GET", {}, false)
|
return query("/groups/" + id, "GET", {}, false)
|
||||||
},
|
},
|
||||||
// put() - Puts a group to PUT /groups/:id
|
// put() - Puts a group to PUT /groups/:id
|
||||||
put: function(group) {
|
put: function (group) {
|
||||||
return query("/groups/" + group.id, "PUT", group, false)
|
return query("/groups/" + group.id, "PUT", group, false)
|
||||||
},
|
},
|
||||||
// delete() - Deletes a group at DELETE /groups/:id
|
// delete() - Deletes a group at DELETE /groups/:id
|
||||||
delete: function(id) {
|
delete: function (id) {
|
||||||
return query("/groups/" + id, "DELETE", {}, false)
|
return query("/groups/" + id, "DELETE", {}, false)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// templates contains the endpoints for /templates
|
// templates contains the endpoints for /templates
|
||||||
templates: {
|
templates: {
|
||||||
// get() - Queries the API for GET /templates
|
// get() - Queries the API for GET /templates
|
||||||
get: function() {
|
get: function () {
|
||||||
return query("/templates/", "GET", {}, false)
|
return query("/templates/", "GET", {}, false)
|
||||||
},
|
},
|
||||||
// post() - Posts a template to POST /templates
|
// post() - Posts a template to POST /templates
|
||||||
post: function(template) {
|
post: function (template) {
|
||||||
return query("/templates/", "POST", template, false)
|
return query("/templates/", "POST", template, false)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// templateId contains the endpoints for /templates/:id
|
// templateId contains the endpoints for /templates/:id
|
||||||
templateId: {
|
templateId: {
|
||||||
// get() - Queries the API for GET /templates/:id
|
// get() - Queries the API for GET /templates/:id
|
||||||
get: function(id) {
|
get: function (id) {
|
||||||
return query("/templates/" + id, "GET", {}, false)
|
return query("/templates/" + id, "GET", {}, false)
|
||||||
},
|
},
|
||||||
// put() - Puts a template to PUT /templates/:id
|
// put() - Puts a template to PUT /templates/:id
|
||||||
put: function(template) {
|
put: function (template) {
|
||||||
return query("/templates/" + template.id, "PUT", template, false)
|
return query("/templates/" + template.id, "PUT", template, false)
|
||||||
},
|
},
|
||||||
// delete() - Deletes a template at DELETE /templates/:id
|
// delete() - Deletes a template at DELETE /templates/:id
|
||||||
delete: function(id) {
|
delete: function (id) {
|
||||||
return query("/templates/" + id, "DELETE", {}, false)
|
return query("/templates/" + id, "DELETE", {}, false)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// pages contains the endpoints for /pages
|
// pages contains the endpoints for /pages
|
||||||
pages: {
|
pages: {
|
||||||
// get() - Queries the API for GET /pages
|
// get() - Queries the API for GET /pages
|
||||||
get: function() {
|
get: function () {
|
||||||
return query("/pages/", "GET", {}, false)
|
return query("/pages/", "GET", {}, false)
|
||||||
},
|
},
|
||||||
// post() - Posts a page to POST /pages
|
// post() - Posts a page to POST /pages
|
||||||
post: function(page) {
|
post: function (page) {
|
||||||
return query("/pages/", "POST", page, false)
|
return query("/pages/", "POST", page, false)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// pageId contains the endpoints for /pages/:id
|
// pageId contains the endpoints for /pages/:id
|
||||||
pageId: {
|
pageId: {
|
||||||
// get() - Queries the API for GET /pages/:id
|
// get() - Queries the API for GET /pages/:id
|
||||||
get: function(id) {
|
get: function (id) {
|
||||||
return query("/pages/" + id, "GET", {}, false)
|
return query("/pages/" + id, "GET", {}, false)
|
||||||
},
|
},
|
||||||
// put() - Puts a page to PUT /pages/:id
|
// put() - Puts a page to PUT /pages/:id
|
||||||
put: function(page) {
|
put: function (page) {
|
||||||
return query("/pages/" + page.id, "PUT", page, false)
|
return query("/pages/" + page.id, "PUT", page, false)
|
||||||
},
|
},
|
||||||
// delete() - Deletes a page at DELETE /pages/:id
|
// delete() - Deletes a page at DELETE /pages/:id
|
||||||
delete: function(id) {
|
delete: function (id) {
|
||||||
return query("/pages/" + id, "DELETE", {}, false)
|
return query("/pages/" + id, "DELETE", {}, false)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// SMTP contains the endpoints for /smtp
|
// SMTP contains the endpoints for /smtp
|
||||||
SMTP: {
|
SMTP: {
|
||||||
// get() - Queries the API for GET /smtp
|
// get() - Queries the API for GET /smtp
|
||||||
get: function() {
|
get: function () {
|
||||||
return query("/smtp/", "GET", {}, false)
|
return query("/smtp/", "GET", {}, false)
|
||||||
},
|
},
|
||||||
// post() - Posts a SMTP to POST /smtp
|
// post() - Posts a SMTP to POST /smtp
|
||||||
post: function(smtp) {
|
post: function (smtp) {
|
||||||
return query("/smtp/", "POST", smtp, false)
|
return query("/smtp/", "POST", smtp, false)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// SMTPId contains the endpoints for /smtp/:id
|
// SMTPId contains the endpoints for /smtp/:id
|
||||||
SMTPId: {
|
SMTPId: {
|
||||||
// get() - Queries the API for GET /smtp/:id
|
// get() - Queries the API for GET /smtp/:id
|
||||||
get: function(id) {
|
get: function (id) {
|
||||||
return query("/smtp/" + id, "GET", {}, false)
|
return query("/smtp/" + id, "GET", {}, false)
|
||||||
},
|
},
|
||||||
// put() - Puts a SMTP to PUT /smtp/:id
|
// put() - Puts a SMTP to PUT /smtp/:id
|
||||||
put: function(smtp) {
|
put: function (smtp) {
|
||||||
return query("/smtp/" + smtp.id, "PUT", smtp, false)
|
return query("/smtp/" + smtp.id, "PUT", smtp, false)
|
||||||
},
|
},
|
||||||
// delete() - Deletes a SMTP at DELETE /smtp/:id
|
// delete() - Deletes a SMTP at DELETE /smtp/:id
|
||||||
delete: function(id) {
|
delete: function (id) {
|
||||||
return query("/smtp/" + id, "DELETE", {}, false)
|
return query("/smtp/" + id, "DELETE", {}, false)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// import handles all of the "import" functions in the api
|
// import handles all of the "import" functions in the api
|
||||||
import_email: function(raw) {
|
import_email: function (raw) {
|
||||||
return query("/import/email", "POST", {}, false)
|
return query("/import/email", "POST", {}, false)
|
||||||
},
|
},
|
||||||
// clone_site handles importing a site by url
|
// clone_site handles importing a site by url
|
||||||
clone_site: function(req) {
|
clone_site: function (req) {
|
||||||
return query("/import/site", "POST", req, false)
|
return query("/import/site", "POST", req, false)
|
||||||
},
|
},
|
||||||
// send_test_email sends an email to the specified email address
|
// 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)
|
return query("/util/send_test_email", "POST", req, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register our moment.js datatables listeners
|
// Register our moment.js datatables listeners
|
||||||
$(document).ready(function() {
|
$(document).ready(function () {
|
||||||
$.fn.dataTable.moment('MMMM Do YYYY, h:mm:ss a');
|
$.fn.dataTable.moment('MMMM Do YYYY, h:mm:ss a');
|
||||||
// Setup tooltips
|
// Setup tooltips
|
||||||
$('[data-toggle="tooltip"]').tooltip()
|
$('[data-toggle="tooltip"]').tooltip()
|
||||||
|
|
|
@ -17,7 +17,9 @@
|
||||||
</li>
|
</li>
|
||||||
<li><a href="/settings">Settings</a>
|
<li><a href="/settings">Settings</a>
|
||||||
</li>
|
</li>
|
||||||
<li><hr></li>
|
<li>
|
||||||
|
<hr>
|
||||||
|
</li>
|
||||||
<li><a href="https://gophish.gitbooks.io/user-guide/content/">User Guide</a>
|
<li><a href="https://gophish.gitbooks.io/user-guide/content/">User Guide</a>
|
||||||
</li>
|
</li>
|
||||||
<li><a href="/api/">API Documentation</a>
|
<li><a href="/api/">API Documentation</a>
|
||||||
|
@ -38,16 +40,17 @@
|
||||||
<a href="/campaigns" class="btn btn-default">
|
<a href="/campaigns" class="btn btn-default">
|
||||||
<i class="fa fa-arrow-circle-o-left fa-lg"></i> Back
|
<i class="fa fa-arrow-circle-o-left fa-lg"></i> Back
|
||||||
</a>
|
</a>
|
||||||
<div class="btn-group">
|
<div class="btn-group">
|
||||||
<button type="button" id="exportButton" class="btn btn-primary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
|
<button type="button" id="exportButton" class="btn btn-primary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true"
|
||||||
<i class="fa fa-file-excel-o"></i> Export CSV
|
aria-expanded="true">
|
||||||
<i class="fa fa-caret-down"></i>
|
<i class="fa fa-file-excel-o"></i> Export CSV
|
||||||
</button>
|
<i class="fa fa-caret-down"></i>
|
||||||
<ul class="dropdown-menu" aria-labelledby="exportButton">
|
</button>
|
||||||
<li><a href="#" onclick="exportAsCSV('results')">Results</a></li>
|
<ul class="dropdown-menu" aria-labelledby="exportButton">
|
||||||
<li><a href="#" onclick="exportAsCSV('events')">Raw Events</a></li>
|
<li><a href="#" onclick="exportAsCSV('results')">Results</a></li>
|
||||||
</ul>
|
<li><a href="#" onclick="exportAsCSV('events')">Raw Events</a></li>
|
||||||
</div>
|
</ul>
|
||||||
|
</div>
|
||||||
<button id="complete_button" type="button" class="btn btn-blue" data-toggle="tooltip" onclick="completeCampaign()">
|
<button id="complete_button" type="button" class="btn btn-blue" data-toggle="tooltip" onclick="completeCampaign()">
|
||||||
<i class="fa fa-flag-checkered"></i> Complete
|
<i class="fa fa-flag-checkered"></i> Complete
|
||||||
</button>
|
</button>
|
||||||
|
@ -57,72 +60,60 @@
|
||||||
<button id="refresh_btn" type="button" class="btn btn-blue" data-toggle="tooltip" onclick="refresh()">
|
<button id="refresh_btn" type="button" class="btn btn-blue" data-toggle="tooltip" onclick="refresh()">
|
||||||
<i class="fa fa-refresh fa-lg"></i> Refresh
|
<i class="fa fa-refresh fa-lg"></i> Refresh
|
||||||
</button>
|
</button>
|
||||||
<span id="refresh_message">
|
<span id="refresh_message">
|
||||||
<i class="fa fa-spin fa-spinner"></i> Refreshing
|
<i class="fa fa-spin fa-spinner"></i> Refreshing
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<br />
|
<br />
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<ul class="nav nav-tabs" role="tablist">
|
<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 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>
|
</ul>
|
||||||
<div class="tab-content">
|
<div class="tab-content">
|
||||||
<div role="tabpanel" class="tab-pane active" id="overview">
|
<div role="tabpanel" class="tab-pane active" id="overview">
|
||||||
<br/>
|
<br/>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-lg-6 col-md-6 col-sm-12 col-xs-12">
|
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||||
<p style="text-align:center;">Campaign Timeline</p>
|
|
||||||
<br/>
|
|
||||||
<div id="timeline_chart"></div>
|
<div id="timeline_chart"></div>
|
||||||
</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>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-6">
|
<div id="sent_chart" style="height:200px;" class="col-lg-3 col-md-3"></div>
|
||||||
<p style="text-align:center;">Targets Map</p>
|
<div id="opened_chart" style="height:200px;" class="col-lg-3 col-md-3"></div>
|
||||||
<div id="resultsMap"></div>
|
<div id="clicked_chart" style="height:200px;" class="col-lg-3 col-md-3"></div>
|
||||||
</div>
|
<div id="submitted_data_chart" style="height:200px;" class="col-lg-3 col-md-3"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!--
|
<div class="row">
|
||||||
<div role="tabpanel" class="tab-pane" id="plugins">
|
<div class="col-md-6">
|
||||||
|
<p style="text-align:center;">Targets Map</p>
|
||||||
|
<div id="resultsMap"></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div role="tabpanel" class="tab-pane" id="demographics">
|
|
||||||
Demographics here
|
|
||||||
</div> -->
|
|
||||||
</div>
|
</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>
|
||||||
<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>
|
</div>
|
||||||
{{end}}
|
<div id="flashes" class="row"></div>
|
||||||
{{define "scripts"}}
|
</div>
|
||||||
|
{{end}} {{define "scripts"}}
|
||||||
<script src="/js/dist/app/campaign_results.min.js"></script>
|
<script src="/js/dist/app/campaign_results.min.js"></script>
|
||||||
{{end}}
|
{{end}}
|
|
@ -38,19 +38,15 @@
|
||||||
No campaigns created yet. Let's create one!
|
No campaigns created yet. Let's create one!
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="dashboard" style="display:none;">
|
<div id="dashboard">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div id="overview_chart" class="col-lg-6 col-md-6 col-sm-12 col-xs-12">
|
<div id="overview_chart" style="height:200px;" class="col-lg-12 col-md-12 col-sm-12 col-xs-12"></div>
|
||||||
<p style="text-align:center;">Phishing Success Overview</p>
|
</div>
|
||||||
</div>
|
<div class="row">
|
||||||
<div class="col-lg-6 col-md-6 col-sm-12 col-xs-12">
|
<div id="sent_chart" style="height:200px;" class="col-lg-3 col-md-3"></div>
|
||||||
<p style="text-align:center;">Average Phishing Results</p>
|
<div id="opened_chart" style="height:200px;" class="col-lg-3 col-md-3"></div>
|
||||||
<div id="stats_chart" class="col-lg-7 col-md-7"></div>
|
<div id="clicked_chart" style="height:200px;" class="col-lg-3 col-md-3"></div>
|
||||||
<div class="col-lg-5 col-md-5">
|
<div id="submitted_data_chart" style="height:200px;" class="col-lg-3 col-md-3"></div>
|
||||||
<ul id="stats_chart_legend" class="chartist-legend">
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<h2>Recent Campaigns</h2>
|
<h2>Recent Campaigns</h2>
|
||||||
|
@ -63,10 +59,14 @@
|
||||||
<table id="campaignTable" class="table">
|
<table id="campaignTable" class="table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Name</th>
|
<th class="col-md-2 col-sm-2">Name</th>
|
||||||
<th>Created Date</th>
|
<th class="col-md-2 col-sm-2">Created Date</th>
|
||||||
<th>Status</th>
|
<th class="col-md-1 col-sm-1"><i class="fa fa-envelope-o"></i></th>
|
||||||
<th class="col-md-2 col-sm-2 no-sort"></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>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
|
Loading…
Reference in New Issue