From e198ba98af1a224e156b6a9d9f2cbf99d15665e5 Mon Sep 17 00:00:00 2001 From: Pavel Tsakalidis Date: Thu, 8 Apr 2021 13:51:10 +0100 Subject: [PATCH] Implement functionality to support custom RId - Ability to specify both the character set and length --- .../20210408110421_0.11.1_custom_rid.sql | 9 ++++ .../20210408110430_0.11.1_custom_rid.sql | 9 ++++ models/campaign.go | 54 +++++++++++++++++-- models/email_request.go | 2 +- models/result.go | 22 ++++++-- models/result_test.go | 12 ++++- static/js/dist/app/campaigns.min.js | 2 +- static/js/src/app/campaigns.js | 22 ++++++++ templates/campaigns.html | 24 ++++++++- 9 files changed, 143 insertions(+), 13 deletions(-) create mode 100644 db/db_mysql/migrations/20210408110421_0.11.1_custom_rid.sql create mode 100644 db/db_sqlite3/migrations/20210408110430_0.11.1_custom_rid.sql diff --git a/db/db_mysql/migrations/20210408110421_0.11.1_custom_rid.sql b/db/db_mysql/migrations/20210408110421_0.11.1_custom_rid.sql new file mode 100644 index 00000000..501cac16 --- /dev/null +++ b/db/db_mysql/migrations/20210408110421_0.11.1_custom_rid.sql @@ -0,0 +1,9 @@ + +-- +goose Up +-- SQL in section 'Up' is executed when this migration is applied +ALTER TABLE `campaigns` ADD COLUMN character_set VARCHAR(255); +ALTER TABLE `campaigns` ADD COLUMN r_id_length INTEGER; + +-- +goose Down +-- SQL section 'Down' is executed when this migration is rolled back + diff --git a/db/db_sqlite3/migrations/20210408110430_0.11.1_custom_rid.sql b/db/db_sqlite3/migrations/20210408110430_0.11.1_custom_rid.sql new file mode 100644 index 00000000..c13f1030 --- /dev/null +++ b/db/db_sqlite3/migrations/20210408110430_0.11.1_custom_rid.sql @@ -0,0 +1,9 @@ + +-- +goose Up +-- SQL in section 'Up' is executed when this migration is applied +ALTER TABLE campaigns ADD COLUMN character_set VARCHAR(255); +ALTER TABLE campaigns ADD COLUMN r_id_length INTEGER; + +-- +goose Down +-- SQL section 'Down' is executed when this migration is rolled back + diff --git a/models/campaign.go b/models/campaign.go index a9e24382..ffa5e8b5 100644 --- a/models/campaign.go +++ b/models/campaign.go @@ -2,7 +2,9 @@ package models import ( "errors" + "math" "net/url" + "strings" "time" log "github.com/gophish/gophish/logger" @@ -31,6 +33,9 @@ type Campaign struct { SMTPId int64 `json:"-"` SMTP SMTP `json:"smtp"` URL string `json:"url"` + CustomRId bool `json:"custom_rid" gorm:"-"` + CharacterSet string `json:"character_set" sql:"not null"` + RIdLength int64 `json:"r_id_length"` } // CampaignResults is a struct representing the results from a campaign @@ -58,6 +63,8 @@ type CampaignSummary struct { Status string `json:"status"` Name string `json:"name"` Stats CampaignStats `json:"stats"` + CharacterSet string `json:"character_set"` + RIdLength int64 `json:"r_id_length"` } // CampaignStats is a struct representing the statistics for a single campaign @@ -126,6 +133,15 @@ var ErrSMTPNotFound = errors.New("Sending profile not found") // launch date var ErrInvalidSendByDate = errors.New("The launch date must be before the \"send emails by\" date") +// ErrInvalidCharacterSet indicates that the user has entered an invalid character set, ie it's empty +var ErrInvalidCharacterSet = errors.New("The defined character set is invalid") + +// ErrInvalidRIdLength indicates that an invalid RId length has been set (ie zero) +var ErrInvalidRIdLength = errors.New("The RId length entered is invalid") + +// ErrInsufficientCharsetKeyspace indicates that there aren't enough RId combinations to match the total recipients, for the given CharacterSet and RIdLength. +var ErrInsufficientCharsetKeyspace = errors.New("The specified CharacterSet/Length combination cannot cover the total recipients in this campaign. Consider increasing the Character Set, RId Length, or both"); + // RecipientParameter is the URL parameter that points to the result ID for a recipient. const RecipientParameter = "rid" @@ -144,6 +160,12 @@ func (c *Campaign) Validate() error { return ErrSMTPNotSpecified case !c.SendByDate.IsZero() && !c.LaunchDate.IsZero() && c.SendByDate.Before(c.LaunchDate): return ErrInvalidSendByDate + case c.CustomRId: + if c.CharacterSet == "" { + return ErrInvalidCharacterSet + } else if c.RIdLength <= 0 { + return ErrInvalidRIdLength + } } return nil } @@ -324,7 +346,7 @@ func GetCampaignSummaries(uid int64) (CampaignSummaries, error) { cs := []CampaignSummary{} // Get the basic campaign information query := db.Table("campaigns").Where("user_id = ?", uid) - query = query.Select("id, name, created_date, launch_date, send_by_date, completed_date, status") + query = query.Select("id, name, created_date, launch_date, send_by_date, completed_date, status, character_set, r_id_length") err := query.Scan(&cs).Error if err != nil { log.Error(err) @@ -347,7 +369,7 @@ func GetCampaignSummaries(uid int64) (CampaignSummaries, error) { func GetCampaignSummary(id int64, uid int64) (CampaignSummary, error) { cs := CampaignSummary{} query := db.Table("campaigns").Where("user_id = ? AND id = ?", uid, id) - query = query.Select("id, name, created_date, launch_date, send_by_date, completed_date, status") + query = query.Select("id, name, created_date, launch_date, send_by_date, completed_date, status, character_set, r_id_length") err := query.Scan(&cs).Error if err != nil { log.Error(err) @@ -487,6 +509,19 @@ func PostCampaign(c *Campaign, uid int64) error { } totalRecipients += len(c.Groups[i].Targets) } + + if c.CustomRId { + c.CharacterSet = CleanCharacterSet(c.CharacterSet) + // We need to check if the keyspace for the custom character set and rid is enough to match the totalRecipients. + // This is on purpose <= so that we don't come too close to the limit. + if int(math.Pow(float64(len(c.CharacterSet)), float64(c.RIdLength))) <= totalRecipients { + return ErrInsufficientCharsetKeyspace + } + } else { + c.CharacterSet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + c.RIdLength = 7 + } + // Check to make sure the template exists t, err := GetTemplateByName(c.Template.Name, uid) if err == gorm.ErrRecordNotFound { @@ -564,7 +599,7 @@ func PostCampaign(c *Campaign, uid int64) error { Reported: false, ModifiedDate: c.CreatedDate, } - err = r.GenerateId(tx) + err = r.GenerateId(tx, c.CharacterSet, c.RIdLength) if err != nil { log.Error(err) tx.Rollback() @@ -667,3 +702,16 @@ func CompleteCampaign(id int64, uid int64) error { } return err } + +func CleanCharacterSet(character_set string) string { + keys := make(map[string]bool) + chars := strings.Split(character_set, "") + unique := []string{} + for _, char := range chars { + if _, value := keys[char]; !value { + keys[char] = true + unique = append(unique, char) + } + } + return strings.Join(unique, "") +} diff --git a/models/email_request.go b/models/email_request.go index 35d2e6b9..899196dd 100644 --- a/models/email_request.go +++ b/models/email_request.go @@ -80,7 +80,7 @@ func (s *EmailRequest) Success() error { // PostEmailRequest stores a SendTestEmailRequest in the database. func PostEmailRequest(s *EmailRequest) error { // Generate an ID to be used in the underlying Result object - rid, err := generateResultId() + rid, err := generateResultId("", 0) if err != nil { return err } diff --git a/models/result.go b/models/result.go index 6ad5812f..4b5cc60c 100644 --- a/models/result.go +++ b/models/result.go @@ -3,6 +3,8 @@ package models import ( "crypto/rand" "encoding/json" + "errors" + "math" "math/big" "net" "time" @@ -170,9 +172,12 @@ func (r *Result) UpdateGeo(addr string) error { return db.Save(r).Error } -func generateResultId() (string, error) { - const alphaNum = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" - k := make([]byte, 7) +func generateResultId(alphaNum string, r_id_length int64) (string, error) { + if len(alphaNum) == 0 || r_id_length <= 0 { + alphaNum = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + r_id_length = 7 + } + k := make([]byte, r_id_length) for i := range k { idx, err := rand.Int(rand.Reader, big.NewInt(int64(len(alphaNum)))) if err != nil { @@ -185,10 +190,12 @@ func generateResultId() (string, error) { // GenerateId generates a unique key to represent the result // in the database -func (r *Result) GenerateId(tx *gorm.DB) error { +func (r *Result) GenerateId(tx *gorm.DB, character_set string, r_id_length int64) error { // Keep trying until we generate a unique key (shouldn't take more than one or two iterations) + max_iterations := int(math.Pow(float64(len(character_set)), float64(r_id_length))) + iteration_count := 0 for { - rid, err := generateResultId() + rid, err := generateResultId(character_set, r_id_length) if err != nil { return err } @@ -197,6 +204,11 @@ func (r *Result) GenerateId(tx *gorm.DB) error { if err == gorm.ErrRecordNotFound { break } + + iteration_count++ + if iteration_count >= max_iterations { + return errors.New("Too many iterations - Consider increasing the character set and/or RId length") + } } return nil } diff --git a/models/result_test.go b/models/result_test.go index c0038878..531f58ad 100644 --- a/models/result_test.go +++ b/models/result_test.go @@ -10,10 +10,20 @@ import ( func (s *ModelsSuite) TestGenerateResultId(c *check.C) { r := Result{} - r.GenerateId(db) + r.GenerateId(db, "", 0) match, err := regexp.Match("[a-zA-Z0-9]{7}", []byte(r.RId)) c.Assert(err, check.Equals, nil) c.Assert(match, check.Equals, true) + + r.GenerateId(db, "0123456789", 4) + match, err = regexp.Match("[0-9]{4}", []byte(r.RId)) + c.Assert(err, check.Equals, nil) + c.Assert(match, check.Equals, true) + + r.GenerateId(db, "abcdefghijklmnopqrstuvwxyz", 3) + match, err = regexp.Match("[a-z]{3}", []byte(r.RId)) + c.Assert(err, check.Equals, nil) + c.Assert(match, check.Equals, true) } func (s *ModelsSuite) TestFormatAddress(c *check.C) { diff --git a/static/js/dist/app/campaigns.min.js b/static/js/dist/app/campaigns.min.js index ed63d1eb..07be5069 100644 --- a/static/js/dist/app/campaigns.min.js +++ b/static/js/dist/app/campaigns.min.js @@ -1 +1 @@ -var labels={"In progress":"label-primary",Queued:"label-info",Completed:"label-success","Emails Sent":"label-success",Error:"label-danger"},campaigns=[],campaign={};function launch(){Swal.fire({title:"Are you sure?",text:"This will schedule the campaign to be launched.",type:"question",animation:!1,showCancelButton:!0,confirmButtonText:"Launch",confirmButtonColor:"#428bca",reverseButtons:!0,allowOutsideClick:!1,showLoaderOnConfirm:!0,preConfirm:function(){return new Promise(function(a,e){groups=[],$("#users").select2("data").forEach(function(e){groups.push({name:e.text})});var t=$("#send_by_date").val();""!=t&&(t=moment(t,"MMMM Do YYYY, h:mm a").utc().format()),campaign={name:$("#name").val(),template:{name:$("#template").select2("data")[0].text},url:$("#url").val(),page:{name:$("#page").select2("data")[0].text},smtp:{name:$("#profile").select2("data")[0].text},launch_date:moment($("#launch_date").val(),"MMMM Do YYYY, h:mm a").utc().format(),send_by_date:t||null,groups:groups},api.campaigns.post(campaign).success(function(e){a(),campaign=e}).error(function(e){$("#modal\\.flashes").empty().append('
'+e.responseJSON.message+"
"),Swal.close()})})}}).then(function(e){e.value&&Swal.fire("Campaign Scheduled!","This campaign has been scheduled for launch!","success"),$('button:contains("OK")').on("click",function(){window.location="/campaigns/"+campaign.id.toString()})})}function sendTestEmail(){var e={template:{name:$("#template").select2("data")[0].text},first_name:$("input[name=to_first_name]").val(),last_name:$("input[name=to_last_name]").val(),email:$("input[name=to_email]").val(),position:$("input[name=to_position]").val(),url:$("#url").val(),page:{name:$("#page").select2("data")[0].text},smtp:{name:$("#profile").select2("data")[0].text}};btnHtml=$("#sendTestModalSubmit").html(),$("#sendTestModalSubmit").html(' Sending'),api.send_test_email(e).success(function(e){$("#sendTestEmailModal\\.flashes").empty().append('
Email Sent!
'),$("#sendTestModalSubmit").html(btnHtml)}).error(function(e){$("#sendTestEmailModal\\.flashes").empty().append('
'+e.responseJSON.message+"
"),$("#sendTestModalSubmit").html(btnHtml)})}function dismiss(){$("#modal\\.flashes").empty(),$("#name").val(""),$("#template").val("").change(),$("#page").val("").change(),$("#url").val(""),$("#profile").val("").change(),$("#users").val("").change(),$("#modal").modal("hide")}function deleteCampaign(e){Swal.fire({title:"Are you sure?",text:"This will delete the campaign. This can't be undone!",type:"warning",animation:!1,showCancelButton:!0,confirmButtonText:"Delete "+campaigns[e].name,confirmButtonColor:"#428bca",reverseButtons:!0,allowOutsideClick:!1,preConfirm:function(){return new Promise(function(a,t){api.campaignId.delete(campaigns[e].id).success(function(e){a()}).error(function(e){t(e.responseJSON.message)})})}}).then(function(e){e.value&&Swal.fire("Campaign Deleted!","This campaign has been deleted!","success"),$('button:contains("OK")').on("click",function(){location.reload()})})}function setupOptions(){api.groups.summary().success(function(e){if(groups=e.groups,0==groups.length)return modalError("No groups found!"),!1;var a=$.map(groups,function(e){return e.text=e.name,e.title=e.num_targets+" targets",e});console.log(a),$("#users.form-control").select2({placeholder:"Select Groups",data:a})}),api.templates.get().success(function(e){if(0==e.length)return modalError("No templates found!"),!1;var a=$.map(e,function(e){return e.text=e.name,e}),t=$("#template.form-control");t.select2({placeholder:"Select a Template",data:a}),1===e.length&&(t.val(a[0].id),t.trigger("change.select2"))}),api.pages.get().success(function(e){if(0==e.length)return modalError("No pages found!"),!1;var a=$.map(e,function(e){return e.text=e.name,e}),t=$("#page.form-control");t.select2({placeholder:"Select a Landing Page",data:a}),1===e.length&&(t.val(a[0].id),t.trigger("change.select2"))}),api.SMTP.get().success(function(e){if(0==e.length)return modalError("No profiles found!"),!1;var a=$.map(e,function(e){return e.text=e.name,e}),t=$("#profile.form-control");t.select2({placeholder:"Select a Sending Profile",data:a}).select2("val",a[0]),1===e.length&&(t.val(a[0].id),t.trigger("change.select2"))})}function edit(e){setupOptions()}function copy(e){setupOptions(),api.campaignId.get(campaigns[e].id).success(function(e){$("#name").val("Copy of "+e.name),e.template.id?($("#template").val(e.template.id.toString()),$("#template").trigger("change.select2")):$("#template").select2({placeholder:e.template.name}),e.page.id?($("#page").val(e.page.id.toString()),$("#page").trigger("change.select2")):$("#page").select2({placeholder:e.page.name}),e.smtp.id?($("#profile").val(e.smtp.id.toString()),$("#profile").trigger("change.select2")):$("#profile").select2({placeholder:e.smtp.name}),$("#url").val(e.url)}).error(function(e){$("#modal\\.flashes").empty().append('
'+e.responseJSON.message+"
")})}$(document).ready(function(){$("#launch_date").datetimepicker({widgetPositioning:{vertical:"bottom"},showTodayButton:!0,defaultDate:moment(),format:"MMMM Do YYYY, h:mm a"}),$("#send_by_date").datetimepicker({widgetPositioning:{vertical:"bottom"},showTodayButton:!0,useCurrent:!1,format:"MMMM Do YYYY, h:mm a"}),$(".modal").on("hidden.bs.modal",function(e){$(this).removeClass("fv-modal-stack"),$("body").data("fv_open_modals",$("body").data("fv_open_modals")-1)}),$(".modal").on("shown.bs.modal",function(e){void 0===$("body").data("fv_open_modals")&&$("body").data("fv_open_modals",0),$(this).hasClass("fv-modal-stack")||($(this).addClass("fv-modal-stack"),$("body").data("fv_open_modals",$("body").data("fv_open_modals")+1),$(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"))}),$(document).on("hidden.bs.modal",".modal",function(){$(".modal:visible").length&&$(document.body).addClass("modal-open")}),$("#modal").on("hidden.bs.modal",function(e){dismiss()}),api.campaigns.summary().success(function(e){campaigns=e.campaigns,$("#loading").hide(),0
Number of recipients: "+a.stats.total;else t="Launch Date: "+moment(a.launch_date).format("MMMM Do YYYY, h:mm:ss a")+"

Number of recipients: "+a.stats.total+"

Emails opened: "+a.stats.opened+"

Emails clicked: "+a.stats.clicked+"

Submitted Credentials: "+a.stats.submitted_data+"

Errors : "+a.stats.error+"

Reported : "+a.stats.email_reported;var n=[escapeHtml(a.name),moment(a.created_date).format("MMMM Do YYYY, h:mm:ss a"),''+a.status+"",""];"Completed"==a.status?rows.archived.push(n):rows.active.push(n)}),activeCampaignsTable.rows.add(rows.active).draw(),archivedCampaignsTable.rows.add(rows.archived).draw(),$('[data-toggle="tooltip"]').tooltip()):$("#emptyMessage").show()}).error(function(){$("#loading").hide(),errorFlash("Error fetching campaigns")}),$.fn.select2.defaults.set("width","100%"),$.fn.select2.defaults.set("dropdownParent",$("#modal_body")),$.fn.select2.defaults.set("theme","bootstrap"),$.fn.select2.defaults.set("sorter",function(e){return e.sort(function(e,a){return e.text.toLowerCase()>a.text.toLowerCase()?1:e.text.toLowerCase() '+e.responseJSON.message+""),Swal.close()})})}}).then(function(e){e.value&&Swal.fire("Campaign Scheduled!","This campaign has been scheduled for launch!","success"),$('button:contains("OK")').on("click",function(){window.location="/campaigns/"+campaign.id.toString()})})}function sendTestEmail(){var e={template:{name:$("#template").select2("data")[0].text},first_name:$("input[name=to_first_name]").val(),last_name:$("input[name=to_last_name]").val(),email:$("input[name=to_email]").val(),position:$("input[name=to_position]").val(),url:$("#url").val(),page:{name:$("#page").select2("data")[0].text},smtp:{name:$("#profile").select2("data")[0].text}};btnHtml=$("#sendTestModalSubmit").html(),$("#sendTestModalSubmit").html(' Sending'),api.send_test_email(e).success(function(e){$("#sendTestEmailModal\\.flashes").empty().append('
Email Sent!
'),$("#sendTestModalSubmit").html(btnHtml)}).error(function(e){$("#sendTestEmailModal\\.flashes").empty().append('
'+e.responseJSON.message+"
"),$("#sendTestModalSubmit").html(btnHtml)})}function dismiss(){$("#modal\\.flashes").empty(),$("#name").val(""),$("#template").val("").change(),$("#page").val("").change(),$("#url").val(""),$("#profile").val("").change(),$("#users").val("").change(),$("#modal").modal("hide")}function deleteCampaign(e){Swal.fire({title:"Are you sure?",text:"This will delete the campaign. This can't be undone!",type:"warning",animation:!1,showCancelButton:!0,confirmButtonText:"Delete "+campaigns[e].name,confirmButtonColor:"#428bca",reverseButtons:!0,allowOutsideClick:!1,preConfirm:function(){return new Promise(function(a,t){api.campaignId.delete(campaigns[e].id).success(function(e){a()}).error(function(e){t(e.responseJSON.message)})})}}).then(function(e){e.value&&Swal.fire("Campaign Deleted!","This campaign has been deleted!","success"),$('button:contains("OK")').on("click",function(){location.reload()})})}function setupOptions(){api.groups.summary().success(function(e){if(groups=e.groups,0==groups.length)return modalError("No groups found!"),!1;var a=$.map(groups,function(e){return e.text=e.name,e.title=e.num_targets+" targets",e});console.log(a),$("#users.form-control").select2({placeholder:"Select Groups",data:a})}),api.templates.get().success(function(e){if(0==e.length)return modalError("No templates found!"),!1;var a=$.map(e,function(e){return e.text=e.name,e}),t=$("#template.form-control");t.select2({placeholder:"Select a Template",data:a}),1===e.length&&(t.val(a[0].id),t.trigger("change.select2"))}),api.pages.get().success(function(e){if(0==e.length)return modalError("No pages found!"),!1;var a=$.map(e,function(e){return e.text=e.name,e}),t=$("#page.form-control");t.select2({placeholder:"Select a Landing Page",data:a}),1===e.length&&(t.val(a[0].id),t.trigger("change.select2"))}),api.SMTP.get().success(function(e){if(0==e.length)return modalError("No profiles found!"),!1;var a=$.map(e,function(e){return e.text=e.name,e}),t=$("#profile.form-control");t.select2({placeholder:"Select a Sending Profile",data:a}).select2("val",a[0]),1===e.length&&(t.val(a[0].id),t.trigger("change.select2"))})}function edit(e){setupOptions()}function copy(e){setupOptions(),api.campaignId.get(campaigns[e].id).success(function(e){$("#name").val("Copy of "+e.name),e.template.id?($("#template").val(e.template.id.toString()),$("#template").trigger("change.select2")):$("#template").select2({placeholder:e.template.name}),e.page.id?($("#page").val(e.page.id.toString()),$("#page").trigger("change.select2")):$("#page").select2({placeholder:e.page.name}),e.smtp.id?($("#profile").val(e.smtp.id.toString()),$("#profile").trigger("change.select2")):$("#profile").select2({placeholder:e.smtp.name}),$("#url").val(e.url)}).error(function(e){$("#modal\\.flashes").empty().append('
'+e.responseJSON.message+"
")})}$(document).ready(function(){$("#launch_date").datetimepicker({widgetPositioning:{vertical:"bottom"},showTodayButton:!0,defaultDate:moment(),format:"MMMM Do YYYY, h:mm a"}),$("#send_by_date").datetimepicker({widgetPositioning:{vertical:"bottom"},showTodayButton:!0,useCurrent:!1,format:"MMMM Do YYYY, h:mm a"}),$(".modal").on("hidden.bs.modal",function(e){$(this).removeClass("fv-modal-stack"),$("body").data("fv_open_modals",$("body").data("fv_open_modals")-1)}),$(".modal").on("shown.bs.modal",function(e){void 0===$("body").data("fv_open_modals")&&$("body").data("fv_open_modals",0),$(this).hasClass("fv-modal-stack")||($(this).addClass("fv-modal-stack"),$("body").data("fv_open_modals",$("body").data("fv_open_modals")+1),$(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"))}),$(document).on("hidden.bs.modal",".modal",function(){$(".modal:visible").length&&$(document.body).addClass("modal-open")}),$("#modal").on("hidden.bs.modal",function(e){dismiss()}),api.campaigns.summary().success(function(e){campaigns=e.campaigns,$("#loading").hide(),0
Number of recipients: "+a.stats.total;else t="Launch Date: "+moment(a.launch_date).format("MMMM Do YYYY, h:mm:ss a")+"

Number of recipients: "+a.stats.total+"

Emails opened: "+a.stats.opened+"

Emails clicked: "+a.stats.clicked+"

Submitted Credentials: "+a.stats.submitted_data+"

Errors : "+a.stats.error+"

Reported : "+a.stats.email_reported;var n="";n="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"==a.character_set?"default":escapeHtml(a.character_set)+" (Length: "+a.r_id_length+")";var s=[escapeHtml(a.name),moment(a.created_date).format("MMMM Do YYYY, h:mm:ss a"),''+a.status+"",""+n+"",""];"Completed"==a.status?rows.archived.push(s):rows.active.push(s)}),activeCampaignsTable.rows.add(rows.active).draw(),archivedCampaignsTable.rows.add(rows.archived).draw(),$('[data-toggle="tooltip"]').tooltip()):$("#emptyMessage").show()}).error(function(){$("#loading").hide(),errorFlash("Error fetching campaigns")}),$.fn.select2.defaults.set("width","100%"),$.fn.select2.defaults.set("dropdownParent",$("#modal_body")),$.fn.select2.defaults.set("theme","bootstrap"),$.fn.select2.defaults.set("sorter",function(e){return e.sort(function(e,a){return e.text.toLowerCase()>a.text.toLowerCase()?1:e.text.toLowerCase()
" + "Number of recipients: " + campaign.stats.total + "

" + "Emails opened: " + campaign.stats.opened + "

" + "Emails clicked: " + campaign.stats.clicked + "

" + "Submitted Credentials: " + campaign.stats.submitted_data + "

" + "Errors : " + campaign.stats.error + "

" + "Reported : " + campaign.stats.email_reported } + var charSetText = ""; + if (campaign.character_set == "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") { + charSetText = "default"; + } else { + charSetText = escapeHtml(campaign.character_set) + " (Length: " + campaign.r_id_length + ")"; + } + var row = [ escapeHtml(campaign.name), moment(campaign.created_date).format('MMMM Do YYYY, h:mm:ss a'), "" + campaign.status + "", + "" + charSetText + "", "
\ \ \ @@ -424,4 +440,10 @@ $(document).ready(function () { return 0; }); }) + + $('#customize_rid').change(function() { + $(this).is(':checked') + ? $('#fields-customize_rid').removeClass('hidden') + : $('#fields-customize_rid').addClass('hidden') + }) }) diff --git a/templates/campaigns.html b/templates/campaigns.html index 7c12c9c2..09a04e77 100644 --- a/templates/campaigns.html +++ b/templates/campaigns.html @@ -37,8 +37,9 @@ Name - Created Date + Created Date Status + Character Set @@ -60,8 +61,9 @@ Name - Created Date + Created Date Status + Character Set @@ -124,6 +126,24 @@
+ +
+
+ + +
+
+ +