Cleaned up error messages - *all* errors in JSON format

Cleaned up flashes - fixes #13
Added specified errors - more to come soon
Added Campaign validation
Added Group validation
Cleaned up the way angular errors are handled. Will double check, but for the most part fixes #11
Results are now shown on the webui with most recent shown first
Added comments, additional cleanup, etc.
pull/24/head
unknown 2015-02-21 00:11:22 -06:00
parent ab8dfc8bb3
commit 66dbe2e799
13 changed files with 411 additions and 324 deletions

View File

@ -69,15 +69,13 @@ func API_Campaigns(w http.ResponseWriter, r *http.Request) {
c := models.Campaign{} c := models.Campaign{}
// Put the request into a campaign // Put the request into a campaign
err := json.NewDecoder(r.Body).Decode(&c) err := json.NewDecoder(r.Body).Decode(&c)
if checkError(err, w, "Invalid Request", http.StatusBadRequest) { if err != nil {
return JSONResponse(w, models.Response{Success: false, Message: "Invalid JSON structure"}, http.StatusBadRequest)
}
if m, ok := c.Validate(); !ok {
http.Error(w, "Error: "+m, http.StatusBadRequest)
return return
} }
err = models.PostCampaign(&c, ctx.Get(r, "user_id").(int64)) err = models.PostCampaign(&c, ctx.Get(r, "user_id").(int64))
if checkError(err, w, "Cannot insert campaign into database", http.StatusInternalServerError) { if err != nil {
JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusBadRequest)
return return
} }
Worker.Queue <- &c Worker.Queue <- &c
@ -91,7 +89,8 @@ func API_Campaigns_Id(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r) vars := mux.Vars(r)
id, _ := strconv.ParseInt(vars["id"], 0, 64) id, _ := strconv.ParseInt(vars["id"], 0, 64)
c, err := models.GetCampaign(id, ctx.Get(r, "user_id").(int64)) c, err := models.GetCampaign(id, ctx.Get(r, "user_id").(int64))
if checkError(err, w, "Campaign not found", http.StatusNotFound) { if err != nil {
JSONResponse(w, models.Response{Success: false, Message: "Campaign not found"}, http.StatusNotFound)
return return
} }
switch { switch {
@ -99,7 +98,8 @@ func API_Campaigns_Id(w http.ResponseWriter, r *http.Request) {
JSONResponse(w, c, http.StatusOK) JSONResponse(w, c, http.StatusOK)
case r.Method == "DELETE": case r.Method == "DELETE":
err = models.DeleteCampaign(id) err = models.DeleteCampaign(id)
if checkError(err, w, "Error deleting campaign", http.StatusInternalServerError) { if err != nil {
JSONResponse(w, models.Response{Success: false, Message: "Error deleting campaign"}, http.StatusInternalServerError)
return return
} }
JSONResponse(w, models.Response{Success: true, Message: "Campaign deleted successfully!"}, http.StatusOK) JSONResponse(w, models.Response{Success: true, Message: "Campaign deleted successfully!"}, http.StatusOK)
@ -112,7 +112,8 @@ func API_Groups(w http.ResponseWriter, r *http.Request) {
switch { switch {
case r.Method == "GET": case r.Method == "GET":
gs, err := models.GetGroups(ctx.Get(r, "user_id").(int64)) gs, err := models.GetGroups(ctx.Get(r, "user_id").(int64))
if checkError(err, w, "Groups not found", http.StatusNotFound) { if err != nil {
JSONResponse(w, models.Response{Success: false, Message: "No groups found"}, http.StatusNotFound)
return return
} }
JSONResponse(w, gs, http.StatusOK) JSONResponse(w, gs, http.StatusOK)
@ -121,7 +122,8 @@ func API_Groups(w http.ResponseWriter, r *http.Request) {
g := models.Group{} g := models.Group{}
// Put the request into a group // Put the request into a group
err := json.NewDecoder(r.Body).Decode(&g) err := json.NewDecoder(r.Body).Decode(&g)
if checkError(err, w, "Invalid Request", http.StatusBadRequest) { if err != nil {
JSONResponse(w, models.Response{Success: false, Message: "Invalid JSON structure"}, http.StatusBadRequest)
return return
} }
_, err = models.GetGroupByName(g.Name, ctx.Get(r, "user_id").(int64)) _, err = models.GetGroupByName(g.Name, ctx.Get(r, "user_id").(int64))
@ -129,15 +131,11 @@ func API_Groups(w http.ResponseWriter, r *http.Request) {
JSONResponse(w, models.Response{Success: false, Message: "Group name already in use"}, http.StatusConflict) JSONResponse(w, models.Response{Success: false, Message: "Group name already in use"}, http.StatusConflict)
return return
} }
// Check to make sure targets were specified
if len(g.Targets) == 0 {
http.Error(w, "Error: No targets specified", http.StatusBadRequest)
return
}
g.ModifiedDate = time.Now() g.ModifiedDate = time.Now()
g.UserId = ctx.Get(r, "user_id").(int64) g.UserId = ctx.Get(r, "user_id").(int64)
err = models.PostGroup(&g) err = models.PostGroup(&g)
if checkError(err, w, "Error inserting group", http.StatusInternalServerError) { if err != nil {
JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusBadRequest)
return return
} }
w.Header().Set("Location", "http://localhost:3333/api/groups/"+string(g.Id)) w.Header().Set("Location", "http://localhost:3333/api/groups/"+string(g.Id))
@ -151,7 +149,8 @@ func API_Groups_Id(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r) vars := mux.Vars(r)
id, _ := strconv.ParseInt(vars["id"], 0, 64) id, _ := strconv.ParseInt(vars["id"], 0, 64)
g, err := models.GetGroup(id, ctx.Get(r, "user_id").(int64)) g, err := models.GetGroup(id, ctx.Get(r, "user_id").(int64))
if checkError(err, w, "Group not found", http.StatusNotFound) { if err != nil {
JSONResponse(w, models.Response{Success: false, Message: "Group not found"}, http.StatusNotFound)
return return
} }
switch { switch {
@ -159,7 +158,8 @@ func API_Groups_Id(w http.ResponseWriter, r *http.Request) {
JSONResponse(w, g, http.StatusOK) JSONResponse(w, g, http.StatusOK)
case r.Method == "DELETE": case r.Method == "DELETE":
err = models.DeleteGroup(&g) err = models.DeleteGroup(&g)
if checkError(err, w, "Error deleting group", http.StatusInternalServerError) { if err != nil {
JSONResponse(w, models.Response{Success: false, Message: "Error deleting group"}, http.StatusInternalServerError)
return return
} }
JSONResponse(w, models.Response{Success: true, Message: "Group deleted successfully!"}, http.StatusOK) JSONResponse(w, models.Response{Success: true, Message: "Group deleted successfully!"}, http.StatusOK)
@ -168,18 +168,14 @@ func API_Groups_Id(w http.ResponseWriter, r *http.Request) {
g = models.Group{} g = models.Group{}
err = json.NewDecoder(r.Body).Decode(&g) err = json.NewDecoder(r.Body).Decode(&g)
if g.Id != id { if g.Id != id {
http.Error(w, "Error: /:id and group_id mismatch", http.StatusBadRequest) JSONResponse(w, models.Response{Success: false, Message: "Error: /:id and group_id mismatch"}, http.StatusInternalServerError)
return
}
// Check to make sure targets were specified
if len(g.Targets) == 0 {
http.Error(w, "Error: No targets specified", http.StatusBadRequest)
return return
} }
g.ModifiedDate = time.Now() g.ModifiedDate = time.Now()
g.UserId = ctx.Get(r, "user_id").(int64) g.UserId = ctx.Get(r, "user_id").(int64)
err = models.PutGroup(&g) err = models.PutGroup(&g)
if checkError(err, w, "Error updating group", http.StatusInternalServerError) { if err != nil {
JSONResponse(w, models.Response{Success: false, Message: "Error updating group"}, http.StatusInternalServerError)
return return
} }
JSONResponse(w, g, http.StatusOK) JSONResponse(w, g, http.StatusOK)
@ -199,11 +195,11 @@ func API_Templates(w http.ResponseWriter, r *http.Request) {
t := models.Template{} t := models.Template{}
// Put the request into a template // Put the request into a template
err := json.NewDecoder(r.Body).Decode(&t) err := json.NewDecoder(r.Body).Decode(&t)
if checkError(err, w, "Invalid Request", http.StatusBadRequest) { if err != nil {
JSONResponse(w, models.Response{Success: false, Message: "Invalid JSON structure"}, http.StatusBadRequest)
return return
} }
_, err = models.GetTemplateByName(t.Name, ctx.Get(r, "user_id").(int64)) _, err = models.GetTemplateByName(t.Name, ctx.Get(r, "user_id").(int64))
fmt.Println(err)
if err != gorm.RecordNotFound { if err != gorm.RecordNotFound {
JSONResponse(w, models.Response{Success: false, Message: "Template name already in use"}, http.StatusConflict) JSONResponse(w, models.Response{Success: false, Message: "Template name already in use"}, http.StatusConflict)
return return
@ -219,7 +215,9 @@ func API_Templates(w http.ResponseWriter, r *http.Request) {
JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusBadRequest) JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusBadRequest)
return return
} }
if checkError(err, w, "Error inserting template", http.StatusInternalServerError) { if err != nil {
JSONResponse(w, models.Response{Success: false, Message: "Error inserting template into database"}, http.StatusInternalServerError)
Logger.Println(err)
return return
} }
JSONResponse(w, t, http.StatusCreated) JSONResponse(w, t, http.StatusCreated)
@ -230,8 +228,8 @@ func API_Templates_Id(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r) vars := mux.Vars(r)
id, _ := strconv.ParseInt(vars["id"], 0, 64) id, _ := strconv.ParseInt(vars["id"], 0, 64)
t, err := models.GetTemplate(id, ctx.Get(r, "user_id").(int64)) t, err := models.GetTemplate(id, ctx.Get(r, "user_id").(int64))
if checkError(err, w, "Template not found", http.StatusNotFound) { if err != nil {
Logger.Println(err) JSONResponse(w, models.Response{Success: false, Message: "Template not found"}, http.StatusNotFound)
return return
} }
switch { switch {
@ -239,7 +237,8 @@ func API_Templates_Id(w http.ResponseWriter, r *http.Request) {
JSONResponse(w, t, http.StatusOK) JSONResponse(w, t, http.StatusOK)
case r.Method == "DELETE": case r.Method == "DELETE":
err = models.DeleteTemplate(id, ctx.Get(r, "user_id").(int64)) err = models.DeleteTemplate(id, ctx.Get(r, "user_id").(int64))
if checkError(err, w, "Error deleting template", http.StatusInternalServerError) { if err != nil {
JSONResponse(w, models.Response{Success: false, Message: "Error deleting template"}, http.StatusInternalServerError)
return return
} }
JSONResponse(w, models.Response{Success: true, Message: "Template deleted successfully!"}, http.StatusOK) JSONResponse(w, models.Response{Success: true, Message: "Template deleted successfully!"}, http.StatusOK)
@ -250,17 +249,14 @@ func API_Templates_Id(w http.ResponseWriter, r *http.Request) {
Logger.Println(err) Logger.Println(err)
} }
if t.Id != id { if t.Id != id {
http.Error(w, "Error: /:id and template_id mismatch", http.StatusBadRequest) JSONResponse(w, models.Response{Success: false, Message: "Error: /:id and template_id mismatch"}, http.StatusBadRequest)
return return
} }
err = t.Validate()
/* if checkError(err, w, http.StatusBadRequest) {
return
}*/
t.ModifiedDate = time.Now() t.ModifiedDate = time.Now()
t.UserId = ctx.Get(r, "user_id").(int64) t.UserId = ctx.Get(r, "user_id").(int64)
err = models.PutTemplate(&t) err = models.PutTemplate(&t)
if checkError(err, w, "Error updating group", http.StatusInternalServerError) { if err != nil {
JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusBadRequest)
return return
} }
JSONResponse(w, t, http.StatusOK) JSONResponse(w, t, http.StatusOK)
@ -306,8 +302,8 @@ func API_Pages_Id(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r) vars := mux.Vars(r)
id, _ := strconv.ParseInt(vars["id"], 0, 64) id, _ := strconv.ParseInt(vars["id"], 0, 64)
p, err := models.GetPage(id, ctx.Get(r, "user_id").(int64)) p, err := models.GetPage(id, ctx.Get(r, "user_id").(int64))
if checkError(err, w, "Page not found", http.StatusNotFound) { if err != nil {
Logger.Println(err) JSONResponse(w, models.Response{Success: false, Message: "Page not found"}, http.StatusNotFound)
return return
} }
switch { switch {
@ -315,7 +311,8 @@ func API_Pages_Id(w http.ResponseWriter, r *http.Request) {
JSONResponse(w, p, http.StatusOK) JSONResponse(w, p, http.StatusOK)
case r.Method == "DELETE": case r.Method == "DELETE":
err = models.DeletePage(id, ctx.Get(r, "user_id").(int64)) err = models.DeletePage(id, ctx.Get(r, "user_id").(int64))
if checkError(err, w, "Error deleting page", http.StatusInternalServerError) { if err != nil {
JSONResponse(w, models.Response{Success: false, Message: "Error deleting page"}, http.StatusInternalServerError)
return return
} }
JSONResponse(w, models.Response{Success: true, Message: "Page Deleted Successfully"}, http.StatusOK) JSONResponse(w, models.Response{Success: true, Message: "Page Deleted Successfully"}, http.StatusOK)
@ -348,7 +345,8 @@ func API_Pages_Id(w http.ResponseWriter, r *http.Request) {
// API_Import_Group imports a CSV of group members // API_Import_Group imports a CSV of group members
func API_Import_Group(w http.ResponseWriter, r *http.Request) { func API_Import_Group(w http.ResponseWriter, r *http.Request) {
ts, err := util.ParseCSV(r) ts, err := util.ParseCSV(r)
if checkError(err, w, "Error deleting template", http.StatusInternalServerError) { if err != nil {
JSONResponse(w, models.Response{Success: false, Message: "Error parsing CSV"}, http.StatusInternalServerError)
return return
} }
JSONResponse(w, ts, http.StatusOK) JSONResponse(w, ts, http.StatusOK)
@ -371,8 +369,9 @@ func API_Import_Email(w http.ResponseWriter, r *http.Request) {
// is written to the given ResponseWriter. // is written to the given ResponseWriter.
func JSONResponse(w http.ResponseWriter, d interface{}, c int) { func JSONResponse(w http.ResponseWriter, d interface{}, c int) {
dj, err := json.MarshalIndent(d, "", " ") dj, err := json.MarshalIndent(d, "", " ")
if checkError(err, w, "Error creating JSON response", http.StatusInternalServerError) { if err != nil {
return http.Error(w, "Error creating JSON response", http.StatusInternalServerError)
Logger.Println(err)
} }
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
w.WriteHeader(c) w.WriteHeader(c)

View File

@ -264,16 +264,6 @@ func getTemplate(w http.ResponseWriter, tmpl string) *template.Template {
return template.Must(templates, err) return template.Must(templates, err)
} }
func checkError(e error, w http.ResponseWriter, m string, c int) bool {
if e != nil {
Logger.Println(e)
w.WriteHeader(c)
JSONResponse(w, models.Response{Success: false, Message: m}, http.StatusBadRequest)
return true
}
return false
}
// Flash handles the rendering flash messages // Flash handles the rendering flash messages
func Flash(w http.ResponseWriter, r *http.Request, t string, m string) { func Flash(w http.ResponseWriter, r *http.Request, t string, m string) {
session := ctx.Get(r, "session").(*sessions.Session) session := ctx.Get(r, "session").(*sessions.Session)

View File

@ -1,6 +1,7 @@
package models package models
import ( import (
"errors"
"fmt" "fmt"
"time" "time"
@ -26,17 +27,32 @@ type Campaign struct {
SMTP SMTP `json:"smtp"` SMTP SMTP `json:"smtp"`
} }
// ErrCampaignNameNotSpecified indicates there was no template given by the user
var ErrCampaignNameNotSpecified = errors.New("Campaign name not specified")
// ErrGroupNotSpecified indicates there was no template given by the user
var ErrGroupNotSpecified = errors.New("No groups specified")
// ErrTemplateNotSpecified indicates there was no template given by the user
var ErrTemplateNotSpecified = errors.New("No email template specified")
// ErrTemplateNotFound indicates the template specified does not exist in the database
var ErrTemplateNotFound = errors.New("Template not found")
// ErrGroupnNotFound indicates a group specified by the user does not exist in the database
var ErrGroupNotFound = errors.New("Group not found")
// Validate checks to make sure there are no invalid fields in a submitted campaign // Validate checks to make sure there are no invalid fields in a submitted campaign
func (c *Campaign) Validate() (string, bool) { func (c *Campaign) Validate() error {
switch { switch {
case c.Name == "": case c.Name == "":
return "Must specify campaign name", false return ErrCampaignNameNotSpecified
case len(c.Groups) == 0: case len(c.Groups) == 0:
return "No groups specified", false return ErrGroupNotSpecified
case c.Template.Name == "": case c.Template.Name == "":
return "No template specified", false return ErrTemplateNotSpecified
} }
return "", true return nil
} }
// UpdateStatus changes the campaign status appropriately // UpdateStatus changes the campaign status appropriately
@ -105,6 +121,9 @@ func GetCampaign(id int64, uid int64) (Campaign, error) {
// PostCampaign inserts a campaign and all associated records into the database. // PostCampaign inserts a campaign and all associated records into the database.
func PostCampaign(c *Campaign, uid int64) error { func PostCampaign(c *Campaign, uid int64) error {
if err := c.Validate(); err != nil {
return err
}
// Fill in the details // Fill in the details
c.UserId = uid c.UserId = uid
c.CreatedDate = time.Now() c.CreatedDate = time.Now()
@ -115,7 +134,7 @@ func PostCampaign(c *Campaign, uid int64) error {
c.Groups[i], err = GetGroupByName(g.Name, uid) c.Groups[i], err = GetGroupByName(g.Name, uid)
if err == gorm.RecordNotFound { if err == gorm.RecordNotFound {
Logger.Printf("Error - Group %s does not exist", g.Name) Logger.Printf("Error - Group %s does not exist", g.Name)
return err return ErrGroupNotFound
} else if err != nil { } else if err != nil {
Logger.Println(err) Logger.Println(err)
return err return err
@ -125,7 +144,7 @@ func PostCampaign(c *Campaign, uid int64) error {
t, err := GetTemplateByName(c.Template.Name, uid) t, err := GetTemplateByName(c.Template.Name, uid)
if err == gorm.RecordNotFound { if err == gorm.RecordNotFound {
Logger.Printf("Error - Template %s does not exist", t.Name) Logger.Printf("Error - Template %s does not exist", t.Name)
return err return ErrTemplateNotFound
} else if err != nil { } else if err != nil {
Logger.Println(err) Logger.Println(err)
return err return err

View File

@ -1,12 +1,15 @@
package models package models
import ( import (
"errors"
"net/mail" "net/mail"
"time" "time"
"github.com/jinzhu/gorm" "github.com/jinzhu/gorm"
) )
// Group contains the fields needed for a user -> group mapping
// Groups contain 1..* Targets
type Group struct { type Group struct {
Id int64 `json:"id"` Id int64 `json:"id"`
UserId int64 `json:"-"` UserId int64 `json:"-"`
@ -15,11 +18,14 @@ type Group struct {
Targets []Target `json:"targets" sql:"-"` Targets []Target `json:"targets" sql:"-"`
} }
// GroupTarget is used for a many-to-many relationship between 1..* Groups and 1..* Targets
type GroupTarget struct { type GroupTarget struct {
GroupId int64 `json:"-"` GroupId int64 `json:"-"`
TargetId int64 `json:"-"` TargetId int64 `json:"-"`
} }
// Target contains the fields needed for individual targets specified by the user
// Groups contain 1..* Targets, but 1 Target may belong to 1..* Groups
type Target struct { type Target struct {
Id int64 `json:"-"` Id int64 `json:"-"`
FirstName string `json:"first_name"` FirstName string `json:"first_name"`
@ -27,6 +33,23 @@ type Target struct {
Email string `json:"email"` Email string `json:"email"`
} }
// ErrGroupNameNotSpecified is thrown when a group name is not specified
var ErrGroupNameNotSpecified = errors.New("Group name not specified")
// ErrNoTargetsSpecified is thrown when no targets are specified by the user
var ErrNoTargetsSpecified = errors.New("No targets specified")
// Validate performs validation on a group given by the user
func (g *Group) Validate() error {
switch {
case g.Name == "":
return ErrGroupNameNotSpecified
case len(g.Targets) == 0:
return ErrNoTargetsSpecified
}
return nil
}
// GetGroups returns the groups owned by the given user. // GetGroups returns the groups owned by the given user.
func GetGroups(uid int64) ([]Group, error) { func GetGroups(uid int64) ([]Group, error) {
gs := []Group{} gs := []Group{}
@ -76,7 +99,11 @@ func GetGroupByName(n string, uid int64) (Group, error) {
// PostGroup creates a new group in the database. // PostGroup creates a new group in the database.
func PostGroup(g *Group) error { func PostGroup(g *Group) error {
// Insert into the DB Logger.Printf("%v", g.Targets)
if err := g.Validate(); err != nil {
return err
}
// Insert the group into the DB
err = db.Save(g).Error err = db.Save(g).Error
if err != nil { if err != nil {
Logger.Println(err) Logger.Println(err)
@ -189,6 +216,7 @@ func insertTargetIntoGroup(t Target, gid int64) error {
return nil return nil
} }
// GetTargets performs a many-to-many select to get all the Targets for a Group
func GetTargets(gid int64) ([]Target, error) { func GetTargets(gid int64) ([]Target, error) {
ts := []Target{} ts := []Target{}
err := db.Table("targets").Select("targets.id, targets.email, targets.first_name, targets.last_name").Joins("left join group_targets gt ON targets.id = gt.target_id").Where("gt.group_id=?", gid).Scan(&ts).Error err := db.Table("targets").Select("targets.id, targets.email, targets.first_name, targets.last_name").Joins("left join group_targets gt ON targets.id = gt.target_id").Where("gt.group_id=?", gid).Scan(&ts).Error

View File

@ -20,7 +20,7 @@ type Template struct {
} }
// ErrTemplateNameNotSpecified is thrown when a template name is not specified // ErrTemplateNameNotSpecified is thrown when a template name is not specified
var ErrTemplateNameNotSpecified = errors.New("Template Name not specified") var ErrTemplateNameNotSpecified = errors.New("Template name not specified")
// ErrTemplateMissingParameter is thrown when a needed parameter is not provided // ErrTemplateMissingParameter is thrown when a needed parameter is not provided
var ErrTemplateMissingParameter = errors.New("Need to specify at least plaintext or HTML content") var ErrTemplateMissingParameter = errors.New("Need to specify at least plaintext or HTML content")
@ -112,8 +112,11 @@ func PostTemplate(t *Template) error {
// PutTemplate edits an existing template in the database. // PutTemplate edits an existing template in the database.
// Per the PUT Method RFC, it presumes all data for a template is provided. // Per the PUT Method RFC, it presumes all data for a template is provided.
func PutTemplate(t *Template) error { func PutTemplate(t *Template) error {
if err := t.Validate(); err != nil {
return err
}
// Delete all attachments, and replace with new ones // Delete all attachments, and replace with new ones
err := db.Debug().Where("template_id=?", t.Id).Delete(&Attachment{}).Error err = db.Where("template_id=?", t.Id).Delete(&Attachment{}).Error
if err != nil && err != gorm.RecordNotFound { if err != nil && err != gorm.RecordNotFound {
Logger.Println(err) Logger.Println(err)
return err return err
@ -129,7 +132,7 @@ func PutTemplate(t *Template) error {
return err return err
} }
} }
err = db.Debug().Where("id=?", t.Id).Save(t).Error err = db.Where("id=?", t.Id).Save(t).Error
if err != nil { if err != nil {
Logger.Println(err) Logger.Println(err)
return err return err

View File

@ -132,7 +132,23 @@ app.controller('DashboardCtrl', function($scope, $filter, $location, CampaignSer
}); });
}) })
app.controller('CampaignCtrl', function($scope, $modal, CampaignService, GroupService, TemplateService, ngTableParams, $http) { app.controller('CampaignCtrl', function($scope, $modal, CampaignService, GroupService, TemplateService, ngTableParams, $http) {
$scope.flashes = [] $scope.errorFlash = function(message) {
$scope.flashes = {"main" : [], "modal" : []};
$scope.flashes.main.push({
"type": "danger",
"message": message,
"icon": "fa-exclamation-circle"
})
}
$scope.successFlash = function(message) {
$scope.flashes = {"main" : [], "modal" : []};;
$scope.flashes.main.push({
"type": "success",
"message": message,
"icon": "fa-check-circle"
})
}
$scope.mainTableParams = new ngTableParams({ $scope.mainTableParams = new ngTableParams({
page: 1, // show first page page: 1, // show first page
count: 10, // count per page count: 10, // count per page
@ -158,21 +174,6 @@ app.controller('CampaignCtrl', function($scope, $modal, CampaignService, GroupSe
$scope.templates = templates; $scope.templates = templates;
}) })
$scope.addGroup = function(group) {
if (group.name != "") {
$scope.campaign.groups.push({
name: group.name
});
group.name = ""
$scope.editGroupTableParams.reload()
}
};
$scope.removeGroup = function(group) {
$scope.campaign.groups.splice($scope.campaign.groups.indexOf(group), 1);
$scope.editGroupTableParams.reload()
};
$scope.newCampaign = function() { $scope.newCampaign = function() {
$scope.campaign = { $scope.campaign = {
name: '', name: '',
@ -188,11 +189,19 @@ app.controller('CampaignCtrl', function($scope, $modal, CampaignService, GroupSe
scope: $scope scope: $scope
}); });
modalInstance.result.then(function(selectedItem) { modalInstance.result.then(function(message) {
$scope.selected = selectedItem; $scope.successFlash(message)
$scope.campaign = {
name: '',
groups: [],
};
}, function() { }, function() {
console.log('closed') $scope.campaign = {
name: '',
groups: [],
};
}); });
$scope.mainTableParams.reload()
}; };
$scope.editGroupTableParams = new ngTableParams({ $scope.editGroupTableParams = new ngTableParams({
@ -209,35 +218,64 @@ app.controller('CampaignCtrl', function($scope, $modal, CampaignService, GroupSe
} }
}); });
$scope.saveCampaign = function(campaign) {
$scope.flashes = []
$scope.validated = true
var newCampaign = new CampaignService(campaign);
newCampaign.$save({}, function() {
$scope.successFlash("Campaign added successfully")
$scope.campaigns.push(newCampaign);
$scope.mainTableParams.reload()
}, function(response) {
$scope.errorFlash(response.data)
});
$scope.campaign = {
groups: [],
};
$scope.editGroupTableParams.reload()
}
$scope.deleteCampaign = function(campaign) { $scope.deleteCampaign = function(campaign) {
var deleteCampaign = new CampaignService(campaign); var deleteCampaign = new CampaignService(campaign);
deleteCampaign.$delete({ deleteCampaign.$delete({
id: deleteCampaign.id id: deleteCampaign.id
}, function() { }, function(response) {
$scope.successFlash("Campaign deleted successfully") if (response.success) {
$scope.successFlash(response.message)
} else {
$scope.errorFlash(response.message)
}
$scope.mainTableParams.reload(); $scope.mainTableParams.reload();
}); });
} }
});
var CampaignModalCtrl = function($scope, CampaignService, $modalInstance) {
$scope.errorFlash = function(message) { $scope.errorFlash = function(message) {
$scope.flashes.push({ $scope.flashes = {"main" : [], "modal" : []};
$scope.flashes.modal.push({
"type": "danger",
"message": message,
"icon": "fa-exclamation-circle"
})
}
$scope.addGroup = function(group) {
if (group.name != "") {
$scope.campaign.groups.push({
name: group.name
});
group.name = ""
$scope.editGroupTableParams.reload()
}
};
$scope.removeGroup = function(group) {
$scope.campaign.groups.splice($scope.campaign.groups.indexOf(group), 1);
$scope.editGroupTableParams.reload()
};
$scope.cancel = function() {
$modalInstance.dismiss('cancel');
};
$scope.ok = function(campaign) {
var newCampaign = new CampaignService(campaign);
newCampaign.$save({}, function() {
$modalInstance.close("Campaign added successfully")
$scope.campaigns.push(newCampaign);
$scope.mainTableParams.reload()
}, function(response) {
$scope.errorFlash(response.data.message)
});
}
};
app.controller('CampaignResultsCtrl', function($scope, $filter, CampaignService, GroupService, ngTableParams, $http, $window) {
id = $window.location.hash.split('/')[2];
$scope.errorFlash = function(message) {
$scope.flashes = {"main" : [], "modal" : []};
$scope.flashes.main.push({
"type": "danger", "type": "danger",
"message": message, "message": message,
"icon": "fa-exclamation-circle" "icon": "fa-exclamation-circle"
@ -245,27 +283,13 @@ app.controller('CampaignCtrl', function($scope, $modal, CampaignService, GroupSe
} }
$scope.successFlash = function(message) { $scope.successFlash = function(message) {
$scope.flashes.push({ $scope.flashes = {"main" : [], "modal" : []};;
$scope.flashes.main.push({
"type": "success", "type": "success",
"message": message, "message": message,
"icon": "fa-check-circle" "icon": "fa-check-circle"
}) })
} }
});
var CampaignModalCtrl = function($scope, $modalInstance) {
$scope.cancel = function() {
$modalInstance.dismiss('cancel');
};
$scope.ok = function(campaign) {
$modalInstance.dismiss("")
$scope.saveCampaign(campaign)
}
};
app.controller('CampaignResultsCtrl', function($scope, $filter, CampaignService, GroupService, ngTableParams, $http, $window) {
id = $window.location.hash.split('/')[2];
$scope.flashes = []
$scope.mainTableParams = new ngTableParams({ $scope.mainTableParams = new ngTableParams({
page: 1, // show first page page: 1, // show first page
count: 10, // count per page count: 10, // count per page
@ -378,51 +402,43 @@ app.controller('CampaignResultsCtrl', function($scope, $filter, CampaignService,
xAxis: { xAxis: {
type: 'datetime', type: 'datetime',
dateTimeLabelFormats: { // don't display the dummy year dateTimeLabelFormats: { // don't display the dummy year
day: "%e of %b", day: "%e of %b",
hour: "%l:%M", hour: "%l:%M",
second: '%l:%M:%S', second: '%l:%M:%S',
minute: '%l:%M' minute: '%l:%M'
},
max: Date.now(),
title: {
text: 'Date'
}
}, },
max: Date.now(),
title: {
text: 'Date'
}
}, },
series: [{ },
name: "Events", series: [{
data: $scope.campaign.timeline name: "Events",
}], data: $scope.campaign.timeline
title: { }],
text: 'Campaign Timeline' title: {
}, text: 'Campaign Timeline'
size: { },
height: 300 size: {
}, height: 300
credits: { },
enabled: false credits: {
}, enabled: false
loading: false, },
} loading: false,
params.total(campaign.results.length) }
$defer.resolve(campaign.results.slice((params.page() - 1) * params.count(), params.page() * params.count())); params.total(campaign.results.length)
}) $defer.resolve(campaign.results.slice((params.page() - 1) * params.count(), params.page() * params.count()));
}
});
$scope.errorFlash = function(message) {
$scope.flashes.push({
"type": "danger",
"message": message,
"icon": "fa-exclamation-circle"
}) })
} }
}); });
})
app.controller('GroupCtrl', function($scope, $modal, GroupService, ngTableParams) { app.controller('GroupCtrl', function($scope, $modal, GroupService, ngTableParams) {
$scope.errorFlash = function(message) { $scope.errorFlash = function(message) {
$scope.flashes = []; $scope.flashes = {"main" : [], "modal" : []};
$scope.flashes.push({ $scope.flashes.main.push({
"type": "danger", "type": "danger",
"message": message, "message": message,
"icon": "fa-exclamation-circle" "icon": "fa-exclamation-circle"
@ -430,8 +446,8 @@ app.controller('GroupCtrl', function($scope, $modal, GroupService, ngTableParams
} }
$scope.successFlash = function(message) { $scope.successFlash = function(message) {
$scope.flashes = []; $scope.flashes = {"main" : [], "modal" : []};;
$scope.flashes.push({ $scope.flashes.main.push({
"type": "success", "type": "success",
"message": message, "message": message,
"icon": "fa-check-circle" "icon": "fa-check-circle"
@ -487,8 +503,64 @@ app.controller('GroupCtrl', function($scope, $modal, GroupService, ngTableParams
controller: GroupModalCtrl, controller: GroupModalCtrl,
scope: $scope scope: $scope
}); });
modalInstance.result.then(function(message) {
$scope.successFlash(message)
$scope.group = {
name: '',
targets: [],
};
}, function() {
$scope.group = {
name: '',
targets: [],
};
});
$scope.mainTableParams.reload()
}; };
$scope.deleteGroup = function(group) {
var deleteGroup = new GroupService(group);
deleteGroup.$delete({
id: deleteGroup.id
}, function(response) {
if (response.success) {
$scope.successFlash(response.message)
} else {
$scope.errorFlash(response.message)
}
$scope.mainTableParams.reload();
});
}
})
var GroupModalCtrl = function($scope, GroupService, $modalInstance, $upload) {
$scope.errorFlash = function(message) {
$scope.flashes = {"main" : [], "modal" : []};
$scope.flashes.modal.push({
"type": "danger",
"message": message,
"icon": "fa-exclamation-circle"
})
}
$scope.onFileSelect = function($file) {
$scope.upload = $upload.upload({
url: '/api/import/group',
data: {},
file: $file,
}).progress(function(evt) {
console.log('percent: ' + parseInt(100.0 * evt.loaded / evt.total));
}).success(function(data, status, headers, config) {
angular.forEach(data, function(record, key) {
$scope.group.targets.push({
first_name : record.first_name,
last_name : record.last_name,
email: record.email
});
});
$scope.editGroupTableParams.reload();
});
};
$scope.addTarget = function() { $scope.addTarget = function() {
if ($scope.newTarget.email != "") { if ($scope.newTarget.email != "") {
$scope.group.targets.push({ $scope.group.targets.push({
@ -502,60 +574,27 @@ app.controller('GroupCtrl', function($scope, $modal, GroupService, ngTableParams
$scope.group.targets.splice($scope.group.targets.indexOf(target), 1); $scope.group.targets.splice($scope.group.targets.indexOf(target), 1);
$scope.editGroupTableParams.reload() $scope.editGroupTableParams.reload()
}; };
$scope.saveGroup = function(group) { $scope.cancel = function() {
$modalInstance.dismiss();
};
$scope.ok = function(group) {
var newGroup = new GroupService(group); var newGroup = new GroupService(group);
if ($scope.newGroup) { if ($scope.newGroup) {
newGroup.$save({}, function() { newGroup.$save({}, function() {
$scope.groups.push(newGroup); $scope.groups.push(newGroup);
$scope.mainTableParams.reload() $modalInstance.close("Group created successfully!")
}, function(error){
$scope.errorFlash(error.data.message)
}); });
} else { } else {
newGroup.$update({ newGroup.$update({
id: newGroup.id id: newGroup.id
},function(){
$modalInstance.close("Group updated successfully!")
}, function(error){
$scope.errorFlash(error.data.message)
}) })
} }
$scope.group = {
name: '',
targets: [],
};
$scope.editGroupTableParams.reload()
}
$scope.deleteGroup = function(group) {
var deleteGroup = new GroupService(group);
deleteGroup.$delete({
id: deleteGroup.id
}, function() {
$scope.mainTableParams.reload();
});
}
})
var GroupModalCtrl = function($scope, $modalInstance, $upload) {
$scope.onFileSelect = function($file) {
$scope.upload = $upload.upload({
url: '/api/import/group',
data: {},
file: $file,
}).progress(function(evt) {
console.log('percent: ' + parseInt(100.0 * evt.loaded / evt.total));
}).success(function(data, status, headers, config) {
angular.forEach(data, function(record, key) {
$scope.group.targets.push({
first_name : record.first_name,
last_name : record.last_name,
email: record.email
});
});
$scope.editGroupTableParams.reload();
//.error(...)
});
};
$scope.cancel = function() {
$modalInstance.dismiss('cancel');
};
$scope.ok = function(group) {
$modalInstance.dismiss('')
$scope.saveGroup(group)
}; };
} }
@ -623,11 +662,11 @@ app.controller('TemplateCtrl', function($scope, $modal, TemplateService, ngTable
text: '', text: '',
}; };
}, function() { }, function() {
$scope.template = { $scope.template = {
name: '', name: '',
html: '', html: '',
text: '', text: '',
}; };
}); });
}; };
@ -648,7 +687,7 @@ app.controller('TemplateCtrl', function($scope, $modal, TemplateService, ngTable
var TemplateModalCtrl = function($scope, TemplateService, $upload, $modalInstance, $modal) { var TemplateModalCtrl = function($scope, TemplateService, $upload, $modalInstance, $modal) {
$scope.editorOptions = { $scope.editorOptions = {
fullPage: true, fullPage: true,
allowedContent: true, allowedContent: true,
} }
$scope.errorFlash = function(message) { $scope.errorFlash = function(message) {
@ -673,9 +712,9 @@ var TemplateModalCtrl = function($scope, TemplateService, $upload, $modalInstanc
var reader = new FileReader(); var reader = new FileReader();
reader.onload = function(e) { reader.onload = function(e) {
$scope.template.attachments.push({ $scope.template.attachments.push({
name : file.name, name : file.name,
content : reader.result.split(",")[1], content : reader.result.split(",")[1],
type : file.type || "application/octet-stream" type : file.type || "application/octet-stream"
}) })
$scope.$apply(); $scope.$apply();
} }
@ -700,9 +739,8 @@ var TemplateModalCtrl = function($scope, TemplateService, $upload, $modalInstanc
// Close the dialog, returning the template // Close the dialog, returning the template
$modalInstance.close("Template created successfully!") $modalInstance.close("Template created successfully!")
}, function(error){ }, function(error){
// Otherwise, leave the dialog open, showing the error // Otherwise, leave the dialog open, showing the error
console.log(error.data) $scope.errorFlash(error.data.message)
$scope.errorFlash(error.data.message)
}); });
} else { } else {
newTemplate.$update({ newTemplate.$update({
@ -710,7 +748,6 @@ var TemplateModalCtrl = function($scope, TemplateService, $upload, $modalInstanc
}, function(){ }, function(){
$modalInstance.close("Template updated successfully!") $modalInstance.close("Template updated successfully!")
}, function(error){ }, function(error){
console.log(error.data)
$scope.errorFlash(error.data.message) $scope.errorFlash(error.data.message)
}) })
} }
@ -733,109 +770,109 @@ var TemplateModalCtrl = function($scope, TemplateService, $upload, $modalInstanc
}; };
var ImportEmailCtrl = function($scope, $http, $modalInstance) { var ImportEmailCtrl = function($scope, $http, $modalInstance) {
$scope.email = {} $scope.email = {}
$scope.ok = function() { $scope.ok = function() {
// Simple POST request example (passing data) : // Simple POST request example (passing data) :
$http.post('/api/import/email', $scope.email.raw, $http.post('/api/import/email', $scope.email.raw,
{ headers : {"Content-Type" : "text/plain"}} { headers : {"Content-Type" : "text/plain"}}
).success(function(data) {console.log("Success: " + data)}) ).success(function(data) {console.log("Success: " + data)})
.error(function(data) {console.log("Error: " + data)}); .error(function(data) {console.log("Error: " + data)});
$modalInstance.close($scope.email.raw) $modalInstance.close($scope.email.raw)
}; };
$scope.cancel = function() {$modalInstance.dismiss()} $scope.cancel = function() {$modalInstance.dismiss()}
}; };
app.controller('LandingPageCtrl', function($scope, $modal, LandingPageService, ngTableParams) { app.controller('LandingPageCtrl', function($scope, $modal, LandingPageService, ngTableParams) {
$scope.errorFlash = function(message) { $scope.errorFlash = function(message) {
$scope.flashes = []; $scope.flashes = [];
$scope.flashes.push({ $scope.flashes.push({
"type": "danger", "type": "danger",
"message": message, "message": message,
"icon": "fa-exclamation-circle" "icon": "fa-exclamation-circle"
}) })
} }
$scope.successFlash = function(message) { $scope.successFlash = function(message) {
$scope.flashes = []; $scope.flashes = [];
$scope.flashes.push({ $scope.flashes.push({
"type": "success", "type": "success",
"message": message, "message": message,
"icon": "fa-check-circle" "icon": "fa-check-circle"
}) })
} }
$scope.mainTableParams = new ngTableParams({ $scope.mainTableParams = new ngTableParams({
page: 1, // show first page page: 1, // show first page
count: 10, // count per page count: 10, // count per page
sorting: { sorting: {
name: 'asc' // initial sorting name: 'asc' // initial sorting
} }
}, { }, {
total: 0, // length of data total: 0, // length of data
getData: function($defer, params) { getData: function($defer, params) {
LandingPageService.query(function(pages) { LandingPageService.query(function(pages) {
$scope.pages = pages $scope.pages = pages
params.total(pages.length) params.total(pages.length)
$defer.resolve(pages.slice((params.page() - 1) * params.count(), params.page() * params.count())); $defer.resolve(pages.slice((params.page() - 1) * params.count(), params.page() * params.count()));
}) })
} }
}); });
$scope.editPage = function(page) { $scope.editPage = function(page) {
if (page === 'new') { if (page === 'new') {
$scope.newPage = true; $scope.newPage = true;
$scope.page = { $scope.page = {
name: '', name: '',
html: '', html: '',
}; };
} else { } else {
$scope.newPage = false; $scope.newPage = false;
$scope.page = page; $scope.page = page;
} }
var modalInstance = $modal.open({ var modalInstance = $modal.open({
templateUrl: '/js/app/partials/modals/LandingPageModal.html', templateUrl: '/js/app/partials/modals/LandingPageModal.html',
controller: LandingPageModalCtrl, controller: LandingPageModalCtrl,
scope: $scope scope: $scope
}); });
modalInstance.result.then(function(selectedItem) { modalInstance.result.then(function(selectedItem) {
$scope.selected = selectedItem; $scope.selected = selectedItem;
}, function() { }, function() {
console.log('closed') console.log('closed')
}); });
}; };
$scope.savePage = function(page) { $scope.savePage = function(page) {
var newPage = new LandingPageService(page); var newPage = new LandingPageService(page);
if ($scope.newPage) { if ($scope.newPage) {
newPage.$save({}, function() { newPage.$save({}, function() {
$scope.pages.push(newPage); $scope.pages.push(newPage);
$scope.mainTableParams.reload() $scope.mainTableParams.reload()
}); });
} else { } else {
newPage.$update({ newPage.$update({
id: newPage.id id: newPage.id
}) })
} }
$scope.page = { $scope.page = {
name: '', name: '',
html: '', html: '',
}; };
} }
$scope.deletePage = function(page) { $scope.deletePage = function(page) {
var deletePage = new LandingPageService(page); var deletePage = new LandingPageService(page);
deletePage.$delete({ deletePage.$delete({
id: deletePage.id id: deletePage.id
}, function(response) { }, function(response) {
if (response.success) { if (response.success) {
$scope.successFlash(response.message) $scope.successFlash(response.message)
} else { } else {
$scope.errorFlash(response.message) $scope.errorFlash(response.message)
} }
$scope.mainTableParams.reload(); $scope.mainTableParams.reload();
}); });
} }
}); });
var LandingPageModalCtrl = function($scope, $modalInstance) { var LandingPageModalCtrl = function($scope, $modalInstance) {
@ -887,13 +924,13 @@ app.controller('SettingsCtrl', function($scope, $http, $window) {
'Content-Type': 'application/x-www-form-urlencoded' 'Content-Type': 'application/x-www-form-urlencoded'
} // set the headers so angular passing info as form data (not request payload) } // set the headers so angular passing info as form data (not request payload)
}) })
.success(function(response) { .success(function(response) {
if (response.success) { if (response.success) {
$scope.user.api_key = response.data; $scope.user.api_key = response.data;
$window.user.api_key = response.data; $window.user.api_key = response.data;
$scope.successFlash(response.message) $scope.successFlash(response.message)
} }
}) })
} }
$scope.save_settings = function() { $scope.save_settings = function() {
$http({ $http({
@ -904,12 +941,12 @@ app.controller('SettingsCtrl', function($scope, $http, $window) {
'Content-Type': 'application/x-www-form-urlencoded' 'Content-Type': 'application/x-www-form-urlencoded'
} }
}) })
.success(function(data) { .success(function(data) {
if (data.success) { if (data.success) {
$scope.successFlash(data.message) $scope.successFlash(data.message)
} else { } else {
$scope.errorFlash(data.message) $scope.errorFlash(data.message)
} }
}) })
} }
}) })

View File

@ -25,7 +25,7 @@
Campaigns Campaigns
</h1> </h1>
<div class="row"> <div class="row">
<div ng-repeat="flash in flashes" style="text-align:center" class="alert alert-{{flash.type}}"> <div ng-repeat="flash in flashes.main" style="text-align:center" class="alert alert-{{flash.type}}">
<i class="fa {{flash.icon}}"></i> {{flash.message}} <i class="fa {{flash.icon}}"></i> {{flash.message}}
</div> </div>
</div> </div>
@ -43,7 +43,7 @@
<div ng-show="campaigns.length" class="row"> <div ng-show="campaigns.length" class="row">
<table ng-table="mainTableParams" class="table table-hover table-striped table-bordered"> <table ng-table="mainTableParams" class="table table-hover table-striped table-bordered">
<tbody> <tbody>
<tr ng-repeat="campaign in $data" class="editable-row"> <tr ng-repeat="campaign in $data | orderBy: '-modified_date'" class="editable-row">
<td data-title="'Created Date'" class="col-sm-1">{{campaign.created_date | date:'medium'}}</td> <td data-title="'Created Date'" class="col-sm-1">{{campaign.created_date | date:'medium'}}</td>
<td data-title="'Name'" class="col-sm-2">{{campaign.name}} <td data-title="'Name'" class="col-sm-2">{{campaign.name}}
<div class="btn-group" style="float: right;"> <div class="btn-group" style="float: right;">

View File

@ -43,7 +43,7 @@
<div ng-show="pages.length" class="row"> <div ng-show="pages.length" class="row">
<table ng-table="mainTableParams" class="table table-hover table-striped table-bordered"> <table ng-table="mainTableParams" class="table table-hover table-striped table-bordered">
<tbody> <tbody>
<tr ng-repeat="page in $data" class="editable-row"> <tr ng-repeat="page in $data | orderBy: '-modified_date'" class="editable-row">
<td data-title="'Name'" sortable="'name'" class="col-sm-2">{{page.name}} <td data-title="'Name'" sortable="'name'" class="col-sm-2">{{page.name}}
<div class="btn-group" style="float: right;"> <div class="btn-group" style="float: right;">
<button type="button" class="btn btn-primary dropdown-toggle edit-button" data-toggle="dropdown"> <button type="button" class="btn btn-primary dropdown-toggle edit-button" data-toggle="dropdown">

View File

@ -4,6 +4,11 @@
<h4 class="modal-title" id="campaignModalLabel">New Campaign</h4> <h4 class="modal-title" id="campaignModalLabel">New Campaign</h4>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<div class="row">
<div ng-repeat="flash in flashes.modal" style="text-align:center" class="alert alert-{{flash.type}}">
<i class="fa {{flash.icon}}"></i> {{flash.message}}
</div>
</div>
<div class="form-group"> <div class="form-group">
<label for="name">Name:</label> <label for="name">Name:</label>
<input type="text" class="form-control" ng-model="campaign.name" id="name" placeholder="Campaign name" autofocus> <input type="text" class="form-control" ng-model="campaign.name" id="name" placeholder="Campaign name" autofocus>
@ -54,7 +59,7 @@
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-default" ng-click="cancel()">Cancel</button> <button type="button" class="btn btn-default" ng-click="cancel()">Cancel</button>
<button type="button" class="btn btn-primary" data-dismiss="modal" ng-click="saveCampaign(campaign)" type="submit">Launch Campaign</button> <button type="button" class="btn btn-primary" data-dismiss="modal" ng-click="ok(campaign)" type="submit">Launch Campaign</button>
</div> </div>
</div> </div>
</div> </div>

View File

@ -6,6 +6,11 @@
<h4 class="modal-title" ng-show="newGroup" id="groupModalLabel">New Group</h4> <h4 class="modal-title" ng-show="newGroup" id="groupModalLabel">New Group</h4>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<div class="row">
<div ng-repeat="flash in flashes.modal" style="text-align:center" class="alert alert-{{flash.type}}">
<i class="fa {{flash.icon}}"></i> {{flash.message}}
</div>
</div>
<label class="control-label" for="name">Name:</label> <label class="control-label" for="name">Name:</label>
<div class="form-group"> <div class="form-group">
<input type="text" class="form-control" ng-model="group.name" placeholder="Group name" id="name" autofocus/> <input type="text" class="form-control" ng-model="group.name" placeholder="Group name" id="name" autofocus/>

View File

@ -43,7 +43,7 @@
<div ng-show="templates.length" class="row"> <div ng-show="templates.length" class="row">
<table ng-table="mainTableParams" class="table table-hover table-striped table-bordered"> <table ng-table="mainTableParams" class="table table-hover table-striped table-bordered">
<tbody> <tbody>
<tr ng-repeat="template in $data" class="editable-row"> <tr ng-repeat="template in $data | orderBy: '-modified_date'" class="editable-row">
<td data-title="'Name'" sortable="'name'" class="col-sm-2">{{template.name}} <td data-title="'Name'" sortable="'name'" class="col-sm-2">{{template.name}}
<div class="btn-group" style="float: right;"> <div class="btn-group" style="float: right;">
<button type="button" class="btn btn-primary dropdown-toggle edit-button" data-toggle="dropdown"> <button type="button" class="btn btn-primary dropdown-toggle edit-button" data-toggle="dropdown">

View File

@ -25,7 +25,7 @@
Users &amp; Groups Users &amp; Groups
</h1> </h1>
<div class="row"> <div class="row">
<div ng-repeat="flash in flashes" style="text-align:center" class="alert alert-{{flash.type}}"> <div ng-repeat="flash in flashes.main" style="text-align:center" class="alert alert-{{flash.type}}">
<i class="fa {{flash.icon}}"></i> {{flash.message}} <i class="fa {{flash.icon}}"></i> {{flash.message}}
</div> </div>
</div> </div>
@ -43,7 +43,7 @@
<div ng-show="groups.length" class="row"> <div ng-show="groups.length" class="row">
<table ng-table="mainTableParams" class="table table-hover table-striped table-bordered"> <table ng-table="mainTableParams" class="table table-hover table-striped table-bordered">
<tbody> <tbody>
<tr ng-repeat="group in $data" class="editable-row"> <tr ng-repeat="group in $data | orderBy: '-modified_date'" class="editable-row">
<td data-title="'Name'" sortable="'name'" class="col-sm-1">{{group.name}}</td> <td data-title="'Name'" sortable="'name'" class="col-sm-1">{{group.name}}</td>
<td data-title="'Members'" class="col-sm-2"> <td data-title="'Members'" class="col-sm-2">
<span ng-repeat="target in group.targets | cut:5 track by $index "> <span ng-repeat="target in group.targets | cut:5 track by $index ">

View File

@ -14,6 +14,7 @@ import (
var Logger = log.New(os.Stdout, " ", log.Ldate|log.Ltime|log.Lshortfile) var Logger = log.New(os.Stdout, " ", log.Ldate|log.Ltime|log.Lshortfile)
// Worker is the background worker that handles watching for new campaigns and sending emails appropriately.
type Worker struct { type Worker struct {
Queue chan *models.Campaign Queue chan *models.Campaign
} }