diff --git a/cmd/database.go b/cmd/database.go index bfc1798c..c438fac7 100644 --- a/cmd/database.go +++ b/cmd/database.go @@ -1,13 +1,13 @@ package main import ( - "github.com/statping/statping/notifiers" "github.com/statping/statping/types/checkins" "github.com/statping/statping/types/failures" "github.com/statping/statping/types/groups" "github.com/statping/statping/types/hits" "github.com/statping/statping/types/incidents" "github.com/statping/statping/types/messages" + "github.com/statping/statping/types/notifications" "github.com/statping/statping/types/services" "github.com/statping/statping/types/users" ) @@ -18,5 +18,5 @@ var ( ) func init() { - DbModels = []interface{}{&services.Service{}, &users.User{}, &hits.Hit{}, &failures.Failure{}, &messages.Message{}, &groups.Group{}, &checkins.Checkin{}, &checkins.CheckinHit{}, ¬ifiers.Notification{}, &incidents.Incident{}, &incidents.IncidentUpdate{}} + DbModels = []interface{}{&services.Service{}, &users.User{}, &hits.Hit{}, &failures.Failure{}, &messages.Message{}, &groups.Group{}, &checkins.Checkin{}, &checkins.CheckinHit{}, ¬ifications.Notification{}, &incidents.Incident{}, &incidents.IncidentUpdate{}} } diff --git a/types/core/init.go b/cmd/init.go similarity index 60% rename from types/core/init.go rename to cmd/init.go index 3e8b915b..b0141cc0 100644 --- a/types/core/init.go +++ b/cmd/init.go @@ -1,12 +1,14 @@ -package core +package main import ( "github.com/statping/statping/database" + "github.com/statping/statping/notifiers" + "github.com/statping/statping/types/core" "github.com/statping/statping/types/services" ) func InitApp() error { - if _, err := Select(); err != nil { + if _, err := core.Select(); err != nil { return err } @@ -16,7 +18,9 @@ func InitApp() error { go services.CheckServices() + notifiers.InitNotifiers() + database.StartMaintenceRoutine() - App.Setup = true + core.App.Setup = true return nil } diff --git a/cmd/main.go b/cmd/main.go index 3e66d75c..af5a0331 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -19,7 +19,6 @@ import ( "flag" "fmt" "github.com/getsentry/sentry-go" - "github.com/statping/statping/notifiers" "os" "os/signal" "syscall" @@ -30,7 +29,6 @@ import ( "github.com/pkg/errors" "github.com/statping/statping/handlers" "github.com/statping/statping/types/configs" - "github.com/statping/statping/types/core" "github.com/statping/statping/types/services" "github.com/statping/statping/utils" ) @@ -165,11 +163,11 @@ func main() { exit(err) } - log.Infoln("Migrating Notifiers...") - if err := notifiers.Migrate(); err != nil { - exit(errors.Wrap(err, "error migrating notifiers")) - } - log.Infoln("Notifiers Migrated") + //log.Infoln("Migrating Notifiers...") + //if err := notifier.Migrate(); err != nil { + // exit(errors.Wrap(err, "error migrating notifiers")) + //} + //log.Infoln("Notifiers Migrated") if err := mainProcess(); err != nil { exit(err) @@ -205,7 +203,7 @@ func mainProcess() error { return errors.Wrap(err, errStr) } - if err := core.InitApp(); err != nil { + if err := InitApp(); err != nil { return err } diff --git a/frontend/public/base.gohtml b/frontend/public/base.gohtml index c5142dc9..525d21e2 100644 --- a/frontend/public/base.gohtml +++ b/frontend/public/base.gohtml @@ -18,7 +18,7 @@ {{if USING_ASSETS}} - + {{else}} <% _.each(htmlWebpackPlugin.tags.headTags, function(headTag) { %> <%= headTag %> <% }) %> diff --git a/frontend/src/forms/Notifier.vue b/frontend/src/forms/Notifier.vue index d332b5da..29ea390b 100644 --- a/frontend/src/forms/Notifier.vue +++ b/frontend/src/forms/Notifier.vue @@ -94,8 +94,8 @@ export default { this.form[field] = this.notifier[field] }); await Api.notifier_save(this.form) - const notifiers = await Api.notifiers() - await this.$store.commit('setNotifiers', notifiers) + // const notifiers = await Api.notifiers() + // await this.$store.commit('setNotifiers', notifiers) this.saved = true this.loading = false setTimeout(() => { diff --git a/handlers/api.go b/handlers/api.go index b79a7611..6bb9b11e 100644 --- a/handlers/api.go +++ b/handlers/api.go @@ -19,12 +19,12 @@ import ( "encoding/json" "errors" "fmt" - "github.com/statping/statping/notifiers" "github.com/statping/statping/types/checkins" "github.com/statping/statping/types/core" "github.com/statping/statping/types/groups" "github.com/statping/statping/types/incidents" "github.com/statping/statping/types/messages" + "github.com/statping/statping/types/notifications" "github.com/statping/statping/types/null" "github.com/statping/statping/types/services" "github.com/statping/statping/types/users" @@ -143,7 +143,7 @@ func sendJsonAction(obj interface{}, method string, w http.ResponseWriter, r *ht case *services.Service: objName = "service" objId = v.Id - case *notifiers.Notification: + case *notifications.Notification: objName = "notifier" objId = v.Id case *core.Core: diff --git a/handlers/notifications.go b/handlers/notifications.go index bc3e8946..b3b44cc3 100644 --- a/handlers/notifications.go +++ b/handlers/notifications.go @@ -1,42 +1,43 @@ -// Statping -// Copyright (C) 2018. Hunter Long and the project contributors -// Written by Hunter Long and the project contributors +//// Statping +//// Copyright (C) 2018. Hunter Long and the project contributors +//// Written by Hunter Long and the project contributors +//// +//// https://github.com/statping/statping +//// +//// The licenses for most software and other practical works are designed +//// to take away your freedom to share and change the works. By contrast, +//// the GNU General Public License is intended to guarantee your freedom to +//// share and change all versions of a program--to make sure it remains free +//// software for all its users. +//// +//// You should have received a copy of the GNU General Public License +//// along with this program. If not, see . // -// https://github.com/statping/statping -// -// The licenses for most software and other practical works are designed -// to take away your freedom to share and change the works. By contrast, -// the GNU General Public License is intended to guarantee your freedom to -// share and change all versions of a program--to make sure it remains free -// software for all its users. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - package handlers import ( "encoding/json" "fmt" "github.com/gorilla/mux" - "github.com/statping/statping/notifiers" + "github.com/statping/statping/types/notifications" "github.com/statping/statping/types/null" + "github.com/statping/statping/types/services" "github.com/statping/statping/utils" "net/http" ) func apiNotifiersHandler(w http.ResponseWriter, r *http.Request) { - var notifs []notifiers.Notifier - all := notifiers.All() - for _, v := range all { - notifs = append(notifs, v) + notifiers := services.AllNotifiers() + var notifs []*notifications.Notification + for _, n := range notifiers { + notifs = append(notifs, n.Select()) } returnJson(notifs, w, r) } func apiNotifierGetHandler(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) - notifier, err := notifiers.Find(vars["notifier"]) + notifier, err := notifications.Find(vars["notifier"]) if err != nil { sendErrorJson(err, w, r) return @@ -46,11 +47,12 @@ func apiNotifierGetHandler(w http.ResponseWriter, r *http.Request) { func apiNotifierUpdateHandler(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) - notifer, err := notifiers.Find(vars["notifier"]) + notifer, err := notifications.Find(vars["notifier"]) if err != nil { sendErrorJson(err, w, r) return } + defer r.Body.Close() decoder := json.NewDecoder(r.Body) err = decoder.Decode(¬ifer) @@ -83,7 +85,7 @@ func testNotificationHandler(w http.ResponseWriter, r *http.Request) { apiSecret := form.Get("api_secret") limits := int(utils.ToInt(form.Get("limits"))) - notifier, err := notifiers.Find(method) + notifier, err := notifications.Find(method) if err != nil { log.Errorln(fmt.Sprintf("issue saving notifier %v: %v", method, err)) sendErrorJson(err, w, r) diff --git a/handlers/prometheus.go b/handlers/prometheus.go index 9516669d..c0d9b569 100644 --- a/handlers/prometheus.go +++ b/handlers/prometheus.go @@ -17,7 +17,6 @@ package handlers import ( "fmt" - "github.com/statping/statping/notifiers" "github.com/statping/statping/types/failures" "github.com/statping/statping/types/services" "github.com/statping/statping/utils" @@ -104,25 +103,22 @@ func prometheusHandler(w http.ResponseWriter, r *http.Request) { } - for _, n := range notifiers.All() { + for _, n := range services.AllNotifiers() { notif := n.Select() PrometheusComment(fmt.Sprintf("Notifier %s:", notif.Method)) - enabled := 0 if notif.Enabled.Bool { - enabled = 1 + PrometheusExportKey("notifier_on_success", notif.Id, notif.Method, notif.Hits.OnSuccess) + PrometheusExportKey("notifier_on_failure", notif.Id, notif.Method, notif.Hits.OnFailure) + PrometheusExportKey("notifier_on_user_new", notif.Id, notif.Method, notif.Hits.OnNewUser) + PrometheusExportKey("notifier_on_user_update", notif.Id, notif.Method, notif.Hits.OnUpdatedUser) + PrometheusExportKey("notifier_on_user_delete", notif.Id, notif.Method, notif.Hits.OnDeletedUser) + PrometheusExportKey("notifier_on_service_new", notif.Id, notif.Method, notif.Hits.OnNewService) + PrometheusExportKey("notifier_on_service_update", notif.Id, notif.Method, notif.Hits.OnUpdatedService) + PrometheusExportKey("notifier_on_service_delete", notif.Id, notif.Method, notif.Hits.OnDeletedService) + PrometheusExportKey("notifier_on_notifier_new", notif.Id, notif.Method, notif.Hits.OnNewNotifier) + PrometheusExportKey("notifier_on_notifier_update", notif.Id, notif.Method, notif.Hits.OnUpdatedNotifier) + PrometheusExportKey("notifier_on_notifier_save", notif.Id, notif.Method, notif.Hits.OnSave) } - PrometheusExportKey("notifier_enabled", notif.Id, notif.Method, enabled) - PrometheusExportKey("notifier_on_success", notif.Id, notif.Method, notif.Hits.OnSuccess) - PrometheusExportKey("notifier_on_failure", notif.Id, notif.Method, notif.Hits.OnFailure) - PrometheusExportKey("notifier_on_user_new", notif.Id, notif.Method, notif.Hits.OnNewUser) - PrometheusExportKey("notifier_on_user_update", notif.Id, notif.Method, notif.Hits.OnUpdatedUser) - PrometheusExportKey("notifier_on_user_delete", notif.Id, notif.Method, notif.Hits.OnDeletedUser) - PrometheusExportKey("notifier_on_service_new", notif.Id, notif.Method, notif.Hits.OnNewService) - PrometheusExportKey("notifier_on_service_update", notif.Id, notif.Method, notif.Hits.OnUpdatedService) - PrometheusExportKey("notifier_on_service_delete", notif.Id, notif.Method, notif.Hits.OnDeletedService) - PrometheusExportKey("notifier_on_notifier_new", notif.Id, notif.Method, notif.Hits.OnNewNotifier) - PrometheusExportKey("notifier_on_notifier_update", notif.Id, notif.Method, notif.Hits.OnUpdatedNotifier) - PrometheusExportKey("notifier_on_notifier_save", notif.Id, notif.Method, notif.Hits.OnSave) } PrometheusComment("HTTP Metrics") diff --git a/handlers/setup.go b/handlers/setup.go index d995d06b..5541af86 100644 --- a/handlers/setup.go +++ b/handlers/setup.go @@ -17,10 +17,10 @@ package handlers import ( "errors" - "github.com/statping/statping/notifiers" "github.com/statping/statping/types/configs" "github.com/statping/statping/types/core" "github.com/statping/statping/types/null" + "github.com/statping/statping/types/services" "github.com/statping/statping/utils" "net/http" "time" @@ -89,11 +89,11 @@ func processSetupHandler(w http.ResponseWriter, r *http.Request) { return } - log.Infoln("Migrating Notifiers...") - if err := notifiers.Migrate(); err != nil { - sendErrorJson(err, w, r) - return - } + //log.Infoln("Migrating Notifiers...") + //if err := notifications.Migrate(); err != nil { + // sendErrorJson(err, w, r) + // return + //} c := &core.Core{ Name: "Statping Sample Data", @@ -117,12 +117,17 @@ func processSetupHandler(w http.ResponseWriter, r *http.Request) { core.App = c log.Infoln("Initializing new Statping instance") - if err := core.InitApp(); err != nil { + + if _, err := services.SelectAllServices(true); err != nil { log.Errorln(err) sendErrorJson(err, w, r) return } + go services.CheckServices() + + core.App.Setup = true + CacheStorage.Delete("/") resetCookies() time.Sleep(1 * time.Second) diff --git a/notifiers/command.go b/notifiers/command.go index f73d3d7a..31384be4 100644 --- a/notifiers/command.go +++ b/notifiers/command.go @@ -16,21 +16,26 @@ package notifiers import ( - "fmt" "github.com/statping/statping/types/failures" + "github.com/statping/statping/types/notifications" + "github.com/statping/statping/types/notifier" "github.com/statping/statping/types/services" "github.com/statping/statping/utils" "strings" "time" ) -var _ Notifier = (*commandLine)(nil) +var _ notifier.Notifier = (*commandLine)(nil) type commandLine struct { - *Notification + *notifications.Notification } -var Command = &commandLine{&Notification{ +func (c *commandLine) Select() *notifications.Notification { + return c.Notification +} + +var Command = &commandLine{¬ifications.Notification{ Method: "command", Title: "Shell Command", Description: "Shell Command allows you to run a customized shell/bash Command on the local machine it's running on.", @@ -39,7 +44,7 @@ var Command = &commandLine{&Notification{ Delay: time.Duration(1 * time.Second), Icon: "fas fa-terminal", Host: "/bin/bash", - Form: []NotificationForm{{ + Form: []notifications.NotificationForm{{ Type: "text", Title: "Shell or Bash", Placeholder: "/bin/bash", @@ -65,28 +70,18 @@ func runCommand(app string, cmd ...string) (string, string, error) { return outStr, errStr, err } -func (u *commandLine) Select() *Notification { - return u.Notification -} - // OnFailure for commandLine will trigger failing service -func (u *commandLine) OnFailure(s *services.Service, f *failures.Failure) { - u.AddQueue(fmt.Sprintf("service_%v", s.Id), u.Var2) +func (u *commandLine) OnFailure(s *services.Service, f *failures.Failure) error { + msg := u.GetValue("host") + _, _, err := runCommand(u.Host, msg) + return err } // OnSuccess for commandLine will trigger successful service -func (u *commandLine) OnSuccess(s *services.Service) { - if !s.Online { - u.ResetUniqueQueue(fmt.Sprintf("service_%v", s.Id)) - u.AddQueue(fmt.Sprintf("service_%v", s.Id), u.Var1) - } -} - -// OnSave for commandLine triggers when this notifier has been saved -func (u *commandLine) OnSave() error { - u.AddQueue("saved", u.Var1) - u.AddQueue("saved", u.Var2) - return nil +func (u *commandLine) OnSuccess(s *services.Service) error { + msg := u.GetValue("host") + _, _, err := runCommand(u.Host, msg) + return err } // OnTest for commandLine triggers when this notifier has been saved @@ -97,10 +92,3 @@ func (u *commandLine) OnTest() error { utils.Log.Infoln(out) return err } - -// Send for commandLine will send message to expo Command push notifications endpoint -func (u *commandLine) Send(msg interface{}) error { - cmd := msg.(string) - _, _, err := runCommand(u.Host, cmd) - return err -} diff --git a/notifiers/discord.go b/notifiers/discord.go index ae4c3e0a..1ec101a2 100644 --- a/notifiers/discord.go +++ b/notifiers/discord.go @@ -21,19 +21,21 @@ import ( "errors" "fmt" "github.com/statping/statping/types/failures" + "github.com/statping/statping/types/notifications" + "github.com/statping/statping/types/notifier" "github.com/statping/statping/types/services" "github.com/statping/statping/utils" "strings" "time" ) -var _ Notifier = (*discord)(nil) +var _ notifier.Notifier = (*discord)(nil) type discord struct { - *Notification + *notifications.Notification } -var Discorder = &discord{&Notification{ +var Discorder = &discord{¬ifications.Notification{ Method: "discord", Title: "discord", Description: "Send notifications to your discord channel using discord webhooks. Insert your discord channel Webhook URL to receive notifications. Based on the discord webhooker API.", @@ -42,7 +44,7 @@ var Discorder = &discord{&Notification{ Delay: time.Duration(5 * time.Second), Host: "https://discordapp.com/api/webhooks/****/*****", Icon: "fab fa-discord", - Form: []NotificationForm{{ + Form: []notifications.NotificationForm{{ Type: "text", Title: "discord webhooker URL", Placeholder: "Insert your Webhook URL here", @@ -51,38 +53,25 @@ var Discorder = &discord{&Notification{ } // Send will send a HTTP Post to the discord API. It accepts type: []byte -func (u *discord) Send(msg interface{}) error { - message := msg.(string) - _, _, err := utils.HttpRequest(Discorder.GetValue("host"), "POST", "application/json", nil, strings.NewReader(message), time.Duration(10*time.Second), true) +func (u *discord) sendRequest(msg string) error { + _, _, err := utils.HttpRequest(Discorder.GetValue("host"), "POST", "application/json", nil, strings.NewReader(msg), time.Duration(10*time.Second), true) return err } -func (u *discord) Select() *Notification { +func (u *discord) Select() *notifications.Notification { return u.Notification } // OnFailure will trigger failing service -func (u *discord) OnFailure(s *services.Service, f *failures.Failure) { +func (u *discord) OnFailure(s *services.Service, f *failures.Failure) error { msg := fmt.Sprintf(`{"content": "Your service '%v' is currently failing! Reason: %v"}`, s.Name, f.Issue) - u.AddQueue(fmt.Sprintf("service_%v", s.Id), msg) + return u.sendRequest(msg) } // OnSuccess will trigger successful service -func (u *discord) OnSuccess(s *services.Service) { - if !s.Online || !s.SuccessNotified { - u.ResetUniqueQueue(fmt.Sprintf("service_%v", s.Id)) - var msg interface{} - msg = s.DownText - - u.AddQueue(fmt.Sprintf("service_%v", s.Id), msg) - } -} - -// OnSave triggers when this notifier has been saved -func (u *discord) OnSave() error { - msg := fmt.Sprintf(`{"content": "The discord notifier on Statping was just updated."}`) - u.AddQueue("saved", msg) - return nil +func (u *discord) OnSuccess(s *services.Service) error { + msg := fmt.Sprintf(`{"content": "Your service '%s' is currently online!"}`, s.Name) + return u.sendRequest(msg) } // OnSave triggers when this notifier has been saved diff --git a/notifiers/doc.go b/notifiers/doc.go deleted file mode 100644 index 25329d19..00000000 --- a/notifiers/doc.go +++ /dev/null @@ -1,9 +0,0 @@ -// Package notifiers holds all the notifiers for Statping, which also includes -// user created notifiers that have been accepted in a Push Request. Read the wiki -// to see a full example of a notifier with all events, visit Statping's -// notifier example code: https://github.com/statping/statping/wiki/Notifier-Example -// -// This package shouldn't contain any exports, to see how notifiers work -// visit the core/notifier package at: https://godoc.org/github.com/statping/statping/core/notifier -// and learn how to create your own custom notifier. -package notifiers diff --git a/notifiers/email.go b/notifiers/email.go index 3141ffa8..520e8af2 100644 --- a/notifiers/email.go +++ b/notifiers/email.go @@ -21,6 +21,8 @@ import ( "fmt" "github.com/go-mail/mail" "github.com/statping/statping/types/failures" + "github.com/statping/statping/types/notifications" + "github.com/statping/statping/types/notifier" "github.com/statping/statping/types/null" "github.com/statping/statping/types/services" "github.com/statping/statping/utils" @@ -28,7 +30,7 @@ import ( "time" ) -var _ Notifier = (*email)(nil) +var _ notifier.Notifier = (*emailer)(nil) const ( mainEmailTemplate = ` @@ -110,18 +112,22 @@ var ( mailer *mail.Dialer ) -type email struct { - *Notification +type emailer struct { + *notifications.Notification } -var Emailer = &email{&Notification{ +func (e *emailer) Select() *notifications.Notification { + return e.Notification +} + +var email = &emailer{¬ifications.Notification{ Method: "email", Title: "email", Description: "Send emails via SMTP when services are online or offline.", Author: "Hunter Long", AuthorUrl: "https://github.com/hunterlong", Icon: "far fa-envelope", - Form: []NotificationForm{{ + Form: []notifications.NotificationForm{{ Type: "text", Title: "SMTP Host", Placeholder: "Insert your SMTP Host here.", @@ -157,17 +163,7 @@ var Emailer = &email{&Notification{ Placeholder: "", SmallText: "To Disable TLS/SSL insert 'true'", DbField: "api_key", - }}, -}} - -// Send will send the SMTP email with your authentication It accepts type: *emailOutgoing -func (u *email) Send(msg interface{}) error { - email := msg.(*emailOutgoing) - err := u.dialSend(email) - if err != nil { - return err - } - return nil + }}}, } type emailOutgoing struct { @@ -181,7 +177,7 @@ type emailOutgoing struct { } // OnFailure will trigger failing service -func (u *email) OnFailure(s *services.Service, f *failures.Failure) { +func (u *emailer) OnFailure(s *services.Service, f *failures.Failure) error { email := &emailOutgoing{ To: u.Var2, Subject: fmt.Sprintf("Service %v is Failing", s.Name), @@ -189,40 +185,24 @@ func (u *email) OnFailure(s *services.Service, f *failures.Failure) { Data: interface{}(s), From: u.Var1, } - u.AddQueue(fmt.Sprintf("service_%v", s.Id), email) + return u.dialSend(email) } // OnSuccess will trigger successful service -func (u *email) OnSuccess(s *services.Service) { - if !s.Online || !s.SuccessNotified { - var msg string - msg = s.DownText - - u.ResetUniqueQueue(fmt.Sprintf("service_%v", s.Id)) - email := &emailOutgoing{ - To: u.Var2, - Subject: msg, - Template: mainEmailTemplate, - Data: interface{}(s), - From: u.Var1, - } - u.AddQueue(fmt.Sprintf("service_%v", s.Id), email) +func (u *emailer) OnSuccess(s *services.Service) error { + msg := s.DownText + email := &emailOutgoing{ + To: u.Var2, + Subject: msg, + Template: mainEmailTemplate, + Data: interface{}(s), + From: u.Var1, } -} - -func (u *email) Select() *Notification { - return u.Notification -} - -// OnSave triggers when this notifier has been saved -func (u *email) OnSave() error { - utils.Log.Infoln(fmt.Sprintf("Notification %v is receiving updated information.", u.Method)) - // Do updating stuff here - return nil + return u.dialSend(email) } // OnTest triggers when this notifier has been saved -func (u *email) OnTest() error { +func (u *emailer) OnTest() error { testService := &services.Service{ Id: 1, Name: "Example Service", @@ -247,8 +227,8 @@ func (u *email) OnTest() error { return u.dialSend(email) } -func (u *email) dialSend(email *emailOutgoing) error { - mailer = mail.NewDialer(Emailer.Host, Emailer.Port, Emailer.Username, Emailer.Password) +func (u *emailer) dialSend(email *emailOutgoing) error { + mailer = mail.NewDialer(u.Host, u.Port, u.Username, u.Password) emailSource(email) m := mail.NewMessage() // if email setting TLS is Disabled diff --git a/notifiers/events.go b/notifiers/events.go deleted file mode 100644 index 12a07008..00000000 --- a/notifiers/events.go +++ /dev/null @@ -1,203 +0,0 @@ -// Statping -// Copyright (C) 2018. Hunter Long and the project contributors -// Written by Hunter Long and the project contributors -// -// https://github.com/statping/statping -// -// The licenses for most software and other practical works are designed -// to take away your freedom to share and change the works. By contrast, -// the GNU General Public License is intended to guarantee your freedom to -// share and change all versions of a program--to make sure it remains free -// software for all its users. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -package notifiers - -import ( - "fmt" - "github.com/statping/statping/types/failures" - "github.com/statping/statping/types/services" - "github.com/statping/statping/types/users" - "github.com/statping/statping/utils" -) - -// OnSave will trigger a notifier when it has been saved - Notifier interface -func OnSave(method string) { - for _, comm := range allNotifiers { - if utils.IsType(comm, new(Notifier)) { - notifier := comm.Select() - if notifier.Method == method { - comm.OnSave() - } - } - } -} - -// OnFailure will be triggered when a service is failing - BasicEvents interface -func OnFailure(s *services.Service, f *failures.Failure) { - if !s.AllowNotifications.Bool { - return - } - - // check if User wants to receive every Status Change - if s.UpdateNotify.Bool { - // send only if User hasn't been already notified about the Downtime - if !s.UserNotified { - s.UserNotified = true - goto sendMessages - } else { - return - } - } - -sendMessages: - for _, comm := range allNotifiers { - if utils.IsType(comm, new(BasicEvents)) && isEnabled(comm) && (s.Online || inLimits(comm)) { - notifier := comm.(*Notification) - log. - WithField("trigger", "OnFailure"). - WithFields(utils.ToFields(notifier, s)).Debugln(fmt.Sprintf("Sending [OnFailure] '%v' notification for service %v", notifier.Method, s.Name)) - comm.(BasicEvents).OnFailure(s, f) - comm.Select().Hits.OnFailure++ - } - } -} - -// OnSuccess will be triggered when a service is successful - BasicEvents interface -func OnSuccess(s *services.Service) { - if !s.AllowNotifications.Bool { - return - } - - // check if User wants to receive every Status Change - if s.UpdateNotify.Bool && s.UserNotified { - s.UserNotified = false - } - - for _, comm := range allNotifiers { - if utils.IsType(comm, new(BasicEvents)) && isEnabled(comm) && (!s.Online || inLimits(comm)) { - notifier := comm.(*Notification) - log. - WithField("trigger", "OnSuccess"). - WithFields(utils.ToFields(notifier, s)).Debugln(fmt.Sprintf("Sending [OnSuccess] '%v' notification for service %v", notifier.Method, s.Name)) - comm.(BasicEvents).OnSuccess(s) - comm.Select().Hits.OnSuccess++ - } - } -} - -// OnNewService is triggered when a new service is created - ServiceEvents interface -func OnNewService(s *services.Service) { - for _, comm := range allNotifiers { - if utils.IsType(comm, new(ServiceEvents)) && isEnabled(comm) && inLimits(comm) { - log. - WithField("trigger", "OnNewService"). - Debugln(fmt.Sprintf("Sending new service notification for service %v", s.Name)) - comm.(ServiceEvents).OnNewService(s) - comm.Select().Hits.OnNewService++ - } - } -} - -// OnUpdatedService is triggered when a service is updated - ServiceEvents interface -func OnUpdatedService(s *services.Service) { - if !s.AllowNotifications.Bool { - return - } - for _, comm := range allNotifiers { - if utils.IsType(comm, new(ServiceEvents)) && isEnabled(comm) && inLimits(comm) { - log.Debugln(fmt.Sprintf("Sending updated service notification for service %v", s.Name)) - comm.(ServiceEvents).OnUpdatedService(s) - comm.Select().Hits.OnUpdatedService++ - } - } -} - -// OnDeletedService is triggered when a service is deleted - ServiceEvents interface -func OnDeletedService(s *services.Service) { - if !s.AllowNotifications.Bool { - return - } - for _, comm := range allNotifiers { - if utils.IsType(comm, new(ServiceEvents)) && isEnabled(comm) && inLimits(comm) { - log.Debugln(fmt.Sprintf("Sending deleted service notification for service %v", s.Name)) - comm.(ServiceEvents).OnDeletedService(s) - comm.Select().Hits.OnDeletedService++ - } - } -} - -// OnNewUser is triggered when a new user is created - UserEvents interface -func OnNewUser(u *users.User) { - for _, comm := range allNotifiers { - if utils.IsType(comm, new(UserEvents)) && isEnabled(comm) && inLimits(comm) { - log.Debugln(fmt.Sprintf("Sending new user notification for user %v", u.Username)) - comm.(UserEvents).OnNewUser(u) - comm.Select().Hits.OnNewUser++ - } - } -} - -// OnUpdatedUser is triggered when a new user is updated - UserEvents interface -func OnUpdatedUser(u *users.User) { - for _, comm := range allNotifiers { - if utils.IsType(comm, new(UserEvents)) && isEnabled(comm) && inLimits(comm) { - log.Debugln(fmt.Sprintf("Sending updated user notification for user %v", u.Username)) - comm.(UserEvents).OnUpdatedUser(u) - comm.Select().Hits.OnUpdatedUser++ - } - } -} - -// OnDeletedUser is triggered when a new user is deleted - UserEvents interface -func OnDeletedUser(u *users.User) { - for _, comm := range allNotifiers { - if utils.IsType(comm, new(UserEvents)) && isEnabled(comm) && inLimits(comm) { - log.Debugln(fmt.Sprintf("Sending deleted user notification for user %v", u.Username)) - comm.(UserEvents).OnDeletedUser(u) - comm.Select().Hits.OnDeletedUser++ - } - } -} - -//// OnUpdatedCore is triggered when the CoreApp settings are saved - CoreEvents interface -//func OnUpdatedCore(c *core.Core) { -// for _, comm := range allNotifiers { -// if utils.IsType(comm, new(CoreEvents)) && isEnabled(comm) && inLimits(comm) { -// log.Debugln(fmt.Sprintf("Sending updated core notification")) -// comm.(CoreEvents).OnUpdatedCore(c) -// } -// } -//} -// -//// OnStart is triggered when the Statping service has started -//func OnStart(c *core.Core) { -// for _, comm := range allNotifiers { -// if utils.IsType(comm, new(CoreEvents)) && isEnabled(comm) && inLimits(comm) { -// comm.(CoreEvents).OnUpdatedCore(c) -// } -// } -//} - -// OnNewNotifier is triggered when a new notifier is loaded -func OnNewNotifier(n *Notification) { - for _, comm := range allNotifiers { - if utils.IsType(comm, new(NotifierEvents)) && isEnabled(comm) && inLimits(comm) { - comm.(NotifierEvents).OnNewNotifier(n) - comm.Select().Hits.OnNewNotifier++ - } - } -} - -// OnUpdatedNotifier is triggered when a notifier has been updated -func OnUpdatedNotifier(n *Notification) { - for _, comm := range allNotifiers { - if utils.IsType(comm, new(NotifierEvents)) && isEnabled(comm) && inLimits(comm) { - log.Infoln(fmt.Sprintf("Sending updated notifier for %v", n.Id)) - comm.(NotifierEvents).OnUpdatedNotifier(n) - comm.Select().Hits.OnUpdatedNotifier++ - } - } -} diff --git a/notifiers/interface/doc.go b/notifiers/interface/doc.go deleted file mode 100644 index 4fb4c946..00000000 --- a/notifiers/interface/doc.go +++ /dev/null @@ -1,120 +0,0 @@ -// Package notifier contains the main functionality for the Statping Notification system -// -// Example Notifier -// -// Below is an example of a Notifier with multiple Form values to custom your inputs. Place your notifier go file -// into the /notifiers/ directory and follow the example below. -// -// type ExampleNotifier struct { -// *Notification -// } -// -// var example = &ExampleNotifier{&Notification{ -// Method: "example", -// Title: "Example Notifier", -// Description: "This is an example of a notifier for Statping!", -// Author: "Hunter Long", -// AuthorUrl: "https://github.com/hunterlong", -// Delay: time.Duration(3 * time.Second), -// Limits: 7, -// Form: []NotificationForm{{ -// Type: "text", -// Title: "Host", -// Placeholder: "Insert your Host here.", -// DbField: "host", -// SmallText: "this is where you would put the host", -// }, { -// Type: "text", -// Title: "Username", -// Placeholder: "Insert your Username here.", -// DbField: "username", -// }, { -// Type: "password", -// Title: "Password", -// Placeholder: "Insert your Password here.", -// DbField: "password", -// }, { -// Type: "number", -// Title: "Port", -// Placeholder: "Insert your Port here.", -// DbField: "port", -// }, { -// Type: "text", -// Title: "API Key", -// Placeholder: "Insert your API Key here", -// DbField: "api_key", -// }, { -// Type: "text", -// Title: "API Secret", -// Placeholder: "Insert your API Secret here", -// DbField: "api_secret", -// }, { -// Type: "text", -// Title: "Var 1", -// Placeholder: "Insert your Var1 here", -// DbField: "var1", -// }, { -// Type: "text", -// Title: "Var2", -// Placeholder: "Var2 goes here", -// DbField: "var2", -// }}, -// }} -// -// Load the Notifier -// -// Include the init() function with AddNotifier and your notification struct. This is ran on start of Statping -// and will automatically create a new row in the database so the end user can save their own values. -// -// func init() { -// AddNotifier(example) -// } -// -// Required Methods for Notifier Interface -// -// Below are the required methods to have your notifier implement the Notifier interface. The Send method -// will be where you would include the logic for your notification. -// -// // REQUIRED -// func (n *ExampleNotifier) Send(msg interface{}) error { -// message := msg.(string) -// fmt.Printf("i received this string: %v\n", message) -// return nil -// } -// -// // REQUIRED -// func (n *ExampleNotifier) Select() *Notification { -// return n.Notification -// } -// -// // REQUIRED -// func (n *ExampleNotifier) OnSave() error { -// msg := fmt.Sprintf("received on save trigger") -// n.AddQueue(msg) -// return errors.New("onsave triggered") -// } -// -// Basic Events for Notifier -// -// You must include OnSuccess and OnFailure methods for your notifier. Anytime a service is online or offline -// these methods will be ran with the service corresponding to it. -// -// // REQUIRED - BASIC EVENT -// func (n *ExampleNotifier) OnSuccess(s *services.Service) { -// msg := fmt.Sprintf("received a count trigger for service: %v\n", s.Name) -// n.AddQueue(msg) -// } -// -// // REQUIRED - BASIC EVENT -// func (n *ExampleNotifier) OnFailure(s *services.Service, f *types.Failure) { -// msg := fmt.Sprintf("received a failure trigger for service: %v\n", s.Name) -// n.AddQueue(msg) -// } -// -// Additional Events -// -// You can implement your notifier to different types of events that are triggered. Checkout the wiki to -// see more details and examples of how to build your own notifier. -// -// More info on: https://github.com/statping/statping/wiki/Notifiers -package _interface diff --git a/notifiers/interfaces.go b/notifiers/interfaces.go deleted file mode 100644 index bf58e992..00000000 --- a/notifiers/interfaces.go +++ /dev/null @@ -1,72 +0,0 @@ -// Statping -// Copyright (C) 2018. Hunter Long and the project contributors -// Written by Hunter Long and the project contributors -// -// https://github.com/statping/statping -// -// The licenses for most software and other practical works are designed -// to take away your freedom to share and change the works. By contrast, -// the GNU General Public License is intended to guarantee your freedom to -// share and change all versions of a program--to make sure it remains free -// software for all its users. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -package notifiers - -import ( - "github.com/statping/statping/types/failures" - "github.com/statping/statping/types/services" - "github.com/statping/statping/types/users" -) - -// Notifier interface is required to create a new Notifier -type Notifier interface { - OnSave() error // OnSave is triggered when the notifier is saved - Send(interface{}) error // OnSave is triggered when the notifier is saved - Select() *Notification // Select returns the *Notification for a notifier -} - -// BasicEvents includes the most minimal events, failing and successful service triggers -type BasicEvents interface { - OnSuccess(*services.Service) // OnSuccess is triggered when a service is successful - OnFailure(*services.Service, *failures.Failure) // OnFailure is triggered when a service is failing -} - -// Tester interface will include a function to Test users settings before saving -type Tester interface { - OnTest() error -} - -// ServiceEvents are events for Services -type ServiceEvents interface { - OnNewService(*services.Service) - OnUpdatedService(*services.Service) - OnDeletedService(*services.Service) -} - -// UserEvents are events for Users -type UserEvents interface { - OnNewUser(*users.User) - OnUpdatedUser(*users.User) - OnDeletedUser(*users.User) -} - -// CoreEvents are events for the main Core app -//type CoreEvents interface { -// OnUpdatedCore(*core.Core) -// OnStart(*core.Core) -//} - -// NotifierEvents are events for other Notifiers -type NotifierEvents interface { - OnNewNotifier(*Notification) - OnUpdatedNotifier(*Notification) -} - -// HTTPRouter interface will allow your notifier to accept http GET/POST requests -type HTTPRouter interface { - OnGET() error - OnPOST() error -} diff --git a/notifiers/line_notify.go b/notifiers/line_notify.go index aeb77003..51f06787 100644 --- a/notifiers/line_notify.go +++ b/notifiers/line_notify.go @@ -18,6 +18,8 @@ package notifiers import ( "fmt" "github.com/statping/statping/types/failures" + "github.com/statping/statping/types/notifications" + "github.com/statping/statping/types/notifier" "github.com/statping/statping/types/services" "github.com/statping/statping/utils" "net/url" @@ -25,24 +27,28 @@ import ( "time" ) -var _ Notifier = (*lineNotifier)(nil) +var _ notifier.Notifier = (*lineNotifier)(nil) const ( lineNotifyMethod = "line_notify" ) type lineNotifier struct { - *Notification + *notifications.Notification } -var LineNotify = &lineNotifier{&Notification{ +func (l *lineNotifier) Select() *notifications.Notification { + return l.Notification +} + +var LineNotify = &lineNotifier{¬ifications.Notification{ Method: lineNotifyMethod, Title: "LINE Notify", Description: "LINE Notify will send notifications to your LINE Notify account when services are offline or online. Based on the LINE Notify API.", Author: "Kanin Peanviriyakulkit", AuthorUrl: "https://github.com/dogrocker", Icon: "far fa-bell", - Form: []NotificationForm{{ + Form: []notifications.NotificationForm{{ Type: "text", Title: "Access Token", Placeholder: "Insert your Line Notify Access Token here.", @@ -51,8 +57,7 @@ var LineNotify = &lineNotifier{&Notification{ } // Send will send a HTTP Post with the Authorization to the notify-api.line.me server. It accepts type: string -func (u *lineNotifier) Send(msg interface{}) error { - message := msg.(string) +func (u *lineNotifier) sendMessage(message string) error { v := url.Values{} v.Set("message", message) headers := []string{fmt.Sprintf("Authorization=Bearer %v", u.ApiSecret)} @@ -60,31 +65,20 @@ func (u *lineNotifier) Send(msg interface{}) error { return err } -func (u *lineNotifier) Select() *Notification { - return u.Notification -} - // OnFailure will trigger failing service -func (u *lineNotifier) OnFailure(s *services.Service, f *failures.Failure) { +func (u *lineNotifier) OnFailure(s *services.Service, f *failures.Failure) error { msg := fmt.Sprintf("Your service '%v' is currently offline!", s.Name) - u.AddQueue(fmt.Sprintf("service_%v", s.Id), msg) + return u.sendMessage(msg) } // OnSuccess will trigger successful service -func (u *lineNotifier) OnSuccess(s *services.Service) { - if !s.Online || !s.SuccessNotified { - var msg string - msg = s.DownText - - u.ResetUniqueQueue(fmt.Sprintf("service_%v", s.Id)) - u.AddQueue(fmt.Sprintf("service_%v", s.Id), msg) - } +func (u *lineNotifier) OnSuccess(s *services.Service) error { + msg := fmt.Sprintf("Service %s is online!", s.Name) + return u.sendMessage(msg) } -// OnSave triggers when this notifier has been saved -func (u *lineNotifier) OnSave() error { - msg := fmt.Sprintf("Notification %v is receiving updated information.", u.Method) - utils.Log.Infoln(msg) - u.AddQueue("saved", msg) - return nil +// OnTest triggers when this notifier has been saved +func (u *lineNotifier) OnTest() error { + msg := fmt.Sprintf("Testing if Line Notifier is working!") + return u.sendMessage(msg) } diff --git a/notifiers/mobile.go b/notifiers/mobile.go index ec0e024d..66469eea 100644 --- a/notifiers/mobile.go +++ b/notifiers/mobile.go @@ -20,20 +20,26 @@ import ( "encoding/json" "fmt" "github.com/statping/statping/types/failures" + "github.com/statping/statping/types/notifications" + "github.com/statping/statping/types/notifier" "github.com/statping/statping/types/services" "github.com/statping/statping/utils" "time" ) -var _ Notifier = (*mobilePush)(nil) +var _ notifier.Notifier = (*mobilePush)(nil) const mobileIdentifier = "com.statping" type mobilePush struct { - *Notification + *notifications.Notification } -var Mobile = &mobilePush{&Notification{ +func (m *mobilePush) Select() *notifications.Notification { + return m.Notification +} + +var Mobile = &mobilePush{¬ifications.Notification{ Method: "mobile", Title: "Mobile Notifications", Description: `Receive push notifications on your Mobile device using the Statping App. You can scan the Authentication QR Code found in Settings to get the Mobile app setup in seconds. @@ -42,7 +48,7 @@ var Mobile = &mobilePush{&Notification{ AuthorUrl: "https://github.com/hunterlong", Delay: time.Duration(5 * time.Second), Icon: "fas fa-mobile-alt", - Form: []NotificationForm{{ + Form: []notifications.NotificationForm{{ Type: "text", Title: "Device Identifiers", Placeholder: "A list of your Mobile device push notification ID's.", @@ -57,10 +63,6 @@ var Mobile = &mobilePush{&Notification{ }}}, } -func (u *mobilePush) Select() *Notification { - return u.Notification -} - func dataJson(s *services.Service, f *failures.Failure) map[string]interface{} { serviceId := "0" if s != nil { @@ -85,7 +87,7 @@ func dataJson(s *services.Service, f *failures.Failure) map[string]interface{} { } // OnFailure will trigger failing service -func (u *mobilePush) OnFailure(s *services.Service, f *failures.Failure) { +func (u *mobilePush) OnFailure(s *services.Service, f *failures.Failure) error { data := dataJson(s, f) msg := &pushArray{ Message: fmt.Sprintf("Your service '%v' is currently failing! Reason: %v", s.Name, f.Issue), @@ -93,30 +95,19 @@ func (u *mobilePush) OnFailure(s *services.Service, f *failures.Failure) { Topic: mobileIdentifier, Data: data, } - u.AddQueue(fmt.Sprintf("service_%v", s.Id), msg) + return u.Send(msg) } // OnSuccess will trigger successful service -func (u *mobilePush) OnSuccess(s *services.Service) { +func (u *mobilePush) OnSuccess(s *services.Service) error { data := dataJson(s, nil) - if !s.Online || !s.SuccessNotified { - var msgStr string - msgStr = s.DownText - - u.ResetUniqueQueue(fmt.Sprintf("service_%v", s.Id)) - msg := &pushArray{ - Message: msgStr, - Title: "Service Online", - Topic: mobileIdentifier, - Data: data, - } - u.AddQueue(fmt.Sprintf("service_%v", s.Id), msg) + msg := &pushArray{ + Message: "Service is Online!", + Title: "Service Online", + Topic: mobileIdentifier, + Data: data, } -} - -// OnSave triggers when this notifier has been saved -func (u *mobilePush) OnSave() error { - return nil + return u.Send(msg) } // OnTest triggers when this notifier has been saved @@ -143,12 +134,10 @@ func (u *mobilePush) OnTest() error { firstLog := output.Logs[0].Error return fmt.Errorf("Mobile Notification error: %v", firstLog) } - return err } // Send will send message to Statping push notifications endpoint -func (u *mobilePush) Send(msg interface{}) error { - pushMessage := msg.(*pushArray) +func (u *mobilePush) Send(pushMessage *pushArray) error { pushMessage.Tokens = []string{u.Var1} pushMessage.Platform = utils.ToInt(u.Var2) _, err := pushRequest(pushMessage) diff --git a/notifiers/notifications_test.go b/notifiers/notifications_test.go deleted file mode 100644 index f6b68f02..00000000 --- a/notifiers/notifications_test.go +++ /dev/null @@ -1,135 +0,0 @@ -package notifiers - -import ( - "github.com/statping/statping/database" - "github.com/statping/statping/types/null" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "testing" -) - -var form1 = NotificationForm{ - Type: "text", - Title: "Example Input", - DbField: "Host", - Required: true, - IsHidden: false, - IsList: false, - IsSwitch: false, -} - -var form2 = NotificationForm{ - Type: "text", - Title: "Example Input 2", - DbField: "ApiKey", - Required: true, - IsHidden: false, - IsList: false, - IsSwitch: false, -} - -var example = &exampleNotif{&Notification{ - Method: "example", - Enabled: null.NewNullBool(true), - Limits: 3, - Removable: false, - Form: []NotificationForm{form1, form2}, - Delay: 30, -}} - -type exampleNotif struct { - *Notification -} - -func (e *exampleNotif) OnSave() error { - return nil -} - -func (e *exampleNotif) Select() *Notification { - return e.Notification -} - -func (e *exampleNotif) Send(data interface{}) error { - return nil -} - -func TestInit(t *testing.T) { - db, err := database.OpenTester() - require.Nil(t, err) - db.CreateTable(&Notification{}) - db.Create(example.Select()) - SetDB(db) -} - -func TestFind(t *testing.T) { - appendList(example) - itemer, err := Find(example.Method) - require.Nil(t, err) - - item := itemer.Select() - require.NotNil(t, item) - - assert.Equal(t, "example", item.Method) - assert.Len(t, allNotifiers, 1) -} - -func TestAll(t *testing.T) { - items := All() - assert.Len(t, items, 1) - assert.Len(t, allNotifiers, 1) -} - -func TestCreate(t *testing.T) { - assert.Len(t, allNotifiers, 1) - - example := &Notification{ - Method: "anotherexample", - Title: "Example 2", - Description: "New Message here", - } - err := example.Create() - require.Nil(t, err) - assert.NotZero(t, example.Id) - assert.Equal(t, "anotherexample", example.Method) - assert.Equal(t, "Example 2", example.Title) - assert.NotZero(t, example.CreatedAt) - - items := All() - assert.Len(t, items, 2) - assert.Len(t, allNotifiers, 2) -} - -func TestUpdate(t *testing.T) { - itemer, err := Find("anotherexample") - require.Nil(t, err) - require.NotNil(t, itemer) - - item := itemer.Select() - require.NotNil(t, item) - - item.Host = "Updated Host Var" - err = item.Update() - require.Nil(t, err) - assert.Equal(t, "Updated Host Var", item.Host) -} - -func TestDelete(t *testing.T) { - all := All() - assert.Len(t, all, 2) - - itemer, err := Find("example2") - require.Nil(t, err) - - item := itemer.Select() - require.NotNil(t, item) - - err = item.Delete() - require.Nil(t, err) - - all = All() - assert.Len(t, all, 2) -} - -func TestClose(t *testing.T) { - assert.Nil(t, db.Close()) -} diff --git a/notifiers/notifiers.go b/notifiers/notifiers.go index e8a82cd5..2a752dc6 100644 --- a/notifiers/notifiers.go +++ b/notifiers/notifiers.go @@ -2,105 +2,28 @@ package notifiers import ( "fmt" - "github.com/statping/statping/types/failures" "github.com/statping/statping/types/services" - "github.com/statping/statping/utils" - "strings" ) -var ( - allowedVars = []string{"host", "username", "password", "port", "api_key", "api_secret", "var1", "var2"} -) - -func SendEvent(data ...interface{}) { - d1 := data[0] - service, ok := d1.(*services.Service) - if !ok { - return - } - d2 := data[1] - if d2 == nil { - OnSuccess(service) - } - fail, ok := d2.(*failures.Failure) - if !ok { - return - } - OnFailure(service, fail) -} - -func checkNotifierForm(n *Notification) error { - for _, f := range n.Form { - contains := contains(f.DbField, allowedVars) - if !contains { - return fmt.Errorf("the DbField '%v' is not allowed, allowed vars: %v", f.DbField, allowedVars) - } - } - return nil -} - -func contains(s string, arr []string) bool { - for _, v := range arr { - if strings.ToLower(s) == v { - return true - } - } - return false -} - -// AddNotifier accept a Notifier interface to be added into the array -func AddNotifiers(notifiers ...Notifier) error { - log.Infof("Initiating %d Notifiers\n", len(notifiers)) - - for _, n := range notifiers { - notif := n.Select() - log.Infof("Initiating %s Notifier\n", notif.Method) - - if err := checkNotifierForm(notif); err != nil { - log.Errorf(err.Error()) - return err - } - - log.Infof("Creating %s Notifier\n", notif.Method) - if err := notif.Create(); err != nil { - return err - } - - if notif.Enabled.Bool { - notif.Close() - notif.Start() - go Queue(notif) - } - - } - return nil -} - -// startAllNotifiers will start the go routine for each loaded notifier -func startAllNotifiers() { - for _, notify := range All() { - n := notify.Select() - log.Infof("Initiating %s Notifier\n", n.Method) - if utils.IsType(notify, new(Notifier)) { - if n.Enabled.Bool { - n.Close() - n.Start() - go Queue(notify) - } - } - } -} - -func Migrate() error { - return AddNotifiers( +func InitNotifiers() { + Add( + slacker, Command, Discorder, - Emailer, + email, LineNotify, - Mobile, - Slacker, Telegram, Twilio, Webhook, + Mobile, ) } + +func Add(notifs ...services.ServiceNotifier) { + for _, n := range notifs { + services.AddNotifier(n) + if err := n.Select().Create(); err != nil { + fmt.Println(err) + } + } +} diff --git a/notifiers/slack.go b/notifiers/slack.go index 984a90db..abd30da8 100644 --- a/notifiers/slack.go +++ b/notifiers/slack.go @@ -20,6 +20,8 @@ import ( "errors" "fmt" "github.com/statping/statping/types/failures" + "github.com/statping/statping/types/notifications" + "github.com/statping/statping/types/notifier" "github.com/statping/statping/types/services" "github.com/statping/statping/utils" "strings" @@ -27,7 +29,7 @@ import ( "time" ) -var _ Notifier = (*slack)(nil) +var _ notifier.Notifier = (*slack)(nil) const ( slackMethod = "slack" @@ -37,10 +39,14 @@ const ( ) type slack struct { - *Notification + *notifications.Notification } -var Slacker = &slack{&Notification{ +func (s *slack) Select() *notifications.Notification { + return s.Notification +} + +var slacker = &slack{¬ifications.Notification{ Method: slackMethod, Title: "slack", Description: "Send notifications to your slack channel when a service is offline. Insert your Incoming webhooker URL for your channel to receive notifications. Based on the slack API.", @@ -49,7 +55,7 @@ var Slacker = &slack{&Notification{ Delay: time.Duration(10 * time.Second), Host: "https://webhooksurl.slack.com/***", Icon: "fab fa-slack", - Form: []NotificationForm{{ + Form: []notifications.NotificationForm{{ Type: "text", Title: "Incoming webhooker Url", Placeholder: "Insert your slack Webhook URL here.", @@ -59,15 +65,14 @@ var Slacker = &slack{&Notification{ }}}, } -func parseSlackMessage(id int64, temp string, data interface{}) error { +func parseSlackMessage(id int64, temp string, data interface{}) string { buf := new(bytes.Buffer) slackTemp, _ := template.New("slack").Parse(temp) err := slackTemp.Execute(buf, data) if err != nil { - return err + return err.Error() } - Slacker.AddQueue(fmt.Sprintf("service_%v", id), buf.String()) - return nil + return buf.String() } type slackMessage struct { @@ -78,51 +83,44 @@ type slackMessage struct { } // Send will send a HTTP Post to the slack webhooker API. It accepts type: string -func (u *slack) Send(msg interface{}) error { - message := msg.(string) - _, _, err := utils.HttpRequest(u.Host, "POST", "application/json", nil, strings.NewReader(message), time.Duration(10*time.Second), true) +func (u *slack) sendSlack(msg string) error { + contents, resp, err := utils.HttpRequest(u.Host, "POST", "application/json", nil, strings.NewReader(msg), time.Duration(10*time.Second), true) + defer resp.Body.Close() + fmt.Println("CONTENTS: ", string(contents)) return err } -func (u *slack) Select() *Notification { - return u.Notification -} - func (u *slack) OnTest() error { - contents, _, err := utils.HttpRequest(u.Host, "POST", "application/json", nil, bytes.NewBuffer([]byte(`{"text":"testing message"}`)), time.Duration(10*time.Second), true) + contents, resp, err := utils.HttpRequest(u.Host, "POST", "application/json", nil, bytes.NewBuffer([]byte(`{"text":"testing message"}`)), time.Duration(10*time.Second), true) + defer resp.Body.Close() if string(contents) != "ok" { - return errors.New("The slack response was incorrect, check the URL") + return errors.New("the slack response was incorrect, check the URL") } return err } // OnFailure will trigger failing service -func (u *slack) OnFailure(s *services.Service, f *failures.Failure) { +func (u *slack) OnFailure(s *services.Service, f *failures.Failure) error { message := slackMessage{ Service: s, Template: failingTemplate, Time: utils.Now().Unix(), - Issue: f.Issue, } - parseSlackMessage(s.Id, failingTemplate, message) + msg := parseSlackMessage(s.Id, failingTemplate, message) + return u.sendSlack(msg) } // OnSuccess will trigger successful service -func (u *slack) OnSuccess(s *services.Service) { - if !s.Online { - u.ResetUniqueQueue(fmt.Sprintf("service_%v", s.Id)) - message := slackMessage{ - Service: s, - Template: successTemplate, - Time: utils.Now().Unix(), - } - parseSlackMessage(s.Id, successTemplate, message) +func (u *slack) OnSuccess(s *services.Service) error { + message := slackMessage{ + Service: s, + Template: successTemplate, + Time: utils.Now().Unix(), } -} + msg := parseSlackMessage(s.Id, successTemplate, message) -// OnSave triggers when this notifier has been saved -func (u *slack) OnSave() error { - message := fmt.Sprintf("Notification %v is receiving updated information.", u.Method) - u.AddQueue("saved", message) - return nil + fmt.Println("Sending OnSuccess message!") + fmt.Println(msg) + fmt.Printf("%s\n", u.Host) + return u.sendSlack(msg) } diff --git a/notifiers/struct.go b/notifiers/struct.go deleted file mode 100644 index 5f2211df..00000000 --- a/notifiers/struct.go +++ /dev/null @@ -1,273 +0,0 @@ -// Statping -// Copyright (C) 2018. Hunter Long and the project contributors -// Written by Hunter Long and the project contributors -// -// https://github.com/statping/statping -// -// The licenses for most software and other practical works are designed -// to take away your freedom to share and change the works. By contrast, -// the GNU General Public License is intended to guarantee your freedom to -// share and change all versions of a program--to make sure it remains free -// software for all its users. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -package notifiers - -import ( - "encoding/json" - "errors" - "fmt" - "github.com/statping/statping/types/null" - "github.com/statping/statping/types/services" - "github.com/statping/statping/utils" - "reflect" - "time" -) - -var ( - // db holds the Statping database connection - log = utils.Log.WithField("type", "notifier") - allNotifiers []Notifier -) - -// Notification contains all the fields for a Statping Notifier. -type Notification struct { - Id int64 `gorm:"primary_key;column:id" json:"id"` - Method string `gorm:"column:method" json:"method"` - Host string `gorm:"not null;column:host" json:"host,omitempty"` - Port int `gorm:"not null;column:port" json:"port,omitempty"` - Username string `gorm:"not null;column:username" json:"username,omitempty"` - Password string `gorm:"not null;column:password" json:"password,omitempty"` - Var1 string `gorm:"not null;column:var1" json:"var1,omitempty"` - Var2 string `gorm:"not null;column:var2" json:"var2,omitempty"` - ApiKey string `gorm:"not null;column:api_key" json:"api_key,omitempty"` - ApiSecret string `gorm:"not null;column:api_secret" json:"api_secret,omitempty"` - Enabled null.NullBool `gorm:"column:enabled;type:boolean;default:false" json:"enabled"` - Limits int `gorm:"not null;column:limits" json:"limits"` - Removable bool `gorm:"column:removable" json:"removeable"` - CreatedAt time.Time `gorm:"column:created_at" json:"created_at"` - UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at"` - Form []NotificationForm `gorm:"-" json:"form"` - logs []*NotificationLog `gorm:"-" json:"logs"` - Title string `gorm:"-" json:"title"` - Description string `gorm:"-" json:"description"` - Author string `gorm:"-" json:"author"` - AuthorUrl string `gorm:"-" json:"author_url"` - Icon string `gorm:"-" json:"icon"` - Delay time.Duration `gorm:"-" json:"delay,string"` - Queue []*QueueData `gorm:"-" json:"-"` - Running chan bool `gorm:"-" json:"-"` - testable bool `gorm:"-" json:"testable"` - - Hits notificationHits - Notifier -} - -type notificationHits struct { - OnSuccess int64 `gorm:"-" json:"-"` - OnFailure int64 `gorm:"-" json:"-"` - OnSave int64 `gorm:"-" json:"-"` - OnNewService int64 `gorm:"-" json:"-"` - OnUpdatedService int64 `gorm:"-" json:"-"` - OnDeletedService int64 `gorm:"-" json:"-"` - OnNewUser int64 `gorm:"-" json:"-"` - OnUpdatedUser int64 `gorm:"-" json:"-"` - OnDeletedUser int64 `gorm:"-" json:"-"` - OnNewNotifier int64 `gorm:"-" json:"-"` - OnUpdatedNotifier int64 `gorm:"-" json:"-"` -} - -// QueueData is the struct for the messaging queue with service -type QueueData struct { - Id string - Data interface{} -} - -// NotificationForm contains the HTML fields for each variable/input you want the notifier to accept. -type NotificationForm struct { - Type string `json:"type"` // the html input type (text, password, email) - Title string `json:"title"` // include a title for ease of use - Placeholder string `json:"placeholder"` // add a placeholder for the input - DbField string `json:"field"` // true variable key for input - SmallText string `json:"small_text"` // insert small text under a html input - Required bool `json:"required"` // require this input on the html form - IsHidden bool `json:"hidden"` // hide this form element from end user - IsList bool `json:"list"` // make this form element a comma separated list - IsSwitch bool `json:"switch"` // make the notifier a boolean true/false switch -} - -// NotificationLog contains the normalized message from previously sent notifications -type NotificationLog struct { - Message string `json:"message"` - Time utils.Timestamp `json:"time"` - Timestamp time.Time `json:"timestamp"` -} - -// normalizeType will accept multiple interfaces and converts it into a string for logging -func normalizeType(ty interface{}) string { - switch v := ty.(type) { - case int, int32, int64: - return fmt.Sprintf("%v", v) - case float32, float64: - return fmt.Sprintf("%v", v) - case string: - return v - case []byte: - return string(v) - case []string: - return fmt.Sprintf("%v", v) - case interface{}, map[string]interface{}: - j, _ := json.Marshal(v) - return string(j) - default: - return fmt.Sprintf("%v", v) - } -} - -// Log will record a new notification into memory and will show the logs on the settings page -func (n *Notification) makeLog(msg interface{}) { - log := &NotificationLog{ - Message: normalizeType(msg), - Time: utils.Timestamp(utils.Now()), - Timestamp: utils.Now(), - } - n.logs = append(n.logs, log) -} - -// Logs returns an array of the notifiers logs -func (n *Notification) Logs() []*NotificationLog { - return reverseLogs(n.logs) -} - -// reverseLogs will reverse the notifier's logs to be time desc -func reverseLogs(input []*NotificationLog) []*NotificationLog { - if len(input) == 0 { - return input - } - return append(reverseLogs(input[1:]), input[0]) -} - -// SelectNotification returns the Notification struct from the database -func SelectNotification(n Notifier) (*Notification, error) { - notifier := n.Select() - err := db.Where("method = ?", notifier.Method).Find(¬ifier) - return notifier, err.Error() -} - -// SelectNotifier returns the Notification struct from the database -func SelectNotifier(method string) (*Notification, Notifier, error) { - for _, comm := range allNotifiers { - n, ok := comm.(Notifier) - if !ok { - return nil, nil, fmt.Errorf("incorrect notification type: %v", reflect.TypeOf(n).String()) - } - notifier := n.Select() - if notifier.Method == method { - return notifier, comm.(Notifier), nil - } - } - return nil, nil, errors.New("cannot find notifier") -} - -// Queue is the FIFO go routine to send notifications when objects are triggered -func Queue(notifer Notifier) { - n := notifer.(*Notification) - rateLimit := n.Delay - -CheckNotifier: - for { - select { - case <-n.Running: - break CheckNotifier - case <-time.After(rateLimit): - n := notifer.(*Notification) - fmt.Printf("checking %s %d\n", n.Method, len(n.Queue)) - if len(n.Queue) > 0 { - ok, _ := n.WithinLimits() - if ok { - msg := n.Queue[0] - err := notifer.Send(msg.Data) - if err != nil { - log.WithFields(utils.ToFields(n, msg)).Error(fmt.Sprintf("Notifier '%v' had an error: %v", n.Method, err)) - } else { - log.WithFields(utils.ToFields(n, msg)).Debug(fmt.Sprintf("Notifier '%v' sent outgoing message (%v) %v left in queue.", n.Method, msg.Id, len(n.Queue))) - } - n.makeLog(msg.Data) - if len(n.Queue) > 1 { - n.Queue = n.Queue[1:] - } else { - n.Queue = nil - } - rateLimit = n.Delay - } - } - } - continue - } -} - -// install will check the database for the notification, if its not inserted it will insert a new record for it -//func install(n Notifier) error { -// log.WithFields(utils.ToFields(n)). -// Debugln(fmt.Sprintf("Checking if notifier '%v' is installed", n.Select().Method)) -// -// if Exists(n.Select().Method) { -// AllCommunications = append(AllCommunications, n) -// } else { -// _, err := insertDatabase(n) -// if err != nil { -// log.Errorln(err) -// return err -// } -// AllCommunications = append(AllCommunications, n) -// } -// return nil -//} - -// isEnabled returns true if the notifier is enabled -func isEnabled(n interface{}) bool { - notifier := n.(Notifier).Select() - return notifier.Enabled.Bool -} - -// inLimits will return true if the notifier is within sending limits -func inLimits(n interface{}) bool { - notifier := n.(Notifier).Select() - ok, _ := notifier.WithinLimits() - return ok -} - -// WithinLimits returns true if the notifier is within its sending limits -func (n *Notification) WithinLimits() (bool, error) { - if n.SentLastMinute() == 0 { - return true, nil - } - if n.SentLastMinute() >= n.Limits { - return false, fmt.Errorf("notifier sent %v out of %v in last minute", n.SentLastMinute(), n.Limits) - } - if n.LastSent().Seconds() == 0 { - return true, nil - } - if n.Delay.Seconds() >= n.LastSent().Seconds() { - return false, fmt.Errorf("notifiers delay (%v) is greater than last message sent (%v)", n.Delay.Seconds(), n.LastSent().Seconds()) - } - return true, nil -} - -// ExampleService can be used for the OnTest() method for notifiers -var ExampleService = &services.Service{ - Id: 1, - Name: "Interpol - All The Rage Back Home", - Domain: "https://www.youtube.com/watch?v=-u6DvRyyKGU", - ExpectedStatus: 200, - Interval: 30, - Type: "http", - Method: "GET", - Timeout: 20, - LastStatusCode: 404, - Expected: null.NewNullString("test example"), - LastResponse: "this is an example response", - CreatedAt: utils.Now().Add(-24 * time.Hour), -} diff --git a/notifiers/telegram.go b/notifiers/telegram.go index a1565577..857e5243 100644 --- a/notifiers/telegram.go +++ b/notifiers/telegram.go @@ -20,6 +20,8 @@ import ( "errors" "fmt" "github.com/statping/statping/types/failures" + "github.com/statping/statping/types/notifications" + "github.com/statping/statping/types/notifier" "github.com/statping/statping/types/services" "github.com/statping/statping/utils" "net/url" @@ -27,13 +29,17 @@ import ( "time" ) -var _ Notifier = (*telegram)(nil) +var _ notifier.Notifier = (*telegram)(nil) type telegram struct { - *Notification + *notifications.Notification } -var Telegram = &telegram{&Notification{ +func (t *telegram) Select() *notifications.Notification { + return t.Notification +} + +var Telegram = &telegram{¬ifications.Notification{ Method: "telegram", Title: "Telegram", Description: "Receive notifications on your Telegram channel when a service has an issue. You must get a Telegram API token from the /botfather. Review the Telegram API Tutorial to learn how to generate a new API Token.", @@ -41,7 +47,7 @@ var Telegram = &telegram{&Notification{ AuthorUrl: "https://github.com/hunterlong", Icon: "fab fa-telegram-plane", Delay: time.Duration(5 * time.Second), - Form: []NotificationForm{{ + Form: []notifications.NotificationForm{{ Type: "text", Title: "Telegram API Token", Placeholder: "383810182:EEx829dtCeufeQYXG7CUdiQopqdmmxBPO7-s", @@ -58,13 +64,8 @@ var Telegram = &telegram{&Notification{ }}}, } -func (u *telegram) Select() *Notification { - return u.Notification -} - // Send will send a HTTP Post to the Telegram API. It accepts type: string -func (u *telegram) Send(msg interface{}) error { - message := msg.(string) +func (u *telegram) sendMessage(message string) error { apiEndpoint := fmt.Sprintf("https://api.telegram.org/bot%v/sendMessage", u.ApiSecret) v := url.Values{} @@ -84,35 +85,21 @@ func (u *telegram) Send(msg interface{}) error { } // OnFailure will trigger failing service -func (u *telegram) OnFailure(s *services.Service, f *failures.Failure) { +func (u *telegram) OnFailure(s *services.Service, f *failures.Failure) error { msg := fmt.Sprintf("Your service '%v' is currently offline!", s.Name) - u.AddQueue(fmt.Sprintf("service_%v", s.Id), msg) + return u.sendMessage(msg) } // OnSuccess will trigger successful service -func (u *telegram) OnSuccess(s *services.Service) { - if !s.Online || !s.SuccessNotified { - u.ResetUniqueQueue(fmt.Sprintf("service_%v", s.Id)) - var msg interface{} - msg = s.DownText - - u.AddQueue(fmt.Sprintf("service_%v", s.Id), msg) - } -} - -// OnSave triggers when this notifier has been saved -func (u *telegram) OnSave() error { - utils.Log.Infoln(fmt.Sprintf("Notification %v is receiving updated information.", u.Method)) - - // Do updating stuff here - - return nil +func (u *telegram) OnSuccess(s *services.Service) error { + msg := fmt.Sprintf("Your service '%v' is currently online!", s.Name) + return u.sendMessage(msg) } // OnTest will test the Twilio SMS messaging func (u *telegram) OnTest() error { msg := fmt.Sprintf("Testing the Twilio SMS Notifier on your Statping server") - return u.Send(msg) + return u.sendMessage(msg) } func telegramSuccess(res []byte) (bool, telegramResponse) { diff --git a/notifiers/twilio.go b/notifiers/twilio.go index 25e88881..39645fbe 100644 --- a/notifiers/twilio.go +++ b/notifiers/twilio.go @@ -20,6 +20,8 @@ import ( "errors" "fmt" "github.com/statping/statping/types/failures" + "github.com/statping/statping/types/notifications" + "github.com/statping/statping/types/notifier" "github.com/statping/statping/types/services" "github.com/statping/statping/utils" "net/url" @@ -27,13 +29,17 @@ import ( "time" ) -var _ Notifier = (*twilio)(nil) +var _ notifier.Notifier = (*twilio)(nil) type twilio struct { - *Notification + *notifications.Notification } -var Twilio = &twilio{&Notification{ +func (t *twilio) Select() *notifications.Notification { + return t.Notification +} + +var Twilio = &twilio{¬ifications.Notification{ Method: "twilio", Title: "Twilio", Description: "Receive SMS text messages directly to your cellphone when a service is offline. You can use a Twilio test account with limits. This notifier uses the Twilio API.", @@ -41,7 +47,7 @@ var Twilio = &twilio{&Notification{ AuthorUrl: "https://github.com/hunterlong", Icon: "far fa-comment-alt", Delay: time.Duration(10 * time.Second), - Form: []NotificationForm{{ + Form: []notifications.NotificationForm{{ Type: "text", Title: "Account SID", Placeholder: "Insert your Twilio Account SID", @@ -68,13 +74,8 @@ var Twilio = &twilio{&Notification{ }}}, } -func (u *twilio) Select() *Notification { - return u.Notification -} - // Send will send a HTTP Post to the Twilio SMS API. It accepts type: string -func (u *twilio) Send(msg interface{}) error { - message := msg.(string) +func (u *twilio) sendMessage(message string) error { twilioUrl := fmt.Sprintf("https://api.twilio.com/2010-04-01/Accounts/%v/Messages.json", u.GetValue("api_key")) v := url.Values{} @@ -94,35 +95,21 @@ func (u *twilio) Send(msg interface{}) error { } // OnFailure will trigger failing service -func (u *twilio) OnFailure(s *services.Service, f *failures.Failure) { +func (u *twilio) OnFailure(s *services.Service, f *failures.Failure) error { msg := fmt.Sprintf("Your service '%v' is currently offline!", s.Name) - u.AddQueue(fmt.Sprintf("service_%v", s.Id), msg) + return u.sendMessage(msg) } // OnSuccess will trigger successful service -func (u *twilio) OnSuccess(s *services.Service) { - if !s.Online || !s.SuccessNotified { - u.ResetUniqueQueue(fmt.Sprintf("service_%v", s.Id)) - var msg string - msg = s.DownText - - u.AddQueue(fmt.Sprintf("service_%v", s.Id), msg) - } -} - -// OnSave triggers when this notifier has been saved -func (u *twilio) OnSave() error { - utils.Log.Infoln(fmt.Sprintf("Notification %v is receiving updated information.", u.Method)) - - // Do updating stuff here - - return nil +func (u *twilio) OnSuccess(s *services.Service) error { + msg := fmt.Sprintf("Your service '%v' is currently online!", s.Name) + return u.sendMessage(msg) } // OnTest will test the Twilio SMS messaging func (u *twilio) OnTest() error { msg := fmt.Sprintf("Testing the Twilio SMS Notifier") - return u.Send(msg) + return u.sendMessage(msg) } func twilioSuccess(res []byte) (bool, twilioResponse) { diff --git a/notifiers/webhook.go b/notifiers/webhook.go index 1c39c71d..93296b50 100644 --- a/notifiers/webhook.go +++ b/notifiers/webhook.go @@ -19,6 +19,8 @@ import ( "bytes" "fmt" "github.com/statping/statping/types/failures" + "github.com/statping/statping/types/notifications" + "github.com/statping/statping/types/notifier" "github.com/statping/statping/types/services" "github.com/statping/statping/utils" "io/ioutil" @@ -27,17 +29,17 @@ import ( "time" ) -var _ Notifier = (*webhooker)(nil) +var _ notifier.Notifier = (*webhooker)(nil) const ( webhookMethod = "webhook" ) type webhooker struct { - *Notification + *notifications.Notification } -var Webhook = &webhooker{&Notification{ +var Webhook = &webhooker{¬ifications.Notification{ Method: webhookMethod, Title: "HTTP webhooker", Description: "Send a custom HTTP request to a specific URL with your own body, headers, and parameters.", @@ -45,7 +47,7 @@ var Webhook = &webhooker{&Notification{ AuthorUrl: "https://github.com/hunterlong", Icon: "fas fa-code-branch", Delay: time.Duration(1 * time.Second), - Form: []NotificationForm{{ + Form: []notifications.NotificationForm{{ Type: "text", Title: "HTTP Endpoint", Placeholder: "http://webhookurl.com/JW2MCP4SKQP", @@ -89,7 +91,7 @@ func (w *webhooker) Send(msg interface{}) error { return err } -func (w *webhooker) Select() *Notification { +func (w *webhooker) Select() *notifications.Notification { return w.Notification } @@ -131,7 +133,7 @@ func (w *webhooker) sendHttpWebhook(body string) (*http.Response, error) { } func (w *webhooker) OnTest() error { - body := replaceBodyText(w.Var2, ExampleService, nil) + body := replaceBodyText(w.Var2, nil, nil) resp, err := w.sendHttpWebhook(body) if err != nil { return err @@ -143,21 +145,17 @@ func (w *webhooker) OnTest() error { } // OnFailure will trigger failing service -func (w *webhooker) OnFailure(s *services.Service, f *failures.Failure) { +func (w *webhooker) OnFailure(s *services.Service, f *failures.Failure) error { msg := replaceBodyText(w.Var2, s, f) - w.AddQueue(fmt.Sprintf("service_%v", s.Id), msg) + resp, err := w.sendHttpWebhook(msg) + defer resp.Body.Close() + return err } // OnSuccess will trigger successful service -func (w *webhooker) OnSuccess(s *services.Service) { - if !s.Online { - w.ResetUniqueQueue(fmt.Sprintf("service_%v", s.Id)) - msg := replaceBodyText(w.Var2, s, nil) - w.AddQueue(fmt.Sprintf("service_%v", s.Id), msg) - } -} - -// OnSave triggers when this notifier has been saved -func (w *webhooker) OnSave() error { - return nil +func (w *webhooker) OnSuccess(s *services.Service) error { + msg := replaceBodyText(w.Var2, s, nil) + resp, err := w.sendHttpWebhook(msg) + defer resp.Body.Close() + return err } diff --git a/types/configs/connection.go b/types/configs/connection.go index 639101a9..123abfca 100644 --- a/types/configs/connection.go +++ b/types/configs/connection.go @@ -5,7 +5,6 @@ import ( "github.com/jinzhu/gorm" "github.com/pkg/errors" "github.com/statping/statping/database" - "github.com/statping/statping/notifiers" "github.com/statping/statping/types/checkins" "github.com/statping/statping/types/core" "github.com/statping/statping/types/failures" @@ -13,6 +12,7 @@ import ( "github.com/statping/statping/types/hits" "github.com/statping/statping/types/incidents" "github.com/statping/statping/types/messages" + "github.com/statping/statping/types/notifications" "github.com/statping/statping/types/null" "github.com/statping/statping/types/services" "github.com/statping/statping/types/users" @@ -100,7 +100,7 @@ func initModels(db database.Database) { hits.SetDB(db) failures.SetDB(db) checkins.SetDB(db) - notifiers.SetDB(db) + notifications.SetDB(db) incidents.SetDB(db) users.SetDB(db) messages.SetDB(db) diff --git a/types/configs/database.go b/types/configs/database.go index a35279f9..4312f324 100644 --- a/types/configs/database.go +++ b/types/configs/database.go @@ -2,7 +2,6 @@ package configs import ( "github.com/statping/statping/database" - "github.com/statping/statping/notifiers" "github.com/statping/statping/types/checkins" "github.com/statping/statping/types/core" "github.com/statping/statping/types/failures" @@ -10,6 +9,7 @@ import ( "github.com/statping/statping/types/hits" "github.com/statping/statping/types/incidents" "github.com/statping/statping/types/messages" + "github.com/statping/statping/types/notifications" "github.com/statping/statping/types/services" "github.com/statping/statping/types/users" "github.com/statping/statping/utils" @@ -73,7 +73,7 @@ func (d *DbConfig) Delete() error { // DropDatabase will DROP each table Statping created func (d *DbConfig) DropDatabase() error { - var DbModels = []interface{}{&services.Service{}, &users.User{}, &hits.Hit{}, &failures.Failure{}, &messages.Message{}, &groups.Group{}, &checkins.Checkin{}, &checkins.CheckinHit{}, ¬ifiers.Notification{}, &incidents.Incident{}, &incidents.IncidentUpdate{}} + var DbModels = []interface{}{&services.Service{}, &users.User{}, &hits.Hit{}, &failures.Failure{}, &messages.Message{}, &groups.Group{}, &checkins.Checkin{}, &checkins.CheckinHit{}, ¬ifications.Notification{}, &incidents.Incident{}, &incidents.IncidentUpdate{}} log.Infoln("Dropping Database Tables...") for _, t := range DbModels { if err := d.Db.DropTableIfExists(t); err != nil { @@ -94,7 +94,7 @@ func (d *DbConfig) Close() { func (d *DbConfig) CreateDatabase() error { var err error - var DbModels = []interface{}{&services.Service{}, &users.User{}, &hits.Hit{}, &failures.Failure{}, &messages.Message{}, &groups.Group{}, &checkins.Checkin{}, &checkins.CheckinHit{}, ¬ifiers.Notification{}, &incidents.Incident{}, &incidents.IncidentUpdate{}} + var DbModels = []interface{}{&services.Service{}, &users.User{}, &hits.Hit{}, &failures.Failure{}, &messages.Message{}, &groups.Group{}, &checkins.Checkin{}, &checkins.CheckinHit{}, ¬ifications.Notification{}, &incidents.Incident{}, &incidents.IncidentUpdate{}} log.Infoln("Creating Database Tables...") for _, table := range DbModels { diff --git a/types/configs/migration.go b/types/configs/migration.go index a466373c..f96daa69 100644 --- a/types/configs/migration.go +++ b/types/configs/migration.go @@ -5,7 +5,8 @@ import ( _ "github.com/jinzhu/gorm/dialects/mysql" _ "github.com/jinzhu/gorm/dialects/postgres" _ "github.com/jinzhu/gorm/dialects/sqlite" - "github.com/statping/statping/notifiers" + "github.com/statping/statping/types/notifications" + "github.com/statping/statping/types/checkins" "github.com/statping/statping/types/core" "github.com/statping/statping/types/failures" @@ -52,7 +53,7 @@ func (c *DbConfig) DatabaseChanges() error { //If this function has an issue, it will ROLLBACK to the previous state. func (c *DbConfig) MigrateDatabase() error { - var DbModels = []interface{}{&services.Service{}, &users.User{}, &hits.Hit{}, &failures.Failure{}, &messages.Message{}, &groups.Group{}, &checkins.Checkin{}, &checkins.CheckinHit{}, ¬ifiers.Notification{}, &incidents.Incident{}, &incidents.IncidentUpdate{}} + var DbModels = []interface{}{&services.Service{}, &users.User{}, &hits.Hit{}, &failures.Failure{}, &messages.Message{}, &groups.Group{}, &checkins.Checkin{}, &checkins.CheckinHit{}, ¬ifications.Notification{}, &incidents.Incident{}, &incidents.IncidentUpdate{}} log.Infoln("Migrating Database Tables...") tx := c.Db.Begin() diff --git a/notifiers/database.go b/types/notifications/database.go similarity index 52% rename from notifiers/database.go rename to types/notifications/database.go index 5fad2de8..1591ae35 100644 --- a/notifiers/database.go +++ b/types/notifications/database.go @@ -1,32 +1,21 @@ -package notifiers +package notifications import ( - "errors" "github.com/statping/statping/database" ) -var db database.Database +var ( + db database.Database +) func SetDB(database database.Database) { db = database.Model(&Notification{}) } -func appendList(n Notifier) { - allNotifiers = append(allNotifiers, n) -} - -func Find(name string) (*Notification, error) { - for _, n := range allNotifiers { - notif := n.(*Notification) - if notif.Method == name { - return notif, nil - } - } - return nil, errors.New("notifier not found") -} - -func All() []Notifier { - return allNotifiers +func Find(method string) (*Notification, error) { + var notification Notification + q := db.Where("method = ?", method).Find(¬ification) + return ¬ification, q.Error() } func (n *Notification) Create() error { @@ -36,7 +25,6 @@ func (n *Notification) Create() error { return err } } - appendList(n) return nil } @@ -45,7 +33,6 @@ func (n *Notification) Update() error { if n.Enabled.Bool { n.Close() n.Start() - go Queue(n) } else { n.Close() } @@ -53,6 +40,8 @@ func (n *Notification) Update() error { return err.Error() } -func (n *Notification) Delete() error { - return nil +func loadAll() []*Notification { + var notifications []*Notification + db.Find(¬ifications) + return notifications } diff --git a/notifiers/methods.go b/types/notifications/methods.go similarity index 65% rename from notifiers/methods.go rename to types/notifications/methods.go index a7ccf177..07b9362f 100644 --- a/notifiers/methods.go +++ b/types/notifications/methods.go @@ -1,4 +1,4 @@ -package notifiers +package notifications import ( "fmt" @@ -21,49 +21,48 @@ func (n *Notification) AfterFind() (err error) { } // AddQueue will add any type of interface (json, string, struct, etc) into the Notifiers queue -func (n *Notification) AddQueue(uid string, msg interface{}) { - data := &QueueData{uid, msg} - n.Queue = append(n.Queue, data) - log.WithFields(utils.ToFields(data, n)).Debug(fmt.Sprintf("Notifier '%v' added new item (%v) to the queue. (%v queued)", n.Method, uid, len(n.Queue))) -} +//func (n *Notification) AddQueue(uid string, msg interface{}) { +// data := &QueueData{uid, msg} +// n.Queue = append(n.Queue, data) +// log.WithFields(utils.ToFields(data, n)).Debug(fmt.Sprintf("Notifier '%v' added new item (%v) to the queue. (%v queued)", n.Method, uid, len(n.Queue))) +//} // CanTest returns true if the notifier implements the OnTest interface -func (n *Notification) CanTest() bool { - return n.testable -} +//func (n *Notification) CanTest() bool { +// return n.testable +//} // LastSent returns a time.Duration of the last sent notification for the notifier func (n *Notification) LastSent() time.Duration { - if len(n.logs) == 0 { - return time.Duration(0) - } - last := n.Logs()[0] - since := time.Since(last.Timestamp) + since := time.Since(n.lastSent) return since } -// SentLastHour returns the total amount of notifications sent in last 1 hour -func (n *Notification) SentLastHour() int { - since := utils.Now().Add(-1 * time.Hour) - return n.SentLast(since) -} +func (n *Notification) CanSend() bool { + if !n.Enabled.Bool { + return false + } -// SentLastMinute returns the total amount of notifications sent in last 1 minute -func (n *Notification) SentLastMinute() int { - since := utils.Now().Add(-1 * time.Minute) - return n.SentLast(since) -} + fmt.Println("Last sent: ", n.lastSent.String()) + fmt.Println("Last count: ", n.lastSentCount) + fmt.Println("Last sent before now: ", n.lastSent.Add(60*time.Second).Before(utils.Now())) -// SentLast accept a time.Time and returns the amount of sent notifications within your time to current -func (n *Notification) SentLast(since time.Time) int { - sent := 0 - for _, v := range n.Logs() { - lastTime := time.Time(v.Time).UTC() - if lastTime.After(since) { - sent++ + // the last sent notification was past 1 minute (limit per minute) + if n.lastSent.Add(60 * time.Second).Before(utils.Now()) { + if n.lastSentCount != 0 { + n.lastSentCount-- } } - return sent + + // dont send if already beyond the notifier's limit + if n.lastSentCount >= n.Limits { + return false + } + + // action to do since notifier is able to send + n.lastSentCount++ + n.lastSent = utils.Now() + return true } // GetValue returns the database value of a accept DbField value. @@ -130,18 +129,6 @@ func (n *Notification) ResetQueue() { n.Queue = nil } -// ResetQueue will clear the notifiers Queue for a service -func (n *Notification) ResetUniqueQueue(uid string) []*QueueData { - var queue []*QueueData - for _, v := range n.Queue { - if v.Id != uid { - queue = append(queue, v) - } - } - n.Queue = queue - return queue -} - // start will start the go routine for the notifier queue func (n *Notification) Start() { n.Running = make(chan bool) diff --git a/types/notifications/struct.go b/types/notifications/struct.go new file mode 100644 index 00000000..db5366db --- /dev/null +++ b/types/notifications/struct.go @@ -0,0 +1,74 @@ +package notifications + +import ( + "github.com/statping/statping/types/null" + "github.com/statping/statping/utils" + "time" +) + +var ( + log = utils.Log +) + +// Notification contains all the fields for a Statping Notifier. +type Notification struct { + Id int64 `gorm:"primary_key;column:id" json:"id"` + Method string `gorm:"column:method" json:"method"` + Host string `gorm:"not null;column:host" json:"host,omitempty"` + Port int `gorm:"not null;column:port" json:"port,omitempty"` + Username string `gorm:"not null;column:username" json:"username,omitempty"` + Password string `gorm:"not null;column:password" json:"password,omitempty"` + Var1 string `gorm:"not null;column:var1" json:"var1,omitempty"` + Var2 string `gorm:"not null;column:var2" json:"var2,omitempty"` + ApiKey string `gorm:"not null;column:api_key" json:"api_key,omitempty"` + ApiSecret string `gorm:"not null;column:api_secret" json:"api_secret,omitempty"` + Enabled null.NullBool `gorm:"column:enabled;type:boolean;default:false" json:"enabled"` + Limits int `gorm:"not null;column:limits" json:"limits"` + Removable bool `gorm:"column:removable" json:"removeable"` + CreatedAt time.Time `gorm:"column:created_at" json:"created_at"` + UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at"` + Form []NotificationForm `gorm:"-" json:"form"` + Title string `gorm:"-" json:"title"` + Description string `gorm:"-" json:"description"` + Author string `gorm:"-" json:"author"` + AuthorUrl string `gorm:"-" json:"author_url"` + Icon string `gorm:"-" json:"icon"` + Delay time.Duration `gorm:"-" json:"delay,string"` + Running chan bool `gorm:"-" json:"-"` + + Queue []RunFunc `gorm:"-" json:"-"` + + lastSent time.Time `gorm:"-" json:"-"` + lastSentCount int `gorm:"-" json:"-"` + + Hits notificationHits `gorm:"-" json:"-"` +} + +type RunFunc func(interface{}) error + +// NotificationForm contains the HTML fields for each variable/input you want the notifier to accept. +type NotificationForm struct { + Type string `json:"type"` // the html input type (text, password, email) + Title string `json:"title"` // include a title for ease of use + Placeholder string `json:"placeholder"` // add a placeholder for the input + DbField string `json:"field"` // true variable key for input + SmallText string `json:"small_text"` // insert small text under a html input + Required bool `json:"required"` // require this input on the html form + IsHidden bool `json:"hidden"` // hide this form element from end user + IsList bool `json:"list"` // make this form element a comma separated list + IsSwitch bool `json:"switch"` // make the notifier a boolean true/false switch +} + +type notificationHits struct { + OnSuccess int64 `gorm:"-" json:"-"` + OnFailure int64 `gorm:"-" json:"-"` + OnSave int64 `gorm:"-" json:"-"` + OnNewService int64 `gorm:"-" json:"-"` + OnUpdatedService int64 `gorm:"-" json:"-"` + OnDeletedService int64 `gorm:"-" json:"-"` + OnNewUser int64 `gorm:"-" json:"-"` + OnUpdatedUser int64 `gorm:"-" json:"-"` + OnDeletedUser int64 `gorm:"-" json:"-"` + OnNewNotifier int64 `gorm:"-" json:"-"` + OnUpdatedNotifier int64 `gorm:"-" json:"-"` +} diff --git a/types/notifier/interface.go b/types/notifier/interface.go new file mode 100644 index 00000000..89906564 --- /dev/null +++ b/types/notifier/interface.go @@ -0,0 +1,13 @@ +package notifier + +import ( + "github.com/statping/statping/types/failures" + "github.com/statping/statping/types/services" +) + +// Notifier interface is required to create a new Notifier +type Notifier interface { + OnSuccess(*services.Service) error // OnSuccess is triggered when a service is successful + OnFailure(*services.Service, *failures.Failure) error // OnFailure is triggered when a service is failing + OnTest() error // OnTest is triggered for testing +} diff --git a/types/services/notifier.go b/types/services/notifier.go new file mode 100644 index 00000000..067acb60 --- /dev/null +++ b/types/services/notifier.go @@ -0,0 +1,21 @@ +package services + +import ( + "github.com/statping/statping/types/failures" + "github.com/statping/statping/types/notifications" +) + +var ( + allNotifiers []ServiceNotifier +) + +func AllNotifiers() []ServiceNotifier { + return allNotifiers +} + +type ServiceNotifier interface { + OnSuccess(*Service) error // OnSuccess is triggered when a service is successful + OnFailure(*Service, *failures.Failure) error // OnFailure is triggered when a service is failing + OnTest() error // OnTest is triggered for testing + Select() *notifications.Notification // OnTest is triggered for testing +} diff --git a/types/services/routine.go b/types/services/routine.go index 6f808a82..e979a782 100644 --- a/types/services/routine.go +++ b/types/services/routine.go @@ -3,16 +3,17 @@ package services import ( "bytes" "fmt" - "github.com/statping/statping/types/failures" - "github.com/statping/statping/types/hits" - "github.com/statping/statping/utils" - "github.com/tatsushid/go-fastping" "net" "net/http" "net/url" "regexp" "strings" "time" + + "github.com/statping/statping/types/failures" + "github.com/statping/statping/types/hits" + "github.com/statping/statping/utils" + "github.com/tatsushid/go-fastping" ) // checkServices will start the checking go routine for each service @@ -242,10 +243,36 @@ func recordSuccess(s *Service) { fmt.Sprintf("Service #%d '%v' Successful Response: %s | Lookup in: %s | Online: %v | Interval: %d seconds", s.Id, s.Name, humanMicro(hit.Latency), humanMicro(hit.PingTime), s.Online, s.Interval)) s.LastLookupTime = hit.PingTime s.LastLatency = hit.Latency - //notifiers.OnSuccess(s) + sendSuccess(s) s.SuccessNotified = true } +func AddNotifier(n ServiceNotifier) { + allNotifiers = append(allNotifiers, n) +} + +func sendSuccess(s *Service) { + if !s.AllowNotifications.Bool { + return + } + // dont send notification if server was already previous online + if s.SuccessNotified { + return + } + for _, n := range allNotifiers { + notif := n.Select() + if notif.CanSend() { + log.Infof("Sending notification to: %s!", notif.Method) + if err := n.OnSuccess(s); err != nil { + log.Errorln(err) + } + s.UserNotified = true + s.SuccessNotified = true + //s.UpdateNotify.Bool + } + } +} + // recordFailure will create a new 'Failure' record in the database for a offline service func recordFailure(s *Service, issue string) { s.LastOffline = utils.Now() @@ -266,7 +293,32 @@ func recordFailure(s *Service, issue string) { s.Online = false s.SuccessNotified = false s.DownText = s.DowntimeText() - //notifiers.OnFailure(s, fail) + sendFailure(s, fail) +} + +func sendFailure(s *Service, f *failures.Failure) { + if !s.AllowNotifications.Bool { + return + } + + // ignore failure if user was already notified and + // they have "continuous notifications" switched off. + if s.UserNotified && !s.UpdateNotify.Bool { + return + } + + for _, n := range allNotifiers { + notif := n.Select() + if notif.CanSend() { + log.Infof("Sending Failure notification to: %s!", notif.Method) + if err := n.OnFailure(s, f); err != nil { + log.Errorln(err) + } + s.UserNotified = true + s.SuccessNotified = true + //s.UpdateNotify.Bool + } + } } // Check will run checkHttp for HTTP services and checkTcp for TCP services