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("/robots.txt", RobotsHandler)
|
||||
router.HandleFunc("/{path:.*}/track", PhishTracker)
|
||||
router.HandleFunc("/{path:.*}/report", PhishReporter)
|
||||
router.HandleFunc("/report", PhishReporter)
|
||||
router.HandleFunc("/{path:.*}", PhishHandler)
|
||||
return router
|
||||
}
|
||||
|
@ -71,6 +73,29 @@ func PhishTracker(w http.ResponseWriter, r *http.Request) {
|
|||
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
|
||||
// (such as clicked link, etc.)
|
||||
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)
|
||||
}
|
||||
|
||||
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) {
|
||||
resp, err := http.Get(fmt.Sprintf("%s/track?%s=%s", ps.URL, models.RecipientParameter, rid))
|
||||
s.Nil(err)
|
||||
|
@ -63,6 +69,19 @@ func (s *ControllersSuite) TestOpenedPhishingEmail() {
|
|||
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() {
|
||||
campaign := s.getFirstCampaign()
|
||||
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"`
|
||||
Name string `json:"name"`
|
||||
Status string `json:"status"`
|
||||
Reported string `json:"reported"`
|
||||
Results []Result `json:"results, omitempty"`
|
||||
Events []Event `json:"timeline,omitempty"`
|
||||
}
|
||||
|
@ -61,6 +62,7 @@ type CampaignStats struct {
|
|||
OpenedEmail int64 `json:"opened"`
|
||||
ClickedLink int64 `json:"clicked"`
|
||||
SubmittedData int64 `json:"submitted_data"`
|
||||
EmailReported int64 `json:"email_reported"`
|
||||
Error int64 `json:"error"`
|
||||
}
|
||||
|
||||
|
@ -194,6 +196,10 @@ func getCampaignStats(cid int64) (CampaignStats, error) {
|
|||
if err != nil {
|
||||
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
|
||||
s.ClickedLink += s.SubmittedData
|
||||
err = query.Where("status=?", EVENT_OPENED).Count(&s.OpenedEmail).Error
|
||||
|
@ -426,6 +432,7 @@ func PostCampaign(c *Campaign, uid int64) error {
|
|||
FirstName: t.FirstName,
|
||||
LastName: t.LastName,
|
||||
SendDate: c.LaunchDate,
|
||||
Reported: false,
|
||||
}
|
||||
if c.Status == CAMPAIGN_IN_PROGRESS {
|
||||
r.Status = STATUS_SENDING
|
||||
|
|
|
@ -32,6 +32,7 @@ const (
|
|||
EVENT_OPENED string = "Email Opened"
|
||||
EVENT_CLICKED string = "Clicked Link"
|
||||
EVENT_DATA_SUBMIT string = "Submitted Data"
|
||||
EVENT_REPORTED string = "Email Reported"
|
||||
EVENT_PROXY_REQUEST string = "Proxied request"
|
||||
STATUS_SUCCESS string = "Success"
|
||||
STATUS_QUEUED string = "Queued"
|
||||
|
|
|
@ -38,6 +38,7 @@ type Result struct {
|
|||
Latitude float64 `json:"latitude"`
|
||||
Longitude float64 `json:"longitude"`
|
||||
SendDate time.Time `json:"send_date"`
|
||||
Reported bool `json:"reported" sql:"not null"`
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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
|
||||
// the database given an IP address
|
||||
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-reported{
|
||||
font-weight: bold;
|
||||
color:#45d6ef;
|
||||
}
|
||||
|
||||
.color-success {
|
||||
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": {
|
||||
color: "#f9bf3b",
|
||||
label: "label-warning",
|
||||
icon: "fa-envelope",
|
||||
icon: "fa-envelope-open",
|
||||
point: "ct-point-opened"
|
||||
},
|
||||
"Clicked Link": {
|
||||
|
@ -42,6 +42,13 @@ var statuses = {
|
|||
icon: "fa-exclamation",
|
||||
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": {
|
||||
color: "#6c7a89",
|
||||
label: "label-default",
|
||||
|
@ -95,6 +102,7 @@ var statusMapping = {
|
|||
"Email Opened": "opened",
|
||||
"Clicked Link": "clicked",
|
||||
"Submitted Data": "submitted_data",
|
||||
"Email Reported": "reported",
|
||||
}
|
||||
|
||||
// This is an underwhelming attempt at an enum
|
||||
|
@ -282,7 +290,8 @@ function renderTimeline(data) {
|
|||
"email": data[4],
|
||||
"position": data[5],
|
||||
"status": data[6],
|
||||
"send_date": data[7]
|
||||
"send_date": data[7],
|
||||
"reported": data[8]
|
||||
}
|
||||
results = '<div class="timeline col-sm-12 well well-lg">' +
|
||||
'<h6>Timeline for ' + escapeHtml(record.first_name) + ' ' + escapeHtml(record.last_name) +
|
||||
|
@ -571,6 +580,9 @@ function poll() {
|
|||
});
|
||||
$.each(campaign.results, function (i, result) {
|
||||
email_series_data[result.status]++;
|
||||
if (result.reported) {
|
||||
email_series_data['Email Reported']++
|
||||
}
|
||||
// Backfill status values
|
||||
var step = progressListing.indexOf(result.status)
|
||||
for (var i = 0; i < step; i++) {
|
||||
|
@ -595,6 +607,7 @@ function poll() {
|
|||
data: email_data
|
||||
})
|
||||
})
|
||||
|
||||
/* Update the datatable */
|
||||
resultsTable = $("#resultsTable").DataTable()
|
||||
resultsTable.rows().every(function (i, tableLoop, rowLoop) {
|
||||
|
@ -603,12 +616,13 @@ function poll() {
|
|||
var rid = rowData[0]
|
||||
$.each(campaign.results, function (j, result) {
|
||||
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
|
||||
resultsTable.row(i).data(rowData)
|
||||
if (row.child.isShown()) {
|
||||
$(row.node()).find("i").removeClass("fa-caret-right")
|
||||
$(row.node()).find("i").addClass("fa-caret-down")
|
||||
$(row.node()).find("#caret").removeClass("fa-caret-right")
|
||||
$(row.node()).find("#caret").addClass("fa-caret-down")
|
||||
row.child(renderTimeline(row.data()))
|
||||
}
|
||||
return false
|
||||
|
@ -669,13 +683,24 @@ function load() {
|
|||
"targets": [1]
|
||||
}, {
|
||||
"visible": false,
|
||||
"targets": [0, 7]
|
||||
"targets": [0, 8]
|
||||
},
|
||||
{
|
||||
"render": function (data, type, row) {
|
||||
return createStatusLabel(data, row[7])
|
||||
return createStatusLabel(data, row[8])
|
||||
},
|
||||
"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) {
|
||||
resultsTable.row.add([
|
||||
result.id,
|
||||
"<i class=\"fa fa-caret-right\"></i>",
|
||||
"<i id=\"caret\" class=\"fa fa-caret-right\"></i>",
|
||||
escapeHtml(result.first_name) || "",
|
||||
escapeHtml(result.last_name) || "",
|
||||
escapeHtml(result.email) || "",
|
||||
escapeHtml(result.position) || "",
|
||||
result.status,
|
||||
result.reported,
|
||||
moment(result.send_date).format('MMMM Do YYYY, h:mm:ss a')
|
||||
])
|
||||
email_series_data[result.status]++;
|
||||
if (result.reported) {
|
||||
email_series_data['Email Reported']++
|
||||
}
|
||||
// Backfill status values
|
||||
var step = progressListing.indexOf(result.status)
|
||||
for (var i = 0; i < step; i++) {
|
||||
|
@ -761,6 +790,7 @@ function load() {
|
|||
colors: [statuses[status].color, '#dddddd']
|
||||
})
|
||||
})
|
||||
|
||||
if (use_map) {
|
||||
$("#resultsMapContainer").show()
|
||||
map = new Datamap({
|
||||
|
|
|
@ -324,7 +324,7 @@ $(document).ready(function () {
|
|||
var quickStats = launchDate + "<br><br>" + "Number of recipients: " + campaign.stats.total
|
||||
} else {
|
||||
launchDate = "Launch Date: " + moment(campaign.launch_date).format('MMMM Do YYYY, h:mm:ss a')
|
||||
var quickStats = launchDate + "<br><br>" + "Number of recipients: " + campaign.stats.total + "<br><br>" + "Emails opened: " + campaign.stats.opened + "<br><br>" + "Emails clicked: " + campaign.stats.clicked + "<br><br>" + "Submitted Credentials: " + campaign.stats.submitted_data + "<br><br>" + "Errors : " + campaign.stats.error
|
||||
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([
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
var campaigns = []
|
||||
|
||||
// statuses is a helper map to point result statuses to ui classes
|
||||
var statuses = {
|
||||
"Email Sent": {
|
||||
|
@ -29,6 +28,12 @@ var statuses = {
|
|||
icon: "fa-envelope",
|
||||
point: "ct-point-opened"
|
||||
},
|
||||
"Email Reported": {
|
||||
color: "#45d6ef",
|
||||
label: "label-warning",
|
||||
icon: "fa-bullhorne",
|
||||
point: "ct-point-reported"
|
||||
},
|
||||
"Clicked Link": {
|
||||
color: "#F39C12",
|
||||
label: "label-clicked",
|
||||
|
@ -80,6 +85,7 @@ var statuses = {
|
|||
var statsMapping = {
|
||||
"sent": "Email Sent",
|
||||
"opened": "Email Opened",
|
||||
"email_reported": "Email Reported",
|
||||
"clicked": "Clicked Link",
|
||||
"submitted_data": "Submitted Data",
|
||||
}
|
||||
|
@ -109,14 +115,16 @@ function renderPieChart(chartopts) {
|
|||
this.innerText = rend.text(chartopts['data'][0].count, left, top).
|
||||
attr({
|
||||
'text-anchor': 'middle',
|
||||
'font-size': '24px',
|
||||
'font-size': '16px',
|
||||
'font-weight': 'bold',
|
||||
'fill': chartopts['colors'][0],
|
||||
'font-family': 'Helvetica,Arial,sans-serif'
|
||||
}).add();
|
||||
},
|
||||
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,
|
||||
colors: [statuses[status_label].color, "#dddddd"]
|
||||
})
|
||||
|
||||
stats_data = []
|
||||
});
|
||||
}
|
||||
|
@ -292,10 +301,27 @@ $(document).ready(function () {
|
|||
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] }],
|
||||
{
|
||||
className: "color-sent",
|
||||
targets: [2]
|
||||
},
|
||||
{
|
||||
className: "color-opened",
|
||||
targets: [3]
|
||||
},
|
||||
{
|
||||
className: "color-clicked",
|
||||
targets: [4]
|
||||
},
|
||||
{
|
||||
className: "color-success",
|
||||
targets: [5]
|
||||
},
|
||||
{
|
||||
className: "color-reported",
|
||||
targets: [6]
|
||||
}
|
||||
],
|
||||
order: [
|
||||
[1, "desc"]
|
||||
]
|
||||
|
@ -310,7 +336,7 @@ $(document).ready(function () {
|
|||
var quickStats = launchDate + "<br><br>" + "Number of recipients: " + campaign.stats.total
|
||||
} else {
|
||||
launchDate = "Launch Date: " + moment(campaign.launch_date).format('MMMM Do YYYY, h:mm:ss a')
|
||||
var quickStats = launchDate + "<br><br>" + "Number of recipients: " + campaign.stats.total + "<br><br>" + "Emails opened: " + campaign.stats.opened + "<br><br>" + "Emails clicked: " + campaign.stats.clicked + "<br><br>" + "Submitted Credentials: " + campaign.stats.submitted_data + "<br><br>" + "Errors : " + campaign.stats.error
|
||||
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
|
||||
campaignTable.row.add([
|
||||
|
@ -320,6 +346,7 @@ $(document).ready(function () {
|
|||
campaign.stats.opened,
|
||||
campaign.stats.clicked,
|
||||
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>",
|
||||
"<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>\
|
||||
|
|
|
@ -3,26 +3,35 @@
|
|||
<div class="row">
|
||||
<div class="col-sm-3 col-md-2 sidebar">
|
||||
<ul class="nav nav-sidebar">
|
||||
<li><a href="/">Dashboard</a>
|
||||
<li>
|
||||
<a href="/">Dashboard</a>
|
||||
</li>
|
||||
<li class="active"><a href="/campaigns">Campaigns</a>
|
||||
<li class="active">
|
||||
<a href="/campaigns">Campaigns</a>
|
||||
</li>
|
||||
<li><a href="/users">Users & Groups</a>
|
||||
<li>
|
||||
<a href="/users">Users & Groups</a>
|
||||
</li>
|
||||
<li><a href="/templates">Email Templates</a>
|
||||
<li>
|
||||
<a href="/templates">Email Templates</a>
|
||||
</li>
|
||||
<li><a href="/landing_pages">Landing Pages</a>
|
||||
<li>
|
||||
<a href="/landing_pages">Landing Pages</a>
|
||||
</li>
|
||||
<li><a href="/sending_profiles">Sending Profiles</a>
|
||||
<li>
|
||||
<a href="/sending_profiles">Sending Profiles</a>
|
||||
</li>
|
||||
<li><a href="/settings">Settings</a>
|
||||
<li>
|
||||
<a href="/settings">Settings</a>
|
||||
</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><a href="/api/">API Documentation</a>
|
||||
<li>
|
||||
<a href="/api/">API Documentation</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
@ -47,8 +56,12 @@
|
|||
<i class="fa fa-caret-down"></i>
|
||||
</button>
|
||||
<ul class="dropdown-menu" aria-labelledby="exportButton">
|
||||
<li><a href="#" onclick="exportAsCSV('results')">Results</a></li>
|
||||
<li><a href="#" onclick="exportAsCSV('events')">Raw Events</a></li>
|
||||
<li>
|
||||
<a href="#" onclick="exportAsCSV('results')">Results</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" onclick="exportAsCSV('events')">Raw Events</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<button id="complete_button" type="button" class="btn btn-blue" data-toggle="tooltip" onclick="completeCampaign()">
|
||||
|
@ -73,10 +86,13 @@
|
|||
</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 style="height:200px;" class="col-lg-1 col-md-1"></div>
|
||||
<div id="sent_chart" style="height:200px;" class="col-lg-2 col-md-2"></div>
|
||||
<div id="opened_chart" style="height:200px;" class="col-lg-2 col-md-2"></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 class="row" id="resultsMapContainer">
|
||||
<div class="col-md-6">
|
||||
|
@ -99,6 +115,7 @@
|
|||
<th>Email</th>
|
||||
<th>Position</th>
|
||||
<th>Status</th>
|
||||
<th class="text-center">Reported</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<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>
|
||||
<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 style="height:200px;" class="col-lg-1 col-md-1"></div>
|
||||
<div id="sent_chart" style="height:200px;" class="col-lg-2 col-md-2"></div>
|
||||
<div id="opened_chart" style="height:200px;" class="col-lg-2 col-md-2"></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 class="row">
|
||||
<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-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-bullhorn"></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>
|
||||
|
|
Loading…
Reference in New Issue