diff --git a/.travis.yml b/.travis.yml index ae717ce6..fcc62ea1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,7 +18,7 @@ services: env: global: - - VERSION=0.29.8 + - VERSION=0.29.9 - DB_HOST=localhost - DB_USER=travis - DB_PASS= diff --git a/Dockerfile b/Dockerfile index 316d0aca..b8d18201 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ FROM alpine:latest -ENV VERSION=v0.29.8 +ENV VERSION=v0.29.9 RUN apk --no-cache add libstdc++ ca-certificates RUN wget -q https://github.com/hunterlong/statup/releases/download/$VERSION/statup-linux-alpine.tar.gz && \ diff --git a/cli.go b/cli.go index 125438f8..cef480c8 100644 --- a/cli.go +++ b/cli.go @@ -5,7 +5,6 @@ import ( "fmt" "github.com/hunterlong/statup/core" "github.com/hunterlong/statup/plugin" - "github.com/hunterlong/statup/types" "github.com/hunterlong/statup/utils" "github.com/joho/godotenv" "io/ioutil" @@ -246,18 +245,6 @@ func FakeSeed(plug plugin.PluginActions) { } fakeUser.Create() - comm := &types.Communication{ - Id: 1, - Method: "email", - } - core.Create(comm) - - comm2 := &types.Communication{ - Id: 2, - Method: "slack", - } - core.Create(comm2) - for i := 0; i <= 50; i++ { dd := core.HitData{ Latency: rand.Float64(), diff --git a/core/communication.go b/core/communication.go deleted file mode 100644 index 9bccb2f6..00000000 --- a/core/communication.go +++ /dev/null @@ -1,97 +0,0 @@ -package core - -import ( - "fmt" - "github.com/hunterlong/statup/notifications" - "github.com/hunterlong/statup/types" - "github.com/hunterlong/statup/utils" - "time" -) - -func LoadDefaultCommunications() { - notifications.EmailComm = SelectCommunication(1) - emailer := notifications.EmailComm - if emailer.Enabled { - admin, _ := SelectUser(1) - notifications.LoadEmailer(emailer) - email := &types.Email{ - To: admin.Email, - Subject: "Test Email", - Template: "message.html", - Data: nil, - From: emailer.Var1, - } - notifications.SendEmail(EmailBox, email) - go notifications.EmailRoutine() - } - notifications.SlackComm = SelectCommunication(2) - slack := notifications.SlackComm - if slack.Enabled { - notifications.LoadSlack(slack.Host) - msg := fmt.Sprintf("Slack loaded on your Statup Status Page!") - notifications.SendSlack(msg) - go notifications.SlackRoutine() - } -} - -func LoadComms() { - for _, c := range CoreApp.Communications { - if c.Enabled { - - } - } -} - -func SelectAllCommunications() ([]*types.Communication, error) { - var c []*types.Communication - col := DbSession.Collection("communication").Find() - err := col.OrderBy("id").All(&c) - CoreApp.Communications = c - return c, err -} - -func Create(c *types.Communication) (int64, error) { - c.CreatedAt = time.Now() - uuid, err := DbSession.Collection("communication").Insert(c) - if err != nil { - utils.Log(3, err) - } - if uuid == nil { - utils.Log(2, err) - return 0, err - } - c.Id = uuid.(int64) - c.Routine = make(chan struct{}) - if CoreApp != nil { - CoreApp.Communications = append(CoreApp.Communications, c) - } - return uuid.(int64), err -} - -func Disable(c *types.Communication) { - c.Enabled = false - Update(c) -} - -func Enable(c *types.Communication) { - c.Enabled = true - Update(c) -} - -func Update(c *types.Communication) *types.Communication { - col := DbSession.Collection("communication").Find("id", c.Id) - col.Update(c) - SelectAllCommunications() - return c -} - -func SelectCommunication(id int64) *types.Communication { - var comm *types.Communication - col := DbSession.Collection("communication").Find("id", id) - err := col.One(&comm) - if err != nil { - utils.Log(2, err) - return nil - } - return comm -} diff --git a/core/configs.go b/core/configs.go index 8f4c5a77..bcee6837 100644 --- a/core/configs.go +++ b/core/configs.go @@ -2,14 +2,19 @@ package core import ( "errors" + "fmt" "github.com/go-yaml/yaml" "github.com/hunterlong/statup/types" + "github.com/hunterlong/statup/utils" "io/ioutil" "os" + "time" ) func LoadConfig() (*types.Config, error) { if os.Getenv("DB_CONN") != "" { + utils.Log(1, "DB_CONN environment variable was found") + //time.Sleep(20 * time.Second) return LoadUsingEnv() } Configs = new(types.Config) @@ -51,12 +56,65 @@ func LoadUsingEnv() (*types.Config, error) { if os.Getenv("USE_CDN") == "true" { CoreApp.UseCdn = true } + + dbConfig := &DbConfig{ + DbConn: os.Getenv("DB_CONN"), + DbHost: os.Getenv("DB_HOST"), + DbUser: os.Getenv("DB_USER"), + DbPass: os.Getenv("DB_PASS"), + DbData: os.Getenv("DB_DATABASE"), + DbPort: 5432, + Project: "Statup - " + os.Getenv("NAME"), + Description: "New Statup Installation", + Domain: os.Getenv("DOMAIN"), + Username: "admin", + Password: "admin", + Email: "info@localhost.com", + } + + err := DbConnection(dbConfig.DbConn) + if err != nil { + utils.Log(4, err) + return nil, err + } + + exists, err := DbSession.Collection("core").Find().Exists() + if !exists { + + utils.Log(1, fmt.Sprintf("Core database does not exist, creating now!")) + DropDatabase() + CreateDatabase() + + CoreApp = &Core{ + Name: dbConfig.Project, + Description: dbConfig.Description, + Config: "config.yml", + ApiKey: utils.NewSHA1Hash(9), + ApiSecret: utils.NewSHA1Hash(16), + Domain: dbConfig.Domain, + MigrationId: time.Now().Unix(), + } + + CoreApp.DbConnection = dbConfig.DbConn + + err := CoreApp.Insert() + if err != nil { + utils.Log(3, err) + } + + admin := &User{ + Username: "admin", + Password: "admin", + Email: "info@admin.com", + Admin: true, + } + admin.Create() + + LoadSampleData() + + return Configs, err + + } + return Configs, nil } - -func ifOr(val, def string) string { - if val == "" { - return def - } - return val -} diff --git a/core/core.go b/core/core.go index d6804229..84bec322 100644 --- a/core/core.go +++ b/core/core.go @@ -2,6 +2,7 @@ package core import ( "github.com/GeertJohan/go.rice" + "github.com/hunterlong/statup/notifiers" "github.com/hunterlong/statup/plugin" "github.com/hunterlong/statup/types" "github.com/pkg/errors" @@ -28,7 +29,7 @@ type Core struct { Plugins []plugin.Info Repos []PluginJSON AllPlugins []plugin.PluginActions - Communications []*types.Communication + Communications []notifiers.AllNotifiers DbConnection string started time.Time } @@ -57,13 +58,18 @@ func NewCore() *Core { return CoreApp } +func (c *Core) Insert() error { + col := DbSession.Collection("core") + _, err := col.Insert(c) + return err +} + func InitApp() { SelectCore() - SelectAllCommunications() - InsertDefaultComms() - LoadDefaultCommunications() + notifiers.Collections = DbSession.Collection("communication") SelectAllServices() CheckServices() + CoreApp.Communications = notifiers.Load() go DatabaseMaintence() } @@ -121,6 +127,11 @@ func SelectLastMigration() (int64, error) { func SelectCore() (*Core, error) { var c *Core + exists := DbSession.Collection("core").Exists() + if !exists { + return nil, errors.New("core database has not been setup yet.") + } + err := DbSession.Collection("core").Find().One(&c) if err != nil { return nil, err diff --git a/core/database.go b/core/database.go index 40e0df5a..1aecfc01 100644 --- a/core/database.go +++ b/core/database.go @@ -135,6 +135,9 @@ func (c *DbConfig) Save() error { } CoreApp, err = SelectCore() + if err != nil { + utils.Log(4, err) + } CoreApp.DbConnection = c.DbConn return err @@ -207,7 +210,7 @@ func RunDatabaseUpgrades() error { } func DropDatabase() { - fmt.Println("Dropping Tables...") + utils.Log(1, "Dropping Database Tables...") down, _ := SqlBox.String("down.sql") requests := strings.Split(down, ";") for _, request := range requests { @@ -219,7 +222,7 @@ func DropDatabase() { } func CreateDatabase() { - fmt.Println("Creating Tables...") + utils.Log(1, "Creating Database Tables...") sql := "postgres_up.sql" if CoreApp.DbConnection == "mysql" { sql = "mysql_up.sql" @@ -236,7 +239,7 @@ func CreateDatabase() { } //secret := NewSHA1Hash() //db.QueryRow("INSERT INTO core (secret, version) VALUES ($1, $2);", secret, VERSION).Scan() - fmt.Println("Database Created") + utils.Log(1, "Database Created") //SampleData() } diff --git a/core/events.go b/core/events.go index 29796333..df27fcc0 100644 --- a/core/events.go +++ b/core/events.go @@ -1,11 +1,9 @@ package core import ( - "fmt" "github.com/fatih/structs" - "github.com/hunterlong/statup/notifications" + "github.com/hunterlong/statup/notifiers" "github.com/hunterlong/statup/plugin" - "github.com/hunterlong/statup/types" "upper.io/db.v3/lib/sqlbuilder" ) @@ -19,48 +17,14 @@ func OnSuccess(s *Service) { for _, p := range CoreApp.AllPlugins { p.OnSuccess(structs.Map(s)) } + notifiers.OnSuccess() } func OnFailure(s *Service, f FailureData) { for _, p := range CoreApp.AllPlugins { p.OnFailure(structs.Map(s)) } - if notifications.SlackComm != nil { - onFailureSlack(s, f) - } - if notifications.EmailComm != nil { - onFailureEmail(s, f) - } -} - -func onFailureSlack(s *Service, f FailureData) { - slack := SelectCommunication(2) - if slack.Enabled { - msg := fmt.Sprintf("Service %v is currently offline! Issue: %v", s.Name, f.Issue) - notifications.SendSlack(msg) - } -} - -type failedEmail struct { - Service *Service - FailureData FailureData - Domain string -} - -func onFailureEmail(s *Service, f FailureData) { - email := SelectCommunication(1) - if email.Enabled { - data := failedEmail{s, f, CoreApp.Domain} - admin, _ := SelectUser(1) - email := &types.Email{ - To: admin.Email, - Subject: fmt.Sprintf("Service %v is Down", s.Name), - Template: "failure.html", - Data: data, - From: email.Var1, - } - notifications.SendEmail(EmailBox, email) - } + notifiers.OnFailure() } func OnSettingsSaved(c *Core) { diff --git a/core/services.go b/core/services.go index 815ed836..d763ea99 100644 --- a/core/services.go +++ b/core/services.go @@ -58,6 +58,7 @@ func SelectAllServices() ([]*Service, error) { err := col.All(&srvcs) if err != nil { utils.Log(3, err) + return nil, err } for _, s := range srvcs { s.Checkins = s.SelectAllCheckins() diff --git a/core/setup.go b/core/setup.go index f81bde73..841ca1f9 100644 --- a/core/setup.go +++ b/core/setup.go @@ -2,32 +2,10 @@ package core import ( "fmt" - "github.com/hunterlong/statup/types" "github.com/hunterlong/statup/utils" "os" ) -func InsertDefaultComms() { - emailer := SelectCommunication(1) - if emailer == nil { - emailer := &types.Communication{ - Method: "email", - Removable: false, - Enabled: false, - } - Create(emailer) - } - slack := SelectCommunication(2) - if slack == nil { - slack := &types.Communication{ - Method: "slack", - Removable: false, - Enabled: false, - } - Create(slack) - } -} - func DeleteConfig() { err := os.Remove("./config.yml") if err != nil { @@ -95,15 +73,15 @@ func LoadSampleData() error { utils.Log(3, fmt.Sprintf("Error creating Service %v: %v", id, err)) } - checkin := &Checkin{ - Service: s2.Id, - Interval: 30, - Api: utils.NewSHA1Hash(18), - } - id, err = checkin.Create() - if err != nil { - utils.Log(3, fmt.Sprintf("Error creating Checkin %v: %v", id, err)) - } + //checkin := &Checkin{ + // Service: s2.Id, + // Interval: 30, + // Api: utils.NewSHA1Hash(18), + //} + //id, err = checkin.Create() + //if err != nil { + // utils.Log(3, fmt.Sprintf("Error creating Checkin %v: %v", id, err)) + //} //for i := 0; i < 3; i++ { // s1.Check() diff --git a/handlers/routes.go b/handlers/routes.go index 42476c54..873afe43 100644 --- a/handlers/routes.go +++ b/handlers/routes.go @@ -37,8 +37,7 @@ func Router() *mux.Router { r.Handle("/settings", http.HandlerFunc(SaveSettingsHandler)).Methods("POST") r.Handle("/settings/css", http.HandlerFunc(SaveSASSHandler)).Methods("POST") r.Handle("/settings/build", http.HandlerFunc(SaveAssetsHandler)).Methods("GET") - r.Handle("/settings/email", http.HandlerFunc(SaveEmailSettingsHandler)).Methods("POST") - r.Handle("/settings/slack", http.HandlerFunc(SaveSlackSettingsHandler)).Methods("POST") + r.Handle("/settings/notifier/{id}", http.HandlerFunc(SaveNotificationHandler)).Methods("POST") r.Handle("/plugins/download/{name}", http.HandlerFunc(PluginsDownloadHandler)) r.Handle("/plugins/{name}/save", http.HandlerFunc(PluginSavedHandler)).Methods("POST") r.Handle("/help", http.HandlerFunc(HelpHandler)) diff --git a/handlers/settings.go b/handlers/settings.go index f95d95ec..59951b21 100644 --- a/handlers/settings.go +++ b/handlers/settings.go @@ -1,9 +1,10 @@ package handlers import ( + "fmt" + "github.com/gorilla/mux" "github.com/hunterlong/statup/core" - "github.com/hunterlong/statup/notifications" - "github.com/hunterlong/statup/types" + "github.com/hunterlong/statup/notifiers" "github.com/hunterlong/statup/utils" "net/http" ) @@ -84,65 +85,72 @@ func SaveAssetsHandler(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, "/settings", http.StatusSeeOther) } -func SaveEmailSettingsHandler(w http.ResponseWriter, r *http.Request) { +func SaveNotificationHandler(w http.ResponseWriter, r *http.Request) { + var err error if !IsAuthenticated(r) { http.Redirect(w, r, "/", http.StatusSeeOther) return } - emailer := core.SelectCommunication(1) + vars := mux.Vars(r) r.ParseForm() - smtpHost := r.PostForm.Get("host") - smtpUser := r.PostForm.Get("username") - smtpPass := r.PostForm.Get("password") - smtpPort := int(utils.StringInt(r.PostForm.Get("port"))) - smtpOutgoing := r.PostForm.Get("address") - enabled := r.PostForm.Get("enable_email") - emailer.Host = smtpHost - emailer.Username = smtpUser - if smtpPass != "#######################" { - emailer.Password = smtpPass + notifierId := vars["id"] + enabled := r.PostForm.Get("enable") + + host := r.PostForm.Get("host") + port := int(utils.StringInt(r.PostForm.Get("port"))) + username := r.PostForm.Get("username") + password := r.PostForm.Get("password") + var1 := r.PostForm.Get("var1") + var2 := r.PostForm.Get("var2") + apiKey := r.PostForm.Get("api_key") + apiSecret := r.PostForm.Get("api_secret") + limits := int64(utils.StringInt(r.PostForm.Get("limits"))) + notifer := notifiers.Select(utils.StringInt(notifierId)) + + if host != "" { + notifer.Host = host + } + if port != 0 { + notifer.Port = port + } + if username != "" { + notifer.Username = username + } + if password != "" && password != "##########" { + notifer.Password = password + } + if var1 != "" { + notifer.Var1 = var1 + } + if var2 != "" { + notifer.Var2 = var2 + } + if apiKey != "" { + notifer.ApiKey = apiKey + } + if apiSecret != "" { + notifer.ApiSecret = apiSecret + } + if limits != 0 { + notifer.Limits = limits } - emailer.Port = smtpPort - emailer.Var1 = smtpOutgoing - emailer.Enabled = false if enabled == "on" { - emailer.Enabled = true + notifer.Enabled = true + } else { + notifer.Enabled = false + } + notifer, err = notifer.Update() + if err != nil { + utils.Log(3, err) } - core.Update(emailer) - sample := &types.Email{ - To: SessionUser(r).Email, - Subject: "Test Email", - Template: "message.html", - From: emailer.Var1, - } - notifications.LoadEmailer(emailer) - notifications.SendEmail(core.EmailBox, sample) - notifications.EmailComm = emailer - if emailer.Enabled { - utils.Log(1, "Starting Email Routine, 1 unique email per 60 seconds") - go notifications.EmailRoutine() + if notifer.Enabled { + notify := notifiers.SelectNotifier(notifer.Id) + go notify.Run() } + utils.Log(1, fmt.Sprintf("Notifier saved: %v", notifer)) + http.Redirect(w, r, "/settings", http.StatusSeeOther) } - -func SaveSlackSettingsHandler(w http.ResponseWriter, r *http.Request) { - if !IsAuthenticated(r) { - http.Redirect(w, r, "/", http.StatusSeeOther) - return - } - slack := core.SelectCommunication(2) - r.ParseForm() - slackWebhook := r.PostForm.Get("slack_url") - enable := r.PostForm.Get("enable_slack") - slack.Enabled = false - if enable == "on" && slackWebhook != "" { - slack.Enabled = true - go notifications.SlackRoutine() - } - slack.Host = slackWebhook - core.Update(slack) - http.Redirect(w, r, "/settings", http.StatusSeeOther) -} diff --git a/main.go b/main.go index eaa305f3..cdc76b76 100644 --- a/main.go +++ b/main.go @@ -65,7 +65,7 @@ func mainProcess() { var err error err = core.DbConnection(core.Configs.Connection) if err != nil { - utils.Log(3, err) + utils.Log(4, fmt.Sprintf("could not connect to database: %v", err)) } core.RunDatabaseUpgrades() diff --git a/main_test.go b/main_test.go index f93064e6..40256ddc 100644 --- a/main_test.go +++ b/main_test.go @@ -5,6 +5,7 @@ import ( "github.com/gorilla/sessions" "github.com/hunterlong/statup/core" "github.com/hunterlong/statup/handlers" + "github.com/hunterlong/statup/notifiers" "github.com/rendon/testcli" "github.com/stretchr/testify/assert" "net/http" @@ -218,7 +219,6 @@ func RunMySQLMakeConfig(t *testing.T, db string) { err = core.DbConnection(core.Configs.Connection) assert.Nil(t, err) - core.InsertDefaultComms() } func RunInsertMysqlSample(t *testing.T) { @@ -254,7 +254,8 @@ func RunSelectAllMysqlServices(t *testing.T) { func RunSelectAllMysqlCommunications(t *testing.T) { var err error - comms, err := core.SelectAllCommunications() + notifiers.Collections = core.DbSession.Collection("communication") + comms := notifiers.Load() assert.Nil(t, err) assert.Equal(t, 2, len(comms)) } @@ -533,7 +534,6 @@ func RunSettingsHandler(t *testing.T) { route.ServeHTTP(rr, req) assert.True(t, strings.Contains(rr.Body.String(), "Statup | Settings")) assert.True(t, strings.Contains(rr.Body.String(), "Theme Editor")) - assert.True(t, strings.Contains(rr.Body.String(), "Email Settings")) assert.True(t, strings.Contains(rr.Body.String(), "footer")) } diff --git a/nocmmitthis.env b/nocmmitthis.env new file mode 100644 index 00000000..ee160523 --- /dev/null +++ b/nocmmitthis.env @@ -0,0 +1,29 @@ +########################### +## Database Information # +########################### +DB_CONNECTION=postgres +DB_HOST=0.0.0.0 +DB_PORT=5432 +DB_USER=root +DB_PASS=password123 +DB_DATABASE=root + +########################### +## STATUP PAGE SETTINGS # +########################### +NAME=Demo +DESCRIPTION=This is an awesome page +DOMAIN=https://domain.com +ADMIN_USER=admin +ADMIN_PASS=admin +ADMIN_EMAIL=info@admin.com +USE_CDN=true + +########################### +## System Values ## +########################### +IS_DOCKER=true +IS_AWS=true +SASS=/usr/local/bin/sass +BASH_ENV=/bin/bash + diff --git a/notifications/email.go b/notifications/email.go deleted file mode 100644 index 49001ee5..00000000 --- a/notifications/email.go +++ /dev/null @@ -1,105 +0,0 @@ -package notifications - -import ( - "bytes" - "crypto/tls" - "fmt" - "github.com/GeertJohan/go.rice" - "github.com/hunterlong/statup/types" - "github.com/hunterlong/statup/utils" - "gopkg.in/gomail.v2" - "html/template" - "time" -) - -var ( - mailer *gomail.Dialer - emailQueue []*types.Email - EmailComm *types.Communication -) - -func EmailRoutine() { - var sentAddresses []string - for _, email := range emailQueue { - if inArray(sentAddresses, email.To) || email.Sent { - emailQueue = removeEmail(emailQueue, email) - continue - } - e := email - go func(email *types.Email) { - err := dialSend(email) - if err == nil { - email.Sent = true - sentAddresses = append(sentAddresses, email.To) - utils.Log(1, fmt.Sprintf("Email '%v' sent to: %v using the %v template (size: %v)", email.Subject, email.To, email.Template, len([]byte(email.Source)))) - emailQueue = removeEmail(emailQueue, email) - } - }(e) - } - time.Sleep(60 * time.Second) - if EmailComm.Enabled { - EmailRoutine() - } -} - -func dialSend(email *types.Email) error { - m := gomail.NewMessage() - m.SetHeader("From", email.From) - m.SetHeader("To", email.To) - m.SetHeader("Subject", email.Subject) - m.SetBody("text/html", email.Source) - if err := mailer.DialAndSend(m); err != nil { - utils.Log(3, fmt.Sprintf("Email '%v' sent to: %v using the %v template (size: %v) %v", email.Subject, email.To, email.Template, len([]byte(email.Source)), err)) - return err - } - return nil -} - -func LoadEmailer(mail *types.Communication) { - utils.Log(1, fmt.Sprintf("Loading SMTP Emailer using host: %v:%v", mail.Host, mail.Port)) - mailer = gomail.NewDialer(mail.Host, mail.Port, mail.Username, mail.Password) - mailer.TLSConfig = &tls.Config{InsecureSkipVerify: true} -} - -func SendEmail(box *rice.Box, email *types.Email) { - source := EmailTemplate(box, email.Template, email.Data) - email.Source = source - emailQueue = append(emailQueue, email) -} - -func EmailTemplate(box *rice.Box, tmpl string, data interface{}) string { - emailTpl, err := box.String(tmpl) - if err != nil { - utils.Log(3, err) - } - t := template.New("email") - t, err = t.Parse(emailTpl) - if err != nil { - utils.Log(3, err) - } - var tpl bytes.Buffer - if err := t.Execute(&tpl, data); err != nil { - utils.Log(2, err) - } - result := tpl.String() - return result -} - -func removeEmail(emails []*types.Email, em *types.Email) []*types.Email { - var newArr []*types.Email - for _, e := range emails { - if e != em { - newArr = append(newArr, e) - } - } - return newArr -} - -func inArray(a []string, v string) bool { - for _, i := range a { - if i == v { - return true - } - } - return false -} diff --git a/notifications/slack.go b/notifications/slack.go deleted file mode 100644 index f6e78480..00000000 --- a/notifications/slack.go +++ /dev/null @@ -1,61 +0,0 @@ -package notifications - -import ( - "bytes" - "fmt" - "github.com/hunterlong/statup/types" - "github.com/hunterlong/statup/utils" - "github.com/pkg/errors" - "net/http" - "time" -) - -var ( - slackUrl string - sentLastMin int - slackMessages []string - SlackComm *types.Communication -) - -func LoadSlack(url string) { - if url == "" { - utils.Log(1, "Slack Webhook URL is empty") - return - } - slackUrl = url -} - -func SlackRoutine() { - for _, msg := range slackMessages { - utils.Log(1, fmt.Sprintf("Sending JSON to Slack Webhook: %v", msg)) - client := http.Client{Timeout: 15 * time.Second} - _, err := client.Post(slackUrl, "application/json", bytes.NewBuffer([]byte(msg))) - if err != nil { - utils.Log(3, fmt.Sprintf("Issue sending Slack notification: %v", err)) - } - slackMessages = removeStrArray(slackMessages, msg) - } - time.Sleep(60 * time.Second) - if SlackComm.Enabled { - SlackRoutine() - } -} - -func removeStrArray(arr []string, v string) []string { - var newArray []string - for _, i := range arr { - if i != v { - newArray = append(newArray, v) - } - } - return newArray -} - -func SendSlack(msg string) error { - if slackUrl == "" { - return errors.New("Slack Webhook URL has not been set in settings") - } - fullMessage := fmt.Sprintf("{\"text\":\"%v\"}", msg) - slackMessages = append(slackMessages, fullMessage) - return nil -} diff --git a/notifiers/email.go b/notifiers/email.go new file mode 100644 index 00000000..ab9631c0 --- /dev/null +++ b/notifiers/email.go @@ -0,0 +1,241 @@ +package notifiers + +import ( + "bytes" + "crypto/tls" + "fmt" + "github.com/GeertJohan/go.rice" + "github.com/hunterlong/statup/types" + "github.com/hunterlong/statup/utils" + "gopkg.in/gomail.v2" + "html/template" + "time" +) + +const ( + EMAIL_ID int64 = 1 + EMAIL_METHOD = "email" +) + +var ( + emailer *Email + emailArray []string + emailQueue []*types.Email +) + +type Email struct { + *Notification + mailer *gomail.Dialer +} + +// DEFINE YOUR NOTIFICATION HERE. +func init() { + + emailer = &Email{ + Notification: &Notification{ + Id: EMAIL_ID, + Method: EMAIL_METHOD, + Form: []NotificationForm{{ + id: 1, + Type: "text", + Title: "SMTP Host", + Placeholder: "Insert your SMTP Host here.", + DbField: "Host", + }, { + id: 1, + Type: "text", + Title: "SMTP Username", + Placeholder: "Insert your SMTP Username here.", + DbField: "Username", + }, { + id: 1, + Type: "password", + Title: "SMTP Password", + Placeholder: "Insert your SMTP Password here.", + DbField: "Password", + }, { + id: 1, + Type: "number", + Title: "SMTP Port", + Placeholder: "Insert your SMTP Port here.", + DbField: "Port", + }, { + id: 1, + Type: "text", + Title: "Outgoing Email Address", + Placeholder: "Insert your Outgoing Email Address", + DbField: "Var1", + }, { + id: 1, + Type: "number", + Title: "Limits per Hour", + Placeholder: "How many emails can it send per hour", + DbField: "Limits", + }}, + }} + + add(emailer) +} + +// Select Obj +func (u *Email) Select() *Notification { + return u.Notification +} + +// WHEN NOTIFIER LOADS +func (u *Email) Init() error { + err := u.Install() + + if err == nil { + notifier, _ := SelectNotification(u.Id) + forms := u.Form + u.Notification = notifier + u.Form = forms + if u.Enabled { + + utils.Log(1, fmt.Sprintf("Loading SMTP Emailer using host: %v:%v", u.Notification.Host, u.Notification.Port)) + u.mailer = gomail.NewDialer(u.Notification.Host, u.Notification.Port, u.Notification.Username, u.Notification.Password) + u.mailer.TLSConfig = &tls.Config{InsecureSkipVerify: true} + + go u.Run() + } + } + + //go u.Run() + return nil +} + +func (u *Email) Test() error { + //email := &types.Email{ + // To: "info@socialeck.com", + // Subject: "Test Email", + // Template: "message.html", + // Data: nil, + // From: emailer.Var1, + //} + //SendEmail(core.EmailBox, email) + return nil +} + +// AFTER NOTIFIER LOADS, IF ENABLED, START A QUEUE PROCESS +func (u *Email) Run() error { + var sentAddresses []string + for _, email := range emailQueue { + if inArray(sentAddresses, email.To) || email.Sent { + emailQueue = removeEmail(emailQueue, email) + continue + } + e := email + go func(email *types.Email) { + err := u.dialSend(email) + if err == nil { + email.Sent = true + sentAddresses = append(sentAddresses, email.To) + utils.Log(1, fmt.Sprintf("Email '%v' sent to: %v using the %v template (size: %v)", email.Subject, email.To, email.Template, len([]byte(email.Source)))) + emailQueue = removeEmail(emailQueue, email) + } + }(e) + } + time.Sleep(60 * time.Second) + if u.Enabled { + return u.Run() + } + return nil +} + +// ON SERVICE FAILURE, DO YOUR OWN FUNCTIONS +func (u *Email) OnFailure() error { + if u.Enabled { + utils.Log(1, fmt.Sprintf("Notification %v is receiving a failure notification.", u.Method)) + // Do failing stuff here! + } + return nil +} + +// ON SERVICE SUCCESS, DO YOUR OWN FUNCTIONS +func (u *Email) OnSuccess() error { + if u.Enabled { + utils.Log(1, fmt.Sprintf("Notification %v is receiving a failure notification.", u.Method)) + // Do failing stuff here! + } + return nil +} + +// ON SAVE OR UPDATE OF THE NOTIFIER FORM +func (u *Email) OnSave() error { + utils.Log(1, fmt.Sprintf("Notification %v is receiving updated information.", u.Method)) + + // Do updating stuff here + + return nil +} + +// ON SERVICE FAILURE, DO YOUR OWN FUNCTIONS +func (u *Email) Install() error { + inDb, err := emailer.Notification.isInDatabase() + if !inDb { + newNotifer, err := InsertDatabase(u.Notification) + if err != nil { + utils.Log(3, err) + return err + } + utils.Log(1, fmt.Sprintf("new notifier #%v installed: %v", newNotifer, u.Method)) + } + return err +} + +func (u *Email) dialSend(email *types.Email) error { + m := gomail.NewMessage() + m.SetHeader("From", email.From) + m.SetHeader("To", email.To) + m.SetHeader("Subject", email.Subject) + m.SetBody("text/html", email.Source) + if err := u.mailer.DialAndSend(m); err != nil { + utils.Log(3, fmt.Sprintf("Email '%v' sent to: %v using the %v template (size: %v) %v", email.Subject, email.To, email.Template, len([]byte(email.Source)), err)) + return err + } + return nil +} + +func SendEmail(box *rice.Box, email *types.Email) { + source := EmailTemplate(box, email.Template, email.Data) + email.Source = source + emailQueue = append(emailQueue, email) +} + +func EmailTemplate(box *rice.Box, tmpl string, data interface{}) string { + emailTpl, err := box.String(tmpl) + if err != nil { + utils.Log(3, err) + } + t := template.New("email") + t, err = t.Parse(emailTpl) + if err != nil { + utils.Log(3, err) + } + var tpl bytes.Buffer + if err := t.Execute(&tpl, data); err != nil { + utils.Log(2, err) + } + result := tpl.String() + return result +} + +func removeEmail(emails []*types.Email, em *types.Email) []*types.Email { + var newArr []*types.Email + for _, e := range emails { + if e != em { + newArr = append(newArr, e) + } + } + return newArr +} + +func inArray(a []string, v string) bool { + for _, i := range a { + if i == v { + return true + } + } + return false +} diff --git a/notifiers/notifiers.go b/notifiers/notifiers.go new file mode 100644 index 00000000..07401e2f --- /dev/null +++ b/notifiers/notifiers.go @@ -0,0 +1,173 @@ +package notifiers + +import ( + "fmt" + "github.com/hunterlong/statup/utils" + "strings" + "time" + "upper.io/db.v3" +) + +var ( + AllCommunications []AllNotifiers + Collections db.Collection +) + +type AllNotifiers interface{} + +func add(c interface{}) { + AllCommunications = append(AllCommunications, c) +} + +func Load() []AllNotifiers { + utils.Log(1, "Loading notifiers") + var notifiers []AllNotifiers + for _, comm := range AllCommunications { + n := comm.(Notifier) + n.Init() + notifiers = append(notifiers, n) + n.Test() + } + return notifiers +} + +type Notification struct { + Id int64 `db:"id,omitempty" json:"id"` + Method string `db:"method" json:"method"` + Host string `db:"host" json:"-"` + Port int `db:"port" json:"-"` + Username string `db:"username" json:"-"` + Password string `db:"password" json:"-"` + Var1 string `db:"var1" json:"-"` + Var2 string `db:"var2" json:"-"` + ApiKey string `db:"api_key" json:"-"` + ApiSecret string `db:"api_secret" json:"-"` + Enabled bool `db:"enabled" json:"enabled"` + Limits int64 `db:"limits" json:"-"` + Removable bool `db:"removable" json:"-"` + CreatedAt time.Time `db:"created_at" json:"created_at"` + Form []NotificationForm + Routine chan struct{} +} + +type Notifier interface { + Init() error + Install() error + Run() error + OnFailure() error + OnSuccess() error + Select() *Notification + Test() error +} + +type NotificationForm struct { + id int64 + Type string + Title string + Placeholder string + DbField string +} + +func (n *Notification) isInDatabase() (bool, error) { + return Collections.Find("id", n.Id).Exists() +} + +func SelectNotification(id int64) (*Notification, error) { + var notifier *Notification + err := Collections.Find("id", id).One(¬ifier) + return notifier, err +} + +func (n *Notification) Update() (*Notification, error) { + n.CreatedAt = time.Now() + err := Collections.Find("id", n.Id).Update(n) + + return n, err +} + +func InsertDatabase(n *Notification) (int64, error) { + n.CreatedAt = time.Now() + newId, err := Collections.Insert(n) + if err != nil { + return 0, err + } + return newId.(int64), err +} + +func Select(id int64) *Notification { + var notifier *Notification + for _, n := range AllCommunications { + notif := n.(Notifier) + notifier = notif.Select() + if notifier.Id == id { + return notifier + } + } + return notifier +} + +func SelectNotifier(id int64) Notifier { + var notifier Notifier + for _, n := range AllCommunications { + notif := n.(Notifier) + n := notif.Select() + if n.Id == id { + return notif + } + } + return notifier +} + +func (f NotificationForm) Value() string { + notifier := Select(f.id) + return notifier.GetValue(f.DbField) +} + +func (n *Notification) GetValue(dbField string) string { + dbField = strings.ToLower(dbField) + switch dbField { + case "host": + return n.Host + case "port": + return fmt.Sprintf("%v", n.Port) + case "username": + return n.Username + case "password": + if n.Password != "" { + return "##########" + } + case "var1": + return n.Var1 + case "var2": + return n.Var2 + case "api_key": + return n.ApiKey + case "api_secret": + return n.ApiSecret + } + return "" +} + +func OnFailure() { + for _, comm := range AllCommunications { + n := comm.(Notifier) + n.OnFailure() + } +} + +func OnSuccess() { + for _, comm := range AllCommunications { + n := comm.(Notifier) + n.OnSuccess() + } +} + +func uniqueMessages(arr []string, v string) []string { + var newArray []string + for _, i := range arr { + if i != v { + newArray = append(newArray, v) + } + } + return newArray +} diff --git a/notifiers/slack.go b/notifiers/slack.go new file mode 100644 index 00000000..ed5648bd --- /dev/null +++ b/notifiers/slack.go @@ -0,0 +1,137 @@ +package notifiers + +import ( + "bytes" + "fmt" + "github.com/hunterlong/statup/utils" + "net/http" + "time" +) + +const ( + SLACK_ID = 2 + SLACK_METHOD = "slack" +) + +var ( + slacker *Slack + slackMessages []string +) + +type Slack struct { + *Notification +} + +// DEFINE YOUR NOTIFICATION HERE. +func init() { + slacker = &Slack{&Notification{ + Id: SLACK_ID, + Method: SLACK_METHOD, + Host: "https://webhooksurl.slack.com/***", + Form: []NotificationForm{{ + id: 2, + Type: "text", + Title: "Incoming Webhook Url", + Placeholder: "Insert your Slack webhook URL here.", + DbField: "Host", + }}}, + } + add(slacker) +} + +// Select Obj +func (u *Slack) Select() *Notification { + return u.Notification +} + +// WHEN NOTIFIER LOADS +func (u *Slack) Init() error { + + err := u.Install() + + if err == nil { + notifier, _ := SelectNotification(u.Id) + forms := u.Form + u.Notification = notifier + u.Form = forms + if u.Enabled { + go u.Run() + } + } + + return err +} + +func (u *Slack) Test() error { + SendSlack("Slack notifications on your Statup server is working!") + return nil +} + +// AFTER NOTIFIER LOADS, IF ENABLED, START A QUEUE PROCESS +func (u *Slack) Run() error { + for _, msg := range slackMessages { + utils.Log(1, fmt.Sprintf("Sending JSON to Slack Webhook: %v", msg)) + client := http.Client{Timeout: 15 * time.Second} + _, err := client.Post("https://hooks.slack.com/services/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", "application/json", bytes.NewBuffer([]byte(msg))) + if err != nil { + utils.Log(3, fmt.Sprintf("Issue sending Slack notification: %v", err)) + } + slackMessages = uniqueMessages(slackMessages, msg) + } + time.Sleep(60 * time.Second) + if u.Enabled { + u.Run() + } + return nil +} + +// CUSTOM FUNCTION FO SENDING SLACK MESSAGES +func SendSlack(msg string) error { + //if slackUrl == "" { + // return errors.New("Slack Webhook URL has not been set in settings") + //} + fullMessage := fmt.Sprintf("{\"text\":\"%v\"}", msg) + slackMessages = append(slackMessages, fullMessage) + return nil +} + +// ON SERVICE FAILURE, DO YOUR OWN FUNCTIONS +func (u *Slack) OnFailure() error { + if u.Enabled { + utils.Log(1, fmt.Sprintf("Notification %v is receiving a failure notification.", u.Method)) + // Do failing stuff here! + } + return nil +} + +// ON SERVICE SUCCESS, DO YOUR OWN FUNCTIONS +func (u *Slack) OnSuccess() error { + if u.Enabled { + utils.Log(1, fmt.Sprintf("Notification %v is receiving a successful notification.", u.Method)) + // Do checking or any successful things here + } + return nil +} + +// ON SAVE OR UPDATE OF THE NOTIFIER FORM +func (u *Slack) OnSave() error { + utils.Log(1, fmt.Sprintf("Notification %v is receiving updated information.", u.Method)) + + // Do updating stuff here + + return nil +} + +// ON SERVICE FAILURE, DO YOUR OWN FUNCTIONS +func (u *Slack) Install() error { + inDb, err := slacker.Notification.isInDatabase() + if !inDb { + newNotifer, err := InsertDatabase(u.Notification) + if err != nil { + utils.Log(3, err) + return err + } + utils.Log(1, fmt.Sprintf("new notifier #%v installed: %v", newNotifer, u.Method)) + } + return err +} diff --git a/source/tmpl/settings.html b/source/tmpl/settings.html index 926eea21..26b2a705 100644 --- a/source/tmpl/settings.html +++ b/source/tmpl/settings.html @@ -32,11 +32,9 @@ - {{ with $c := index .Communications 0 }} -
- -
+ {{ range .Communications }} +
+ + {{range .Form}}
- - + +
- -
- - -
- -
- - -
- -
- - -
- -
- - -
- -
- - -
- -
-
+ {{end}} +
+
- - + + -
- -
- -
- - - - -
- {{ end }} - - - {{ with $c := index .Communications 1 }} -
- -
- -
- - +
+
+
-
-
- - - - -
- -
- -
-
- - -
- -
+ +
{{ end }}
diff --git a/types/types.go b/types/types.go index 7ef3364c..d54bba6e 100644 --- a/types/types.go +++ b/types/types.go @@ -40,24 +40,6 @@ type Checkin struct { Last time.Time `json:"last"` } -type Communication struct { - Id int64 `db:"id,omitempty" json:"id"` - Method string `db:"method" json:"method"` - Host string `db:"host" json:"-"` - Port int `db:"port" json:"-"` - Username string `db:"username" json:"-"` - Password string `db:"password" json:"-"` - Var1 string `db:"var1" json:"-"` - Var2 string `db:"var2" json:"-"` - ApiKey string `db:"api_key" json:"-"` - ApiSecret string `db:"api_secret" json:"-"` - Enabled bool `db:"enabled" json:"enabled"` - Limits int64 `db:"limits" json:"-"` - Removable bool `db:"removable" json:"-"` - CreatedAt time.Time `db:"created_at" json:"created_at"` - Routine chan struct{} -} - type Email struct { To string Subject string