From d991fe64b194ec59d171243b467fb857b1cbb0e1 Mon Sep 17 00:00:00 2001 From: Hugo van Rijswijk Date: Tue, 7 Jul 2020 20:58:46 +0000 Subject: [PATCH 01/16] Add Gotify notifier --- notifiers/gotify.go | 91 ++++++++++++++++++++++++++++++++++++++++ notifiers/gotify_test.go | 79 ++++++++++++++++++++++++++++++++++ notifiers/notifiers.go | 1 + 3 files changed, 171 insertions(+) create mode 100644 notifiers/gotify.go create mode 100644 notifiers/gotify_test.go diff --git a/notifiers/gotify.go b/notifiers/gotify.go new file mode 100644 index 00000000..d49ace5f --- /dev/null +++ b/notifiers/gotify.go @@ -0,0 +1,91 @@ +package notifiers + +import ( + "strings" + "time" + + "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" +) + +var _ notifier.Notifier = (*gotify)(nil) + +type gotify struct { + *notifications.Notification +} + +func (g *gotify) Select() *notifications.Notification { + return g.Notification +} + +var Gotify = &gotify{¬ifications.Notification{ + Method: "gotify", + Title: "Gotify", + Host: "https://gotify.myserver.com", + Description: "Use Gotify to receive push notifications. Add your Gotify URL and App Token to receive notifications.", + Author: "Hugo van Rijswijk", + AuthorUrl: "https://github.com/hugo-vrijswijk", + Icon: "fa dot-circle", + Delay: time.Duration(5 * time.Second), + Limits: 60, + SuccessData: `{"title": "{{.Service.Name}}", "message": "Your service '{{.Service.Name}}' is currently online!", "priority": 2}`, + FailureData: `{"title": "{{.Service.Name}}", "message": "Your service '{{.Service.Name}}' is currently failing! Reason: {{.Failure.Issue}}", "priority": 5}`, + DataType: "json", + Form: []notifications.NotificationForm{{ + Type: "text", + Title: "Gotify URL", + SmallText: "Gotify server URL, including http(s):// and port if needed", + DbField: "Host", + Required: true, + }, { + Type: "text", + Title: "App Token", + SmallText: "The Application Token generated by Gotify", + DbField: "api_key", + Required: true, + }}}, +} + +// Send will send a HTTP Post to the Gotify API. It accepts type: string +func (g *gotify) sendMessage(msg string) (string, error) { + var url string + if strings.HasSuffix(g.Host, "/") { + url = g.Host + "message" + } else { + url = g.Host + "/message" + } + + headers := []string{"X-Gotify-Key=" + g.ApiKey} + + content, _, err := utils.HttpRequest(url, "POST", "application/json", headers, strings.NewReader(msg), time.Duration(10*time.Second), true, nil) + + return string(content), err +} + +// OnFailure will trigger failing service +func (g *gotify) OnFailure(s services.Service, f failures.Failure) (string, error) { + out, err := g.sendMessage(ReplaceVars(g.FailureData, s, f)) + return out, err +} + +// OnSuccess will trigger successful service +func (g *gotify) OnSuccess(s services.Service) (string, error) { + out, err := g.sendMessage(ReplaceVars(g.SuccessData, s, failures.Failure{})) + return out, err +} + +// OnTest will test the Gotify notifier +func (g *gotify) OnTest() (string, error) { + msg := `{"title:" "Test" "message": "Testing the Gotify Notifier", "priority": 0}` + content, err := g.sendMessage(msg) + + return content, err +} + +// OnSave will trigger when this notifier is saved +func (g *gotify) OnSave() (string, error) { + return "", nil +} diff --git a/notifiers/gotify_test.go b/notifiers/gotify_test.go new file mode 100644 index 00000000..d1718629 --- /dev/null +++ b/notifiers/gotify_test.go @@ -0,0 +1,79 @@ +package notifiers + +import ( + "testing" + "time" + + "github.com/statping/statping/database" + "github.com/statping/statping/types/core" + "github.com/statping/statping/types/failures" + "github.com/statping/statping/types/notifications" + "github.com/statping/statping/types/null" + "github.com/statping/statping/types/services" + "github.com/statping/statping/utils" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +var ( + GOTIFY_URL string + GOTIFY_TOKEN string +) + +func TestGotifyNotifier(t *testing.T) { + err := utils.InitLogs() + require.Nil(t, err) + GOTIFY_URL = utils.Params.GetString("GOTIFY_URL") + GOTIFY_TOKEN = utils.Params.GetString("GOTIFY_TOKEN") + + db, err := database.OpenTester() + require.Nil(t, err) + db.AutoMigrate(¬ifications.Notification{}) + notifications.SetDB(db) + core.Example() + + if GOTIFY_URL == "" { + t.Log("gotify notifier testing skipped, missing GOTIFY_URL environment variable") + t.SkipNow() + } + if GOTIFY_TOKEN == "" { + t.Log("gotify notifier testing skipped, missing GOTIFY_TOKEN environment variable") + t.SkipNow() + } + + t.Run("Load gotify", func(t *testing.T) { + Gotify.Host = GOTIFY_URL + Gotify.Delay = time.Duration(100 * time.Millisecond) + Gotify.Enabled = null.NewNullBool(true) + + Add(Gotify) + + assert.Equal(t, "Hugo van Rijswijk", Gotify.Author) + assert.Equal(t, GOTIFY_URL, Gotify.Host) + }) + + t.Run("gotify Notifier Tester", func(t *testing.T) { + assert.True(t, Gotify.CanSend()) + }) + + t.Run("gotify Notifier Tester OnSave", func(t *testing.T) { + _, err := Gotify.OnSave() + assert.Nil(t, err) + }) + + t.Run("gotify OnFailure", func(t *testing.T) { + _, err := Gotify.OnFailure(services.Example(false), failures.Example()) + assert.Nil(t, err) + }) + + t.Run("gotify OnSuccess", func(t *testing.T) { + _, err := Gotify.OnSuccess(services.Example(true)) + assert.Nil(t, err) + }) + + t.Run("gotify Test", func(t *testing.T) { + _, err := Gotify.OnTest() + assert.Nil(t, err) + }) + +} diff --git a/notifiers/notifiers.go b/notifiers/notifiers.go index d1a74a26..f7e59dee 100644 --- a/notifiers/notifiers.go +++ b/notifiers/notifiers.go @@ -32,6 +32,7 @@ func InitNotifiers() { Mobile, Pushover, statpingMailer, + Gotify, ) } From c3a85e3d7fbcdd5024d5d02d740a01cbcbec32be Mon Sep 17 00:00:00 2001 From: hunterlong Date: Tue, 7 Jul 2020 14:15:47 -0700 Subject: [PATCH 02/16] webhook notifier fix --- notifiers/webhook.go | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/notifiers/webhook.go b/notifiers/webhook.go index 8f3d520c..ad1e300f 100644 --- a/notifiers/webhook.go +++ b/notifiers/webhook.go @@ -82,12 +82,8 @@ func (w *webhooker) Select() *notifications.Notification { func (w *webhooker) sendHttpWebhook(body string) (*http.Response, error) { utils.Log.Infoln(fmt.Sprintf("sending body: '%v' to %v as a %v request", body, w.Host, w.Var1)) client := new(http.Client) - client.Timeout = time.Duration(10 * time.Second) - buf := bytes.NewBuffer(nil) - if w.Var2 != "" { - buf = bytes.NewBuffer([]byte(body)) - } - req, err := http.NewRequest(w.Var1, w.Host, buf) + client.Timeout = 10 * time.Second + req, err := http.NewRequest(w.Var1, w.Host, bytes.NewBufferString(body)) if err != nil { return nil, err } @@ -100,6 +96,8 @@ func (w *webhooker) sendHttpWebhook(body string) (*http.Response, error) { } if w.ApiKey != "" { req.Header.Add("Content-Type", w.ApiKey) + } else { + req.Header.Add("Content-Type", "application/json") } req.Header.Set("User-Agent", "Statping") req.Header.Set("Statping-Version", utils.Version) From 23fb2eb2a99f4deff3d19907b099a68ea9ccfe00 Mon Sep 17 00:00:00 2001 From: hunterlong Date: Tue, 7 Jul 2020 21:22:59 -0700 Subject: [PATCH 03/16] import and export CLI command updates (added configs to export/import), modified Webhook notifier headers --- cmd/cli.go | 61 +++++++++++++++++++++++++++++++------- cmd/main.go | 35 ++-------------------- notifiers/webhook.go | 13 +++++--- types/configs/file.go | 12 -------- types/configs/load.go | 19 ++++++++---- types/configs/migration.go | 34 +++++++++++++++++++++ 6 files changed, 109 insertions(+), 65 deletions(-) diff --git a/cmd/cli.go b/cmd/cli.go index 672fb676..8c06a086 100644 --- a/cmd/cli.go +++ b/cmd/cli.go @@ -153,8 +153,10 @@ func onceCli() error { func importCli(args []string) error { var err error var data []byte - filename := args[1] - if data, err = ioutil.ReadFile(filename); err != nil { + if len(args) < 1 { + return errors.New("invalid command arguments") + } + if data, err = ioutil.ReadFile(args[0]); err != nil { return err } var exportData ExportData @@ -162,11 +164,40 @@ func importCli(args []string) error { return err } log.Printf("=== %s ===\n", exportData.Core.Name) - log.Printf("Services: %d\n", len(exportData.Services)) - log.Printf("Checkins: %d\n", len(exportData.Checkins)) - log.Printf("Groups: %d\n", len(exportData.Groups)) - log.Printf("Messages: %d\n", len(exportData.Messages)) - log.Printf("Users: %d\n", len(exportData.Users)) + if exportData.Config != nil { + log.Printf("Configs: %s\n", exportData.Config.DbConn) + if exportData.Config.DbUser != "" { + log.Printf(" - Host: %s\n", exportData.Config.DbHost) + log.Printf(" - User: %s\n", exportData.Config.DbUser) + } + } + if len(exportData.Services) > 0 { + log.Printf("Services: %d\n", len(exportData.Services)) + } + if len(exportData.Checkins) > 0 { + log.Printf("Checkins: %d\n", len(exportData.Checkins)) + } + if len(exportData.Groups) > 0 { + log.Printf("Groups: %d\n", len(exportData.Groups)) + } + if len(exportData.Messages) > 0 { + log.Printf("Messages: %d\n", len(exportData.Messages)) + } + if len(exportData.Users) > 0 { + log.Printf("Users: %d\n", len(exportData.Users)) + } + + if exportData.Config != nil { + if ask("Create config.yml file from Configs?") { + log.Printf("Database User: %d\n", exportData.Config.DbUser) + log.Printf("Database Password: %d\n", exportData.Config.DbPass) + log.Printf("Database Host: %d\n", exportData.Config.DbHost) + log.Printf("Database Port: %d\n", exportData.Config.DbPort) + if err := exportData.Config.Save(utils.Directory); err != nil { + return err + } + } + } config, err := configs.LoadConfigs(configFile) if err != nil { @@ -175,8 +206,10 @@ func importCli(args []string) error { if err = configs.ConnectConfigs(config, false); err != nil { return err } - if data, err = ExportSettings(); err != nil { - return fmt.Errorf("could not export settings: %v", err.Error()) + if ask("Create database rows and sample data?") { + if err := config.ResetCore(); err != nil { + return err + } } if ask("Import Core settings?") { @@ -221,7 +254,7 @@ func importCli(args []string) error { if ask(fmt.Sprintf("Import User '%s'?", s.Username)) { s.Id = 0 if err := s.Create(); err != nil { - return err + log.Errorln(err) } } } @@ -376,6 +409,7 @@ type gitUploader struct { // ExportChartsJs renders the charts for the index page type ExportData struct { + Config *configs.DbConfig `json:"config"` Core *core.Core `json:"core"` Services []services.Service `json:"services"` Messages []*messages.Message `json:"messages"` @@ -403,7 +437,14 @@ func ExportSettings() ([]byte, error) { s.Failures = nil srvs = append(srvs, s) } + + cfg, err := configs.LoadConfigs(configFile) + if err != nil { + return nil, err + } + data := ExportData{ + Config: cfg, Core: c, Notifiers: core.App.Notifications, Checkins: checkins.All(), diff --git a/cmd/main.go b/cmd/main.go index 87c2d72e..056202fe 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -96,39 +96,8 @@ func start() { exit(err) } - if !confgs.Db.HasTable("core") { - var srvs int64 - if confgs.Db.HasTable(&services.Service{}) { - confgs.Db.Model(&services.Service{}).Count(&srvs) - if srvs > 0 { - exit(errors.Wrap(err, "there are already services setup.")) - return - } - } - - if err := confgs.DropDatabase(); err != nil { - exit(errors.Wrap(err, "error dropping database")) - } - - if err := confgs.CreateDatabase(); err != nil { - exit(errors.Wrap(err, "error creating database")) - } - - if err := configs.CreateAdminUser(confgs); err != nil { - exit(errors.Wrap(err, "error creating default admin user")) - } - - if utils.Params.GetBool("SAMPLE_DATA") { - log.Infoln("Adding Sample Data") - if err := configs.TriggerSamples(); err != nil { - exit(errors.Wrap(err, "error adding sample data")) - } - } else { - if err := core.Samples(); err != nil { - exit(errors.Wrap(err, "error added core details")) - } - } - + if err = confgs.ResetCore(); err != nil { + exit(err) } if err = confgs.DatabaseChanges(); err != nil { diff --git a/notifiers/webhook.go b/notifiers/webhook.go index ad1e300f..08bc75fb 100644 --- a/notifiers/webhook.go +++ b/notifiers/webhook.go @@ -88,10 +88,15 @@ func (w *webhooker) sendHttpWebhook(body string) (*http.Response, error) { return nil, err } if w.ApiSecret != "" { - splitArray := strings.Split(w.ApiSecret, ",") - for _, a := range splitArray { - split := strings.Split(a, "=") - req.Header.Add(split[0], split[1]) + keyVal := strings.SplitN(w.ApiSecret, "=", 2) + if len(keyVal) == 2 { + if keyVal[0] != "" && keyVal[1] != "" { + if strings.ToLower(keyVal[0]) == "host" { + req.Host = strings.TrimSpace(keyVal[1]) + } else { + req.Header.Set(keyVal[0], keyVal[1]) + } + } } } if w.ApiKey != "" { diff --git a/types/configs/file.go b/types/configs/file.go index f336e62f..9c71e075 100644 --- a/types/configs/file.go +++ b/types/configs/file.go @@ -21,18 +21,6 @@ func ConnectConfigs(configs *DbConfig, retry bool) error { return nil } -func LoadConfigs(cfgFile string) (*DbConfig, error) { - writeAble, err := utils.DirWritable(utils.Directory) - if err != nil { - return nil, err - } - if !writeAble { - return nil, errors.Errorf("Directory %s is not writable!", utils.Directory) - } - - return LoadConfigFile(cfgFile) -} - func findDbFile(configs *DbConfig) (string, error) { location := utils.Directory + "/" + SqliteFilename if configs == nil { diff --git a/types/configs/load.go b/types/configs/load.go index 572927e5..26dd21d0 100644 --- a/types/configs/load.go +++ b/types/configs/load.go @@ -1,21 +1,28 @@ package configs import ( - "github.com/statping/statping/types/errors" + "errors" "github.com/statping/statping/utils" "gopkg.in/yaml.v2" "os" ) -func LoadConfigFile(configFile string) (*DbConfig, error) { +func LoadConfigs(cfgFile string) (*DbConfig, error) { + writeAble, err := utils.DirWritable(utils.Directory) + if err != nil { + return nil, err + } + if !writeAble { + return nil, errors.New("Directory %s is not writable: " + utils.Directory) + } p := utils.Params - log.Infof("Attempting to read config file at: %s", configFile) - p.SetConfigFile(configFile) + log.Infof("Attempting to read config file at: %s", cfgFile) + p.SetConfigFile(cfgFile) p.SetConfigType("yaml") p.ReadInConfig() db := new(DbConfig) - content, err := utils.OpenFile(configFile) + content, err := utils.OpenFile(cfgFile) if err == nil { if err := yaml.Unmarshal([]byte(content), &db); err != nil { return nil, err @@ -74,7 +81,7 @@ func LoadConfigFile(configFile string) (*DbConfig, error) { Language: p.GetString("LANGUAGE"), SendReports: p.GetBool("ALLOW_REPORTS"), } - log.WithFields(utils.ToFields(configs)).Debugln("read config file: " + configFile) + log.WithFields(utils.ToFields(configs)).Debugln("read config file: " + cfgFile) if configs.DbConn == "" { return configs, errors.New("Starting in setup mode") diff --git a/types/configs/migration.go b/types/configs/migration.go index 48c87660..953e8455 100644 --- a/types/configs/migration.go +++ b/types/configs/migration.go @@ -5,6 +5,7 @@ import ( _ "github.com/jinzhu/gorm/dialects/mysql" _ "github.com/jinzhu/gorm/dialects/postgres" _ "github.com/mattn/go-sqlite3" + "github.com/pkg/errors" "github.com/statping/statping/source" "github.com/statping/statping/types/notifications" "github.com/statping/statping/utils" @@ -20,6 +21,39 @@ import ( "github.com/statping/statping/types/users" ) +func (d *DbConfig) ResetCore() error { + if d.Db.HasTable("core") { + return nil + } + var srvs int64 + if d.Db.HasTable(&services.Service{}) { + d.Db.Model(&services.Service{}).Count(&srvs) + if srvs > 0 { + return errors.New("there are already services setup.") + } + } + if err := d.DropDatabase(); err != nil { + return errors.Wrap(err, "error dropping database") + } + if err := d.CreateDatabase(); err != nil { + return errors.Wrap(err, "error creating database") + } + if err := CreateAdminUser(d); err != nil { + return errors.Wrap(err, "error creating default admin user") + } + if utils.Params.GetBool("SAMPLE_DATA") { + log.Infoln("Adding Sample Data") + if err := TriggerSamples(); err != nil { + return errors.Wrap(err, "error adding sample data") + } + } else { + if err := core.Samples(); err != nil { + return errors.Wrap(err, "error added core details") + } + } + return nil +} + func (d *DbConfig) DatabaseChanges() error { var cr core.Core d.Db.Model(&core.Core{}).Find(&cr) From 7f5a6a2e7c33d785f3687d24b5c7063570c4bb5c Mon Sep 17 00:00:00 2001 From: hunterlong Date: Thu, 9 Jul 2020 11:19:31 -0700 Subject: [PATCH 04/16] fixed ICMP latency and ping duration, organized Vue files --- .../components/Dashboard/DashboardIndex.vue | 2 +- .../{Service => Dashboard}/ServiceInfo.vue | 4 ++-- .../ServiceSparkLine.vue | 2 +- frontend/src/components/Service/Analytics.vue | 2 +- .../src/components/Service/ServiceBlock.vue | 14 ------------- types/services/routine.go | 9 ++++++++- utils/utils.go | 1 - utils/utils_custom.go | 20 +++++++++++++------ 8 files changed, 27 insertions(+), 27 deletions(-) rename frontend/src/components/{Service => Dashboard}/ServiceInfo.vue (98%) rename frontend/src/components/{Service => Dashboard}/ServiceSparkLine.vue (98%) diff --git a/frontend/src/components/Dashboard/DashboardIndex.vue b/frontend/src/components/Dashboard/DashboardIndex.vue index 8049883e..e8290888 100644 --- a/frontend/src/components/Dashboard/DashboardIndex.vue +++ b/frontend/src/components/Dashboard/DashboardIndex.vue @@ -23,7 +23,7 @@