From 5a042e3c6623f290be13ef932d3040d90b9cb2d7 Mon Sep 17 00:00:00 2001 From: hunterlong Date: Sun, 21 Jun 2020 21:51:40 -0700 Subject: [PATCH 01/16] metrics now include service name for each service metric --- types/failures/database.go | 1 - types/hits/database.go | 1 - types/metrics/services.go | 2 +- types/services/routine.go | 16 +++++++++------- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/types/failures/database.go b/types/failures/database.go index e2cd7d1b..e3aad286 100644 --- a/types/failures/database.go +++ b/types/failures/database.go @@ -28,7 +28,6 @@ func (f *Failure) AfterDelete() { } func (f *Failure) AfterCreate() { - metrics.Inc("failure", f.Service) metrics.Query("failure", "create") } diff --git a/types/hits/database.go b/types/hits/database.go index 3268279f..5c3ce19f 100644 --- a/types/hits/database.go +++ b/types/hits/database.go @@ -27,7 +27,6 @@ func (h *Hit) AfterDelete() { } func (h *Hit) AfterCreate() { - metrics.Inc("success", h.Service) metrics.Query("hit", "create") } diff --git a/types/metrics/services.go b/types/metrics/services.go index 24f5908b..a0b43439 100644 --- a/types/metrics/services.go +++ b/types/metrics/services.go @@ -10,7 +10,7 @@ var ( Name: "service_online", Help: "If service is online", }, - []string{"service", "name", "type"}, + []string{"service", "type"}, ) // service failures diff --git a/types/services/routine.go b/types/services/routine.go index 3162d641..30eb2b8d 100644 --- a/types/services/routine.go +++ b/types/services/routine.go @@ -88,7 +88,7 @@ func isIPv6(address string) bool { // checkIcmp will send a ICMP ping packet to the service func CheckIcmp(s *Service, record bool) (*Service, error) { defer s.updateLastCheck() - timer := prometheus.NewTimer(metrics.ServiceTimer(s.Id)) + timer := prometheus.NewTimer(metrics.ServiceTimer(s.Name)) defer timer.ObserveDuration() if err := utils.Ping(s.Domain, s.Timeout); err != nil { @@ -105,7 +105,7 @@ func CheckIcmp(s *Service, record bool) (*Service, error) { // CheckGrpc will check a gRPC service func CheckGrpc(s *Service, record bool) (*Service, error) { defer s.updateLastCheck() - timer := prometheus.NewTimer(metrics.ServiceTimer(s.Id)) + timer := prometheus.NewTimer(metrics.ServiceTimer(s.Name)) defer timer.ObserveDuration() dnsLookup, err := dnsCheck(s) @@ -152,7 +152,7 @@ func CheckGrpc(s *Service, record bool) (*Service, error) { // checkTcp will check a TCP service func CheckTcp(s *Service, record bool) (*Service, error) { defer s.updateLastCheck() - timer := prometheus.NewTimer(metrics.ServiceTimer(s.Id)) + timer := prometheus.NewTimer(metrics.ServiceTimer(s.Name)) defer timer.ObserveDuration() dnsLookup, err := dnsCheck(s) @@ -219,7 +219,7 @@ func (s *Service) updateLastCheck() { // checkHttp will check a HTTP service func CheckHttp(s *Service, record bool) (*Service, error) { defer s.updateLastCheck() - timer := prometheus.NewTimer(metrics.ServiceTimer(s.Id)) + timer := prometheus.NewTimer(metrics.ServiceTimer(s.Name)) defer timer.ObserveDuration() dnsLookup, err := dnsCheck(s) @@ -285,7 +285,7 @@ func CheckHttp(s *Service, record bool) (*Service, error) { s.LastResponse = string(content) s.LastStatusCode = res.StatusCode - metrics.Gauge("status_code", float64(res.StatusCode), s.Id) + metrics.Gauge("status_code", float64(res.StatusCode), s.Name) if s.Expected.String != "" { match, err := regexp.MatchString(s.Expected.String, string(content)) @@ -329,7 +329,8 @@ func recordSuccess(s *Service) { fmt.Sprintf("Service #%d '%v' Successful Response: %s | Lookup in: %s | Online: %v | Interval: %d seconds", s.Id, s.Name, humanMicro(hit.Latency), humanMicro(hit.PingTime), s.Online, s.Interval)) s.LastLookupTime = hit.PingTime s.LastLatency = hit.Latency - metrics.Gauge("online", 1., s.Id, s.Name, s.Type) + metrics.Gauge("online", 1., s.Name, s.Type) + metrics.Inc("success", s.Name) sendSuccess(s) s.SuccessNotified = true } @@ -383,7 +384,8 @@ func recordFailure(s *Service, issue string) { s.Online = false s.SuccessNotified = false s.DownText = s.DowntimeText() - metrics.Gauge("online", 0., s.Id, s.Name, s.Type) + metrics.Gauge("online", 0., s.Name, s.Type) + metrics.Inc("failure", s.Name) sendFailure(s, fail) } From 04a04ad7152313ba9e28a9621fccc03ffafaeea0 Mon Sep 17 00:00:00 2001 From: hunterlong Date: Mon, 22 Jun 2020 00:13:57 -0700 Subject: [PATCH 02/16] added Switch and List inputs for notifiers, added more variable examples --- .../src/components/Dashboard/Variables.vue | 28 +++++++++++++++++-- frontend/src/forms/Notifier.vue | 26 ++++++++++++++--- frontend/src/forms/Setup.vue | 13 +++++++-- notifiers/email.go | 2 +- notifiers/pushover.go | 16 ++++++++++- notifiers/twilio.go | 4 +-- notifiers/webhook.go | 3 +- 7 files changed, 78 insertions(+), 14 deletions(-) diff --git a/frontend/src/components/Dashboard/Variables.vue b/frontend/src/components/Dashboard/Variables.vue index 57393818..3dc1ec9d 100644 --- a/frontend/src/components/Dashboard/Variables.vue +++ b/frontend/src/components/Dashboard/Variables.vue @@ -38,8 +38,16 @@ 8080 - {{"\{\{.Service.DowntimeAgo\}\}"}} - 35 minutes ago + {{"\{\{.Service.Downtime.Human\}\}"}} + 2 minutes + + + {{"\{\{.Service.Uptime.Human\}\}"}} + 13 hours + + + {{"\{\{.Service.Online\}\}"}} + true/false {{"\{\{.Service.LastStatusCode\}\}"}} @@ -49,6 +57,22 @@ {{"\{\{.Service.FailuresLast24Hours\}\}"}} 38 + + {{"\{\{.Service.LastOnline\}\}"}} + 2020-11-05T13:15:30Z + + + {{"\{\{.Service.LastOffline\}\}"}} + 2020-10-01T13:15:30Z + + + {{"\{\{.Service.Online24Hours\}\}"}} + 0.99 + + + {{"\{\{.Service.Online7Days\}\}"}} + 0.97 + Additional variables within the Service struct diff --git a/frontend/src/forms/Notifier.vue b/frontend/src/forms/Notifier.vue index a071dbeb..cddf47a6 100644 --- a/frontend/src/forms/Notifier.vue +++ b/frontend/src/forms/Notifier.vue @@ -14,7 +14,16 @@
- + + + + + + + +
@@ -155,7 +164,7 @@ export default { theme: 'neat', mode: "mymode", lineWrapping: true, - json: true, + json: this.notifier.data_type === "json", autoRefresh: true, mime: this.notifier.data_type === "json" ? "application/json" : "text/plain" }, @@ -166,6 +175,9 @@ export default { }, methods: { + formVisible(want, form) { + return !!want.includes(form.type); + }, visible(isVisible, entry) { if (isVisible) { this.$refs.cmfailure.codemirror.refresh() @@ -173,13 +185,19 @@ export default { } }, onCmSuccessReady(cm) { - this.success_data = beautify(this.notifier.success_data, this.beautifySettings) + this.success_data = this.notifier.success_data + if (this.notifier.data_type === "json") { + this.success_data = beautify(this.notifier.success_data, this.beautifySettings) + } setTimeout(function() { cm.refresh(); },1); }, onCmFailureReady(cm) { - this.failure_data = beautify(this.notifier.failure_data, this.beautifySettings) + this.failure_data = this.notifier.failure_data + if (this.notifier.data_type === "json") { + this.failure_data = beautify(this.notifier.failure_data, this.beautifySettings) + } setTimeout(function() { cm.refresh(); },1); diff --git a/frontend/src/forms/Setup.vue b/frontend/src/forms/Setup.vue index 8f5fc45c..5232b4e0 100644 --- a/frontend/src/forms/Setup.vue +++ b/frontend/src/forms/Setup.vue @@ -74,12 +74,12 @@
- +
- +
@@ -100,6 +100,7 @@
+ Both passwords should match
@@ -108,7 +109,7 @@
-
+
@@ -145,6 +146,7 @@ error: null, loading: false, disabled: true, + passnomatch: false, setup: { language: "en", db_connection: "sqlite", @@ -186,6 +188,11 @@ canSubmit() { this.error = null const s = this.setup + if (s.confirm_password.length > 0 && s.confirm_password !== s.password) { + this.passnomatch = true + } else { + this.passnomatch = false + } if (s.db_connection !== 'sqlite') { if (!s.db_host || !s.db_port || !s.db_user || !s.db_password || !s.db_database) { this.disabled = true diff --git a/notifiers/email.go b/notifiers/email.go index 1f7b5d03..37c1a291 100644 --- a/notifiers/email.go +++ b/notifiers/email.go @@ -70,7 +70,7 @@ var email = &emailer{¬ifications.Notification{ Type: "switch", Title: "Disable TLS/SSL", Placeholder: "", - SmallText: "To Disable TLS/SSL insert 'true'", + SmallText: "Enabling this will set Insecure Skip Verify to true", DbField: "api_key", }}}, } diff --git a/notifiers/pushover.go b/notifiers/pushover.go index f480a3ad..bb45a6a3 100644 --- a/notifiers/pushover.go +++ b/notifiers/pushover.go @@ -41,7 +41,7 @@ var Pushover = &pushover{¬ifications.Notification{ Form: []notifications.NotificationForm{{ Type: "text", Title: "User Token", - Placeholder: "Insert your device's Pushover Token", + Placeholder: "Insert your Pushover User Token", DbField: "api_key", Required: true, }, { @@ -50,6 +50,20 @@ var Pushover = &pushover{¬ifications.Notification{ Placeholder: "Create an Application and insert the API Key here", DbField: "api_secret", Required: true, + }, { + Type: "list", + Title: "Priority", + Placeholder: "Set the notification priority level", + DbField: "Var1", + Required: true, + ListOptions: []string{"Lowest", "Low", "Normal", "High", "Emergency"}, + }, { + Type: "list", + Title: "Notification Sound", + Placeholder: "Choose a sound for this Pushover notification", + DbField: "Var2", + Required: true, + ListOptions: []string{"none", "pushover", "bike", "bugle", "cashregister", "classical", "cosmic", "falling", "gamelan", "incoming", "intermissioon", "magic", "mechanical", "painobar", "siren", "spacealarm", "tugboat", "alien", "climb", "persistent", "echo", "updown"}, }, }}, } diff --git a/notifiers/twilio.go b/notifiers/twilio.go index da4272f3..2892fd1b 100644 --- a/notifiers/twilio.go +++ b/notifiers/twilio.go @@ -50,13 +50,13 @@ var Twilio = &twilio{¬ifications.Notification{ DbField: "api_secret", Required: true, }, { - Type: "text", + Type: "number", Title: "SMS to Phone Number", Placeholder: "18555555555", DbField: "Var1", Required: true, }, { - Type: "text", + Type: "number", Title: "From Phone Number", Placeholder: "18555555555", DbField: "Var2", diff --git a/notifiers/webhook.go b/notifiers/webhook.go index 30a43b6e..e2ab9327 100644 --- a/notifiers/webhook.go +++ b/notifiers/webhook.go @@ -44,12 +44,13 @@ var Webhook = &webhooker{¬ifications.Notification{ DbField: "Host", Required: true, }, { - Type: "text", + Type: "list", Title: "HTTP Method", Placeholder: "POST", SmallText: "Choose a HTTP method for example: GET, POST, DELETE, or PATCH.", DbField: "Var1", Required: true, + ListOptions: []string{"GET", "POST", "PATCH", "DELETE"}, }, { Type: "text", Title: "Content Type", From 1044337ec610b3f567c3763e98a84ac7fa2ccd18 Mon Sep 17 00:00:00 2001 From: hunterlong Date: Mon, 22 Jun 2020 00:21:26 -0700 Subject: [PATCH 03/16] add error for theme editor --- frontend/src/components/Dashboard/ThemeEditor.vue | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/Dashboard/ThemeEditor.vue b/frontend/src/components/Dashboard/ThemeEditor.vue index 5d402f16..bf0f6d1a 100644 --- a/frontend/src/components/Dashboard/ThemeEditor.vue +++ b/frontend/src/components/Dashboard/ThemeEditor.vue @@ -107,9 +107,14 @@ }, async createAssets() { this.pending = true - const resp = await Api.theme_generate(true) + let resp + try { + resp = await Api.theme_generate(true) + } catch(e) { + this.error = e.response.data.error + } this.pending = false - await this.fetchTheme() + await this.fetchTheme() }, async deleteAssets() { this.pending = true From 7be131d0cf6a43a83334232e8b9ead5c6f655b53 Mon Sep 17 00:00:00 2001 From: hunterlong Date: Wed, 24 Jun 2020 18:58:21 -0700 Subject: [PATCH 04/16] added http headers for outgoing requests, removed mobile notifier inputs, added Pushover notifier fields --- frontend/src/forms/Notifier.vue | 19 +++++++++++++++---- notifiers/pushover.go | 21 +++++++++++++++++++++ notifiers/webhook.go | 10 +++++----- utils/env.go | 2 +- utils/log.go | 8 ++++---- utils/utils.go | 4 ++++ 6 files changed, 50 insertions(+), 14 deletions(-) diff --git a/frontend/src/forms/Notifier.vue b/frontend/src/forms/Notifier.vue index cddf47a6..53cd3fef 100644 --- a/frontend/src/forms/Notifier.vue +++ b/frontend/src/forms/Notifier.vue @@ -12,7 +12,12 @@

-

+
+ + Scan this QR Code on the Statping Mobile App for quick setup +
+ +
@@ -171,9 +176,15 @@ export default { beautifySettings: { indent_size: 2, space_in_empty_paren: true }, } }, - computed: { - - }, + computed: { + core() { + return this.$store.getters.core + }, + qrcode() { + const u = `statping://setup?domain=${this.core.domain}&api=${this.core.api_secret}` + return "https://chart.googleapis.com/chart?chs=500x500&cht=qr&chl=" + encodeURIComponent(u) + } + }, methods: { formVisible(want, form) { return !!want.includes(form.type); diff --git a/notifiers/pushover.go b/notifiers/pushover.go index bb45a6a3..1f295e13 100644 --- a/notifiers/pushover.go +++ b/notifiers/pushover.go @@ -68,12 +68,33 @@ var Pushover = &pushover{¬ifications.Notification{ }}, } +func priority(val string) string { + switch strings.ToLower(val) { + case "lowest": + return "-2" + case "low": + return "-1" + case "normal": + return "0" + case "high": + return "1" + case "emergency": + return "2" + default: + return "1" + } +} + // Send will send a HTTP Post to the Pushover API. It accepts type: string func (t *pushover) sendMessage(message string) (string, error) { v := url.Values{} v.Set("token", t.ApiSecret) v.Set("user", t.ApiKey) v.Set("message", message) + v.Set("priority", priority(t.Var1)) + if t.Var2 != "" { + v.Set("sound", t.Var2) + } rb := strings.NewReader(v.Encode()) content, _, err := utils.HttpRequest(pushoverUrl, "POST", "application/x-www-form-urlencoded", nil, rb, time.Duration(10*time.Second), true, nil) diff --git a/notifiers/webhook.go b/notifiers/webhook.go index e2ab9327..b1ff300d 100644 --- a/notifiers/webhook.go +++ b/notifiers/webhook.go @@ -31,9 +31,9 @@ var Webhook = &webhooker{¬ifications.Notification{ Author: "Hunter Long", AuthorUrl: "https://github.com/hunterlong", Icon: "fas fa-code-branch", - Delay: time.Duration(1 * time.Second), - SuccessData: `{"id": {{.Service.Id}}, "online": true}`, - FailureData: `{"id": {{.Service.Id}}, "online": false}`, + Delay: time.Duration(3 * time.Second), + SuccessData: `{"id": "{{.Service.Id}}", "online": true}`, + FailureData: `{"id": "{{.Service.Id}}", "online": false}`, DataType: "json", Limits: 180, Form: []notifications.NotificationForm{{ @@ -83,8 +83,7 @@ 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) - var buf *bytes.Buffer - buf = bytes.NewBuffer(nil) + buf := bytes.NewBuffer(nil) if w.Var2 != "" { buf = bytes.NewBuffer([]byte(body)) } @@ -103,6 +102,7 @@ func (w *webhooker) sendHttpWebhook(body string) (*http.Response, error) { req.Header.Add("Content-Type", w.ApiKey) } req.Header.Set("User-Agent", "Statping") + req.Header.Set("Statping-Version", utils.Version) resp, err := client.Do(req) if err != nil { return nil, err diff --git a/utils/env.go b/utils/env.go index 295760c9..10b9ab08 100644 --- a/utils/env.go +++ b/utils/env.go @@ -24,7 +24,7 @@ func InitEnvs() { Log.Errorln(err) defaultDir = "." } - Params.Set("VERSION", version) + Params.Set("VERSION", Version) Params.SetDefault("DISABLE_HTTP", false) Params.SetDefault("STATPING_DIR", defaultDir) Params.SetDefault("GO_ENV", "production") diff --git a/utils/log.go b/utils/log.go index a6aaf5ea..35da0913 100644 --- a/utils/log.go +++ b/utils/log.go @@ -21,7 +21,7 @@ var ( LastLines []*logRow LockLines sync.Mutex VerboseMode int - version string + Version string allowReports bool ) @@ -36,7 +36,7 @@ func SentryInit(v *string, allow bool) { if *v == "" { *v = "development" } - version = *v + Version = *v } goEnv := Params.GetString("GO_ENV") allowReports := Params.GetBool("ALLOW_REPORTS") @@ -44,7 +44,7 @@ func SentryInit(v *string, allow bool) { if err := sentry.Init(sentry.ClientOptions{ Dsn: errorReporter, Environment: goEnv, - Release: version, + Release: Version, AttachStacktrace: true, }); err != nil { Log.Errorln(err) @@ -63,7 +63,7 @@ func SentryErr(err error) { func SentryLogEntry(entry *Logger.Entry) { e := sentry.NewEvent() e.Message = entry.Message - e.Release = version + e.Release = Version e.Contexts = entry.Data sentry.CaptureEvent(e) } diff --git a/utils/utils.go b/utils/utils.go index bd293b37..353c4be7 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -217,6 +217,10 @@ func HttpRequest(url, method string, content interface{}, headers []string, body } } } + + req.Header.Set("User-Agent", "Statping") + req.Header.Set("Statping-Version", Version) + var resp *http.Response dialer := &net.Dialer{ From 6f0f712bad0ec89a10cc06a8ed4a7de9fbc27bb4 Mon Sep 17 00:00:00 2001 From: hunterlong Date: Thu, 25 Jun 2020 17:15:59 -0700 Subject: [PATCH 05/16] oauth fixes, login cookies fixes --- frontend/src/API.js | 12 +-- frontend/src/components/Dashboard/TopNav.vue | 2 +- frontend/src/forms/Login.vue | 5 +- frontend/src/forms/OAuth.vue | 21 ++--- frontend/src/routes.js | 2 +- frontend/src/store.js | 14 +-- frontend/vue.config.js | 15 ++- handlers/api.go | 11 ++- handlers/handlers.go | 8 +- handlers/jwt.go | 10 +- handlers/oauth.go | 29 +----- handlers/oauth_github.go | 99 ++++++++++++++++++++ handlers/routes.go | 4 +- 13 files changed, 157 insertions(+), 75 deletions(-) create mode 100644 handlers/oauth_github.go diff --git a/frontend/src/API.js b/frontend/src/API.js index 1adbbacc..aed0c464 100644 --- a/frontend/src/API.js +++ b/frontend/src/API.js @@ -9,7 +9,7 @@ const errorReporter = "https://bed4d75404924cb3a799e370733a1b64@sentry.statping. class Api { constructor() { - + axios.defaults.withCredentials = true } async oauth() { @@ -251,17 +251,13 @@ class Api { } token() { - const tk = $cookies.get(tokenKey) - if (!tk) { - return {admin: false}; - } - return tk; + return $cookies.get(tokenKey); } authToken() { const tk = $cookies.get(tokenKey) - if (tk.token) { - return {'Authorization': 'Bearer ' + tk.token}; + if (tk) { + return {'Authorization': 'Bearer ' + tk}; } else { return {}; } diff --git a/frontend/src/components/Dashboard/TopNav.vue b/frontend/src/components/Dashboard/TopNav.vue index 8b887f89..3129592a 100644 --- a/frontend/src/components/Dashboard/TopNav.vue +++ b/frontend/src/components/Dashboard/TopNav.vue @@ -52,7 +52,7 @@ this.$store.commit('setHasAllData', false) this.$store.commit('setToken', null) this.$store.commit('setAdmin', false) - this.$cookies.remove("statping_auth") + // this.$cookies.remove("statping_auth") await this.$router.push('/logout') } } diff --git a/frontend/src/forms/Login.vue b/frontend/src/forms/Login.vue index dd0fce1d..796f02ec 100644 --- a/frontend/src/forms/Login.vue +++ b/frontend/src/forms/Login.vue @@ -80,9 +80,8 @@ if (auth.error) { this.error = true } else if (auth.token) { - const u = {username: this.username, admin: auth.admin, token: auth.token} - this.$cookies.set("statping_auth", JSON.stringify(u)) - this.$store.dispatch('loadAdmin') + // this.$cookies.set("statping_auth", auth.token) + await this.$store.dispatch('loadAdmin') this.$store.commit('setAdmin', auth.admin) this.$router.push('/dashboard') } diff --git a/frontend/src/forms/OAuth.vue b/frontend/src/forms/OAuth.vue index ca0c7a5d..46933705 100644 --- a/frontend/src/forms/OAuth.vue +++ b/frontend/src/forms/OAuth.vue @@ -1,6 +1,5 @@ @@ -95,6 +99,9 @@ }, Googlelogin() { window.location = `https://accounts.google.com/signin/oauth?client_id=${this.oauth.google_client_id}&redirect_uri=${this.core.domain}/oauth/google&response_type=code&scope=https://www.googleapis.com/auth/userinfo.profile+https://www.googleapis.com/auth/userinfo.email` + }, + Customlogin() { + window.location = `${this.oauth.custom_endpoint_auth}?client_id=${this.oauth.custom_client_id}&redirect_uri=${this.core.domain}/oauth/custom${this.oauth.custom_scopes !== "" ? "&scope="+this.oauth.custom_scopes : "" }` } } } diff --git a/frontend/src/forms/OAuth.vue b/frontend/src/forms/OAuth.vue index acaff1d7..8c3e3904 100644 --- a/frontend/src/forms/OAuth.vue +++ b/frontend/src/forms/OAuth.vue @@ -168,6 +168,70 @@
+
+
Custom oAuth Settings
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ + Optional comma delimited list of oauth scopes +
+
+
+ +
+ + + + +
+
+
+ +
+
+ +
+ +
+
+
+
+
+
+ + @@ -191,6 +255,7 @@ slack_enabled: false, github_enabled: false, local_enabled: false, + custom_enabled: false, loading: false, oauth: { gh_client_id: "", @@ -204,7 +269,13 @@ slack_client_id: "", slack_client_secret: "", slack_team: "", - slack_users: "" + slack_users: "", + custom_name: "", + custom_client_id: "", + custom_client_secret: "", + custom_endpoint_auth: "", + custom_endpoint_token: "", + custom_scopes: "", } } }, @@ -214,6 +285,7 @@ this.github_enabled = this.has('github') this.google_enabled = this.has('google') this.slack_enabled = this.has('slack') + this.custom_enabled = this.has('custom') }, methods: { providers() { @@ -230,6 +302,9 @@ if (this.slack_enabled) { providers.push("slack") } + if (this.custom_enabled) { + providers.push("custom") + } return providers.join(",") }, has(val) { diff --git a/handlers/oauth.go b/handlers/oauth.go index ba27d012..f6a4aa8c 100644 --- a/handlers/oauth.go +++ b/handlers/oauth.go @@ -29,6 +29,8 @@ func oauthHandler(w http.ResponseWriter, r *http.Request) { oauth, err = githubOAuth(r) case "slack": oauth, err = slackOAuth(r) + case "custom": + oauth, err = customOAuth(r) } if err != nil { diff --git a/handlers/oauth_custom.go b/handlers/oauth_custom.go new file mode 100644 index 00000000..fb595617 --- /dev/null +++ b/handlers/oauth_custom.go @@ -0,0 +1,40 @@ +package handlers + +import ( + "github.com/statping/statping/types/core" + "github.com/statping/statping/types/errors" + "golang.org/x/oauth2" + "net/http" + "strings" +) + +func customOAuth(r *http.Request) (*oAuth, error) { + auth := core.App.OAuth + code := r.URL.Query().Get("code") + + scopes := strings.Split(auth.CustomScopes, ",") + + config := &oauth2.Config{ + ClientID: auth.CustomClientID, + ClientSecret: auth.CustomClientSecret, + Endpoint: oauth2.Endpoint{ + AuthURL: auth.CustomEndpointAuth, + TokenURL: auth.CustomEndpointToken, + }, + RedirectURL: core.App.Domain + basePath + "oauth/custom", + Scopes: scopes, + } + + gg, err := config.Exchange(r.Context(), code) + if err != nil { + return nil, err + } + + if !gg.Valid() { + return nil, errors.New("oauth token is not valid") + } + + return &oAuth{ + Token: gg, + }, nil +} diff --git a/handlers/oauth_github.go b/handlers/oauth_github.go index d35d3117..fe753938 100644 --- a/handlers/oauth_github.go +++ b/handlers/oauth_github.go @@ -20,6 +20,7 @@ func githubOAuth(r *http.Request) (*oAuth, error) { ClientID: auth.GithubClientID, ClientSecret: auth.GithubClientSecret, Endpoint: github.Endpoint, + RedirectURL: core.App.Domain + basePath + "oauth/github", } gg, err := config.Exchange(r.Context(), code) diff --git a/handlers/oauth_google.go b/handlers/oauth_google.go index 0f866fcf..d3f81edd 100644 --- a/handlers/oauth_google.go +++ b/handlers/oauth_google.go @@ -20,7 +20,7 @@ func googleOAuth(r *http.Request) (*oAuth, error) { ClientID: auth.GoogleClientID, ClientSecret: auth.GoogleClientSecret, Endpoint: google.Endpoint, - RedirectURL: core.App.Domain + "/oauth/google", + RedirectURL: core.App.Domain + basePath + "oauth/google", } gg, err := config.Exchange(r.Context(), code) diff --git a/types/core/samples.go b/types/core/samples.go index 700cc848..15810248 100644 --- a/types/core/samples.go +++ b/types/core/samples.go @@ -28,6 +28,8 @@ func Samples() error { apiSecret = utils.RandomString(32) } + oauth := OAuth{Providers: "local"} + core := &Core{ Name: utils.Params.GetString("NAME"), Description: utils.Params.GetString("DESCRIPTION"), @@ -38,6 +40,7 @@ func Samples() error { Footer: null.NewNullString(""), MigrationId: utils.Now().Unix(), Language: utils.Params.GetString("LANGUAGE"), + OAuth: oauth, } return core.Create() diff --git a/types/core/struct.go b/types/core/struct.go index fdcc6c50..88334f3b 100644 --- a/types/core/struct.go +++ b/types/core/struct.go @@ -43,18 +43,24 @@ type Core struct { } type OAuth struct { - Providers string `gorm:"column:oauth_providers;" json:"oauth_providers"` - GithubClientID string `gorm:"column:gh_client_id" json:"gh_client_id"` - GithubClientSecret string `gorm:"column:gh_client_secret" json:"gh_client_secret" scope:"admin"` - GithubUsers string `gorm:"column:gh_users" json:"gh_users" scope:"admin"` - GithubOrgs string `gorm:"column:gh_orgs" json:"gh_orgs" scope:"admin"` - GoogleClientID string `gorm:"column:google_client_id" json:"google_client_id"` - GoogleClientSecret string `gorm:"column:google_client_secret" json:"google_client_secret" scope:"admin"` - GoogleUsers string `gorm:"column:google_users" json:"google_users" scope:"admin"` - SlackClientID string `gorm:"column:slack_client_id" json:"slack_client_id"` - SlackClientSecret string `gorm:"column:slack_client_secret" json:"slack_client_secret" scope:"admin"` - SlackTeam string `gorm:"column:slack_team" json:"slack_team" scope:"admin"` - SlackUsers string `gorm:"column:slack_users" json:"slack_users" scope:"admin"` + Providers string `gorm:"column:oauth_providers;" json:"oauth_providers"` + GithubClientID string `gorm:"column:gh_client_id" json:"gh_client_id"` + GithubClientSecret string `gorm:"column:gh_client_secret" json:"gh_client_secret" scope:"admin"` + GithubUsers string `gorm:"column:gh_users" json:"gh_users" scope:"admin"` + GithubOrgs string `gorm:"column:gh_orgs" json:"gh_orgs" scope:"admin"` + GoogleClientID string `gorm:"column:google_client_id" json:"google_client_id"` + GoogleClientSecret string `gorm:"column:google_client_secret" json:"google_client_secret" scope:"admin"` + GoogleUsers string `gorm:"column:google_users" json:"google_users" scope:"admin"` + SlackClientID string `gorm:"column:slack_client_id" json:"slack_client_id"` + SlackClientSecret string `gorm:"column:slack_client_secret" json:"slack_client_secret" scope:"admin"` + SlackTeam string `gorm:"column:slack_team" json:"slack_team" scope:"admin"` + SlackUsers string `gorm:"column:slack_users" json:"slack_users" scope:"admin"` + CustomName string `gorm:"column:custom_name" json:"custom_name"` + CustomClientID string `gorm:"column:custom_client_id" json:"custom_client_id"` + CustomClientSecret string `gorm:"column:custom_client_secret" json:"custom_client_secret" scope:"admin"` + CustomEndpointAuth string `gorm:"column:custom_endpoint_auth" json:"custom_endpoint_auth"` + CustomEndpointToken string `gorm:"column:custom_endpoint_token" json:"custom_endpoint_token" scope:"admin"` + CustomScopes string `gorm:"column:custom_scopes" json:"custom_scopes"` } // AllNotifiers contains all the Notifiers loaded From d9a5aa84fa07b48b31e17108d253742d278b4f0d Mon Sep 17 00:00:00 2001 From: hunterlong Date: Thu, 25 Jun 2020 21:51:51 -0700 Subject: [PATCH 10/16] code clean --- handlers/api.go | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/handlers/api.go b/handlers/api.go index b153bb98..c5acf04e 100644 --- a/handlers/api.go +++ b/handlers/api.go @@ -45,22 +45,19 @@ func apiRenewHandler(w http.ResponseWriter, r *http.Request) { } func apiUpdateOAuthHandler(w http.ResponseWriter, r *http.Request) { - var c core.OAuth - if err := DecodeJSON(r, &c); err != nil { + var oauth core.OAuth + if err := DecodeJSON(r, &oauth); err != nil { sendErrorJson(err, w, r) return } - app := core.App - app.OAuth = c - if err := app.Update(); err != nil { + core.App.OAuth = oauth + if err := core.App.Update(); err != nil { sendErrorJson(err, w, r) return } - fmt.Println(app) - - sendJsonAction(app.OAuth, "update", w, r) + sendJsonAction(core.App.OAuth, "update", w, r) } func apiOAuthHandler(r *http.Request) interface{} { From 918035455a119c42640f6a750888abe9a3c97cfa Mon Sep 17 00:00:00 2001 From: hunterlong Date: Thu, 25 Jun 2020 22:52:45 -0700 Subject: [PATCH 11/16] test fix --- handlers/oauth.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/handlers/oauth.go b/handlers/oauth.go index f6a4aa8c..91c241b9 100644 --- a/handlers/oauth.go +++ b/handlers/oauth.go @@ -49,7 +49,7 @@ func oauthLogin(oauth *oAuth, w http.ResponseWriter, r *http.Request) { Email: oauth.Email, Admin: null.NewNullBool(true), } - log.Infoln(fmt.Sprintf("OAuth %s User %s logged in from IP %s", oauth.Type, oauth.Email, r.RemoteAddr)) + log.Infoln(fmt.Sprintf("OAuth %s User %s logged in from IP %s", oauth.Type(), oauth.Email, r.RemoteAddr)) setJwtToken(user, w) http.Redirect(w, r, core.App.Domain+"/dashboard", http.StatusPermanentRedirect) From 507c42f07ef61becccc5c7a44a27fe9b80782a85 Mon Sep 17 00:00:00 2001 From: hunterlong Date: Fri, 26 Jun 2020 16:51:12 -0700 Subject: [PATCH 12/16] setup form fix --- CHANGELOG.md | 1 + handlers/oauth.go | 3 +++ handlers/setup.go | 6 ++---- types/configs/configs_form.go | 3 +++ types/configs/connection.go | 5 ++--- utils/env.go | 1 + 6 files changed, 12 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1fe0388e..004ed371 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ - Modified notifiers to use dereferenced services and failures - Added core.Example() function for testing - Added Custom oAuth Authentication method +- Fixed setup form not creating user from values inputted in form # 0.90.55 (06-18-2020) - Added 404 page diff --git a/handlers/oauth.go b/handlers/oauth.go index 91c241b9..9aa4119b 100644 --- a/handlers/oauth.go +++ b/handlers/oauth.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/gorilla/mux" "github.com/statping/statping/types/core" + "github.com/statping/statping/types/errors" "github.com/statping/statping/types/null" "github.com/statping/statping/types/users" "golang.org/x/oauth2" @@ -31,6 +32,8 @@ func oauthHandler(w http.ResponseWriter, r *http.Request) { oauth, err = slackOAuth(r) case "custom": oauth, err = customOAuth(r) + default: + err = errors.New("unknown oauth provider") } if err != nil { diff --git a/handlers/setup.go b/handlers/setup.go index b4d618d4..565020ed 100644 --- a/handlers/setup.go +++ b/handlers/setup.go @@ -31,10 +31,8 @@ func processSetupHandler(w http.ResponseWriter, r *http.Request) { project := r.PostForm.Get("project") description := r.PostForm.Get("description") domain := r.PostForm.Get("domain") - newsletter := r.PostForm.Get("newsletter") - sendNews, _ := strconv.ParseBool(newsletter) - reports := r.PostForm.Get("send_reports") - sendReports, _ := strconv.ParseBool(reports) + sendNews, _ := strconv.ParseBool(r.PostForm.Get("newsletter")) + sendReports, _ := strconv.ParseBool(r.PostForm.Get("send_reports")) log.WithFields(utils.ToFields(core.App, confgs)).Debugln("new configs posted") diff --git a/types/configs/configs_form.go b/types/configs/configs_form.go index 6de6c5d7..a2c9da12 100644 --- a/types/configs/configs_form.go +++ b/types/configs/configs_form.go @@ -43,6 +43,9 @@ func LoadConfigForm(r *http.Request) (*DbConfig, error) { p.Set("DESCRIPTION", description) p.Set("LANGUAGE", language) p.Set("ALLOW_REPORTS", reports) + p.Set("ADMIN_USER", username) + p.Set("ADMIN_PASSWORD", password) + p.Set("ADMIN_EMAIL", email) confg := &DbConfig{ DbConn: dbConn, diff --git a/types/configs/connection.go b/types/configs/connection.go index c32c4fe2..92b632a5 100644 --- a/types/configs/connection.go +++ b/types/configs/connection.go @@ -75,10 +75,9 @@ func initModels(db database.Database) { } func CreateAdminUser(c *DbConfig) error { - log.Infoln(fmt.Sprintf("Default Admininstrator user does not exist, creating now! (admin/admin)")) - adminUser := utils.Params.GetString("ADMIN_USER") adminPass := utils.Params.GetString("ADMIN_PASSWORD") + adminEmail := utils.Params.GetString("ADMIN_EMAIL") if adminUser == "" || adminPass == "" { adminUser = "admin" @@ -88,7 +87,7 @@ func CreateAdminUser(c *DbConfig) error { admin := &users.User{ Username: adminUser, Password: adminPass, - Email: "info@admin.com", + Email: adminEmail, Admin: null.NewNullBool(true), } diff --git a/utils/env.go b/utils/env.go index 10b9ab08..3e54141c 100644 --- a/utils/env.go +++ b/utils/env.go @@ -35,6 +35,7 @@ func InitEnvs() { Params.SetDefault("BASE_PATH", "") Params.SetDefault("ADMIN_USER", "admin") Params.SetDefault("ADMIN_PASSWORD", "admin") + Params.SetDefault("ADMIN_EMAIL", "info@admin.com") Params.SetDefault("MAX_OPEN_CONN", 25) Params.SetDefault("MAX_IDLE_CONN", 25) Params.SetDefault("MAX_LIFE_CONN", 5*time.Minute) From 3330161d17930d32b24cd7c8cb8734aabf203cce Mon Sep 17 00:00:00 2001 From: hunterlong Date: Fri, 26 Jun 2020 18:12:12 -0700 Subject: [PATCH 13/16] telegram additional description and fix --- CHANGELOG.md | 1 + notifiers/telegram.go | 15 +++++++-------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 004ed371..d0e82971 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ - Added core.Example() function for testing - Added Custom oAuth Authentication method - Fixed setup form not creating user from values inputted in form +- Fixed issues with Telegram Notifier # 0.90.55 (06-18-2020) - Added 404 page diff --git a/notifiers/telegram.go b/notifiers/telegram.go index e7ce4c0d..8af363b5 100644 --- a/notifiers/telegram.go +++ b/notifiers/telegram.go @@ -45,9 +45,9 @@ var Telegram = &telegram{¬ifications.Notification{ Required: true, }, { Type: "text", - Title: "Channel or User", + Title: "Channel", Placeholder: "@statping_channel", - SmallText: "Insert your Telegram Channel or User here.", + SmallText: "Insert your Telegram Channel including the @ symbol. The bot will need to be an administrator of this channel.", DbField: "var1", Required: true, }}}, @@ -60,9 +60,8 @@ func (t *telegram) sendMessage(message string) (string, error) { v := url.Values{} v.Set("chat_id", t.Var1) v.Set("text", message) - rb := *strings.NewReader(v.Encode()) - contents, _, err := utils.HttpRequest(apiEndpoint, "GET", "application/x-www-form-urlencoded", nil, &rb, time.Duration(10*time.Second), true, nil) + contents, _, err := utils.HttpRequest(apiEndpoint, "POST", "application/x-www-form-urlencoded", nil, strings.NewReader(v.Encode()), time.Duration(10*time.Second), true, nil) success, _ := telegramSuccess(contents) if !success { @@ -89,14 +88,14 @@ func (t *telegram) OnSuccess(s services.Service) (string, error) { // OnTest will test the Twilio SMS messaging func (t *telegram) OnTest() (string, error) { - msg := fmt.Sprintf("Testing the Twilio SMS Notifier on your Statping server") - content, err := t.sendMessage(msg) - return content, err + msg := fmt.Sprintf("Testing the Telegram Notifier on your Statping server") + return t.sendMessage(msg) } // OnSave will trigger when this notifier is saved func (t *telegram) OnSave() (string, error) { - return "", nil + msg := fmt.Sprintf("The Telegram Notifier on your Statping server was just saved") + return t.sendMessage(msg) } func telegramSuccess(res []byte) (bool, telegramResponse) { From 9247ef6f8c600b39d00d4141a8c1ee991b707393 Mon Sep 17 00:00:00 2001 From: hunterlong Date: Fri, 26 Jun 2020 18:16:29 -0700 Subject: [PATCH 14/16] stale workflow update --- .github/workflows/stale.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index b5ba5fda..bb4f79b3 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -13,6 +13,6 @@ jobs: repo-token: ${{ secrets.GITHUB_TOKEN }} stale-issue-message: "This issue hasn't had any updates in a while. If this is still a problem, please create a new issue." stale-issue-label: "stale" - days-before-stale: 30 - days-before-close: 7 + days-before-stale: 45 + days-before-close: 14 exempt-issue-label: "bug,urgent,feature,pinned,locked" From 906711e968a44d5d13e1c0295f3a5e0b7fef4cd4 Mon Sep 17 00:00:00 2001 From: hunterlong Date: Fri, 26 Jun 2020 18:20:48 -0700 Subject: [PATCH 15/16] notifier test handler possible fix --- CHANGELOG.md | 1 + handlers/notifications.go | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d0e82971..cf99f370 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ - Added Custom oAuth Authentication method - Fixed setup form not creating user from values inputted in form - Fixed issues with Telegram Notifier +- Modified notifier test handler to return notifier based on URL, not JSON payload # 0.90.55 (06-18-2020) - Added 404 page diff --git a/handlers/notifications.go b/handlers/notifications.go index daa82de7..b809c7e2 100644 --- a/handlers/notifications.go +++ b/handlers/notifications.go @@ -70,7 +70,7 @@ type testNotificationReq struct { func testNotificationHandler(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) - _, err := notifications.Find(vars["notifier"]) + n, err := notifications.Find(vars["notifier"]) if err != nil { sendErrorJson(err, w, r) return @@ -82,7 +82,7 @@ func testNotificationHandler(w http.ResponseWriter, r *http.Request) { return } - notif := services.ReturnNotifier(req.Notification.Method) + notif := services.ReturnNotifier(n.Method) var out string if req.Method == "success" { From c937fb358527ae4800ce9cf0a5468361f7bda8a0 Mon Sep 17 00:00:00 2001 From: hunterlong Date: Fri, 26 Jun 2020 21:23:38 -0700 Subject: [PATCH 16/16] postman fix --- dev/postman.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dev/postman.json b/dev/postman.json index 6bd364fb..1f6b4ba6 100644 --- a/dev/postman.json +++ b/dev/postman.json @@ -3391,7 +3391,7 @@ " var user = jsonData[0];", " pm.expect(user.id).to.eql(1);", " pm.expect(user.username).to.eql(\"admin\");", - " pm.expect(user.email).to.eql(\"info@admin.com\");", + " pm.expect(user.email).to.eql(\"info@domain.com\");", "});" ], "type": "text/javascript" @@ -4233,7 +4233,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"method\": \"success\",\n \"notifier\": {\n \"enabled\": false,\n \"limits\": 60,\n \"method\": \"slack\",\n \"host\": \"https://webhooksurl.slack.com/***\",\n \"success_data\": \"{\\n \\\"blocks\\\": [{\\n \\\"type\\\": \\\"section\\\",\\n \\\"text\\\": {\\n \\\"type\\\": \\\"mrkdwn\\\",\\n \\\"text\\\": \\\"The service {{.Service.Name}} is back online.\\\"\\n }\\n }, {\\n \\\"type\\\": \\\"actions\\\",\\n \\\"elements\\\": [{\\n \\\"type\\\": \\\"button\\\",\\n \\\"text\\\": {\\n \\\"type\\\": \\\"plain_text\\\",\\n \\\"text\\\": \\\"View Service\\\",\\n \\\"emoji\\\": true\\n },\\n \\\"style\\\": \\\"primary\\\",\\n \\\"url\\\": \\\"{{.Core.Domain}}/service/{{.Service.Id}}\\\"\\n }, {\\n \\\"type\\\": \\\"button\\\",\\n \\\"text\\\": {\\n \\\"type\\\": \\\"plain_text\\\",\\n \\\"text\\\": \\\"Go to Statping\\\",\\n \\\"emoji\\\": true\\n },\\n \\\"url\\\": \\\"{{.Core.Domain}}\\\"\\n }]\\n }]\\n}\",\n \"failure_data\": \"{\\n \\\"blocks\\\": [{\\n \\\"type\\\": \\\"section\\\",\\n \\\"text\\\": {\\n \\\"type\\\": \\\"mrkdwn\\\",\\n \\\"text\\\": \\\":warning: The service {{.Service.Name}} is currently offline! :warning:\\\"\\n }\\n }, {\\n \\\"type\\\": \\\"divider\\\"\\n }, {\\n \\\"type\\\": \\\"section\\\",\\n \\\"fields\\\": [{\\n \\\"type\\\": \\\"mrkdwn\\\",\\n \\\"text\\\": \\\"*Service:*\\\\n{{.Service.Name}}\\\"\\n }, {\\n \\\"type\\\": \\\"mrkdwn\\\",\\n \\\"text\\\": \\\"*URL:*\\\\n{{.Service.Domain}}\\\"\\n }, {\\n \\\"type\\\": \\\"mrkdwn\\\",\\n \\\"text\\\": \\\"*Status Code:*\\\\n{{.Service.LastStatusCode}}\\\"\\n }, {\\n \\\"type\\\": \\\"mrkdwn\\\",\\n \\\"text\\\": \\\"*When:*\\\\n{{.Failure.CreatedAt}}\\\"\\n }, {\\n \\\"type\\\": \\\"mrkdwn\\\",\\n \\\"text\\\": \\\"*Downtime:*\\\\n{{.Service.DowntimeAgo}}\\\"\\n }, {\\n \\\"type\\\": \\\"plain_text\\\",\\n \\\"text\\\": \\\"*Error:*\\\\n{{.Failure.Issue}}\\\"\\n }]\\n }, {\\n \\\"type\\\": \\\"divider\\\"\\n }, {\\n \\\"type\\\": \\\"actions\\\",\\n \\\"elements\\\": [{\\n \\\"type\\\": \\\"button\\\",\\n \\\"text\\\": {\\n \\\"type\\\": \\\"plain_text\\\",\\n \\\"text\\\": \\\"View Offline Service\\\",\\n \\\"emoji\\\": true\\n },\\n \\\"style\\\": \\\"danger\\\",\\n \\\"url\\\": \\\"{{.Core.Domain}}/service/{{.Service.Id}}\\\"\\n }, {\\n \\\"type\\\": \\\"button\\\",\\n \\\"text\\\": {\\n \\\"type\\\": \\\"plain_text\\\",\\n \\\"text\\\": \\\"Go to Statping\\\",\\n \\\"emoji\\\": true\\n },\\n \\\"url\\\": \\\"{{.Core.Domain}}\\\"\\n }]\\n }]\\n}\"\n }\n}", + "raw": "{\n \"method\": \"success\",\n \"notifier\": {\n \"enabled\": false,\n \"limits\": 60,\n \"method\": \"slack\",\n \"host\": \"https://webhooksurl.slack.com/***\",\n \"success_data\": \"{\\n \\\"blocks\\\": [{\\n \\\"type\\\": \\\"section\\\",\\n \\\"text\\\": {\\n \\\"type\\\": \\\"mrkdwn\\\",\\n \\\"text\\\": \\\"The service {{.Service.Name}} is back online.\\\"\\n }\\n }, {\\n \\\"type\\\": \\\"actions\\\",\\n \\\"elements\\\": [{\\n \\\"type\\\": \\\"button\\\",\\n \\\"text\\\": {\\n \\\"type\\\": \\\"plain_text\\\",\\n \\\"text\\\": \\\"View Service\\\",\\n \\\"emoji\\\": true\\n },\\n \\\"style\\\": \\\"primary\\\",\\n \\\"url\\\": \\\"{{.Core.Domain}}/service/{{.Service.Id}}\\\"\\n }, {\\n \\\"type\\\": \\\"button\\\",\\n \\\"text\\\": {\\n \\\"type\\\": \\\"plain_text\\\",\\n \\\"text\\\": \\\"Go to Statping\\\",\\n \\\"emoji\\\": true\\n },\\n \\\"url\\\": \\\"{{.Core.Domain}}\\\"\\n }]\\n }]\\n}\",\n \"failure_data\": \"{\\n \\\"blocks\\\": [{\\n \\\"type\\\": \\\"section\\\",\\n \\\"text\\\": {\\n \\\"type\\\": \\\"mrkdwn\\\",\\n \\\"text\\\": \\\":warning: The service {{.Service.Name}} is currently offline! :warning:\\\"\\n }\\n }, {\\n \\\"type\\\": \\\"divider\\\"\\n }, {\\n \\\"type\\\": \\\"section\\\",\\n \\\"fields\\\": [{\\n \\\"type\\\": \\\"mrkdwn\\\",\\n \\\"text\\\": \\\"*Service:*\\\\n{{.Service.Name}}\\\"\\n }, {\\n \\\"type\\\": \\\"mrkdwn\\\",\\n \\\"text\\\": \\\"*URL:*\\\\n{{.Service.Domain}}\\\"\\n }, {\\n \\\"type\\\": \\\"mrkdwn\\\",\\n \\\"text\\\": \\\"*Status Code:*\\\\n{{.Service.LastStatusCode}}\\\"\\n }, {\\n \\\"type\\\": \\\"mrkdwn\\\",\\n \\\"text\\\": \\\"*When:*\\\\n{{.Failure.CreatedAt}}\\\"\\n }, {\\n \\\"type\\\": \\\"mrkdwn\\\",\\n \\\"text\\\": \\\"*Downtime:*\\\\n{{.Service.DowntimeAgo}}\\\"\\n }, {\\n \\\"type\\\": \\\"plain_text\\\",\\n \\\"text\\\": \\\"*Error:*\\\\n{{.Failure.Issue}}\\\"\\n }]\\n }, {\\n \\\"type\\\": \\\"divider\\\"\\n }, {\\n \\\"type\\\": \\\"actions\\\",\\n \\\"elements\\\": [{\\n \\\"type\\\": \\\"button\\\",\\n \\\"text\\\": {\\n \\\"type\\\": \\\"plain_text\\\",\\n \\\"text\\\": \\\"View Offline Service\\\",\\n \\\"emoji\\\": true\\n },\\n \\\"style\\\": \\\"danger\\\",\\n \\\"url\\\": \\\"{{.Core.Domain}}/service/{{.Service.Id}}\\\"\\n }, {\\n \\\"type\\\": \\\"button\\\",\\n \\\"text\\\": {\\n \\\"type\\\": \\\"plain_text\\\",\\n \\\"text\\\": \\\"Go to Statping\\\",\\n \\\"emoji\\\": true\\n },\\n \\\"url\\\": \\\"{{.Core.Domain}}\\\"\\n }]\\n }]\\n}\"\n }\n}", "options": { "raw": {} }