Merge pull request #1 from gophish/master

Catching up to the main branch from gophish/master
pull/91/head
Justin Gray 2016-01-20 22:27:58 -06:00
commit cf97520ddf
29 changed files with 1291 additions and 948 deletions

View File

@ -3,11 +3,11 @@
gophish gophish
======= =======
[![Build Status](https://travis-ci.org/gophish/gophish.svg?branch=master)](https://travis-ci.org/gophish/gophish) [![Build Status](https://travis-ci.org/gophish/gophish.svg?branch=master)](https://travis-ci.org/gophish/gophish) [![GoDoc](https://godoc.org/github.com/gophish/gophish?status.svg)](https://godoc.org/github.com/gophish/gophish)
Open-Source Phishing Toolkit Open-Source Phishing Toolkit
Gophish is an open-source phishing toolkit designed for businesses and penetration testers. It provides the ability to quickly and easily setup and execute phishing engagements and security awareness training. [Gophish](https://getgophish.com) is an open-source phishing toolkit designed for businesses and penetration testers. It provides the ability to quickly and easily setup and execute phishing engagements and security awareness training.
###Current Status ###Current Status
**Update 01/12/2016** **Update 01/12/2016**

View File

@ -1,10 +1,21 @@
{ {
"admin_url" : "127.0.0.1:3333", "admin_server" : {
"phish_url" : "0.0.0.0:80", "listen_url" : "127.0.0.1:3333",
"use_tls" : false,
"cert_path" : "example.crt",
"key_path" : "example.key"
},
"phish_server" : {
"listen_url" : "0.0.0.0:80",
"use_tls" : false,
"cert_path" : "example.crt",
"key_path": "example.key"
},
"smtp" : { "smtp" : {
"host" : "smtp.example.com:25", "host" : "smtp.example.com:25",
"user" : "username", "user" : "username",
"pass" : "password" "pass" : "password"
}, },
"dbpath" : "gophish.db" "db_path" : "gophish.db",
"migrations_path" : "db/migrations/"
} }

View File

@ -13,12 +13,29 @@ type SMTPServer struct {
Password string `json:"password"` Password string `json:"password"`
} }
// AdminServer represents the Admin server configuration details
type AdminServer struct {
ListenURL string `json:"listen_url"`
UseTLS bool `json:"use_tls"`
CertPath string `json:"cert_path"`
KeyPath string `json:"key_path"`
}
// PhishServer represents the Phish server configuration details
type PhishServer struct {
ListenURL string `json:"listen_url"`
UseTLS bool `json:"use_tls"`
CertPath string `json:"cert_path"`
KeyPath string `json:"key_path"`
}
// Config represents the configuration information. // Config represents the configuration information.
type Config struct { type Config struct {
AdminURL string `json:"admin_url"` AdminConf AdminServer `json:"admin_server"`
PhishURL string `json:"phish_url"` PhishConf PhishServer `json:"phish_server"`
SMTP SMTPServer `json:"smtp"` SMTPConf SMTPServer `json:"smtp"`
DBPath string `json:"dbpath"` DBPath string `json:"db_path"`
MigrationsPath string `json:"migrations_path"`
} }
var Conf Config var Conf Config

View File

@ -10,14 +10,14 @@ import (
"time" "time"
"github.com/PuerkitoBio/goquery" "github.com/PuerkitoBio/goquery"
ctx "github.com/gorilla/context"
"github.com/gorilla/mux"
"github.com/jinzhu/gorm"
"github.com/jordan-wright/email"
"github.com/gophish/gophish/auth" "github.com/gophish/gophish/auth"
"github.com/gophish/gophish/models" "github.com/gophish/gophish/models"
"github.com/gophish/gophish/util" "github.com/gophish/gophish/util"
"github.com/gophish/gophish/worker" "github.com/gophish/gophish/worker"
ctx "github.com/gorilla/context"
"github.com/gorilla/mux"
"github.com/jinzhu/gorm"
"github.com/jordan-wright/email"
) )
// Worker is the worker that processes phishing events and updates campaigns. // Worker is the worker that processes phishing events and updates campaigns.
@ -92,6 +92,7 @@ func API_Campaigns_Id(w http.ResponseWriter, r *http.Request) {
id, _ := strconv.ParseInt(vars["id"], 0, 64) id, _ := strconv.ParseInt(vars["id"], 0, 64)
c, err := models.GetCampaign(id, ctx.Get(r, "user_id").(int64)) c, err := models.GetCampaign(id, ctx.Get(r, "user_id").(int64))
if err != nil { if err != nil {
Logger.Println(err)
JSONResponse(w, models.Response{Success: false, Message: "Campaign not found"}, http.StatusNotFound) JSONResponse(w, models.Response{Success: false, Message: "Campaign not found"}, http.StatusNotFound)
return return
} }

View File

@ -9,9 +9,9 @@ import (
"os" "os"
"testing" "testing"
"github.com/gorilla/handlers"
"github.com/gophish/gophish/config" "github.com/gophish/gophish/config"
"github.com/gophish/gophish/models" "github.com/gophish/gophish/models"
"github.com/gorilla/handlers"
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
) )
@ -26,13 +26,14 @@ var as *httptest.Server = httptest.NewUnstartedServer(handlers.CombinedLoggingHa
func (s *ControllersSuite) SetupSuite() { func (s *ControllersSuite) SetupSuite() {
config.Conf.DBPath = ":memory:" config.Conf.DBPath = ":memory:"
config.Conf.MigrationsPath = "../db/migrations/"
err := models.Setup() err := models.Setup()
if err != nil { if err != nil {
s.T().Fatalf("Failed creating database: %v", err) s.T().Fatalf("Failed creating database: %v", err)
} }
s.Nil(err) s.Nil(err)
// Setup the admin server for use in testing // Setup the admin server for use in testing
as.Config.Addr = config.Conf.AdminURL as.Config.Addr = config.Conf.AdminConf.ListenURL
as.Start() as.Start()
// Get the API key to use for these tests // Get the API key to use for these tests
u, err := models.GetUser(1) u, err := models.GetUser(1)

View File

@ -0,0 +1,28 @@
-- +goose Up
-- SQL in section 'Up' is executed when this migration is applied
CREATE TABLE IF NOT EXISTS "users" ("id" integer primary key autoincrement,"username" varchar(255) NOT NULL UNIQUE,"hash" varchar(255),"api_key" varchar(255) NOT NULL UNIQUE );
CREATE TABLE IF NOT EXISTS "templates" ("id" integer primary key autoincrement,"user_id" bigint,"name" varchar(255),"subject" varchar(255),"text" varchar(255),"html" varchar(255),"modified_date" datetime );
CREATE TABLE IF NOT EXISTS "targets" ("id" integer primary key autoincrement,"first_name" varchar(255),"last_name" varchar(255),"email" varchar(255),"position" varchar(255) );
CREATE TABLE IF NOT EXISTS "smtp" ("smtp_id" integer primary key autoincrement,"campaign_id" bigint,"host" varchar(255),"username" varchar(255),"from_address" varchar(255) );
CREATE TABLE IF NOT EXISTS "results" ("id" integer primary key autoincrement,"campaign_id" bigint,"user_id" bigint,"r_id" varchar(255),"email" varchar(255),"first_name" varchar(255),"last_name" varchar(255),"status" varchar(255) NOT NULL ,"ip" varchar(255),"latitude" real,"longitude" real );
CREATE TABLE IF NOT EXISTS "pages" ("id" integer primary key autoincrement,"user_id" bigint,"name" varchar(255),"html" varchar(255),"modified_date" datetime );
CREATE TABLE IF NOT EXISTS "groups" ("id" integer primary key autoincrement,"user_id" bigint,"name" varchar(255),"modified_date" datetime );
CREATE TABLE IF NOT EXISTS "group_targets" ("group_id" bigint,"target_id" bigint );
CREATE TABLE IF NOT EXISTS "events" ("id" integer primary key autoincrement,"campaign_id" bigint,"email" varchar(255),"time" datetime,"message" varchar(255) );
CREATE TABLE IF NOT EXISTS "campaigns" ("id" integer primary key autoincrement,"user_id" bigint,"name" varchar(255) NOT NULL ,"created_date" datetime,"completed_date" datetime,"template_id" bigint,"page_id" bigint,"status" varchar(255),"url" varchar(255) );
CREATE TABLE IF NOT EXISTS "attachments" ("id" integer primary key autoincrement,"template_id" bigint,"content" varchar(255),"type" varchar(255),"name" varchar(255) );
-- +goose Down
-- SQL section 'Down' is executed when this migration is rolled back
DROP TABLE "attachments";
DROP TABLE "campaigns";
DROP TABLE "events";
DROP TABLE "group_targets";
DROP TABLE "groups";
DROP TABLE "pages";
DROP TABLE "results";
DROP TABLE "smtp";
DROP TABLE "targets";
DROP TABLE "templates";
DROP TABLE "users";

View File

@ -30,11 +30,12 @@ import (
"log" "log"
"net/http" "net/http"
"os" "os"
"sync"
"github.com/gorilla/handlers"
"github.com/gophish/gophish/config" "github.com/gophish/gophish/config"
"github.com/gophish/gophish/controllers" "github.com/gophish/gophish/controllers"
"github.com/gophish/gophish/models" "github.com/gophish/gophish/models"
"github.com/gorilla/handlers"
) )
var Logger = log.New(os.Stdout, " ", log.Ldate|log.Ltime|log.Lshortfile) var Logger = log.New(os.Stdout, " ", log.Ldate|log.Ltime|log.Lshortfile)
@ -45,9 +46,31 @@ func main() {
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
} }
wg := &sync.WaitGroup{}
wg.Add(1)
// Start the web servers // Start the web servers
Logger.Printf("Admin server started at http://%s\n", config.Conf.AdminURL) go func() {
go http.ListenAndServe(config.Conf.AdminURL, handlers.CombinedLoggingHandler(os.Stdout, controllers.CreateAdminRouter())) defer wg.Done()
Logger.Printf("Phishing server started at http://%s\n", config.Conf.PhishURL) if config.Conf.AdminConf.UseTLS { // use TLS for Admin web server if available
http.ListenAndServe(config.Conf.PhishURL, handlers.CombinedLoggingHandler(os.Stdout, controllers.CreatePhishingRouter())) Logger.Printf("Starting admin server at https://%s\n", config.Conf.AdminConf.ListenURL)
Logger.Fatal(http.ListenAndServeTLS(config.Conf.AdminConf.ListenURL, config.Conf.AdminConf.CertPath, config.Conf.AdminConf.KeyPath,
handlers.CombinedLoggingHandler(os.Stdout, controllers.CreateAdminRouter())))
} else {
Logger.Printf("Starting admin server at http://%s\n", config.Conf.AdminConf.ListenURL)
Logger.Fatal(http.ListenAndServe(config.Conf.AdminConf.ListenURL, handlers.CombinedLoggingHandler(os.Stdout, controllers.CreateAdminRouter())))
}
}()
wg.Add(1)
go func() {
defer wg.Done()
if config.Conf.PhishConf.UseTLS { // use TLS for Phish web server if available
Logger.Printf("Starting phishing server at https://%s\n", config.Conf.PhishConf.ListenURL)
Logger.Fatal(http.ListenAndServeTLS(config.Conf.PhishConf.ListenURL, config.Conf.PhishConf.CertPath, config.Conf.PhishConf.KeyPath,
handlers.CombinedLoggingHandler(os.Stdout, controllers.CreatePhishingRouter())))
} else {
Logger.Printf("Starting phishing server at http://%s\n", config.Conf.PhishConf.ListenURL)
Logger.Fatal(http.ListenAndServe(config.Conf.PhishConf.ListenURL, handlers.CombinedLoggingHandler(os.Stdout, controllers.CreatePhishingRouter())))
}
}()
wg.Wait()
} }

View File

@ -117,21 +117,28 @@ func GetCampaign(id int64, uid int64) (Campaign, error) {
c := Campaign{} c := Campaign{}
err := db.Where("id = ?", id).Where("user_id = ?", uid).Find(&c).Error err := db.Where("id = ?", id).Where("user_id = ?", uid).Find(&c).Error
if err != nil { if err != nil {
Logger.Printf("%s: campaign not found\n", err)
return c, err return c, err
} }
err = db.Model(&c).Related(&c.Results).Error err = db.Model(&c).Related(&c.Results).Error
if err != nil { if err != nil {
Logger.Printf("%s: results not found for campaign\n", err)
return c, err return c, err
} }
err = db.Model(&c).Related(&c.Events).Error err = db.Model(&c).Related(&c.Events).Error
if err != nil { if err != nil {
Logger.Printf("%s: events not found for campaign\n", err)
return c, err return c, err
} }
err = db.Table("templates").Where("id=?", c.TemplateId).Find(&c.Template).Error err = db.Table("templates").Where("id=?", c.TemplateId).Find(&c.Template).Error
if err != nil { if err != nil {
Logger.Printf("%s: template not found for campaign\n", err)
return c, err return c, err
} }
err = db.Table("pages").Where("id=?", c.PageId).Find(&c.Page).Error err = db.Table("pages").Where("id=?", c.PageId).Find(&c.Page).Error
if err != nil {
Logger.Printf("%s: page not found for campaign\n", err)
}
return c, err return c, err
} }
@ -207,6 +214,7 @@ func PostCampaign(c *Campaign, uid int64) error {
//DeleteCampaign deletes the specified campaign //DeleteCampaign deletes the specified campaign
func DeleteCampaign(id int64) error { func DeleteCampaign(id int64) error {
Logger.Printf("Deleting campaign %d\n", id)
// Delete all the campaign results // Delete all the campaign results
err := db.Where("campaign_id=?", id).Delete(&Result{}).Error err := db.Where("campaign_id=?", id).Delete(&Result{}).Error
if err != nil { if err != nil {

View File

@ -8,6 +8,8 @@ import (
"log" "log"
"os" "os"
"bitbucket.org/liamstask/goose/lib/goose"
"github.com/gophish/gophish/config" "github.com/gophish/gophish/config"
"github.com/jinzhu/gorm" "github.com/jinzhu/gorm"
_ "github.com/mattn/go-sqlite3" // Blank import needed to import sqlite3 _ "github.com/mattn/go-sqlite3" // Blank import needed to import sqlite3
@ -62,6 +64,24 @@ func Setup() error {
if _, err = os.Stat(config.Conf.DBPath); err != nil || config.Conf.DBPath == ":memory:" { if _, err = os.Stat(config.Conf.DBPath); err != nil || config.Conf.DBPath == ":memory:" {
create_db = true create_db = true
} }
// Setup the goose configuration
migrateConf := &goose.DBConf{
MigrationsDir: config.Conf.MigrationsPath,
Env: "production",
Driver: goose.DBDriver{
Name: "sqlite3",
OpenStr: config.Conf.DBPath,
Import: "github.com/mattn/go-sqlite3",
Dialect: &goose.Sqlite3Dialect{},
},
}
// Get the latest possible migration
latest, err := goose.GetMostRecentDBVersion(migrateConf.MigrationsDir)
if err != nil {
Logger.Println(err)
return err
}
// Open our database connection
db, err = gorm.Open("sqlite3", config.Conf.DBPath) db, err = gorm.Open("sqlite3", config.Conf.DBPath)
db.LogMode(false) db.LogMode(false)
db.SetLogger(Logger) db.SetLogger(Logger)
@ -69,20 +89,14 @@ func Setup() error {
Logger.Println(err) Logger.Println(err)
return err return err
} }
//If the file already exists, delete it and recreate it // Migrate up to the latest version
err = goose.RunMigrationsOnDb(migrateConf, migrateConf.MigrationsDir, latest, db.DB())
if err != nil {
Logger.Println(err)
return err
}
//If the database didn't exist, we need to create the admin user
if create_db { if create_db {
Logger.Printf("Database not found... creating db at %s\n", config.Conf.DBPath)
db.CreateTable(User{})
db.CreateTable(Target{})
db.CreateTable(Result{})
db.CreateTable(Group{})
db.CreateTable(GroupTarget{})
db.CreateTable(Template{})
db.CreateTable(Attachment{})
db.CreateTable(Page{})
db.CreateTable(SMTP{})
db.CreateTable(Event{})
db.CreateTable(Campaign{})
//Create the default user //Create the default user
initUser := User{ initUser := User{
Username: "admin", Username: "admin",
@ -92,6 +106,7 @@ func Setup() error {
err = db.Save(&initUser).Error err = db.Save(&initUser).Error
if err != nil { if err != nil {
Logger.Println(err) Logger.Println(err)
return err
} }
} }
return nil return nil

View File

@ -16,6 +16,7 @@ var _ = check.Suite(&ModelsSuite{})
func (s *ModelsSuite) SetUpSuite(c *check.C) { func (s *ModelsSuite) SetUpSuite(c *check.C) {
config.Conf.DBPath = ":memory:" config.Conf.DBPath = ":memory:"
config.Conf.MigrationsPath = "../db/migrations/"
err := Setup() err := Setup()
if err != nil { if err != nil {
c.Fatalf("Failed creating database: %v", err) c.Fatalf("Failed creating database: %v", err)

30
static/css/main.css vendored
View File

@ -341,3 +341,33 @@
float:none !important; float:none !important;
} }
} }
/* Table Styling */
.modal-content .dataTable tbody td {
font-size: 16px;/* Smaller font on modal tables */
}
.dataTables_info{
font-size: 15px;
}
/* Sort Icons */
table.dataTable thead .sorting:after, table.dataTable thead .sorting_asc:after, table.dataTable thead .sorting_desc:after {
font-family: 'FontAwesome' !important;
position: relative !important;
display: initial !important;
top: initial!important;
right: initial!important;
left: 6px;
color: #1abc9c;
}
table.dataTable thead .sorting:after{
content: "\f0dc" !important;
color: initial;
}
table.dataTable thead .sorting_asc:after {
content: "\f0de" !important;
opacity: .8 !important;
}
table.dataTable thead .sorting_desc:after {
content: "\f0dd" !important;
opacity: .8 !important;
}

View File

@ -2,254 +2,287 @@ var map = null
// statuses is a helper map to point result statuses to ui classes // statuses is a helper map to point result statuses to ui classes
var statuses = { var statuses = {
"Email Sent" : { "Email Sent": {
slice: "ct-slice-donut-sent", slice: "ct-slice-donut-sent",
legend: "ct-legend-sent", legend: "ct-legend-sent",
label: "label-success" label: "label-success"
}, },
"Email Opened" : { "Email Opened": {
slice: "ct-slice-donut-opened", slice: "ct-slice-donut-opened",
legend: "ct-legend-opened", legend: "ct-legend-opened",
label: "label-warning" label: "label-warning"
}, },
"Clicked Link" : { "Clicked Link": {
slice: "ct-slice-donut-clicked", slice: "ct-slice-donut-clicked",
legend: "ct-legend-clicked", legend: "ct-legend-clicked",
label: "label-danger" label: "label-danger"
}, },
"Success" : { "Success": {
slice: "ct-slice-donut-clicked", slice: "ct-slice-donut-clicked",
legend: "ct-legend-clicked", legend: "ct-legend-clicked",
label: "label-danger" label: "label-danger"
}, },
"Error" : { "Error": {
slice: "ct-slice-donut-error", slice: "ct-slice-donut-error",
legend: "ct-legend-error", legend: "ct-legend-error",
label: "label-default" label: "label-default"
}, },
"Unknown" : { "Unknown": {
slice: "ct-slice-donut-error", slice: "ct-slice-donut-error",
legend: "ct-legend-error", legend: "ct-legend-error",
label: "label-default" label: "label-default"
} }
} }
var campaign = {} var campaign = {}
function dismiss(){ function dismiss() {
$("#modal\\.flashes").empty() $("#modal\\.flashes").empty()
$("#modal").modal('hide') $("#modal").modal('hide')
$("#resultsTable").dataTable().DataTable().clear().draw() $("#resultsTable").dataTable().DataTable().clear().draw()
} }
// Deletes a campaign after prompting the user // Deletes a campaign after prompting the user
function deleteCampaign(){ function deleteCampaign() {
if (confirm("Are you sure you want to delete: " + campaign.name + "?")){ if (confirm("Are you sure you want to delete: " + campaign.name + "?")) {
api.campaignId.delete(campaign.id) api.campaignId.delete(campaign.id)
.success(function(msg){ .success(function(msg) {
console.log(msg) location.href = '/campaigns'
}) })
.error(function(e){ .error(function(e) {
$("#modal\\.flashes").empty().append("<div style=\"text-align:center\" class=\"alert alert-danger\">\ $("#modal\\.flashes").empty().append("<div style=\"text-align:center\" class=\"alert alert-danger\">\
<i class=\"fa fa-exclamation-circle\"></i> " + data.responseJSON.message + "</div>") <i class=\"fa fa-exclamation-circle\"></i> " + data.responseJSON.message + "</div>")
}) })
} }
} }
$(document).ready(function(){ // Exports campaign results as a CSV file
function exportAsCSV() {
exportHTML = $("#exportButton").html()
$("#exportButton").html('<i class="fa fa-spinner fa-spin"></i>')
var csvString = Papa.unparse(campaign.results, {})
var csvData = new Blob([csvString], {
type: 'text/csv;charset=utf-8;'
});
if (navigator.msSaveBlob) {
navigator.msSaveBlob(csvData, 'results.csv');
} else {
var csvURL = window.URL.createObjectURL(csvData);
var dlLink = document.createElement('a');
dlLink.href = csvURL;
dlLink.setAttribute('download', 'results.csv');
dlLink.click();
}
$("#exportButton").html(exportHTML)
}
$(document).ready(function() {
campaign.id = window.location.pathname.split('/').slice(-1)[0] campaign.id = window.location.pathname.split('/').slice(-1)[0]
api.campaignId.get(campaign.id) api.campaignId.get(campaign.id)
.success(function(c){ .success(function(c) {
campaign = c campaign = c
if (campaign){ if (campaign) {
// Set the title // Set the title
$("#page-title").text("Results for " + c.name) $("#page-title").text("Results for " + c.name)
// Setup tooltips // Setup tooltips
$('[data-toggle="tooltip"]').tooltip() $('[data-toggle="tooltip"]').tooltip()
// Setup our graphs // Setup our graphs
var timeline_data = {series:[{ var timeline_data = {
name: "Events", series: [{
data: [] 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')
}
},
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();
$.each(campaign.results, function(i, result){
label = statuses[result.status].label || "label-default";
resultsTable.row.add([
result.first_name || "",
result.last_name || "",
result.email || "",
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]++;
} }
}) var email_data = {
// Setup the graphs series: []
$.each(campaign.timeline, function(i, event){
timeline_data.series[0].data.push({meta : i, x: new Date(event.time), y:1})
})
$.each(email_series_data, function(status, count){
email_data.series.push({meta: status, value: count})
})
var timeline_chart = new Chartist.Line('#timeline_chart', timeline_data, timeline_opts)
// 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-point', function() {
var $point = $(this)
value = $point.attr('ct:value')
cidx = $point.attr('ct:meta')
html = "Event: " + campaign.timeline[cidx].message
if (campaign.timeline[cidx].email) {
html += '<br>' + "Email: " + campaign.timeline[cidx].email
} }
$toolTip.html(html).show() var email_legend = {}
}); var email_series_data = {}
$chart.on('mouseleave', '.ct-point', function() { var timeline_opts = {
$toolTip.hide(); axisX: {
}); showGrid: false,
$chart.on('mousemove', function(event) { type: Chartist.FixedScaleAxis,
$toolTip.css({ divisor: 5,
left: (event.offsetX || event.originalEvent.layerX) - $toolTip.width() / 2 - 10, labelInterpolationFnc: function(value) {
top: (event.offsetY + 70 || event.originalEvent.layerY) - $toolTip.height() - 40 return moment(value).format('MMMM Do YYYY h:mm')
});
});
var email_chart = new Chartist.Pie("#email_chart", email_data, email_opts)
email_chart.on('draw', function(data){
// We don't want to create the legend twice
if (!email_legend[data.meta]) {
console.log(data.meta)
$("#email_chart_legend").append('<li><span class="' + statuses[data.meta].legend + '"></span>' + data.meta + '</li>')
email_legend[data.meta] = true
}
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
});
});
$("#loading").hide()
$("#campaignResults").show()
map = new Datamap({
element: document.getElementById("resultsMap"),
responsive: true,
fills: {
defaultFill: "#ffffff",
point: "#283F50"
},
geographyConfig: {
highlightFillColor : "#1abc9c",
borderColor:"#283F50"
},
bubblesConfig: {
borderColor: "#283F50"
}
});
bubbles = []
$.each(campaign.results, function(i, result){
// Check that it wasn't an internal IP
if (result.latitude == 0 && result.longitude == 0) { return true; }
newIP = true
$.each(bubbles, function(i, bubble){
if (bubble.ip == result.ip){
bubbles[i].radius += 1
newIP = false
return false
}
})
if (newIP){
console.log("Adding bubble at: ")
console.log({
latitude : result.latitude,
longitude: result.longitude,
name : result.ip,
fillKey: "point"
})
bubbles.push({
latitude : result.latitude,
longitude: result.longitude,
name : result.ip,
fillKey: "point",
radius: 2
})
}
})
map.bubbles(bubbles)
}
// Load up the map data (only once!)
$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
if ($(e.target).attr('href') == "#overview"){
if (!map){
map = new Datamap({
element: document.getElementById("resultsMap"),
responsive: true,
fills: {
defaultFill: "#ffffff"
},
geographyConfig: {
highlightFillColor : "#1abc9c",
borderColor:"#283F50"
} }
}); },
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();
$.each(campaign.results, function(i, result) {
label = statuses[result.status].label || "label-default";
resultsTable.row.add([
result.first_name || "",
result.last_name || "",
result.email || "",
result.position || "",
"<span class=\"label " + label + "\">" + result.status + "</span>"
]).draw()
if (!email_series_data[result.status]) {
email_series_data[result.status] = 1
} else {
email_series_data[result.status]++;
}
})
// Setup the graphs
$.each(campaign.timeline, function(i, event) {
timeline_data.series[0].data.push({
meta: i,
x: new Date(event.time),
y: 1
})
})
$.each(email_series_data, function(status, count) {
email_data.series.push({
meta: status,
value: count
})
})
var timeline_chart = new Chartist.Line('#timeline_chart', timeline_data, timeline_opts)
// 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-point', function() {
var $point = $(this)
value = $point.attr('ct:value')
cidx = $point.attr('ct:meta')
html = "Event: " + campaign.timeline[cidx].message
if (campaign.timeline[cidx].email) {
html += '<br>' + "Email: " + campaign.timeline[cidx].email
}
$toolTip.html(html).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 + 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) {
// We don't want to create the legend twice
if (!email_legend[data.meta]) {
console.log(data.meta)
$("#email_chart_legend").append('<li><span class="' + statuses[data.meta].legend + '"></span>' + data.meta + '</li>')
email_legend[data.meta] = true
}
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
});
});
$("#loading").hide()
$("#campaignResults").show()
map = new Datamap({
element: document.getElementById("resultsMap"),
responsive: true,
fills: {
defaultFill: "#ffffff",
point: "#283F50"
},
geographyConfig: {
highlightFillColor: "#1abc9c",
borderColor: "#283F50"
},
bubblesConfig: {
borderColor: "#283F50"
}
});
bubbles = []
$.each(campaign.results, function(i, result) {
// Check that it wasn't an internal IP
if (result.latitude == 0 && result.longitude == 0) {
return true;
}
newIP = true
$.each(bubbles, function(i, bubble) {
if (bubble.ip == result.ip) {
bubbles[i].radius += 1
newIP = false
return false
}
})
if (newIP) {
console.log("Adding bubble at: ")
console.log({
latitude: result.latitude,
longitude: result.longitude,
name: result.ip,
fillKey: "point"
})
bubbles.push({
latitude: result.latitude,
longitude: result.longitude,
name: result.ip,
fillKey: "point",
radius: 2
})
}
})
map.bubbles(bubbles)
} }
// Load up the map data (only once!)
$('a[data-toggle="tab"]').on('shown.bs.tab', function(e) {
if ($(e.target).attr('href') == "#overview") {
if (!map) {
map = new Datamap({
element: document.getElementById("resultsMap"),
responsive: true,
fills: {
defaultFill: "#ffffff"
},
geographyConfig: {
highlightFillColor: "#1abc9c",
borderColor: "#283F50"
}
});
}
}
})
})
.error(function() {
$("#loading").hide()
errorFlash(" Campaign not found!")
}) })
})
.error(function(){
$("#loading").hide()
errorFlash(" Campaign not found!")
})
}) })

View File

@ -1,212 +1,253 @@
// labels is a map of campaign statuses to // labels is a map of campaign statuses to
// CSS classes // CSS classes
var labels = { var labels = {
"In progress" : "label-primary", "In progress": "label-primary",
"Queued" : "label-info", "Queued": "label-info",
"Completed" : "label-success", "Completed": "label-success",
"Emails Sent" : "label-success", "Emails Sent": "label-success",
"Error" : "label-danger" "Error": "label-danger"
} }
var campaigns = []
// Save attempts to POST to /campaigns/ // Save attempts to POST to /campaigns/
function save(){ function launch() {
groups = [] if (!confirm("This will launch the campaign. Are you sure?")) {
$.each($("#groupTable").DataTable().rows().data(), function(i, group){ return false;
groups.push({name: group[0]})
})
console.log(groups)
var campaign = {
name: $("#name").val(),
template:{
name: $("#template").val()
},
url: $("#url").val(),
page: {
name: $("#page").val()
},
smtp: {
from_address: $("input[name=from]").val(),
host: $("input[name=host]").val(),
username: $("input[name=username]").val(),
password: $("input[name=password]").val(),
},
groups: groups
} }
// Submit the campaign groups = []
$.each($("#groupTable").DataTable().rows().data(), function(i, group) {
groups.push({
name: group[0]
})
})
var campaign = {
name: $("#name").val(),
template: {
name: $("#template").val()
},
url: $("#url").val(),
page: {
name: $("#page").val()
},
smtp: {
from_address: $("input[name=from]").val(),
host: $("input[name=host]").val(),
username: $("input[name=username]").val(),
password: $("input[name=password]").val(),
},
groups: groups
}
// Submit the campaign
api.campaigns.post(campaign) api.campaigns.post(campaign)
.success(function(data){ .success(function(data) {
successFlash("Campaign successfully launched!") successFlash("Campaign successfully launched!")
window.location = "/campaigns/" + campaign.id.toString() window.location = "/campaigns/" + data.id.toString()
}) })
.error(function(data){ .error(function(data) {
$("#modal\\.flashes").empty().append("<div style=\"text-align:center\" class=\"alert alert-danger\">\ $("#modal\\.flashes").empty().append("<div style=\"text-align:center\" class=\"alert alert-danger\">\
<i class=\"fa fa-exclamation-circle\"></i> " + data.responseJSON.message + "</div>") <i class=\"fa fa-exclamation-circle\"></i> " + data.responseJSON.message + "</div>")
}) })
} }
function dismiss(){ function dismiss() {
$("#modal\\.flashes").empty() $("#modal\\.flashes").empty()
$("#modal").modal('hide') $("#modal").modal('hide')
$("#groupTable").dataTable().DataTable().clear().draw() $("#groupTable").dataTable().DataTable().clear().draw()
} }
function edit(campaign){ function deleteCampaign(idx) {
if (confirm("Delete " + campaigns[idx].name + "?")) {
api.campaignId.delete(campaigns[idx].id)
.success(function(data) {
successFlash(data.message)
location.reload()
})
}
}
function edit(campaign) {
// Clear the bloodhound instance // Clear the bloodhound instance
group_bh.clear(); group_bh.clear();
template_bh.clear(); template_bh.clear();
page_bh.clear(); page_bh.clear();
if (campaign == "new") { if (campaign == "new") {
api.groups.get() api.groups.get()
.success(function(groups){ .success(function(groups) {
if (groups.length == 0){ if (groups.length == 0) {
modalError("No groups found!") modalError("No groups found!")
return false; return false;
} } else {
else { group_bh.add(groups)
group_bh.add(groups) }
} })
}) api.templates.get()
api.templates.get() .success(function(templates) {
.success(function(templates){ if (templates.length == 0) {
if (templates.length == 0){ modalError("No templates found!")
modalError("No templates found!") return false
return false } else {
} template_bh.add(templates)
else { }
template_bh.add(templates) })
} api.pages.get()
}) .success(function(pages) {
api.pages.get() if (pages.length == 0) {
.success(function(pages){ modalError("No pages found!")
if (pages.length == 0){ return false
modalError("No pages found!") } else {
return false page_bh.add(pages)
} }
else { })
page_bh.add(pages)
}
})
} }
} }
$(document).ready(function(){ $(document).ready(function() {
api.campaigns.get() api.campaigns.get()
.success(function(campaigns){ .success(function(cs) {
$("#loading").hide() campaigns = cs
if (campaigns.length > 0){ campaignTable = $("#campaignTable").DataTable({
$("#campaignTable").show() columnDefs: [{
campaignTable = $("#campaignTable").DataTable(); orderable: false,
$.each(campaigns, function(i, campaign){ targets: "no-sort"
label = labels[campaign.status] || "label-default"; }]
campaignTable.row.add([ });
campaign.name, $("#loading").hide()
moment(campaign.created_date).format('MMMM Do YYYY, h:mm:ss a'), if (campaigns.length > 0) {
"<span class=\"label " + label + "\">" + campaign.status + "</span>", $("#campaignTable").show()
"<div class='pull-right'><a class='btn btn-primary' href='/campaigns/" + campaign.id + "'>\ campaignTable = $("#campaignTable").DataTable();
$.each(campaigns, function(i, campaign) {
label = labels[campaign.status] || "label-default";
campaignTable.row.add([
campaign.name,
moment(campaign.created_date).format('MMMM Do YYYY, h:mm:ss a'),
"<span class=\"label " + label + "\">" + campaign.status + "</span>",
"<div class='pull-right'><a class='btn btn-primary' href='/campaigns/" + campaign.id + "' data-toggle='tooltip' data-placement='left' title='View Results'>\
<i class='fa fa-bar-chart'></i>\ <i class='fa fa-bar-chart'></i>\
</a>\ </a>\
<button class='btn btn-danger' onclick='alert(\"test\")'>\ <button class='btn btn-danger' onclick='deleteCampaign(" + i + ")' data-toggle='tooltip' data-placement='left' title='Delete Campaign'>\
<i class='fa fa-trash-o'></i>\ <i class='fa fa-trash-o'></i>\
</button></div>" </button></div>"
]).draw() ]).draw()
}) $('[data-toggle="tooltip"]').tooltip()
} else { })
$("#emptyMessage").show() } else {
} $("#emptyMessage").show()
}) }
.error(function(){
$("#loading").hide()
errorFlash("Error fetching campaigns")
})
$("#groupForm").submit(function(){
groupTable.row.add([
$("#groupSelect").val(),
'<span style="cursor:pointer;"><i class="fa fa-trash-o"></i></span>'
]).draw()
$("#groupTable").on("click", "span>i.fa-trash-o", function(){
groupTable.row( $(this).parents('tr') )
.remove()
.draw();
}) })
return false; .error(function() {
$("#loading").hide()
errorFlash("Error fetching campaigns")
})
$("#groupForm").submit(function() {
groupTable.row.add([
$("#groupSelect").val(),
'<span style="cursor:pointer;"><i class="fa fa-trash-o"></i></span>'
]).draw()
$("#groupTable").on("click", "span>i.fa-trash-o", function() {
groupTable.row($(this).parents('tr'))
.remove()
.draw();
})
return false;
})
// Create the group typeahead objects
groupTable = $("#groupTable").DataTable({
columnDefs: [{
orderable: false,
targets: "no-sort"
}]
}) })
// Create the group typeahead objects
groupTable = $("#groupTable").DataTable()
group_bh = new Bloodhound({ group_bh = new Bloodhound({
datumTokenizer: function(g) { return Bloodhound.tokenizers.whitespace(g.name) }, datumTokenizer: function(g) {
return Bloodhound.tokenizers.whitespace(g.name)
},
queryTokenizer: Bloodhound.tokenizers.whitespace, queryTokenizer: Bloodhound.tokenizers.whitespace,
local: [] local: []
}) })
group_bh.initialize() group_bh.initialize()
$("#groupSelect.typeahead.form-control").typeahead({ $("#groupSelect.typeahead.form-control").typeahead({
hint: true, hint: true,
highlight: true, highlight: true,
minLength: 1 minLength: 1
}, }, {
{ name: "groups",
name: "groups", source: group_bh,
source: group_bh, templates: {
templates: { empty: function(data) {
empty: function(data) {return '<div class="tt-suggestion">No groups matched that query</div>' }, return '<div class="tt-suggestion">No groups matched that query</div>'
suggestion: function(data){ return '<div>' + data.name + '</div>' } },
} suggestion: function(data) {
}) return '<div>' + data.name + '</div>'
.bind('typeahead:select', function(ev, group){ }
$("#groupSelect").typeahead('val', group.name) }
}) })
.bind('typeahead:autocomplete', function(ev, group){ .bind('typeahead:select', function(ev, group) {
$("#groupSelect").typeahead('val', group.name) $("#groupSelect").typeahead('val', group.name)
}); })
.bind('typeahead:autocomplete', function(ev, group) {
$("#groupSelect").typeahead('val', group.name)
});
// Create the template typeahead objects // Create the template typeahead objects
template_bh = new Bloodhound({ template_bh = new Bloodhound({
datumTokenizer: function(t) { return Bloodhound.tokenizers.whitespace(t.name) }, datumTokenizer: function(t) {
return Bloodhound.tokenizers.whitespace(t.name)
},
queryTokenizer: Bloodhound.tokenizers.whitespace, queryTokenizer: Bloodhound.tokenizers.whitespace,
local: [] local: []
}) })
template_bh.initialize() template_bh.initialize()
$("#template.typeahead.form-control").typeahead({ $("#template.typeahead.form-control").typeahead({
hint: true, hint: true,
highlight: true, highlight: true,
minLength: 1 minLength: 1
}, }, {
{ name: "templates",
name: "templates", source: template_bh,
source: template_bh, templates: {
templates: { empty: function(data) {
empty: function(data) {return '<div class="tt-suggestion">No templates matched that query</div>' }, return '<div class="tt-suggestion">No templates matched that query</div>'
suggestion: function(data){ return '<div>' + data.name + '</div>' } },
} suggestion: function(data) {
}) return '<div>' + data.name + '</div>'
.bind('typeahead:select', function(ev, template){ }
$("#template").typeahead('val', template.name) }
}) })
.bind('typeahead:autocomplete', function(ev, template){ .bind('typeahead:select', function(ev, template) {
$("#template").typeahead('val', template.name) $("#template").typeahead('val', template.name)
}); })
.bind('typeahead:autocomplete', function(ev, template) {
$("#template").typeahead('val', template.name)
});
// Create the landing page typeahead objects // Create the landing page typeahead objects
page_bh = new Bloodhound({ page_bh = new Bloodhound({
datumTokenizer: function(p) { return Bloodhound.tokenizers.whitespace(p.name) }, datumTokenizer: function(p) {
return Bloodhound.tokenizers.whitespace(p.name)
},
queryTokenizer: Bloodhound.tokenizers.whitespace, queryTokenizer: Bloodhound.tokenizers.whitespace,
local: [] local: []
}) })
page_bh.initialize() page_bh.initialize()
$("#page.typeahead.form-control").typeahead({ $("#page.typeahead.form-control").typeahead({
hint: true, hint: true,
highlight: true, highlight: true,
minLength: 1 minLength: 1
}, }, {
{ name: "pages",
name: "pages", source: page_bh,
source: page_bh, templates: {
templates: { empty: function(data) {
empty: function(data) {return '<div class="tt-suggestion">No pages matched that query</div>' }, return '<div class="tt-suggestion">No pages matched that query</div>'
suggestion: function(data){ return '<div>' + data.name + '</div>' } },
} suggestion: function(data) {
}) return '<div>' + data.name + '</div>'
.bind('typeahead:select', function(ev, page){ }
$("#page").typeahead('val', page.name) }
}) })
.bind('typeahead:autocomplete', function(ev, page){ .bind('typeahead:select', function(ev, page) {
$("#page").typeahead('val', page.name) $("#page").typeahead('val', page.name)
}); })
.bind('typeahead:autocomplete', function(ev, page) {
$("#page").typeahead('val', page.name)
});
}) })

View File

@ -1,129 +1,159 @@
var campaigns = [] var campaigns = []
// labels is a map of campaign statuses to // labels is a map of campaign statuses to
// CSS classes // CSS classes
var labels = { var labels = {
"In progress" : "label-primary", "In progress": "label-primary",
"Queued" : "label-info", "Queued": "label-info",
"Completed" : "label-success", "Completed": "label-success",
"Emails Sent" : "label-success", "Emails Sent": "label-success",
"Error" : "label-danger" "Error": "label-danger"
} }
$(document).ready(function(){ function deleteCampaign(idx) {
if (confirm("Delete " + campaigns[idx].name + "?")) {
api.campaignId.delete(campaigns[idx].id)
.success(function(data) {
successFlash(data.message)
location.reload()
})
}
}
$(document).ready(function() {
api.campaigns.get() api.campaigns.get()
.success(function(cs){ .success(function(cs) {
$("#loading").hide() $("#loading").hide()
campaigns = cs campaigns = cs
if (campaigns.length > 0){ if (campaigns.length > 0) {
$("#dashboard").show() $("#dashboard").show()
// Create the overview chart data // Create the overview chart data
var overview_data = {labels:[],series:[[]]} var overview_data = {
var average_data = {series:[]} labels: [],
var overview_opts = { series: [
axisX: { []
showGrid: false ]
}, }
showArea: true, var average_data = {
plugins: [] series: []
} }
var average_opts = { var overview_opts = {
donut : true, axisX: {
donutWidth: 40, showGrid: false
chartPadding: 0, },
showLabel: false showArea: true,
} plugins: []
var average = 0 }
campaignTable = $("#campaignTable").DataTable(); var average_opts = {
$.each(campaigns, function(i, campaign){ donut: true,
var campaign_date = moment(campaign.created_date).format('MMMM Do YYYY h:mm:ss a') donutWidth: 40,
var label = labels[campaign.status] || "label-default"; chartPadding: 0,
// Add it to the table showLabel: false
campaignTable.row.add([ }
campaign.name, var average = 0
campaign_date, campaignTable = $("#campaignTable").DataTable({
"<span class=\"label " + label + "\">" + campaign.status + "</span>", columnDefs: [
"<div class='pull-right'><a class='btn btn-primary' href='/campaigns/" + campaign.id + "'>\ { orderable: false, targets: "no-sort" }
]
});
$.each(campaigns, function(i, campaign) {
var campaign_date = moment(campaign.created_date).format('MMMM Do YYYY h:mm:ss a')
var label = labels[campaign.status] || "label-default";
// Add it to the table
campaignTable.row.add([
campaign.name,
campaign_date,
"<span class=\"label " + label + "\">" + campaign.status + "</span>",
"<div class='pull-right'><a class='btn btn-primary' href='/campaigns/" + campaign.id + "' data-toggle='tooltip' data-placement='right' title='View Results'>\
<i class='fa fa-bar-chart'></i>\ <i class='fa fa-bar-chart'></i>\
</a>\ </a>\
<button class='btn btn-danger' onclick='deleteCampaign(" + i + ")'>\ <button class='btn btn-danger' onclick='deleteCampaign(" + i + ")' data-toggle='tooltip' data-placement='right' title='Delete Campaign'>\
<i class='fa fa-trash-o'></i>\ <i class='fa fa-trash-o'></i>\
</button></div>" </button></div>"
]).draw() ]).draw()
// Add it to the chart data // Add it to the chart data
campaign.y = 0 campaign.y = 0
$.each(campaign.results, function(j, result){ $.each(campaign.results, function(j, result) {
if (result.status == "Success"){ if (result.status == "Success") {
campaign.y++; campaign.y++;
} }
})
campaign.y = Math.floor((campaign.y / campaign.results.length) * 100)
average += campaign.y
// Add the data to the overview chart
overview_data.labels.push(campaign_date)
overview_data.series[0].push({
meta: i,
value: campaign.y
})
}) })
campaign.y = Math.floor((campaign.y / campaign.results.length) * 100) average = Math.floor(average / campaigns.length);
average += campaign.y average_data.series.push({
// Add the data to the overview chart meta: "Unsuccessful Phishes",
overview_data.labels.push(campaign_date) value: 100 - average
overview_data.series[0].push({meta : i, value: campaign.y}) })
}) average_data.series.push({
average = Math.floor(average / campaigns.length); meta: "Successful Phishes",
average_data.series.push({meta: "Unsuccessful Phishes", value: 100 - average}) value: average
average_data.series.push({meta: "Successful Phishes", value: average}) })
// Build the charts // Build the charts
var average_chart = new Chartist.Pie("#average_chart", average_data, average_opts) var average_chart = new Chartist.Pie("#average_chart", average_data, average_opts)
var overview_chart = new Chartist.Line('#overview_chart', overview_data, overview_opts) var overview_chart = new Chartist.Line('#overview_chart', overview_data, overview_opts)
// Setup the average chart listeners // Setup the average chart listeners
$piechart = $("#average_chart") $piechart = $("#average_chart")
var $pietoolTip = $piechart var $pietoolTip = $piechart
.append('<div class="chartist-tooltip"></div>') .append('<div class="chartist-tooltip"></div>')
.find('.chartist-tooltip') .find('.chartist-tooltip')
.hide(); .hide();
$piechart.on('mouseenter', '.ct-slice-donut', function() { $piechart.on('mouseenter', '.ct-slice-donut', function() {
var $point = $(this) var $point = $(this)
value = $point.attr('ct:value') value = $point.attr('ct:value')
label = $point.attr('ct:meta') label = $point.attr('ct:meta')
$pietoolTip.html(label + ': ' + value.toString() + "%").show(); $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
}); });
});
// Setup the overview chart listeners $piechart.on('mouseleave', '.ct-slice-donut', function() {
$chart = $("#overview_chart") $pietoolTip.hide();
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
}); });
}); $piechart.on('mousemove', function(event) {
$("#overview_chart").on("click", ".ct-point", function(e) { $pietoolTip.css({
var $cidx = $(this).attr('ct:meta'); left: (event.offsetX || event.originalEvent.layerX) - $pietoolTip.width() / 2 - 10,
window.location.href = "/campaigns/" + campaigns[cidx].id top: (event.offsetY + 40 || event.originalEvent.layerY) - $pietoolTip.height() - 80
}); });
} else { });
$("#emptyMessage").show()
} // Setup the overview chart listeners
}) $chart = $("#overview_chart")
.error(function(){ var $toolTip = $chart
errorFlash("Error fetching campaigns") .append('<div class="chartist-tooltip"></div>')
}) .find('.chartist-tooltip')
.hide();
$chart.on('mouseenter', '.ct-point', function() {
var $point = $(this)
value = $point.attr('ct:value') || 0
cidx = $point.attr('ct:meta')
$toolTip.html(campaigns[cidx].name + '<br>' + "Successes: " + value.toString() + "%").show();
});
$chart.on('mouseleave', '.ct-point', function() {
$toolTip.hide();
});
$chart.on('mousemove', function(event) {
$toolTip.css({
left: (event.offsetX || event.originalEvent.layerX) - $toolTip.width() / 2 - 10,
top: (event.offsetY + 40 || event.originalEvent.layerY) - $toolTip.height() - 40
});
});
$("#overview_chart").on("click", ".ct-point", function(e) {
var $cidx = $(this).attr('ct:meta');
window.location.href = "/campaigns/" + campaigns[cidx].id
});
} else {
$("#emptyMessage").show()
}
})
.error(function() {
errorFlash("Error fetching campaigns")
})
}) })

View File

@ -4,72 +4,75 @@
Author: Jordan Wright <github.com/jordan-wright> Author: Jordan Wright <github.com/jordan-wright>
*/ */
var pages = [] var pages = []
// Save attempts to POST to /templates/ // Save attempts to POST to /templates/
function save(idx){ function save(idx) {
var page = {} var page = {}
page.name = $("#name").val() page.name = $("#name").val()
page.html = CKEDITOR.instances["html_editor"].getData(); page.html = CKEDITOR.instances["html_editor"].getData();
if (idx != -1){ if (idx != -1) {
page.id = pages[idx].id page.id = pages[idx].id
api.pageId.put(page) api.pageId.put(page)
.success(function(data){ .success(function(data) {
successFlash("Page edited successfully!") successFlash("Page edited successfully!")
load() load()
dismiss() dismiss()
}) })
} else { } else {
// Submit the page // Submit the page
api.pages.post(page) api.pages.post(page)
.success(function(data){ .success(function(data) {
successFlash("Page added successfully!") successFlash("Page added successfully!")
load() load()
dismiss() dismiss()
}) })
.error(function(data){ .error(function(data) {
modalError(data.responseJSON.message) modalError(data.responseJSON.message)
}) })
} }
} }
function dismiss(){ function dismiss() {
$("#modal\\.flashes").empty() $("#modal\\.flashes").empty()
$("#name").val("") $("#name").val("")
$("#html_editor").val("") $("#html_editor").val("")
$("#newLandingPageModal").modal('hide') $("#newLandingPageModal").modal('hide')
} }
function deletePage(idx){ function deletePage(idx) {
if (confirm("Delete " + pages[idx].name + "?")){ if (confirm("Delete " + pages[idx].name + "?")) {
api.pageId.delete(pages[idx].id) api.pageId.delete(pages[idx].id)
.success(function(data){ .success(function(data) {
successFlash(data.message) successFlash(data.message)
load() load()
}) })
} }
} }
function importSite(){ function importSite() {
url = $("#url").val() url = $("#url").val()
if (!url){ if (!url) {
modalError("No URL Specified!") modalError("No URL Specified!")
} else { } else {
api.clone_site({ api.clone_site({
url: url, url: url,
include_resources: false include_resources: false
}) })
.success(function(data){ .success(function(data) {
console.log($("#html_editor")) console.log($("#html_editor"))
$("#html_editor").val(data.html) $("#html_editor").val(data.html)
$("#importSiteModal").modal("hide") $("#importSiteModal").modal("hide")
}) })
.error(function(data){ .error(function(data) {
modalError(data.responseJSON.message) modalError(data.responseJSON.message)
}) })
} }
} }
function edit(idx){ function edit(idx) {
$("#modalSubmit").unbind('click').click(function(){save(idx)}) $("#modalSubmit").unbind('click').click(function() {
save(idx)
})
$("#html_editor").ckeditor() $("#html_editor").ckeditor()
var page = {} var page = {}
if (idx != -1) { if (idx != -1) {
@ -79,82 +82,86 @@ function edit(idx){
} }
} }
function load(){ function load() {
/* /*
load() - Loads the current pages using the API load() - Loads the current pages using the API
*/ */
$("#pagesTable").hide() $("#pagesTable").hide()
$("#emptyMessage").hide() $("#emptyMessage").hide()
$("#loading").show() $("#loading").show()
api.pages.get() api.pages.get()
.success(function(ps){ .success(function(ps) {
pages = ps pages = ps
$("#loading").hide() $("#loading").hide()
if (pages.length > 0){ if (pages.length > 0) {
$("#pagesTable").show() $("#pagesTable").show()
pagesTable = $("#pagesTable").DataTable(); pagesTable = $("#pagesTable").DataTable({
pagesTable.clear() destroy: true,
$.each(pages, function(i, page){ columnDefs: [{
pagesTable.row.add([ orderable: false,
page.name, targets: "no-sort"
moment(page.modified_date).format('MMMM Do YYYY, h:mm:ss a'), }]
"<div class='pull-right'><button class='btn btn-primary' data-toggle='modal' data-target='#newLandingPageModal' onclick='edit(" + i + ")'>\ });
pagesTable.clear()
$.each(pages, function(i, page) {
pagesTable.row.add([
page.name,
moment(page.modified_date).format('MMMM Do YYYY, h:mm:ss a'),
"<div class='pull-right'><button class='btn btn-primary' data-toggle='modal' data-target='#newLandingPageModal' onclick='edit(" + i + ")'>\
<i class='fa fa-pencil'></i>\ <i class='fa fa-pencil'></i>\
</button>\ </button>\
<button class='btn btn-danger' onclick='deletePage(" + i + ")'>\ <button class='btn btn-danger' onclick='deletePage(" + i + ")'>\
<i class='fa fa-trash-o'></i>\ <i class='fa fa-trash-o'></i>\
</button></div>" </button></div>"
]).draw() ]).draw()
}) })
} else { } else {
$("#emptyMessage").show() $("#emptyMessage").show()
} }
}) })
.error(function(){ .error(function() {
$("#loading").hide() $("#loading").hide()
errorFlash("Error fetching pages") errorFlash("Error fetching pages")
}) })
} }
$(document).ready(function(){ $(document).ready(function() {
// Setup multiple modals // Setup multiple modals
// Code based on http://miles-by-motorcycle.com/static/bootstrap-modal/index.html // Code based on http://miles-by-motorcycle.com/static/bootstrap-modal/index.html
$('.modal').on('hidden.bs.modal', function( event ) { $('.modal').on('hidden.bs.modal', function(event) {
$(this).removeClass( 'fv-modal-stack' ); $(this).removeClass('fv-modal-stack');
$('body').data( 'fv_open_modals', $('body').data( 'fv_open_modals' ) - 1 ); $('body').data('fv_open_modals', $('body').data('fv_open_modals') - 1);
}); });
$( '.modal' ).on( 'shown.bs.modal', function ( event ) { $('.modal').on('shown.bs.modal', function(event) {
// Keep track of the number of open modals // Keep track of the number of open modals
if ( typeof( $('body').data( 'fv_open_modals' ) ) == 'undefined' ) if (typeof($('body').data('fv_open_modals')) == 'undefined') {
{ $('body').data('fv_open_modals', 0);
$('body').data( 'fv_open_modals', 0 );
} }
// if the z-index of this modal has been set, ignore. // if the z-index of this modal has been set, ignore.
if ( $(this).hasClass( 'fv-modal-stack' ) ) if ($(this).hasClass('fv-modal-stack')) {
{
return; return;
} }
$(this).addClass( 'fv-modal-stack' ); $(this).addClass('fv-modal-stack');
// Increment the number of open modals // Increment the number of open modals
$('body').data( 'fv_open_modals', $('body').data( 'fv_open_modals' ) + 1 ); $('body').data('fv_open_modals', $('body').data('fv_open_modals') + 1);
// Setup the appropriate z-index // Setup the appropriate z-index
$(this).css('z-index', 1040 + (10 * $('body').data( 'fv_open_modals' ))); $(this).css('z-index', 1040 + (10 * $('body').data('fv_open_modals')));
$( '.modal-backdrop' ).not( '.fv-modal-stack' ).css( 'z-index', 1039 + (10 * $('body').data( 'fv_open_modals' ))); $('.modal-backdrop').not('.fv-modal-stack').css('z-index', 1039 + (10 * $('body').data('fv_open_modals')));
$( '.modal-backdrop' ).not( 'fv-modal-stack' ).addClass( 'fv-modal-stack' ); $('.modal-backdrop').not('fv-modal-stack').addClass('fv-modal-stack');
}); });
$.fn.modal.Constructor.prototype.enforceFocus = function() { $.fn.modal.Constructor.prototype.enforceFocus = function() {
$( document ) $(document)
.off( 'focusin.bs.modal' ) // guard against infinite focus loop .off('focusin.bs.modal') // guard against infinite focus loop
.on( 'focusin.bs.modal', $.proxy( function( e ) { .on('focusin.bs.modal', $.proxy(function(e) {
if ( if (
this.$element[ 0 ] !== e.target && !this.$element.has( e.target ).length this.$element[0] !== e.target && !this.$element.has(e.target).length
// CKEditor compatibility fix start. // CKEditor compatibility fix start.
&& !$( e.target ).closest( '.cke_dialog, .cke' ).length && !$(e.target).closest('.cke_dialog, .cke').length
// CKEditor compatibility fix end. // CKEditor compatibility fix end.
) { ) {
this.$element.trigger( 'focus' ); this.$element.trigger('focus');
} }
}, this ) ); }, this));
}; };
load() load()
}) })

View File

@ -1,24 +1,24 @@
$(document).ready(function(){ $(document).ready(function() {
$("#apiResetForm").submit(function(e){ $("#apiResetForm").submit(function(e) {
$.post("/api/reset", $(this).serialize()) $.post("/api/reset", $(this).serialize())
.done(function(data){ .done(function(data) {
api_key = data.data api_key = data.data
successFlash(data.message) successFlash(data.message)
$("#api_key").val(api_key) $("#api_key").val(api_key)
}) })
.fail(function(data){ .fail(function(data) {
errorFlash(data.message) errorFlash(data.message)
}) })
return false return false
}) })
$("#settingsForm").submit(function(e){ $("#settingsForm").submit(function(e) {
$.post("/settings", $(this).serialize()) $.post("/settings", $(this).serialize())
.done(function(data){ .done(function(data) {
successFlash(data.message) successFlash(data.message)
}) })
.fail(function(data){ .fail(function(data) {
errorFlash(data.responseJSON.message) errorFlash(data.responseJSON.message)
}) })
return false return false
}) })
}) })

View File

@ -1,64 +1,66 @@
var templates = [] var templates = []
var icons = { var icons = {
"application/vnd.ms-excel" : "fa-file-excel-o", "application/vnd.ms-excel": "fa-file-excel-o",
"text/plain" : "fa-file-text-o", "text/plain": "fa-file-text-o",
"image/gif" : "fa-file-image-o", "image/gif": "fa-file-image-o",
"image/png" : "fa-file-image-o", "image/png": "fa-file-image-o",
"application/pdf" : "fa-file-pdf-o", "application/pdf": "fa-file-pdf-o",
"application/x-zip-compressed" : "fa-file-archive-o", "application/x-zip-compressed": "fa-file-archive-o",
"application/x-gzip" : "fa-file-archive-o", "application/x-gzip": "fa-file-archive-o",
"application/vnd.openxmlformats-officedocument.presentationml.presentation" : "fa-file-powerpoint-o", "application/vnd.openxmlformats-officedocument.presentationml.presentation": "fa-file-powerpoint-o",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document" : "fa-file-word-o", "application/vnd.openxmlformats-officedocument.wordprocessingml.document": "fa-file-word-o",
"application/octet-stream" : "fa-file-o", "application/octet-stream": "fa-file-o",
"application/x-msdownload" : "fa-file-o" "application/x-msdownload": "fa-file-o"
} }
// Save attempts to POST to /templates/ // Save attempts to POST to /templates/
function save(idx){ function save(idx) {
var template = {attachments:[]} var template = {
attachments: []
}
template.name = $("#name").val() template.name = $("#name").val()
template.subject = $("#subject").val() template.subject = $("#subject").val()
template.html = CKEDITOR.instances["html_editor"].getData(); template.html = CKEDITOR.instances["html_editor"].getData();
// Fix the URL Scheme added by CKEditor (until we can remove it from the plugin) // Fix the URL Scheme added by CKEditor (until we can remove it from the plugin)
template.html = template.html.replace(/https?:\/\/{{\.URL}}/gi, "{{.URL}}") template.html = template.html.replace(/https?:\/\/{{\.URL}}/gi, "{{.URL}}")
// If the "Add Tracker Image" checkbox is checked, add the tracker // If the "Add Tracker Image" checkbox is checked, add the tracker
if ($("#use_tracker_checkbox").prop("checked") && if ($("#use_tracker_checkbox").prop("checked") &&
template.html.indexOf("{{.Tracker}}") == -1 && template.html.indexOf("{{.Tracker}}") == -1 &&
template.html.indexOf("{{.TrackingUrl}}") == -1){ template.html.indexOf("{{.TrackingUrl}}") == -1) {
template.html = template.html.replace("</body>", "{{.Tracker}}</body>") template.html = template.html.replace("</body>", "{{.Tracker}}</body>")
} }
template.text = $("#text_editor").val() template.text = $("#text_editor").val()
// Add the attachments // Add the attachments
$.each($("#attachmentsTable").DataTable().rows().data(), function(i, target){ $.each($("#attachmentsTable").DataTable().rows().data(), function(i, target) {
template.attachments.push({ template.attachments.push({
name : target[1], name: target[1],
content: target[3], content: target[3],
type: target[4], type: target[4],
}) })
}) })
if (idx != -1){ if (idx != -1) {
template.id = templates[idx].id template.id = templates[idx].id
api.templateId.put(template) api.templateId.put(template)
.success(function(data){ .success(function(data) {
successFlash("Template edited successfully!") successFlash("Template edited successfully!")
load() load()
dismiss() dismiss()
}) })
} else { } else {
// Submit the template // Submit the template
api.templates.post(template) api.templates.post(template)
.success(function(data){ .success(function(data) {
successFlash("Template added successfully!") successFlash("Template added successfully!")
load() load()
dismiss() dismiss()
}) })
.error(function(data){ .error(function(data) {
modalError(data.responseJSON.message) modalError(data.responseJSON.message)
}) })
} }
} }
function dismiss(){ function dismiss() {
$("#modal\\.flashes").empty() $("#modal\\.flashes").empty()
$("#attachmentsTable").dataTable().DataTable().clear().draw() $("#attachmentsTable").dataTable().DataTable().clear().draw()
$("#name").val("") $("#name").val("")
@ -67,24 +69,36 @@ function dismiss(){
$("#modal").modal('hide') $("#modal").modal('hide')
} }
function deleteTemplate(idx){ function deleteTemplate(idx) {
if (confirm("Delete " + templates[idx].name + "?")){ if (confirm("Delete " + templates[idx].name + "?")) {
api.templateId.delete(templates[idx].id) api.templateId.delete(templates[idx].id)
.success(function(data){ .success(function(data) {
successFlash(data.message) successFlash(data.message)
load() load()
}) })
} }
} }
function attach(files){ function attach(files) {
attachmentsTable = $("#attachmentsTable").DataTable(); attachmentsTable = $("#attachmentsTable").DataTable({
$.each(files, function(i, file){ destroy: true,
"order": [
[1, "asc"]
],
columnDefs: [{
orderable: false,
targets: "no-sort"
}, {
sClass: "datatable_hidden",
targets: [3, 4]
}]
});
$.each(files, function(i, file) {
var reader = new FileReader(); var reader = new FileReader();
/* Make this a datatable */ /* Make this a datatable */
reader.onload = function(e){ reader.onload = function(e) {
var icon = icons[file.type] || "fa-file-o" var icon = icons[file.type] || "fa-file-o"
// Add the record to the modal // Add the record to the modal
attachmentsTable.row.add([ attachmentsTable.row.add([
'<i class="fa ' + icon + '"></i>', '<i class="fa ' + icon + '"></i>',
file.name, file.name,
@ -100,33 +114,40 @@ function attach(files){
}) })
} }
function edit(idx){ function edit(idx) {
$("#modalSubmit").unbind('click').click(function(){save(idx)}) $("#modalSubmit").unbind('click').click(function() {
$("#attachmentUpload").unbind('click').click(function(){this.value=null}) save(idx)
})
$("#attachmentUpload").unbind('click').click(function() {
this.value = null
})
$("#html_editor").ckeditor() $("#html_editor").ckeditor()
$("#attachmentsTable").show() $("#attachmentsTable").show()
attachmentsTable = null attachmentsTable = $('#attachmentsTable').DataTable({
if ( $.fn.dataTable.isDataTable('#attachmentsTable') ) { destroy: true,
attachmentsTable = $('#attachmentsTable').DataTable(); "order": [
[1, "asc"]
],
columnDefs: [{
orderable: false,
targets: "no-sort"
}, {
sClass: "datatable_hidden",
targets: [3, 4]
}]
});
var template = {
attachments: []
} }
else {
attachmentsTable = $("#attachmentsTable").DataTable({
"aoColumnDefs" : [{
"targets" : [3,4],
"sClass" : "datatable_hidden"
}]
});
}
var template = {attachments:[]}
if (idx != -1) { if (idx != -1) {
template = templates[idx] template = templates[idx]
$("#name").val(template.name) $("#name").val(template.name)
$("#subject").val(template.subject) $("#subject").val(template.subject)
$("#html_editor").val(template.html) $("#html_editor").val(template.html)
$("#text_editor").val(template.text) $("#text_editor").val(template.text)
$.each(template.attachments, function(i, file){ $.each(template.attachments, function(i, file) {
var icon = icons[file.type] || "fa-file-o" var icon = icons[file.type] || "fa-file-o"
// Add the record to the modal // Add the record to the modal
attachmentsTable.row.add([ attachmentsTable.row.add([
'<i class="fa ' + icon + '"></i>', '<i class="fa ' + icon + '"></i>',
file.name, file.name,
@ -137,110 +158,114 @@ function edit(idx){
}) })
} }
// Handle Deletion // Handle Deletion
$("#attachmentsTable").unbind('click').on("click", "span>i.fa-trash-o", function(){ $("#attachmentsTable").unbind('click').on("click", "span>i.fa-trash-o", function() {
attachmentsTable.row( $(this).parents('tr') ) attachmentsTable.row($(this).parents('tr'))
.remove() .remove()
.draw(); .draw();
}) })
} }
function importEmail(){ function importEmail() {
raw = $("#email_content").val() raw = $("#email_content").val()
if (!raw){ if (!raw) {
modalError("No Content Specified!") modalError("No Content Specified!")
} else { } else {
$.ajax({ $.ajax({
type: "POST", type: "POST",
url: "/api/import/email", url: "/api/import/email",
data: raw, data: raw,
dataType: "json", dataType: "json",
contentType: "text/plain" contentType: "text/plain"
}) })
.success(function(data){ .success(function(data) {
$("#text_editor").val(data.text) $("#text_editor").val(data.text)
$("#html_editor").val(data.html) $("#html_editor").val(data.html)
$("#subject").val(data.subject) $("#subject").val(data.subject)
$("#importEmailModal").modal("hide") $("#importEmailModal").modal("hide")
}) })
.error(function(data){ .error(function(data) {
modalError(data.responseJSON.message) modalError(data.responseJSON.message)
}) })
} }
} }
function load(){ function load() {
$("#templateTable").hide() $("#templateTable").hide()
$("#emptyMessage").hide() $("#emptyMessage").hide()
$("#loading").show() $("#loading").show()
api.templates.get() api.templates.get()
.success(function(ts){ .success(function(ts) {
templates = ts templates = ts
$("#loading").hide() $("#loading").hide()
if (templates.length > 0){ if (templates.length > 0) {
$("#templateTable").show() $("#templateTable").show()
templateTable = $("#templateTable").DataTable(); templateTable = $("#templateTable").DataTable({
templateTable.clear() destroy: true,
$.each(templates, function(i, template){ columnDefs: [{
templateTable.row.add([ orderable: false,
template.name, targets: "no-sort"
moment(template.modified_date).format('MMMM Do YYYY, h:mm:ss a'), }]
"<div class='pull-right'><button class='btn btn-primary' data-toggle='modal' data-target='#modal' onclick='edit(" + i + ")'>\ });
templateTable.clear()
$.each(templates, function(i, template) {
templateTable.row.add([
template.name,
moment(template.modified_date).format('MMMM Do YYYY, h:mm:ss a'),
"<div class='pull-right'><button class='btn btn-primary' data-toggle='modal' data-target='#modal' onclick='edit(" + i + ")'>\
<i class='fa fa-pencil'></i>\ <i class='fa fa-pencil'></i>\
</button>\ </button>\
<button class='btn btn-danger' onclick='deleteTemplate(" + i + ")'>\ <button class='btn btn-danger' onclick='deleteTemplate(" + i + ")'>\
<i class='fa fa-trash-o'></i>\ <i class='fa fa-trash-o'></i>\
</button></div>" </button></div>"
]).draw() ]).draw()
}) })
} else { } else {
$("#emptyMessage").show() $("#emptyMessage").show()
} }
}) })
.error(function(){ .error(function() {
$("#loading").hide() $("#loading").hide()
errorFlash("Error fetching templates") errorFlash("Error fetching templates")
}) })
} }
$(document).ready(function(){ $(document).ready(function() {
// Setup multiple modals // Setup multiple modals
// Code based on http://miles-by-motorcycle.com/static/bootstrap-modal/index.html // Code based on http://miles-by-motorcycle.com/static/bootstrap-modal/index.html
$('.modal').on('hidden.bs.modal', function( event ) { $('.modal').on('hidden.bs.modal', function(event) {
$(this).removeClass( 'fv-modal-stack' ); $(this).removeClass('fv-modal-stack');
$('body').data( 'fv_open_modals', $('body').data( 'fv_open_modals' ) - 1 ); $('body').data('fv_open_modals', $('body').data('fv_open_modals') - 1);
}); });
$( '.modal' ).on( 'shown.bs.modal', function ( event ) { $('.modal').on('shown.bs.modal', function(event) {
// Keep track of the number of open modals // Keep track of the number of open modals
if ( typeof( $('body').data( 'fv_open_modals' ) ) == 'undefined' ) if (typeof($('body').data('fv_open_modals')) == 'undefined') {
{ $('body').data('fv_open_modals', 0);
$('body').data( 'fv_open_modals', 0 );
} }
// if the z-index of this modal has been set, ignore. // if the z-index of this modal has been set, ignore.
if ( $(this).hasClass( 'fv-modal-stack' ) ) if ($(this).hasClass('fv-modal-stack')) {
{
return; return;
} }
$(this).addClass( 'fv-modal-stack' ); $(this).addClass('fv-modal-stack');
// Increment the number of open modals // Increment the number of open modals
$('body').data( 'fv_open_modals', $('body').data( 'fv_open_modals' ) + 1 ); $('body').data('fv_open_modals', $('body').data('fv_open_modals') + 1);
// Setup the appropriate z-index // Setup the appropriate z-index
$(this).css('z-index', 1040 + (10 * $('body').data( 'fv_open_modals' ))); $(this).css('z-index', 1040 + (10 * $('body').data('fv_open_modals')));
$( '.modal-backdrop' ).not( '.fv-modal-stack' ).css( 'z-index', 1039 + (10 * $('body').data( 'fv_open_modals' ))); $('.modal-backdrop').not('.fv-modal-stack').css('z-index', 1039 + (10 * $('body').data('fv_open_modals')));
$( '.modal-backdrop' ).not( 'fv-modal-stack' ).addClass( 'fv-modal-stack' ); $('.modal-backdrop').not('fv-modal-stack').addClass('fv-modal-stack');
}); });
$.fn.modal.Constructor.prototype.enforceFocus = function() { $.fn.modal.Constructor.prototype.enforceFocus = function() {
$( document ) $(document)
.off( 'focusin.bs.modal' ) // guard against infinite focus loop .off('focusin.bs.modal') // guard against infinite focus loop
.on( 'focusin.bs.modal', $.proxy( function( e ) { .on('focusin.bs.modal', $.proxy(function(e) {
if ( if (
this.$element[ 0 ] !== e.target && !this.$element.has( e.target ).length this.$element[0] !== e.target && !this.$element.has(e.target).length
// CKEditor compatibility fix start. // CKEditor compatibility fix start.
&& !$( e.target ).closest( '.cke_dialog, .cke' ).length && !$(e.target).closest('.cke_dialog, .cke').length
// CKEditor compatibility fix end. // CKEditor compatibility fix end.
) { ) {
this.$element.trigger( 'focus' ); this.$element.trigger('focus');
} }
}, this ) ); }, this));
}; };
load() load()
}) })

View File

@ -1,59 +1,68 @@
var groups = [] var groups = []
// Save attempts to POST or PUT to /groups/ // Save attempts to POST or PUT to /groups/
function save(idx){ function save(idx) {
var targets = [] var targets = []
$.each($("#targetsTable").DataTable().rows().data(), function(i, target){ $.each($("#targetsTable").DataTable().rows().data(), function(i, target) {
targets.push({ targets.push({
first_name : target[0], first_name: target[0],
last_name: target[1], last_name: target[1],
email: target[2], email: target[2],
position: target[3] position: target[3]
}) })
}) })
var group = { var group = {
name: $("#name").val(), name: $("#name").val(),
targets: targets targets: targets
} }
// Submit the group // Submit the group
if (idx != -1) { if (idx != -1) {
// If we're just editing an existing group, // If we're just editing an existing group,
// we need to PUT /groups/:id // we need to PUT /groups/:id
group.id = groups[idx].id group.id = groups[idx].id
api.groupId.put(group) api.groupId.put(group)
.success(function(data){ .success(function(data) {
successFlash("Group updated successfully!") successFlash("Group updated successfully!")
load() load()
dismiss() dismiss()
}) $("#modal").modal('hide')
.error(function(data){ })
modalError(data.responseJSON.message) .error(function(data) {
}) modalError(data.responseJSON.message)
})
} else { } else {
// Else, if this is a new group, POST it // Else, if this is a new group, POST it
// to /groups // to /groups
api.groups.post(group) api.groups.post(group)
.success(function(data){ .success(function(data) {
successFlash("Group added successfully!") successFlash("Group added successfully!")
load() load()
dismiss() dismiss()
}) $("#modal").modal('hide')
.error(function(data){ })
modalError(data.responseJSON.message) .error(function(data) {
}) modalError(data.responseJSON.message)
})
} }
} }
function dismiss(){ function dismiss() {
$("#targetsTable").dataTable().DataTable().clear().draw() $("#targetsTable").dataTable().DataTable().clear().draw()
$("#name").val("") $("#name").val("")
$("#modal\\.flashes").empty() $("#modal\\.flashes").empty()
$("#modal").modal('hide')
} }
function edit(idx){ function edit(idx) {
targets = $("#targetsTable").dataTable() targets = $("#targetsTable").dataTable({
$("#modalSubmit").unbind('click').click(function(){save(idx)}) destroy: true, // Destroy any other instantiated table - http://datatables.net/manual/tech-notes/3#destroy
columnDefs: [{
orderable: false,
targets: "no-sort"
}]
})
$("#modalSubmit").unbind('click').click(function() {
save(idx)
})
if (idx == -1) { if (idx == -1) {
group = {} group = {}
} else { } else {
@ -61,31 +70,6 @@ function edit(idx){
$("#name").val(group.name) $("#name").val(group.name)
$.each(group.targets, function(i, record) { $.each(group.targets, function(i, record) {
targets.DataTable() targets.DataTable()
.row.add([
record.first_name,
record.last_name,
record.email,
record.position,
'<span style="cursor:pointer;"><i class="fa fa-trash-o"></i></span>'
]).draw()
});
}
// Handle file uploads
$("#csvupload").fileupload({
dataType:"json",
add: function(e, data){
$("#modal\\.flashes").empty()
var acceptFileTypes= /(csv|txt)$/i;
var filename = data.originalFiles[0]['name']
if (filename && !acceptFileTypes.test(filename.split(".").pop())) {
modalError("Unsupported file extension (use .csv or .txt)")
return false;
}
data.submit();
},
done: function(e, data){
$.each(data.result, function(i, record) {
targets.DataTable()
.row.add([ .row.add([
record.first_name, record.first_name,
record.last_name, record.last_name,
@ -93,87 +77,121 @@ function edit(idx){
record.position, record.position,
'<span style="cursor:pointer;"><i class="fa fa-trash-o"></i></span>' '<span style="cursor:pointer;"><i class="fa fa-trash-o"></i></span>'
]).draw() ]).draw()
});
}
// Handle file uploads
$("#csvupload").fileupload({
dataType: "json",
add: function(e, data) {
$("#modal\\.flashes").empty()
var acceptFileTypes = /(csv|txt)$/i;
var filename = data.originalFiles[0]['name']
if (filename && !acceptFileTypes.test(filename.split(".").pop())) {
modalError("Unsupported file extension (use .csv or .txt)")
return false;
}
data.submit();
},
done: function(e, data) {
$.each(data.result, function(i, record) {
targets.DataTable()
.row.add([
record.first_name,
record.last_name,
record.email,
record.position,
'<span style="cursor:pointer;"><i class="fa fa-trash-o"></i></span>'
]).draw()
}); });
} }
}) })
} }
function deleteGroup(idx){ function deleteGroup(idx) {
if (confirm("Delete " + groups[idx].name + "?")){ if (confirm("Delete " + groups[idx].name + "?")) {
api.groupId.delete(groups[idx].id) api.groupId.delete(groups[idx].id)
.success(function(data){ .success(function(data) {
successFlash(data.message) successFlash(data.message)
load() load()
}) })
} }
} }
function load(){ function load() {
$("#groupTable").hide() $("#groupTable").hide()
$("#emptyMessage").hide() $("#emptyMessage").hide()
$("#loading").show() $("#loading").show()
api.groups.get() api.groups.get()
.success(function(gs){ .success(function(gs) {
$("#loading").hide() $("#loading").hide()
if (gs.length > 0){ if (gs.length > 0) {
groups = gs groups = gs
$("#emptyMessage").hide() $("#emptyMessage").hide()
$("#groupTable").show() $("#groupTable").show()
groupTable = $("#groupTable").DataTable(); groupTable = $("#groupTable").DataTable({
groupTable.clear(); destroy: true,
$.each(groups, function(i, group){ columnDefs: [{
var targets = "" orderable: false,
$.each(group.targets, function(i, target){ targets: "no-sort"
targets += target.email + ", " }]
if (targets.length > 50) { });
targets = targets.slice(0,-3) + "..." groupTable.clear();
return false; $.each(groups, function(i, group) {
} var targets = ""
}) $.each(group.targets, function(i, target) {
groupTable.row.add([ targets += target.email + ", "
group.name, if (targets.length > 50) {
targets, targets = targets.slice(0, -3) + "..."
moment(group.modified_date).format('MMMM Do YYYY, h:mm:ss a'), return false;
"<div class='pull-right'><button class='btn btn-primary' data-toggle='modal' data-target='#modal' onclick='edit(" + i + ")'>\ }
})
groupTable.row.add([
group.name,
targets,
moment(group.modified_date).format('MMMM Do YYYY, h:mm:ss a'),
"<div class='pull-right'><button class='btn btn-primary' data-toggle='modal' data-target='#modal' onclick='edit(" + i + ")'>\
<i class='fa fa-pencil'></i>\ <i class='fa fa-pencil'></i>\
</button>\ </button>\
<button class='btn btn-danger' onclick='deleteGroup(" + i + ")'>\ <button class='btn btn-danger' onclick='deleteGroup(" + i + ")'>\
<i class='fa fa-trash-o'></i>\ <i class='fa fa-trash-o'></i>\
</button></div>" </button></div>"
]).draw() ]).draw()
}) })
} else { } else {
$("#emptyMessage").show() $("#emptyMessage").show()
} }
}) })
.error(function(){ .error(function() {
errorFlash("Error fetching groups") errorFlash("Error fetching groups")
}) })
} }
$(document).ready(function(){ $(document).ready(function() {
load() load()
// Setup the event listeners // Setup the event listeners
// Handle manual additions // Handle manual additions
$("#targetForm").submit(function(){ $("#targetForm").submit(function() {
targets.DataTable()
.row.add([
$("#firstName").val(),
$("#lastName").val(),
$("#email").val(),
$("#position").val(),
'<span style="cursor:pointer;"><i class="fa fa-trash-o"></i></span>'
])
.draw()
$("#targetForm>div>input").val('')
$("#firstName").focus()
return false
})
// Handle Deletion
$("#targetsTable").on("click", "span>i.fa-trash-o", function() {
targets.DataTable() targets.DataTable()
.row.add([ .row($(this).parents('tr'))
$("#firstName").val(), .remove()
$("#lastName").val(), .draw();
$("#email").val(),
$("#position").val(),
'<span style="cursor:pointer;"><i class="fa fa-trash-o"></i></span>'
])
.draw()
$("#targetForm>div>input").val('')
$("#firstName").focus()
return false
}) })
// Handle Deletion $("#modal").on("hide.bs.modal", function() {
$("#targetsTable").on("click", "span>i.fa-trash-o", function(){ dismiss()
targets.DataTable()
.row( $(this).parents('tr') )
.remove()
.draw();
}) })
}) })

View File

@ -1,18 +1,16 @@
function errorFlash(message) { function errorFlash(message) {
$("#flashes").empty() $("#flashes").empty()
$("#flashes").append("<div style=\"text-align:center\" class=\"alert alert-danger\">\ $("#flashes").append("<div style=\"text-align:center\" class=\"alert alert-danger\">\
<i class=\"fa fa-exclamation-circle\"></i> " + message + "</div>" <i class=\"fa fa-exclamation-circle\"></i> " + message + "</div>")
)
} }
function successFlash(message) { function successFlash(message) {
$("#flashes").empty() $("#flashes").empty()
$("#flashes").append("<div style=\"text-align:center\" class=\"alert alert-success\">\ $("#flashes").append("<div style=\"text-align:center\" class=\"alert alert-success\">\
<i class=\"fa fa-check-circle\"></i> " + message + "</div>" <i class=\"fa fa-check-circle\"></i> " + message + "</div>")
)
} }
function modalError(message){ function modalError(message) {
$("#modal\\.flashes").empty().append("<div style=\"text-align:center\" class=\"alert alert-danger\">\ $("#modal\\.flashes").empty().append("<div style=\"text-align:center\" class=\"alert alert-danger\">\
<i class=\"fa fa-exclamation-circle\"></i> " + message + "</div>") <i class=\"fa fa-exclamation-circle\"></i> " + message + "</div>")
} }
@ -23,7 +21,7 @@ function query(endpoint, method, data) {
async: false, async: false,
method: method, method: method,
data: JSON.stringify(data), data: JSON.stringify(data),
dataType:"json", dataType: "json",
contentType: "application/json" contentType: "application/json"
}) })
} }
@ -33,117 +31,117 @@ Define our API Endpoints
*/ */
var api = { var api = {
// campaigns contains the endpoints for /campaigns // campaigns contains the endpoints for /campaigns
campaigns : { campaigns: {
// get() - Queries the API for GET /campaigns // get() - Queries the API for GET /campaigns
get: function(){ get: function() {
return query("/campaigns/", "GET", {}) return query("/campaigns/", "GET", {})
}, },
// post() - Posts a campaign to POST /campaigns // post() - Posts a campaign to POST /campaigns
post: function(data){ post: function(data) {
return query("/campaigns/", "POST", data) return query("/campaigns/", "POST", data)
} }
}, },
// campaignId contains the endpoints for /campaigns/:id // campaignId contains the endpoints for /campaigns/:id
campaignId : { campaignId: {
// get() - Queries the API for GET /campaigns/:id // get() - Queries the API for GET /campaigns/:id
get: function(id){ get: function(id) {
return query("/campaigns/" + id, "GET", {}) return query("/campaigns/" + id, "GET", {})
}, },
// delete() - Deletes a campaign at DELETE /campaigns/:id // delete() - Deletes a campaign at DELETE /campaigns/:id
delete: function(id){ delete: function(id) {
return query("/campaigns/" + id, "DELETE", data) return query("/campaigns/" + id, "DELETE", {})
} }
}, },
// groups contains the endpoints for /groups // groups contains the endpoints for /groups
groups : { groups: {
// get() - Queries the API for GET /groups // get() - Queries the API for GET /groups
get: function(){ get: function() {
return query("/groups/", "GET", {}) return query("/groups/", "GET", {})
}, },
// post() - Posts a campaign to POST /groups // post() - Posts a campaign to POST /groups
post: function(group){ post: function(group) {
return query("/groups/", "POST", group) return query("/groups/", "POST", group)
} }
}, },
// groupId contains the endpoints for /groups/:id // groupId contains the endpoints for /groups/:id
groupId : { groupId: {
// get() - Queries the API for GET /groups/:id // get() - Queries the API for GET /groups/:id
get: function(id){ get: function(id) {
return query("/groups/" + id, "GET", {}) return query("/groups/" + id, "GET", {})
}, },
// put() - Puts a campaign to PUT /groups/:id // put() - Puts a campaign to PUT /groups/:id
put: function (group){ put: function(group) {
return query("/groups/" + group.id, "PUT", group) return query("/groups/" + group.id, "PUT", group)
}, },
// delete() - Deletes a campaign at DELETE /groups/:id // delete() - Deletes a campaign at DELETE /groups/:id
delete: function(id){ delete: function(id) {
return query("/groups/" + id, "DELETE", {}) return query("/groups/" + id, "DELETE", {})
} }
}, },
// templates contains the endpoints for /templates // templates contains the endpoints for /templates
templates : { templates: {
// get() - Queries the API for GET /templates // get() - Queries the API for GET /templates
get: function(){ get: function() {
return query("/templates/", "GET", {}) return query("/templates/", "GET", {})
}, },
// post() - Posts a campaign to POST /templates // post() - Posts a campaign to POST /templates
post: function(template){ post: function(template) {
return query("/templates/", "POST", template) return query("/templates/", "POST", template)
} }
}, },
// templateId contains the endpoints for /templates/:id // templateId contains the endpoints for /templates/:id
templateId : { templateId: {
// get() - Queries the API for GET /templates/:id // get() - Queries the API for GET /templates/:id
get: function(id){ get: function(id) {
return query("/templates/" + id, "GET", {}) return query("/templates/" + id, "GET", {})
}, },
// put() - Puts a campaign to PUT /templates/:id // put() - Puts a campaign to PUT /templates/:id
put: function (template){ put: function(template) {
return query("/templates/" + template.id, "PUT", template) return query("/templates/" + template.id, "PUT", template)
}, },
// delete() - Deletes a campaign at DELETE /templates/:id // delete() - Deletes a campaign at DELETE /templates/:id
delete: function(id){ delete: function(id) {
return query("/templates/" + id, "DELETE", {}) return query("/templates/" + id, "DELETE", {})
} }
}, },
// pages contains the endpoints for /pages // pages contains the endpoints for /pages
pages : { pages: {
// get() - Queries the API for GET /pages // get() - Queries the API for GET /pages
get: function(){ get: function() {
return query("/pages/", "GET", {}) return query("/pages/", "GET", {})
}, },
// post() - Posts a campaign to POST /pages // post() - Posts a campaign to POST /pages
post: function(page){ post: function(page) {
return query("/pages/", "POST", page) return query("/pages/", "POST", page)
} }
}, },
// templateId contains the endpoints for /templates/:id // templateId contains the endpoints for /templates/:id
pageId : { pageId: {
// get() - Queries the API for GET /templates/:id // get() - Queries the API for GET /templates/:id
get: function(id){ get: function(id) {
return query("/pages/" + id, "GET", {}) return query("/pages/" + id, "GET", {})
}, },
// put() - Puts a campaign to PUT /templates/:id // put() - Puts a campaign to PUT /templates/:id
put: function (page){ put: function(page) {
return query("/pages/" + page.id, "PUT", page) return query("/pages/" + page.id, "PUT", page)
}, },
// delete() - Deletes a campaign at DELETE /templates/:id // delete() - Deletes a campaign at DELETE /templates/:id
delete: function(id){ delete: function(id) {
return query("/pages/" + id, "DELETE", {}) return query("/pages/" + id, "DELETE", {})
} }
}, },
// import handles all of the "import" functions in the api // import handles all of the "import" functions in the api
import_email : function(raw) { import_email: function(raw) {
return query("/import/email", "POST", {}) return query("/import/email", "POST", {})
}, },
clone_site : function(req){ clone_site: function(req) {
return query("/import/site", "POST", req) return query("/import/site", "POST", req)
} }
} }
// Register our moment.js datatables listeners // Register our moment.js datatables listeners
$(document).ready(function(){ $(document).ready(function() {
$.fn.dataTable.moment('MMMM Do YYYY, h:mm:ss a'); $.fn.dataTable.moment('MMMM Do YYYY, h:mm:ss a');
// Setup tooltips // Setup tooltips
$('[data-toggle="tooltip"]').tooltip() $('[data-toggle="tooltip"]').tooltip()
}); });

6
static/js/papaparse.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -30,22 +30,11 @@
<h1 class="page-header" id="page-title">Results for campaign.name</h1> <h1 class="page-header" id="page-title">Results for campaign.name</h1>
</div> </div>
<div class="row"> <div class="row">
<!-- <button type="button" id="exportButton" class="btn btn-primary" onclick="exportAsCSV()">
<div class="btn-group"> <i class="fa fa-file-excel-o"></i> Export CSV
<button type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown"> </button>
<i class="fa fa-cogs fa-lg"></i> <button type="button" class="btn btn-danger" data-toggle="tooltip" onclick="deleteCampaign()">
<span class="caret"></span> <i class="fa fa-trash-o fa-lg"></i> Delete
</button>
<ul class="dropdown-menu">
<li><a href="#">Export</a>
</li>
<li><a href="#">Relaunch</a>
</li>
</ul>
</div>
-->
<button type="button" class="btn btn-danger" data-toggle="tooltip" data-placement="right" title="Delete Campaign" onclick="deleteCampaign()">
<i class="fa fa-times fa-lg"></i>
</button> </button>
</div> </div>
<br /> <br />
@ -114,5 +103,6 @@
<script src="/js/d3.min.js"></script> <script src="/js/d3.min.js"></script>
<script src="/js/topojson.min.js"></script> <script src="/js/topojson.min.js"></script>
<script src="/js/datamaps.min.js"></script> <script src="/js/datamaps.min.js"></script>
<script src="/js/papaparse.min.js"></script>
<script src="/js/app/campaign_results.js"></script> <script src="/js/app/campaign_results.js"></script>
{{end}} {{end}}

View File

@ -47,7 +47,7 @@
<th>Name</th> <th>Name</th>
<th>Created Date</th> <th>Created Date</th>
<th>Status</th> <th>Status</th>
<th class="col-md-2"></th> <th class="col-md-2 no-sort"></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -113,7 +113,7 @@
<table id="groupTable" class="table table-hover table-striped table-condensed"> <table id="groupTable" class="table table-hover table-striped table-condensed">
<thead> <thead>
<th>Group Name</th> <th>Group Name</th>
<th></th> <th class="no-sort"></th>
<tbody> <tbody>
</tbody> </tbody>
</table> </table>
@ -121,7 +121,7 @@
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button> <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary" onclick="save()">Save changes</button> <button type="button" class="btn btn-primary" onclick="launch()"><i class="fa fa-envelope"></i> Launch Campaign</button>
</div> </div>
</div> </div>
</div> </div>

View File

@ -67,7 +67,7 @@
<th>Name</th> <th>Name</th>
<th>Created Date</th> <th>Created Date</th>
<th>Status</th> <th>Status</th>
<th class="col-md-2 col-sm-2"></th> <th class="col-md-2 col-sm-2 no-sort"></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>

View File

@ -44,7 +44,7 @@
<tr> <tr>
<th>Name</th> <th>Name</th>
<th>Last Modified Date</th> <th>Last Modified Date</th>
<th class="col-md-2"></th> <th class="col-md-2 no-sort"></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>

View File

@ -46,7 +46,7 @@
<tr> <tr>
<th>Name</th> <th>Name</th>
<th>Modified Date</th> <th>Modified Date</th>
<th class="col-md-2"></th> <th class="col-md-2 no-sort"></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -102,11 +102,11 @@
<table id="attachmentsTable" class="table"> <table id="attachmentsTable" class="table">
<thead> <thead>
<tr> <tr>
<th class="col-md-1"></th> <th class="col-md-1 no-sort"></th>
<th class="col-md-10">Name</th> <th class="col-md-10">Name</th>
<th class="col-md-1"></th> <th class="col-md-1 no-sort"></th>
<th class="datatable_hidden">Content</th> <th class="datatable_hidden no-sort">Content</th>
<th class="datatable_hidden">Type</th> <th class="datatable_hidden no-sort">Type</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>

View File

@ -47,7 +47,7 @@
<th>Name</th> <th>Name</th>
<th>Members</th> <th>Members</th>
<th>Modified Date</th> <th>Modified Date</th>
<th class="col-md-2"></th> <th class="col-md-2 no-sort"></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -94,20 +94,20 @@
</form> </form>
</div> </div>
<br /> <br />
<table id="targetsTable" class="table table-hover table-striped"> <table id="targetsTable" class="table table-hover table-striped table-condensed">
<thead> <thead>
<tr> <tr>
<th>First Name</th> <th>First Name</th>
<th>Last Name</th> <th>Last Name</th>
<th>Email</th> <th>Email</th>
<th>Position</th> <th>Position</th>
<th></th> <th class="no-sort"></th>
<tbody> <tbody>
</tbody> </tbody>
</table> </table>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-default" onclick="dismiss()">Close</button> <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary" id="modalSubmit">Save changes</button> <button type="button" class="btn btn-primary" id="modalSubmit">Save changes</button>
</div> </div>
</div> </div>

28
util/doc.go Normal file
View File

@ -0,0 +1,28 @@
/*
gophish - Open-Source Phishing Framework
The MIT License (MIT)
Copyright (c) 2013 Jordan Wright
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
// Package util provides misc. utility functions for gophish
package util

28
worker/doc.go Normal file
View File

@ -0,0 +1,28 @@
/*
gophish - Open-Source Phishing Framework
The MIT License (MIT)
Copyright (c) 2013 Jordan Wright
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
// Package worker contains the functionality for the background worker process.
package worker

View File

@ -9,8 +9,8 @@ import (
"strings" "strings"
"text/template" "text/template"
"github.com/jordan-wright/email"
"github.com/gophish/gophish/models" "github.com/gophish/gophish/models"
"github.com/jordan-wright/email"
) )
// Logger is the logger for the worker // Logger is the logger for the worker
@ -119,6 +119,10 @@ func processCampaign(c *models.Campaign) {
if err != nil { if err != nil {
Logger.Println(err) Logger.Println(err)
} }
err = c.AddEvent(models.Event{Email: t.Email, Message: models.EVENT_SENT})
if err != nil {
Logger.Println(err)
}
} }
} }
err = c.UpdateStatus(models.CAMPAIGN_EMAILS_SENT) err = c.UpdateStatus(models.CAMPAIGN_EMAILS_SENT)