mirror of https://github.com/statping/statping
Merge pull request #682 from statping/notifier-updates-fixes
Notifier Updates and UI Fixespull/687/head
commit
89ba7a6359
|
@ -135,6 +135,7 @@ jobs:
|
|||
TWILIO_SECRET: ${{ secrets.TWILIO_SECRET }}
|
||||
TWILIO_FROM: ${{ secrets.TWILIO_FROM }}
|
||||
TWILIO_TO: ${{ secrets.TWILIO_TO }}
|
||||
TEST_EMAIL: ${{ secrets.TEST_EMAIL }}
|
||||
|
||||
- name: Coveralls Testing Coverage
|
||||
run: |
|
||||
|
|
|
@ -135,6 +135,7 @@ jobs:
|
|||
TWILIO_SECRET: ${{ secrets.TWILIO_SECRET }}
|
||||
TWILIO_FROM: ${{ secrets.TWILIO_FROM }}
|
||||
TWILIO_TO: ${{ secrets.TWILIO_TO }}
|
||||
TEST_EMAIL: ${{ secrets.TEST_EMAIL }}
|
||||
|
||||
- name: Coveralls Testing Coverage
|
||||
run: |
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
# 0.90.55 (06-18-2020)
|
||||
- Added 404 page
|
||||
- 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)
|
||||
- Fixed Slack Notifier's failure/success data saving issue
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
{{$t('dashboard.wrong_login')}}
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -89,12 +89,12 @@
|
|||
</button>
|
||||
</div>
|
||||
<div class="col-4 col-md-4">
|
||||
<button @click.prevent="testNotifier('success')" class="btn btn-outline-dark btn-block text-capitalize test-notifier">
|
||||
<i class="fa fa-vial"></i>{{loadingTest ? "Loading..." : "Test Success"}}</button>
|
||||
<button @click.prevent="testNotifier('success')" :disabled="loadingTest" class="btn btn-outline-dark btn-block text-capitalize test-notifier">
|
||||
<font-awesome-icon v-if="loadingTest" icon="circle-notch" class="mr-2" spin/>{{loadingTest ? "Loading..." : "Test Success"}}</button>
|
||||
</div>
|
||||
<div class="col-4 col-md-4">
|
||||
<button @click.prevent="testNotifier('failure')" class="btn btn-outline-dark btn-block text-capitalize test-notifier">
|
||||
<i class="fa fa-vial"></i>{{loadingTest ? "Loading..." : "Test Failure"}}</button>
|
||||
<button @click.prevent="testNotifier('failure')" :disabled="loadingTest" class="btn btn-outline-dark btn-block text-capitalize test-notifier">
|
||||
<font-awesome-icon v-if="loadingTest" icon="circle-notch" class="mr-2" spin/>{{loadingTest ? "Loading..." : "Test Failure"}}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -150,7 +150,6 @@
|
|||
return {
|
||||
tab: "v-pills-home-tab",
|
||||
qrcode: "",
|
||||
qrurl: "",
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
@ -174,8 +173,8 @@
|
|||
const n = await Api.notifiers()
|
||||
this.$store.commit('setNotifiers', n)
|
||||
|
||||
this.qrurl = `statping://setup?domain=${c.domain}&api=${c.api_secret}`
|
||||
this.qrcode = "https://chart.googleapis.com/chart?chs=500x500&cht=qr&chl=" + encodeURI(this.qrurl)
|
||||
const u = `statping://setup?domain=${c.domain}&api=${c.api_secret}`
|
||||
this.qrcode = "https://chart.googleapis.com/chart?chs=500x500&cht=qr&chl=" + encodeURIComponent(u)
|
||||
this.cache = await Api.cache()
|
||||
},
|
||||
changeTab(e) {
|
||||
|
|
1
go.mod
1
go.mod
|
@ -15,6 +15,7 @@ require (
|
|||
github.com/golang/protobuf v1.3.5 // indirect
|
||||
github.com/gorilla/mux v1.7.4
|
||||
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/magiconair/properties v1.8.1
|
||||
github.com/mattn/go-sqlite3 v2.0.3+incompatible
|
||||
|
|
2
go.sum
2
go.sum
|
@ -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-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/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/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
|
|
|
@ -248,8 +248,6 @@ func TestMainApiRoutes(t *testing.T) {
|
|||
ExpectedContains: []string{
|
||||
`go_goroutines`,
|
||||
`go_memstats_alloc_bytes`,
|
||||
`process_cpu_seconds_total`,
|
||||
`promhttp_metric_handler_requests_total`,
|
||||
`go_threads`,
|
||||
},
|
||||
},
|
||||
|
|
|
@ -53,7 +53,13 @@ func apiNotifierUpdateHandler(w http.ResponseWriter, r *http.Request) {
|
|||
sendErrorJson(err, w, r)
|
||||
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)
|
||||
}
|
||||
|
||||
|
|
|
@ -70,3 +70,8 @@ func (c *commandLine) OnTest() (string, error) {
|
|||
utils.Log.Infoln(out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// OnSave will trigger when this notifier is saved
|
||||
func (c *commandLine) OnSave() (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
|
|
@ -52,15 +52,13 @@ func (d *discord) Select() *notifications.Notification {
|
|||
|
||||
// OnFailure will trigger failing service
|
||||
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(msg, s, f))
|
||||
out, err := d.sendRequest(ReplaceVars(d.FailureData, s, f))
|
||||
return out, err
|
||||
}
|
||||
|
||||
// OnSuccess will trigger successful service
|
||||
func (d *discord) OnSuccess(s *services.Service) (string, error) {
|
||||
msg := `{"content": "Your service '{{.Service.Name}}' is currently online!"}`
|
||||
out, err := d.sendRequest(ReplaceVars(msg, s, nil))
|
||||
out, err := d.sendRequest(ReplaceVars(d.SuccessData, s, nil))
|
||||
return out, err
|
||||
}
|
||||
|
||||
|
@ -83,6 +81,11 @@ func (d *discord) OnTest() (string, error) {
|
|||
return string(contents), nil
|
||||
}
|
||||
|
||||
// OnSave will trigger when this notifier is saved
|
||||
func (d *discord) OnSave() (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
type discordTestJson struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
|
|
|
@ -1,96 +1,21 @@
|
|||
package notifiers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"github.com/go-mail/mail"
|
||||
"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/null"
|
||||
"github.com/statping/statping/types/services"
|
||||
"github.com/statping/statping/utils"
|
||||
"time"
|
||||
"html/template"
|
||||
)
|
||||
|
||||
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 (
|
||||
mailer *mail.Dialer
|
||||
)
|
||||
|
@ -110,9 +35,6 @@ var email = &emailer{¬ifications.Notification{
|
|||
Author: "Hunter Long",
|
||||
AuthorUrl: "https://github.com/hunterlong",
|
||||
Icon: "far fa-envelope",
|
||||
SuccessData: "Service {{.Service.Name}} is Back Online",
|
||||
FailureData: "Service {{.Service.Name}} is Offline",
|
||||
DataType: "text",
|
||||
Limits: 30,
|
||||
Form: []notifications.NotificationForm{{
|
||||
Type: "text",
|
||||
|
@ -145,7 +67,7 @@ var email = &emailer{¬ifications.Notification{
|
|||
Placeholder: "sendto@email.com",
|
||||
DbField: "Var2",
|
||||
}, {
|
||||
Type: "text",
|
||||
Type: "switch",
|
||||
Title: "Disable TLS/SSL",
|
||||
Placeholder: "",
|
||||
SmallText: "To Disable TLS/SSL insert 'true'",
|
||||
|
@ -166,64 +88,69 @@ type emailOutgoing struct {
|
|||
// OnFailure will trigger failing service
|
||||
func (e *emailer) OnFailure(s *services.Service, f *failures.Failure) (string, error) {
|
||||
subject := fmt.Sprintf("Service %s is Offline", s.Name)
|
||||
tmpl := renderEmail(s, f)
|
||||
email := &emailOutgoing{
|
||||
To: e.Var2,
|
||||
Subject: subject,
|
||||
Template: mainEmailTemplate,
|
||||
Data: replacer{
|
||||
Service: s,
|
||||
Failure: f,
|
||||
},
|
||||
From: e.Var1,
|
||||
Template: tmpl,
|
||||
From: e.Var1,
|
||||
}
|
||||
return "email failed", e.dialSend(email)
|
||||
return tmpl, e.dialSend(email)
|
||||
}
|
||||
|
||||
// OnSuccess will trigger successful service
|
||||
func (e *emailer) OnSuccess(s *services.Service) (string, error) {
|
||||
subject := fmt.Sprintf("Service %s is Back Online", s.Name)
|
||||
tmpl := renderEmail(s, nil)
|
||||
email := &emailOutgoing{
|
||||
To: e.Var2,
|
||||
Subject: subject,
|
||||
Template: mainEmailTemplate,
|
||||
Data: replacer{
|
||||
Service: s,
|
||||
Failure: &failures.Failure{},
|
||||
},
|
||||
From: e.Var1,
|
||||
Template: tmpl,
|
||||
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
|
||||
func (e *emailer) OnTest() (string, error) {
|
||||
testService := services.Service{
|
||||
Id: 1,
|
||||
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)
|
||||
service := services.Example(true)
|
||||
subject := fmt.Sprintf("Service %v is Back Online", service.Name)
|
||||
email := &emailOutgoing{
|
||||
To: e.Var2,
|
||||
Subject: subject,
|
||||
Template: mainEmailTemplate,
|
||||
Data: replacer{
|
||||
Service: &testService,
|
||||
Failure: &failures.Failure{},
|
||||
},
|
||||
From: e.Var1,
|
||||
Template: renderEmail(service, failures.Example()),
|
||||
From: e.Var1,
|
||||
}
|
||||
err := e.dialSend(email)
|
||||
return subject, err
|
||||
return subject, e.dialSend(email)
|
||||
}
|
||||
|
||||
// OnSave will trigger when this notifier is saved
|
||||
func (e *emailer) OnSave() (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (e *emailer) dialSend(email *emailOutgoing) error {
|
||||
|
@ -236,14 +163,15 @@ func (e *emailer) dialSend(email *emailOutgoing) error {
|
|||
mailer.TLSConfig = &tls.Config{InsecureSkipVerify: true}
|
||||
}
|
||||
|
||||
m.SetHeader("From", email.From)
|
||||
m.SetAddressHeader("From", email.From, "Statping")
|
||||
m.SetHeader("To", email.To)
|
||||
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 {
|
||||
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 nil
|
||||
}
|
||||
|
|
|
@ -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;"> </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;">© 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>         <a class="footer-link" href="https://github.com/statping/statping">Github</a>        
|
||||
<a class="footer-link" href="https://statping.com/privacy">Privacy</a>         <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>
|
||||
`
|
|
@ -71,3 +71,8 @@ func (l *lineNotifier) OnTest() (string, error) {
|
|||
_, err := l.sendMessage(msg)
|
||||
return msg, err
|
||||
}
|
||||
|
||||
// OnSave will trigger when this notifier is saved
|
||||
func (l *lineNotifier) OnSave() (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
|
|
@ -129,6 +129,11 @@ func (m *mobilePush) Send(pushMessage *pushArray) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// OnSave will trigger when this notifier is saved
|
||||
func (m *mobilePush) OnSave() (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func pushRequest(msg *pushArray) ([]byte, error) {
|
||||
body, err := json.Marshal(&PushNotification{[]*pushArray{msg}})
|
||||
if err != nil {
|
||||
|
|
|
@ -16,6 +16,7 @@ type replacer struct {
|
|||
Core *core.Core
|
||||
Service *services.Service
|
||||
Failure *failures.Failure
|
||||
Custom map[string]string
|
||||
}
|
||||
|
||||
func InitNotifiers() {
|
||||
|
@ -30,6 +31,7 @@ func InitNotifiers() {
|
|||
Webhook,
|
||||
Mobile,
|
||||
Pushover,
|
||||
statpingMailer,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -90,3 +90,8 @@ func (t *pushover) OnTest() (string, error) {
|
|||
content, err := t.sendMessage(msg)
|
||||
return content, err
|
||||
}
|
||||
|
||||
// OnSave will trigger when this notifier is saved
|
||||
func (t *pushover) OnSave() (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
|
|
@ -86,3 +86,8 @@ func (s *slack) OnSuccess(srv *services.Service) (string, error) {
|
|||
out, err := s.sendSlack(msg)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// OnSave will trigger when this notifier is saved
|
||||
func (s *slack) OnSave() (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
|
|
@ -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{¬ifications.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
|
||||
}
|
|
@ -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(¬ifications.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)
|
||||
})
|
||||
|
||||
}
|
|
@ -94,6 +94,11 @@ func (t *telegram) OnTest() (string, error) {
|
|||
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) {
|
||||
var obj telegramResponse
|
||||
json.Unmarshal(res, &obj)
|
||||
|
|
|
@ -107,6 +107,11 @@ func (t *twilio) OnTest() (string, error) {
|
|||
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) {
|
||||
var obj twilioResponse
|
||||
json.Unmarshal(res, &obj)
|
||||
|
|
|
@ -148,3 +148,8 @@ func (w *webhooker) OnSuccess(s *services.Service) (string, error) {
|
|||
content, err := ioutil.ReadAll(resp.Body)
|
||||
return string(content), err
|
||||
}
|
||||
|
||||
// OnSave will trigger when this notifier is saved
|
||||
func (w *webhooker) OnSave() (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
|
|
@ -10,4 +10,5 @@ type Notifier interface {
|
|||
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
|
||||
OnTest() (string, error) // OnTest is triggered for testing
|
||||
OnSave() (string, error) // OnSave is triggered for when saved
|
||||
}
|
||||
|
|
|
@ -326,18 +326,13 @@ func (s *Service) OnlineSince(ago time.Time) float32 {
|
|||
return s.Online24Hours
|
||||
}
|
||||
|
||||
// Downtime returns the amount of time of a offline service
|
||||
func (s *Service) Downtime() time.Duration {
|
||||
hit := s.LastHit()
|
||||
fail := s.AllFailures().Last()
|
||||
if hit == nil {
|
||||
return time.Duration(0)
|
||||
}
|
||||
if fail == nil {
|
||||
return utils.Now().Sub(hit.CreatedAt)
|
||||
}
|
||||
func (s *Service) Uptime() utils.Duration {
|
||||
return utils.Duration{Duration: utils.Now().Sub(s.LastOffline)}
|
||||
}
|
||||
|
||||
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)
|
||||
|
|
|
@ -29,5 +29,6 @@ type ServiceNotifier interface {
|
|||
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
|
||||
OnTest() (string, error) // OnTest is triggered for testing
|
||||
OnSave() (string, error) // OnSave is triggered for testing
|
||||
Select() *notifications.Notification // OnTest is triggered for testing
|
||||
}
|
||||
|
|
|
@ -10,14 +10,14 @@ func Example(online bool) *Service {
|
|||
return &Service{
|
||||
Id: 6283,
|
||||
Name: "Statping Example",
|
||||
Domain: "https://localhost:8080",
|
||||
Domain: "https://statping.com",
|
||||
Expected: null.NewNullString(""),
|
||||
ExpectedStatus: 200,
|
||||
Interval: int(time.Duration(15 * time.Second).Seconds()),
|
||||
Type: "http",
|
||||
Method: "get",
|
||||
PostData: null.NullString{},
|
||||
Port: 0,
|
||||
Port: 443,
|
||||
Timeout: int(time.Duration(2 * time.Second).Seconds()),
|
||||
Order: 0,
|
||||
VerifySSL: null.NewNullBool(true),
|
||||
|
@ -46,7 +46,7 @@ func Example(online bool) *Service {
|
|||
AllowNotifications: null.NewNullBool(true),
|
||||
UserNotified: false,
|
||||
UpdateNotify: null.NewNullBool(true),
|
||||
DownText: "The service ws responding with 500 status code",
|
||||
DownText: "The service was responding with 500 status code",
|
||||
SuccessNotified: false,
|
||||
LastStatusCode: 200,
|
||||
Failures: nil,
|
||||
|
@ -55,9 +55,7 @@ func Example(online bool) *Service {
|
|||
LastLatency: 124399,
|
||||
LastCheck: utils.Now().Add(-37 * time.Second),
|
||||
LastOnline: utils.Now().Add(-37 * time.Second),
|
||||
LastOffline: utils.Now().Add((-14 * 24) * time.Hour),
|
||||
SecondsOnline: int64(utils.Now().Add(-24 * time.Hour).Second()),
|
||||
SecondsOffline: int64(utils.Now().Add(-150 * time.Second).Second()),
|
||||
LastOffline: utils.Now().Add(-75 * time.Second),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -432,7 +432,7 @@ func TestServices(t *testing.T) {
|
|||
item, err := Find(1)
|
||||
require.Nil(t, err)
|
||||
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) {
|
||||
|
|
|
@ -59,9 +59,6 @@ type Service struct {
|
|||
LastOnline time.Time `gorm:"-" json:"last_success" yaml:"-"`
|
||||
LastOffline time.Time `gorm:"-" json:"last_error" yaml:"-"`
|
||||
Stats *Stats `gorm:"-" json:"stats,omitempty" yaml:"-"`
|
||||
|
||||
SecondsOnline int64 `gorm:"-" json:"-" yaml:"-"`
|
||||
SecondsOffline int64 `gorm:"-" json:"-" yaml:"-"`
|
||||
}
|
||||
|
||||
type Stats struct {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/hako/durafmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
|
@ -10,58 +10,17 @@ func Now() time.Time {
|
|||
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
|
||||
func FormatDuration(d time.Duration) string {
|
||||
var out 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
|
||||
return durafmt.ParseShort(d).LimitFirstN(3).String()
|
||||
}
|
||||
|
||||
func rev(f float64) float64 {
|
||||
|
|
|
@ -89,9 +89,9 @@ func TestDeleteFile(t *testing.T) {
|
|||
|
||||
func TestFormatDuration(t *testing.T) {
|
||||
dur, _ := time.ParseDuration("158s")
|
||||
assert.Equal(t, "3 minutes", FormatDuration(dur))
|
||||
assert.Equal(t, "2 minutes 38 seconds", FormatDuration(dur))
|
||||
dur, _ = time.ParseDuration("-65s")
|
||||
assert.Equal(t, "1 minute", FormatDuration(dur))
|
||||
assert.Equal(t, "-1 minute 5 seconds", FormatDuration(dur))
|
||||
dur, _ = time.ParseDuration("3s")
|
||||
assert.Equal(t, "3 seconds", FormatDuration(dur))
|
||||
dur, _ = time.ParseDuration("48h")
|
||||
|
|
Loading…
Reference in New Issue