From efcdd9fdd8b645073f271392ebb2e400b9fedc7a Mon Sep 17 00:00:00 2001 From: Hunter Long Date: Fri, 3 Apr 2020 17:28:09 -0700 Subject: [PATCH] design changes, form updates, API fixes --- .travis.yml | 3 +- CHANGELOG.md | 7 + Makefile | 13 +- cmd/cli_test.go | 10 -- cmd/init.go | 28 ---- cmd/main.go | 113 +++++++------- database/routines.go | 62 +++----- frontend/package.json | 2 +- frontend/src/assets/scss/base.scss | 43 ++++++ frontend/src/forms/CoreSettings.vue | 4 +- frontend/src/forms/Notifier.vue | 118 +++++++-------- frontend/src/forms/Service.vue | 218 +++++++++++++++++----------- frontend/src/mixin.js | 12 ++ frontend/src/pages/Settings.vue | 19 +++ frontend/src/routes.js | 40 ++++- frontend/vue.config.js | 1 + handlers/api.go | 6 +- handlers/notifications.go | 91 ++++-------- handlers/routes.go | 2 +- handlers/services_test.go | 4 +- handlers/users.go | 3 +- notifiers/slack.go | 8 +- types/checkins/database.go | 5 - types/checkins/methods.go | 3 +- types/hits/database.go | 12 -- types/notifications/database.go | 27 +--- types/notifications/methods.go | 30 +--- types/notifications/struct.go | 63 ++++---- types/null/marshal.go | 24 +-- types/null/unmarshal.go | 24 +-- types/services/database.go | 31 +--- types/services/notifier.go | 16 +- types/services/routine.go | 37 ++--- types/users/auth.go | 6 +- utils/log.go | 39 ++++- utils/utils.go | 22 ++- version.txt | 2 +- 37 files changed, 600 insertions(+), 548 deletions(-) delete mode 100644 cmd/init.go diff --git a/.travis.yml b/.travis.yml index 90c413d9..0cd945d3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,7 +28,7 @@ env: go: 1.14 go_import_path: github.com/statping/statping install: - - "npm install -g sass newman cross-env wait-on" + - "npm install -g sass newman cross-env wait-on @sentry/cli" - "pip install --user awscli" - "go get github.com/mattn/goveralls" - "go mod download" @@ -49,7 +49,6 @@ os: - linux script: - "travis_retry make clean test-ci" - - "travis_retry make test-cypress" - "if [[ \"$TRAVIS_BRANCH\" == \"master\" && \"$TRAVIS_PULL_REQUEST\" = \"false\" ]]; then make coverage; fi" services: - docker diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d1f4b30..6bfb1779 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# 0.90.22 +- Added range input types for integer form fields +- Modified Sentry error logging details +- Modified form field layouts for better UX. +- Modified Notifier form +- Fixed Notifier Test form and logic + # 0.90.21 - Fixed BASE_PATH when using a path for Statping - Added Cypress testing diff --git a/Makefile b/Makefile index 5e85a685..cbb2735a 100644 --- a/Makefile +++ b/Makefile @@ -40,12 +40,10 @@ test-ci: clean compile test-deps SASS=`which sass` go test -v -covermode=count -coverprofile=coverage.out -p=1 ./... goveralls -coverprofile=coverage.out -service=travis-ci -repotoken ${COVERALLS} -test-cypress: clean +cypress: clean echo "Statping Bin: "`which statping` echo "Statping Version: "`statping version` - statping -port 8585 & wait-on http://localhost:8585/setup - cd frontend && yarn dev & wait-on http://localhost:8888 - cd frontend && yarn cypress:test + cd frontend && yarn test killall statping test-api: @@ -159,6 +157,7 @@ clean: rm -rf source/{logs,assets,plugins,*.db,config.yml,.sass-cache,*.log} rm -rf types/{logs,assets,plugins,*.db,config.yml,.sass-cache,*.log} rm -rf utils/{logs,assets,plugins,*.db,config.yml,.sass-cache,*.log} + rm -rf frontend/{logs,plugins,*.db,config.yml,.sass-cache,*.log} rm -rf dev/{logs,assets,plugins,*.db,config.yml,.sass-cache,*.log,test/app,plugin/*.so} rm -rf {parts,prime,snap,stage} rm -rf dev/test/cypress/videos @@ -283,6 +282,10 @@ xgo-install: clean go get github.com/crazy-max/xgo docker pull crazymax/xgo:${GOVERSION} +sentry-release: + sentry-cli releases new -p backend -p frontend v${VERSION} + sentry-cli releases set-commits --auto v${VERSION} + sentry-cli releases finalize v${VERSION} -.PHONY: all build build-all build-alpine test-all test test-api docker frontend up down print_details lite +.PHONY: all build build-all build-alpine test-all test test-api docker frontend up down print_details lite sentry-release .SILENT: travis_s3_creds diff --git a/cmd/cli_test.go b/cmd/cli_test.go index 7aa19d72..89d02755 100644 --- a/cmd/cli_test.go +++ b/cmd/cli_test.go @@ -1,8 +1,6 @@ package main import ( - "fmt" - "github.com/getsentry/sentry-go" "github.com/rendon/testcli" "github.com/statping/statping/utils" "github.com/stretchr/testify/assert" @@ -20,14 +18,6 @@ var ( func init() { dir = utils.Directory //core.SampleHits = 480 - - if err := sentry.Init(sentry.ClientOptions{ - Dsn: errorReporter, - Environment: "testing", - }); err != nil { - fmt.Println(err) - } - } func TestStartServerCommand(t *testing.T) { diff --git a/cmd/init.go b/cmd/init.go deleted file mode 100644 index c58bf972..00000000 --- a/cmd/init.go +++ /dev/null @@ -1,28 +0,0 @@ -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" - "github.com/statping/statping/utils" -) - -func InitApp() error { - if _, err := core.Select(); err != nil { - return err - } - - if _, err := services.SelectAllServices(true); err != nil { - return err - } - - go services.CheckServices() - - notifiers.InitNotifiers() - - database.StartMaintenceRoutine() - core.App.Setup = true - core.App.Started = utils.Now() - return nil -} diff --git a/cmd/main.go b/cmd/main.go index 7044c2a9..5d6be616 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -3,34 +3,31 @@ package main import ( "flag" "fmt" - "github.com/getsentry/sentry-go" - "github.com/statping/statping/handlers/protos" + "github.com/pkg/errors" + "github.com/statping/statping/database" + "github.com/statping/statping/handlers" + "github.com/statping/statping/notifiers" + "github.com/statping/statping/source" + "github.com/statping/statping/types/configs" "github.com/statping/statping/types/core" + "github.com/statping/statping/types/services" + "github.com/statping/statping/utils" "os" "os/signal" "syscall" - "time" - - "github.com/pkg/errors" - "github.com/statping/statping/handlers" - "github.com/statping/statping/source" - "github.com/statping/statping/types/configs" - "github.com/statping/statping/types/services" - "github.com/statping/statping/utils" ) var ( // VERSION stores the current version of Statping VERSION string // COMMIT stores the git commit hash for this version of Statping - COMMIT string - ipAddress string - grpcPort int + COMMIT string + ipAddress string + //grpcPort int envFile string verboseMode int port int log = utils.Log.WithField("type", "cmd") - httpServer = make(chan bool) confgs *configs.DbConfig ) @@ -43,27 +40,34 @@ func parseFlags() { envPort := utils.Getenv("PORT", 8080).(int) envIpAddress := utils.Getenv("IP", "0.0.0.0").(string) envVerbose := utils.Getenv("VERBOSE", 2).(int) - envGrpcPort := utils.Getenv("GRPC_PORT", 0).(int) + //envGrpcPort := utils.Getenv("GRPC_PORT", 0).(int) flag.StringVar(&ipAddress, "ip", envIpAddress, "IP address to run the Statping HTTP server") flag.StringVar(&envFile, "env", "", "IP address to run the Statping HTTP server") flag.IntVar(&port, "port", envPort, "Port to run the HTTP server") - flag.IntVar(&grpcPort, "grpc", envGrpcPort, "Port to run the gRPC server") + //flag.IntVar(&grpcPort, "grpc", envGrpcPort, "Port to run the gRPC server") flag.IntVar(&verboseMode, "verbose", envVerbose, "Run in verbose mode to see detailed logs (1 - 4)") flag.Parse() } -func exit(err error) { - sentry.CaptureException(err) - log.Fatalln(err) - Close() - os.Exit(2) -} - func init() { core.New(VERSION) } +// exit will return an error and return an exit code 1 due to this error +func exit(err error) { + utils.SentryErr(err) + Close() + log.Fatalln(err) +} + +// Close will gracefully stop the database connection, and log file +func Close() { + utils.CloseLogs() + confgs.Close() + fmt.Println("Shutting down Statping") +} + // main will run the Statping application func main() { var err error @@ -71,6 +75,8 @@ func main() { parseFlags() + utils.SentryInit(VERSION) + if err := source.Assets(); err != nil { exit(err) } @@ -93,20 +99,12 @@ func main() { exit(err) } } - log.Info(fmt.Sprintf("Starting Statping v%v", VERSION)) + log.Info(fmt.Sprintf("Starting Statping v%s", VERSION)) if err := updateDisplay(); err != nil { log.Warnln(err) } - errorEnv := utils.Getenv("GO_ENV", "production").(string) - if err := sentry.Init(sentry.ClientOptions{ - Dsn: errorReporter, - Environment: errorEnv, - }); err != nil { - log.Errorln(err) - } - confgs, err = configs.LoadConfigs() if err != nil { if err := SetupMode(); err != nil { @@ -165,13 +163,6 @@ func main() { } } -// Close will gracefully stop the database connection, and log file -func Close() { - sentry.Flush(3 * time.Second) - utils.CloseLogs() - confgs.Close() -} - func SetupMode() error { return handlers.RunHTTPServer(ipAddress, port) } @@ -181,7 +172,6 @@ func sigterm() { sigs := make(chan os.Signal, 1) signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) <-sigs - fmt.Println("Shutting down Statping") Close() os.Exit(0) } @@ -205,28 +195,25 @@ func mainProcess() error { return nil } -func StartHTTPServer() { - httpServer = make(chan bool) - go httpServerProcess(httpServer) -} - -func StopHTTPServer() { - -} - -func httpServerProcess(process <-chan bool) { - for { - select { - case <-process: - fmt.Println("HTTP Server has stopped") - return - default: - if err := handlers.RunHTTPServer(ipAddress, port); err != nil { - log.Errorln(err) - exit(err) - } - } +// InitApp will start the Statping instance with a valid database connection +// This function will gather all services in database, add/init Notifiers, +// and start the database cleanup routine +func InitApp() error { + if _, err := core.Select(); err != nil { + return err } -} -const errorReporter = "https://2bedd272821643e1b92c774d3fdf28e7@sentry.statping.com/2" + if _, err := services.SelectAllServices(true); err != nil { + return err + } + + go services.CheckServices() + + notifiers.InitNotifiers() + + core.App.Setup = true + core.App.Started = utils.Now() + + go database.Maintenance() + return nil +} diff --git a/database/routines.go b/database/routines.go index 75454f26..c92d5116 100644 --- a/database/routines.go +++ b/database/routines.go @@ -2,9 +2,7 @@ package database import ( "fmt" - "github.com/statping/statping/types" "github.com/statping/statping/utils" - "os" "time" _ "github.com/jinzhu/gorm/dialects/mysql" @@ -13,54 +11,36 @@ import ( ) var ( - log = utils.Log - removeRowsAfter = types.Day * 90 - maintenceDuration = types.Hour + log = utils.Log.WithField("type", "database") ) -func StartMaintenceRoutine() { - dur := os.Getenv("REMOVE_AFTER") - var removeDur time.Duration - - if dur != "" { - parsedDur, err := time.ParseDuration(dur) - if err != nil { - log.Errorf("could not parse duration: %s, using default: %s", dur, removeRowsAfter.String()) - removeDur = removeRowsAfter - } else { - removeDur = parsedDur - } - } else { - removeDur = removeRowsAfter - } - - log.Infof("Service Failure and Hit records will be automatically removed after %s", removeDur.String()) - go databaseMaintence(removeDur) -} - -// databaseMaintence will automatically delete old records from 'failures' and 'hits' +// Maintenance will automatically delete old records from 'failures' and 'hits' // this function is currently set to delete records 7+ days old every 60 minutes -func databaseMaintence(dur time.Duration) { - //deleteAfter := time.Now().UTC().Add(dur) +func Maintenance() { + dur := utils.GetenvAs("REMOVE_AFTER", "2160h").Duration() + interval := utils.GetenvAs("CLEANUP_INTERVAL", "1h").Duration() - time.Sleep(20 * types.Second) + log.Infof("Database Cleanup runs every %s and will remove records older than %s", interval.String(), dur.String()) + ticker := interval - for range time.Tick(maintenceDuration) { - log.Infof("Deleting failures older than %s", dur.String()) - //DeleteAllSince("failures", deleteAfter) + for range time.Tick(ticker) { + deleteAfter := utils.Now().Add(-dur) - log.Infof("Deleting hits older than %s", dur.String()) - //DeleteAllSince("hits", deleteAfter) + log.Infof("Deleting failures older than %s", deleteAfter.String()) + deleteAllSince("failures", deleteAfter) - maintenceDuration = types.Hour + log.Infof("Deleting hits older than %s", deleteAfter.String()) + deleteAllSince("hits", deleteAfter) + + ticker = interval } } -// DeleteAllSince will delete a specific table's records based on a time. -func DeleteAllSince(table string, date time.Time) { - sql := fmt.Sprintf("DELETE FROM %s WHERE created_at < '%s';", table, database.FormatTime(date)) - q := database.Exec(sql).Debug() - if q.Error() != nil { - log.Warnln(q.Error()) +// deleteAllSince will delete a specific table's records based on a time. +func deleteAllSince(table string, date time.Time) { + sql := fmt.Sprintf("DELETE FROM %s WHERE created_at < '%s'", table, database.FormatTime(date)) + log.Info(sql) + if err := database.Exec(sql).Error(); err != nil { + log.WithField("query", sql).Errorln(err) } } diff --git a/frontend/package.json b/frontend/package.json index f5ae2329..ecd198ff 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -11,7 +11,7 @@ "cypress:open": "cypress open", "cypress:test": "cypress run --record --key 49d99e5e-04c6-46df-beef-54b68e152a4d", "test": "start-server-and-test start http://0.0.0.0:8888/api cypress:test", - "start": "statping -port 8888" + "start": "statping -port 8888 > /dev/null 2>&1" }, "dependencies": { "@fortawesome/fontawesome-free-solid": "^5.1.0-3", diff --git a/frontend/src/assets/scss/base.scss b/frontend/src/assets/scss/base.scss index 81252a98..e692ffa6 100644 --- a/frontend/src/assets/scss/base.scss +++ b/frontend/src/assets/scss/base.scss @@ -14,6 +14,46 @@ HTML,BODY { transition: height 0.3s ease; } +.slider-info { + font-size: 9pt; + font-weight: bold; +} + +/* The slider itself */ +.slider { + -webkit-appearance: none; /* Override default CSS styles */ + appearance: none; + width: 100%; /* Full-width */ + height: 5px; /* Specified height */ + background: #d3d3d3; /* Grey background */ + outline: none; /* Remove outline */ + -webkit-transition: .2s; /* 0.2 seconds transition on hover */ + transition: opacity .2s; +} + +/* Mouse-over effects */ +.slider:hover { + opacity: 1; /* Fully shown on mouse-over */ +} + +/* The slider handle (use -webkit- (Chrome, Opera, Safari, Edge) and -moz- (Firefox) to override default look) */ +.slider::-webkit-slider-thumb { + -webkit-appearance: none; /* Override default look */ + appearance: none; + border-radius: 50%; + width: 20px; /* Set a specific slider handle width */ + height: 20px; /* Slider handle height */ + background: #4CAF50; /* Green background */ + cursor: pointer; /* Cursor on hover */ +} + +.slider::-moz-range-thumb { + width: 15px; /* Set a specific slider handle width */ + height: 15px; /* Slider handle height */ + background: #4CAF50; /* Green background */ + cursor: pointer; /* Cursor on hover */ +} + @-o-keyframes fadeIt { 0% { background-color: #f5f5f5; } 50% { background-color: #f2f2f2; } @@ -548,6 +588,9 @@ HTML,BODY { background-color: white; transition: 0.2s all; } + .switch-rd-gr input:checked + label::before { + background-color: #cd141b; + } .switch input:checked + label::before { background-color: #08d; } diff --git a/frontend/src/forms/CoreSettings.vue b/frontend/src/forms/CoreSettings.vue index dfd4b27d..6309ac69 100644 --- a/frontend/src/forms/CoreSettings.vue +++ b/frontend/src/forms/CoreSettings.vue @@ -69,8 +69,8 @@
- - + +
diff --git a/frontend/src/forms/Notifier.vue b/frontend/src/forms/Notifier.vue index 296e3ae1..5ed580ca 100644 --- a/frontend/src/forms/Notifier.vue +++ b/frontend/src/forms/Notifier.vue @@ -1,14 +1,16 @@ @@ -76,22 +79,27 @@ export default { loading: false, loadingTest: false, error: null, + success: false, saved: false, - ok: false, form: {}, } - }, - mounted() { - }, methods: { + async enableToggle() { + this.notifier.enabled = !!this.notifier.enabled + const form = { + enabled: !this.notifier.enabled, + method: this.notifier.method, + } + await Api.notifier_save(form) + }, async saveNotifier() { this.loading = true this.form.enabled = this.notifier.enabled this.form.limits = parseInt(this.notifier.limits) this.form.method = this.notifier.method this.notifier.form.forEach((f) => { - let field = f.field.toLowerCase() + let field = f.field.toLowerCase() let val = this.notifier[field] if (this.isNumeric(val)) { val = parseInt(val) @@ -99,34 +107,28 @@ export default { this.form[field] = val }); 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(() => { - this.saved = false - }, 2000) }, async testNotifier() { - this.ok = false + this.success = false this.loadingTest = true - let form = {} + this.form.method = this.notifier.method this.notifier.form.forEach((f) => { let field = f.field.toLowerCase() - let val = this.notifier[field] - if (this.isNumeric(val)) { - val = parseInt(val) - } + let val = this.notifier[field] + if (this.isNumeric(val)) { + val = parseInt(val) + } this.form[field] = val }); - this.form.enabled = this.notifier.enabled - this.form.limits = parseInt(this.notifier.limits) - this.form.method = this.notifier.method const tested = await Api.notifier_test(this.form) - if (tested === 'ok') { - this.ok = true + if (tested.success) { + this.success = true } else { - this.error = tested + this.error = tested.error } this.loadingTest = false }, diff --git a/frontend/src/forms/Service.vue b/frontend/src/forms/Service.vue index a85ef7b6..22b5ea84 100644 --- a/frontend/src/forms/Service.vue +++ b/frontend/src/forms/Service.vue @@ -1,35 +1,28 @@ @@ -227,6 +245,30 @@ } }, methods: { + updatePermalink() { + const a = 'àáâäæãåāăąçćčđďèéêëēėęěğǵḧîïíīįìłḿñńǹňôöòóœøōõőṕŕřßśšşșťțûüùúūǘůűųẃẍÿýžźż·/_,:;' + const b = 'aaaaaaaaaacccddeeeeeeeegghiiiiiilmnnnnoooooooooprrsssssttuuuuuuuuuwxyyzzz------' + const p = new RegExp(a.split('').join('|'), 'g') + + this.service.permalink = this.service.name.toLowerCase() + .replace(/\s+/g, '-') // Replace spaces with - + .replace(p, c => b.charAt(a.indexOf(c))) // Replace special characters + .replace(/&/g, '-and-') // Replace & with 'and' + .replace(/[^\w\-]+/g, '') // Remove all non-word characters + .replace(/\-\-+/g, '-') // Replace multiple - with single - + .replace(/^-+/, '') // Trim - from start of text + .replace(/-+$/, '') // Trim - from end of text + }, + stepVal(val) { + if (val > 1800) { + return 300 + } else if (val > 300) { + return 60 + } else if (val > 120) { + return 10 + } + return 1 + }, async saveService () { let s = this.service this.loading = true diff --git a/frontend/src/mixin.js b/frontend/src/mixin.js index 4326d20a..486a6174 100644 --- a/frontend/src/mixin.js +++ b/frontend/src/mixin.js @@ -3,6 +3,7 @@ const { zonedTimeToUtc, utcToZonedTime, lastDayOfMonth, subSeconds, parse, getUn import formatDistanceToNow from 'date-fns/formatDistanceToNow' import format from 'date-fns/format' import parseISO from 'date-fns/parseISO' +import addSeconds from 'date-fns/addSeconds' export default Vue.mixin({ methods: { @@ -15,6 +16,17 @@ export default Vue.mixin({ current() { return parseISO(new Date()) }, + secondsHumanize (val) { + const t2 = addSeconds(new Date(0), val) + if (val >= 60) { + let minword = "minute" + if (val >= 120) { + minword = "minutes" + } + return format(t2, "m '"+minword+"' s 'seconds'") + } + return format(t2, "s 'seconds'") + }, utc(val) { return new Date.UTC(val) }, diff --git a/frontend/src/pages/Settings.vue b/frontend/src/pages/Settings.vue index a94e0ba1..5743e8d5 100644 --- a/frontend/src/pages/Settings.vue +++ b/frontend/src/pages/Settings.vue @@ -24,7 +24,26 @@
+
Statping Links
+ + + Documentation + + + + API Documentation + + + + Changelog + + + + Statping Github Repo + + +
diff --git a/frontend/src/routes.js b/frontend/src/routes.js index 869acc28..c20f569f 100644 --- a/frontend/src/routes.js +++ b/frontend/src/routes.js @@ -40,28 +40,52 @@ const routes = [ } },{ path: 'users', - component: DashboardUsers + component: DashboardUsers, + meta: { + requiresAuth: true + } },{ path: 'services', - component: DashboardServices + component: DashboardServices, + meta: { + requiresAuth: true + } },{ path: 'create_service', - component: EditService + component: EditService, + meta: { + requiresAuth: true + } },{ path: 'edit_service/:id', - component: EditService + component: EditService, + meta: { + requiresAuth: true + } },{ path: 'messages', - component: DashboardMessages + component: DashboardMessages, + meta: { + requiresAuth: true + } },{ path: 'settings', - component: Settings + component: Settings, + meta: { + requiresAuth: true + } },{ path: 'logs', - component: Logs + component: Logs, + meta: { + requiresAuth: true + } },{ path: 'help', - component: Logs + component: Logs, + meta: { + requiresAuth: true + } }] }, { diff --git a/frontend/vue.config.js b/frontend/vue.config.js index ada6f2a4..3aad5d8e 100644 --- a/frontend/vue.config.js +++ b/frontend/vue.config.js @@ -1,4 +1,5 @@ module.exports = { + baseUrl: '/', assetsDir: 'assets', filenameHashing: false, devServer: { diff --git a/handlers/api.go b/handlers/api.go index a417d51a..a6847511 100644 --- a/handlers/api.go +++ b/handlers/api.go @@ -112,7 +112,11 @@ func apiClearCacheHandler(w http.ResponseWriter, r *http.Request) { } func sendErrorJson(err error, w http.ResponseWriter, r *http.Request, statusCode ...int) { - log.Warnln(fmt.Errorf("sending error response for %v: %v", r.URL.String(), err.Error())) + log.WithField("url", r.URL.String()). + WithField("method", r.Method). + WithField("code", statusCode). + Errorln(fmt.Errorf("sending error response for %s: %s", r.URL.String(), err.Error())) + output := apiResponse{ Status: "error", Error: err.Error(), diff --git a/handlers/notifications.go b/handlers/notifications.go index b4e7df60..7fc87b86 100644 --- a/handlers/notifications.go +++ b/handlers/notifications.go @@ -2,18 +2,24 @@ package handlers import ( "encoding/json" - "fmt" "github.com/gorilla/mux" "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" + "sort" ) func apiNotifiersHandler(w http.ResponseWriter, r *http.Request) { + var notifs []notifications.Notification notifiers := services.AllNotifiers() - returnJson(notifiers, w, r) + for _, n := range notifiers { + notif := n.Select() + notifer, _ := notifications.Find(notif.Method) + notif.UpdateFields(notifer) + notifs = append(notifs, *notif) + } + sort.Sort(notifications.NotificationOrder(notifs)) + returnJson(notifs, w, r) } func apiNotifierGetHandler(w http.ResponseWriter, r *http.Request) { @@ -24,8 +30,7 @@ func apiNotifierGetHandler(w http.ResponseWriter, r *http.Request) { sendErrorJson(err, w, r) return } - notif = notif.UpdateFields(notifer) - returnJson(notif, w, r) + returnJson(notifer, w, r) } func apiNotifierUpdateHandler(w http.ResponseWriter, r *http.Request) { @@ -36,14 +41,12 @@ func apiNotifierUpdateHandler(w http.ResponseWriter, r *http.Request) { return } - var notif *notifications.Notification decoder := json.NewDecoder(r.Body) - err = decoder.Decode(¬if) + err = decoder.Decode(¬ifer) if err != nil { sendErrorJson(err, w, r) return } - notifer = notifer.UpdateFields(notif) err = notifer.Update() if err != nil { sendErrorJson(err, w, r) @@ -54,63 +57,31 @@ func apiNotifierUpdateHandler(w http.ResponseWriter, r *http.Request) { } func testNotificationHandler(w http.ResponseWriter, r *http.Request) { - var err error - form := parseForm(r) vars := mux.Vars(r) - method := vars["method"] - enabled := form.Get("enable") - host := form.Get("host") - port := int(utils.ToInt(form.Get("port"))) - username := form.Get("username") - password := form.Get("password") - var1 := form.Get("var1") - var2 := form.Get("var2") - apiKey := form.Get("api_key") - apiSecret := form.Get("api_secret") - limits := int(utils.ToInt(form.Get("limits"))) - - notifier, err := notifications.Find(method) + notifer, err := notifications.Find(vars["notifier"]) if err != nil { - log.Errorln(fmt.Sprintf("issue saving notifier %v: %v", method, err)) sendErrorJson(err, w, r) return } - n := notifier + decoder := json.NewDecoder(r.Body) + err = decoder.Decode(¬ifer) + if err != nil { + sendErrorJson(err, w, r) + return + } - if host != "" { - n.Host = host - } - if port != 0 { - n.Port = port - } - if username != "" { - n.Username = username - } - if password != "" && password != "##########" { - n.Password = password - } - if var1 != "" { - n.Var1 = var1 - } - if var2 != "" { - n.Var2 = var2 - } - if apiKey != "" { - n.ApiKey = apiKey - } - if apiSecret != "" { - n.ApiSecret = apiSecret - } - if limits != 0 { - n.Limits = limits - } - n.Enabled = null.NewNullBool(enabled == "on") + notif := services.ReturnNotifier(notifer.Method) + err = notif.OnTest() - //err = notifications.OnTest(notifier) - //if err == nil { - // w.Write([]byte("ok")) - //} else { - // w.Write([]byte(err.Error())) - //} + resp := ¬ifierTestResp{ + Success: err == nil, + Error: err, + } + returnJson(resp, w, r) +} + +type notifierTestResp struct { + Success bool `json:"success"` + Error error `json:"error,omitempty"` } diff --git a/handlers/routes.go b/handlers/routes.go index fd8c83e5..de8d94a4 100644 --- a/handlers/routes.go +++ b/handlers/routes.go @@ -136,7 +136,7 @@ func Router() *mux.Router { api.Handle("/api/notifiers", authenticated(apiNotifiersHandler, false)).Methods("GET") api.Handle("/api/notifier/{notifier}", authenticated(apiNotifierGetHandler, false)).Methods("GET") api.Handle("/api/notifier/{notifier}", authenticated(apiNotifierUpdateHandler, false)).Methods("POST") - api.Handle("/api/notifier/{method}/test", authenticated(testNotificationHandler, false)).Methods("POST") + api.Handle("/api/notifier/{notifier}/test", authenticated(testNotificationHandler, false)).Methods("POST") // API MESSAGES Routes api.Handle("/api/messages", scoped(apiAllMessagesHandler)).Methods("GET") diff --git a/handlers/services_test.go b/handlers/services_test.go index bd1895ef..5445bd45 100644 --- a/handlers/services_test.go +++ b/handlers/services_test.go @@ -119,7 +119,7 @@ func TestApiServiceRoutes(t *testing.T) { Name: "Statping Service 1 Failure Data - 1 Hour", URL: "/api/services/1/failure_data" + startEndQuery + "&group=1h", Method: "GET", - ResponseLen: 72, + ResponseLen: 73, ExpectedStatus: 200, }, { @@ -140,7 +140,7 @@ func TestApiServiceRoutes(t *testing.T) { Name: "Statping Service 1 Failure Data", URL: "/api/services/1/failure_data" + startEndQuery, Method: "GET", - ResponseLen: 72, + ResponseLen: 73, ExpectedStatus: 200, }, { diff --git a/handlers/users.go b/handlers/users.go index f5831817..c47f3cdc 100644 --- a/handlers/users.go +++ b/handlers/users.go @@ -65,8 +65,7 @@ func apiUserDeleteHandler(w http.ResponseWriter, r *http.Request) { sendErrorJson(err, w, r) return } - err = user.Delete() - if err != nil { + if err := user.Delete(); err != nil { sendErrorJson(err, w, r) return } diff --git a/notifiers/slack.go b/notifiers/slack.go index b1d44330..dcf921d3 100644 --- a/notifiers/slack.go +++ b/notifiers/slack.go @@ -31,7 +31,7 @@ func (s *slack) Select() *notifications.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.", + Description: "Send notifications to your slack channel when a service is offline. Insert your Incoming webhook URL for your channel to receive notifications. Based on the Slack API.", Author: "Hunter Long", AuthorUrl: "https://github.com/hunterlong", Delay: time.Duration(10 * time.Second), @@ -40,9 +40,9 @@ var slacker = &slack{¬ifications.Notification{ Limits: 60, Form: []notifications.NotificationForm{{ Type: "text", - Title: "Incoming webhooker Url", - Placeholder: "Insert your slack Webhook URL here.", - SmallText: "Incoming webhooker URL from slack Apps", + Title: "Incoming Webhook Url", + Placeholder: "Insert your Slack Webhook URL here.", + SmallText: "Incoming Webhook URL from Slack Apps", DbField: "Host", Required: true, }}}, diff --git a/types/checkins/database.go b/types/checkins/database.go index 18d3ea54..6732be18 100644 --- a/types/checkins/database.go +++ b/types/checkins/database.go @@ -54,8 +54,3 @@ func (c *Checkin) Delete() error { q = db.Model(&Checkin{}).Delete(c) return q.Error() } - -//func (c *Checkin) AfterDelete() error { -// //q := dbHits.Where("checkin = ?", c.Id).Delete(&CheckinHit{}) -// return q.Error() -//} diff --git a/types/checkins/methods.go b/types/checkins/methods.go index 50fe3861..9133b300 100644 --- a/types/checkins/methods.go +++ b/types/checkins/methods.go @@ -2,12 +2,13 @@ package checkins import ( "fmt" + "github.com/statping/statping/utils" "time" ) func (c *Checkin) Expected() time.Duration { last := c.LastHit() - now := time.Now().UTC() + now := utils.Now() lastDir := now.Sub(last.CreatedAt) sub := time.Duration(c.Period() - lastDir) return sub diff --git a/types/hits/database.go b/types/hits/database.go index 158f3418..67f69027 100644 --- a/types/hits/database.go +++ b/types/hits/database.go @@ -13,18 +13,6 @@ func SetDB(database database.Database) { db = database.Model(&Hit{}) } -func Find(id int64) (*Hit, error) { - var group Hit - q := db.Where("id = ?", id).Find(&group) - return &group, q.Error() -} - -func All() []*Hit { - var hits []*Hit - db.Find(&hits) - return hits -} - func (h *Hit) Create() error { q := db.Create(h) return q.Error() diff --git a/types/notifications/database.go b/types/notifications/database.go index 80830d3e..f1362f03 100644 --- a/types/notifications/database.go +++ b/types/notifications/database.go @@ -14,12 +14,13 @@ func SetDB(database database.Database) { } func Find(method string) (*Notification, error) { - var notification Notification - q := db.Where("method = ?", method).Find(¬ification) - if ¬ification == nil { + var n Notification + q := db.Where("method = ?", method).Find(&n) + if &n == nil { return nil, errors.New("cannot find notifier") } - return ¬ification, q.Error() + n.UpdateFields(&n) + return &n, q.Error() } func (n *Notification) Create() error { @@ -33,6 +34,7 @@ func (n *Notification) Create() error { } func (n *Notification) UpdateFields(notif *Notification) *Notification { + n.Limits = notif.Limits n.Enabled = notif.Enabled n.Host = notif.Host n.Port = notif.Port @@ -46,21 +48,8 @@ func (n *Notification) UpdateFields(notif *Notification) *Notification { } func (n *Notification) Update() error { - if err := db.Update(n); err.Error() != nil { - return err.Error() - } - n.ResetQueue() - if n.Enabled.Bool { - n.Close() - n.Start() - } else { - n.Close() + if err := db.Update(n).Error(); err != nil { + return err } return nil } - -func loadAll() []*Notification { - var notifications []*Notification - db.Find(¬ifications) - return notifications -} diff --git a/types/notifications/methods.go b/types/notifications/methods.go index 067389cc..cec62d52 100644 --- a/types/notifications/methods.go +++ b/types/notifications/methods.go @@ -38,22 +38,6 @@ func (n *Notification) LastSent() time.Duration { return since } -func SelectNotifier(n *Notification) *Notification { - notif, err := Find(n.Method) - if err != nil { - log.Errorln(err) - return n - } - n.Host = notif.Host - n.Username = notif.Username - n.Password = notif.Password - n.ApiSecret = notif.ApiSecret - n.ApiKey = notif.ApiKey - n.Var1 = notif.Var1 - n.Host = notif.Var2 - return n -} - func (n *Notification) CanSend() bool { if !n.Enabled.Bool { return false @@ -87,13 +71,11 @@ func (n *Notification) GetValue(dbField string) string { case "host": return n.Host case "port": - return fmt.Sprintf("%v", n.Port) + return fmt.Sprintf("%d", n.Port) case "username": return n.Username case "password": - if n.Password != "" { - return "##########" - } + return n.Password case "var1": return n.Var1 case "var2": @@ -104,13 +86,9 @@ func (n *Notification) GetValue(dbField string) string { return n.ApiSecret case "limits": return utils.ToString(int(n.Limits)) + default: + return "" } - return "" -} - -// ResetQueue will clear the notifiers Queue -func (n *Notification) ResetQueue() { - n.Queue = nil } // start will start the go routine for the notifier queue diff --git a/types/notifications/struct.go b/types/notifications/struct.go index db5366db..17cb1575 100644 --- a/types/notifications/struct.go +++ b/types/notifications/struct.go @@ -1,42 +1,43 @@ package notifications import ( + "github.com/sirupsen/logrus" "github.com/statping/statping/types/null" "github.com/statping/statping/utils" "time" ) var ( - log = utils.Log + log = utils.Log.WithField("type", "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"` - 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:"-"` + 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:"removable"` + CreatedAt time.Time `gorm:"column:created_at" json:"created_at"` + UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at"` + 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:"-"` + Form []NotificationForm `gorm:"-" json:"form"` + Queue []RunFunc `gorm:"-" json:"-"` lastSent time.Time `gorm:"-" json:"-"` lastSentCount int `gorm:"-" json:"-"` @@ -44,6 +45,10 @@ type Notification struct { Hits notificationHits `gorm:"-" json:"-"` } +func (n *Notification) Logger() *logrus.Logger { + return log.WithField("notifier", n.Method).Logger +} + type RunFunc func(interface{}) error // NotificationForm contains the HTML fields for each variable/input you want the notifier to accept. @@ -72,3 +77,11 @@ type notificationHits struct { OnNewNotifier int64 `gorm:"-" json:"-"` OnUpdatedNotifier int64 `gorm:"-" json:"-"` } + +// NotificationOrder will reorder the services based on 'order_id' (Order) +type NotificationOrder []Notification + +// Sort interface for resorting the Notifications in order +func (c NotificationOrder) Len() int { return len(c) } +func (c NotificationOrder) Swap(i, j int) { c[int64(i)], c[int64(j)] = c[int64(j)], c[int64(i)] } +func (c NotificationOrder) Less(i, j int) bool { return c[i].Id < c[j].Id } diff --git a/types/null/marshal.go b/types/null/marshal.go index e5a286d5..345646cb 100644 --- a/types/null/marshal.go +++ b/types/null/marshal.go @@ -3,33 +3,33 @@ package null import "encoding/json" // MarshalJSON for NullInt64 -func (ni NullInt64) MarshalJSON() ([]byte, error) { - if !ni.Valid { +func (i NullInt64) MarshalJSON() ([]byte, error) { + if !i.Valid { return []byte("null"), nil } - return json.Marshal(ni.Int64) + return json.Marshal(i.Int64) } // MarshalJSON for NullFloat64 -func (ni NullFloat64) MarshalJSON() ([]byte, error) { - if !ni.Valid { +func (f NullFloat64) MarshalJSON() ([]byte, error) { + if !f.Valid { return []byte("null"), nil } - return json.Marshal(ni.Float64) + return json.Marshal(f.Float64) } // MarshalJSON for NullBool -func (nb NullBool) MarshalJSON() ([]byte, error) { - if !nb.Valid { +func (bb NullBool) MarshalJSON() ([]byte, error) { + if !bb.Valid { return []byte("null"), nil } - return json.Marshal(nb.Bool) + return json.Marshal(bb.Bool) } // MarshalJSON for NullString -func (ns NullString) MarshalJSON() ([]byte, error) { - if !ns.Valid { +func (s NullString) MarshalJSON() ([]byte, error) { + if !s.Valid { return []byte("null"), nil } - return json.Marshal(ns.String) + return json.Marshal(s.String) } diff --git a/types/null/unmarshal.go b/types/null/unmarshal.go index 7b59ecda..fdc1cff4 100644 --- a/types/null/unmarshal.go +++ b/types/null/unmarshal.go @@ -3,29 +3,29 @@ package null import "encoding/json" // Unmarshaler for NullInt64 -func (nf *NullInt64) UnmarshalJSON(b []byte) error { - err := json.Unmarshal(b, &nf.Int64) - nf.Valid = (err == nil) +func (i *NullInt64) UnmarshalJSON(b []byte) error { + err := json.Unmarshal(b, &i.Int64) + i.Valid = (err == nil) return err } // Unmarshaler for NullFloat64 -func (nf *NullFloat64) UnmarshalJSON(b []byte) error { - err := json.Unmarshal(b, &nf.Float64) - nf.Valid = (err == nil) +func (f *NullFloat64) UnmarshalJSON(b []byte) error { + err := json.Unmarshal(b, &f.Float64) + f.Valid = (err == nil) return err } // Unmarshaler for NullBool -func (nf *NullBool) UnmarshalJSON(b []byte) error { - err := json.Unmarshal(b, &nf.Bool) - nf.Valid = (err == nil) +func (bb *NullBool) UnmarshalJSON(b []byte) error { + err := json.Unmarshal(b, &bb.Bool) + bb.Valid = (err == nil) return err } // Unmarshaler for NullString -func (nf *NullString) UnmarshalJSON(b []byte) error { - err := json.Unmarshal(b, &nf.String) - nf.Valid = (err == nil) +func (s *NullString) UnmarshalJSON(b []byte) error { + err := json.Unmarshal(b, &s.String) + s.Valid = (err == nil) return err } diff --git a/types/services/database.go b/types/services/database.go index fec86a29..0bd08d4a 100644 --- a/types/services/database.go +++ b/types/services/database.go @@ -8,9 +8,10 @@ import ( "sort" ) -var log = utils.Log - -var db database.Database +var ( + db database.Database + log = utils.Log.WithField("type", "service") +) func SetDB(database database.Database) { db = database.Model(&Service{}) @@ -60,38 +61,23 @@ func (s *Service) AfterCreate() error { func (s *Service) Update() error { q := db.Update(s) - allServices[s.Id] = s - - if !s.AllowNotifications.Bool { - //for _, n := range CoreApp.Notifications { - // notif := n.(notifier.Notifier).Select() - // notif.ResetUniqueQueue(fmt.Sprintf("service_%v", s.Id)) - //} - } s.Close() s.SleepDuration = s.Duration() go ServiceCheckQueue(allServices[s.Id], true) - - //notifier.OnUpdatedService(s.Service) - return q.Error() } func (s *Service) Delete() error { s.Close() - if err := s.DeleteFailures(); err != nil { return err } if err := s.DeleteHits(); err != nil { return err } - delete(allServices, s.Id) - q := db.Model(&Service{}).Delete(s) - //notifier.OnDeletedService(s.Service) return q.Error() } @@ -111,12 +97,3 @@ func (s *Service) DeleteCheckins() error { } return nil } - -//func (s *Service) AfterDelete() error { -// -// return nil -//} - -func (s *Service) AfterFind() error { - return nil -} diff --git a/types/services/notifier.go b/types/services/notifier.go index 9043e6d3..6e9e7fbe 100644 --- a/types/services/notifier.go +++ b/types/services/notifier.go @@ -6,19 +6,21 @@ import ( ) var ( - allNotifiers []ServiceNotifier + allNotifiers = make(map[string]ServiceNotifier) ) -func AllNotifiers() []ServiceNotifier { +func AllNotifiers() map[string]ServiceNotifier { return allNotifiers } +func ReturnNotifier(method string) ServiceNotifier { + return allNotifiers[method] +} + func FindNotifier(method string) *notifications.Notification { - for _, n := range allNotifiers { - notif := n.Select() - if notif.Method == method { - return notif - } + n := allNotifiers[method] + if n != nil { + return n.Select() } return nil } diff --git a/types/services/routine.go b/types/services/routine.go index 120fb26c..7842d685 100644 --- a/types/services/routine.go +++ b/types/services/routine.go @@ -3,7 +3,6 @@ package services import ( "bytes" "fmt" - "github.com/statping/statping/types/core" "google.golang.org/grpc" "net" "net/http" @@ -31,7 +30,7 @@ func CheckServices() { // CheckQueue is the main go routine for checking a service func ServiceCheckQueue(s *Service, record bool) { s.Start() - s.Checkpoint = time.Now() + s.Checkpoint = utils.Now() s.SleepDuration = (time.Duration(s.Id) * 100) * time.Millisecond CheckLoop: @@ -56,7 +55,7 @@ CheckLoop: } func parseHost(s *Service) string { - if s.Type == "tcp" || s.Type == "udp" { + if s.Type == "tcp" || s.Type == "udp" || s.Type == "grpc" { return s.Domain } else { u, err := url.Parse(s.Domain) @@ -72,7 +71,7 @@ func dnsCheck(s *Service) (int64, error) { var err error t1 := utils.Now() host := parseHost(s) - if s.Type == "tcp" { + if s.Type == "tcp" || s.Type == "udp" || s.Type == "grpc" { _, err = net.LookupHost(host) } else { _, err = net.LookupIP(host) @@ -80,7 +79,7 @@ func dnsCheck(s *Service) (int64, error) { if err != nil { return 0, err } - t2 := time.Now() + t2 := utils.Now() subTime := t2.Sub(t1).Microseconds() return subTime, err } @@ -225,19 +224,27 @@ func CheckHttp(s *Service, record bool) *Service { timeout := time.Duration(s.Timeout) * time.Second var content []byte var res *http.Response - + var cnx string + var data *bytes.Buffer var headers []string + if s.Headers.Valid { headers = strings.Split(s.Headers.String, ",") } else { headers = nil } - if s.Method == "POST" { - content, res, err = utils.HttpRequest(s.Domain, s.Method, "application/json", headers, bytes.NewBuffer([]byte(s.PostData.String)), timeout, s.VerifySSL.Bool) + if s.PostData.String != "" { + data = bytes.NewBuffer([]byte(s.PostData.String)) } else { - content, res, err = utils.HttpRequest(s.Domain, s.Method, nil, headers, nil, timeout, s.VerifySSL.Bool) + data = bytes.NewBuffer(nil) } + + if s.Method == "POST" { + cnx = "application/json" + } + + content, res, err = utils.HttpRequest(s.Domain, s.Method, cnx, headers, data, timeout, s.VerifySSL.Bool) if err != nil { if record { recordFailure(s, fmt.Sprintf("HTTP Error %v", err)) @@ -295,7 +302,8 @@ func recordSuccess(s *Service) { } func AddNotifier(n ServiceNotifier) { - allNotifiers = append(allNotifiers, n) + notif := n.Select() + allNotifiers[notif.Method] = n } func sendSuccess(s *Service) { @@ -307,17 +315,12 @@ func sendSuccess(s *Service) { return } - // dont send notification if server recently started (60 seconds) - if core.App.Started.Add(60 * time.Second).After(utils.Now()) { - s.SuccessNotified = true - 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) + notif.Logger().Errorln(err) } s.UserNotified = true s.SuccessNotified = true @@ -367,7 +370,7 @@ func sendFailure(s *Service, f *failures.Failure) { if notif.CanSend() { log.Infof("Sending Failure notification to: %s!", notif.Method) if err := n.OnFailure(s, f); err != nil { - log.Errorln(err) + notif.Logger().WithField("failure", f.Issue).Errorln(err) } s.UserNotified = true s.SuccessNotified = true diff --git a/types/users/auth.go b/types/users/auth.go index 663079a3..b1f3294e 100644 --- a/types/users/auth.go +++ b/types/users/auth.go @@ -15,7 +15,7 @@ func AuthUser(username, password string) (*User, bool) { log.Warnln(fmt.Errorf("user %v not found", username)) return nil, false } - if CheckHash(password, user.Password) { + if checkHash(password, user.Password) { user.UpdatedAt = time.Now().UTC() user.Update() return user, true @@ -23,8 +23,8 @@ func AuthUser(username, password string) (*User, bool) { return nil, false } -// CheckHash returns true if the password matches with a hashed bcrypt password -func CheckHash(password, hash string) bool { +// checkHash returns true if the password matches with a hashed bcrypt password +func checkHash(password, hash string) bool { err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) return err == nil } diff --git a/utils/log.go b/utils/log.go index 7db2fef0..c760f7df 100644 --- a/utils/log.go +++ b/utils/log.go @@ -21,9 +21,40 @@ var ( LastLines []*logRow LockLines sync.Mutex VerboseMode int + version string ) -const logFilePath = "/logs/statping.log" +const ( + logFilePath = "/logs/statping.log" + errorReporter = "https://ddf2784201134d51a20c3440e222cebe@sentry.statping.com/4" +) + +func SentryInit(v string) { + if v == "" { + v = "development" + } + version = v + errorEnv := Getenv("GO_ENV", "production").(string) + if err := sentry.Init(sentry.ClientOptions{ + Dsn: errorReporter, + Environment: errorEnv, + Release: v, + }); err != nil { + Log.Errorln(err) + } +} + +func SentryErr(err error) { + sentry.CaptureException(err) +} + +func SentryLogEntry(entry *Logger.Entry) { + e := sentry.NewEvent() + e.Message = entry.Message + e.Release = version + e.Contexts = entry.Data + sentry.CaptureEvent(e) +} type hook struct { Entries []Logger.Entry @@ -32,6 +63,9 @@ type hook struct { func (t *hook) Fire(e *Logger.Entry) error { pushLastLine(e.Message) + if e.Level == Logger.ErrorLevel { + SentryLogEntry(e) + } return nil } @@ -120,8 +154,6 @@ func InitLogs() error { }) checkVerboseMode() - sentry.CaptureMessage("It works!") - LastLines = make([]*logRow, 0) return nil } @@ -154,6 +186,7 @@ func CloseLogs() { ljLogger.Rotate() Log.Writer().Close() ljLogger.Close() + sentry.Flush(5 * time.Second) } func pushLastLine(line interface{}) { diff --git a/utils/utils.go b/utils/utils.go index 81c6d2d8..65ec0b73 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -44,6 +44,24 @@ func init() { checkVerboseMode() } +type env struct { + data interface{} +} + +func GetenvAs(key string, defaultValue interface{}) *env { + return &env{ + data: Getenv(key, defaultValue), + } +} + +func (e *env) Duration() time.Duration { + t, err := time.ParseDuration(e.data.(string)) + if err != nil { + Log.Errorln(err) + } + return t +} + func Getenv(key string, defaultValue interface{}) interface{} { if val, ok := os.LookupEnv(key); ok { if val != "" { @@ -216,7 +234,7 @@ func DurationReadable(d time.Duration) string { func HttpRequest(url, method string, content interface{}, headers []string, body io.Reader, timeout time.Duration, verifySSL bool) ([]byte, *http.Response, error) { var err error var req *http.Request - t1 := time.Now() + t1 := Now() if req, err = http.NewRequest(method, url, body); err != nil { httpMetric.Errors++ return nil, nil, err @@ -275,7 +293,7 @@ func HttpRequest(url, method string, content interface{}, headers []string, body contents, err := ioutil.ReadAll(resp.Body) // record HTTP metrics - t2 := time.Now().Sub(t1).Milliseconds() + t2 := Now().Sub(t1).Milliseconds() httpMetric.Requests++ httpMetric.Milliseconds += t2 / httpMetric.Requests httpMetric.Bytes += int64(len(contents)) diff --git a/version.txt b/version.txt index 2721a4b0..839980d4 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -0.90.21 +0.90.22