mirror of https://github.com/statping/statping
notifier testing - doc.go added - timezone column
parent
e39bf6ef0a
commit
f144b8cec1
|
@ -0,0 +1,12 @@
|
|||
// Package main for building the Statup CLI binary application. This package
|
||||
// connects to all the other packages to make a runnable binary for multiple
|
||||
// operating system.
|
||||
//
|
||||
// To build Statup from source, run the follow command in the root directory:
|
||||
// // go build -o statup ./cmd
|
||||
//
|
||||
// Remember that you'll need to compile the static assets using Rice:
|
||||
// // cd source && rice embed-go
|
||||
//
|
||||
// by Hunter Long
|
||||
package main
|
|
@ -0,0 +1,6 @@
|
|||
// Package core contains the main functionality of Statup. This includes everything for
|
||||
// Services, Hits, Failures, Users, service checking mechanisms, databases, and notifiers
|
||||
// in the notifier package
|
||||
//
|
||||
// by Hunter Long
|
||||
package core
|
|
@ -262,7 +262,8 @@ CheckNotifier:
|
|||
case <-time.After(rateLimit):
|
||||
notification = n.Select()
|
||||
if len(notification.Queue) > 0 {
|
||||
if notification.WithinLimits() {
|
||||
ok, _ := notification.WithinLimits()
|
||||
if ok {
|
||||
msg := notification.Queue[0]
|
||||
err := n.Send(msg)
|
||||
if err != nil {
|
||||
|
@ -371,23 +372,27 @@ func isEnabled(n interface{}) bool {
|
|||
|
||||
func inLimits(n interface{}) bool {
|
||||
notifier := n.(Notifier).Select()
|
||||
return notifier.WithinLimits()
|
||||
ok, _ := notifier.WithinLimits()
|
||||
return ok
|
||||
}
|
||||
|
||||
func (notify *Notification) WithinLimits() bool {
|
||||
func (notify *Notification) WithinLimits() (bool, error) {
|
||||
if notify.SentLastMinute() == 0 {
|
||||
return true, nil
|
||||
}
|
||||
if notify.SentLastMinute() >= notify.Limits {
|
||||
return false
|
||||
return false, errors.New(fmt.Sprintf("notifier sent %v out of %v in last minute", notify.SentLastMinute(), notify.Limits))
|
||||
}
|
||||
if notify.Delay.Seconds() == 0 {
|
||||
notify.Delay = time.Duration(500 * time.Millisecond)
|
||||
}
|
||||
if notify.LastSent().Seconds() == 0 {
|
||||
return true
|
||||
return true, nil
|
||||
}
|
||||
if notify.Delay.Seconds() >= notify.LastSent().Seconds() {
|
||||
return false
|
||||
return false, errors.New(fmt.Sprintf("notifiers delay (%v) is greater than last message sent (%v)", notify.Delay.Seconds(), notify.LastSent().Seconds()))
|
||||
}
|
||||
return true
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (n *Notification) ResetQueue() {
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
// Package handlers holds all the HTTP requests and routes. All HTTP related
|
||||
// functions are in this package.
|
||||
//
|
||||
// by Hunter Long
|
||||
package handlers
|
|
@ -51,19 +51,7 @@ func Router() *mux.Router {
|
|||
r.Handle("/charts/{id}.js", http.HandlerFunc(RenderServiceChartHandler))
|
||||
r.Handle("/setup", http.HandlerFunc(SetupHandler)).Methods("GET")
|
||||
r.Handle("/setup", http.HandlerFunc(ProcessSetupHandler)).Methods("POST")
|
||||
r.Handle("/dashboard", http.HandlerFunc(DashboardHandler)).Methods("GET")
|
||||
r.Handle("/dashboard", http.HandlerFunc(LoginHandler)).Methods("POST")
|
||||
r.Handle("/logout", http.HandlerFunc(LogoutHandler))
|
||||
r.Handle("/services", http.HandlerFunc(ServicesHandler)).Methods("GET")
|
||||
r.Handle("/services", http.HandlerFunc(CreateServiceHandler)).Methods("POST")
|
||||
r.Handle("/services/reorder", http.HandlerFunc(ReorderServiceHandler)).Methods("POST")
|
||||
r.Handle("/service/{id}", http.HandlerFunc(ServicesViewHandler)).Methods("GET")
|
||||
r.Handle("/service/{id}", http.HandlerFunc(ServicesUpdateHandler)).Methods("POST")
|
||||
r.Handle("/service/{id}/edit", http.HandlerFunc(ServicesViewHandler))
|
||||
r.Handle("/service/{id}/delete", http.HandlerFunc(ServicesDeleteHandler))
|
||||
r.Handle("/service/{id}/delete_failures", http.HandlerFunc(ServicesDeleteFailuresHandler)).Methods("GET")
|
||||
r.Handle("/service/{id}/checkin", http.HandlerFunc(CheckinCreateUpdateHandler)).Methods("POST")
|
||||
r.Handle("/users", http.HandlerFunc(UsersHandler)).Methods("GET")
|
||||
r.Handle("/dashboard", http.HandlerFunc(DashboardHandler)).Methods("GET").HandlerFunc(UsersHandler).Methods("GET")
|
||||
r.Handle("/users", http.HandlerFunc(CreateUserHandler)).Methods("POST")
|
||||
r.Handle("/user/{id}", http.HandlerFunc(UsersEditHandler)).Methods("GET")
|
||||
r.Handle("/user/{id}", http.HandlerFunc(UpdateUserHandler)).Methods("POST")
|
||||
|
|
|
@ -59,7 +59,6 @@ func init() {
|
|||
|
||||
func (u *Discord) Send(msg interface{}) error {
|
||||
message := msg.([]byte)
|
||||
fmt.Println("sending: ", message)
|
||||
req, _ := http.NewRequest("POST", discorder.GetValue("host"), bytes.NewBuffer(message))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
client := &http.Client{}
|
||||
|
@ -76,7 +75,6 @@ func (u *Discord) Select() *notifier.Notification {
|
|||
|
||||
func (u *Discord) OnFailure(s *types.Service, f *types.Failure) {
|
||||
msg := fmt.Sprintf(`{"content": "Your service '%v' is currently failing! Reason: %v"}`, s.Name, f.Issue)
|
||||
fmt.Println(msg)
|
||||
u.AddQueue(msg)
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
package notifiers
|
||||
|
||||
import (
|
||||
"github.com/hunterlong/statup/core/notifier"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
DISCORD_URL = os.Getenv("DISCORD_URL")
|
||||
discordMessage = `{"content": "The Discord notifier on Statup has been tested!"}`
|
||||
)
|
||||
|
||||
func init() {
|
||||
discorder.Host = DISCORD_URL
|
||||
}
|
||||
|
||||
func TestDiscordNotifier(t *testing.T) {
|
||||
if DISCORD_URL == "" {
|
||||
t.Log("Discord notifier testing skipped, missing DISCORD_URL environment variable")
|
||||
t.SkipNow()
|
||||
}
|
||||
currentCount = CountNotifiers()
|
||||
|
||||
t.Run("Load Discord", func(t *testing.T) {
|
||||
discorder.Host = DISCORD_URL
|
||||
discorder.Delay = time.Duration(100 * time.Millisecond)
|
||||
err := notifier.AddNotifier(discorder)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "Hunter Long", discorder.Author)
|
||||
assert.Equal(t, DISCORD_URL, discorder.Host)
|
||||
assert.Equal(t, currentCount+1, CountNotifiers())
|
||||
})
|
||||
|
||||
t.Run("Load Discord Notifier", func(t *testing.T) {
|
||||
count := notifier.Load()
|
||||
assert.Equal(t, currentCount+1, len(count))
|
||||
})
|
||||
|
||||
t.Run("Discord Within Limits", func(t *testing.T) {
|
||||
ok, err := discorder.WithinLimits()
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, ok)
|
||||
})
|
||||
|
||||
t.Run("Discord Send", func(t *testing.T) {
|
||||
err := discorder.Send([]byte(discordMessage))
|
||||
assert.Nil(t, err)
|
||||
})
|
||||
|
||||
t.Run("Discord Queue", func(t *testing.T) {
|
||||
go notifier.Queue(discorder)
|
||||
time.Sleep(1 * time.Second)
|
||||
assert.Equal(t, DISCORD_URL, discorder.Host)
|
||||
assert.Equal(t, 0, len(discorder.Queue))
|
||||
})
|
||||
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
// Package notifiers holds all the notifiers for Statup, which also includes
|
||||
// user created notifiers that have been accepted in a Push Request. Read the
|
||||
// Statup notifier wiki at: https://github.com/hunterlong/statup/wiki
|
||||
//
|
||||
// To see a full example of a notifier with all events, visit Statup's
|
||||
// notifier example code: https://github.com/hunterlong/statup/wiki/Notifier-Example
|
||||
//
|
||||
// by Hunter Long
|
||||
package notifiers
|
|
@ -17,6 +17,7 @@ package notifiers
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"github.com/hunterlong/statup/core/notifier"
|
||||
"github.com/hunterlong/statup/types"
|
||||
|
@ -85,12 +86,12 @@ func init() {
|
|||
}
|
||||
|
||||
func (u *Email) Send(msg interface{}) error {
|
||||
//email := msg.(*EmailOutgoing)
|
||||
//err := u.dialSend(email)
|
||||
//if err != nil {
|
||||
// utils.Log(3, fmt.Sprintf("Email Notifier could not send email: %v", err))
|
||||
// return err
|
||||
//}
|
||||
email := msg.(*EmailOutgoing)
|
||||
err := u.dialSend(email)
|
||||
if err != nil {
|
||||
utils.Log(3, fmt.Sprintf("Email Notifier could not send email: %v", err))
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -145,6 +146,9 @@ func (u *Email) OnSave() error {
|
|||
}
|
||||
|
||||
func (u *Email) dialSend(email *EmailOutgoing) error {
|
||||
mailer = gomail.NewDialer(emailer.Host, emailer.Port, emailer.Username, emailer.Password)
|
||||
mailer.TLSConfig = &tls.Config{InsecureSkipVerify: true}
|
||||
emailSource(email)
|
||||
m := gomail.NewMessage()
|
||||
m.SetHeader("From", email.From)
|
||||
m.SetHeader("To", email.To)
|
||||
|
@ -157,7 +161,7 @@ func (u *Email) dialSend(email *EmailOutgoing) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func SendEmail(email *EmailOutgoing) {
|
||||
func emailSource(email *EmailOutgoing) {
|
||||
source := EmailTemplate(email.Template, email.Data)
|
||||
email.Source = source
|
||||
}
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
package notifiers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/hunterlong/statup/core/notifier"
|
||||
"github.com/hunterlong/statup/utils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
EMAIL_HOST = os.Getenv("EMAIL_HOST")
|
||||
EMAIL_USER = os.Getenv("EMAIL_USER")
|
||||
EMAIL_PASS = os.Getenv("EMAIL_PASS")
|
||||
EMAIL_OUTGOING = os.Getenv("EMAIL_OUTGOING")
|
||||
EMAIL_SEND_TO = os.Getenv("EMAIL_SEND_TO")
|
||||
EMAIL_PORT = utils.StringInt(os.Getenv("EMAIL_PORT"))
|
||||
)
|
||||
|
||||
var testEmail *EmailOutgoing
|
||||
|
||||
func init() {
|
||||
emailer.Host = EMAIL_HOST
|
||||
emailer.Username = EMAIL_USER
|
||||
emailer.Password = EMAIL_PASS
|
||||
emailer.Var1 = EMAIL_OUTGOING
|
||||
emailer.Var2 = EMAIL_SEND_TO
|
||||
emailer.Port = int(EMAIL_PORT)
|
||||
}
|
||||
|
||||
func TestEmailNotifier(t *testing.T) {
|
||||
if EMAIL_HOST == "" || EMAIL_USER == "" || EMAIL_PASS == "" {
|
||||
t.Log("Email notifier testing skipped, missing EMAIL_ environment variables")
|
||||
t.SkipNow()
|
||||
}
|
||||
currentCount = CountNotifiers()
|
||||
|
||||
t.Run("New Emailer", func(t *testing.T) {
|
||||
emailer.Host = EMAIL_HOST
|
||||
emailer.Username = EMAIL_USER
|
||||
emailer.Password = EMAIL_PASS
|
||||
emailer.Var1 = EMAIL_OUTGOING
|
||||
emailer.Var2 = EMAIL_SEND_TO
|
||||
emailer.Port = int(EMAIL_PORT)
|
||||
emailer.Delay = time.Duration(100 * time.Millisecond)
|
||||
|
||||
message := "this is a test email!"
|
||||
|
||||
testEmail = &EmailOutgoing{
|
||||
To: emailer.GetValue("var2"),
|
||||
Subject: fmt.Sprintf("Service %v is Failing", TestService.Name),
|
||||
Template: MESSAGE,
|
||||
Data: interface{}(message),
|
||||
From: emailer.GetValue("var1"),
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Add Email Notifier", func(t *testing.T) {
|
||||
err := notifier.AddNotifier(emailer)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "Hunter Long", emailer.Author)
|
||||
assert.Equal(t, EMAIL_HOST, emailer.Host)
|
||||
assert.Equal(t, currentCount+1, CountNotifiers())
|
||||
})
|
||||
|
||||
t.Run("Emailer Load", func(t *testing.T) {
|
||||
count := notifier.Load()
|
||||
assert.Equal(t, currentCount+1, len(count))
|
||||
})
|
||||
|
||||
t.Run("Email Within Limits", func(t *testing.T) {
|
||||
ok, err := emailer.WithinLimits()
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, ok)
|
||||
})
|
||||
|
||||
t.Run("Emailer Test Source", func(t *testing.T) {
|
||||
emailSource(testEmail)
|
||||
assert.NotEmpty(t, testEmail.Source)
|
||||
})
|
||||
|
||||
t.Run("Email Send", func(t *testing.T) {
|
||||
err := emailer.Send(testEmail)
|
||||
assert.Nil(t, err)
|
||||
})
|
||||
|
||||
t.Run("Email Run Queue", func(t *testing.T) {
|
||||
go notifier.Queue(emailer)
|
||||
time.Sleep(5 * time.Second)
|
||||
assert.Equal(t, EMAIL_HOST, emailer.Host)
|
||||
assert.Equal(t, 0, len(emailer.Queue))
|
||||
})
|
||||
|
||||
}
|
|
@ -1,69 +0,0 @@
|
|||
<!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>Sample 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 Offline!</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">
|
||||
Your Statup 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>
|
||||
|
||||
{{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;">Statup Dashboard</a>
|
||||
</td>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
|
@ -1,61 +0,0 @@
|
|||
<!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>Sample 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">Looks Like Emails Work!</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">
|
||||
Since you got this email, it confirms that your Statup Status Page email system is working correctly.
|
||||
</p>
|
||||
</p>
|
||||
<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">
|
||||
Enjoy using Statup!
|
||||
<br />Statup.io Team</p>
|
||||
|
||||
<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;">
|
||||
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,60 @@
|
|||
package notifiers
|
||||
|
||||
import (
|
||||
"github.com/hunterlong/statup/core/notifier"
|
||||
"github.com/hunterlong/statup/source"
|
||||
"github.com/hunterlong/statup/types"
|
||||
"github.com/hunterlong/statup/utils"
|
||||
"github.com/jinzhu/gorm"
|
||||
_ "github.com/jinzhu/gorm/dialects/sqlite"
|
||||
)
|
||||
|
||||
var (
|
||||
dir string
|
||||
db *gorm.DB
|
||||
currentCount int
|
||||
)
|
||||
|
||||
var TestService = &types.Service{
|
||||
Name: "Interpol - All The Rage Back Home",
|
||||
Domain: "https://www.youtube.com/watch?v=-u6DvRyyKGU",
|
||||
ExpectedStatus: 200,
|
||||
Interval: 30,
|
||||
Type: "http",
|
||||
Method: "GET",
|
||||
Timeout: 20,
|
||||
}
|
||||
|
||||
var TestFailure = &types.Failure{
|
||||
Issue: "testing",
|
||||
}
|
||||
|
||||
var TestUser = &types.User{
|
||||
Username: "admin",
|
||||
Email: "info@email.com",
|
||||
}
|
||||
|
||||
var TestCore = &types.Core{
|
||||
Name: "testing notifiers",
|
||||
}
|
||||
|
||||
func CountNotifiers() int {
|
||||
return len(notifier.AllCommunications)
|
||||
}
|
||||
|
||||
func init() {
|
||||
dir = utils.Directory
|
||||
source.Assets()
|
||||
utils.InitLogs()
|
||||
injectDatabase()
|
||||
}
|
||||
|
||||
func injectDatabase() {
|
||||
utils.DeleteFile(dir + "/statup.db")
|
||||
db, err := gorm.Open("sqlite3", dir+"/statup.db")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
db.CreateTable(¬ifier.Notification{})
|
||||
notifier.SetDB(db)
|
||||
}
|
|
@ -21,6 +21,7 @@ import (
|
|||
"github.com/hunterlong/statup/core/notifier"
|
||||
"github.com/hunterlong/statup/types"
|
||||
"github.com/hunterlong/statup/utils"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"text/template"
|
||||
"time"
|
||||
|
@ -56,7 +57,7 @@ var slacker = &Slack{¬ifier.Notification{
|
|||
}}},
|
||||
}
|
||||
|
||||
func sendSlack(temp string, data interface{}) error {
|
||||
func parseSlackMessage(temp string, data interface{}) error {
|
||||
buf := new(bytes.Buffer)
|
||||
slackTemp, _ := template.New("slack").Parse(temp)
|
||||
err := slackTemp.Execute(buf, data)
|
||||
|
@ -67,7 +68,7 @@ func sendSlack(temp string, data interface{}) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
type slackMessage struct {
|
||||
type SlackMessage struct {
|
||||
Service *types.Service
|
||||
Template string
|
||||
Time int64
|
||||
|
@ -84,10 +85,13 @@ func init() {
|
|||
func (u *Slack) Send(msg interface{}) error {
|
||||
message := msg.(string)
|
||||
client := new(http.Client)
|
||||
_, err := client.Post(u.Host, "application/json", bytes.NewBuffer([]byte(message)))
|
||||
res, err := client.Post(u.Host, "application/json", bytes.NewBuffer([]byte(message)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
contents, _ := ioutil.ReadAll(res.Body)
|
||||
fmt.Println(string(contents))
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -98,18 +102,18 @@ func (u *Slack) Select() *notifier.Notification {
|
|||
func (u *Slack) OnTest(n notifier.Notification) (bool, error) {
|
||||
utils.Log(1, "Slack notifier loaded")
|
||||
msg := fmt.Sprintf("You're Statup Slack Notifier is working correctly!")
|
||||
err := sendSlack(TEST_TEMPLATE, msg)
|
||||
err := parseSlackMessage(TEST_TEMPLATE, msg)
|
||||
return true, err
|
||||
}
|
||||
|
||||
// ON SERVICE FAILURE, DO YOUR OWN FUNCTIONS
|
||||
func (u *Slack) OnFailure(s *types.Service, f *types.Failure) {
|
||||
message := slackMessage{
|
||||
message := SlackMessage{
|
||||
Service: s,
|
||||
Template: FAILURE,
|
||||
Time: time.Now().Unix(),
|
||||
}
|
||||
sendSlack(FAILING_TEMPLATE, message)
|
||||
parseSlackMessage(FAILING_TEMPLATE, message)
|
||||
}
|
||||
|
||||
// ON SERVICE SUCCESS, DO YOUR OWN FUNCTIONS
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
package notifiers
|
||||
|
||||
import (
|
||||
"github.com/hunterlong/statup/core/notifier"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
SLACK_URL = os.Getenv("SLACK_URL")
|
||||
slackMessage = `{"text":"this is a test from the Slack notifier!"}`
|
||||
slackTestMessage = SlackMessage{
|
||||
Service: TestService,
|
||||
Template: FAILURE,
|
||||
Time: time.Now().Unix(),
|
||||
}
|
||||
)
|
||||
|
||||
func init() {
|
||||
slacker.Host = SLACK_URL
|
||||
}
|
||||
|
||||
func TestSlackNotifier(t *testing.T) {
|
||||
if SLACK_URL == "" {
|
||||
t.Log("Slack notifier testing skipped, missing SLACK_URL environment variable")
|
||||
t.SkipNow()
|
||||
}
|
||||
currentCount = CountNotifiers()
|
||||
|
||||
t.Run("Load Slack", func(t *testing.T) {
|
||||
slacker.Host = SLACK_URL
|
||||
slacker.Delay = time.Duration(100 * time.Millisecond)
|
||||
err := notifier.AddNotifier(slacker)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "Hunter Long", slacker.Author)
|
||||
assert.Equal(t, SLACK_URL, slacker.Host)
|
||||
assert.Equal(t, currentCount+1, CountNotifiers())
|
||||
})
|
||||
|
||||
t.Run("Load Slack Notifier", func(t *testing.T) {
|
||||
count := notifier.Load()
|
||||
assert.Equal(t, currentCount+1, len(count))
|
||||
})
|
||||
|
||||
t.Run("Slack parse message", func(t *testing.T) {
|
||||
err := parseSlackMessage("this is a test message!", slackTestMessage)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(slacker.Queue))
|
||||
})
|
||||
|
||||
t.Run("Slack Within Limits", func(t *testing.T) {
|
||||
ok, err := slacker.WithinLimits()
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, ok)
|
||||
})
|
||||
|
||||
t.Run("Slack Send", func(t *testing.T) {
|
||||
err := slacker.Send(slackMessage)
|
||||
assert.Nil(t, err)
|
||||
})
|
||||
|
||||
t.Run("Slack Queue", func(t *testing.T) {
|
||||
go notifier.Queue(slacker)
|
||||
time.Sleep(1 * time.Second)
|
||||
assert.Equal(t, SLACK_URL, slacker.Host)
|
||||
assert.Equal(t, 0, len(slacker.Queue))
|
||||
})
|
||||
|
||||
}
|
|
@ -16,10 +16,13 @@
|
|||
package notifiers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/hunterlong/statup/core/notifier"
|
||||
"github.com/hunterlong/statup/types"
|
||||
"github.com/hunterlong/statup/utils"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
@ -27,14 +30,9 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
TWILIO_ID = 3
|
||||
TWILIO_METHOD = "twilio"
|
||||
)
|
||||
|
||||
var (
|
||||
twilioMessages []string
|
||||
)
|
||||
|
||||
type Twilio struct {
|
||||
*notifier.Notification
|
||||
}
|
||||
|
@ -59,12 +57,12 @@ var twilio = &Twilio{¬ifier.Notification{
|
|||
}, {
|
||||
Type: "text",
|
||||
Title: "SMS to Phone Number",
|
||||
Placeholder: "+18555555555",
|
||||
Placeholder: "18555555555",
|
||||
DbField: "Var1",
|
||||
}, {
|
||||
Type: "text",
|
||||
Title: "From Phone Number",
|
||||
Placeholder: "+18555555555",
|
||||
Placeholder: "18555555555",
|
||||
DbField: "Var2",
|
||||
}}},
|
||||
}
|
||||
|
@ -86,19 +84,24 @@ func (u *Twilio) Send(msg interface{}) error {
|
|||
twilioUrl := fmt.Sprintf("https://api.twilio.com/2010-04-01/Accounts/%v/Messages.json", u.GetValue("api_key"))
|
||||
client := &http.Client{}
|
||||
v := url.Values{}
|
||||
v.Set("To", u.Var1)
|
||||
v.Set("From", u.Var2)
|
||||
v.Set("To", "+"+u.Var1)
|
||||
v.Set("From", "+"+u.Var2)
|
||||
v.Set("Body", message)
|
||||
rb := *strings.NewReader(v.Encode())
|
||||
req, err := http.NewRequest("POST", twilioUrl, &rb)
|
||||
req.SetBasicAuth(u.ApiKey, u.ApiSecret)
|
||||
req.Header.Add("Accept", "application/json")
|
||||
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
|
||||
client.Do(req)
|
||||
res, err := client.Do(req)
|
||||
if err != nil {
|
||||
utils.Log(3, fmt.Sprintf("Issue sending Twilio notification: %v", err))
|
||||
return err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
contents, _ := ioutil.ReadAll(res.Body)
|
||||
success, twilioRes := twilioSuccess(contents)
|
||||
if !success {
|
||||
return errors.New(fmt.Sprintf("twilio didn't receive the expected status of 'enque' from API got: %v", twilioRes))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -121,3 +124,37 @@ func (u *Twilio) OnSave() error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
func twilioSuccess(res []byte) (bool, TwilioResponse) {
|
||||
var obj TwilioResponse
|
||||
json.Unmarshal(res, &obj)
|
||||
if obj.Status == "queued" {
|
||||
return true, obj
|
||||
}
|
||||
return false, obj
|
||||
}
|
||||
|
||||
type TwilioResponse struct {
|
||||
Sid string `json:"sid"`
|
||||
DateCreated string `json:"date_created"`
|
||||
DateUpdated string `json:"date_updated"`
|
||||
DateSent interface{} `json:"date_sent"`
|
||||
AccountSid string `json:"account_sid"`
|
||||
To string `json:"to"`
|
||||
From string `json:"from"`
|
||||
MessagingServiceSid interface{} `json:"messaging_service_sid"`
|
||||
Body string `json:"body"`
|
||||
Status string `json:"status"`
|
||||
NumSegments string `json:"num_segments"`
|
||||
NumMedia string `json:"num_media"`
|
||||
Direction string `json:"direction"`
|
||||
APIVersion string `json:"api_version"`
|
||||
Price interface{} `json:"price"`
|
||||
PriceUnit string `json:"price_unit"`
|
||||
ErrorCode interface{} `json:"error_code"`
|
||||
ErrorMessage interface{} `json:"error_message"`
|
||||
URI string `json:"uri"`
|
||||
SubresourceUris struct {
|
||||
Media string `json:"media"`
|
||||
} `json:"subresource_uris"`
|
||||
}
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
package notifiers
|
||||
|
||||
import (
|
||||
"github.com/hunterlong/statup/core/notifier"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
TWILIO_SID = os.Getenv("TWILIO_SID")
|
||||
TWILIO_SECRET = os.Getenv("TWILIO_SECRET")
|
||||
TWILIO_FROM = os.Getenv("TWILIO_FROM")
|
||||
TWILIO_TO = os.Getenv("TWILIO_TO")
|
||||
twilioMessage = "The Twilio notifier on Statup has been tested!"
|
||||
)
|
||||
|
||||
func init() {
|
||||
twilio.ApiKey = TWILIO_SID
|
||||
twilio.ApiSecret = TWILIO_SECRET
|
||||
twilio.Var1 = TWILIO_TO
|
||||
twilio.Var2 = TWILIO_FROM
|
||||
}
|
||||
|
||||
func TestTwilioNotifier(t *testing.T) {
|
||||
if TWILIO_SID == "" || TWILIO_SECRET == "" || TWILIO_FROM == "" {
|
||||
t.Log("Twilio notifier testing skipped, missing TWILIO_SID environment variable")
|
||||
t.SkipNow()
|
||||
}
|
||||
currentCount = CountNotifiers()
|
||||
|
||||
t.Run("Load Twilio", func(t *testing.T) {
|
||||
twilio.ApiKey = TWILIO_SID
|
||||
twilio.Delay = time.Duration(100 * time.Millisecond)
|
||||
err := notifier.AddNotifier(twilio)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "Hunter Long", twilio.Author)
|
||||
assert.Equal(t, TWILIO_SID, twilio.ApiKey)
|
||||
assert.Equal(t, currentCount+1, CountNotifiers())
|
||||
})
|
||||
|
||||
t.Run("Load Twilio Notifier", func(t *testing.T) {
|
||||
count := notifier.Load()
|
||||
assert.Equal(t, currentCount+1, len(count))
|
||||
})
|
||||
|
||||
t.Run("Twilio Within Limits", func(t *testing.T) {
|
||||
ok, err := twilio.WithinLimits()
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, ok)
|
||||
})
|
||||
|
||||
t.Run("Twilio Send", func(t *testing.T) {
|
||||
err := twilio.Send(twilioMessage)
|
||||
assert.Nil(t, err)
|
||||
})
|
||||
|
||||
t.Run("Twilio Queue", func(t *testing.T) {
|
||||
go notifier.Queue(twilio)
|
||||
time.Sleep(1 * time.Second)
|
||||
assert.Equal(t, TWILIO_SID, twilio.ApiKey)
|
||||
assert.Equal(t, 0, len(twilio.Queue))
|
||||
})
|
||||
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
// Package source holds all the assets for Statup. This includes
|
||||
// CSS, JS, SCSS, HTML and other website related content.
|
||||
// This package uses Rice to compile all assets into a single 'rice-box.go' file.
|
||||
//
|
||||
// To compile all the assets run `rice embed-go` in the source directory.
|
||||
//
|
||||
// by Hunter Long
|
||||
package source
|
|
@ -32,7 +32,7 @@
|
|||
<a class="nav-link active" id="v-pills-home-tab" data-toggle="pill" href="#v-pills-home" role="tab" aria-controls="v-pills-home" aria-selected="true">Settings</a>
|
||||
<a class="nav-link" id="v-pills-style-tab" data-toggle="pill" href="#v-pills-style" role="tab" aria-controls="v-pills-style" aria-selected="false">Theme Editor</a>
|
||||
{{ range .Notifications }}
|
||||
<a class="nav-link text-capitalize" id="v-pills-{{underscore .Select.Method}}-tab" data-toggle="pill" href="#v-pills-{{underscore .Select.Method}}" role="tab" aria-controls="v-pills-{{underscore .Select.Method}}" aria-selected="false">{{.Select.Method}}</a>
|
||||
<a class="nav-link text-capitalize" id="v-pills-{{underscore .Select.Method}}-tab" data-toggle="pill" href="#v-pills-{{underscore .Select.Method}}" role="tab" aria-controls="v-pills-{{underscore .Select.Method}}" aria-selected="false">{{.Select.Method}} <span class="badge badge-pill badge-secondary"></span></a>
|
||||
{{ end }}
|
||||
<a class="nav-link" id="v-pills-browse-tab" data-toggle="pill" href="#v-pills-browse" role="tab" aria-controls="v-pills-home" aria-selected="false">Browse Plugins</a>
|
||||
<a class="nav-link d-none" id="v-pills-backups-tab" data-toggle="pill" href="#v-pills-backups" role="tab" aria-controls="v-pills-backups" aria-selected="false">Backups</a>
|
||||
|
|
|
@ -21,6 +21,7 @@ type Core struct {
|
|||
Version string `gorm:"column:version" json:"version"`
|
||||
MigrationId int64 `gorm:"column:migration_id" json:"migration_id,omitempty"`
|
||||
UseCdn bool `gorm:"column:use_cdn;default:false" json:"using_cdn,omitempty"`
|
||||
Timezone float32 `gorm:"column:timezone;default:-8.0" json:"timezone,omitempty"`
|
||||
CreatedAt time.Time `gorm:"column:created_at" json:"created_at"`
|
||||
UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at"`
|
||||
DbConnection string `gorm:"-" json:"database"`
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
// Package type contains all of the structs for objects in Statup including
|
||||
// services, hits, failures, Core, and others.
|
||||
//
|
||||
// by Hunter Long
|
||||
package types
|
|
@ -0,0 +1,10 @@
|
|||
// Package utils contains common methods used in most packages in Statup.
|
||||
// This package contains multiple function like:
|
||||
// Logging, encryption, type conversions, setting utils.Directory as the current directory,
|
||||
// running local CMD commands, and creaing/deleting files/folder.
|
||||
//
|
||||
// You can overwrite the utils.Directory global variable by including
|
||||
// STATUP_DIR environment variable to be an absolute path.
|
||||
//
|
||||
// by Hunter Long
|
||||
package utils
|
Loading…
Reference in New Issue