From 89e72dd78d996792dd23a12b391ed2a744e39b31 Mon Sep 17 00:00:00 2001 From: Jordan Wright Date: Thu, 14 Jan 2016 22:46:43 -0600 Subject: [PATCH 01/24] Added error handling on startup of web servers. Fixes #70 --- gophish.go | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/gophish.go b/gophish.go index 81693d48..d5d49ddc 100644 --- a/gophish.go +++ b/gophish.go @@ -30,11 +30,12 @@ import ( "log" "net/http" "os" + "sync" - "github.com/gorilla/handlers" "github.com/gophish/gophish/config" "github.com/gophish/gophish/controllers" "github.com/gophish/gophish/models" + "github.com/gorilla/handlers" ) var Logger = log.New(os.Stdout, " ", log.Ldate|log.Ltime|log.Lshortfile) @@ -45,9 +46,19 @@ func main() { if err != nil { fmt.Println(err) } + wg := &sync.WaitGroup{} + wg.Add(1) // Start the web servers - Logger.Printf("Admin server started at http://%s\n", config.Conf.AdminURL) - go http.ListenAndServe(config.Conf.AdminURL, handlers.CombinedLoggingHandler(os.Stdout, controllers.CreateAdminRouter())) - Logger.Printf("Phishing server started at http://%s\n", config.Conf.PhishURL) - http.ListenAndServe(config.Conf.PhishURL, handlers.CombinedLoggingHandler(os.Stdout, controllers.CreatePhishingRouter())) + go func() { + defer wg.Done() + Logger.Printf("Starting admin server at http://%s\n", config.Conf.AdminURL) + Logger.Fatal(http.ListenAndServe(config.Conf.AdminURL, handlers.CombinedLoggingHandler(os.Stdout, controllers.CreateAdminRouter()))) + }() + wg.Add(1) + go func() { + defer wg.Done() + Logger.Printf("Starting phishing server at http://%s\n", config.Conf.PhishURL) + Logger.Fatal(http.ListenAndServe(config.Conf.PhishURL, handlers.CombinedLoggingHandler(os.Stdout, controllers.CreatePhishingRouter()))) + }() + wg.Wait() } From 86e0255ae6ef8fdd5d5082c4c96e0ad93ec3df7e Mon Sep 17 00:00:00 2001 From: Jordan Wright Date: Thu, 14 Jan 2016 22:50:09 -0600 Subject: [PATCH 02/24] Adding event for each email that's sent - can keep track of each target better this way. --- worker/worker.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/worker/worker.go b/worker/worker.go index 568b198b..478fb12f 100644 --- a/worker/worker.go +++ b/worker/worker.go @@ -9,8 +9,8 @@ import ( "strings" "text/template" - "github.com/jordan-wright/email" "github.com/gophish/gophish/models" + "github.com/jordan-wright/email" ) // Logger is the logger for the worker @@ -119,6 +119,10 @@ func processCampaign(c *models.Campaign) { if err != nil { 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) From 65005a28053168a5c57aab75776c52ea3ee30eb1 Mon Sep 17 00:00:00 2001 From: Jordan Wright Date: Sat, 16 Jan 2016 19:24:13 -0600 Subject: [PATCH 03/24] Moved modal closing to hide.bs.modal - Fixes #71 --- static/js/app/users.js | 6 +++++- templates/users.html | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/static/js/app/users.js b/static/js/app/users.js index feb0e9ac..10f310f1 100644 --- a/static/js/app/users.js +++ b/static/js/app/users.js @@ -25,6 +25,7 @@ function save(idx){ successFlash("Group updated successfully!") load() dismiss() + $("#modal").modal('hide') }) .error(function(data){ modalError(data.responseJSON.message) @@ -37,6 +38,7 @@ function save(idx){ successFlash("Group added successfully!") load() dismiss() + $("#modal").modal('hide') }) .error(function(data){ modalError(data.responseJSON.message) @@ -48,7 +50,6 @@ function dismiss(){ $("#targetsTable").dataTable().DataTable().clear().draw() $("#name").val("") $("#modal\\.flashes").empty() - $("#modal").modal('hide') } function edit(idx){ @@ -176,4 +177,7 @@ $(document).ready(function(){ .remove() .draw(); }) + $("#modal").on("hide.bs.modal", function(){ + dismiss() + }) }) diff --git a/templates/users.html b/templates/users.html index 4ee56f64..da1b731e 100644 --- a/templates/users.html +++ b/templates/users.html @@ -107,7 +107,7 @@ From e78ec27ef2ebd21215dcf035b6e1bbaa855d7a42 Mon Sep 17 00:00:00 2001 From: Jordan Wright Date: Sat, 16 Jan 2016 22:34:02 -0600 Subject: [PATCH 04/24] Adding package documentation for util and worker packages --- util/doc.go | 28 ++++++++++++++++++++++++++++ worker/doc.go | 28 ++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 util/doc.go create mode 100644 worker/doc.go diff --git a/util/doc.go b/util/doc.go new file mode 100644 index 00000000..b68e3aec --- /dev/null +++ b/util/doc.go @@ -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 diff --git a/worker/doc.go b/worker/doc.go new file mode 100644 index 00000000..dbc961b7 --- /dev/null +++ b/worker/doc.go @@ -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 From 2dda83814c2115f7f06ce88d68b268ed0c04fb8f Mon Sep 17 00:00:00 2001 From: Jordan Wright Date: Sat, 16 Jan 2016 22:59:40 -0600 Subject: [PATCH 05/24] Formatting Javascript using js-beautifier --- static/js/app/campaign_results.js | 425 +++++++++++++++--------------- static/js/app/campaigns.js | 326 ++++++++++++----------- static/js/app/dashboard.js | 242 +++++++++-------- static/js/app/landing_pages.js | 187 ++++++------- static/js/app/settings.js | 34 +-- static/js/app/templates.js | 273 +++++++++---------- static/js/app/users.js | 232 ++++++++-------- static/js/gophish.js | 80 +++--- 8 files changed, 924 insertions(+), 875 deletions(-) diff --git a/static/js/app/campaign_results.js b/static/js/app/campaign_results.js index 8574f619..002f0a57 100644 --- a/static/js/app/campaign_results.js +++ b/static/js/app/campaign_results.js @@ -2,254 +2,267 @@ var map = null // statuses is a helper map to point result statuses to ui classes var statuses = { - "Email Sent" : { + "Email Sent": { slice: "ct-slice-donut-sent", legend: "ct-legend-sent", label: "label-success" }, - "Email Opened" : { + "Email Opened": { slice: "ct-slice-donut-opened", legend: "ct-legend-opened", label: "label-warning" }, - "Clicked Link" : { + "Clicked Link": { slice: "ct-slice-donut-clicked", legend: "ct-legend-clicked", label: "label-danger" }, - "Success" : { + "Success": { slice: "ct-slice-donut-clicked", legend: "ct-legend-clicked", label: "label-danger" }, - "Error" : { + "Error": { slice: "ct-slice-donut-error", legend: "ct-legend-error", label: "label-default" }, - "Unknown" : { - slice: "ct-slice-donut-error", - legend: "ct-legend-error", - label: "label-default" + "Unknown": { + slice: "ct-slice-donut-error", + legend: "ct-legend-error", + label: "label-default" } } var campaign = {} -function dismiss(){ +function dismiss() { $("#modal\\.flashes").empty() $("#modal").modal('hide') $("#resultsTable").dataTable().DataTable().clear().draw() } // Deletes a campaign after prompting the user -function deleteCampaign(){ - if (confirm("Are you sure you want to delete: " + campaign.name + "?")){ +function deleteCampaign() { + if (confirm("Are you sure you want to delete: " + campaign.name + "?")) { api.campaignId.delete(campaign.id) - .success(function(msg){ - console.log(msg) - }) - .error(function(e){ - $("#modal\\.flashes").empty().append("
\ + .success(function(msg) { + console.log(msg) + }) + .error(function(e) { + $("#modal\\.flashes").empty().append("
\ " + data.responseJSON.message + "
") - }) + }) } } -$(document).ready(function(){ +$(document).ready(function() { campaign.id = window.location.pathname.split('/').slice(-1)[0] api.campaignId.get(campaign.id) - .success(function(c){ - campaign = c - if (campaign){ - // Set the title - $("#page-title").text("Results for " + c.name) - // Setup tooltips - $('[data-toggle="tooltip"]').tooltip() - // Setup our graphs - var timeline_data = {series:[{ - name: "Events", - data: [] - }]} - var email_data = {series:[]} - var email_legend = {} - var email_series_data = {} - var timeline_opts = { - axisX: { - showGrid: false, - type: Chartist.FixedScaleAxis, - divisor: 5, - labelInterpolationFnc: function(value){ - return moment(value).format('MMMM Do YYYY h:mm') - } - }, - 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 || "", - "" + result.status + "" - ]).draw() - if (!email_series_data[result.status]){ - email_series_data[result.status] = 1 - } else { - email_series_data[result.status]++; + .success(function(c) { + campaign = c + if (campaign) { + // Set the title + $("#page-title").text("Results for " + c.name) + // Setup tooltips + $('[data-toggle="tooltip"]').tooltip() + // Setup our graphs + var timeline_data = { + series: [{ + name: "Events", + data: [] + }] } - }) - // 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('
') - .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 += '
' + "Email: " + campaign.timeline[cidx].email + var email_data = { + series: [] } - $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('
  • ' + data.meta + '
  • ') - email_legend[data.meta] = true - } - data.element.addClass(statuses[data.meta].slice) - }) - // Setup the average chart listeners - $piechart = $("#email_chart") - var $pietoolTip = $piechart - .append('
    ') - .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" + 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 || "", + "" + result.status + "" + ]).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('
    ') + .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 += '
    ' + "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('
  • ' + data.meta + '
  • ') + email_legend[data.meta] = true + } + data.element.addClass(statuses[data.meta].slice) + }) + // Setup the average chart listeners + $piechart = $("#email_chart") + var $pietoolTip = $piechart + .append('
    ') + .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!") - }) }) diff --git a/static/js/app/campaigns.js b/static/js/app/campaigns.js index 4c3a1045..c3a64ee0 100644 --- a/static/js/app/campaigns.js +++ b/static/js/app/campaigns.js @@ -1,212 +1,226 @@ // labels is a map of campaign statuses to // CSS classes var labels = { - "In progress" : "label-primary", - "Queued" : "label-info", - "Completed" : "label-success", - "Emails Sent" : "label-success", - "Error" : "label-danger" + "In progress": "label-primary", + "Queued": "label-info", + "Completed": "label-success", + "Emails Sent": "label-success", + "Error": "label-danger" } // Save attempts to POST to /campaigns/ -function save(){ +function save() { groups = [] - $.each($("#groupTable").DataTable().rows().data(), function(i, group){ - groups.push({name: group[0]}) - }) + $.each($("#groupTable").DataTable().rows().data(), function(i, group) { + 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 + 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) - .success(function(data){ - successFlash("Campaign successfully launched!") - window.location = "/campaigns/" + campaign.id.toString() - }) - .error(function(data){ - $("#modal\\.flashes").empty().append("
    \ + .success(function(data) { + successFlash("Campaign successfully launched!") + window.location = "/campaigns/" + campaign.id.toString() + }) + .error(function(data) { + $("#modal\\.flashes").empty().append("
    \ " + data.responseJSON.message + "
    ") - }) + }) } -function dismiss(){ +function dismiss() { $("#modal\\.flashes").empty() $("#modal").modal('hide') $("#groupTable").dataTable().DataTable().clear().draw() } -function edit(campaign){ +function edit(campaign) { // Clear the bloodhound instance group_bh.clear(); template_bh.clear(); page_bh.clear(); if (campaign == "new") { api.groups.get() - .success(function(groups){ - if (groups.length == 0){ - modalError("No groups found!") - return false; - } - else { - group_bh.add(groups) - } - }) - api.templates.get() - .success(function(templates){ - if (templates.length == 0){ - modalError("No templates found!") - return false - } - else { - template_bh.add(templates) - } - }) - api.pages.get() - .success(function(pages){ - if (pages.length == 0){ - modalError("No pages found!") - return false - } - else { - page_bh.add(pages) - } - }) + .success(function(groups) { + if (groups.length == 0) { + modalError("No groups found!") + return false; + } else { + group_bh.add(groups) + } + }) + api.templates.get() + .success(function(templates) { + if (templates.length == 0) { + modalError("No templates found!") + return false + } else { + template_bh.add(templates) + } + }) + api.pages.get() + .success(function(pages) { + if (pages.length == 0) { + modalError("No pages found!") + return false + } else { + page_bh.add(pages) + } + }) } } -$(document).ready(function(){ +$(document).ready(function() { api.campaigns.get() - .success(function(campaigns){ - $("#loading").hide() - if (campaigns.length > 0){ - $("#campaignTable").show() - 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'), - "" + campaign.status + "", - "
    \ + .success(function(campaigns) { + $("#loading").hide() + if (campaigns.length > 0) { + $("#campaignTable").show() + 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'), + "" + campaign.status + "", + "
    \ \ \
    " - ]).draw() - }) - } else { - $("#emptyMessage").show() - } - }) - .error(function(){ - $("#loading").hide() - errorFlash("Error fetching campaigns") - }) - $("#groupForm").submit(function(){ - groupTable.row.add([ - $("#groupSelect").val(), - '' - ]).draw() - $("#groupTable").on("click", "span>i.fa-trash-o", function(){ - groupTable.row( $(this).parents('tr') ) - .remove() - .draw(); + ]).draw() + }) + } else { + $("#emptyMessage").show() + } }) - return false; - }) - // Create the group typeahead objects + .error(function() { + $("#loading").hide() + errorFlash("Error fetching campaigns") + }) + $("#groupForm").submit(function() { + groupTable.row.add([ + $("#groupSelect").val(), + '' + ]).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() 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, local: [] }) group_bh.initialize() $("#groupSelect.typeahead.form-control").typeahead({ - hint: true, - highlight: true, - minLength: 1 - }, - { - name: "groups", - source: group_bh, - templates: { - empty: function(data) {return '
    No groups matched that query
    ' }, - suggestion: function(data){ return '
    ' + data.name + '
    ' } - } - }) - .bind('typeahead:select', function(ev, group){ - $("#groupSelect").typeahead('val', group.name) - }) - .bind('typeahead:autocomplete', function(ev, group){ - $("#groupSelect").typeahead('val', group.name) - }); + hint: true, + highlight: true, + minLength: 1 + }, { + name: "groups", + source: group_bh, + templates: { + empty: function(data) { + return '
    No groups matched that query
    ' + }, + suggestion: function(data) { + return '
    ' + data.name + '
    ' + } + } + }) + .bind('typeahead:select', function(ev, group) { + $("#groupSelect").typeahead('val', group.name) + }) + .bind('typeahead:autocomplete', function(ev, group) { + $("#groupSelect").typeahead('val', group.name) + }); // Create the template typeahead objects 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, local: [] }) template_bh.initialize() $("#template.typeahead.form-control").typeahead({ - hint: true, - highlight: true, - minLength: 1 - }, - { - name: "templates", - source: template_bh, - templates: { - empty: function(data) {return '
    No templates matched that query
    ' }, - suggestion: function(data){ return '
    ' + data.name + '
    ' } - } - }) - .bind('typeahead:select', function(ev, template){ - $("#template").typeahead('val', template.name) - }) - .bind('typeahead:autocomplete', function(ev, template){ - $("#template").typeahead('val', template.name) - }); + hint: true, + highlight: true, + minLength: 1 + }, { + name: "templates", + source: template_bh, + templates: { + empty: function(data) { + return '
    No templates matched that query
    ' + }, + suggestion: function(data) { + return '
    ' + data.name + '
    ' + } + } + }) + .bind('typeahead:select', function(ev, template) { + $("#template").typeahead('val', template.name) + }) + .bind('typeahead:autocomplete', function(ev, template) { + $("#template").typeahead('val', template.name) + }); // Create the landing page typeahead objects 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, local: [] }) page_bh.initialize() $("#page.typeahead.form-control").typeahead({ - hint: true, - highlight: true, - minLength: 1 - }, - { - name: "pages", - source: page_bh, - templates: { - empty: function(data) {return '
    No pages matched that query
    ' }, - suggestion: function(data){ return '
    ' + data.name + '
    ' } - } - }) - .bind('typeahead:select', function(ev, page){ - $("#page").typeahead('val', page.name) - }) - .bind('typeahead:autocomplete', function(ev, page){ - $("#page").typeahead('val', page.name) - }); + hint: true, + highlight: true, + minLength: 1 + }, { + name: "pages", + source: page_bh, + templates: { + empty: function(data) { + return '
    No pages matched that query
    ' + }, + suggestion: function(data) { + return '
    ' + data.name + '
    ' + } + } + }) + .bind('typeahead:select', function(ev, page) { + $("#page").typeahead('val', page.name) + }) + .bind('typeahead:autocomplete', function(ev, page) { + $("#page").typeahead('val', page.name) + }); }) diff --git a/static/js/app/dashboard.js b/static/js/app/dashboard.js index c8445b9f..23ed7752 100644 --- a/static/js/app/dashboard.js +++ b/static/js/app/dashboard.js @@ -1,129 +1,145 @@ var campaigns = [] -// labels is a map of campaign statuses to -// CSS classes + // labels is a map of campaign statuses to + // CSS classes var labels = { - "In progress" : "label-primary", - "Queued" : "label-info", - "Completed" : "label-success", - "Emails Sent" : "label-success", - "Error" : "label-danger" + "In progress": "label-primary", + "Queued": "label-info", + "Completed": "label-success", + "Emails Sent": "label-success", + "Error": "label-danger" } -$(document).ready(function(){ +$(document).ready(function() { api.campaigns.get() - .success(function(cs){ - $("#loading").hide() - campaigns = cs - if (campaigns.length > 0){ - $("#dashboard").show() - // Create the overview chart data - var overview_data = {labels:[],series:[[]]} - var average_data = {series:[]} - var overview_opts = { - axisX: { - showGrid: false - }, - showArea: true, - plugins: [] - } - var average_opts = { - donut : true, - donutWidth: 40, - chartPadding: 0, - showLabel: false - } - var average = 0 - campaignTable = $("#campaignTable").DataTable(); - $.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, - "" + campaign.status + "", - "
    \ + .success(function(cs) { + $("#loading").hide() + campaigns = cs + if (campaigns.length > 0) { + $("#dashboard").show() + // Create the overview chart data + var overview_data = { + labels: [], + series: [ + [] + ] + } + var average_data = { + series: [] + } + var overview_opts = { + axisX: { + showGrid: false + }, + showArea: true, + plugins: [] + } + var average_opts = { + donut: true, + donutWidth: 40, + chartPadding: 0, + showLabel: false + } + var average = 0 + campaignTable = $("#campaignTable").DataTable(); + $.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, + "" + campaign.status + "", + "
    \ \ \
    " - ]).draw() - // Add it to the chart data - campaign.y = 0 - $.each(campaign.results, function(j, result){ - if (result.status == "Success"){ - campaign.y++; - } + ]).draw() + // Add it to the chart data + campaign.y = 0 + $.each(campaign.results, function(j, result) { + if (result.status == "Success") { + 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 += 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}) - }) - average = Math.floor(average / campaigns.length); - average_data.series.push({meta: "Unsuccessful Phishes", value: 100 - average}) - average_data.series.push({meta: "Successful Phishes", value: average}) - // Build the charts - var average_chart = new Chartist.Pie("#average_chart", average_data, average_opts) - var overview_chart = new Chartist.Line('#overview_chart', overview_data, overview_opts) - // Setup the average chart listeners - $piechart = $("#average_chart") - var $pietoolTip = $piechart - .append('
    ') - .find('.chartist-tooltip') - .hide(); + average = Math.floor(average / campaigns.length); + average_data.series.push({ + meta: "Unsuccessful Phishes", + value: 100 - average + }) + average_data.series.push({ + meta: "Successful Phishes", + value: average + }) + // Build the charts + var average_chart = new Chartist.Pie("#average_chart", average_data, average_opts) + var overview_chart = new Chartist.Line('#overview_chart', overview_data, overview_opts) + // Setup the average chart listeners + $piechart = $("#average_chart") + var $pietoolTip = $piechart + .append('
    ') + .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 + $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(); }); - }); - // Setup the overview chart listeners - $chart = $("#overview_chart") - var $toolTip = $chart - .append('
    ') - .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 + '
    ' + "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('mouseleave', '.ct-slice-donut', function() { + $pietoolTip.hide(); }); - }); - $("#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") - }) + $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 + $chart = $("#overview_chart") + var $toolTip = $chart + .append('
    ') + .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 + '
    ' + "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") + }) }) diff --git a/static/js/app/landing_pages.js b/static/js/app/landing_pages.js index dd7b30cb..c2df241e 100644 --- a/static/js/app/landing_pages.js +++ b/static/js/app/landing_pages.js @@ -4,72 +4,75 @@ Author: Jordan Wright */ var pages = [] + // Save attempts to POST to /templates/ -function save(idx){ +function save(idx) { var page = {} page.name = $("#name").val() page.html = CKEDITOR.instances["html_editor"].getData(); - if (idx != -1){ + if (idx != -1) { page.id = pages[idx].id api.pageId.put(page) - .success(function(data){ - successFlash("Page edited successfully!") - load() - dismiss() - }) + .success(function(data) { + successFlash("Page edited successfully!") + load() + dismiss() + }) } else { // Submit the page api.pages.post(page) - .success(function(data){ - successFlash("Page added successfully!") - load() - dismiss() - }) - .error(function(data){ - modalError(data.responseJSON.message) - }) + .success(function(data) { + successFlash("Page added successfully!") + load() + dismiss() + }) + .error(function(data) { + modalError(data.responseJSON.message) + }) } } -function dismiss(){ +function dismiss() { $("#modal\\.flashes").empty() $("#name").val("") $("#html_editor").val("") $("#newLandingPageModal").modal('hide') } -function deletePage(idx){ - if (confirm("Delete " + pages[idx].name + "?")){ +function deletePage(idx) { + if (confirm("Delete " + pages[idx].name + "?")) { api.pageId.delete(pages[idx].id) - .success(function(data){ - successFlash(data.message) - load() - }) + .success(function(data) { + successFlash(data.message) + load() + }) } } -function importSite(){ +function importSite() { url = $("#url").val() - if (!url){ + if (!url) { modalError("No URL Specified!") } else { api.clone_site({ - url: url, - include_resources: false - }) - .success(function(data){ - console.log($("#html_editor")) - $("#html_editor").val(data.html) - $("#importSiteModal").modal("hide") - }) - .error(function(data){ - modalError(data.responseJSON.message) - }) + url: url, + include_resources: false + }) + .success(function(data) { + console.log($("#html_editor")) + $("#html_editor").val(data.html) + $("#importSiteModal").modal("hide") + }) + .error(function(data) { + modalError(data.responseJSON.message) + }) } } -function edit(idx){ - $("#modalSubmit").unbind('click').click(function(){save(idx)}) +function edit(idx) { + $("#modalSubmit").unbind('click').click(function() { + save(idx) + }) $("#html_editor").ckeditor() var page = {} if (idx != -1) { @@ -79,82 +82,80 @@ function edit(idx){ } } -function load(){ -/* - load() - Loads the current pages using the API -*/ +function load() { + /* + load() - Loads the current pages using the API + */ $("#pagesTable").hide() $("#emptyMessage").hide() $("#loading").show() api.pages.get() - .success(function(ps){ - pages = ps - $("#loading").hide() - if (pages.length > 0){ - $("#pagesTable").show() - pagesTable = $("#pagesTable").DataTable(); - pagesTable.clear() - $.each(pages, function(i, page){ - pagesTable.row.add([ - page.name, - moment(page.modified_date).format('MMMM Do YYYY, h:mm:ss a'), - "
    \
    " - ]).draw() - }) - } else { - $("#emptyMessage").show() - } - }) - .error(function(){ - $("#loading").hide() - errorFlash("Error fetching pages") - }) + ]).draw() + }) + } else { + $("#emptyMessage").show() + } + }) + .error(function() { + $("#loading").hide() + errorFlash("Error fetching pages") + }) } -$(document).ready(function(){ +$(document).ready(function() { // Setup multiple modals // Code based on http://miles-by-motorcycle.com/static/bootstrap-modal/index.html - $('.modal').on('hidden.bs.modal', function( event ) { - $(this).removeClass( 'fv-modal-stack' ); - $('body').data( 'fv_open_modals', $('body').data( 'fv_open_modals' ) - 1 ); + $('.modal').on('hidden.bs.modal', function(event) { + $(this).removeClass('fv-modal-stack'); + $('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 - if ( typeof( $('body').data( 'fv_open_modals' ) ) == 'undefined' ) - { - $('body').data( 'fv_open_modals', 0 ); + if (typeof($('body').data('fv_open_modals')) == 'undefined') { + $('body').data('fv_open_modals', 0); } // if the z-index of this modal has been set, ignore. - if ( $(this).hasClass( 'fv-modal-stack' ) ) - { + if ($(this).hasClass('fv-modal-stack')) { return; } - $(this).addClass( 'fv-modal-stack' ); - // Increment the number of open modals - $('body').data( 'fv_open_modals', $('body').data( 'fv_open_modals' ) + 1 ); - // Setup the appropriate z-index - $(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' ).addClass( 'fv-modal-stack' ); + $(this).addClass('fv-modal-stack'); + // Increment the number of open modals + $('body').data('fv_open_modals', $('body').data('fv_open_modals') + 1); + // Setup the appropriate z-index + $(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').addClass('fv-modal-stack'); }); $.fn.modal.Constructor.prototype.enforceFocus = function() { - $( document ) - .off( 'focusin.bs.modal' ) // guard against infinite focus loop - .on( 'focusin.bs.modal', $.proxy( function( e ) { - if ( - this.$element[ 0 ] !== e.target && !this.$element.has( e.target ).length - // CKEditor compatibility fix start. - && !$( e.target ).closest( '.cke_dialog, .cke' ).length - // CKEditor compatibility fix end. - ) { - this.$element.trigger( 'focus' ); - } - }, this ) ); - }; + $(document) + .off('focusin.bs.modal') // guard against infinite focus loop + .on('focusin.bs.modal', $.proxy(function(e) { + if ( + this.$element[0] !== e.target && !this.$element.has(e.target).length + // CKEditor compatibility fix start. + && !$(e.target).closest('.cke_dialog, .cke').length + // CKEditor compatibility fix end. + ) { + this.$element.trigger('focus'); + } + }, this)); + }; load() }) diff --git a/static/js/app/settings.js b/static/js/app/settings.js index 36fd712a..87e6e98b 100644 --- a/static/js/app/settings.js +++ b/static/js/app/settings.js @@ -1,24 +1,24 @@ -$(document).ready(function(){ - $("#apiResetForm").submit(function(e){ +$(document).ready(function() { + $("#apiResetForm").submit(function(e) { $.post("/api/reset", $(this).serialize()) - .done(function(data){ - api_key = data.data - successFlash(data.message) - $("#api_key").val(api_key) - }) - .fail(function(data){ - errorFlash(data.message) - }) + .done(function(data) { + api_key = data.data + successFlash(data.message) + $("#api_key").val(api_key) + }) + .fail(function(data) { + errorFlash(data.message) + }) return false }) - $("#settingsForm").submit(function(e){ + $("#settingsForm").submit(function(e) { $.post("/settings", $(this).serialize()) - .done(function(data){ - successFlash(data.message) - }) - .fail(function(data){ - errorFlash(data.responseJSON.message) - }) + .done(function(data) { + successFlash(data.message) + }) + .fail(function(data) { + errorFlash(data.responseJSON.message) + }) return false }) }) diff --git a/static/js/app/templates.js b/static/js/app/templates.js index 4da16ac1..6f487e73 100644 --- a/static/js/app/templates.js +++ b/static/js/app/templates.js @@ -1,64 +1,66 @@ var templates = [] var icons = { - "application/vnd.ms-excel" : "fa-file-excel-o", - "text/plain" : "fa-file-text-o", - "image/gif" : "fa-file-image-o", - "image/png" : "fa-file-image-o", - "application/pdf" : "fa-file-pdf-o", - "application/x-zip-compressed" : "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.wordprocessingml.document" : "fa-file-word-o", - "application/octet-stream" : "fa-file-o", - "application/x-msdownload" : "fa-file-o" + "application/vnd.ms-excel": "fa-file-excel-o", + "text/plain": "fa-file-text-o", + "image/gif": "fa-file-image-o", + "image/png": "fa-file-image-o", + "application/pdf": "fa-file-pdf-o", + "application/x-zip-compressed": "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.wordprocessingml.document": "fa-file-word-o", + "application/octet-stream": "fa-file-o", + "application/x-msdownload": "fa-file-o" } // Save attempts to POST to /templates/ -function save(idx){ - var template = {attachments:[]} +function save(idx) { + var template = { + attachments: [] + } template.name = $("#name").val() template.subject = $("#subject").val() template.html = CKEDITOR.instances["html_editor"].getData(); // Fix the URL Scheme added by CKEditor (until we can remove it from the plugin) template.html = template.html.replace(/https?:\/\/{{\.URL}}/gi, "{{.URL}}") - // If the "Add Tracker Image" checkbox is checked, add the tracker - if ($("#use_tracker_checkbox").prop("checked") && - template.html.indexOf("{{.Tracker}}") == -1 && - template.html.indexOf("{{.TrackingUrl}}") == -1){ - template.html = template.html.replace("", "{{.Tracker}}") + // If the "Add Tracker Image" checkbox is checked, add the tracker + if ($("#use_tracker_checkbox").prop("checked") && + template.html.indexOf("{{.Tracker}}") == -1 && + template.html.indexOf("{{.TrackingUrl}}") == -1) { + template.html = template.html.replace("", "{{.Tracker}}") } template.text = $("#text_editor").val() - // Add the attachments - $.each($("#attachmentsTable").DataTable().rows().data(), function(i, target){ + // Add the attachments + $.each($("#attachmentsTable").DataTable().rows().data(), function(i, target) { template.attachments.push({ - name : target[1], + name: target[1], content: target[3], type: target[4], }) }) - if (idx != -1){ + if (idx != -1) { template.id = templates[idx].id api.templateId.put(template) - .success(function(data){ - successFlash("Template edited successfully!") - load() - dismiss() - }) + .success(function(data) { + successFlash("Template edited successfully!") + load() + dismiss() + }) } else { // Submit the template api.templates.post(template) - .success(function(data){ - successFlash("Template added successfully!") - load() - dismiss() - }) - .error(function(data){ - modalError(data.responseJSON.message) - }) + .success(function(data) { + successFlash("Template added successfully!") + load() + dismiss() + }) + .error(function(data) { + modalError(data.responseJSON.message) + }) } } -function dismiss(){ +function dismiss() { $("#modal\\.flashes").empty() $("#attachmentsTable").dataTable().DataTable().clear().draw() $("#name").val("") @@ -67,24 +69,24 @@ function dismiss(){ $("#modal").modal('hide') } -function deleteTemplate(idx){ - if (confirm("Delete " + templates[idx].name + "?")){ +function deleteTemplate(idx) { + if (confirm("Delete " + templates[idx].name + "?")) { api.templateId.delete(templates[idx].id) - .success(function(data){ - successFlash(data.message) - load() - }) + .success(function(data) { + successFlash(data.message) + load() + }) } } -function attach(files){ +function attach(files) { attachmentsTable = $("#attachmentsTable").DataTable(); - $.each(files, function(i, file){ + $.each(files, function(i, file) { var reader = new FileReader(); /* Make this a datatable */ - reader.onload = function(e){ + reader.onload = function(e) { var icon = icons[file.type] || "fa-file-o" - // Add the record to the modal + // Add the record to the modal attachmentsTable.row.add([ '', file.name, @@ -100,33 +102,38 @@ function attach(files){ }) } -function edit(idx){ - $("#modalSubmit").unbind('click').click(function(){save(idx)}) - $("#attachmentUpload").unbind('click').click(function(){this.value=null}) +function edit(idx) { + $("#modalSubmit").unbind('click').click(function() { + save(idx) + }) + $("#attachmentUpload").unbind('click').click(function() { + this.value = null + }) $("#html_editor").ckeditor() $("#attachmentsTable").show() attachmentsTable = null - if ( $.fn.dataTable.isDataTable('#attachmentsTable') ) { + if ($.fn.dataTable.isDataTable('#attachmentsTable')) { attachmentsTable = $('#attachmentsTable').DataTable(); - } - else { + } else { attachmentsTable = $("#attachmentsTable").DataTable({ - "aoColumnDefs" : [{ - "targets" : [3,4], - "sClass" : "datatable_hidden" + "aoColumnDefs": [{ + "targets": [3, 4], + "sClass": "datatable_hidden" }] }); } - var template = {attachments:[]} + var template = { + attachments: [] + } if (idx != -1) { template = templates[idx] $("#name").val(template.name) - $("#subject").val(template.subject) + $("#subject").val(template.subject) $("#html_editor").val(template.html) $("#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" - // Add the record to the modal + // Add the record to the modal attachmentsTable.row.add([ '', file.name, @@ -137,110 +144,108 @@ function edit(idx){ }) } // Handle Deletion - $("#attachmentsTable").unbind('click').on("click", "span>i.fa-trash-o", function(){ - attachmentsTable.row( $(this).parents('tr') ) - .remove() - .draw(); + $("#attachmentsTable").unbind('click').on("click", "span>i.fa-trash-o", function() { + attachmentsTable.row($(this).parents('tr')) + .remove() + .draw(); }) } -function importEmail(){ +function importEmail() { raw = $("#email_content").val() - if (!raw){ + if (!raw) { modalError("No Content Specified!") } else { - $.ajax({ - type: "POST", - url: "/api/import/email", - data: raw, - dataType: "json", - contentType: "text/plain" - }) - .success(function(data){ - $("#text_editor").val(data.text) - $("#html_editor").val(data.html) - $("#subject").val(data.subject) - $("#importEmailModal").modal("hide") - }) - .error(function(data){ - modalError(data.responseJSON.message) - }) + $.ajax({ + type: "POST", + url: "/api/import/email", + data: raw, + dataType: "json", + contentType: "text/plain" + }) + .success(function(data) { + $("#text_editor").val(data.text) + $("#html_editor").val(data.html) + $("#subject").val(data.subject) + $("#importEmailModal").modal("hide") + }) + .error(function(data) { + modalError(data.responseJSON.message) + }) } } -function load(){ +function load() { $("#templateTable").hide() $("#emptyMessage").hide() $("#loading").show() api.templates.get() - .success(function(ts){ - templates = ts - $("#loading").hide() - if (templates.length > 0){ - $("#templateTable").show() - templateTable = $("#templateTable").DataTable(); - templateTable.clear() - $.each(templates, function(i, template){ - templateTable.row.add([ - template.name, - moment(template.modified_date).format('MMMM Do YYYY, h:mm:ss a'), - "
    \
    " - ]).draw() - }) - } else { - $("#emptyMessage").show() - } - }) - .error(function(){ - $("#loading").hide() - errorFlash("Error fetching templates") - }) + ]).draw() + }) + } else { + $("#emptyMessage").show() + } + }) + .error(function() { + $("#loading").hide() + errorFlash("Error fetching templates") + }) } -$(document).ready(function(){ +$(document).ready(function() { // Setup multiple modals // Code based on http://miles-by-motorcycle.com/static/bootstrap-modal/index.html - $('.modal').on('hidden.bs.modal', function( event ) { - $(this).removeClass( 'fv-modal-stack' ); - $('body').data( 'fv_open_modals', $('body').data( 'fv_open_modals' ) - 1 ); + $('.modal').on('hidden.bs.modal', function(event) { + $(this).removeClass('fv-modal-stack'); + $('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 - if ( typeof( $('body').data( 'fv_open_modals' ) ) == 'undefined' ) - { - $('body').data( 'fv_open_modals', 0 ); + if (typeof($('body').data('fv_open_modals')) == 'undefined') { + $('body').data('fv_open_modals', 0); } // if the z-index of this modal has been set, ignore. - if ( $(this).hasClass( 'fv-modal-stack' ) ) - { + if ($(this).hasClass('fv-modal-stack')) { return; } - $(this).addClass( 'fv-modal-stack' ); - // Increment the number of open modals - $('body').data( 'fv_open_modals', $('body').data( 'fv_open_modals' ) + 1 ); - // Setup the appropriate z-index - $(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' ).addClass( 'fv-modal-stack' ); + $(this).addClass('fv-modal-stack'); + // Increment the number of open modals + $('body').data('fv_open_modals', $('body').data('fv_open_modals') + 1); + // Setup the appropriate z-index + $(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').addClass('fv-modal-stack'); }); $.fn.modal.Constructor.prototype.enforceFocus = function() { - $( document ) - .off( 'focusin.bs.modal' ) // guard against infinite focus loop - .on( 'focusin.bs.modal', $.proxy( function( e ) { - if ( - this.$element[ 0 ] !== e.target && !this.$element.has( e.target ).length - // CKEditor compatibility fix start. - && !$( e.target ).closest( '.cke_dialog, .cke' ).length - // CKEditor compatibility fix end. - ) { - this.$element.trigger( 'focus' ); - } - }, this ) ); - }; + $(document) + .off('focusin.bs.modal') // guard against infinite focus loop + .on('focusin.bs.modal', $.proxy(function(e) { + if ( + this.$element[0] !== e.target && !this.$element.has(e.target).length + // CKEditor compatibility fix start. + && !$(e.target).closest('.cke_dialog, .cke').length + // CKEditor compatibility fix end. + ) { + this.$element.trigger('focus'); + } + }, this)); + }; load() }) diff --git a/static/js/app/users.js b/static/js/app/users.js index 10f310f1..c2a62dab 100644 --- a/static/js/app/users.js +++ b/static/js/app/users.js @@ -1,60 +1,62 @@ var groups = [] // Save attempts to POST or PUT to /groups/ -function save(idx){ +function save(idx) { var targets = [] - $.each($("#targetsTable").DataTable().rows().data(), function(i, target){ + $.each($("#targetsTable").DataTable().rows().data(), function(i, target) { targets.push({ - first_name : target[0], + first_name: target[0], last_name: target[1], email: target[2], position: target[3] }) }) var group = { - name: $("#name").val(), - targets: targets - } - // Submit the group + name: $("#name").val(), + targets: targets + } + // Submit the group if (idx != -1) { // If we're just editing an existing group, // we need to PUT /groups/:id group.id = groups[idx].id api.groupId.put(group) - .success(function(data){ - successFlash("Group updated successfully!") - load() - dismiss() - $("#modal").modal('hide') - }) - .error(function(data){ - modalError(data.responseJSON.message) - }) + .success(function(data) { + successFlash("Group updated successfully!") + load() + dismiss() + $("#modal").modal('hide') + }) + .error(function(data) { + modalError(data.responseJSON.message) + }) } else { // Else, if this is a new group, POST it // to /groups api.groups.post(group) - .success(function(data){ - successFlash("Group added successfully!") - load() - dismiss() - $("#modal").modal('hide') - }) - .error(function(data){ - modalError(data.responseJSON.message) - }) + .success(function(data) { + successFlash("Group added successfully!") + load() + dismiss() + $("#modal").modal('hide') + }) + .error(function(data) { + modalError(data.responseJSON.message) + }) } } -function dismiss(){ +function dismiss() { $("#targetsTable").dataTable().DataTable().clear().draw() $("#name").val("") $("#modal\\.flashes").empty() } -function edit(idx){ +function edit(idx) { targets = $("#targetsTable").dataTable() - $("#modalSubmit").unbind('click').click(function(){save(idx)}) + $("#modalSubmit").unbind('click').click(function() { + save(idx) + }) if (idx == -1) { group = {} } else { @@ -62,31 +64,6 @@ function edit(idx){ $("#name").val(group.name) $.each(group.targets, function(i, record) { targets.DataTable() - .row.add([ - record.first_name, - record.last_name, - record.email, - record.position, - '' - ]).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, @@ -94,90 +71,115 @@ function edit(idx){ record.position, '' ]).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, + '' + ]).draw() }); } }) } -function deleteGroup(idx){ - if (confirm("Delete " + groups[idx].name + "?")){ +function deleteGroup(idx) { + if (confirm("Delete " + groups[idx].name + "?")) { api.groupId.delete(groups[idx].id) - .success(function(data){ - successFlash(data.message) - load() - }) + .success(function(data) { + successFlash(data.message) + load() + }) } } -function load(){ +function load() { $("#groupTable").hide() $("#emptyMessage").hide() $("#loading").show() api.groups.get() - .success(function(gs){ - $("#loading").hide() - if (gs.length > 0){ - groups = gs - $("#emptyMessage").hide() - $("#groupTable").show() - groupTable = $("#groupTable").DataTable(); - groupTable.clear(); - $.each(groups, function(i, group){ - var targets = "" - $.each(group.targets, function(i, target){ - targets += target.email + ", " - if (targets.length > 50) { - targets = targets.slice(0,-3) + "..." - return false; - } - }) - groupTable.row.add([ - group.name, - targets, - moment(group.modified_date).format('MMMM Do YYYY, h:mm:ss a'), - "
    \
    " - ]).draw() - }) - } else { - $("#emptyMessage").show() - } - }) - .error(function(){ - errorFlash("Error fetching groups") - }) + ]).draw() + }) + } else { + $("#emptyMessage").show() + } + }) + .error(function() { + errorFlash("Error fetching groups") + }) } -$(document).ready(function(){ +$(document).ready(function() { load() - // Setup the event listeners - // Handle manual additions - $("#targetForm").submit(function(){ + // Setup the event listeners + // Handle manual additions + $("#targetForm").submit(function() { + targets.DataTable() + .row.add([ + $("#firstName").val(), + $("#lastName").val(), + $("#email").val(), + $("#position").val(), + '' + ]) + .draw() + $("#targetForm>div>input").val('') + $("#firstName").focus() + return false + }) + // Handle Deletion + $("#targetsTable").on("click", "span>i.fa-trash-o", function() { targets.DataTable() - .row.add([ - $("#firstName").val(), - $("#lastName").val(), - $("#email").val(), - $("#position").val(), - '' - ]) - .draw() - $("#targetForm>div>input").val('') - $("#firstName").focus() - return false + .row($(this).parents('tr')) + .remove() + .draw(); }) - // Handle Deletion - $("#targetsTable").on("click", "span>i.fa-trash-o", function(){ - targets.DataTable() - .row( $(this).parents('tr') ) - .remove() - .draw(); - }) - $("#modal").on("hide.bs.modal", function(){ - dismiss() + $("#modal").on("hide.bs.modal", function() { + dismiss() }) }) diff --git a/static/js/gophish.js b/static/js/gophish.js index 4e4dc6d8..b95ec7cd 100644 --- a/static/js/gophish.js +++ b/static/js/gophish.js @@ -1,18 +1,16 @@ function errorFlash(message) { $("#flashes").empty() $("#flashes").append("
    \ - " + message + "
    " - ) + " + message + "
    ") } function successFlash(message) { $("#flashes").empty() $("#flashes").append("
    \ - " + message + "
    " - ) + " + message + "
    ") } -function modalError(message){ +function modalError(message) { $("#modal\\.flashes").empty().append("
    \ " + message + "
    ") } @@ -23,7 +21,7 @@ function query(endpoint, method, data) { async: false, method: method, data: JSON.stringify(data), - dataType:"json", + dataType: "json", contentType: "application/json" }) } @@ -33,117 +31,117 @@ Define our API Endpoints */ var api = { // campaigns contains the endpoints for /campaigns - campaigns : { + campaigns: { // get() - Queries the API for GET /campaigns - get: function(){ + get: function() { return query("/campaigns/", "GET", {}) }, // post() - Posts a campaign to POST /campaigns - post: function(data){ + post: function(data) { return query("/campaigns/", "POST", data) } }, // campaignId contains the endpoints for /campaigns/:id - campaignId : { + campaignId: { // get() - Queries the API for GET /campaigns/:id - get: function(id){ + get: function(id) { return query("/campaigns/" + id, "GET", {}) }, // delete() - Deletes a campaign at DELETE /campaigns/:id - delete: function(id){ + delete: function(id) { return query("/campaigns/" + id, "DELETE", data) } }, // groups contains the endpoints for /groups - groups : { + groups: { // get() - Queries the API for GET /groups - get: function(){ + get: function() { return query("/groups/", "GET", {}) }, // post() - Posts a campaign to POST /groups - post: function(group){ + post: function(group) { return query("/groups/", "POST", group) } }, // groupId contains the endpoints for /groups/:id - groupId : { + groupId: { // get() - Queries the API for GET /groups/:id - get: function(id){ + get: function(id) { return query("/groups/" + id, "GET", {}) }, // put() - Puts a campaign to PUT /groups/:id - put: function (group){ + put: function(group) { return query("/groups/" + group.id, "PUT", group) }, // delete() - Deletes a campaign at DELETE /groups/:id - delete: function(id){ + delete: function(id) { return query("/groups/" + id, "DELETE", {}) } }, // templates contains the endpoints for /templates - templates : { + templates: { // get() - Queries the API for GET /templates - get: function(){ + get: function() { return query("/templates/", "GET", {}) }, // post() - Posts a campaign to POST /templates - post: function(template){ + post: function(template) { return query("/templates/", "POST", template) } }, // templateId contains the endpoints for /templates/:id - templateId : { + templateId: { // get() - Queries the API for GET /templates/:id - get: function(id){ + get: function(id) { return query("/templates/" + id, "GET", {}) }, // put() - Puts a campaign to PUT /templates/:id - put: function (template){ + put: function(template) { return query("/templates/" + template.id, "PUT", template) }, // delete() - Deletes a campaign at DELETE /templates/:id - delete: function(id){ + delete: function(id) { return query("/templates/" + id, "DELETE", {}) } }, // pages contains the endpoints for /pages - pages : { + pages: { // get() - Queries the API for GET /pages - get: function(){ + get: function() { return query("/pages/", "GET", {}) }, // post() - Posts a campaign to POST /pages - post: function(page){ + post: function(page) { return query("/pages/", "POST", page) } }, // templateId contains the endpoints for /templates/:id - pageId : { + pageId: { // get() - Queries the API for GET /templates/:id - get: function(id){ + get: function(id) { return query("/pages/" + id, "GET", {}) }, // put() - Puts a campaign to PUT /templates/:id - put: function (page){ + put: function(page) { return query("/pages/" + page.id, "PUT", page) }, // delete() - Deletes a campaign at DELETE /templates/:id - delete: function(id){ + delete: function(id) { return query("/pages/" + id, "DELETE", {}) } }, // import handles all of the "import" functions in the api - import_email : function(raw) { - return query("/import/email", "POST", {}) + import_email: function(raw) { + return query("/import/email", "POST", {}) }, - clone_site : function(req){ - return query("/import/site", "POST", req) + clone_site: function(req) { + return query("/import/site", "POST", req) } } // Register our moment.js datatables listeners -$(document).ready(function(){ - $.fn.dataTable.moment('MMMM Do YYYY, h:mm:ss a'); - // Setup tooltips - $('[data-toggle="tooltip"]').tooltip() +$(document).ready(function() { + $.fn.dataTable.moment('MMMM Do YYYY, h:mm:ss a'); + // Setup tooltips + $('[data-toggle="tooltip"]').tooltip() }); From 66720aa1b81235ac43d83594afd5fe5f9f2283eb Mon Sep 17 00:00:00 2001 From: Jordan Wright Date: Sat, 16 Jan 2016 23:01:53 -0600 Subject: [PATCH 06/24] Added GoDoc reference as well as website link --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6062abc9..3f53db7b 100644 --- a/README.md +++ b/README.md @@ -3,11 +3,11 @@ 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 -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 **Update 01/12/2016** From cff666119e54536ddfc736599c09fb5a9ed45474 Mon Sep 17 00:00:00 2001 From: Jordan Wright Date: Sat, 16 Jan 2016 23:50:11 -0600 Subject: [PATCH 07/24] Added ability to delete campaign. Fixes #72 --- static/js/app/campaigns.js | 18 +++++++++++++++--- static/js/app/dashboard.js | 9 +++++++++ 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/static/js/app/campaigns.js b/static/js/app/campaigns.js index c3a64ee0..1cf81a17 100644 --- a/static/js/app/campaigns.js +++ b/static/js/app/campaigns.js @@ -8,6 +8,8 @@ var labels = { "Error": "label-danger" } +var campaigns = [] + // Save attempts to POST to /campaigns/ function save() { groups = [] @@ -16,7 +18,6 @@ function save() { name: group[0] }) }) - console.log(groups) var campaign = { name: $("#name").val(), template: { @@ -52,6 +53,16 @@ function dismiss() { $("#groupTable").dataTable().DataTable().clear().draw() } +function deleteCampaign(idx) { + if (confirm("Delete " + campaigns[idx].name + "?")) { + api.campaignId.delete(campaigns[idx].id) + .success(function(data) { + successFlash(data.message) + load() + }) + } +} + function edit(campaign) { // Clear the bloodhound instance group_bh.clear(); @@ -90,7 +101,8 @@ function edit(campaign) { $(document).ready(function() { api.campaigns.get() - .success(function(campaigns) { + .success(function(cs) { + campaigns = cs $("#loading").hide() if (campaigns.length > 0) { $("#campaignTable").show() @@ -104,7 +116,7 @@ $(document).ready(function() { "
    \ \ \ -
    " ]).draw() diff --git a/static/js/app/dashboard.js b/static/js/app/dashboard.js index 23ed7752..59a03c29 100644 --- a/static/js/app/dashboard.js +++ b/static/js/app/dashboard.js @@ -9,6 +9,15 @@ var labels = { "Error": "label-danger" } +function deleteCampaign(idx) { + if (confirm("Delete " + campaigns[idx].name + "?")) { + api.campaignId.delete(campaigns[idx].id) + .success(function(data) { + successFlash(data.message) + }) + } +} + $(document).ready(function() { api.campaigns.get() .success(function(cs) { From 3ae09be98986eab0b4d11df4a76c94c6c150ce33 Mon Sep 17 00:00:00 2001 From: Jordan Wright Date: Sat, 16 Jan 2016 23:50:32 -0600 Subject: [PATCH 08/24] Final fix for campaign deletion --- static/js/gophish.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/js/gophish.js b/static/js/gophish.js index b95ec7cd..ca9c0e11 100644 --- a/static/js/gophish.js +++ b/static/js/gophish.js @@ -49,7 +49,7 @@ var api = { }, // delete() - Deletes a campaign at DELETE /campaigns/:id delete: function(id) { - return query("/campaigns/" + id, "DELETE", data) + return query("/campaigns/" + id, "DELETE", {}) } }, // groups contains the endpoints for /groups From 1d8ac6f9f341c8be1924f939b68492531ebc7192 Mon Sep 17 00:00:00 2001 From: Jordan Wright Date: Sat, 16 Jan 2016 23:51:01 -0600 Subject: [PATCH 09/24] Added better logging for campaign retrieval --- controllers/api.go | 9 +++++---- models/campaign.go | 8 ++++++++ 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/controllers/api.go b/controllers/api.go index 46bb87b7..589b92e5 100644 --- a/controllers/api.go +++ b/controllers/api.go @@ -10,14 +10,14 @@ import ( "time" "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/models" "github.com/gophish/gophish/util" "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. @@ -92,6 +92,7 @@ func API_Campaigns_Id(w http.ResponseWriter, r *http.Request) { id, _ := strconv.ParseInt(vars["id"], 0, 64) c, err := models.GetCampaign(id, ctx.Get(r, "user_id").(int64)) if err != nil { + Logger.Println(err) JSONResponse(w, models.Response{Success: false, Message: "Campaign not found"}, http.StatusNotFound) return } diff --git a/models/campaign.go b/models/campaign.go index bab7bedf..3d701d92 100644 --- a/models/campaign.go +++ b/models/campaign.go @@ -117,21 +117,28 @@ func GetCampaign(id int64, uid int64) (Campaign, error) { c := Campaign{} err := db.Where("id = ?", id).Where("user_id = ?", uid).Find(&c).Error if err != nil { + Logger.Printf("%s: campaign not found\n", err) return c, err } err = db.Model(&c).Related(&c.Results).Error if err != nil { + Logger.Printf("%s: results not found for campaign\n", err) return c, err } err = db.Model(&c).Related(&c.Events).Error if err != nil { + Logger.Printf("%s: events not found for campaign\n", err) return c, err } err = db.Table("templates").Where("id=?", c.TemplateId).Find(&c.Template).Error if err != nil { + Logger.Printf("%s: template not found for campaign\n", err) return c, err } 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 } @@ -207,6 +214,7 @@ func PostCampaign(c *Campaign, uid int64) error { //DeleteCampaign deletes the specified campaign func DeleteCampaign(id int64) error { + Logger.Printf("Deleting campaign %d\n", id) // Delete all the campaign results err := db.Where("campaign_id=?", id).Delete(&Result{}).Error if err != nil { From 84d21ab6556d862a39a84014586d48f74d2d38b7 Mon Sep 17 00:00:00 2001 From: Justin Gray Date: Sun, 17 Jan 2016 01:31:46 -0600 Subject: [PATCH 10/24] Sample Table Style Changed the style of the add/manage users table. --- static/css/main.css | 31 +++++++++++++++++++++++++++++++ static/js/app/users.js | 6 +++++- templates/users.html | 2 +- 3 files changed, 37 insertions(+), 2 deletions(-) diff --git a/static/css/main.css b/static/css/main.css index dcdb603b..3a48879e 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -341,3 +341,34 @@ 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; +} +table.dataTable thead .sorting_asc:after { + content: "\f0de" !important; + opacity: .8 !important; +} +table.dataTable thead .sorting_desc:after { + content: "\f0dd" !important; + opacity: .8 !important; +} + diff --git a/static/js/app/users.js b/static/js/app/users.js index feb0e9ac..0aa8c68c 100644 --- a/static/js/app/users.js +++ b/static/js/app/users.js @@ -52,7 +52,11 @@ function dismiss(){ } function edit(idx){ - targets = $("#targetsTable").dataTable() + targets = $("#targetsTable").dataTable({ + columnDefs: [ + { orderable: false, targets: -1 } + ] + }) $("#modalSubmit").unbind('click').click(function(){save(idx)}) if (idx == -1) { group = {} diff --git a/templates/users.html b/templates/users.html index 4ee56f64..4d25e4fd 100644 --- a/templates/users.html +++ b/templates/users.html @@ -94,7 +94,7 @@

    - +
    From 44852546e0a9f941ea8aba5cce3bb334ccc49ef9 Mon Sep 17 00:00:00 2001 From: William Woodson Date: Sun, 17 Jan 2016 10:45:13 -0600 Subject: [PATCH 11/24] Added UseTLS config option for both Admin and Phish servers --- config.json | 18 ++++++++++++++---- config/config.go | 24 ++++++++++++++++++++---- controllers/api_test.go | 2 +- gophish.go | 20 ++++++++++++++++---- 4 files changed, 51 insertions(+), 13 deletions(-) diff --git a/config.json b/config.json index 148dd219..7b7213c9 100644 --- a/config.json +++ b/config.json @@ -1,10 +1,20 @@ { - "admin_url" : "127.0.0.1:3333", - "phish_url" : "0.0.0.0:80", + "admin_server" : { + "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" : { "host" : "smtp.example.com:25", "user" : "username", "pass" : "password" }, - "dbpath" : "gophish.db" -} \ No newline at end of file + "db_path" : "gophish.db" +} diff --git a/config/config.go b/config/config.go index a4941e9d..6f1f5e01 100644 --- a/config/config.go +++ b/config/config.go @@ -13,12 +13,28 @@ type SMTPServer struct { 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. type Config struct { - AdminURL string `json:"admin_url"` - PhishURL string `json:"phish_url"` - SMTP SMTPServer `json:"smtp"` - DBPath string `json:"dbpath"` + AdminConf AdminServer `json:"admin_server"` + PhishConf PhishServer `json:"phish_server"` + SMTPConf SMTPServer `json:"smtp"` + DBPath string `json:"db_path"` } var Conf Config diff --git a/controllers/api_test.go b/controllers/api_test.go index 4255a7f6..4afccd11 100644 --- a/controllers/api_test.go +++ b/controllers/api_test.go @@ -32,7 +32,7 @@ func (s *ControllersSuite) SetupSuite() { } s.Nil(err) // Setup the admin server for use in testing - as.Config.Addr = config.Conf.AdminURL + as.Config.Addr = config.Conf.AdminConf.ListenURL as.Start() // Get the API key to use for these tests u, err := models.GetUser(1) diff --git a/gophish.go b/gophish.go index d5d49ddc..fdd969fa 100644 --- a/gophish.go +++ b/gophish.go @@ -51,14 +51,26 @@ func main() { // Start the web servers go func() { defer wg.Done() - Logger.Printf("Starting admin server at http://%s\n", config.Conf.AdminURL) - Logger.Fatal(http.ListenAndServe(config.Conf.AdminURL, handlers.CombinedLoggingHandler(os.Stdout, controllers.CreateAdminRouter()))) + if config.Conf.AdminConf.UseTLS { // use TLS for Admin web server if available + 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() - Logger.Printf("Starting phishing server at http://%s\n", config.Conf.PhishURL) - Logger.Fatal(http.ListenAndServe(config.Conf.PhishURL, handlers.CombinedLoggingHandler(os.Stdout, controllers.CreatePhishingRouter()))) + 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() } From 3eef2905c882134d10c823b674a7491526a853a5 Mon Sep 17 00:00:00 2001 From: William Woodson Date: Sun, 17 Jan 2016 13:15:32 -0600 Subject: [PATCH 12/24] UI fixes to dashboard, campaign, and campaign_result views. fixes #76 --- static/js/app/campaign_results.js | 2 +- static/js/app/campaigns.js | 6 +++--- static/js/app/dashboard.js | 6 ++++-- templates/campaign_results.html | 2 +- templates/campaigns.html | 2 +- 5 files changed, 10 insertions(+), 8 deletions(-) diff --git a/static/js/app/campaign_results.js b/static/js/app/campaign_results.js index 002f0a57..b4c78c06 100644 --- a/static/js/app/campaign_results.js +++ b/static/js/app/campaign_results.js @@ -47,7 +47,7 @@ function deleteCampaign() { if (confirm("Are you sure you want to delete: " + campaign.name + "?")) { api.campaignId.delete(campaign.id) .success(function(msg) { - console.log(msg) + document.location.href = '/campaigns' }) .error(function(e) { $("#modal\\.flashes").empty().append("
    \ diff --git a/static/js/app/campaigns.js b/static/js/app/campaigns.js index 1cf81a17..a4ebc0d1 100644 --- a/static/js/app/campaigns.js +++ b/static/js/app/campaigns.js @@ -58,7 +58,7 @@ function deleteCampaign(idx) { api.campaignId.delete(campaigns[idx].id) .success(function(data) { successFlash(data.message) - load() + location.reload() }) } } @@ -113,10 +113,10 @@ $(document).ready(function() { campaign.name, moment(campaign.created_date).format('MMMM Do YYYY, h:mm:ss a'), "" + campaign.status + "", - "
    \ + "
    \ \ \ -
    " ]).draw() diff --git a/static/js/app/dashboard.js b/static/js/app/dashboard.js index 59a03c29..37a3f379 100644 --- a/static/js/app/dashboard.js +++ b/static/js/app/dashboard.js @@ -14,6 +14,8 @@ function deleteCampaign(idx) { api.campaignId.delete(campaigns[idx].id) .success(function(data) { successFlash(data.message) + load() + //location.reload() }) } } @@ -58,10 +60,10 @@ $(document).ready(function() { campaign.name, campaign_date, "" + campaign.status + "", - "
    \ + "
    \ \ \ -
    " ]).draw() diff --git a/templates/campaign_results.html b/templates/campaign_results.html index 8bba8327..bad1058f 100644 --- a/templates/campaign_results.html +++ b/templates/campaign_results.html @@ -45,7 +45,7 @@
    -->

    diff --git a/templates/campaigns.html b/templates/campaigns.html index 8c8354c6..105b4c73 100644 --- a/templates/campaigns.html +++ b/templates/campaigns.html @@ -127,7 +127,7 @@
    {{end}} -{{define "scripts"}} +{{define "scripts"}}i From e6a9848c20d880b947e77d42dff7436200a26b10 Mon Sep 17 00:00:00 2001 From: William Woodson Date: Sun, 17 Jan 2016 13:15:32 -0600 Subject: [PATCH 13/24] UI fixes to dashboard, campaign, and campaign_result views. fixes #76 --- static/js/app/campaign_results.js | 2 +- static/js/app/campaigns.js | 6 +++--- static/js/app/dashboard.js | 6 ++++-- templates/campaign_results.html | 2 +- templates/campaigns.html | 2 +- 5 files changed, 10 insertions(+), 8 deletions(-) diff --git a/static/js/app/campaign_results.js b/static/js/app/campaign_results.js index 002f0a57..b4c78c06 100644 --- a/static/js/app/campaign_results.js +++ b/static/js/app/campaign_results.js @@ -47,7 +47,7 @@ function deleteCampaign() { if (confirm("Are you sure you want to delete: " + campaign.name + "?")) { api.campaignId.delete(campaign.id) .success(function(msg) { - console.log(msg) + document.location.href = '/campaigns' }) .error(function(e) { $("#modal\\.flashes").empty().append("
    \ diff --git a/static/js/app/campaigns.js b/static/js/app/campaigns.js index 1cf81a17..a4ebc0d1 100644 --- a/static/js/app/campaigns.js +++ b/static/js/app/campaigns.js @@ -58,7 +58,7 @@ function deleteCampaign(idx) { api.campaignId.delete(campaigns[idx].id) .success(function(data) { successFlash(data.message) - load() + location.reload() }) } } @@ -113,10 +113,10 @@ $(document).ready(function() { campaign.name, moment(campaign.created_date).format('MMMM Do YYYY, h:mm:ss a'), "" + campaign.status + "", - "
    \ + "
    \ \ \ -
    " ]).draw() diff --git a/static/js/app/dashboard.js b/static/js/app/dashboard.js index 59a03c29..37a3f379 100644 --- a/static/js/app/dashboard.js +++ b/static/js/app/dashboard.js @@ -14,6 +14,8 @@ function deleteCampaign(idx) { api.campaignId.delete(campaigns[idx].id) .success(function(data) { successFlash(data.message) + load() + //location.reload() }) } } @@ -58,10 +60,10 @@ $(document).ready(function() { campaign.name, campaign_date, "" + campaign.status + "", - "
    \ + "
    \ \ \ -
    " ]).draw() diff --git a/templates/campaign_results.html b/templates/campaign_results.html index 8bba8327..bad1058f 100644 --- a/templates/campaign_results.html +++ b/templates/campaign_results.html @@ -45,7 +45,7 @@
    -->

    diff --git a/templates/campaigns.html b/templates/campaigns.html index 8c8354c6..105b4c73 100644 --- a/templates/campaigns.html +++ b/templates/campaigns.html @@ -127,7 +127,7 @@
    {{end}} -{{define "scripts"}} +{{define "scripts"}}i From fa5a5c65afb2fd9cac6286e1871e4ecdca5d702f Mon Sep 17 00:00:00 2001 From: William Woodson Date: Sun, 17 Jan 2016 13:25:13 -0600 Subject: [PATCH 14/24] fix dumb things in last commit --- static/js/app/campaign_results.js | 2 +- static/js/app/dashboard.js | 3 +-- templates/campaigns.html | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/static/js/app/campaign_results.js b/static/js/app/campaign_results.js index b4c78c06..47595fae 100644 --- a/static/js/app/campaign_results.js +++ b/static/js/app/campaign_results.js @@ -47,7 +47,7 @@ function deleteCampaign() { if (confirm("Are you sure you want to delete: " + campaign.name + "?")) { api.campaignId.delete(campaign.id) .success(function(msg) { - document.location.href = '/campaigns' + location.href = '/campaigns' }) .error(function(e) { $("#modal\\.flashes").empty().append("
    \ diff --git a/static/js/app/dashboard.js b/static/js/app/dashboard.js index 37a3f379..7eac0080 100644 --- a/static/js/app/dashboard.js +++ b/static/js/app/dashboard.js @@ -14,8 +14,7 @@ function deleteCampaign(idx) { api.campaignId.delete(campaigns[idx].id) .success(function(data) { successFlash(data.message) - load() - //location.reload() + location.reload() }) } } diff --git a/templates/campaigns.html b/templates/campaigns.html index 105b4c73..8c8354c6 100644 --- a/templates/campaigns.html +++ b/templates/campaigns.html @@ -127,7 +127,7 @@
    {{end}} -{{define "scripts"}}i +{{define "scripts"}} From 2ff2cbd1cae6c0a967de7498a080686b3fef1a4c Mon Sep 17 00:00:00 2001 From: Justin Gray Date: Sun, 17 Jan 2016 14:37:18 -0600 Subject: [PATCH 15/24] Changed Sortable Icon Color Changed the color of the sortable icon back to the initial color. Adds a little more contrast to the enabled icon. --- static/css/main.css | 1 + 1 file changed, 1 insertion(+) diff --git a/static/css/main.css b/static/css/main.css index 3a48879e..631014fe 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -362,6 +362,7 @@ table.dataTable thead .sorting:after, table.dataTable thead .sorting_asc:after, } table.dataTable thead .sorting:after{ content: "\f0dc" !important; + color: initial; } table.dataTable thead .sorting_asc:after { content: "\f0de" !important; From b9fd654f8a698b86e64e755694de84439628c894 Mon Sep 17 00:00:00 2001 From: Justin Gray Date: Sun, 17 Jan 2016 15:27:11 -0600 Subject: [PATCH 16/24] Added no-sort options to tables MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added a no-sort class to column headers where sorting doesn’t make sense. Still have the attachments table in the templates page to do, there was something strange happening. --- static/js/app/campaigns.js | 12 ++++++++++-- static/js/app/dashboard.js | 6 +++++- static/js/app/landing_pages.js | 6 +++++- static/js/app/templates.js | 6 +++++- static/js/app/users.js | 9 +++++++-- templates/campaigns.html | 4 ++-- templates/dashboard.html | 2 +- templates/landing_pages.html | 2 +- templates/templates.html | 2 +- templates/users.html | 4 ++-- 10 files changed, 39 insertions(+), 14 deletions(-) diff --git a/static/js/app/campaigns.js b/static/js/app/campaigns.js index 4c3a1045..6ef3159b 100644 --- a/static/js/app/campaigns.js +++ b/static/js/app/campaigns.js @@ -95,7 +95,11 @@ $(document).ready(function(){ $("#loading").hide() if (campaigns.length > 0){ $("#campaignTable").show() - campaignTable = $("#campaignTable").DataTable(); + campaignTable = $("#campaignTable").DataTable({ + columnDefs: [ + { orderable: false, targets: "no-sort" } + ] + }); $.each(campaigns, function(i, campaign){ label = labels[campaign.status] || "label-default"; campaignTable.row.add([ @@ -131,7 +135,11 @@ $(document).ready(function(){ return false; }) // Create the group typeahead objects - groupTable = $("#groupTable").DataTable() + groupTable = $("#groupTable").DataTable({ + columnDefs: [ + { orderable: false, targets: "no-sort" } + ] + }) group_bh = new Bloodhound({ datumTokenizer: function(g) { return Bloodhound.tokenizers.whitespace(g.name) }, queryTokenizer: Bloodhound.tokenizers.whitespace, diff --git a/static/js/app/dashboard.js b/static/js/app/dashboard.js index c8445b9f..81084adf 100644 --- a/static/js/app/dashboard.js +++ b/static/js/app/dashboard.js @@ -33,7 +33,11 @@ $(document).ready(function(){ showLabel: false } var average = 0 - campaignTable = $("#campaignTable").DataTable(); + campaignTable = $("#campaignTable").DataTable({ + columnDefs: [ + { 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"; diff --git a/static/js/app/landing_pages.js b/static/js/app/landing_pages.js index dd7b30cb..edbead61 100644 --- a/static/js/app/landing_pages.js +++ b/static/js/app/landing_pages.js @@ -92,7 +92,11 @@ function load(){ $("#loading").hide() if (pages.length > 0){ $("#pagesTable").show() - pagesTable = $("#pagesTable").DataTable(); + pagesTable = $("#pagesTable").DataTable({ + columnDefs: [ + { orderable: false, targets: "no-sort" } + ] + }); pagesTable.clear() $.each(pages, function(i, page){ pagesTable.row.add([ diff --git a/static/js/app/templates.js b/static/js/app/templates.js index 4da16ac1..85ece065 100644 --- a/static/js/app/templates.js +++ b/static/js/app/templates.js @@ -178,7 +178,11 @@ function load(){ $("#loading").hide() if (templates.length > 0){ $("#templateTable").show() - templateTable = $("#templateTable").DataTable(); + templateTable = $("#templateTable").DataTable({ + columnDefs: [ + { orderable: false, targets: "no-sort" } + ] + }); templateTable.clear() $.each(templates, function(i, template){ templateTable.row.add([ diff --git a/static/js/app/users.js b/static/js/app/users.js index 0aa8c68c..5a9a7d37 100644 --- a/static/js/app/users.js +++ b/static/js/app/users.js @@ -53,8 +53,9 @@ function dismiss(){ function edit(idx){ targets = $("#targetsTable").dataTable({ + destroy: true,// Destroy any other instantiated table - http://datatables.net/manual/tech-notes/3#destroy columnDefs: [ - { orderable: false, targets: -1 } + { orderable: false, targets: "no-sort" } ] }) $("#modalSubmit").unbind('click').click(function(){save(idx)}) @@ -123,7 +124,11 @@ function load(){ groups = gs $("#emptyMessage").hide() $("#groupTable").show() - groupTable = $("#groupTable").DataTable(); + groupTable = $("#groupTable").DataTable({ + columnDefs: [ + { orderable: false, targets: "no-sort" } + ] + }); groupTable.clear(); $.each(groups, function(i, group){ var targets = "" diff --git a/templates/campaigns.html b/templates/campaigns.html index 8c8354c6..e2c3c48b 100644 --- a/templates/campaigns.html +++ b/templates/campaigns.html @@ -47,7 +47,7 @@
    - + @@ -113,7 +113,7 @@
    First NameName Created Date Status
    - +
    Group Name
    diff --git a/templates/dashboard.html b/templates/dashboard.html index 3d652870..52083259 100644 --- a/templates/dashboard.html +++ b/templates/dashboard.html @@ -67,7 +67,7 @@ Name Created Date Status - + diff --git a/templates/landing_pages.html b/templates/landing_pages.html index 837307ad..6efe51f8 100644 --- a/templates/landing_pages.html +++ b/templates/landing_pages.html @@ -44,7 +44,7 @@ Name Last Modified Date - + diff --git a/templates/templates.html b/templates/templates.html index 70fde2d6..ac84b37f 100644 --- a/templates/templates.html +++ b/templates/templates.html @@ -46,7 +46,7 @@ Name Modified Date - + diff --git a/templates/users.html b/templates/users.html index 4d25e4fd..00812643 100644 --- a/templates/users.html +++ b/templates/users.html @@ -47,7 +47,7 @@ Name Members Modified Date - + @@ -101,7 +101,7 @@ Last Name Email Position - + From ac368e1046bea5c324ccbf506e9d2bbb93f31411 Mon Sep 17 00:00:00 2001 From: Jordan Wright Date: Sun, 17 Jan 2016 22:20:38 -0600 Subject: [PATCH 17/24] Fixing tooltips on campaign results table --- static/js/app/campaigns.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/static/js/app/campaigns.js b/static/js/app/campaigns.js index a4ebc0d1..c6e42c5f 100644 --- a/static/js/app/campaigns.js +++ b/static/js/app/campaigns.js @@ -113,13 +113,14 @@ $(document).ready(function() { campaign.name, moment(campaign.created_date).format('MMMM Do YYYY, h:mm:ss a'), "" + campaign.status + "", - "
    \ + "
    \ \ \ -
    " ]).draw() + $('[data-toggle="tooltip"]').tooltip() }) } else { $("#emptyMessage").show() From 1b5fb638ea0078512d9bbbf551bfd507c515a1f3 Mon Sep 17 00:00:00 2001 From: Jordan Wright Date: Mon, 18 Jan 2016 11:53:27 -0600 Subject: [PATCH 18/24] Fixed new campaign redirect --- static/js/app/campaigns.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/js/app/campaigns.js b/static/js/app/campaigns.js index c6e42c5f..6f009d1d 100644 --- a/static/js/app/campaigns.js +++ b/static/js/app/campaigns.js @@ -39,7 +39,7 @@ function save() { api.campaigns.post(campaign) .success(function(data) { successFlash("Campaign successfully launched!") - window.location = "/campaigns/" + campaign.id.toString() + window.location = "/campaigns/" + data.id.toString() }) .error(function(data) { $("#modal\\.flashes").empty().append("
    \ From 379edf73a37ccf0a382bed3eed9ff083f3ed0af9 Mon Sep 17 00:00:00 2001 From: Jordan Wright Date: Mon, 18 Jan 2016 21:13:32 -0600 Subject: [PATCH 19/24] Adding first round of database migrations using goose --- config.json | 3 +- config/config.go | 9 +++--- controllers/api_test.go | 3 +- db/migrations/20160118194630_init.sql | 28 ++++++++++++++++++ models/models.go | 41 ++++++++++++++++++--------- models/models_test.go | 1 + 6 files changed, 66 insertions(+), 19 deletions(-) create mode 100644 db/migrations/20160118194630_init.sql diff --git a/config.json b/config.json index 7b7213c9..8aae33de 100644 --- a/config.json +++ b/config.json @@ -16,5 +16,6 @@ "user" : "username", "pass" : "password" }, - "db_path" : "gophish.db" + "db_path" : "gophish.db", + "migrations_path" : "db/migrations/" } diff --git a/config/config.go b/config/config.go index 6f1f5e01..669de289 100644 --- a/config/config.go +++ b/config/config.go @@ -31,10 +31,11 @@ type PhishServer struct { // Config represents the configuration information. type Config struct { - AdminConf AdminServer `json:"admin_server"` - PhishConf PhishServer `json:"phish_server"` - SMTPConf SMTPServer `json:"smtp"` - DBPath string `json:"db_path"` + AdminConf AdminServer `json:"admin_server"` + PhishConf PhishServer `json:"phish_server"` + SMTPConf SMTPServer `json:"smtp"` + DBPath string `json:"db_path"` + MigrationsPath string `json:"migrations_path"` } var Conf Config diff --git a/controllers/api_test.go b/controllers/api_test.go index 4afccd11..893d1f7c 100644 --- a/controllers/api_test.go +++ b/controllers/api_test.go @@ -9,9 +9,9 @@ import ( "os" "testing" - "github.com/gorilla/handlers" "github.com/gophish/gophish/config" "github.com/gophish/gophish/models" + "github.com/gorilla/handlers" "github.com/stretchr/testify/suite" ) @@ -26,6 +26,7 @@ var as *httptest.Server = httptest.NewUnstartedServer(handlers.CombinedLoggingHa func (s *ControllersSuite) SetupSuite() { config.Conf.DBPath = ":memory:" + config.Conf.MigrationsPath = "../db/migrations/" err := models.Setup() if err != nil { s.T().Fatalf("Failed creating database: %v", err) diff --git a/db/migrations/20160118194630_init.sql b/db/migrations/20160118194630_init.sql new file mode 100644 index 00000000..390c8d52 --- /dev/null +++ b/db/migrations/20160118194630_init.sql @@ -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"; diff --git a/models/models.go b/models/models.go index 530ce55d..326d326b 100644 --- a/models/models.go +++ b/models/models.go @@ -8,6 +8,8 @@ import ( "log" "os" + "bitbucket.org/liamstask/goose/lib/goose" + "github.com/gophish/gophish/config" "github.com/jinzhu/gorm" _ "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:" { 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.LogMode(false) db.SetLogger(Logger) @@ -69,20 +89,14 @@ func Setup() error { Logger.Println(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 { - 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 initUser := User{ Username: "admin", @@ -92,6 +106,7 @@ func Setup() error { err = db.Save(&initUser).Error if err != nil { Logger.Println(err) + return err } } return nil diff --git a/models/models_test.go b/models/models_test.go index 99fd3a00..ec84a5b6 100644 --- a/models/models_test.go +++ b/models/models_test.go @@ -16,6 +16,7 @@ var _ = check.Suite(&ModelsSuite{}) func (s *ModelsSuite) SetUpSuite(c *check.C) { config.Conf.DBPath = ":memory:" + config.Conf.MigrationsPath = "../db/migrations/" err := Setup() if err != nil { c.Fatalf("Failed creating database: %v", err) From 655faaf72bee2dfe6147afb2225463f898b61425 Mon Sep 17 00:00:00 2001 From: Jordan Wright Date: Wed, 20 Jan 2016 18:45:53 -0600 Subject: [PATCH 20/24] Added ability to export as CSV. Fixes #34 --- static/js/app/campaign_results.js | 20 ++++++++++++++++++++ static/js/papaparse.min.js | 6 ++++++ templates/campaign_results.html | 22 ++++++---------------- 3 files changed, 32 insertions(+), 16 deletions(-) create mode 100644 static/js/papaparse.min.js diff --git a/static/js/app/campaign_results.js b/static/js/app/campaign_results.js index 47595fae..e3953f50 100644 --- a/static/js/app/campaign_results.js +++ b/static/js/app/campaign_results.js @@ -56,6 +56,26 @@ function deleteCampaign() { } } +// Exports campaign results as a CSV file +function exportAsCSV() { + exportHTML = $("#exportButton").html() + $("#exportButton").html('') + 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] api.campaignId.get(campaign.id) diff --git a/static/js/papaparse.min.js b/static/js/papaparse.min.js new file mode 100644 index 00000000..a62a9266 --- /dev/null +++ b/static/js/papaparse.min.js @@ -0,0 +1,6 @@ +/*! + Papa Parse + v4.1.2 + https://github.com/mholt/PapaParse +*/ +!function(e){"use strict";function t(t,r){if(r=r||{},r.worker&&S.WORKERS_SUPPORTED){var n=f();return n.userStep=r.step,n.userChunk=r.chunk,n.userComplete=r.complete,n.userError=r.error,r.step=m(r.step),r.chunk=m(r.chunk),r.complete=m(r.complete),r.error=m(r.error),delete r.worker,void n.postMessage({input:t,config:r,workerId:n.id})}var o=null;return"string"==typeof t?o=r.download?new i(r):new a(r):(e.File&&t instanceof File||t instanceof Object)&&(o=new s(r)),o.stream(t)}function r(e,t){function r(){"object"==typeof t&&("string"==typeof t.delimiter&&1==t.delimiter.length&&-1==S.BAD_DELIMITERS.indexOf(t.delimiter)&&(u=t.delimiter),("boolean"==typeof t.quotes||t.quotes instanceof Array)&&(o=t.quotes),"string"==typeof t.newline&&(h=t.newline))}function n(e){if("object"!=typeof e)return[];var t=[];for(var r in e)t.push(r);return t}function i(e,t){var r="";"string"==typeof e&&(e=JSON.parse(e)),"string"==typeof t&&(t=JSON.parse(t));var n=e instanceof Array&&e.length>0,i=!(t[0]instanceof Array);if(n){for(var a=0;a0&&(r+=u),r+=s(e[a],a);t.length>0&&(r+=h)}for(var o=0;oc;c++){c>0&&(r+=u);var d=n&&i?e[c]:c;r+=s(t[o][d],c)}o-1||" "==e.charAt(0)||" "==e.charAt(e.length-1);return r?'"'+e+'"':e}function a(e,t){for(var r=0;r-1)return!0;return!1}var o=!1,u=",",h="\r\n";if(r(),"string"==typeof e&&(e=JSON.parse(e)),e instanceof Array){if(!e.length||e[0]instanceof Array)return i(null,e);if("object"==typeof e[0])return i(n(e[0]),e)}else if("object"==typeof e)return"string"==typeof e.data&&(e.data=JSON.parse(e.data)),e.data instanceof Array&&(e.fields||(e.fields=e.data[0]instanceof Array?e.fields:n(e.data[0])),e.data[0]instanceof Array||"object"==typeof e.data[0]||(e.data=[e.data])),i(e.fields||[],e.data||[]);throw"exception: Unable to serialize unrecognized input"}function n(t){function r(e){var t=_(e);t.chunkSize=parseInt(t.chunkSize),e.step||e.chunk||(t.chunkSize=null),this._handle=new o(t),this._handle.streamer=this,this._config=t}this._handle=null,this._paused=!1,this._finished=!1,this._input=null,this._baseIndex=0,this._partialLine="",this._rowCount=0,this._start=0,this._nextChunk=null,this.isFirstChunk=!0,this._completeResults={data:[],errors:[],meta:{}},r.call(this,t),this.parseChunk=function(t){if(this.isFirstChunk&&m(this._config.beforeFirstChunk)){var r=this._config.beforeFirstChunk(t);void 0!==r&&(t=r)}this.isFirstChunk=!1;var n=this._partialLine+t;this._partialLine="";var i=this._handle.parse(n,this._baseIndex,!this._finished);if(!this._handle.paused()&&!this._handle.aborted()){var s=i.meta.cursor;this._finished||(this._partialLine=n.substring(s-this._baseIndex),this._baseIndex=s),i&&i.data&&(this._rowCount+=i.data.length);var a=this._finished||this._config.preview&&this._rowCount>=this._config.preview;if(y)e.postMessage({results:i,workerId:S.WORKER_ID,finished:a});else if(m(this._config.chunk)){if(this._config.chunk(i,this._handle),this._paused)return;i=void 0,this._completeResults=void 0}return this._config.step||this._config.chunk||(this._completeResults.data=this._completeResults.data.concat(i.data),this._completeResults.errors=this._completeResults.errors.concat(i.errors),this._completeResults.meta=i.meta),!a||!m(this._config.complete)||i&&i.meta.aborted||this._config.complete(this._completeResults),a||i&&i.meta.paused||this._nextChunk(),i}},this._sendError=function(t){m(this._config.error)?this._config.error(t):y&&this._config.error&&e.postMessage({workerId:S.WORKER_ID,error:t,finished:!1})}}function i(e){function t(e){var t=e.getResponseHeader("Content-Range");return parseInt(t.substr(t.lastIndexOf("/")+1))}e=e||{},e.chunkSize||(e.chunkSize=S.RemoteChunkSize),n.call(this,e);var r;this._nextChunk=k?function(){this._readChunk(),this._chunkLoaded()}:function(){this._readChunk()},this.stream=function(e){this._input=e,this._nextChunk()},this._readChunk=function(){if(this._finished)return void this._chunkLoaded();if(r=new XMLHttpRequest,k||(r.onload=g(this._chunkLoaded,this),r.onerror=g(this._chunkError,this)),r.open("GET",this._input,!k),this._config.chunkSize){var e=this._start+this._config.chunkSize-1;r.setRequestHeader("Range","bytes="+this._start+"-"+e),r.setRequestHeader("If-None-Match","webkit-no-cache")}try{r.send()}catch(t){this._chunkError(t.message)}k&&0==r.status?this._chunkError():this._start+=this._config.chunkSize},this._chunkLoaded=function(){if(4==r.readyState){if(r.status<200||r.status>=400)return void this._chunkError();this._finished=!this._config.chunkSize||this._start>t(r),this.parseChunk(r.responseText)}},this._chunkError=function(e){var t=r.statusText||e;this._sendError(t)}}function s(e){e=e||{},e.chunkSize||(e.chunkSize=S.LocalChunkSize),n.call(this,e);var t,r,i="undefined"!=typeof FileReader;this.stream=function(e){this._input=e,r=e.slice||e.webkitSlice||e.mozSlice,i?(t=new FileReader,t.onload=g(this._chunkLoaded,this),t.onerror=g(this._chunkError,this)):t=new FileReaderSync,this._nextChunk()},this._nextChunk=function(){this._finished||this._config.preview&&!(this._rowCount=this._input.size,this.parseChunk(e.target.result)},this._chunkError=function(){this._sendError(t.error)}}function a(e){e=e||{},n.call(this,e);var t,r;this.stream=function(e){return t=e,r=e,this._nextChunk()},this._nextChunk=function(){if(!this._finished){var e=this._config.chunkSize,t=e?r.substr(0,e):r;return r=e?r.substr(e):"",this._finished=!r,this.parseChunk(t)}}}function o(e){function t(){if(b&&d&&(h("Delimiter","UndetectableDelimiter","Unable to auto-detect delimiting character; defaulted to '"+S.DefaultDelimiter+"'"),d=!1),e.skipEmptyLines)for(var t=0;t=y.length?(r.__parsed_extra||(r.__parsed_extra=[]),r.__parsed_extra.push(b.data[t][n])):r[y[n]]=b.data[t][n])}e.header&&(b.data[t]=r,n>y.length?h("FieldMismatch","TooManyFields","Too many fields: expected "+y.length+" fields but parsed "+n,t):n1&&(h+=Math.abs(l-i),i=l):i=l}c.data.length>0&&(f/=c.data.length),("undefined"==typeof n||n>h)&&f>1.99&&(n=h,r=o)}return e.delimiter=r,{successful:!!r,bestDelimiter:r}}function a(e){e=e.substr(0,1048576);var t=e.split("\r");if(1==t.length)return"\n";for(var r=0,n=0;n=t.length/2?"\r\n":"\r"}function o(e){var t=l.test(e);return t?parseFloat(e):e}function h(e,t,r,n){b.errors.push({type:e,code:t,message:r,row:n})}var f,c,d,l=/^\s*-?(\d*\.?\d+|\d+\.?\d*)(e[-+]?\d+)?\s*$/i,p=this,g=0,v=!1,k=!1,y=[],b={data:[],errors:[],meta:{}};if(m(e.step)){var R=e.step;e.step=function(n){if(b=n,r())t();else{if(t(),0==b.data.length)return;g+=n.data.length,e.preview&&g>e.preview?c.abort():R(b,p)}}}this.parse=function(r,n,i){if(e.newline||(e.newline=a(r)),d=!1,!e.delimiter){var o=s(r);o.successful?e.delimiter=o.bestDelimiter:(d=!0,e.delimiter=S.DefaultDelimiter),b.meta.delimiter=e.delimiter}var h=_(e);return e.preview&&e.header&&h.preview++,f=r,c=new u(h),b=c.parse(f,n,i),t(),v?{meta:{paused:!0}}:b||{meta:{paused:!1}}},this.paused=function(){return v},this.pause=function(){v=!0,c.abort(),f=f.substr(c.getCharIndex())},this.resume=function(){v=!1,p.streamer.parseChunk(f)},this.aborted=function(){return k},this.abort=function(){k=!0,c.abort(),b.meta.aborted=!0,m(e.complete)&&e.complete(b),f=""}}function u(e){e=e||{};var t=e.delimiter,r=e.newline,n=e.comments,i=e.step,s=e.preview,a=e.fastMode;if(("string"!=typeof t||S.BAD_DELIMITERS.indexOf(t)>-1)&&(t=","),n===t)throw"Comment character same as delimiter";n===!0?n="#":("string"!=typeof n||S.BAD_DELIMITERS.indexOf(n)>-1)&&(n=!1),"\n"!=r&&"\r"!=r&&"\r\n"!=r&&(r="\n");var o=0,u=!1;this.parse=function(e,h,f){function c(e){b.push(e),S=o}function d(t){return f?p():("undefined"==typeof t&&(t=e.substr(o)),w.push(t),o=g,c(w),y&&_(),p())}function l(t){o=t,c(w),w=[],O=e.indexOf(r,o)}function p(e){return{data:b,errors:R,meta:{delimiter:t,linebreak:r,aborted:u,truncated:!!e,cursor:S+(h||0)}}}function _(){i(p()),b=[],R=[]}if("string"!=typeof e)throw"Input must be a string";var g=e.length,m=t.length,v=r.length,k=n.length,y="function"==typeof i;o=0;var b=[],R=[],w=[],S=0;if(!e)return p();if(a||a!==!1&&-1===e.indexOf('"')){for(var C=e.split(r),E=0;E=s)return b=b.slice(0,s),p(!0)}}return p()}for(var x=e.indexOf(t,o),O=e.indexOf(r,o);;)if('"'!=e[o])if(n&&0===w.length&&e.substr(o,k)===n){if(-1==O)return p();o=O+v,O=e.indexOf(r,o),x=e.indexOf(t,o)}else if(-1!==x&&(O>x||-1===O))w.push(e.substring(o,x)),o=x+m,x=e.indexOf(t,o);else{if(-1===O)break;if(w.push(e.substring(o,O)),l(O+v),y&&(_(),u))return p();if(s&&b.length>=s)return p(!0)}else{var I=o;for(o++;;){var I=e.indexOf('"',I+1);if(-1===I)return f||R.push({type:"Quotes",code:"MissingQuotes",message:"Quoted field unterminated",row:b.length,index:o}),d();if(I===g-1){var D=e.substring(o,I).replace(/""/g,'"');return d(D)}if('"'!=e[I+1]){if(e[I+1]==t){w.push(e.substring(o,I).replace(/""/g,'"')),o=I+1+m,x=e.indexOf(t,o),O=e.indexOf(r,o);break}if(e.substr(I+1,v)===r){if(w.push(e.substring(o,I).replace(/""/g,'"')),l(I+1+v),x=e.indexOf(t,o),y&&(_(),u))return p();if(s&&b.length>=s)return p(!0);break}}else I++}}return d()},this.abort=function(){u=!0},this.getCharIndex=function(){return o}}function h(){var e=document.getElementsByTagName("script");return e.length?e[e.length-1].src:""}function f(){if(!S.WORKERS_SUPPORTED)return!1;if(!b&&null===S.SCRIPT_PATH)throw new Error("Script path cannot be determined automatically when Papa Parse is loaded asynchronously. You need to set Papa.SCRIPT_PATH manually.");var t=S.SCRIPT_PATH||v;t+=(-1!==t.indexOf("?")?"&":"?")+"papaworker";var r=new e.Worker(t);return r.onmessage=c,r.id=w++,R[r.id]=r,r}function c(e){var t=e.data,r=R[t.workerId],n=!1;if(t.error)r.userError(t.error,t.file);else if(t.results&&t.results.data){var i=function(){n=!0,d(t.workerId,{data:[],errors:[],meta:{aborted:!0}})},s={abort:i,pause:l,resume:l};if(m(r.userStep)){for(var a=0;aResults for campaign.name
    - - +

    @@ -114,5 +103,6 @@ + {{end}} From 52e5c6051142f9e5c7b8866e3c8eccda180831cf Mon Sep 17 00:00:00 2001 From: Jordan Wright Date: Wed, 20 Jan 2016 19:00:32 -0600 Subject: [PATCH 21/24] Changed button on campaign to say "Launch Campaign" - minor formatting fixes. --- static/js/app/campaigns.js | 9 ++++++--- templates/campaigns.html | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/static/js/app/campaigns.js b/static/js/app/campaigns.js index 6f009d1d..3c5e18a2 100644 --- a/static/js/app/campaigns.js +++ b/static/js/app/campaigns.js @@ -11,7 +11,10 @@ var labels = { var campaigns = [] // Save attempts to POST to /campaigns/ -function save() { +function launch() { + if (!confirm("This will launch the campaign. Are you sure?")) { + return false; + } groups = [] $.each($("#groupTable").DataTable().rows().data(), function(i, group) { groups.push({ @@ -58,7 +61,7 @@ function deleteCampaign(idx) { api.campaignId.delete(campaigns[idx].id) .success(function(data) { successFlash(data.message) - location.reload() + location.reload() }) } } @@ -102,7 +105,7 @@ function edit(campaign) { $(document).ready(function() { api.campaigns.get() .success(function(cs) { - campaigns = cs + campaigns = cs $("#loading").hide() if (campaigns.length > 0) { $("#campaignTable").show() diff --git a/templates/campaigns.html b/templates/campaigns.html index 8c8354c6..16732340 100644 --- a/templates/campaigns.html +++ b/templates/campaigns.html @@ -121,7 +121,7 @@
    From 22c2f659e8dc7915bc847e76ea99e79985ad2cd0 Mon Sep 17 00:00:00 2001 From: Justin Gray Date: Wed, 20 Jan 2016 19:42:41 -0600 Subject: [PATCH 22/24] Fixed the attachments table Fixed the sorting on the attachments table, there could be opportunity to clean this up a little. --- static/js/app/templates.js | 28 ++++++++++++++++++++++------ templates/templates.html | 8 ++++---- 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/static/js/app/templates.js b/static/js/app/templates.js index 85ece065..48dd0f1c 100644 --- a/static/js/app/templates.js +++ b/static/js/app/templates.js @@ -78,7 +78,14 @@ function deleteTemplate(idx){ } function attach(files){ - attachmentsTable = $("#attachmentsTable").DataTable(); + attachmentsTable = $("#attachmentsTable").DataTable({ + 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(); /* Make this a datatable */ @@ -107,14 +114,23 @@ function edit(idx){ $("#attachmentsTable").show() attachmentsTable = null if ( $.fn.dataTable.isDataTable('#attachmentsTable') ) { - attachmentsTable = $('#attachmentsTable').DataTable(); + attachmentsTable = $('#attachmentsTable').DataTable({ + destroy: true, + "order": [[ 1, "asc" ]], + columnDefs: [ + { orderable: false, targets: "no-sort" }, + { sClass: "datatable_hidden", targets:[3,4]} + ] + }); } else { attachmentsTable = $("#attachmentsTable").DataTable({ - "aoColumnDefs" : [{ - "targets" : [3,4], - "sClass" : "datatable_hidden" - }] + destroy: true, + "order": [[ 1, "asc" ]], + columnDefs: [ + { orderable: false, targets: "no-sort" }, + { sClass: "datatable_hidden", targets:[3,4]} + ] }); } var template = {attachments:[]} diff --git a/templates/templates.html b/templates/templates.html index ac84b37f..5be682f1 100644 --- a/templates/templates.html +++ b/templates/templates.html @@ -102,11 +102,11 @@ - + - - - + + + From 0bdc4a751d1a43bfed334f96017340bae822625d Mon Sep 17 00:00:00 2001 From: Justin Gray Date: Wed, 20 Jan 2016 20:33:59 -0600 Subject: [PATCH 23/24] Remove unnecessary code Removed check and just always destroy and rebuild datatables. --- static/js/app/templates.js | 29 ++++++++--------------------- 1 file changed, 8 insertions(+), 21 deletions(-) diff --git a/static/js/app/templates.js b/static/js/app/templates.js index 48dd0f1c..fb04e267 100644 --- a/static/js/app/templates.js +++ b/static/js/app/templates.js @@ -112,27 +112,14 @@ function edit(idx){ $("#attachmentUpload").unbind('click').click(function(){this.value=null}) $("#html_editor").ckeditor() $("#attachmentsTable").show() - attachmentsTable = null - if ( $.fn.dataTable.isDataTable('#attachmentsTable') ) { - attachmentsTable = $('#attachmentsTable').DataTable({ - destroy: true, - "order": [[ 1, "asc" ]], - columnDefs: [ - { orderable: false, targets: "no-sort" }, - { sClass: "datatable_hidden", targets:[3,4]} - ] - }); - } - else { - attachmentsTable = $("#attachmentsTable").DataTable({ - destroy: true, - "order": [[ 1, "asc" ]], - columnDefs: [ - { orderable: false, targets: "no-sort" }, - { sClass: "datatable_hidden", targets:[3,4]} - ] - }); - } + attachmentsTable = $('#attachmentsTable').DataTable({ + destroy: true, + "order": [[ 1, "asc" ]], + columnDefs: [ + { orderable: false, targets: "no-sort" }, + { sClass: "datatable_hidden", targets:[3,4]} + ] + }); var template = {attachments:[]} if (idx != -1) { template = templates[idx] From 964ce6f1525aa486dde613403ad8c2780a53bd95 Mon Sep 17 00:00:00 2001 From: Justin Gray Date: Wed, 20 Jan 2016 20:35:51 -0600 Subject: [PATCH 24/24] Fixed Double Instantiation When a modal was closed and the data was loaded again it tried to instantiate the table again, so always destroy the table on load. --- static/js/app/landing_pages.js | 1 + static/js/app/templates.js | 1 + static/js/app/users.js | 1 + 3 files changed, 3 insertions(+) diff --git a/static/js/app/landing_pages.js b/static/js/app/landing_pages.js index edbead61..c1f809f0 100644 --- a/static/js/app/landing_pages.js +++ b/static/js/app/landing_pages.js @@ -93,6 +93,7 @@ function load(){ if (pages.length > 0){ $("#pagesTable").show() pagesTable = $("#pagesTable").DataTable({ + destroy: true, columnDefs: [ { orderable: false, targets: "no-sort" } ] diff --git a/static/js/app/templates.js b/static/js/app/templates.js index fb04e267..fde003fe 100644 --- a/static/js/app/templates.js +++ b/static/js/app/templates.js @@ -182,6 +182,7 @@ function load(){ if (templates.length > 0){ $("#templateTable").show() templateTable = $("#templateTable").DataTable({ + destroy: true, columnDefs: [ { orderable: false, targets: "no-sort" } ] diff --git a/static/js/app/users.js b/static/js/app/users.js index 5a9a7d37..b8f116cf 100644 --- a/static/js/app/users.js +++ b/static/js/app/users.js @@ -125,6 +125,7 @@ function load(){ $("#emptyMessage").hide() $("#groupTable").show() groupTable = $("#groupTable").DataTable({ + destroy: true, columnDefs: [ { orderable: false, targets: "no-sort" } ]
    NameContentTypeContentType