pull/10/head
Hunter Long 2018-06-23 01:42:50 -07:00
parent dc110c0412
commit 470ffb090a
15 changed files with 245 additions and 116 deletions

View File

@ -10,8 +10,6 @@ import (
func CheckServices() {
services, _ = SelectAllServices()
core.Communications, _ = SelectAllCommunications()
LoadDefaultCommunications()
for _, v := range services {
obj := v
go obj.StartCheckins()
@ -42,18 +40,24 @@ func (s *Service) Check() *Service {
return s
}
defer response.Body.Close()
if s.Expected != "" {
contents, _ := ioutil.ReadAll(response.Body)
if s.Expected != "" {
match, _ := regexp.MatchString(s.Expected, string(contents))
if !match {
s.LastResponse = string(contents)
s.LastStatusCode = response.StatusCode
s.Failure(fmt.Sprintf("HTTP Response Body did not match '%v'", s.Expected))
return s
}
}
if s.ExpectedStatus != response.StatusCode {
s.LastResponse = string(contents)
s.LastStatusCode = response.StatusCode
s.Failure(fmt.Sprintf("HTTP Status Code %v did not match %v", response.StatusCode, s.ExpectedStatus))
return s
}
s.LastResponse = string(contents)
s.LastStatusCode = response.StatusCode
s.Online = true
s.Record(response)
return s
@ -65,6 +69,7 @@ type HitData struct {
func (s *Service) Record(response *http.Response) {
s.Online = true
s.LastOnline = time.Now()
data := HitData{
Latency: s.Latency,
}
@ -82,5 +87,8 @@ func (s *Service) Failure(issue string) {
Issue: issue,
}
s.CreateFailure(data)
SendFailureEmail(s)
OnFailure(s)
}

View File

@ -1,77 +0,0 @@
package comms
import (
"bytes"
"crypto/tls"
"fmt"
"github.com/hunterlong/statup/types"
"gopkg.in/gomail.v2"
"html/template"
"log"
"time"
)
var (
Emailer *gomail.Dialer
Outgoing []*types.Email
)
func AddEmail(email *types.Email) {
Outgoing = append(Outgoing, email)
}
func EmailerQueue() {
defer EmailerQueue()
for _, out := range Outgoing {
fmt.Printf("sending email to: %v \n", out.To)
Send(out)
}
Outgoing = nil
fmt.Println("running emailer queue")
time.Sleep(10 * time.Second)
}
func Send(em *types.Email) {
source := EmailTemplate("comms/templates/error.html", nil)
m := gomail.NewMessage()
m.SetHeader("From", "info@betatude.com")
m.SetHeader("To", em.To)
m.SetHeader("Subject", em.Subject)
m.SetBody("text/html", source)
if err := Emailer.DialAndSend(m); err != nil {
fmt.Println(err)
}
}
func SendSample(em *types.Email) {
source := EmailTemplate("comms/templates/error.html", nil)
m := gomail.NewMessage()
m.SetHeader("From", "info@betatude.com")
m.SetHeader("To", em.To)
m.SetHeader("Subject", em.Subject)
m.SetBody("text/html", source)
if err := Emailer.DialAndSend(m); err != nil {
fmt.Println(err)
}
}
func LoadMailer(config *types.Communication) *gomail.Dialer {
Emailer = gomail.NewDialer(config.Host, config.Port, config.Username, config.Password)
Emailer.TLSConfig = &tls.Config{InsecureSkipVerify: true}
return Emailer
}
func EmailTemplate(tmpl string, data interface{}) string {
t := template.New("error.html")
var err error
t, err = t.ParseFiles(tmpl)
if err != nil {
panic(err)
}
var tpl bytes.Buffer
if err := t.Execute(&tpl, data); err != nil {
log.Println(err)
}
result := tpl.String()
return result
}

View File

@ -1,15 +1,16 @@
package main
import (
"github.com/hunterlong/statup/comms"
"fmt"
"github.com/hunterlong/statup/types"
"time"
)
func LoadDefaultCommunications() {
emailer := SelectCommunication(1)
comms.LoadMailer(emailer)
go comms.EmailerQueue()
fmt.Println(emailer)
LoadMailer(emailer)
go EmailerQueue()
}
func LoadComms() {
@ -27,7 +28,7 @@ func Run(c *types.Communication) {
Subject: "Test Email from Statup",
}
comms.AddEmail(sample)
AddEmail(sample)
}
func SelectAllCommunications() ([]*types.Communication, error) {

101
emailer.go Normal file
View File

@ -0,0 +1,101 @@
package main
import (
"bytes"
"crypto/tls"
"fmt"
"github.com/hunterlong/statup/types"
"gopkg.in/gomail.v2"
"html/template"
"log"
"time"
)
var (
emailQue *Que
)
type Que struct {
Mailer *gomail.Dialer
Outgoing []*types.Email
LastSent int
LastSentTime time.Time
}
func AddEmail(email *types.Email) {
emailQue.Outgoing = append(emailQue.Outgoing, email)
}
func EmailerQueue() {
defer EmailerQueue()
uniques := []*types.Email{}
for _, out := range emailQue.Outgoing {
if isUnique(uniques, out) {
fmt.Printf("sending email to: %v \n", out.To)
Send(out)
uniques = append(uniques, out)
}
}
emailQue.Outgoing = nil
fmt.Println("running emailer queue")
time.Sleep(60 * time.Second)
}
func isUnique(arr []*types.Email, obj *types.Email) bool {
for _, v := range arr {
if v.To == obj.To && v.Subject == obj.Subject {
return false
}
}
return true
}
func Send(em *types.Email) {
source := EmailTemplate(em.Template, em.Data)
m := gomail.NewMessage()
m.SetHeader("From", "info@betatude.com")
m.SetHeader("To", em.To)
m.SetHeader("Subject", em.Subject)
m.SetBody("text/html", source)
if err := emailQue.Mailer.DialAndSend(m); err != nil {
fmt.Println(err)
}
emailQue.LastSent++
emailQue.LastSentTime = time.Now()
}
func SendFailureEmail(service *Service) {
email := &types.Email{
To: "info@socialeck.com",
Subject: fmt.Sprintf("Service %v is Failing", service.Name),
Template: "failure.html",
Data: service,
}
AddEmail(email)
}
func LoadMailer(config *types.Communication) *gomail.Dialer {
emailQue = &Que{}
emailQue.Outgoing = []*types.Email{}
emailQue.Mailer = gomail.NewDialer(config.Host, config.Port, config.Username, config.Password)
emailQue.Mailer.TLSConfig = &tls.Config{InsecureSkipVerify: true}
return emailQue.Mailer
}
func EmailTemplate(tmpl string, data interface{}) string {
emailTpl, err := emailBox.String(tmpl)
if err != nil {
panic(err)
}
t := template.New("email")
t, err = t.Parse(emailTpl)
if err != nil {
panic(err)
}
var tpl bytes.Buffer
if err := t.Execute(&tpl, data); err != nil {
log.Println(err)
}
result := tpl.String()
return result
}

View File

@ -9,9 +9,16 @@ import (
var httpFunctions template.FuncMap
func init() {
httpFunctions = template.FuncMap{
func ExportIndexHTML() string {
out := index{*core, services}
nav, _ := tmplBox.String("nav.html")
footer, _ := tmplBox.String("footer.html")
render, err := tmplBox.String("index.html")
if err != nil {
panic(err)
}
t := template.New("message")
t.Funcs(template.FuncMap{
"js": func(html string) template.JS {
return template.JS(html)
},
@ -24,20 +31,7 @@ func init() {
"underscore": func(html string) string {
return UnderScoreString(html)
},
}
}
func ExportIndexHTML() string {
out := index{*core, services}
nav, _ := tmplBox.String("nav.html")
footer, _ := tmplBox.String("footer.html")
render, err := tmplBox.String("index.html")
if err != nil {
panic(err)
}
t := template.New("message")
t.Funcs(httpFunctions)
})
t, _ = t.Parse(nav)
t, _ = t.Parse(footer)
t.Parse(render)

66
html/emails/failure.html Normal file
View File

@ -0,0 +1,66 @@
<!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">{{ .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 named '{{.Name}}' has been triggered with a HTTP status code of '{{.LastStatusCode}}' and is currently offline based on your requirements.
</p>
<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">
{{ .LastResponse }}
</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;">
<td style="box-sizing: border-box; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; word-break: break-word;">
<a href="/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>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>

View File

@ -163,6 +163,20 @@
</form>
</div>
<div class="col-12">
<h3>Last Response</h3>
<textarea class="form-control" readonly>{{ .LastResponse }}</textarea>
<div class="form-group row mt-2">
<label for="last_status_code" class="col-sm-3 col-form-label">Status Code</label>
<div class="col-sm-9">
<input type="text" id="last_status_code" class="form-control" value="{{ .LastStatusCode }}">
</div>
</div>
</div>
{{end}}

View File

@ -74,6 +74,11 @@
<input type="text" name="username" class="form-control" value="{{.Username}}" id="formGroupExampleInput" value="admin" placeholder="admin">
</div>
<div class="form-group">
<label for="formGroupExampleInput">Admin Email Address</label>
<input type="email" name="email" class="form-control" value="{{.Email}}" id="formGroupExampleInput" placeholder="info@admin.com">
</div>
<div class="form-group">
<label for="formGroupExampleInput">Admin Password</label>
<input type="password" name="password" class="form-control" value="{{.Password}}" id="formGroupExampleInput" value="admin" placeholder="admin">

View File

@ -26,6 +26,7 @@ var (
cssBox *rice.Box
jsBox *rice.Box
tmplBox *rice.Box
emailBox *rice.Box
setupMode bool
allPlugins []plugin.PluginActions
)
@ -152,6 +153,8 @@ func mainProcess() {
RunHTTPServer()
}
CheckServices()
core.Communications, _ = SelectAllCommunications()
LoadDefaultCommunications()
if !setupMode {
LoadPlugins()
RunHTTPServer()
@ -221,6 +224,7 @@ func RenderBoxes() {
cssBox = rice.MustFindBox("html/css")
jsBox = rice.MustFindBox("html/js")
tmplBox = rice.MustFindBox("html/tmpl")
emailBox = rice.MustFindBox("html/emails")
}
func LoadConfig() (*Config, error) {

View File

@ -35,6 +35,9 @@ type Service struct {
Failures []*Failure `json:"failures"`
Checkins []*Checkin `json:"checkins"`
runRoutine bool
LastResponse string
LastStatusCode int
LastOnline time.Time
}
func serviceCol() db.Collection {

View File

@ -45,6 +45,7 @@ func ProcessSetupHandler(w http.ResponseWriter, r *http.Request) {
sample := r.PostForm.Get("sample_data")
description := r.PostForm.Get("description")
domain := r.PostForm.Get("domain")
email := r.PostForm.Get("email")
config := &DbConfig{
dbConn,
@ -85,6 +86,7 @@ func ProcessSetupHandler(w http.ResponseWriter, r *http.Request) {
admin := &User{
Username: config.Username,
Password: config.Password,
Email: email,
Admin: true,
}
admin.Create()

View File

@ -24,5 +24,4 @@ type Email struct {
Subject string
Template string
Data interface{}
Body string
}

View File

@ -2,6 +2,7 @@ package main
import (
"golang.org/x/crypto/bcrypt"
"net/http"
"time"
)
@ -16,6 +17,19 @@ type User struct {
CreatedAt time.Time `db:"created_at" json:"created_at"`
}
func SessionUser(r *http.Request) *User {
session, _ := store.Get(r, cookieKey)
if session == nil {
return nil
}
uuid := session.Values["user_id"]
var user *User
col := dbSession.Collection("users")
res := col.Find("id", uuid)
res.One(&user)
return user
}
func SelectUser(id int64) (*User, error) {
var user User
col := dbSession.Collection("users")
@ -40,8 +54,7 @@ func (u *User) Delete() error {
func (u *User) Create() (int64, error) {
u.CreatedAt = time.Now()
password := HashPassword(u.Password)
u.Password = password
u.Password = HashPassword(u.Password)
u.ApiKey = NewSHA1Hash(5)
u.ApiSecret = NewSHA1Hash(10)
col := dbSession.Collection("users")

20
web.go
View File

@ -5,7 +5,6 @@ import (
"github.com/fatih/structs"
"github.com/gorilla/mux"
"github.com/gorilla/sessions"
"github.com/hunterlong/statup/comms"
"github.com/hunterlong/statup/types"
"html/template"
"net/http"
@ -96,9 +95,10 @@ func LoginHandler(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
username := r.PostForm.Get("username")
password := r.PostForm.Get("password")
_, auth := AuthUser(username, password)
user, auth := AuthUser(username, password)
if auth {
session.Values["authenticated"] = true
session.Values["user_id"] = user.Id
session.Save(r, w)
http.Redirect(w, r, "/dashboard", http.StatusSeeOther)
} else {
@ -265,13 +265,11 @@ func SaveEmailSettingsHandler(w http.ResponseWriter, r *http.Request) {
Update(emailer)
sample := &types.Email{
To: "info@socialeck.com",
To: SessionUser(r).Email,
Subject: "Sample Email",
Template: "templates/error.html",
Body: "okkokkok",
Template: "error.html",
}
comms.AddEmail(sample)
AddEmail(sample)
http.Redirect(w, r, "/settings", http.StatusSeeOther)
}
@ -416,11 +414,6 @@ func ServicesBadgeHandler(w http.ResponseWriter, r *http.Request) {
}
func ServicesViewHandler(w http.ResponseWriter, r *http.Request) {
auth := IsAuthenticated(r)
if !auth {
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
vars := mux.Vars(r)
service := SelectService(StringInt(vars["id"]))
ExecuteResponse(w, r, "service.html", service)
@ -450,6 +443,9 @@ func ExecuteResponse(w http.ResponseWriter, r *http.Request, file string, data i
"underscore": func(html string) string {
return UnderScoreString(html)
},
"User": func() *User {
return SessionUser(r)
},
})
t, _ = t.Parse(nav)
t, _ = t.Parse(footer)