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
31
gulpfile.js
31
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,17 +17,9 @@ 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([
|
||||
return gulp.src([
|
||||
vendor_directory + 'jquery.js',
|
||||
vendor_directory + 'bootstrap.min.js',
|
||||
vendor_directory + 'moment.min.js',
|
||||
|
@ -47,7 +37,8 @@ gulp.task('build', function() {
|
|||
vendor_directory + 'sweetalert2.min.js',
|
||||
vendor_directory + 'bootstrap-datetime.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(rename({
|
||||
|
@ -55,7 +46,9 @@ gulp.task('build', function() {
|
|||
}))
|
||||
.pipe(uglify())
|
||||
.pipe(gulp.dest(dest_js_directory));
|
||||
})
|
||||
|
||||
gulp.task('scripts', function () {
|
||||
// Gophish app files
|
||||
gulp.src(app_directory)
|
||||
.pipe(rename({
|
||||
|
@ -65,7 +58,9 @@ gulp.task('build', function() {
|
|||
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',
|
||||
|
@ -78,13 +73,13 @@ gulp.task('build', function() {
|
|||
css_directory + 'checkbox.css',
|
||||
css_directory + 'sweetalert2.min.css',
|
||||
css_directory + 'select2.min.css',
|
||||
css_directory + 'select2-bootstrap.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.
3316
static/font/fontawesome-webfont.svg
vendored
3316
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,6 +115,7 @@ function deleteCampaign() {
|
|||
confirmButtonColor: "#428bca",
|
||||
reverseButtons: true,
|
||||
allowOutsideClick: false,
|
||||
showLoaderOnConfirm: true,
|
||||
preConfirm: function () {
|
||||
return new Promise(function (resolve, reject) {
|
||||
api.campaignId.delete(campaign.id)
|
||||
|
@ -128,6 +151,7 @@ function completeCampaign() {
|
|||
confirmButtonColor: "#428bca",
|
||||
reverseButtons: true,
|
||||
allowOutsideClick: false,
|
||||
showLoaderOnConfirm: true,
|
||||
preConfirm: function () {
|
||||
return new Promise(function (resolve, reject) {
|
||||
api.campaignId.complete(campaign.id)
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -310,51 +459,64 @@ function poll() {
|
|||
.success(function (c) {
|
||||
campaign = c
|
||||
/* Update the timeline */
|
||||
var timeline_data = {
|
||||
series: [{
|
||||
name: "Events",
|
||||
data: []
|
||||
}]
|
||||
}
|
||||
var timeline_series_data = []
|
||||
$.each(campaign.timeline, function (i, event) {
|
||||
timeline_data.series[0].data.push({
|
||||
meta: i,
|
||||
x: new Date(event.time),
|
||||
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 = {}
|
||||
// Load the initial data
|
||||
Object.keys(statusMapping).forEach(function (k) {
|
||||
email_series_data[k] = 0
|
||||
});
|
||||
$.each(campaign.results, function (i, result) {
|
||||
if (!email_series_data[result.status]) {
|
||||
email_series_data[result.status] = 1
|
||||
} else {
|
||||
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
|
||||
})
|
||||
$("#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)
|
||||
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
|
||||
})
|
||||
})
|
||||
/* Update the datatable */
|
||||
resultsTable = $("#resultsTable").DataTable()
|
||||
resultsTable.rows().every(function (i, tableLoop, rowLoop) {
|
||||
|
@ -436,42 +598,6 @@ function load() {
|
|||
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
|
||||
resultsTable = $("#resultsTable").DataTable({
|
||||
destroy: true,
|
||||
|
@ -490,6 +616,11 @@ function load() {
|
|||
}]
|
||||
});
|
||||
resultsTable.clear();
|
||||
var email_series_data = {}
|
||||
var timeline_series_data = []
|
||||
Object.keys(statusMapping).forEach(function (k) {
|
||||
email_series_data[k] = 0
|
||||
});
|
||||
$.each(campaign.results, function (i, result) {
|
||||
label = statuses[result.status].label || "label-default";
|
||||
resultsTable.row.add([
|
||||
|
@ -501,10 +632,11 @@ function load() {
|
|||
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]++;
|
||||
// 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
|
||||
|
@ -529,87 +661,41 @@ 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
|
||||
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
|
||||
}
|
||||
})
|
||||
})
|
||||
$("#email_chart_legend").html("")
|
||||
renderTimelineChart({
|
||||
data: timeline_series_data
|
||||
})
|
||||
$.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)
|
||||
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 = renderPieChart({
|
||||
elemId: statusMapping[status] + '_chart',
|
||||
title: status,
|
||||
name: status,
|
||||
data: email_data,
|
||||
colors: [statuses[status].color, '#dddddd']
|
||||
})
|
||||
// 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)
|
||||
}
|
||||
$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)
|
||||
})
|
||||
// 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
|
||||
});
|
||||
});
|
||||
if (!map) {
|
||||
map = new Datamap({
|
||||
element: document.getElementById("resultsMap"),
|
||||
|
@ -692,8 +778,12 @@ function refresh() {
|
|||
|
||||
|
||||
$(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,7 +82,6 @@ var statsMapping = {
|
|||
"opened": "Email Opened",
|
||||
"clicked": "Clicked Link",
|
||||
"submitted_data": "Submitted Data",
|
||||
"error": "Error"
|
||||
}
|
||||
|
||||
function deleteCampaign(idx) {
|
||||
|
@ -105,17 +94,64 @@ function deleteCampaign(idx) {
|
|||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
|
@ -134,44 +170,108 @@ function generateStatsPieChart(campaigns) {
|
|||
$.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>')
|
||||
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)
|
||||
var stats_chart = renderPieChart({
|
||||
elemId: status + '_chart',
|
||||
title: status_label,
|
||||
name: status,
|
||||
data: stats_data,
|
||||
colors: [statuses[status_label].color, "#dddddd"]
|
||||
})
|
||||
// Update with the latest data
|
||||
$piechart.get(0).__chartist__.update(stats_data)
|
||||
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
|
||||
});
|
||||
});
|
||||
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 () {
|
||||
|
@ -182,26 +282,15 @@ $(document).ready(function() {
|
|||
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
|
||||
}
|
||||
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"]
|
||||
]
|
||||
|
@ -222,6 +311,10 @@ $(document).ready(function() {
|
|||
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>\
|
||||
|
@ -231,48 +324,10 @@ $(document).ready(function() {
|
|||
</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
|
||||
});
|
||||
generateStatsPieCharts(campaigns)
|
||||
generateTimelineChart(campaigns)
|
||||
} else {
|
||||
$("#emptyMessage").show()
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
@ -39,7 +41,8 @@
|
|||
<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">
|
||||
<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>
|
||||
|
@ -65,25 +68,20 @@
|
|||
<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 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 class="row">
|
||||
|
@ -93,12 +91,6 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!--
|
||||
<div role="tabpanel" class="tab-pane" id="plugins">
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane" id="demographics">
|
||||
Demographics here
|
||||
</div> -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
|
@ -122,7 +114,6 @@
|
|||
</div>
|
||||
<div id="flashes" class="row"></div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{define "scripts"}}
|
||||
{{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 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