From d0ad55488ddbaf0e25a23cc4002fb90391f5375a Mon Sep 17 00:00:00 2001 From: Hunter Long Date: Tue, 17 Jul 2018 02:18:20 -0700 Subject: [PATCH] notifier - moved core --- cli.go | 14 ++--- core/checker.go | 5 +- core/checkin.go | 3 +- core/configs.go | 6 +- core/core.go | 43 +++++--------- core/database.go | 6 +- core/events.go | 7 +-- core/failures.go | 8 ++- core/services.go | 38 ++++++------ handlers/api.go | 15 +++++ handlers/routes.go | 3 +- handlers/services.go | 5 +- handlers/settings.go | 22 ++----- main.go | 6 +- notifiers/email.go | 53 +++++++++-------- notifiers/notifiers.go | 118 ++++++++++++++++++++++++++----------- notifiers/slack.go | 18 +++--- plugin/main.go | 44 ++------------ source/js/main.js | 5 ++ source/tmpl/dashboard.html | 11 ++-- source/tmpl/service.html | 9 ++- source/tmpl/services.html | 3 +- source/tmpl/settings.html | 38 +++++++++--- types/types.go | 73 +++++++++++++++++++---- utils/utils.go | 17 ++++++ 25 files changed, 336 insertions(+), 234 deletions(-) diff --git a/cli.go b/cli.go index c3fe9de6..38918577 100644 --- a/cli.go +++ b/cli.go @@ -4,7 +4,6 @@ import ( "encoding/json" "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" @@ -106,9 +105,8 @@ func RunOnce() { if err != nil { utils.Log(4, err) } - for _, ser := range core.CoreApp.Services { - s := ser.ToService() - out := core.ServiceCheck(s) + for _, s := range core.CoreApp.Services { + out := core.ServiceCheck(s.ToService()) fmt.Printf(" Service %v | URL: %v | Latency: %0.0fms | Online: %v\n", out.Name, out.Domain, (out.Latency * 1000), out.Online) } } @@ -129,7 +127,7 @@ func HelpEcho() { fmt.Println("Give Statup a Star at https://github.com/hunterlong/statup") } -func TestPlugin(plug plugin.PluginActions) { +func TestPlugin(plug types.PluginActions) { defer utils.DeleteFile("./.plugin_test.db") RenderBoxes() @@ -160,7 +158,7 @@ func TestPlugin(plug plugin.PluginActions) { fmt.Println("\n" + BRAKER) fmt.Println(POINT + "Sending 'OnSettingsSaved(Core)'") fmt.Println(BRAKER) - core.OnSettingsSaved(core.CoreApp) + core.OnSettingsSaved(core.CoreApp.ToCore()) fmt.Println("\n" + BRAKER) fmt.Println(POINT + "Sending 'OnNewService(Service)'") core.OnNewService(core.SelectService(2).ToService()) @@ -180,11 +178,11 @@ func TestPlugin(plug plugin.PluginActions) { fmt.Println("\n" + BRAKER) } -func FakeSeed(plug plugin.PluginActions) { +func FakeSeed(plug types.PluginActions) { var err error core.CoreApp = core.NewCore() - core.CoreApp.AllPlugins = []plugin.PluginActions{plug} + core.CoreApp.AllPlugins = []types.PluginActions{plug} fmt.Printf("\n" + BRAKER) diff --git a/core/checker.go b/core/checker.go index e21b52b6..68ad41c6 100644 --- a/core/checker.go +++ b/core/checker.go @@ -20,10 +20,9 @@ func CheckServices() { utils.Log(1, fmt.Sprintf("Starting monitoring process for %v Services", len(CoreApp.Services))) for _, ser := range CoreApp.Services { s := ser.ToService() - obj := s //go obj.StartCheckins() - obj.StopRoutine = make(chan struct{}) - go CheckQueue(obj) + s.StopRoutine = make(chan struct{}) + go CheckQueue(s) } } diff --git a/core/checkin.go b/core/checkin.go index 9dabb240..8b0721ec 100644 --- a/core/checkin.go +++ b/core/checkin.go @@ -16,8 +16,7 @@ func (c *Checkin) String() string { func FindCheckin(api string) *types.Checkin { for _, ser := range CoreApp.Services { - s := ser.ToService() - for _, c := range s.Checkins { + for _, c := range ser.ToService().Checkins { if c.Api == api { return c } diff --git a/core/configs.go b/core/configs.go index 7908fdb6..e80d81de 100644 --- a/core/configs.go +++ b/core/configs.go @@ -85,7 +85,7 @@ func LoadUsingEnv() (*types.Config, error) { DropDatabase() CreateDatabase() - CoreApp = &Core{ + CoreApp = &Core{Core: &types.Core{ Name: dbConfig.Project, Description: dbConfig.Description, Config: "config.yml", @@ -93,11 +93,11 @@ func LoadUsingEnv() (*types.Config, error) { ApiSecret: utils.NewSHA1Hash(16), Domain: dbConfig.Domain, MigrationId: time.Now().Unix(), - } + }} CoreApp.DbConnection = dbConfig.DbConn - err := CoreApp.Insert() + err := InsertCore(CoreApp) if err != nil { utils.Log(3, err) } diff --git a/core/core.go b/core/core.go index 7c75c969..947cb9e7 100644 --- a/core/core.go +++ b/core/core.go @@ -3,7 +3,6 @@ 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" "os" @@ -14,24 +13,8 @@ type PluginJSON types.PluginJSON type PluginRepos types.PluginRepos type Core struct { - Name string `db:"name" json:"name"` - Description string `db:"description" json:"name"` - Config string `db:"config" json:"-"` - ApiKey string `db:"api_key" json:"-"` - ApiSecret string `db:"api_secret" json:"-"` - Style string `db:"style" json:"-"` - Footer string `db:"footer" json:"-"` - Domain string `db:"domain" json:"domain,omitempty"` - Version string `db:"version" json:"version,omitempty"` - MigrationId int64 `db:"migration_id" json:"-"` - UseCdn bool `db:"use_cdn" json:"-"` - Services []*Service `json:"services,omitempty"` - Plugins []plugin.Info - Repos []PluginJSON - AllPlugins []plugin.PluginActions - Communications []notifiers.AllNotifiers - DbConnection string - started time.Time + *types.Core + Services []*Service } var ( @@ -53,16 +36,21 @@ func init() { func NewCore() *Core { CoreApp = new(Core) - CoreApp.started = time.Now() + CoreApp.Core = new(types.Core) + CoreApp.Started = time.Now() return CoreApp } -func (c *Core) Insert() error { +func InsertCore(c *Core) error { col := DbSession.Collection("core") - _, err := col.Insert(c) + _, err := col.Insert(c.Core) return err } +func (c *Core) ToCore() *types.Core { + return c.Core +} + func InitApp() { SelectCore() notifiers.Collections = DbSession.Collection("communication") @@ -72,9 +60,9 @@ func InitApp() { go DatabaseMaintence() } -func (c *Core) Update() (*Core, error) { +func UpdateCore(c *Core) (*Core, error) { res := DbSession.Collection("core").Find().Limit(1) - err := res.Update(c) + err := res.Update(c.Core) return c, err } @@ -114,7 +102,7 @@ func (c Core) AllOnline() bool { } func SelectLastMigration() (int64, error) { - var c *Core + var c *types.Core if DbSession == nil { return 0, errors.New("Database connection has not been created yet") } @@ -126,17 +114,16 @@ func SelectLastMigration() (int64, error) { } func SelectCore() (*Core, error) { - var c *Core + var c *types.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 } - CoreApp = c + CoreApp.Core = c CoreApp.DbConnection = Configs.Connection CoreApp.Version = VERSION CoreApp.Services, _ = SelectAllServices() diff --git a/core/database.go b/core/database.go index 1aecfc01..4adf195b 100644 --- a/core/database.go +++ b/core/database.go @@ -119,7 +119,7 @@ func (c *DbConfig) Save() error { DropDatabase() CreateDatabase() - newCore := &Core{ + newCore := &types.Core{ Name: c.Project, Description: c.Description, Config: "config.yml", @@ -131,7 +131,7 @@ func (c *DbConfig) Save() error { col := DbSession.Collection("core") _, err = col.Insert(newCore) if err == nil { - CoreApp = newCore + CoreApp = &Core{Core: newCore} } CoreApp, err = SelectCore() @@ -204,7 +204,7 @@ func RunDatabaseUpgrades() error { panic(err) } CoreApp.MigrationId = currentMigration - CoreApp.Update() + UpdateCore(CoreApp) } return err } diff --git a/core/events.go b/core/events.go index 0fc2d202..2cfe3216 100644 --- a/core/events.go +++ b/core/events.go @@ -3,7 +3,6 @@ package core import ( "github.com/fatih/structs" "github.com/hunterlong/statup/notifiers" - "github.com/hunterlong/statup/plugin" "github.com/hunterlong/statup/types" "upper.io/db.v3/lib/sqlbuilder" ) @@ -28,7 +27,7 @@ func OnFailure(s *types.Service, f FailureData) { notifiers.OnFailure(s) } -func OnSettingsSaved(c *Core) { +func OnSettingsSaved(c *types.Core) { for _, p := range CoreApp.AllPlugins { p.OnSettingsSaved(structs.Map(c)) } @@ -58,11 +57,11 @@ func OnUpdateService(s *types.Service) { } } -func SelectPlugin(name string) plugin.PluginActions { +func SelectPlugin(name string) types.PluginActions { for _, p := range CoreApp.AllPlugins { if p.GetInfo().Name == name { return p } } - return plugin.PluginInfo{} + return types.PluginInfo{} } diff --git a/core/failures.go b/core/failures.go index 9f939f5d..b25cbbe2 100644 --- a/core/failures.go +++ b/core/failures.go @@ -46,12 +46,16 @@ func DeleteFailures(u *types.Service) { } } -func (ser *Service) LimitedFailures() []*types.Failure { +func (ser *Service) LimitedFailures() []*Failure { s := ser.ToService() var fails []*types.Failure + var failArr []*Failure col := DbSession.Collection("failures").Find("service", s.Id).OrderBy("-id").Limit(10) col.All(&fails) - return fails + for _, f := range fails { + failArr = append(failArr, MakeFailure(f)) + } + return failArr } func reverseFailures(input []*types.Failure) []*types.Failure { diff --git a/core/services.go b/core/services.go index e08c01ec..292b6449 100644 --- a/core/services.go +++ b/core/services.go @@ -11,7 +11,7 @@ import ( ) type Service struct { - S interface{} + s *types.Service } type Failure struct { @@ -23,10 +23,10 @@ func serviceCol() db.Collection { } func SelectService(id int64) *Service { - for _, ser := range CoreApp.Services { - s := ser.ToService() - if s.Id == id { - return ser + for _, s := range CoreApp.Services { + ser := s.ToService() + if ser.Id == id { + return &Service{ser} } } return nil @@ -64,7 +64,7 @@ func (s *Service) AvgTime() float64 { } func (ser *Service) Online24() float32 { - s := ser.S.(*types.Service) + s := ser.ToService() total, _ := ser.TotalHits() failed, _ := ser.TotalFailures24Hours() if failed == 0 { @@ -91,12 +91,11 @@ type DateScan struct { } func (s *Service) ToService() *types.Service { - return s.S.(*types.Service) + return s.s } func NewService(s *types.Service) *Service { - ser := &Service{s} - return ser + return &Service{s} } func (ser *Service) SmallText() string { @@ -105,8 +104,8 @@ func (ser *Service) SmallText() string { hits, _ := ser.LimitedHits() if !s.Online { if len(last) > 0 { - lastFailure := MakeFailure(last[0]) - return fmt.Sprintf("%v on %v", lastFailure.ParseError(), last[0].CreatedAt.Format("Monday 3:04PM, Jan _2 2006")) + lastFailure := MakeFailure(last[0].ToFailure()) + return fmt.Sprintf("%v on %v", lastFailure.ParseError(), last[0].ToFailure().CreatedAt.Format("Monday 3:04PM, Jan _2 2006")) } else { return fmt.Sprintf("%v is currently offline", s.Name) } @@ -194,10 +193,10 @@ func (ser *Service) AvgUptime() string { func RemoveArray(u *types.Service) []*Service { var srvcs []*Service - for _, ser := range CoreApp.Services { - s := ser.ToService() - if s.Id != u.Id { - srvcs = append(srvcs, ser) + for _, s := range CoreApp.Services { + ser := s.ToService() + if ser.Id != u.Id { + srvcs = append(srvcs, s) } } CoreApp.Services = srvcs @@ -241,16 +240,15 @@ func CreateService(u *types.Service) (int64, error) { } u.Id = uuid.(int64) u.StopRoutine = make(chan struct{}) - nn := &Service{u} - CoreApp.Services = append(CoreApp.Services, nn) + CoreApp.Services = append(CoreApp.Services, &Service{u}) return uuid.(int64), err } func CountOnline() int { amount := 0 - for _, ser := range CoreApp.Services { - s := ser.ToService() - if s.Online { + for _, s := range CoreApp.Services { + ser := s.ToService() + if ser.Online { amount++ } } diff --git a/handlers/api.go b/handlers/api.go index 1fbbc9a3..bedc8eb4 100644 --- a/handlers/api.go +++ b/handlers/api.go @@ -17,6 +17,21 @@ func ApiIndexHandler(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(core.CoreApp) } +func ApiRenewHandler(w http.ResponseWriter, r *http.Request) { + if !isAPIAuthorized(r) { + http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) + return + } + var err error + core.CoreApp.ApiKey = utils.NewSHA1Hash(40) + core.CoreApp.ApiSecret = utils.NewSHA1Hash(40) + core.CoreApp, err = core.UpdateCore(core.CoreApp) + if err != nil { + utils.Log(3, err) + } + http.Redirect(w, r, "/settings", http.StatusSeeOther) +} + func ApiCheckinHandler(w http.ResponseWriter, r *http.Request) { if !isAPIAuthorized(r) { http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) diff --git a/handlers/routes.go b/handlers/routes.go index 873afe43..c71e38d6 100644 --- a/handlers/routes.go +++ b/handlers/routes.go @@ -33,7 +33,7 @@ func Router() *mux.Router { r.Handle("/user/{id}", http.HandlerFunc(UsersEditHandler)).Methods("GET") r.Handle("/user/{id}", http.HandlerFunc(UpdateUserHandler)).Methods("POST") r.Handle("/user/{id}/delete", http.HandlerFunc(UsersDeleteHandler)).Methods("GET") - r.Handle("/settings", http.HandlerFunc(PluginsHandler)).Methods("GET") + r.Handle("/settings", http.HandlerFunc(SettingsHandler)).Methods("GET") 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") @@ -42,6 +42,7 @@ func Router() *mux.Router { r.Handle("/plugins/{name}/save", http.HandlerFunc(PluginSavedHandler)).Methods("POST") r.Handle("/help", http.HandlerFunc(HelpHandler)) r.Handle("/api", http.HandlerFunc(ApiIndexHandler)) + r.Handle("/api/renew", http.HandlerFunc(ApiRenewHandler)) r.Handle("/api/checkin/{api}", http.HandlerFunc(ApiCheckinHandler)) r.Handle("/api/services", http.HandlerFunc(ApiAllServicesHandler)) r.Handle("/api/services/{id}", http.HandlerFunc(ApiServiceHandler)).Methods("GET") diff --git a/handlers/services.go b/handlers/services.go index e4107f4f..0e7bea86 100644 --- a/handlers/services.go +++ b/handlers/services.go @@ -78,6 +78,9 @@ func ServicesDeleteHandler(w http.ResponseWriter, r *http.Request) { func ServicesViewHandler(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) serv := core.SelectService(utils.StringInt(vars["id"])) + + fmt.Println(serv.ToService()) + ExecuteResponse(w, r, "service.html", serv) } @@ -94,9 +97,7 @@ func ServicesBadgeHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "image/svg+xml") w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate") - w.Write(badge) - } func ServicesUpdateHandler(w http.ResponseWriter, r *http.Request) { diff --git a/handlers/settings.go b/handlers/settings.go index 59951b21..9b9a230a 100644 --- a/handlers/settings.go +++ b/handlers/settings.go @@ -9,23 +9,11 @@ import ( "net/http" ) -func PluginsHandler(w http.ResponseWriter, r *http.Request) { +func SettingsHandler(w http.ResponseWriter, r *http.Request) { if !IsAuthenticated(r) { http.Redirect(w, r, "/", http.StatusSeeOther) return } - //CoreApp.FetchPluginRepo() - - //var pluginFields []PluginSelect - // - //for _, p := range allPlugins { - // fields := structs.Map(p.GetInfo()) - // - // pluginFields = append(pluginFields, PluginSelect{p.GetInfo().Name, p.GetForm(), fields}) - //} - - //CoreApp.PluginFields = pluginFields - ExecuteResponse(w, r, "settings.html", core.CoreApp) } @@ -56,8 +44,8 @@ func SaveSettingsHandler(w http.ResponseWriter, r *http.Request) { core.CoreApp.Domain = domain } core.CoreApp.UseCdn = (r.PostForm.Get("enable_cdn") == "on") - core.CoreApp.Update() - core.OnSettingsSaved(core.CoreApp) + core.CoreApp, _ = core.UpdateCore(core.CoreApp) + core.OnSettingsSaved(core.CoreApp.ToCore()) http.Redirect(w, r, "/settings", http.StatusSeeOther) } @@ -105,8 +93,8 @@ func SaveNotificationHandler(w http.ResponseWriter, r *http.Request) { 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)) + limits := int(utils.StringInt(r.PostForm.Get("limits"))) + notifer := notifiers.SelectNotifier(utils.StringInt(notifierId)).Select() if host != "" { notifer.Host = host diff --git a/main.go b/main.go index 99ba6d33..9de838e7 100644 --- a/main.go +++ b/main.go @@ -6,7 +6,7 @@ import ( "github.com/fatih/structs" "github.com/hunterlong/statup/core" "github.com/hunterlong/statup/handlers" - "github.com/hunterlong/statup/plugin" + "github.com/hunterlong/statup/types" "github.com/hunterlong/statup/utils" "github.com/joho/godotenv" "io/ioutil" @@ -123,8 +123,8 @@ func LoadPlugins(debug bool) { utils.Log(1, structs.Map(symPlugin)) } - var plugActions plugin.PluginActions - plugActions, ok := symPlugin.(plugin.PluginActions) + var plugActions types.PluginActions + plugActions, ok := symPlugin.(types.PluginActions) if !ok { utils.Log(3, fmt.Sprintf("Plugin '%v' could not load correctly, error: %v", f.Name(), err)) if debug { diff --git a/notifiers/email.go b/notifiers/email.go index e9f14bb9..ad9715e9 100644 --- a/notifiers/email.go +++ b/notifiers/email.go @@ -20,7 +20,7 @@ const ( var ( emailer *Email emailArray []string - emailQueue []*types.Email + emailQueue []*EmailOutgoing emailBox *rice.Box mailer *gomail.Dialer ) @@ -37,41 +37,35 @@ func init() { Id: EMAIL_ID, Method: EMAIL_METHOD, Form: []NotificationForm{{ - id: 1, + Id: 1, Type: "text", Title: "SMTP Host", Placeholder: "Insert your SMTP Host here.", DbField: "Host", }, { - id: 1, + Id: 1, Type: "text", Title: "SMTP Username", Placeholder: "Insert your SMTP Username here.", DbField: "Username", }, { - id: 1, + Id: 1, Type: "password", Title: "SMTP Password", Placeholder: "Insert your SMTP Password here.", DbField: "Password", }, { - id: 1, + Id: 1, Type: "number", Title: "SMTP Port", Placeholder: "Insert your SMTP Port here.", DbField: "Port", }, { - id: 1, + 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", }}, }} @@ -108,7 +102,7 @@ func (u *Email) Init() error { func (u *Email) Test() error { if u.Enabled { - email := &types.Email{ + email := &EmailOutgoing{ To: "info@socialeck.com", Subject: "Test Email", Template: "message.html", @@ -124,6 +118,16 @@ type emailMessage struct { Service *types.Service } +type EmailOutgoing struct { + To string + Subject string + Template string + From string + Data interface{} + Source string + Sent bool +} + // AFTER NOTIFIER LOADS, IF ENABLED, START A QUEUE PROCESS func (u *Email) Run() error { var sentAddresses []string @@ -132,16 +136,18 @@ func (u *Email) Run() error { emailQueue = removeEmail(emailQueue, email) continue } - e := email - go func(email *types.Email) { + if u.CanSend() { 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) + u.Log(fmt.Sprintf("Subject: %v to %v", email.Subject, email.To)) + } else { + utils.Log(3, fmt.Sprintf("Email Notifier could not send email: %v", err)) } - }(e) + } } time.Sleep(60 * time.Second) if u.Enabled { @@ -158,7 +164,7 @@ func (u *Email) OnFailure(s *types.Service) error { Service: s, } - email := &types.Email{ + email := &EmailOutgoing{ To: "info@socialeck.com", Subject: fmt.Sprintf("Service %v is Failing", s.Name), Template: "failure.html", @@ -190,10 +196,7 @@ func (u *Email) OnSave() error { // ON SERVICE FAILURE, DO YOUR OWN FUNCTIONS func (u *Email) Install() error { - - fmt.Println("installing emailer") - - inDb, err := emailer.Notification.isInDatabase() + inDb, err := emailer.Notification.IsInDatabase() if !inDb { newNotifer, err := InsertDatabase(u.Notification) if err != nil { @@ -205,7 +208,7 @@ func (u *Email) Install() error { return err } -func (u *Email) dialSend(email *types.Email) error { +func (u *Email) dialSend(email *EmailOutgoing) error { fmt.Println("sending dailsend to emailer") m := gomail.NewMessage() m.SetHeader("From", email.From) @@ -219,7 +222,7 @@ func (u *Email) dialSend(email *types.Email) error { return nil } -func SendEmail(box *rice.Box, email *types.Email) { +func SendEmail(box *rice.Box, email *EmailOutgoing) { source := EmailTemplate(box, email.Template, email.Data) email.Source = source emailQueue = append(emailQueue, email) @@ -243,8 +246,8 @@ func EmailTemplate(box *rice.Box, tmpl string, data interface{}) string { return result } -func removeEmail(emails []*types.Email, em *types.Email) []*types.Email { - var newArr []*types.Email +func removeEmail(emails []*EmailOutgoing, em *EmailOutgoing) []*EmailOutgoing { + var newArr []*EmailOutgoing for _, e := range emails { if e != em { newArr = append(newArr, e) diff --git a/notifiers/notifiers.go b/notifiers/notifiers.go index a54f1786..f1b2c8e4 100644 --- a/notifiers/notifiers.go +++ b/notifiers/notifiers.go @@ -10,28 +10,11 @@ import ( ) var ( - AllCommunications []AllNotifiers + AllCommunications []types.AllNotifiers Collections db.Collection + Logs []*NotificationLog ) -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"` @@ -44,7 +27,7 @@ type Notification struct { ApiKey string `db:"api_key" json:"-"` ApiSecret string `db:"api_secret" json:"-"` Enabled bool `db:"enabled" json:"enabled"` - Limits int64 `db:"limits" json:"-"` + Limits int `db:"limits" json:"-"` Removable bool `db:"removable" json:"-"` CreatedAt time.Time `db:"created_at" json:"created_at"` Form []NotificationForm @@ -62,14 +45,62 @@ type Notifier interface { } type NotificationForm struct { - id int64 + Id int64 Type string Title string Placeholder string DbField string } -func (n *Notification) isInDatabase() (bool, error) { +type NotificationLog struct { + Notifier *Notification + Message string + Time utils.Timestamp +} + +func add(c interface{}) { + AllCommunications = append(AllCommunications, c) +} + +func Load() []types.AllNotifiers { + utils.Log(1, "Loading notifiers") + var notifiers []types.AllNotifiers + for _, comm := range AllCommunications { + n := comm.(Notifier) + n.Init() + notifiers = append(notifiers, n) + n.Test() + } + return notifiers +} + +func (n *Notification) Log(msg string) { + log := &NotificationLog{ + Notifier: n, + Message: msg, + Time: utils.Timestamp(time.Now()), + } + Logs = append(Logs, log) +} + +func (n *Notification) Logs() []*NotificationLog { + var logs []*NotificationLog + for _, v := range Logs { + if v.Notifier.Id == n.Id { + logs = append(logs, v) + } + } + return reverseLogs(logs) +} + +func reverseLogs(input []*NotificationLog) []*NotificationLog { + if len(input) == 0 { + return input + } + return append(reverseLogs(input[1:]), input[0]) +} + +func (n *Notification) IsInDatabase() (bool, error) { return Collections.Find("id", n.Id).Exists() } @@ -87,6 +118,7 @@ func (n *Notification) Update() (*Notification, error) { func InsertDatabase(n *Notification) (int64, error) { n.CreatedAt = time.Now() + n.Limits = 3 newId, err := Collections.Insert(n) if err != nil { return 0, err @@ -94,18 +126,6 @@ func InsertDatabase(n *Notification) (int64, error) { 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 { @@ -118,9 +138,33 @@ func SelectNotifier(id int64) Notifier { return notifier } +func (f Notification) CanSend() bool { + if f.SentLastHour() >= f.Limits { + return false + } + return true +} + +func (f Notification) SentLastHour() int { + sent := 0 + hourAgo := time.Now().Add(-1 * time.Hour) + for _, v := range f.Logs() { + lastTime := time.Time(v.Time) + if lastTime.After(hourAgo) { + sent++ + } + } + return sent +} + func (f NotificationForm) Value() string { - notifier := Select(f.id) - return notifier.GetValue(f.DbField) + noti := SelectNotifier(f.Id) + return noti.Select().GetValue(f.DbField) +} + +func (f Notification) LimitValue() int64 { + notifier, _ := SelectNotification(f.Id) + return utils.StringInt(notifier.GetValue("limits")) } func (n *Notification) GetValue(dbField string) string { @@ -144,6 +188,8 @@ func (n *Notification) GetValue(dbField string) string { return n.ApiKey case "api_secret": return n.ApiSecret + case "limits": + return utils.IntString(int(n.Limits)) } return "" } diff --git a/notifiers/slack.go b/notifiers/slack.go index 292e4b64..f7df1b9d 100644 --- a/notifiers/slack.go +++ b/notifiers/slack.go @@ -41,7 +41,7 @@ func init() { Method: SLACK_METHOD, Host: "https://webhooksurl.slack.com/***", Form: []NotificationForm{{ - id: 2, + Id: 2, Type: "text", Title: "Incoming Webhook Url", Placeholder: "Insert your Slack webhook URL here.", @@ -84,11 +84,15 @@ func (u *Slack) Run() error { messageLock.Lock() slackMessages = uniqueStrings(slackMessages) for _, msg := range slackMessages { - utils.Log(1, fmt.Sprintf("Sending JSON to Slack Webhook")) - client := http.Client{Timeout: 15 * time.Second} - _, err := client.Post(u.Host, "application/json", bytes.NewBuffer([]byte(msg))) - if err != nil { - utils.Log(3, fmt.Sprintf("Issue sending Slack notification: %v", err)) + + if u.CanSend() { + utils.Log(1, fmt.Sprintf("Sending JSON to Slack Webhook")) + client := http.Client{Timeout: 15 * time.Second} + _, err := client.Post(u.Host, "application/json", bytes.NewBuffer([]byte(msg))) + if err != nil { + utils.Log(3, fmt.Sprintf("Issue sending Slack notification: %v", err)) + } + u.Log(msg) } } slackMessages = []string{} @@ -146,7 +150,7 @@ func (u *Slack) OnSave() error { // ON SERVICE FAILURE, DO YOUR OWN FUNCTIONS func (u *Slack) Install() error { - inDb, err := slacker.Notification.isInDatabase() + inDb, err := slacker.Notification.IsInDatabase() if !inDb { newNotifer, err := InsertDatabase(u.Notification) if err != nil { diff --git a/plugin/main.go b/plugin/main.go index 8ddfc49c..645f9bd0 100644 --- a/plugin/main.go +++ b/plugin/main.go @@ -1,7 +1,7 @@ package plugin import ( - "net/http" + "github.com/hunterlong/statup/types" "upper.io/db.v3/lib/sqlbuilder" ) @@ -21,48 +21,14 @@ var ( DB sqlbuilder.Database ) -func SetDatabase(database sqlbuilder.Database) { - DB = database +type PluginInfo struct { + i *types.Info } -type PluginInfo struct { - Info Info - PluginActions +func SetDatabase(database sqlbuilder.Database) { + DB = database } func (p *PluginInfo) Form() string { return "okkokokkok" } - -type PluginActions interface { - GetInfo() Info - GetForm() string - OnLoad(sqlbuilder.Database) - SetInfo(map[string]interface{}) Info - Routes() []Routing - OnSave(map[string]interface{}) - OnFailure(map[string]interface{}) - OnSuccess(map[string]interface{}) - OnSettingsSaved(map[string]interface{}) - OnNewUser(map[string]interface{}) - OnNewService(map[string]interface{}) - OnUpdatedService(map[string]interface{}) - OnDeletedService(map[string]interface{}) - OnInstall(map[string]interface{}) - OnUninstall(map[string]interface{}) - OnBeforeRequest(map[string]interface{}) - OnAfterRequest(map[string]interface{}) - OnShutdown() -} - -type Routing struct { - URL string - Method string - Handler func(http.ResponseWriter, *http.Request) -} - -type Info struct { - Name string - Description string - Form string -} diff --git a/source/js/main.js b/source/js/main.js index e87e2e29..9d1d0154 100644 --- a/source/js/main.js +++ b/source/js/main.js @@ -21,6 +21,11 @@ $(".confirm-btn").on('click', function() { }); +$(".select-input").on("click", function () { + $(this).select(); +}); + + var ranVar = false; var ranTheme = false; $('a[data-toggle="pill"]').on('shown.bs.tab', function (e) { diff --git a/source/tmpl/dashboard.html b/source/tmpl/dashboard.html index aa301a95..25e270c1 100644 --- a/source/tmpl/dashboard.html +++ b/source/tmpl/dashboard.html @@ -59,26 +59,23 @@ {{ end }} - -

Latest Failures

- {{ range .Services }} {{ $s := .ToService }} {{ if .LimitedFailures }} -
+

{{$s.Name}} Failures

+
{{ range .LimitedFailures }} + {{ $f := .ToFailure }}
{{.ParseError}}
Reported {{.Ago}}
-

{{.Issue}}

+

{{$f.Issue}}

{{ end }}
{{ end }} - - {{ end }} diff --git a/source/tmpl/service.html b/source/tmpl/service.html index 47d63c71..b2105971 100644 --- a/source/tmpl/service.html +++ b/source/tmpl/service.html @@ -59,14 +59,15 @@ {{ if .LimitedFailures }} -
+
{{ range .LimitedFailures }} + {{ $f := .ToFailure }}
{{.ParseError}}
Reported {{.Ago}}
-

{{.Issue}}

+

{{$f.Issue}}

{{ end }}
@@ -147,7 +148,7 @@
@@ -156,9 +157,7 @@

Last Response

- -
diff --git a/source/tmpl/services.html b/source/tmpl/services.html index c1d93ae7..83c4f5f5 100644 --- a/source/tmpl/services.html +++ b/source/tmpl/services.html @@ -40,7 +40,7 @@ @@ -91,6 +91,7 @@
+ You can insert Regex to validate the response
diff --git a/source/tmpl/settings.html b/source/tmpl/settings.html index 26b2a705..7ab12473 100644 --- a/source/tmpl/settings.html +++ b/source/tmpl/settings.html @@ -20,7 +20,6 @@
- {{template "nav"}}
@@ -79,14 +78,19 @@ -
- - +
+ +
+ +
-
- - +
+ +
+ + You can Regenerate API Keys if you need to. +
@@ -122,7 +126,7 @@ {{ range .Communications }} -
+
{{range .Form}}
@@ -130,6 +134,12 @@
{{end}} + +
+ + +
+
@@ -143,6 +153,18 @@
+ + {{ if .Logs }} + Sent {{.SentLastHour}} out of {{.LimitValue}} in the last hour
+ {{ range .Logs }} +
+
+ {{.Message}} +

Sent {{.Time.Ago}}

+
+
+ {{ end }} + {{ end }}
{{ end }} diff --git a/types/types.go b/types/types.go index 5c38ea9f..a767b682 100644 --- a/types/types.go +++ b/types/types.go @@ -1,9 +1,72 @@ package types import ( + "net/http" "time" + "upper.io/db.v3/lib/sqlbuilder" ) +type PluginInfo struct { + Info Info + PluginActions +} + +type Routing struct { + URL string + Method string + Handler func(http.ResponseWriter, *http.Request) +} + +type Info struct { + Name string + Description string + Form string +} + +type PluginActions interface { + GetInfo() Info + GetForm() string + OnLoad(sqlbuilder.Database) + SetInfo(map[string]interface{}) Info + Routes() []Routing + OnSave(map[string]interface{}) + OnFailure(map[string]interface{}) + OnSuccess(map[string]interface{}) + OnSettingsSaved(map[string]interface{}) + OnNewUser(map[string]interface{}) + OnNewService(map[string]interface{}) + OnUpdatedService(map[string]interface{}) + OnDeletedService(map[string]interface{}) + OnInstall(map[string]interface{}) + OnUninstall(map[string]interface{}) + OnBeforeRequest(map[string]interface{}) + OnAfterRequest(map[string]interface{}) + OnShutdown() +} + +type AllNotifiers interface{} + +type Core struct { + Name string `db:"name" json:"name"` + Description string `db:"description" json:"name"` + Config string `db:"config" json:"-"` + ApiKey string `db:"api_key" json:"-"` + ApiSecret string `db:"api_secret" json:"-"` + Style string `db:"style" json:"-"` + Footer string `db:"footer" json:"-"` + Domain string `db:"domain" json:"domain,omitempty"` + Version string `db:"version" json:"version,omitempty"` + MigrationId int64 `db:"migration_id" json:"-"` + UseCdn bool `db:"use_cdn" json:"-"` + Services []*Service `json:"services,omitempty"` + Plugins []Info + Repos []PluginJSON + AllPlugins []PluginActions + Communications []AllNotifiers + DbConnection string + Started time.Time +} + type Service struct { Id int64 `db:"id,omitempty" json:"id"` Name string `db:"name" json:"name"` @@ -67,16 +130,6 @@ type Checkin struct { Last time.Time `json:"last"` } -type Email struct { - To string - Subject string - Template string - From string - Data interface{} - Source string - Sent bool -} - type Config struct { Connection string `yaml:"connection"` Host string `yaml:"host"` diff --git a/utils/utils.go b/utils/utils.go index 8a638b15..1f457785 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -1,10 +1,12 @@ package utils import ( + "github.com/ararog/timeago" "os" "regexp" "strconv" "strings" + "time" ) func StringInt(s string) int64 { @@ -12,6 +14,21 @@ func StringInt(s string) int64 { return int64(num) } +func IntString(s int) string { + return strconv.Itoa(s) +} + +type Timestamp time.Time + +type Timestamper interface { + Ago() string +} + +func (t Timestamp) Ago() string { + got, _ := timeago.TimeAgoWithTime(time.Now(), time.Time(t)) + return got +} + func UnderScoreString(str string) string { // convert every letter to lower case