mirror of https://github.com/gophish/gophish
Adding "Report Email" Support (#1014)
Adds the capability to report phishing campaigns using an email client extension. **Note: Gophish does not currently provide an email client extension out of the box. This is simply a mechanism to let existing email client add-ons send report status information to Gophish, and have that information reflected in the dashboard.**pull/1003/merge
parent
709e83bade
commit
f21536da7c
|
@ -39,6 +39,8 @@ func CreatePhishingRouter() http.Handler {
|
||||||
router.HandleFunc("/track", PhishTracker)
|
router.HandleFunc("/track", PhishTracker)
|
||||||
router.HandleFunc("/robots.txt", RobotsHandler)
|
router.HandleFunc("/robots.txt", RobotsHandler)
|
||||||
router.HandleFunc("/{path:.*}/track", PhishTracker)
|
router.HandleFunc("/{path:.*}/track", PhishTracker)
|
||||||
|
router.HandleFunc("/{path:.*}/report", PhishReporter)
|
||||||
|
router.HandleFunc("/report", PhishReporter)
|
||||||
router.HandleFunc("/{path:.*}", PhishHandler)
|
router.HandleFunc("/{path:.*}", PhishHandler)
|
||||||
return router
|
return router
|
||||||
}
|
}
|
||||||
|
@ -71,6 +73,29 @@ func PhishTracker(w http.ResponseWriter, r *http.Request) {
|
||||||
http.ServeFile(w, r, "static/images/pixel.png")
|
http.ServeFile(w, r, "static/images/pixel.png")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PhishReporter tracks emails as they are reported, updating the status for the given Result
|
||||||
|
func PhishReporter(w http.ResponseWriter, r *http.Request) {
|
||||||
|
err, r := setupContext(r)
|
||||||
|
if err != nil {
|
||||||
|
// Log the error if it wasn't something we can safely ignore
|
||||||
|
if err != ErrInvalidRequest && err != ErrCampaignComplete {
|
||||||
|
Logger.Println(err)
|
||||||
|
}
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rs := ctx.Get(r, "result").(models.Result)
|
||||||
|
c := ctx.Get(r, "campaign").(models.Campaign)
|
||||||
|
rj := ctx.Get(r, "details").([]byte)
|
||||||
|
c.AddEvent(models.Event{Email: rs.Email, Message: models.EVENT_REPORTED, Details: string(rj)})
|
||||||
|
|
||||||
|
err = rs.UpdateReported(true)
|
||||||
|
if err != nil {
|
||||||
|
Logger.Println(err)
|
||||||
|
}
|
||||||
|
w.WriteHeader(http.StatusNoContent)
|
||||||
|
}
|
||||||
|
|
||||||
// PhishHandler handles incoming client connections and registers the associated actions performed
|
// PhishHandler handles incoming client connections and registers the associated actions performed
|
||||||
// (such as clicked link, etc.)
|
// (such as clicked link, etc.)
|
||||||
func PhishHandler(w http.ResponseWriter, r *http.Request) {
|
func PhishHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
|
@ -26,6 +26,12 @@ func (s *ControllersSuite) openEmail(rid string) {
|
||||||
s.Equal(bytes.Compare(body, expected), 0)
|
s.Equal(bytes.Compare(body, expected), 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *ControllersSuite) reportedEmail(rid string) {
|
||||||
|
resp, err := http.Get(fmt.Sprintf("%s/report?%s=%s", ps.URL, models.RecipientParameter, rid))
|
||||||
|
s.Nil(err)
|
||||||
|
s.Equal(resp.StatusCode, http.StatusNoContent)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *ControllersSuite) openEmail404(rid string) {
|
func (s *ControllersSuite) openEmail404(rid string) {
|
||||||
resp, err := http.Get(fmt.Sprintf("%s/track?%s=%s", ps.URL, models.RecipientParameter, rid))
|
resp, err := http.Get(fmt.Sprintf("%s/track?%s=%s", ps.URL, models.RecipientParameter, rid))
|
||||||
s.Nil(err)
|
s.Nil(err)
|
||||||
|
@ -63,6 +69,19 @@ func (s *ControllersSuite) TestOpenedPhishingEmail() {
|
||||||
s.Equal(result.Status, models.EVENT_OPENED)
|
s.Equal(result.Status, models.EVENT_OPENED)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *ControllersSuite) TestReportedPhishingEmail() {
|
||||||
|
campaign := s.getFirstCampaign()
|
||||||
|
result := campaign.Results[0]
|
||||||
|
s.Equal(result.Status, models.STATUS_SENDING)
|
||||||
|
|
||||||
|
s.reportedEmail(result.RId)
|
||||||
|
|
||||||
|
campaign = s.getFirstCampaign()
|
||||||
|
result = campaign.Results[0]
|
||||||
|
s.Equal(result.Reported, true)
|
||||||
|
s.Equal(campaign.Events[len(campaign.Events)-1].Message, models.EVENT_REPORTED)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *ControllersSuite) TestClickedPhishingLinkAfterOpen() {
|
func (s *ControllersSuite) TestClickedPhishingLinkAfterOpen() {
|
||||||
campaign := s.getFirstCampaign()
|
campaign := s.getFirstCampaign()
|
||||||
result := campaign.Results[0]
|
result := campaign.Results[0]
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
|
||||||
|
-- +goose Up
|
||||||
|
-- SQL in section 'Up' is executed when this migration is applied
|
||||||
|
ALTER TABLE results ADD COLUMN reported boolean default 0;
|
||||||
|
|
||||||
|
-- +goose Down
|
||||||
|
-- SQL section 'Down' is executed when this migration is rolled back
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
|
||||||
|
-- +goose Up
|
||||||
|
-- SQL in section 'Up' is executed when this migration is applied
|
||||||
|
ALTER TABLE results ADD COLUMN reported boolean default 0;
|
||||||
|
|
||||||
|
-- +goose Down
|
||||||
|
-- SQL section 'Down' is executed when this migration is rolled back
|
||||||
|
|
|
@ -33,6 +33,7 @@ type CampaignResults struct {
|
||||||
Id int64 `json:"id"`
|
Id int64 `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
|
Reported string `json:"reported"`
|
||||||
Results []Result `json:"results, omitempty"`
|
Results []Result `json:"results, omitempty"`
|
||||||
Events []Event `json:"timeline,omitempty"`
|
Events []Event `json:"timeline,omitempty"`
|
||||||
}
|
}
|
||||||
|
@ -61,6 +62,7 @@ type CampaignStats struct {
|
||||||
OpenedEmail int64 `json:"opened"`
|
OpenedEmail int64 `json:"opened"`
|
||||||
ClickedLink int64 `json:"clicked"`
|
ClickedLink int64 `json:"clicked"`
|
||||||
SubmittedData int64 `json:"submitted_data"`
|
SubmittedData int64 `json:"submitted_data"`
|
||||||
|
EmailReported int64 `json:"email_reported"`
|
||||||
Error int64 `json:"error"`
|
Error int64 `json:"error"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -194,6 +196,10 @@ func getCampaignStats(cid int64) (CampaignStats, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return s, err
|
return s, err
|
||||||
}
|
}
|
||||||
|
query.Where("reported=?", true).Count(&s.EmailReported)
|
||||||
|
if err != nil {
|
||||||
|
return s, err
|
||||||
|
}
|
||||||
// Every submitted data event implies they clicked the link
|
// Every submitted data event implies they clicked the link
|
||||||
s.ClickedLink += s.SubmittedData
|
s.ClickedLink += s.SubmittedData
|
||||||
err = query.Where("status=?", EVENT_OPENED).Count(&s.OpenedEmail).Error
|
err = query.Where("status=?", EVENT_OPENED).Count(&s.OpenedEmail).Error
|
||||||
|
@ -426,6 +432,7 @@ func PostCampaign(c *Campaign, uid int64) error {
|
||||||
FirstName: t.FirstName,
|
FirstName: t.FirstName,
|
||||||
LastName: t.LastName,
|
LastName: t.LastName,
|
||||||
SendDate: c.LaunchDate,
|
SendDate: c.LaunchDate,
|
||||||
|
Reported: false,
|
||||||
}
|
}
|
||||||
if c.Status == CAMPAIGN_IN_PROGRESS {
|
if c.Status == CAMPAIGN_IN_PROGRESS {
|
||||||
r.Status = STATUS_SENDING
|
r.Status = STATUS_SENDING
|
||||||
|
|
|
@ -32,6 +32,7 @@ const (
|
||||||
EVENT_OPENED string = "Email Opened"
|
EVENT_OPENED string = "Email Opened"
|
||||||
EVENT_CLICKED string = "Clicked Link"
|
EVENT_CLICKED string = "Clicked Link"
|
||||||
EVENT_DATA_SUBMIT string = "Submitted Data"
|
EVENT_DATA_SUBMIT string = "Submitted Data"
|
||||||
|
EVENT_REPORTED string = "Email Reported"
|
||||||
EVENT_PROXY_REQUEST string = "Proxied request"
|
EVENT_PROXY_REQUEST string = "Proxied request"
|
||||||
STATUS_SUCCESS string = "Success"
|
STATUS_SUCCESS string = "Success"
|
||||||
STATUS_QUEUED string = "Queued"
|
STATUS_QUEUED string = "Queued"
|
||||||
|
|
|
@ -38,6 +38,7 @@ type Result struct {
|
||||||
Latitude float64 `json:"latitude"`
|
Latitude float64 `json:"latitude"`
|
||||||
Longitude float64 `json:"longitude"`
|
Longitude float64 `json:"longitude"`
|
||||||
SendDate time.Time `json:"send_date"`
|
SendDate time.Time `json:"send_date"`
|
||||||
|
Reported bool `json:"reported" sql:"not null"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateStatus updates the status of the result in the database
|
// UpdateStatus updates the status of the result in the database
|
||||||
|
@ -45,6 +46,11 @@ func (r *Result) UpdateStatus(s string) error {
|
||||||
return db.Table("results").Where("id=?", r.Id).Update("status", s).Error
|
return db.Table("results").Where("id=?", r.Id).Update("status", s).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateReported updates when a user reports a campaign
|
||||||
|
func (r *Result) UpdateReported(s bool) error {
|
||||||
|
return db.Table("results").Where("id=?", r.Id).Update("reported", s).Error
|
||||||
|
}
|
||||||
|
|
||||||
// UpdateGeo updates the latitude and longitude of the result in
|
// UpdateGeo updates the latitude and longitude of the result in
|
||||||
// the database given an IP address
|
// the database given an IP address
|
||||||
func (r *Result) UpdateGeo(addr string) error {
|
func (r *Result) UpdateGeo(addr string) error {
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -655,6 +655,11 @@ table.dataTable {
|
||||||
color: #f39c12;
|
color: #f39c12;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.color-reported{
|
||||||
|
font-weight: bold;
|
||||||
|
color:#45d6ef;
|
||||||
|
}
|
||||||
|
|
||||||
.color-success {
|
.color-success {
|
||||||
color: #f05b4f;
|
color: #f05b4f;
|
||||||
}
|
}
|
||||||
|
|
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
|
@ -27,7 +27,7 @@ var statuses = {
|
||||||
"Email Opened": {
|
"Email Opened": {
|
||||||
color: "#f9bf3b",
|
color: "#f9bf3b",
|
||||||
label: "label-warning",
|
label: "label-warning",
|
||||||
icon: "fa-envelope",
|
icon: "fa-envelope-open",
|
||||||
point: "ct-point-opened"
|
point: "ct-point-opened"
|
||||||
},
|
},
|
||||||
"Clicked Link": {
|
"Clicked Link": {
|
||||||
|
@ -42,6 +42,13 @@ var statuses = {
|
||||||
icon: "fa-exclamation",
|
icon: "fa-exclamation",
|
||||||
point: "ct-point-clicked"
|
point: "ct-point-clicked"
|
||||||
},
|
},
|
||||||
|
//not a status, but is used for the campaign timeline and user timeline
|
||||||
|
"Email Reported": {
|
||||||
|
color: "#45d6ef",
|
||||||
|
label: "label-info",
|
||||||
|
icon: "fa-bullhorn",
|
||||||
|
point: "ct-point-reported"
|
||||||
|
},
|
||||||
"Error": {
|
"Error": {
|
||||||
color: "#6c7a89",
|
color: "#6c7a89",
|
||||||
label: "label-default",
|
label: "label-default",
|
||||||
|
@ -95,6 +102,7 @@ var statusMapping = {
|
||||||
"Email Opened": "opened",
|
"Email Opened": "opened",
|
||||||
"Clicked Link": "clicked",
|
"Clicked Link": "clicked",
|
||||||
"Submitted Data": "submitted_data",
|
"Submitted Data": "submitted_data",
|
||||||
|
"Email Reported": "reported",
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is an underwhelming attempt at an enum
|
// This is an underwhelming attempt at an enum
|
||||||
|
@ -282,7 +290,8 @@ function renderTimeline(data) {
|
||||||
"email": data[4],
|
"email": data[4],
|
||||||
"position": data[5],
|
"position": data[5],
|
||||||
"status": data[6],
|
"status": data[6],
|
||||||
"send_date": data[7]
|
"send_date": data[7],
|
||||||
|
"reported": data[8]
|
||||||
}
|
}
|
||||||
results = '<div class="timeline col-sm-12 well well-lg">' +
|
results = '<div class="timeline col-sm-12 well well-lg">' +
|
||||||
'<h6>Timeline for ' + escapeHtml(record.first_name) + ' ' + escapeHtml(record.last_name) +
|
'<h6>Timeline for ' + escapeHtml(record.first_name) + ' ' + escapeHtml(record.last_name) +
|
||||||
|
@ -571,6 +580,9 @@ function poll() {
|
||||||
});
|
});
|
||||||
$.each(campaign.results, function (i, result) {
|
$.each(campaign.results, function (i, result) {
|
||||||
email_series_data[result.status]++;
|
email_series_data[result.status]++;
|
||||||
|
if (result.reported) {
|
||||||
|
email_series_data['Email Reported']++
|
||||||
|
}
|
||||||
// Backfill status values
|
// Backfill status values
|
||||||
var step = progressListing.indexOf(result.status)
|
var step = progressListing.indexOf(result.status)
|
||||||
for (var i = 0; i < step; i++) {
|
for (var i = 0; i < step; i++) {
|
||||||
|
@ -595,6 +607,7 @@ function poll() {
|
||||||
data: email_data
|
data: 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) {
|
||||||
|
@ -603,12 +616,13 @@ function poll() {
|
||||||
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) {
|
||||||
rowData[7] = moment(result.send_date).format('MMMM Do YYYY, h:mm:ss a')
|
rowData[8] = moment(result.send_date).format('MMMM Do YYYY, h:mm:ss a')
|
||||||
|
rowData[7] = result.reported
|
||||||
rowData[6] = result.status
|
rowData[6] = result.status
|
||||||
resultsTable.row(i).data(rowData)
|
resultsTable.row(i).data(rowData)
|
||||||
if (row.child.isShown()) {
|
if (row.child.isShown()) {
|
||||||
$(row.node()).find("i").removeClass("fa-caret-right")
|
$(row.node()).find("#caret").removeClass("fa-caret-right")
|
||||||
$(row.node()).find("i").addClass("fa-caret-down")
|
$(row.node()).find("#caret").addClass("fa-caret-down")
|
||||||
row.child(renderTimeline(row.data()))
|
row.child(renderTimeline(row.data()))
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
@ -669,13 +683,24 @@ function load() {
|
||||||
"targets": [1]
|
"targets": [1]
|
||||||
}, {
|
}, {
|
||||||
"visible": false,
|
"visible": false,
|
||||||
"targets": [0, 7]
|
"targets": [0, 8]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"render": function (data, type, row) {
|
"render": function (data, type, row) {
|
||||||
return createStatusLabel(data, row[7])
|
return createStatusLabel(data, row[8])
|
||||||
},
|
},
|
||||||
"targets": [6]
|
"targets": [6]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
className: "text-center",
|
||||||
|
"render": function (reported, type, row) {
|
||||||
|
if (reported) {
|
||||||
|
return "<i class='fa fa-check-circle text-center text-success'></i>"
|
||||||
|
} else {
|
||||||
|
return "<i class='fa fa-times-circle text-center text-danger'></i>"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [7]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
@ -688,15 +713,19 @@ function load() {
|
||||||
$.each(campaign.results, function (i, result) {
|
$.each(campaign.results, function (i, result) {
|
||||||
resultsTable.row.add([
|
resultsTable.row.add([
|
||||||
result.id,
|
result.id,
|
||||||
"<i class=\"fa fa-caret-right\"></i>",
|
"<i id=\"caret\" class=\"fa fa-caret-right\"></i>",
|
||||||
escapeHtml(result.first_name) || "",
|
escapeHtml(result.first_name) || "",
|
||||||
escapeHtml(result.last_name) || "",
|
escapeHtml(result.last_name) || "",
|
||||||
escapeHtml(result.email) || "",
|
escapeHtml(result.email) || "",
|
||||||
escapeHtml(result.position) || "",
|
escapeHtml(result.position) || "",
|
||||||
result.status,
|
result.status,
|
||||||
|
result.reported,
|
||||||
moment(result.send_date).format('MMMM Do YYYY, h:mm:ss a')
|
moment(result.send_date).format('MMMM Do YYYY, h:mm:ss a')
|
||||||
])
|
])
|
||||||
email_series_data[result.status]++;
|
email_series_data[result.status]++;
|
||||||
|
if (result.reported) {
|
||||||
|
email_series_data['Email Reported']++
|
||||||
|
}
|
||||||
// Backfill status values
|
// Backfill status values
|
||||||
var step = progressListing.indexOf(result.status)
|
var step = progressListing.indexOf(result.status)
|
||||||
for (var i = 0; i < step; i++) {
|
for (var i = 0; i < step; i++) {
|
||||||
|
@ -761,6 +790,7 @@ function load() {
|
||||||
colors: [statuses[status].color, '#dddddd']
|
colors: [statuses[status].color, '#dddddd']
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
if (use_map) {
|
if (use_map) {
|
||||||
$("#resultsMapContainer").show()
|
$("#resultsMapContainer").show()
|
||||||
map = new Datamap({
|
map = new Datamap({
|
||||||
|
|
|
@ -324,7 +324,7 @@ $(document).ready(function () {
|
||||||
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 + "Reported : " + campaign.stats.reported
|
||||||
}
|
}
|
||||||
|
|
||||||
campaignTable.row.add([
|
campaignTable.row.add([
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
var campaigns = []
|
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": {
|
||||||
|
@ -29,6 +28,12 @@ var statuses = {
|
||||||
icon: "fa-envelope",
|
icon: "fa-envelope",
|
||||||
point: "ct-point-opened"
|
point: "ct-point-opened"
|
||||||
},
|
},
|
||||||
|
"Email Reported": {
|
||||||
|
color: "#45d6ef",
|
||||||
|
label: "label-warning",
|
||||||
|
icon: "fa-bullhorne",
|
||||||
|
point: "ct-point-reported"
|
||||||
|
},
|
||||||
"Clicked Link": {
|
"Clicked Link": {
|
||||||
color: "#F39C12",
|
color: "#F39C12",
|
||||||
label: "label-clicked",
|
label: "label-clicked",
|
||||||
|
@ -80,6 +85,7 @@ var statuses = {
|
||||||
var statsMapping = {
|
var statsMapping = {
|
||||||
"sent": "Email Sent",
|
"sent": "Email Sent",
|
||||||
"opened": "Email Opened",
|
"opened": "Email Opened",
|
||||||
|
"email_reported": "Email Reported",
|
||||||
"clicked": "Clicked Link",
|
"clicked": "Clicked Link",
|
||||||
"submitted_data": "Submitted Data",
|
"submitted_data": "Submitted Data",
|
||||||
}
|
}
|
||||||
|
@ -107,16 +113,18 @@ function renderPieChart(chartopts) {
|
||||||
left = chart.plotLeft + pie.center[0],
|
left = chart.plotLeft + pie.center[0],
|
||||||
top = chart.plotTop + pie.center[1];
|
top = chart.plotTop + pie.center[1];
|
||||||
this.innerText = rend.text(chartopts['data'][0].count, left, top).
|
this.innerText = rend.text(chartopts['data'][0].count, left, top).
|
||||||
attr({
|
attr({
|
||||||
'text-anchor': 'middle',
|
'text-anchor': 'middle',
|
||||||
'font-size': '24px',
|
'font-size': '16px',
|
||||||
'font-weight': 'bold',
|
'font-weight': 'bold',
|
||||||
'fill': chartopts['colors'][0],
|
'fill': chartopts['colors'][0],
|
||||||
'font-family': 'Helvetica,Arial,sans-serif'
|
'font-family': 'Helvetica,Arial,sans-serif'
|
||||||
}).add();
|
}).add();
|
||||||
},
|
},
|
||||||
render: function () {
|
render: function () {
|
||||||
this.innerText.attr({ text: chartopts['data'][0].count })
|
this.innerText.attr({
|
||||||
|
text: chartopts['data'][0].count
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -190,6 +198,7 @@ function generateStatsPieCharts(campaigns) {
|
||||||
data: stats_data,
|
data: stats_data,
|
||||||
colors: [statuses[status_label].color, "#dddddd"]
|
colors: [statuses[status_label].color, "#dddddd"]
|
||||||
})
|
})
|
||||||
|
|
||||||
stats_data = []
|
stats_data = []
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -289,13 +298,30 @@ $(document).ready(function () {
|
||||||
// Create the overview chart data
|
// Create the overview chart data
|
||||||
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-sent",
|
||||||
{ className: "color-clicked", targets: [4] },
|
targets: [2]
|
||||||
{ className: "color-success", targets: [5] }],
|
},
|
||||||
|
{
|
||||||
|
className: "color-opened",
|
||||||
|
targets: [3]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
className: "color-clicked",
|
||||||
|
targets: [4]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
className: "color-success",
|
||||||
|
targets: [5]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
className: "color-reported",
|
||||||
|
targets: [6]
|
||||||
|
}
|
||||||
|
],
|
||||||
order: [
|
order: [
|
||||||
[1, "desc"]
|
[1, "desc"]
|
||||||
]
|
]
|
||||||
|
@ -310,7 +336,7 @@ $(document).ready(function () {
|
||||||
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 + "<br><br>" + "Reported : " + campaign.stats.email_reported
|
||||||
}
|
}
|
||||||
// Add it to the table
|
// Add it to the table
|
||||||
campaignTable.row.add([
|
campaignTable.row.add([
|
||||||
|
@ -320,6 +346,7 @@ $(document).ready(function () {
|
||||||
campaign.stats.opened,
|
campaign.stats.opened,
|
||||||
campaign.stats.clicked,
|
campaign.stats.clicked,
|
||||||
campaign.stats.submitted_data,
|
campaign.stats.submitted_data,
|
||||||
|
campaign.stats.email_reported,
|
||||||
"<span class=\"label " + label + "\" data-toggle=\"tooltip\" data-placement=\"right\" data-html=\"true\" title=\"" + quickStats + "\">" + campaign.status + "</span>",
|
"<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'>\
|
"<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>\
|
||||||
|
|
|
@ -3,26 +3,35 @@
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-3 col-md-2 sidebar">
|
<div class="col-sm-3 col-md-2 sidebar">
|
||||||
<ul class="nav nav-sidebar">
|
<ul class="nav nav-sidebar">
|
||||||
<li><a href="/">Dashboard</a>
|
<li>
|
||||||
|
<a href="/">Dashboard</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="active"><a href="/campaigns">Campaigns</a>
|
<li class="active">
|
||||||
|
<a href="/campaigns">Campaigns</a>
|
||||||
</li>
|
</li>
|
||||||
<li><a href="/users">Users & Groups</a>
|
<li>
|
||||||
|
<a href="/users">Users & Groups</a>
|
||||||
</li>
|
</li>
|
||||||
<li><a href="/templates">Email Templates</a>
|
<li>
|
||||||
|
<a href="/templates">Email Templates</a>
|
||||||
</li>
|
</li>
|
||||||
<li><a href="/landing_pages">Landing Pages</a>
|
<li>
|
||||||
|
<a href="/landing_pages">Landing Pages</a>
|
||||||
</li>
|
</li>
|
||||||
<li><a href="/sending_profiles">Sending Profiles</a>
|
<li>
|
||||||
|
<a href="/sending_profiles">Sending Profiles</a>
|
||||||
</li>
|
</li>
|
||||||
<li><a href="/settings">Settings</a>
|
<li>
|
||||||
|
<a href="/settings">Settings</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<hr>
|
<hr>
|
||||||
</li>
|
</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>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
@ -47,8 +56,12 @@
|
||||||
<i class="fa fa-caret-down"></i>
|
<i class="fa fa-caret-down"></i>
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu" aria-labelledby="exportButton">
|
<ul class="dropdown-menu" aria-labelledby="exportButton">
|
||||||
<li><a href="#" onclick="exportAsCSV('results')">Results</a></li>
|
<li>
|
||||||
<li><a href="#" onclick="exportAsCSV('events')">Raw Events</a></li>
|
<a href="#" onclick="exportAsCSV('results')">Results</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="#" onclick="exportAsCSV('events')">Raw Events</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</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()">
|
||||||
|
@ -73,10 +86,13 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div id="sent_chart" style="height:200px;" class="col-lg-3 col-md-3"></div>
|
<div style="height:200px;" class="col-lg-1 col-md-1"></div>
|
||||||
<div id="opened_chart" style="height:200px;" class="col-lg-3 col-md-3"></div>
|
<div id="sent_chart" style="height:200px;" class="col-lg-2 col-md-2"></div>
|
||||||
<div id="clicked_chart" style="height:200px;" class="col-lg-3 col-md-3"></div>
|
<div id="opened_chart" style="height:200px;" class="col-lg-2 col-md-2"></div>
|
||||||
<div id="submitted_data_chart" style="height:200px;" class="col-lg-3 col-md-3"></div>
|
<div id="clicked_chart" style="height:200px;" class="col-lg-2 col-md-2"></div>
|
||||||
|
<div id="submitted_data_chart" style="height:200px;" class="col-lg-2 col-md-2"></div>
|
||||||
|
<div id="reported_chart" style="height:200px;" class="col-lg-2 col-md-2"></div>
|
||||||
|
<div style="height:200px;" class="col-lg-1 col-md-1"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row" id="resultsMapContainer">
|
<div class="row" id="resultsMapContainer">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
|
@ -99,6 +115,7 @@
|
||||||
<th>Email</th>
|
<th>Email</th>
|
||||||
<th>Position</th>
|
<th>Position</th>
|
||||||
<th>Status</th>
|
<th>Status</th>
|
||||||
|
<th class="text-center">Reported</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
|
|
@ -43,10 +43,13 @@
|
||||||
<div id="overview_chart" style="height:200px;" class="col-lg-12 col-md-12 col-sm-12 col-xs-12"></div>
|
<div id="overview_chart" style="height:200px;" class="col-lg-12 col-md-12 col-sm-12 col-xs-12"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div id="sent_chart" style="height:200px;" class="col-lg-3 col-md-3"></div>
|
<div style="height:200px;" class="col-lg-1 col-md-1"></div>
|
||||||
<div id="opened_chart" style="height:200px;" class="col-lg-3 col-md-3"></div>
|
<div id="sent_chart" style="height:200px;" class="col-lg-2 col-md-2"></div>
|
||||||
<div id="clicked_chart" style="height:200px;" class="col-lg-3 col-md-3"></div>
|
<div id="opened_chart" style="height:200px;" class="col-lg-2 col-md-2"></div>
|
||||||
<div id="submitted_data_chart" style="height:200px;" class="col-lg-3 col-md-3"></div>
|
<div id="clicked_chart" style="height:200px;" class="col-lg-2 col-md-2"></div>
|
||||||
|
<div id="submitted_data_chart" style="height:200px;" class="col-lg-2 col-md-2"></div>
|
||||||
|
<div id="email_reported_chart" style="height:200px;" class="col-lg-2 col-md-2"></div>
|
||||||
|
<div style="height:200px;" class="col-lg-1 col-md-1"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<h2>Recent Campaigns</h2>
|
<h2>Recent Campaigns</h2>
|
||||||
|
@ -65,6 +68,7 @@
|
||||||
<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-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-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"><i class="fa fa-exclamation-circle"></i></th>
|
||||||
|
<th class="col-md-1 col-sm-1"><i class="fa fa-bullhorn"></i></th>
|
||||||
<th class="col-md-1 col-sm-1">Status</th>
|
<th class="col-md-1 col-sm-1">Status</th>
|
||||||
<th class="col-md-2 col-sm-2 no-sort"></i></th>
|
<th class="col-md-2 col-sm-2 no-sort"></i></th>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
Loading…
Reference in New Issue