Merge pull request #682 from statping/notifier-updates-fixes

Notifier Updates and UI Fixes
pull/687/head
Hunter Long 2020-06-21 00:00:37 -07:00 committed by GitHub
commit 89ba7a6359
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 936 additions and 210 deletions

View File

@ -135,6 +135,7 @@ jobs:
TWILIO_SECRET: ${{ secrets.TWILIO_SECRET }} TWILIO_SECRET: ${{ secrets.TWILIO_SECRET }}
TWILIO_FROM: ${{ secrets.TWILIO_FROM }} TWILIO_FROM: ${{ secrets.TWILIO_FROM }}
TWILIO_TO: ${{ secrets.TWILIO_TO }} TWILIO_TO: ${{ secrets.TWILIO_TO }}
TEST_EMAIL: ${{ secrets.TEST_EMAIL }}
- name: Coveralls Testing Coverage - name: Coveralls Testing Coverage
run: | run: |

View File

@ -135,6 +135,7 @@ jobs:
TWILIO_SECRET: ${{ secrets.TWILIO_SECRET }} TWILIO_SECRET: ${{ secrets.TWILIO_SECRET }}
TWILIO_FROM: ${{ secrets.TWILIO_FROM }} TWILIO_FROM: ${{ secrets.TWILIO_FROM }}
TWILIO_TO: ${{ secrets.TWILIO_TO }} TWILIO_TO: ${{ secrets.TWILIO_TO }}
TEST_EMAIL: ${{ secrets.TEST_EMAIL }}
- name: Coveralls Testing Coverage - name: Coveralls Testing Coverage
run: | run: |

View File

@ -1,6 +1,9 @@
# 0.90.55 (06-18-2020) # 0.90.55 (06-18-2020)
- Added 404 page - Added 404 page
- Modified Statping's PR process, dev -> master - Modified Statping's PR process, dev -> master
- Fixed Discord notifier
- Modified email template for SMTP emails
- Added OnSave() method for all notifiers
# 0.90.54 (06-17-2020) # 0.90.54 (06-17-2020)
- Fixed Slack Notifier's failure/success data saving issue - Fixed Slack Notifier's failure/success data saving issue

View File

@ -19,7 +19,7 @@
{{$t('dashboard.wrong_login')}} {{$t('dashboard.wrong_login')}}
</div> </div>
<button @click.prevent="login" type="submit" class="btn btn-block mb-3 btn-primary" :disabled="disabled || loading"> <button @click.prevent="login" type="submit" class="btn btn-block mb-3 btn-primary" :disabled="disabled || loading">
{{loading ? $t('dashboard.loading') : $t('dashboard.sign_in')}} <font-awesome-icon v-if="loading" icon="circle-notch" class="mr-2" spin/>{{loading ? $t('dashboard.loading') : $t('dashboard.sign_in')}}
</button> </button>
</div> </div>
</div> </div>

View File

@ -89,12 +89,12 @@
</button> </button>
</div> </div>
<div class="col-4 col-md-4"> <div class="col-4 col-md-4">
<button @click.prevent="testNotifier('success')" class="btn btn-outline-dark btn-block text-capitalize test-notifier"> <button @click.prevent="testNotifier('success')" :disabled="loadingTest" class="btn btn-outline-dark btn-block text-capitalize test-notifier">
<i class="fa fa-vial"></i>{{loadingTest ? "Loading..." : "Test Success"}}</button> <font-awesome-icon v-if="loadingTest" icon="circle-notch" class="mr-2" spin/>{{loadingTest ? "Loading..." : "Test Success"}}</button>
</div> </div>
<div class="col-4 col-md-4"> <div class="col-4 col-md-4">
<button @click.prevent="testNotifier('failure')" class="btn btn-outline-dark btn-block text-capitalize test-notifier"> <button @click.prevent="testNotifier('failure')" :disabled="loadingTest" class="btn btn-outline-dark btn-block text-capitalize test-notifier">
<i class="fa fa-vial"></i>{{loadingTest ? "Loading..." : "Test Failure"}}</button> <font-awesome-icon v-if="loadingTest" icon="circle-notch" class="mr-2" spin/>{{loadingTest ? "Loading..." : "Test Failure"}}</button>
</div> </div>
</div> </div>

View File

@ -150,7 +150,6 @@
return { return {
tab: "v-pills-home-tab", tab: "v-pills-home-tab",
qrcode: "", qrcode: "",
qrurl: "",
} }
}, },
computed: { computed: {
@ -174,8 +173,8 @@
const n = await Api.notifiers() const n = await Api.notifiers()
this.$store.commit('setNotifiers', n) this.$store.commit('setNotifiers', n)
this.qrurl = `statping://setup?domain=${c.domain}&api=${c.api_secret}` const u = `statping://setup?domain=${c.domain}&api=${c.api_secret}`
this.qrcode = "https://chart.googleapis.com/chart?chs=500x500&cht=qr&chl=" + encodeURI(this.qrurl) this.qrcode = "https://chart.googleapis.com/chart?chs=500x500&cht=qr&chl=" + encodeURIComponent(u)
this.cache = await Api.cache() this.cache = await Api.cache()
}, },
changeTab(e) { changeTab(e) {

1
go.mod
View File

@ -15,6 +15,7 @@ require (
github.com/golang/protobuf v1.3.5 // indirect github.com/golang/protobuf v1.3.5 // indirect
github.com/gorilla/mux v1.7.4 github.com/gorilla/mux v1.7.4
github.com/gorilla/securecookie v1.1.1 github.com/gorilla/securecookie v1.1.1
github.com/hako/durafmt v0.0.0-20200605151348-3a43fc422dd9
github.com/jinzhu/gorm v1.9.12 github.com/jinzhu/gorm v1.9.12
github.com/magiconair/properties v1.8.1 github.com/magiconair/properties v1.8.1
github.com/mattn/go-sqlite3 v2.0.3+incompatible github.com/mattn/go-sqlite3 v2.0.3+incompatible

2
go.sum
View File

@ -129,6 +129,8 @@ github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoA
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/hako/durafmt v0.0.0-20200605151348-3a43fc422dd9 h1:IEhIezS5kcD4ZzOwVl8dAyJ9JCi4Xo6tg44Vj/z7UsI=
github.com/hako/durafmt v0.0.0-20200605151348-3a43fc422dd9/go.mod h1:5Scbynm8dF1XAPwIwkGPqzkM/shndPm79Jd1003hTjE=
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=

View File

@ -248,8 +248,6 @@ func TestMainApiRoutes(t *testing.T) {
ExpectedContains: []string{ ExpectedContains: []string{
`go_goroutines`, `go_goroutines`,
`go_memstats_alloc_bytes`, `go_memstats_alloc_bytes`,
`process_cpu_seconds_total`,
`promhttp_metric_handler_requests_total`,
`go_threads`, `go_threads`,
}, },
}, },

View File

@ -53,7 +53,13 @@ func apiNotifierUpdateHandler(w http.ResponseWriter, r *http.Request) {
sendErrorJson(err, w, r) sendErrorJson(err, w, r)
return return
} }
//notifications.OnSave(notifer.Method)
notif := services.ReturnNotifier(notifer.Method)
if _, err := notif.OnSave(); err != nil {
sendErrorJson(err, w, r)
return
}
sendJsonAction(vars["notifier"], "update", w, r) sendJsonAction(vars["notifier"], "update", w, r)
} }

View File

@ -70,3 +70,8 @@ func (c *commandLine) OnTest() (string, error) {
utils.Log.Infoln(out) utils.Log.Infoln(out)
return out, err return out, err
} }
// OnSave will trigger when this notifier is saved
func (c *commandLine) OnSave() (string, error) {
return "", nil
}

View File

@ -52,15 +52,13 @@ func (d *discord) Select() *notifications.Notification {
// OnFailure will trigger failing service // OnFailure will trigger failing service
func (d *discord) OnFailure(s *services.Service, f *failures.Failure) (string, error) { func (d *discord) OnFailure(s *services.Service, f *failures.Failure) (string, error) {
msg := `{"content": "Your service '{{.Service.Name}}' is currently failing! Reason: {{.Failure.Issue}}"}` out, err := d.sendRequest(ReplaceVars(d.FailureData, s, f))
out, err := d.sendRequest(ReplaceVars(msg, s, f))
return out, err return out, err
} }
// OnSuccess will trigger successful service // OnSuccess will trigger successful service
func (d *discord) OnSuccess(s *services.Service) (string, error) { func (d *discord) OnSuccess(s *services.Service) (string, error) {
msg := `{"content": "Your service '{{.Service.Name}}' is currently online!"}` out, err := d.sendRequest(ReplaceVars(d.SuccessData, s, nil))
out, err := d.sendRequest(ReplaceVars(msg, s, nil))
return out, err return out, err
} }
@ -83,6 +81,11 @@ func (d *discord) OnTest() (string, error) {
return string(contents), nil return string(contents), nil
} }
// OnSave will trigger when this notifier is saved
func (d *discord) OnSave() (string, error) {
return "", nil
}
type discordTestJson struct { type discordTestJson struct {
Code int `json:"code"` Code int `json:"code"`
Message string `json:"message"` Message string `json:"message"`

View File

@ -1,96 +1,21 @@
package notifiers package notifiers
import ( import (
"bytes"
"crypto/tls" "crypto/tls"
"fmt" "fmt"
"github.com/go-mail/mail" "github.com/go-mail/mail"
"github.com/statping/statping/types/core"
"github.com/statping/statping/types/failures" "github.com/statping/statping/types/failures"
"github.com/statping/statping/types/notifications" "github.com/statping/statping/types/notifications"
"github.com/statping/statping/types/notifier" "github.com/statping/statping/types/notifier"
"github.com/statping/statping/types/null"
"github.com/statping/statping/types/services" "github.com/statping/statping/types/services"
"github.com/statping/statping/utils" "github.com/statping/statping/utils"
"time" "html/template"
) )
var _ notifier.Notifier = (*emailer)(nil) var _ notifier.Notifier = (*emailer)(nil)
const (
mainEmailTemplate = `<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Statping email</title>
</head>
<body style="-webkit-text-size-adjust: none; box-sizing: border-box; color: #74787E; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; height: 100%; line-height: 1.4; margin: 0; width: 100% !important;" bgcolor="#F2F4F6">
<style type="text/css">
body {
width: 100% !important;
height: 100%;
margin: 0;
line-height: 1.4;
background-color: #F2F4F6;
color: #74787E;
-webkit-text-size-adjust: none;
}
@media only screen and (max-width: 600px) {
.email-body_inner {
width: 100% !important;
}
.email-footer {
width: 100% !important;
}
}
@media only screen and (max-width: 500px) {
.button {
width: 100% !important;
}
}
</style>
<table class="email-wrapper" width="100%" cellpadding="0" cellspacing="0" style="box-sizing: border-box; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; margin: 0; padding: 0; width: 100%;" bgcolor="#F2F4F6">
<tr>
<td align="center" style="box-sizing: border-box; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; word-break: break-word;">
<table class="email-content" width="100%" cellpadding="0" cellspacing="0" style="box-sizing: border-box; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; margin: 0; padding: 0; width: 100%;">
<tr>
<td class="email-body" width="100%" cellpadding="0" cellspacing="0" style="-premailer-cellpadding: 0; -premailer-cellspacing: 0; border-bottom-color: #EDEFF2; border-bottom-style: solid; border-bottom-width: 1px; border-top-color: #EDEFF2; border-top-style: solid; border-top-width: 1px; box-sizing: border-box; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; margin: 0; padding: 0; width: 100%; word-break: break-word;" bgcolor="#FFFFFF">
<table class="email-body_inner" align="center" width="570" cellpadding="0" cellspacing="0" style="box-sizing: border-box; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; margin: 0 auto; padding: 0; width: 570px;" bgcolor="#FFFFFF">
<tr>
<td class="content-cell" style="box-sizing: border-box; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; padding: 35px; word-break: break-word;">
<h1 style="box-sizing: border-box; color: #2F3133; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; font-size: 19px; font-weight: bold; margin-top: 0;" align="left">
{{ .Service.Name }} is {{ if .Service.Online }}Online{{else}}Offline{{end}}!
</h1>
<p style="box-sizing: border-box; color: #74787E; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; font-size: 16px; line-height: 1.5em; margin-top: 0;" align="left">
{{ if .Service.Online }}
Your Statping service <a target="_blank" href="{{.Service.Domain}}">{{.Service.Name}}</a> is back online. This service has been triggered with a HTTP status code of '{{.Service.LastStatusCode}}' and is currently online based on your requirements. Your service was reported online at {{.Service.CreatedAt}}. </p>
{{ else }}
Your Statping service <a target="_blank" href="{{.Service.Domain}}">{{.Service.Name}}</a> has been triggered with a HTTP status code of '{{.Service.LastStatusCode}}' and is currently offline based on your requirements. This failure was created on {{.Service.CreatedAt}}. </p>
{{ end }}
{{if .Service.LastResponse }}
<h1 style="box-sizing: border-box; color: #2F3133; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; font-size: 19px; font-weight: bold; margin-top: 0;" align="left">
Last Response</h1>
<p style="box-sizing: border-box; color: #74787E; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; font-size: 16px; line-height: 1.5em; margin-top: 0;" align="left">
{{ .Service.LastResponse }} </p> {{end}}
<table class="body-sub" style="border-top-color: #EDEFF2; border-top-style: solid; border-top-width: 1px; box-sizing: border-box; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; margin-top: 25px; padding-top: 25px;">
<td style="box-sizing: border-box; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; word-break: break-word;"> <a href="/service/{{.Service.Id}}" class="button button--blue" target="_blank" style="-webkit-text-size-adjust: none; background: #3869D4; border-color: #3869d4; border-radius: 3px; border-style: solid; border-width: 10px 18px; box-shadow: 0 2px 3px rgba(0, 0, 0, 0.16); box-sizing: border-box; color: #FFF; display: inline-block; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; text-decoration: none;">View Service</a> </td>
<td style="box-sizing: border-box; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; word-break: break-word;"> <a href="/dashboard" class="button button--blue" target="_blank" style="-webkit-text-size-adjust: none; background: #3869D4; border-color: #3869d4; border-radius: 3px; border-style: solid; border-width: 10px 18px; box-shadow: 0 2px 3px rgba(0, 0, 0, 0.16); box-sizing: border-box; color: #FFF; display: inline-block; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; text-decoration: none;">Statping Dashboard</a> </td>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>`
)
var ( var (
mailer *mail.Dialer mailer *mail.Dialer
) )
@ -110,9 +35,6 @@ var email = &emailer{&notifications.Notification{
Author: "Hunter Long", Author: "Hunter Long",
AuthorUrl: "https://github.com/hunterlong", AuthorUrl: "https://github.com/hunterlong",
Icon: "far fa-envelope", Icon: "far fa-envelope",
SuccessData: "Service {{.Service.Name}} is Back Online",
FailureData: "Service {{.Service.Name}} is Offline",
DataType: "text",
Limits: 30, Limits: 30,
Form: []notifications.NotificationForm{{ Form: []notifications.NotificationForm{{
Type: "text", Type: "text",
@ -145,7 +67,7 @@ var email = &emailer{&notifications.Notification{
Placeholder: "sendto@email.com", Placeholder: "sendto@email.com",
DbField: "Var2", DbField: "Var2",
}, { }, {
Type: "text", Type: "switch",
Title: "Disable TLS/SSL", Title: "Disable TLS/SSL",
Placeholder: "", Placeholder: "",
SmallText: "To Disable TLS/SSL insert 'true'", SmallText: "To Disable TLS/SSL insert 'true'",
@ -166,64 +88,69 @@ type emailOutgoing struct {
// OnFailure will trigger failing service // OnFailure will trigger failing service
func (e *emailer) OnFailure(s *services.Service, f *failures.Failure) (string, error) { func (e *emailer) OnFailure(s *services.Service, f *failures.Failure) (string, error) {
subject := fmt.Sprintf("Service %s is Offline", s.Name) subject := fmt.Sprintf("Service %s is Offline", s.Name)
tmpl := renderEmail(s, f)
email := &emailOutgoing{ email := &emailOutgoing{
To: e.Var2, To: e.Var2,
Subject: subject, Subject: subject,
Template: mainEmailTemplate, Template: tmpl,
Data: replacer{ From: e.Var1,
Service: s,
Failure: f,
},
From: e.Var1,
} }
return "email failed", e.dialSend(email) return tmpl, e.dialSend(email)
} }
// OnSuccess will trigger successful service // OnSuccess will trigger successful service
func (e *emailer) OnSuccess(s *services.Service) (string, error) { func (e *emailer) OnSuccess(s *services.Service) (string, error) {
subject := fmt.Sprintf("Service %s is Back Online", s.Name) subject := fmt.Sprintf("Service %s is Back Online", s.Name)
tmpl := renderEmail(s, nil)
email := &emailOutgoing{ email := &emailOutgoing{
To: e.Var2, To: e.Var2,
Subject: subject, Subject: subject,
Template: mainEmailTemplate, Template: tmpl,
Data: replacer{ From: e.Var1,
Service: s,
Failure: &failures.Failure{},
},
From: e.Var1,
} }
return "email sent", e.dialSend(email) return tmpl, e.dialSend(email)
}
func renderEmail(s *services.Service, f *failures.Failure) string {
wr := bytes.NewBuffer(nil)
tmpl := template.New("email")
tmpl, err := tmpl.Parse(emailBase)
if err != nil {
log.Errorln(err)
return emailBase
}
data := replacer{
Core: core.App,
Service: s,
Failure: f,
Custom: nil,
}
if err = tmpl.ExecuteTemplate(wr, "email", data); err != nil {
log.Errorln(err)
return emailBase
}
return wr.String()
} }
// OnTest triggers when this notifier has been saved // OnTest triggers when this notifier has been saved
func (e *emailer) OnTest() (string, error) { func (e *emailer) OnTest() (string, error) {
testService := services.Service{ service := services.Example(true)
Id: 1, subject := fmt.Sprintf("Service %v is Back Online", service.Name)
Name: "Example Service",
Domain: "https://www.youtube.com/watch?v=-u6DvRyyKGU",
ExpectedStatus: 200,
Interval: 30,
Type: "http",
Method: "GET",
Timeout: 20,
LastStatusCode: 200,
Expected: null.NewNullString("test example"),
LastResponse: "<html>this is an example response</html>",
CreatedAt: utils.Now().Add(-24 * time.Hour),
}
subject := fmt.Sprintf("Service %v is Back Online", testService.Name)
email := &emailOutgoing{ email := &emailOutgoing{
To: e.Var2, To: e.Var2,
Subject: subject, Subject: subject,
Template: mainEmailTemplate, Template: renderEmail(service, failures.Example()),
Data: replacer{ From: e.Var1,
Service: &testService,
Failure: &failures.Failure{},
},
From: e.Var1,
} }
err := e.dialSend(email) return subject, e.dialSend(email)
return subject, err }
// OnSave will trigger when this notifier is saved
func (e *emailer) OnSave() (string, error) {
return "", nil
} }
func (e *emailer) dialSend(email *emailOutgoing) error { func (e *emailer) dialSend(email *emailOutgoing) error {
@ -236,14 +163,15 @@ func (e *emailer) dialSend(email *emailOutgoing) error {
mailer.TLSConfig = &tls.Config{InsecureSkipVerify: true} mailer.TLSConfig = &tls.Config{InsecureSkipVerify: true}
} }
m.SetHeader("From", email.From) m.SetAddressHeader("From", email.From, "Statping")
m.SetHeader("To", email.To) m.SetHeader("To", email.To)
m.SetHeader("Subject", email.Subject) m.SetHeader("Subject", email.Subject)
m.SetBody("text/html", ReplaceTemplate(email.Template, email.Data)) m.SetBody("text/html", email.Template)
if err := mailer.DialAndSend(m); err != nil { if err := mailer.DialAndSend(m); err != nil {
utils.Log.Errorln(fmt.Sprintf("email '%v' sent to: %v (size: %v) %v", email.Subject, email.To, len([]byte(email.Source)), err)) utils.Log.Errorln(fmt.Sprintf("email '%v' sent to: %v (size: %v) %v", email.Subject, email.To, len([]byte(email.Source)), err))
return err return err
} }
return nil return nil
} }

628
notifiers/email_template.go Normal file
View File

@ -0,0 +1,628 @@
package notifiers
const emailBase = `
{{$banner := "https://assets.statping.com/greenbackground.png"}}
{{$color := "#4caf50"}}
{{if not .Service.Online}}
{{$banner = "https://assets.statping.com/offlinebanner.png"}}
{{$color = "#c30c0c"}}
{{end}}
<!doctype html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
<head>
<title> Statping Service Notification </title>
<!--[if !mso]><!-- -->
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<!--<![endif]-->
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style type="text/css">
#outlook a {
padding: 0;
}
body {
margin: 0;
padding: 0;
-webkit-text-size-adjust: 100%;
-ms-text-size-adjust: 100%;
}
table,
td {
border-collapse: collapse;
mso-table-lspace: 0pt;
mso-table-rspace: 0pt;
}
img {
border: 0;
height: auto;
line-height: 100%;
outline: none;
text-decoration: none;
-ms-interpolation-mode: bicubic;
}
p {
display: block;
margin: 13px 0;
}
</style>
<!--[if mso]>
<xml>
<o:OfficeDocumentSettings>
<o:AllowPNG/>
<o:PixelsPerInch>96</o:PixelsPerInch>
</o:OfficeDocumentSettings>
</xml>
<![endif]-->
<!--[if lte mso 11]>
<style type="text/css">
.mj-outlook-group-fix { width:100% !important; }
</style>
<![endif]-->
<!--[if !mso]><!-->
<link href="https://fonts.googleapis.com/css?family=Ubuntu:300,400,500,700" rel="stylesheet" type="text/css">
<style type="text/css">
@import url(https://fonts.googleapis.com/css?family=Ubuntu:300,400,500,700);
</style>
<!--<![endif]-->
<style type="text/css">
@media only screen and (min-width:480px) {
.mj-column-per-100 {
width: 100% !important;
max-width: 100%;
}
}
</style>
<style type="text/css">
@media only screen and (max-width:480px) {
table.mj-full-width-mobile {
width: 100% !important;
}
td.mj-full-width-mobile {
width: auto !important;
}
}
</style>
</head>
<body style="background-color:#E7E7E7;">
<div style="background-color:#E7E7E7;">
<!-- Top Bar -->
<!--[if mso | IE]>
<table
align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600"
>
<tr>
<td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;">
<v:rect style="width:600px;" xmlns:v="urn:schemas-microsoft-com:vml" fill="true" stroke="false">
<v:fill origin="0.5, 0" position="0.5, 0" src="{{$banner}}" color="#FF3FB4" type="tile" />
<v:textbox style="mso-fit-shape-to-text:true" inset="0,0,0,0">
<![endif]-->
<div style="background:#FF3FB4 url({{$banner}}) top center / auto repeat;margin:0px auto;max-width:600px;">
<div style="line-height:0;font-size:0;">
<table align="center" background="{{$banner}}" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#FF3FB4 url({{$banner}}) top center / auto repeat;width:100%;">
<tbody>
<tr>
<td style="direction:ltr;font-size:0px;padding:0px;text-align:center;">
<!--[if mso | IE]>
<table role="presentation" border="0" cellpadding="0" cellspacing="0">
<tr>
<td
class="" style="vertical-align:top;width:600px;"
>
<![endif]-->
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
<tr>
<td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse:collapse;border-spacing:0px;">
<tbody>
<tr>
<td style="width:45px;"> <a href="https://statping.com" target="_blank">
<img
alt="Statping" height="auto" src="https://assets.statping.com/iconlight.png" style="border:0;display:block;outline:none;text-decoration:none;height:auto;width:100%;font-size:13px;" width="45"
/>
</a> </td>
</tr>
</tbody>
</table>
</td>
</tr>
</table>
</div>
<!--[if mso | IE]>
</td>
</tr>
</table>
<![endif]-->
</td>
</tr>
</tbody>
</table>
</div>
</div>
<!--[if mso | IE]>
</v:textbox>
</v:rect>
</td>
</tr>
</table>
<table
align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600"
>
<tr>
<td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;">
<![endif]-->
<div style="background:#ffffff;background-color:#ffffff;margin:0px auto;max-width:600px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#ffffff;background-color:#ffffff;width:100%;">
<tbody>
<tr>
<td style="direction:ltr;font-size:0px;padding:20px 0;text-align:center;">
<!--[if mso | IE]>
<table role="presentation" border="0" cellpadding="0" cellspacing="0">
<tr>
<td
class="" style="vertical-align:top;width:600px;"
>
<![endif]-->
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
<tr>
<td align="left" style="font-size:0px;padding:15px;word-break:break-word;">
<div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:22px;line-height:30px;text-align:left;color:#000000;">
{{if .Service.Online}}
{{.Service.Name}} is back online.
{{else}}
{{.Service.Name}} is currently offline, you might want to check it.
{{end}}
</div>
</td>
</tr>
<tr>
<td style="font-size:0px;padding:20px 0;padding-top:10px;padding-right:0px;padding-bottom:10px;padding-left:0px;word-break:break-word;">
<!--[if mso | IE]>
<table
align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600"
>
<tr>
<td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;">
<![endif]-->
<div style="margin:0px auto;max-width:600px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
<tbody>
<tr>
<td style="direction:ltr;font-size:0px;padding:20px 0;padding-bottom:10px;padding-left:0px;padding-right:0px;padding-top:10px;text-align:center;">
<!--[if mso | IE]>
<table role="presentation" border="0" cellpadding="0" cellspacing="0">
<tr>
<td
class="" style="vertical-align:top;width:600px;"
>
<![endif]-->
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
<tr>
<td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
<div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:20px;line-height:1;text-align:center;color:#626262;">
{{if .Service.Online}}
Online for {{.Service.Uptime.Human}}
{{else}}
Offline for {{.Service.Downtime.Human}}
{{end}}
</div>
</td>
</tr>
<tr>
<td align="center" vertical-align="middle" style="font-size:0px;padding:10px 25px;word-break:break-word;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse:separate;line-height:100%;">
<tr>
<td align="center" bgcolor="{{$color}}" role="presentation" style="border:none;border-radius:4px;cursor:auto;mso-padding-alt:10px 25px;background:{{$color}};" valign="middle">
<a href="{{.Core.Domain}}/service/{{.Service.Id}}" style="display:inline-block;background:{{$color}};color:#ffffff;font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;font-weight:normal;line-height:120%;margin:0;text-decoration:none;text-transform:none;padding:10px 25px;mso-padding-alt:0px;border-radius:4px;"
target="_blank">
View Dashboard
</a> </td>
</tr>
</table>
</td>
</tr>
</table>
</div>
<!--[if mso | IE]>
</td>
</tr>
</table>
<![endif]-->
</td>
</tr>
</tbody>
</table>
</div>
<!--[if mso | IE]>
</td>
</tr>
</table>
<![endif]-->
</td>
</tr>
<!-- Bottom Graphic -->
<tr>
<td style="font-size:0px;padding:0px;word-break:break-word;">
<!--[if mso | IE]>
<table
align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600"
>
<tr>
<td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;">
<![endif]-->
<div style="background:#fafafa;background-color:#fafafa;margin:0px auto;max-width:600px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#fafafa;background-color:#fafafa;width:100%;">
<tbody>
<tr>
<td style="direction:ltr;font-size:0px;padding:0px;text-align:center;">
<!--[if mso | IE]>
<table role="presentation" border="0" cellpadding="0" cellspacing="0">
<tr>
<td
class="" style="vertical-align:top;width:600px;"
>
<![endif]-->
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
<tr>
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
<div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:20px;line-height:1;text-align:left;color:#626262;">Service Domain</div>
</td>
</tr>
<tr>
<td align="left" style="font-size:0px;padding:10px 25px;padding-top:0px;word-break:break-word;">
<div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:14px;line-height:1;text-align:left;color:#626262;">{{.Service.Domain}}</div>
</td>
</tr>
</table>
</div>
<!--[if mso | IE]>
</td>
</tr>
</table>
<![endif]-->
</td>
</tr>
</tbody>
</table>
</div>
<!--[if mso | IE]>
</td>
</tr>
</table>
<![endif]-->
</td>
</tr>
<tr>
<td style="font-size:0px;padding:0px;word-break:break-word;">
<!--[if mso | IE]>
<table
align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600"
>
<tr>
<td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;">
<![endif]-->
<div style="background:#ffffff;background-color:#ffffff;margin:0px auto;max-width:600px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#ffffff;background-color:#ffffff;width:100%;">
<tbody>
<tr>
<td style="direction:ltr;font-size:0px;padding:0px;text-align:center;">
<!--[if mso | IE]>
<table role="presentation" border="0" cellpadding="0" cellspacing="0">
<tr>
<td
class="" style="vertical-align:top;width:600px;"
>
<![endif]-->
{{if .Failure}}
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
<tr>
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
<div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:20px;line-height:1;text-align:left;color:#626262;">Current Issue</div>
</td>
</tr>
<tr>
<td align="left" style="font-size:0px;padding:10px 25px;padding-top:0px;word-break:break-word;">
<div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:14px;line-height:1;text-align:left;color:#626262;">{{.Failure.Issue}}</div>
</td>
</tr>
</table>
</div>
{{end}}
<!--[if mso | IE]>
</td>
</tr>
</table>
<![endif]-->
</td>
</tr>
</tbody>
</table>
</div>
<!--[if mso | IE]>
</td>
</tr>
</table>
<![endif]-->
</td>
</tr>
<tr>
<td style="font-size:0px;word-break:break-word;">
<!--[if mso | IE]>
<table role="presentation" border="0" cellpadding="0" cellspacing="0"><tr><td height="30" style="vertical-align:top;height:30px;">
<![endif]-->
<div style="height:30px;"> &nbsp; </div>
<!--[if mso | IE]>
</td></tr></table>
<![endif]-->
</td>
</tr>
<tr>
<td style="font-size:0px;padding:0;word-break:break-word;">
<!--[if mso | IE]>
<table
align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600"
>
<tr>
<td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;">
<v:rect style="width:600px;" xmlns:v="urn:schemas-microsoft-com:vml" fill="true" stroke="false">
<v:fill origin="0.5, 0" position="0.5, 0" src="{{$banner}}" color="#F15822" type="tile" />
<v:textbox style="mso-fit-shape-to-text:true" inset="0,0,0,0">
<![endif]-->
<div style="background:#F15822 url({{$banner}}) top center / auto repeat;margin:0px auto;max-width:600px;">
<div style="line-height:0;font-size:0;">
<table align="center" background="{{$banner}}" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#F15822 url({{$banner}}) top center / auto repeat;width:100%;">
<tbody>
<tr>
<td style="direction:ltr;font-size:0px;padding:0;text-align:center;">
<!--[if mso | IE]>
<table role="presentation" border="0" cellpadding="0" cellspacing="0">
<tr>
<td
class="" style="vertical-align:top;width:600px;"
>
<![endif]-->
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
<tr>
<td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse:collapse;border-spacing:0px;">
<tbody>
<tr>
<td style="width:250px;"> <a href="https://www.sphero.com" target="_blank">
<img
height="auto" src="https://assets.statping.com/statpingcom.png" style="border:0;display:block;outline:none;text-decoration:none;height:auto;width:100%;font-size:13px;" width="250"
/>
</a> </td>
</tr>
</tbody>
</table>
</td>
</tr>
</table>
</div>
<!--[if mso | IE]>
</td>
</tr>
</table>
<![endif]-->
</td>
</tr>
</tbody>
</table>
</div>
</div>
<!--[if mso | IE]>
</v:textbox>
</v:rect>
</td>
</tr>
</table>
<![endif]-->
</td>
</tr>
<tr>
<td style="font-size:0px;padding:20px 0;padding-top:10px;padding-bottom:0;word-break:break-word;">
<!--[if mso | IE]>
<table
align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600"
>
<tr>
<td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;">
<![endif]-->
<div style="margin:0px auto;max-width:600px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
<tbody>
<tr>
<td style="direction:ltr;font-size:0px;padding:20px 0;padding-bottom:0;padding-top:10px;text-align:center;">
<!--[if mso | IE]>
<table role="presentation" border="0" cellpadding="0" cellspacing="0">
<tr>
<td
class="" style="vertical-align:top;width:600px;"
>
<![endif]-->
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
<tr>
<td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
<div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:11px;line-height:16px;text-align:center;color:#445566;">You are receiving this email because one of your services has changed on your Statping instance. You can modify this email on the Email Notifier page in Settings.</div>
</td>
</tr>
<tr>
<td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
<div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:11px;line-height:16px;text-align:center;color:#445566;">&copy; Statping</div>
</td>
</tr>
</table>
</div>
<!--[if mso | IE]>
</td>
</tr>
</table>
<![endif]-->
</td>
</tr>
</tbody>
</table>
</div>
<!--[if mso | IE]>
</td>
</tr>
</table>
<![endif]-->
</td>
</tr>
<tr>
<td style="font-size:0px;padding:20px 0;padding-top:0;padding-bottom:0;word-break:break-word;">
<!--[if mso | IE]>
<table
align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600"
>
<tr>
<td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;">
<![endif]-->
<div style="margin:0px auto;max-width:600px;">
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
<tbody>
<tr>
<td style="direction:ltr;font-size:0px;padding:20px 0;padding-bottom:0;padding-top:0;text-align:center;">
<!--[if mso | IE]>
<table role="presentation" border="0" cellpadding="0" cellspacing="0">
<tr>
<td
class="" style="width:600px;"
>
<![endif]-->
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0;line-height:0;text-align:left;display:inline-block;width:100%;direction:ltr;">
<!--[if mso | IE]>
<table
border="0" cellpadding="0" cellspacing="0" role="presentation"
>
<tr>
<td
style="vertical-align:top;width:600px;"
>
<![endif]-->
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%">
<tbody>
<tr>
<td style="vertical-align:top;padding-right:0;">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="" width="100%">
<tr>
<td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
<div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:11px;font-weight:bold;line-height:16px;text-align:center;color:#445566;"><a class="footer-link" href="https://statping.com">Statping.com</a>&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; <a class="footer-link" href="https://github.com/statping/statping">Github</a>&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;
<a class="footer-link" href="https://statping.com/privacy">Privacy</a>&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0;&#xA0; <a class="footer-link" href="https://www.google.com">Unsubscribe</a></div>
</td>
</tr>
</table>
</td>
</tr>
</tbody>
</table>
</div>
<!--[if mso | IE]>
</td>
</tr>
</table>
<![endif]-->
</div>
<!--[if mso | IE]>
</td>
</tr>
</table>
<![endif]-->
</td>
</tr>
</tbody>
</table>
</div>
<!--[if mso | IE]>
</td>
</tr>
</table>
<![endif]-->
</td>
</tr>
</table>
</div>
<!--[if mso | IE]>
</td>
</tr>
</table>
<![endif]-->
</td>
</tr>
</tbody>
</table>
</div>
<!--[if mso | IE]>
</td>
</tr>
</table>
<![endif]-->
</div>
</body>
</html>
`

View File

@ -71,3 +71,8 @@ func (l *lineNotifier) OnTest() (string, error) {
_, err := l.sendMessage(msg) _, err := l.sendMessage(msg)
return msg, err return msg, err
} }
// OnSave will trigger when this notifier is saved
func (l *lineNotifier) OnSave() (string, error) {
return "", nil
}

View File

@ -129,6 +129,11 @@ func (m *mobilePush) Send(pushMessage *pushArray) error {
return nil return nil
} }
// OnSave will trigger when this notifier is saved
func (m *mobilePush) OnSave() (string, error) {
return "", nil
}
func pushRequest(msg *pushArray) ([]byte, error) { func pushRequest(msg *pushArray) ([]byte, error) {
body, err := json.Marshal(&PushNotification{[]*pushArray{msg}}) body, err := json.Marshal(&PushNotification{[]*pushArray{msg}})
if err != nil { if err != nil {

View File

@ -16,6 +16,7 @@ type replacer struct {
Core *core.Core Core *core.Core
Service *services.Service Service *services.Service
Failure *failures.Failure Failure *failures.Failure
Custom map[string]string
} }
func InitNotifiers() { func InitNotifiers() {
@ -30,6 +31,7 @@ func InitNotifiers() {
Webhook, Webhook,
Mobile, Mobile,
Pushover, Pushover,
statpingMailer,
) )
} }

View File

@ -90,3 +90,8 @@ func (t *pushover) OnTest() (string, error) {
content, err := t.sendMessage(msg) content, err := t.sendMessage(msg)
return content, err return content, err
} }
// OnSave will trigger when this notifier is saved
func (t *pushover) OnSave() (string, error) {
return "", nil
}

View File

@ -86,3 +86,8 @@ func (s *slack) OnSuccess(srv *services.Service) (string, error) {
out, err := s.sendSlack(msg) out, err := s.sendSlack(msg)
return out, err return out, err
} }
// OnSave will trigger when this notifier is saved
func (s *slack) OnSave() (string, error) {
return "", nil
}

View File

@ -0,0 +1,102 @@
package notifiers
import (
"bytes"
"encoding/json"
"github.com/statping/statping/types/core"
"github.com/statping/statping/types/failures"
"github.com/statping/statping/types/notifications"
"github.com/statping/statping/types/notifier"
"github.com/statping/statping/types/services"
"github.com/statping/statping/utils"
"time"
)
var _ notifier.Notifier = (*statpingEmailer)(nil)
const (
statpingEmailerName = "statping_emailer"
statpingEmailerHost = "https://news.statping.com"
)
type statpingEmailer struct {
*notifications.Notification
}
func (s *statpingEmailer) Select() *notifications.Notification {
return s.Notification
}
var statpingMailer = &statpingEmailer{&notifications.Notification{
Method: statpingEmailerName,
Title: "Statping Emailer",
Description: "Send an email when a service becomes offline or back online using Statping's email service. You will need to verify your email address.",
Author: "Hunter Long",
AuthorUrl: "https://github.com/hunterlong",
Delay: time.Duration(10 * time.Second),
Icon: "fas envelope-square",
Limits: 60,
Form: []notifications.NotificationForm{{
Type: "email",
Title: "Send to Email Address",
Placeholder: "Insert your email address",
DbField: "Host",
Required: true,
}}},
}
// Send will send a HTTP Post to the slack webhooker API. It accepts type: string
func (s *statpingEmailer) sendStatpingEmail(msg statpingMail) (string, error) {
data, _ := json.Marshal(msg)
resp, _, err := utils.HttpRequest(statpingEmailerHost+"/notifier", "POST", "application/json", nil, bytes.NewBuffer(data), time.Duration(10*time.Second), true, nil)
if err != nil {
return "", err
}
return string(resp), nil
}
func (s *statpingEmailer) OnTest() (string, error) {
return "", nil
}
type statpingMail struct {
Email string `json:"email"`
Core *core.Core `json:"core,omitempty"`
Service *services.Service `json:"service,omitempty"`
Failure *failures.Failure `json:"failure,omitempty"`
}
// OnFailure will trigger failing service
func (s *statpingEmailer) OnFailure(srv *services.Service, f *failures.Failure) (string, error) {
ee := statpingMail{
Email: s.Host,
Core: core.App,
Service: srv,
Failure: f,
}
return s.sendStatpingEmail(ee)
}
// OnSuccess will trigger successful service
func (s *statpingEmailer) OnSuccess(srv *services.Service) (string, error) {
ee := statpingMail{
Email: s.Host,
Core: core.App,
Service: srv,
Failure: nil,
}
return s.sendStatpingEmail(ee)
}
// OnSave will trigger when this notifier is saved
func (s *statpingEmailer) OnSave() (string, error) {
ee := statpingMail{
Email: s.Host,
Core: core.App,
Service: nil,
Failure: nil,
}
out, err := s.sendStatpingEmail(ee)
log.Println("statping emailer response", out)
return out, err
}

View File

@ -0,0 +1,61 @@
package notifiers
import (
"github.com/statping/statping/database"
"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"
"testing"
"time"
)
var (
testEmail string
)
func TestStatpingEmailerNotifier(t *testing.T) {
err := utils.InitLogs()
require.Nil(t, err)
db, err := database.OpenTester()
require.Nil(t, err)
db.AutoMigrate(&notifications.Notification{})
notifications.SetDB(db)
testEmail = utils.Params.GetString("TEST_EMAIL")
statpingMailer.Host = testEmail
statpingMailer.Enabled = null.NewNullBool(true)
if testEmail == "" {
t.Log("statping email notifier testing skipped, missing TEST_EMAIL environment variable")
t.SkipNow()
}
t.Run("Load statping emailer", func(t *testing.T) {
statpingMailer.Host = testEmail
statpingMailer.Delay = time.Duration(100 * time.Millisecond)
statpingMailer.Limits = 3
Add(statpingMailer)
assert.Equal(t, "Hunter Long", statpingMailer.Author)
assert.Equal(t, testEmail, statpingMailer.Host)
})
t.Run("statping emailer Within Limits", func(t *testing.T) {
ok := statpingMailer.CanSend()
assert.True(t, ok)
})
t.Run("statping emailer OnFailure", func(t *testing.T) {
_, err := statpingMailer.OnFailure(services.Example(false), failures.Example())
assert.Nil(t, err)
})
t.Run("statping emailer OnSuccess", func(t *testing.T) {
_, err := statpingMailer.OnSuccess(services.Example(true))
assert.Nil(t, err)
})
}

View File

@ -94,6 +94,11 @@ func (t *telegram) OnTest() (string, error) {
return content, err return content, err
} }
// OnSave will trigger when this notifier is saved
func (t *telegram) OnSave() (string, error) {
return "", nil
}
func telegramSuccess(res []byte) (bool, telegramResponse) { func telegramSuccess(res []byte) (bool, telegramResponse) {
var obj telegramResponse var obj telegramResponse
json.Unmarshal(res, &obj) json.Unmarshal(res, &obj)

View File

@ -107,6 +107,11 @@ func (t *twilio) OnTest() (string, error) {
return t.sendMessage(msg) return t.sendMessage(msg)
} }
// OnSave will trigger when this notifier is saved
func (t *twilio) OnSave() (string, error) {
return "", nil
}
func twilioSuccess(res []byte) (bool, twilioResponse) { func twilioSuccess(res []byte) (bool, twilioResponse) {
var obj twilioResponse var obj twilioResponse
json.Unmarshal(res, &obj) json.Unmarshal(res, &obj)

View File

@ -148,3 +148,8 @@ func (w *webhooker) OnSuccess(s *services.Service) (string, error) {
content, err := ioutil.ReadAll(resp.Body) content, err := ioutil.ReadAll(resp.Body)
return string(content), err return string(content), err
} }
// OnSave will trigger when this notifier is saved
func (w *webhooker) OnSave() (string, error) {
return "", nil
}

View File

@ -10,4 +10,5 @@ type Notifier interface {
OnSuccess(*services.Service) (string, error) // OnSuccess is triggered when a service is successful OnSuccess(*services.Service) (string, error) // OnSuccess is triggered when a service is successful
OnFailure(*services.Service, *failures.Failure) (string, error) // OnFailure is triggered when a service is failing OnFailure(*services.Service, *failures.Failure) (string, error) // OnFailure is triggered when a service is failing
OnTest() (string, error) // OnTest is triggered for testing OnTest() (string, error) // OnTest is triggered for testing
OnSave() (string, error) // OnSave is triggered for when saved
} }

View File

@ -326,18 +326,13 @@ func (s *Service) OnlineSince(ago time.Time) float32 {
return s.Online24Hours return s.Online24Hours
} }
// Downtime returns the amount of time of a offline service func (s *Service) Uptime() utils.Duration {
func (s *Service) Downtime() time.Duration { return utils.Duration{Duration: utils.Now().Sub(s.LastOffline)}
hit := s.LastHit() }
fail := s.AllFailures().Last()
if hit == nil {
return time.Duration(0)
}
if fail == nil {
return utils.Now().Sub(hit.CreatedAt)
}
return fail.CreatedAt.Sub(hit.CreatedAt) // Downtime returns the amount of time of a offline service
func (s *Service) Downtime() utils.Duration {
return utils.Duration{Duration: utils.Now().Sub(s.LastOnline)}
} }
// ServiceOrder will reorder the services based on 'order_id' (Order) // ServiceOrder will reorder the services based on 'order_id' (Order)

View File

@ -29,5 +29,6 @@ type ServiceNotifier interface {
OnSuccess(*Service) (string, error) // OnSuccess is triggered when a service is successful OnSuccess(*Service) (string, error) // OnSuccess is triggered when a service is successful
OnFailure(*Service, *failures.Failure) (string, error) // OnFailure is triggered when a service is failing OnFailure(*Service, *failures.Failure) (string, error) // OnFailure is triggered when a service is failing
OnTest() (string, error) // OnTest is triggered for testing OnTest() (string, error) // OnTest is triggered for testing
OnSave() (string, error) // OnSave is triggered for testing
Select() *notifications.Notification // OnTest is triggered for testing Select() *notifications.Notification // OnTest is triggered for testing
} }

View File

@ -10,14 +10,14 @@ func Example(online bool) *Service {
return &Service{ return &Service{
Id: 6283, Id: 6283,
Name: "Statping Example", Name: "Statping Example",
Domain: "https://localhost:8080", Domain: "https://statping.com",
Expected: null.NewNullString(""), Expected: null.NewNullString(""),
ExpectedStatus: 200, ExpectedStatus: 200,
Interval: int(time.Duration(15 * time.Second).Seconds()), Interval: int(time.Duration(15 * time.Second).Seconds()),
Type: "http", Type: "http",
Method: "get", Method: "get",
PostData: null.NullString{}, PostData: null.NullString{},
Port: 0, Port: 443,
Timeout: int(time.Duration(2 * time.Second).Seconds()), Timeout: int(time.Duration(2 * time.Second).Seconds()),
Order: 0, Order: 0,
VerifySSL: null.NewNullBool(true), VerifySSL: null.NewNullBool(true),
@ -46,7 +46,7 @@ func Example(online bool) *Service {
AllowNotifications: null.NewNullBool(true), AllowNotifications: null.NewNullBool(true),
UserNotified: false, UserNotified: false,
UpdateNotify: null.NewNullBool(true), UpdateNotify: null.NewNullBool(true),
DownText: "The service ws responding with 500 status code", DownText: "The service was responding with 500 status code",
SuccessNotified: false, SuccessNotified: false,
LastStatusCode: 200, LastStatusCode: 200,
Failures: nil, Failures: nil,
@ -55,9 +55,7 @@ func Example(online bool) *Service {
LastLatency: 124399, LastLatency: 124399,
LastCheck: utils.Now().Add(-37 * time.Second), LastCheck: utils.Now().Add(-37 * time.Second),
LastOnline: utils.Now().Add(-37 * time.Second), LastOnline: utils.Now().Add(-37 * time.Second),
LastOffline: utils.Now().Add((-14 * 24) * time.Hour), LastOffline: utils.Now().Add(-75 * time.Second),
SecondsOnline: int64(utils.Now().Add(-24 * time.Hour).Second()),
SecondsOffline: int64(utils.Now().Add(-150 * time.Second).Second()),
} }
} }

View File

@ -432,7 +432,7 @@ func TestServices(t *testing.T) {
item, err := Find(1) item, err := Find(1)
require.Nil(t, err) require.Nil(t, err)
amount := item.Downtime().Seconds() amount := item.Downtime().Seconds()
assert.Equal(t, "25", fmt.Sprintf("%0.f", amount)) assert.Equal(t, "75", fmt.Sprintf("%0.f", amount))
}) })
t.Run("Test Failures Since", func(t *testing.T) { t.Run("Test Failures Since", func(t *testing.T) {

View File

@ -59,9 +59,6 @@ type Service struct {
LastOnline time.Time `gorm:"-" json:"last_success" yaml:"-"` LastOnline time.Time `gorm:"-" json:"last_success" yaml:"-"`
LastOffline time.Time `gorm:"-" json:"last_error" yaml:"-"` LastOffline time.Time `gorm:"-" json:"last_error" yaml:"-"`
Stats *Stats `gorm:"-" json:"stats,omitempty" yaml:"-"` Stats *Stats `gorm:"-" json:"stats,omitempty" yaml:"-"`
SecondsOnline int64 `gorm:"-" json:"-" yaml:"-"`
SecondsOffline int64 `gorm:"-" json:"-" yaml:"-"`
} }
type Stats struct { type Stats struct {

View File

@ -1,7 +1,7 @@
package utils package utils
import ( import (
"fmt" "github.com/hako/durafmt"
"time" "time"
) )
@ -10,58 +10,17 @@ func Now() time.Time {
return time.Now().UTC() return time.Now().UTC()
} }
type Duration struct {
time.Duration
}
func (d Duration) Human() string {
return durafmt.Parse(d.Duration).LimitFirstN(2).String()
}
// FormatDuration converts a time.Duration into a string // FormatDuration converts a time.Duration into a string
func FormatDuration(d time.Duration) string { func FormatDuration(d time.Duration) string {
var out string return durafmt.ParseShort(d).LimitFirstN(3).String()
if d.Hours() >= 24 {
out = fmt.Sprintf("%0.0f day", d.Hours()/24)
if (d.Hours() / 24) >= 2 {
out += "s"
}
return out
} else if d.Hours() >= 1 {
out = fmt.Sprintf("%0.0f hour", d.Hours())
if d.Hours() >= 2 {
out += "s"
}
return out
} else if d.Minutes() >= 1 {
out = fmt.Sprintf("%0.0f minute", d.Minutes())
if d.Minutes() >= 2 {
out += "s"
}
return out
} else if d.Seconds() >= 1 {
out = fmt.Sprintf("%0.0f second", d.Seconds())
if d.Seconds() >= 2 {
out += "s"
}
return out
} else if rev(d.Hours()) >= 24 {
out = fmt.Sprintf("%0.0f day", rev(d.Hours()/24))
if rev(d.Hours()/24) >= 2 {
out += "s"
}
return out
} else if rev(d.Hours()) >= 1 {
out = fmt.Sprintf("%0.0f hour", rev(d.Hours()))
if rev(d.Hours()) >= 2 {
out += "s"
}
return out
} else if rev(d.Minutes()) >= 1 {
out = fmt.Sprintf("%0.0f minute", rev(d.Minutes()))
if rev(d.Minutes()) >= 2 {
out += "s"
}
return out
} else {
out = fmt.Sprintf("%0.0f second", rev(d.Seconds()))
if rev(d.Seconds()) >= 2 {
out += "s"
}
}
return out
} }
func rev(f float64) float64 { func rev(f float64) float64 {

View File

@ -89,9 +89,9 @@ func TestDeleteFile(t *testing.T) {
func TestFormatDuration(t *testing.T) { func TestFormatDuration(t *testing.T) {
dur, _ := time.ParseDuration("158s") dur, _ := time.ParseDuration("158s")
assert.Equal(t, "3 minutes", FormatDuration(dur)) assert.Equal(t, "2 minutes 38 seconds", FormatDuration(dur))
dur, _ = time.ParseDuration("-65s") dur, _ = time.ParseDuration("-65s")
assert.Equal(t, "1 minute", FormatDuration(dur)) assert.Equal(t, "-1 minute 5 seconds", FormatDuration(dur))
dur, _ = time.ParseDuration("3s") dur, _ = time.ParseDuration("3s")
assert.Equal(t, "3 seconds", FormatDuration(dur)) assert.Equal(t, "3 seconds", FormatDuration(dur))
dur, _ = time.ParseDuration("48h") dur, _ = time.ParseDuration("48h")