mirror of https://github.com/statping/statping
				
				
				
			
							parent
							
								
									b741e55e35
								
							
						
					
					
						commit
						70150c5318
					
				| 
						 | 
				
			
			@ -18,7 +18,7 @@ services:
 | 
			
		|||
 | 
			
		||||
env:
 | 
			
		||||
  global:
 | 
			
		||||
     - VERSION=0.28.5
 | 
			
		||||
     - VERSION=0.28.6
 | 
			
		||||
     - DB_HOST=localhost
 | 
			
		||||
     - DB_USER=travis
 | 
			
		||||
     - DB_PASS=
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,27 @@
 | 
			
		|||
FROM golang:1.10.3-alpine
 | 
			
		||||
 | 
			
		||||
RUN apk update && apk add git g++ libstdc++ ca-certificates
 | 
			
		||||
 | 
			
		||||
WORKDIR $GOPATH/src/github.com/hunterlong/statup/
 | 
			
		||||
 | 
			
		||||
COPY . $GOPATH/src/github.com/hunterlong/statup/
 | 
			
		||||
RUN go get github.com/GeertJohan/go.rice/rice
 | 
			
		||||
RUN go get -d -v
 | 
			
		||||
RUN rice embed-go
 | 
			
		||||
RUN go install
 | 
			
		||||
 | 
			
		||||
RUN wget -q https://assets.statup.io/sass && \
 | 
			
		||||
      chmod +x sass && \
 | 
			
		||||
      mv sass /usr/local/bin/sass
 | 
			
		||||
 | 
			
		||||
ENV IS_DOCKER=true
 | 
			
		||||
ENV SASS=/usr/local/bin/sass
 | 
			
		||||
ENV CMD_FILE=/usr/bin/cmd
 | 
			
		||||
 | 
			
		||||
RUN printf "#!/usr/bin/env sh\n\$1\n" > $CMD_FILE && \
 | 
			
		||||
      chmod +x $CMD_FILE
 | 
			
		||||
 | 
			
		||||
WORKDIR /app
 | 
			
		||||
VOLUME /app
 | 
			
		||||
EXPOSE 8080
 | 
			
		||||
CMD ["/go/bin/statup"]
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,8 @@
 | 
			
		|||
#!/usr/bin/env bash
 | 
			
		||||
 | 
			
		||||
cd .travis
 | 
			
		||||
cp Dockerfile.dev ../
 | 
			
		||||
cd ../
 | 
			
		||||
docker build -t hunterlong/statup:dev -f Dockerfile.dev .
 | 
			
		||||
 | 
			
		||||
rm -rf Dockerfile.dev
 | 
			
		||||
| 
						 | 
				
			
			@ -1,6 +1,6 @@
 | 
			
		|||
FROM alpine:latest
 | 
			
		||||
 | 
			
		||||
ENV VERSION=v0.28.5
 | 
			
		||||
ENV VERSION=v0.28.6
 | 
			
		||||
 | 
			
		||||
RUN apk --no-cache add libstdc++ ca-certificates
 | 
			
		||||
RUN wget -q https://github.com/hunterlong/statup/releases/download/$VERSION/statup-linux-alpine.tar.gz && \
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -95,7 +95,7 @@ func CreateAllAssets() {
 | 
			
		|||
	utils.Log(1, "Inserting scss, css, emails, and javascript files into assets..")
 | 
			
		||||
	CopyToPublic(ScssBox, "scss", "base.scss")
 | 
			
		||||
	CopyToPublic(ScssBox, "scss", "variables.scss")
 | 
			
		||||
	CopyToPublic(EmailBox, "emails", "error.html")
 | 
			
		||||
	CopyToPublic(EmailBox, "emails", "message.html")
 | 
			
		||||
	CopyToPublic(EmailBox, "emails", "failure.html")
 | 
			
		||||
	CopyToPublic(CssBox, "css", "bootstrap.min.css")
 | 
			
		||||
	CopyToPublic(JsBox, "js", "bootstrap.min.js")
 | 
			
		||||
| 
						 | 
				
			
			@ -106,7 +106,6 @@ func CreateAllAssets() {
 | 
			
		|||
	CopyToPublic(JsBox, "js", "setup.js")
 | 
			
		||||
	CopyToPublic(TmplBox, "", "robots.txt")
 | 
			
		||||
	CopyToPublic(TmplBox, "", "favicon.ico")
 | 
			
		||||
 | 
			
		||||
	utils.Log(1, "Compiling CSS from SCSS style...")
 | 
			
		||||
	err := CompileSASS()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -17,10 +17,10 @@ type FailureData types.FailureData
 | 
			
		|||
 | 
			
		||||
func CheckServices() {
 | 
			
		||||
	CoreApp.Services, _ = SelectAllServices()
 | 
			
		||||
	utils.Log(1, fmt.Sprintf("Loaded %v Services", len(CoreApp.Services)))
 | 
			
		||||
	utils.Log(1, fmt.Sprintf("Starting monitoring process for %v Services", len(CoreApp.Services)))
 | 
			
		||||
	for _, v := range CoreApp.Services {
 | 
			
		||||
		obj := v
 | 
			
		||||
		go obj.StartCheckins()
 | 
			
		||||
		//go obj.StartCheckins()
 | 
			
		||||
		go obj.CheckQueue()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,21 +1,36 @@
 | 
			
		|||
package core
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"github.com/hunterlong/statup/notifications"
 | 
			
		||||
	"github.com/hunterlong/statup/types"
 | 
			
		||||
	"github.com/hunterlong/statup/utils"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Communication types.Communication
 | 
			
		||||
 | 
			
		||||
func LoadDefaultCommunications() {
 | 
			
		||||
	emailer := SelectCommunication(1)
 | 
			
		||||
	notifications.EmailComm = SelectCommunication(1)
 | 
			
		||||
	emailer := notifications.EmailComm
 | 
			
		||||
	if emailer.Enabled {
 | 
			
		||||
		//LoadMailer(emailer)
 | 
			
		||||
		//go EmailerQueue()
 | 
			
		||||
		admin, _ := SelectUser(1)
 | 
			
		||||
		notifications.LoadEmailer(emailer)
 | 
			
		||||
		email := &types.Email{
 | 
			
		||||
			To:       admin.Email,
 | 
			
		||||
			Subject:  "Test Email",
 | 
			
		||||
			Template: "message.html",
 | 
			
		||||
			Data:     nil,
 | 
			
		||||
			From:     emailer.Var1,
 | 
			
		||||
		}
 | 
			
		||||
		notifications.SendEmail(EmailBox, email)
 | 
			
		||||
		go notifications.EmailRoutine()
 | 
			
		||||
	}
 | 
			
		||||
	notifications.SlackComm = SelectCommunication(2)
 | 
			
		||||
	slack := notifications.SlackComm
 | 
			
		||||
	if slack.Enabled {
 | 
			
		||||
		notifications.LoadSlack(slack.Host)
 | 
			
		||||
		msg := fmt.Sprintf("Slack loaded on your Statup Status Page!")
 | 
			
		||||
		notifications.SendSlack(msg)
 | 
			
		||||
		go notifications.SlackRoutine()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -27,25 +42,15 @@ func LoadComms() {
 | 
			
		|||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Run(c *Communication) {
 | 
			
		||||
 | 
			
		||||
	//sample := &Email{
 | 
			
		||||
	//	To:      "info@socialeck.com",
 | 
			
		||||
	//	Subject: "Test Email from Statup",
 | 
			
		||||
	//}
 | 
			
		||||
 | 
			
		||||
	//AddEmail(sample)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func SelectAllCommunications() ([]*Communication, error) {
 | 
			
		||||
	var c []*Communication
 | 
			
		||||
func SelectAllCommunications() ([]*types.Communication, error) {
 | 
			
		||||
	var c []*types.Communication
 | 
			
		||||
	col := DbSession.Collection("communication").Find()
 | 
			
		||||
	err := col.All(&c)
 | 
			
		||||
	err := col.OrderBy("id").All(&c)
 | 
			
		||||
	CoreApp.Communications = c
 | 
			
		||||
	return c, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Create(c *Communication) (int64, error) {
 | 
			
		||||
func Create(c *types.Communication) (int64, error) {
 | 
			
		||||
	c.CreatedAt = time.Now()
 | 
			
		||||
	uuid, err := DbSession.Collection("communication").Insert(c)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
| 
						 | 
				
			
			@ -62,45 +67,30 @@ func Create(c *Communication) (int64, error) {
 | 
			
		|||
	return uuid.(int64), err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Disable(c *Communication) {
 | 
			
		||||
func Disable(c *types.Communication) {
 | 
			
		||||
	c.Enabled = false
 | 
			
		||||
	Update(c)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Enable(c *Communication) {
 | 
			
		||||
func Enable(c *types.Communication) {
 | 
			
		||||
	c.Enabled = true
 | 
			
		||||
	Update(c)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Update(c *Communication) *Communication {
 | 
			
		||||
func Update(c *types.Communication) *types.Communication {
 | 
			
		||||
	col := DbSession.Collection("communication").Find("id", c.Id)
 | 
			
		||||
	col.Update(c)
 | 
			
		||||
	SelectAllCommunications()
 | 
			
		||||
	return c
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func SelectCommunication(id int64) *Communication {
 | 
			
		||||
	for _, c := range CoreApp.Communications {
 | 
			
		||||
		if c.Id == id {
 | 
			
		||||
			return c
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func SendSlackMessage(msg string) error {
 | 
			
		||||
	fullMessage := fmt.Sprintf("{\"text\":\"%v\"}", msg)
 | 
			
		||||
	utils.Log(1, fmt.Sprintf("Sending JSON to Slack Webhook: %v", fullMessage))
 | 
			
		||||
	slack := SelectCommunication(2)
 | 
			
		||||
	if slack == nil {
 | 
			
		||||
		utils.Log(3, fmt.Sprintf("Slack communication database entry was not found."))
 | 
			
		||||
func SelectCommunication(id int64) *types.Communication {
 | 
			
		||||
	var comm *types.Communication
 | 
			
		||||
	col := DbSession.Collection("communication").Find("id", id)
 | 
			
		||||
	err := col.One(&comm)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		utils.Log(2, err)
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	client := http.Client{
 | 
			
		||||
		Timeout: 15 * time.Second,
 | 
			
		||||
	}
 | 
			
		||||
	_, err := client.Post(slack.Host, "application/json", bytes.NewBuffer([]byte(fullMessage)))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		utils.Log(3, err)
 | 
			
		||||
	}
 | 
			
		||||
	return err
 | 
			
		||||
	return comm
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										12
									
								
								core/core.go
								
								
								
								
							
							
						
						
									
										12
									
								
								core/core.go
								
								
								
								
							| 
						 | 
				
			
			@ -23,7 +23,7 @@ type Core struct {
 | 
			
		|||
	Plugins     []plugin.Info
 | 
			
		||||
	Repos       []PluginJSON
 | 
			
		||||
	//PluginFields   []PluginSelect
 | 
			
		||||
	Communications []*Communication
 | 
			
		||||
	Communications []*types.Communication
 | 
			
		||||
	OfflineAssets  bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -45,6 +45,16 @@ func init() {
 | 
			
		|||
	CoreApp = new(Core)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func InitApp() {
 | 
			
		||||
	SelectCore()
 | 
			
		||||
	SelectAllCommunications()
 | 
			
		||||
	InsertDefaultComms()
 | 
			
		||||
	LoadDefaultCommunications()
 | 
			
		||||
	SelectAllServices()
 | 
			
		||||
	CheckServices()
 | 
			
		||||
	go DatabaseMaintence()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *Core) Update() (*Core, error) {
 | 
			
		||||
	res := DbSession.Collection("core").Find().Limit(1)
 | 
			
		||||
	res.Update(c)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,7 +3,9 @@ package core
 | 
			
		|||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"github.com/fatih/structs"
 | 
			
		||||
	"github.com/hunterlong/statup/notifications"
 | 
			
		||||
	"github.com/hunterlong/statup/plugin"
 | 
			
		||||
	"github.com/hunterlong/statup/types"
 | 
			
		||||
	"upper.io/db.v3/lib/sqlbuilder"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -23,13 +25,38 @@ func OnFailure(s *Service, f FailureData) {
 | 
			
		|||
	for _, p := range AllPlugins {
 | 
			
		||||
		p.OnFailure(structs.Map(s))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	onFailureEmail(s, f)
 | 
			
		||||
	onFailureSlack(s, f)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func onFailureSlack(s *Service, f FailureData) {
 | 
			
		||||
	slack := SelectCommunication(2)
 | 
			
		||||
	if slack == nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if slack.Enabled {
 | 
			
		||||
		msg := fmt.Sprintf("Service %v is currently offline! Issue: %v", s.Name, f.Issue)
 | 
			
		||||
		SendSlackMessage(msg)
 | 
			
		||||
		notifications.SendSlack(msg)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type failedEmail struct {
 | 
			
		||||
	Service     *Service
 | 
			
		||||
	FailureData FailureData
 | 
			
		||||
	Domain      string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func onFailureEmail(s *Service, f FailureData) {
 | 
			
		||||
	email := SelectCommunication(1)
 | 
			
		||||
	if email.Enabled {
 | 
			
		||||
		data := failedEmail{s, f, CoreApp.Domain}
 | 
			
		||||
		admin, _ := SelectUser(1)
 | 
			
		||||
		email := &types.Email{
 | 
			
		||||
			To:       admin.Email,
 | 
			
		||||
			Subject:  fmt.Sprintf("Service %v is Down", s.Name),
 | 
			
		||||
			Template: "failure.html",
 | 
			
		||||
			Data:     data,
 | 
			
		||||
			From:     email.Var1,
 | 
			
		||||
		}
 | 
			
		||||
		notifications.SendEmail(EmailBox, email)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,23 +1,30 @@
 | 
			
		|||
package core
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/hunterlong/statup/types"
 | 
			
		||||
	"github.com/hunterlong/statup/utils"
 | 
			
		||||
	"os"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func InsertDefaultComms() {
 | 
			
		||||
	emailer := &Communication{
 | 
			
		||||
		Method:    "email",
 | 
			
		||||
		Removable: false,
 | 
			
		||||
		Enabled:   false,
 | 
			
		||||
	emailer := SelectCommunication(1)
 | 
			
		||||
	if emailer == nil {
 | 
			
		||||
		emailer := &types.Communication{
 | 
			
		||||
			Method:    "email",
 | 
			
		||||
			Removable: false,
 | 
			
		||||
			Enabled:   false,
 | 
			
		||||
		}
 | 
			
		||||
		Create(emailer)
 | 
			
		||||
	}
 | 
			
		||||
	Create(emailer)
 | 
			
		||||
	slack := &Communication{
 | 
			
		||||
		Method:    "slack",
 | 
			
		||||
		Removable: false,
 | 
			
		||||
		Enabled:   false,
 | 
			
		||||
	slack := SelectCommunication(2)
 | 
			
		||||
	if slack == nil {
 | 
			
		||||
		slack := &types.Communication{
 | 
			
		||||
			Method:    "slack",
 | 
			
		||||
			Removable: false,
 | 
			
		||||
			Enabled:   false,
 | 
			
		||||
		}
 | 
			
		||||
		Create(slack)
 | 
			
		||||
	}
 | 
			
		||||
	Create(slack)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func DeleteConfig() {
 | 
			
		||||
| 
						 | 
				
			
			@ -82,12 +89,14 @@ func LoadSampleData() error {
 | 
			
		|||
	}
 | 
			
		||||
	checkin.Create()
 | 
			
		||||
 | 
			
		||||
	for i := 0; i < 20; i++ {
 | 
			
		||||
		s1.Check()
 | 
			
		||||
		s2.Check()
 | 
			
		||||
		s3.Check()
 | 
			
		||||
		s4.Check()
 | 
			
		||||
	}
 | 
			
		||||
	//for i := 0; i < 3; i++ {
 | 
			
		||||
	//	s1.Check()
 | 
			
		||||
	//	s2.Check()
 | 
			
		||||
	//	s3.Check()
 | 
			
		||||
	//	s4.Check()
 | 
			
		||||
	//}
 | 
			
		||||
 | 
			
		||||
	utils.Log(1, "Sample data has finished importing")
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										114
									
								
								emailer.go
								
								
								
								
							
							
						
						
									
										114
									
								
								emailer.go
								
								
								
								
							| 
						 | 
				
			
			@ -1,114 +0,0 @@
 | 
			
		|||
package main
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"crypto/tls"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"github.com/hunterlong/statup/core"
 | 
			
		||||
	"github.com/hunterlong/statup/types"
 | 
			
		||||
	"github.com/hunterlong/statup/utils"
 | 
			
		||||
	"gopkg.in/gomail.v2"
 | 
			
		||||
	"html/template"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	emailQue *Que
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Email types.Email
 | 
			
		||||
 | 
			
		||||
type Que struct {
 | 
			
		||||
	Mailer       *gomail.Dialer
 | 
			
		||||
	Outgoing     []*Email
 | 
			
		||||
	LastSent     int
 | 
			
		||||
	LastSentTime time.Time
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func AddEmail(email *Email) {
 | 
			
		||||
	if emailQue == nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	emailQue.Outgoing = append(emailQue.Outgoing, email)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func EmailerQueue() {
 | 
			
		||||
	if emailQue == nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	uniques := []*Email{}
 | 
			
		||||
	for _, out := range emailQue.Outgoing {
 | 
			
		||||
		if isUnique(uniques, out) {
 | 
			
		||||
			msg := fmt.Sprintf("sending email to: %v \n", out.To)
 | 
			
		||||
			Send(out)
 | 
			
		||||
			utils.Log(0, msg)
 | 
			
		||||
			uniques = append(uniques, out)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	emailQue.Outgoing = nil
 | 
			
		||||
	fmt.Println("running emailer queue")
 | 
			
		||||
	time.Sleep(60 * time.Second)
 | 
			
		||||
	EmailerQueue()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func isUnique(arr []*Email, obj *Email) bool {
 | 
			
		||||
	for _, v := range arr {
 | 
			
		||||
		if v.To == obj.To && v.Subject == obj.Subject {
 | 
			
		||||
			return false
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Send(em *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 {
 | 
			
		||||
		utils.Log(2, err)
 | 
			
		||||
	}
 | 
			
		||||
	emailQue.LastSent++
 | 
			
		||||
	emailQue.LastSentTime = time.Now()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func SendFailureEmail(service *core.Service) {
 | 
			
		||||
	email := &Email{
 | 
			
		||||
		To:       "info@socialeck.com",
 | 
			
		||||
		Subject:  fmt.Sprintf("Service %v is Failing", service.Name),
 | 
			
		||||
		Template: "failure.html",
 | 
			
		||||
		Data:     service,
 | 
			
		||||
	}
 | 
			
		||||
	AddEmail(email)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func LoadMailer(config *core.Communication) *gomail.Dialer {
 | 
			
		||||
	if config.Host == "" || config.Username == "" || config.Password == "" {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	emailQue = new(Que)
 | 
			
		||||
	emailQue.Outgoing = []*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 := core.EmailBox.String(tmpl)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		utils.Log(3, err)
 | 
			
		||||
	}
 | 
			
		||||
	t := template.New("email")
 | 
			
		||||
	t, err = t.Parse(emailTpl)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		utils.Log(3, err)
 | 
			
		||||
	}
 | 
			
		||||
	var tpl bytes.Buffer
 | 
			
		||||
	if err := t.Execute(&tpl, data); err != nil {
 | 
			
		||||
		utils.Log(2, err)
 | 
			
		||||
	}
 | 
			
		||||
	result := tpl.String()
 | 
			
		||||
	return result
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -7,4 +7,4 @@ import (
 | 
			
		|||
func Error404Handler(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
	w.WriteHeader(http.StatusNotFound)
 | 
			
		||||
	ExecuteResponse(w, r, "error_404.html", nil)
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -3,17 +3,17 @@ package handlers
 | 
			
		|||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"github.com/hunterlong/statup/core"
 | 
			
		||||
	"github.com/hunterlong/statup/notifications"
 | 
			
		||||
	"github.com/hunterlong/statup/types"
 | 
			
		||||
	"github.com/hunterlong/statup/utils"
 | 
			
		||||
	"net/http"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func PluginsHandler(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
	auth := IsAuthenticated(r)
 | 
			
		||||
	if !auth {
 | 
			
		||||
	if !IsAuthenticated(r) {
 | 
			
		||||
		http.Redirect(w, r, "/", http.StatusSeeOther)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	//CoreApp.FetchPluginRepo()
 | 
			
		||||
 | 
			
		||||
	//var pluginFields []PluginSelect
 | 
			
		||||
| 
						 | 
				
			
			@ -31,8 +31,7 @@ func PluginsHandler(w http.ResponseWriter, r *http.Request) {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
func SaveSettingsHandler(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
	auth := IsAuthenticated(r)
 | 
			
		||||
	if !auth {
 | 
			
		||||
	if !IsAuthenticated(r) {
 | 
			
		||||
		http.Redirect(w, r, "/", http.StatusSeeOther)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -63,8 +62,7 @@ func SaveSettingsHandler(w http.ResponseWriter, r *http.Request) {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
func SaveSASSHandler(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
	auth := IsAuthenticated(r)
 | 
			
		||||
	if !auth {
 | 
			
		||||
	if !IsAuthenticated(r) {
 | 
			
		||||
		http.Redirect(w, r, "/", http.StatusSeeOther)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -78,8 +76,7 @@ func SaveSASSHandler(w http.ResponseWriter, r *http.Request) {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
func SaveAssetsHandler(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
	auth := IsAuthenticated(r)
 | 
			
		||||
	if !auth {
 | 
			
		||||
	if !IsAuthenticated(r) {
 | 
			
		||||
		http.Redirect(w, r, "/", http.StatusSeeOther)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -89,45 +86,64 @@ func SaveAssetsHandler(w http.ResponseWriter, r *http.Request) {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
func SaveEmailSettingsHandler(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
	auth := IsAuthenticated(r)
 | 
			
		||||
	if !auth {
 | 
			
		||||
	if !IsAuthenticated(r) {
 | 
			
		||||
		http.Redirect(w, r, "/", http.StatusSeeOther)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	emailer := core.SelectCommunication(1)
 | 
			
		||||
 | 
			
		||||
	r.ParseForm()
 | 
			
		||||
	emailer.Host = r.PostForm.Get("host")
 | 
			
		||||
	emailer.Username = r.PostForm.Get("username")
 | 
			
		||||
	emailer.Password = r.PostForm.Get("password")
 | 
			
		||||
	emailer.Port = int(utils.StringInt(r.PostForm.Get("port")))
 | 
			
		||||
	emailer.Var1 = r.PostForm.Get("address")
 | 
			
		||||
	smtpHost := r.PostForm.Get("host")
 | 
			
		||||
	smtpUser := r.PostForm.Get("username")
 | 
			
		||||
	smtpPass := r.PostForm.Get("password")
 | 
			
		||||
	smtpPort := int(utils.StringInt(r.PostForm.Get("port")))
 | 
			
		||||
	smtpOutgoing := r.PostForm.Get("address")
 | 
			
		||||
	enabled := r.PostForm.Get("enable_email")
 | 
			
		||||
 | 
			
		||||
	emailer.Host = smtpHost
 | 
			
		||||
	emailer.Username = smtpUser
 | 
			
		||||
	if smtpPass != "#######################" {
 | 
			
		||||
		emailer.Password = smtpPass
 | 
			
		||||
	}
 | 
			
		||||
	emailer.Port = smtpPort
 | 
			
		||||
	emailer.Var1 = smtpOutgoing
 | 
			
		||||
	emailer.Enabled = false
 | 
			
		||||
	if enabled == "on" {
 | 
			
		||||
		emailer.Enabled = true
 | 
			
		||||
	}
 | 
			
		||||
	core.Update(emailer)
 | 
			
		||||
 | 
			
		||||
	//sample := &Email{
 | 
			
		||||
	//	To:       SessionUser(r).Email,
 | 
			
		||||
	//	Subject:  "Sample Email",
 | 
			
		||||
	//	Template: "error.html",
 | 
			
		||||
	//}
 | 
			
		||||
	//AddEmail(sample)
 | 
			
		||||
	sample := &types.Email{
 | 
			
		||||
		To:       SessionUser(r).Email,
 | 
			
		||||
		Subject:  "Test Email",
 | 
			
		||||
		Template: "message.html",
 | 
			
		||||
		From:     emailer.Var1,
 | 
			
		||||
	}
 | 
			
		||||
	notifications.LoadEmailer(emailer)
 | 
			
		||||
	notifications.SendEmail(core.EmailBox, sample)
 | 
			
		||||
	notifications.EmailComm = emailer
 | 
			
		||||
	if emailer.Enabled {
 | 
			
		||||
		utils.Log(1, "Starting Email Routine, 1 unique email per 60 seconds")
 | 
			
		||||
		go notifications.EmailRoutine()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	http.Redirect(w, r, "/settings", http.StatusSeeOther)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func SaveSlackSettingsHandler(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
	auth := IsAuthenticated(r)
 | 
			
		||||
	if !auth {
 | 
			
		||||
	if !IsAuthenticated(r) {
 | 
			
		||||
		http.Redirect(w, r, "/", http.StatusSeeOther)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	slack := core.SelectCommunication(2)
 | 
			
		||||
	r.ParseForm()
 | 
			
		||||
	slack.Host = r.PostForm.Get("host")
 | 
			
		||||
	slack.Enabled = true
 | 
			
		||||
	if slack.Host == "" {
 | 
			
		||||
		slack.Enabled = false
 | 
			
		||||
	slackWebhook := r.PostForm.Get("slack_url")
 | 
			
		||||
	enable := r.PostForm.Get("enable_slack")
 | 
			
		||||
	slack.Enabled = false
 | 
			
		||||
	if enable == "on" && slackWebhook != "" {
 | 
			
		||||
		slack.Enabled = true
 | 
			
		||||
		go notifications.SlackRoutine()
 | 
			
		||||
	}
 | 
			
		||||
	slack.Host = slackWebhook
 | 
			
		||||
	core.Update(slack)
 | 
			
		||||
	core.SendSlackMessage("This is a test from Statup!")
 | 
			
		||||
	http.Redirect(w, r, "/settings", http.StatusSeeOther)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,6 +7,7 @@ import (
 | 
			
		|||
	"net/http"
 | 
			
		||||
	"os"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func SetupHandler(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
| 
						 | 
				
			
			@ -14,6 +15,7 @@ func SetupHandler(w http.ResponseWriter, r *http.Request) {
 | 
			
		|||
		http.Redirect(w, r, "/", http.StatusSeeOther)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	w.WriteHeader(http.StatusOK)
 | 
			
		||||
	port := 5432
 | 
			
		||||
	if os.Getenv("DB_CONN") == "mysql" {
 | 
			
		||||
		port = 3306
 | 
			
		||||
| 
						 | 
				
			
			@ -109,15 +111,13 @@ func ProcessSetupHandler(w http.ResponseWriter, r *http.Request) {
 | 
			
		|||
	}
 | 
			
		||||
	admin.Create()
 | 
			
		||||
 | 
			
		||||
	core.InsertDefaultComms()
 | 
			
		||||
 | 
			
		||||
	if sample == "on" {
 | 
			
		||||
		go core.LoadSampleData()
 | 
			
		||||
		core.LoadSampleData()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	core.SelectCore()
 | 
			
		||||
	core.InitApp()
 | 
			
		||||
	time.Sleep(2 * time.Second)
 | 
			
		||||
	http.Redirect(w, r, "/", http.StatusSeeOther)
 | 
			
		||||
	//mainProcess()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func SetupResponseError(w http.ResponseWriter, r *http.Request, a interface{}) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,7 +1,6 @@
 | 
			
		|||
package handlers
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"github.com/gorilla/mux"
 | 
			
		||||
	"github.com/hunterlong/statup/core"
 | 
			
		||||
	"github.com/hunterlong/statup/types"
 | 
			
		||||
| 
						 | 
				
			
			@ -39,15 +38,17 @@ func CreateUserHandler(w http.ResponseWriter, r *http.Request) {
 | 
			
		|||
		http.Redirect(w, r, "/", http.StatusSeeOther)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	fmt.Println("creating user")
 | 
			
		||||
	r.ParseForm()
 | 
			
		||||
	username := r.PostForm.Get("username")
 | 
			
		||||
	password := r.PostForm.Get("password")
 | 
			
		||||
	email := r.PostForm.Get("email")
 | 
			
		||||
	admin := r.PostForm.Get("admin")
 | 
			
		||||
 | 
			
		||||
	user := &core.User{
 | 
			
		||||
		Username: username,
 | 
			
		||||
		Password: password,
 | 
			
		||||
		Email:    email,
 | 
			
		||||
		Admin:    (admin == "on"),
 | 
			
		||||
	}
 | 
			
		||||
	_, err := user.Create()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										15
									
								
								main.go
								
								
								
								
							
							
						
						
									
										15
									
								
								main.go
								
								
								
								
							| 
						 | 
				
			
			@ -65,20 +65,7 @@ func mainProcess() {
 | 
			
		|||
		utils.Log(3, err)
 | 
			
		||||
	}
 | 
			
		||||
	core.RunDatabaseUpgrades()
 | 
			
		||||
	core.CoreApp, err = core.SelectCore()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		utils.Log(2, "Core database was not found, Statup is not setup yet.")
 | 
			
		||||
		handlers.RunHTTPServer()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	core.CheckServices()
 | 
			
		||||
	core.CoreApp.Communications, err = core.SelectAllCommunications()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		utils.Log(2, err)
 | 
			
		||||
	}
 | 
			
		||||
	core.LoadDefaultCommunications()
 | 
			
		||||
 | 
			
		||||
	go core.DatabaseMaintence()
 | 
			
		||||
	core.InitApp()
 | 
			
		||||
 | 
			
		||||
	if !core.SetupMode {
 | 
			
		||||
		LoadPlugins()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,105 @@
 | 
			
		|||
package notifications
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"crypto/tls"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"github.com/GeertJohan/go.rice"
 | 
			
		||||
	"github.com/hunterlong/statup/types"
 | 
			
		||||
	"github.com/hunterlong/statup/utils"
 | 
			
		||||
	"gopkg.in/gomail.v2"
 | 
			
		||||
	"html/template"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	mailer     *gomail.Dialer
 | 
			
		||||
	emailQueue []*types.Email
 | 
			
		||||
	EmailComm  *types.Communication
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func EmailRoutine() {
 | 
			
		||||
	var sentAddresses []string
 | 
			
		||||
	for _, email := range emailQueue {
 | 
			
		||||
		if inArray(sentAddresses, email.To) || email.Sent {
 | 
			
		||||
			emailQueue = removeEmail(emailQueue, email)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		e := email
 | 
			
		||||
		go func(email *types.Email) {
 | 
			
		||||
			err := dialSend(email)
 | 
			
		||||
			if err == nil {
 | 
			
		||||
				email.Sent = true
 | 
			
		||||
				sentAddresses = append(sentAddresses, email.To)
 | 
			
		||||
				utils.Log(1, fmt.Sprintf("Email '%v' sent to: %v using the %v template (size: %v)", email.Subject, email.To, email.Template, len([]byte(email.Source))))
 | 
			
		||||
				emailQueue = removeEmail(emailQueue, email)
 | 
			
		||||
			}
 | 
			
		||||
		}(e)
 | 
			
		||||
	}
 | 
			
		||||
	time.Sleep(60 * time.Second)
 | 
			
		||||
	if EmailComm.Enabled {
 | 
			
		||||
		EmailRoutine()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func dialSend(email *types.Email) error {
 | 
			
		||||
	m := gomail.NewMessage()
 | 
			
		||||
	m.SetHeader("From", email.From)
 | 
			
		||||
	m.SetHeader("To", email.To)
 | 
			
		||||
	m.SetHeader("Subject", email.Subject)
 | 
			
		||||
	m.SetBody("text/html", email.Source)
 | 
			
		||||
	if err := mailer.DialAndSend(m); err != nil {
 | 
			
		||||
		utils.Log(3, fmt.Sprintf("Email '%v' sent to: %v using the %v template (size: %v) %v", email.Subject, email.To, email.Template, len([]byte(email.Source)), err))
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func LoadEmailer(mail *types.Communication) {
 | 
			
		||||
	utils.Log(1, fmt.Sprintf("Loading SMTP Emailer using host: %v:%v", mail.Host, mail.Port))
 | 
			
		||||
	mailer = gomail.NewDialer(mail.Host, mail.Port, mail.Username, mail.Password)
 | 
			
		||||
	mailer.TLSConfig = &tls.Config{InsecureSkipVerify: true}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func SendEmail(box *rice.Box, email *types.Email) {
 | 
			
		||||
	source := EmailTemplate(box, email.Template, email.Data)
 | 
			
		||||
	email.Source = source
 | 
			
		||||
	emailQueue = append(emailQueue, email)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func EmailTemplate(box *rice.Box, tmpl string, data interface{}) string {
 | 
			
		||||
	emailTpl, err := box.String(tmpl)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		utils.Log(3, err)
 | 
			
		||||
	}
 | 
			
		||||
	t := template.New("email")
 | 
			
		||||
	t, err = t.Parse(emailTpl)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		utils.Log(3, err)
 | 
			
		||||
	}
 | 
			
		||||
	var tpl bytes.Buffer
 | 
			
		||||
	if err := t.Execute(&tpl, data); err != nil {
 | 
			
		||||
		utils.Log(2, err)
 | 
			
		||||
	}
 | 
			
		||||
	result := tpl.String()
 | 
			
		||||
	return result
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func removeEmail(emails []*types.Email, em *types.Email) []*types.Email {
 | 
			
		||||
	var newArr []*types.Email
 | 
			
		||||
	for _, e := range emails {
 | 
			
		||||
		if e != em {
 | 
			
		||||
			newArr = append(newArr, e)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return newArr
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func inArray(a []string, v string) bool {
 | 
			
		||||
	for _, i := range a {
 | 
			
		||||
		if i == v {
 | 
			
		||||
			return true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,61 @@
 | 
			
		|||
package notifications
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"github.com/hunterlong/statup/types"
 | 
			
		||||
	"github.com/hunterlong/statup/utils"
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	slackUrl      string
 | 
			
		||||
	sentLastMin   int
 | 
			
		||||
	slackMessages []string
 | 
			
		||||
	SlackComm     *types.Communication
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func LoadSlack(url string) {
 | 
			
		||||
	if url == "" {
 | 
			
		||||
		utils.Log(1, "Slack Webhook URL is empty")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	slackUrl = url
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func SlackRoutine() {
 | 
			
		||||
	for _, msg := range slackMessages {
 | 
			
		||||
		utils.Log(1, fmt.Sprintf("Sending JSON to Slack Webhook: %v", msg))
 | 
			
		||||
		client := http.Client{Timeout: 15 * time.Second}
 | 
			
		||||
		_, err := client.Post(slackUrl, "application/json", bytes.NewBuffer([]byte(msg)))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			utils.Log(3, fmt.Sprintf("Issue sending Slack notification: %v", err))
 | 
			
		||||
		}
 | 
			
		||||
		slackMessages = removeStrArray(slackMessages, msg)
 | 
			
		||||
	}
 | 
			
		||||
	time.Sleep(60 * time.Second)
 | 
			
		||||
	if SlackComm.Enabled {
 | 
			
		||||
		SlackRoutine()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func removeStrArray(arr []string, v string) []string {
 | 
			
		||||
	var newArray []string
 | 
			
		||||
	for _, i := range arr {
 | 
			
		||||
		if i != v {
 | 
			
		||||
			newArray = append(newArray, v)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return newArray
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func SendSlack(msg string) error {
 | 
			
		||||
	if slackUrl == "" {
 | 
			
		||||
		return errors.New("Slack Webhook URL has not been set in settings")
 | 
			
		||||
	}
 | 
			
		||||
	fullMessage := fmt.Sprintf("{\"text\":\"%v\"}", msg)
 | 
			
		||||
	slackMessages = append(slackMessages, fullMessage)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -189,6 +189,117 @@ HTML, BODY {
 | 
			
		|||
  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6);
 | 
			
		||||
  transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; }
 | 
			
		||||
 | 
			
		||||
.switch {
 | 
			
		||||
  font-size: 1rem;
 | 
			
		||||
  position: relative; }
 | 
			
		||||
 | 
			
		||||
.switch input {
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  height: 1px;
 | 
			
		||||
  width: 1px;
 | 
			
		||||
  background: none;
 | 
			
		||||
  border: 0;
 | 
			
		||||
  clip: rect(0 0 0 0);
 | 
			
		||||
  clip-path: inset(50%);
 | 
			
		||||
  overflow: hidden;
 | 
			
		||||
  padding: 0; }
 | 
			
		||||
 | 
			
		||||
.switch input + label {
 | 
			
		||||
  position: relative;
 | 
			
		||||
  min-width: calc(calc(2.375rem * .8) * 2);
 | 
			
		||||
  border-radius: calc(2.375rem * .8);
 | 
			
		||||
  height: calc(2.375rem * .8);
 | 
			
		||||
  line-height: calc(2.375rem * .8);
 | 
			
		||||
  display: inline-block;
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
  outline: none;
 | 
			
		||||
  user-select: none;
 | 
			
		||||
  vertical-align: middle;
 | 
			
		||||
  text-indent: calc(calc(calc(2.375rem * .8) * 2) + .5rem); }
 | 
			
		||||
 | 
			
		||||
.switch input + label::before,
 | 
			
		||||
.switch input + label::after {
 | 
			
		||||
  content: '';
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  top: 0;
 | 
			
		||||
  left: 0;
 | 
			
		||||
  width: calc(calc(2.375rem * .8) * 2);
 | 
			
		||||
  bottom: 0;
 | 
			
		||||
  display: block; }
 | 
			
		||||
 | 
			
		||||
.switch input + label::before {
 | 
			
		||||
  right: 0;
 | 
			
		||||
  background-color: #dee2e6;
 | 
			
		||||
  border-radius: calc(2.375rem * .8);
 | 
			
		||||
  transition: 0.2s all; }
 | 
			
		||||
 | 
			
		||||
.switch input + label::after {
 | 
			
		||||
  top: 2px;
 | 
			
		||||
  left: 2px;
 | 
			
		||||
  width: calc(calc(2.375rem * .8) - calc(2px * 2));
 | 
			
		||||
  height: calc(calc(2.375rem * .8) - calc(2px * 2));
 | 
			
		||||
  border-radius: 50%;
 | 
			
		||||
  background-color: white;
 | 
			
		||||
  transition: 0.2s all; }
 | 
			
		||||
 | 
			
		||||
.switch input:checked + label::before {
 | 
			
		||||
  background-color: #08d; }
 | 
			
		||||
 | 
			
		||||
.switch input:checked + label::after {
 | 
			
		||||
  margin-left: calc(2.375rem * .8); }
 | 
			
		||||
 | 
			
		||||
.switch input:focus + label::before {
 | 
			
		||||
  outline: none;
 | 
			
		||||
  box-shadow: 0 0 0 0.2rem rgba(0, 136, 221, 0.25); }
 | 
			
		||||
 | 
			
		||||
.switch input:disabled + label {
 | 
			
		||||
  color: #868e96;
 | 
			
		||||
  cursor: not-allowed; }
 | 
			
		||||
 | 
			
		||||
.switch input:disabled + label::before {
 | 
			
		||||
  background-color: #e9ecef; }
 | 
			
		||||
 | 
			
		||||
.switch.switch-sm {
 | 
			
		||||
  font-size: 0.875rem; }
 | 
			
		||||
 | 
			
		||||
.switch.switch-sm input + label {
 | 
			
		||||
  min-width: calc(calc(1.9375rem * .8) * 2);
 | 
			
		||||
  height: calc(1.9375rem * .8);
 | 
			
		||||
  line-height: calc(1.9375rem * .8);
 | 
			
		||||
  text-indent: calc(calc(calc(1.9375rem * .8) * 2) + .5rem); }
 | 
			
		||||
 | 
			
		||||
.switch.switch-sm input + label::before {
 | 
			
		||||
  width: calc(calc(1.9375rem * .8) * 2); }
 | 
			
		||||
 | 
			
		||||
.switch.switch-sm input + label::after {
 | 
			
		||||
  width: calc(calc(1.9375rem * .8) - calc(2px * 2));
 | 
			
		||||
  height: calc(calc(1.9375rem * .8) - calc(2px * 2)); }
 | 
			
		||||
 | 
			
		||||
.switch.switch-sm input:checked + label::after {
 | 
			
		||||
  margin-left: calc(1.9375rem * .8); }
 | 
			
		||||
 | 
			
		||||
.switch.switch-lg {
 | 
			
		||||
  font-size: 1.25rem; }
 | 
			
		||||
 | 
			
		||||
.switch.switch-lg input + label {
 | 
			
		||||
  min-width: calc(calc(3rem * .8) * 2);
 | 
			
		||||
  height: calc(3rem * .8);
 | 
			
		||||
  line-height: calc(3rem * .8);
 | 
			
		||||
  text-indent: calc(calc(calc(3rem * .8) * 2) + .5rem); }
 | 
			
		||||
 | 
			
		||||
.switch.switch-lg input + label::before {
 | 
			
		||||
  width: calc(calc(3rem * .8) * 2); }
 | 
			
		||||
 | 
			
		||||
.switch.switch-lg input + label::after {
 | 
			
		||||
  width: calc(calc(3rem * .8) - calc(2px * 2));
 | 
			
		||||
  height: calc(calc(3rem * .8) - calc(2px * 2)); }
 | 
			
		||||
 | 
			
		||||
.switch.switch-lg input:checked + label::after {
 | 
			
		||||
  margin-left: calc(3rem * .8); }
 | 
			
		||||
 | 
			
		||||
.switch + .switch {
 | 
			
		||||
  margin-left: 1rem; }
 | 
			
		||||
 | 
			
		||||
@keyframes pulse_animation {
 | 
			
		||||
  0% {
 | 
			
		||||
    transform: scale(1); }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| 
						 | 
				
			
			@ -36,19 +36,24 @@
 | 
			
		|||
 | 
			
		||||
                            <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>
 | 
			
		||||
                                    <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 named '{{.Name}}' has been triggered with a HTTP status code of '{{.LastStatusCode}}' and is currently offline based on your requirements.
 | 
			
		||||
                                        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">
 | 
			
		||||
                                        {{ .LastResponse }}
 | 
			
		||||
                                        {{ .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/{{.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>
 | 
			
		||||
                                            <a href="{{.Domain}}/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="{{.Domain}}/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>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -36,7 +36,7 @@
 | 
			
		|||
 | 
			
		||||
                            <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>
 | 
			
		||||
                                    <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>
 | 
			
		||||
| 
						 | 
				
			
			@ -242,6 +242,119 @@ HTML,BODY {
 | 
			
		|||
  transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.switch {
 | 
			
		||||
  font-size: 1rem;
 | 
			
		||||
  position: relative;
 | 
			
		||||
}
 | 
			
		||||
.switch input {
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  height: 1px;
 | 
			
		||||
  width: 1px;
 | 
			
		||||
  background: none;
 | 
			
		||||
  border: 0;
 | 
			
		||||
  clip: rect(0 0 0 0);
 | 
			
		||||
  clip-path: inset(50%);
 | 
			
		||||
  overflow: hidden;
 | 
			
		||||
  padding: 0;
 | 
			
		||||
}
 | 
			
		||||
.switch input + label {
 | 
			
		||||
  position: relative;
 | 
			
		||||
  min-width: calc(calc(2.375rem * .8) * 2);
 | 
			
		||||
  border-radius: calc(2.375rem * .8);
 | 
			
		||||
  height: calc(2.375rem * .8);
 | 
			
		||||
  line-height: calc(2.375rem * .8);
 | 
			
		||||
  display: inline-block;
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
  outline: none;
 | 
			
		||||
  user-select: none;
 | 
			
		||||
  vertical-align: middle;
 | 
			
		||||
  text-indent: calc(calc(calc(2.375rem * .8) * 2) + .5rem);
 | 
			
		||||
}
 | 
			
		||||
.switch input + label::before,
 | 
			
		||||
.switch input + label::after {
 | 
			
		||||
  content: '';
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  top: 0;
 | 
			
		||||
  left: 0;
 | 
			
		||||
  width: calc(calc(2.375rem * .8) * 2);
 | 
			
		||||
  bottom: 0;
 | 
			
		||||
  display: block;
 | 
			
		||||
}
 | 
			
		||||
.switch input + label::before {
 | 
			
		||||
  right: 0;
 | 
			
		||||
  background-color: #dee2e6;
 | 
			
		||||
  border-radius: calc(2.375rem * .8);
 | 
			
		||||
  transition: 0.2s all;
 | 
			
		||||
}
 | 
			
		||||
.switch input + label::after {
 | 
			
		||||
  top: 2px;
 | 
			
		||||
  left: 2px;
 | 
			
		||||
  width: calc(calc(2.375rem * .8) - calc(2px * 2));
 | 
			
		||||
  height: calc(calc(2.375rem * .8) - calc(2px * 2));
 | 
			
		||||
  border-radius: 50%;
 | 
			
		||||
  background-color: white;
 | 
			
		||||
  transition: 0.2s all;
 | 
			
		||||
}
 | 
			
		||||
.switch input:checked + label::before {
 | 
			
		||||
  background-color: #08d;
 | 
			
		||||
}
 | 
			
		||||
.switch input:checked + label::after {
 | 
			
		||||
  margin-left: calc(2.375rem * .8);
 | 
			
		||||
}
 | 
			
		||||
.switch input:focus + label::before {
 | 
			
		||||
  outline: none;
 | 
			
		||||
  box-shadow: 0 0 0 0.2rem rgba(0, 136, 221, 0.25);
 | 
			
		||||
}
 | 
			
		||||
.switch input:disabled + label {
 | 
			
		||||
  color: #868e96;
 | 
			
		||||
  cursor: not-allowed;
 | 
			
		||||
}
 | 
			
		||||
.switch input:disabled + label::before {
 | 
			
		||||
  background-color: #e9ecef;
 | 
			
		||||
}
 | 
			
		||||
.switch.switch-sm {
 | 
			
		||||
  font-size: 0.875rem;
 | 
			
		||||
}
 | 
			
		||||
.switch.switch-sm input + label {
 | 
			
		||||
  min-width: calc(calc(1.9375rem * .8) * 2);
 | 
			
		||||
  height: calc(1.9375rem * .8);
 | 
			
		||||
  line-height: calc(1.9375rem * .8);
 | 
			
		||||
  text-indent: calc(calc(calc(1.9375rem * .8) * 2) + .5rem);
 | 
			
		||||
}
 | 
			
		||||
.switch.switch-sm input + label::before {
 | 
			
		||||
  width: calc(calc(1.9375rem * .8) * 2);
 | 
			
		||||
}
 | 
			
		||||
.switch.switch-sm input + label::after {
 | 
			
		||||
  width: calc(calc(1.9375rem * .8) - calc(2px * 2));
 | 
			
		||||
  height: calc(calc(1.9375rem * .8) - calc(2px * 2));
 | 
			
		||||
}
 | 
			
		||||
.switch.switch-sm input:checked + label::after {
 | 
			
		||||
  margin-left: calc(1.9375rem * .8);
 | 
			
		||||
}
 | 
			
		||||
.switch.switch-lg {
 | 
			
		||||
  font-size: 1.25rem;
 | 
			
		||||
}
 | 
			
		||||
.switch.switch-lg input + label {
 | 
			
		||||
  min-width: calc(calc(3rem * .8) * 2);
 | 
			
		||||
  height: calc(3rem * .8);
 | 
			
		||||
  line-height: calc(3rem * .8);
 | 
			
		||||
  text-indent: calc(calc(calc(3rem * .8) * 2) + .5rem);
 | 
			
		||||
}
 | 
			
		||||
.switch.switch-lg input + label::before {
 | 
			
		||||
  width: calc(calc(3rem * .8) * 2);
 | 
			
		||||
}
 | 
			
		||||
.switch.switch-lg input + label::after {
 | 
			
		||||
  width: calc(calc(3rem * .8) - calc(2px * 2));
 | 
			
		||||
  height: calc(calc(3rem * .8) - calc(2px * 2));
 | 
			
		||||
}
 | 
			
		||||
.switch.switch-lg input:checked + label::after {
 | 
			
		||||
  margin-left: calc(3rem * .8);
 | 
			
		||||
}
 | 
			
		||||
.switch + .switch {
 | 
			
		||||
  margin-left: 1rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@keyframes pulse_animation {
 | 
			
		||||
    0% { transform: scale(1); }
 | 
			
		||||
    30% { transform: scale(1); }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -79,22 +79,22 @@
 | 
			
		|||
                <div class="form-group row">
 | 
			
		||||
                    <label for="service_name" class="col-sm-4 col-form-label">Service Name</label>
 | 
			
		||||
                    <div class="col-sm-8">
 | 
			
		||||
                        <input type="text" name="name" class="form-control" id="service_name" value="{{.Name}}" placeholder="Name">
 | 
			
		||||
                        <input type="text" name="name" class="form-control" id="service_name" value="{{.Name}}" placeholder="Name" required>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="form-group row">
 | 
			
		||||
                    <label for="service_type" class="col-sm-4 col-form-label">Service Check Type</label>
 | 
			
		||||
                    <div class="col-sm-8">
 | 
			
		||||
                        <select name="check_type" class="form-control" id="service_type" value="{{.Type}}">
 | 
			
		||||
                            <option value="http" selected>HTTP Service</option>
 | 
			
		||||
                            <option value="tcp">TCP Service</option>
 | 
			
		||||
                            <option value="http" {{if eq .Type "http"}}selected{{end}}>HTTP Service</option>
 | 
			
		||||
                            <option value="tcp" {{if eq .Type "tcp"}}selected{{end}}>TCP Service</option>
 | 
			
		||||
                        </select>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="form-group row">
 | 
			
		||||
                    <label for="service_url" class="col-sm-4 col-form-label">Application Endpoint (URL)</label>
 | 
			
		||||
                    <div class="col-sm-8">
 | 
			
		||||
                        <input type="text" name="domain" class="form-control" id="service_url" value="{{.Domain}}" placeholder="https://google.com">
 | 
			
		||||
                        <input type="text" name="domain" class="form-control" id="service_url" value="{{.Domain}}" placeholder="https://google.com" required>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="form-group row">
 | 
			
		||||
| 
						 | 
				
			
			@ -121,7 +121,7 @@
 | 
			
		|||
                <div class="form-group row">
 | 
			
		||||
                    <label for="service_response_code" class="col-sm-4 col-form-label">Expected Status Code</label>
 | 
			
		||||
                    <div class="col-sm-8">
 | 
			
		||||
                        <input type="number" name="expected_status" class="form-control" value="{{.ExpectedStatus}}" id="service_response_code" value="200">
 | 
			
		||||
                        <input type="number" name="expected_status" class="form-control" value="{{.ExpectedStatus}}" id="service_response_code">
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="form-group row">
 | 
			
		||||
| 
						 | 
				
			
			@ -133,7 +133,7 @@
 | 
			
		|||
                <div class="form-group row">
 | 
			
		||||
                    <label for="service_interval" class="col-sm-4 col-form-label">Check Interval (Seconds)</label>
 | 
			
		||||
                    <div class="col-sm-8">
 | 
			
		||||
                        <input type="number" name="interval" class="form-control" value="{{.Interval}}" id="service_interval" placeholder="10">
 | 
			
		||||
                        <input type="number" name="interval" class="form-control" value="{{.Interval}}" id="service_interval" required>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="form-group row">
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -50,7 +50,7 @@
 | 
			
		|||
                <div class="form-group row">
 | 
			
		||||
                    <label for="service_name" class="col-sm-4 col-form-label">Service Name</label>
 | 
			
		||||
                    <div class="col-sm-8">
 | 
			
		||||
                        <input type="text" name="name" class="form-control" id="service_name" placeholder="Name">
 | 
			
		||||
                        <input type="text" name="name" class="form-control" id="service_name" placeholder="Name" required>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="form-group row">
 | 
			
		||||
| 
						 | 
				
			
			@ -65,7 +65,7 @@
 | 
			
		|||
                <div class="form-group row">
 | 
			
		||||
                    <label for="service_url" class="col-sm-4 col-form-label">Application Endpoint (URL)</label>
 | 
			
		||||
                    <div class="col-sm-8">
 | 
			
		||||
                        <input type="text" name="domain" class="form-control" id="service_url" placeholder="https://google.com">
 | 
			
		||||
                        <input type="text" name="domain" class="form-control" id="service_url" placeholder="https://google.com" required>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="form-group row">
 | 
			
		||||
| 
						 | 
				
			
			@ -104,7 +104,7 @@
 | 
			
		|||
                <div class="form-group row">
 | 
			
		||||
                    <label for="service_interval" class="col-sm-4 col-form-label">Check Interval (Seconds)</label>
 | 
			
		||||
                    <div class="col-sm-8">
 | 
			
		||||
                        <input type="number" name="interval" class="form-control" id="service_interval" placeholder="10">
 | 
			
		||||
                        <input type="number" name="interval" class="form-control" id="service_interval" value="60" required>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="form-group row">
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -105,7 +105,7 @@
 | 
			
		|||
                        </div>
 | 
			
		||||
                        <button type="submit" class="btn btn-primary btn-block mt-2">Save Style</button>
 | 
			
		||||
                    </form>
 | 
			
		||||
        {{end}}
 | 
			
		||||
            {{end}}
 | 
			
		||||
                </div>
 | 
			
		||||
 | 
			
		||||
            {{ with $c := index .Communications 0 }}
 | 
			
		||||
| 
						 | 
				
			
			@ -125,7 +125,7 @@
 | 
			
		|||
 | 
			
		||||
                        <div class="form-group">
 | 
			
		||||
                            <label for="password">SMTP Password</label>
 | 
			
		||||
                            <input type="password" name="password" class="form-control" value="{{ $c.Password }}" id="password">
 | 
			
		||||
                            <input type="password" name="password" class="form-control" value="{{ if $c.Password }}#######################{{end}}" id="password">
 | 
			
		||||
                        </div>
 | 
			
		||||
 | 
			
		||||
                        <div class="form-group">
 | 
			
		||||
| 
						 | 
				
			
			@ -143,7 +143,19 @@
 | 
			
		|||
                            <input type="number" name="limit" class="form-control" value="30" id="limit" placeholder="noreply@domain.com">
 | 
			
		||||
                        </div>
 | 
			
		||||
 | 
			
		||||
                        <button type="submit" class="btn btn-primary btn-block">Save Email Settings</button>
 | 
			
		||||
                        <div class="form-group row">
 | 
			
		||||
                            <div class="col-sm-6">
 | 
			
		||||
                          <span class="switch">
 | 
			
		||||
                            <input type="checkbox" name="enable_{{ $c.Method }}" class="switch" id="switch-{{ $c.Method }}" {{if .Enabled}}checked{{end}}>
 | 
			
		||||
                            <label for="switch-{{ $c.Method }}">Enable Emails</label>
 | 
			
		||||
                          </span>
 | 
			
		||||
                            </div>
 | 
			
		||||
 | 
			
		||||
                            <div class="col-sm-6">
 | 
			
		||||
                                <button type="submit" class="btn btn-primary btn-block">Save Email Settings</button>
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                    </form>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -151,22 +163,34 @@
 | 
			
		|||
                    {{ end }}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
{{ with $c := index .Communications 1 }}
 | 
			
		||||
    <div class="tab-pane fade" id="v-pills-{{ $c.Method }}" role="tabpanel" aria-labelledby="v-pills-{{ $c.Method }}-tab">
 | 
			
		||||
            {{ with $c := index .Communications 1 }}
 | 
			
		||||
                <div class="tab-pane fade" id="v-pills-{{ $c.Method }}" role="tabpanel" aria-labelledby="v-pills-{{ $c.Method }}-tab">
 | 
			
		||||
 | 
			
		||||
        <form method="POST" action="/settings/{{ $c.Method }}">
 | 
			
		||||
                    <form method="POST" action="/settings/{{ $c.Method }}">
 | 
			
		||||
 | 
			
		||||
            <div class="form-group">
 | 
			
		||||
                <label for="host">Slack Webhook URL</label>
 | 
			
		||||
                <input type="text" name="host" class="form-control" value="{{ $c.Host }}" id="host" placeholder="https://hooks.slack.com/services/TJIIDSJIFJ/729FJSDF/hua463asda9af79">
 | 
			
		||||
            </div>
 | 
			
		||||
                        <div class="form-group">
 | 
			
		||||
                            <label for="slack_url">Slack Webhook URL</label>
 | 
			
		||||
                            <input type="text" name="slack_url" class="form-control" value="{{ $c.Host }}" id="slack_url" placeholder="https://hooks.slack.com/services/TJIIDSJIFJ/729FJSDF/hua463asda9af79">
 | 
			
		||||
                        </div>
 | 
			
		||||
 | 
			
		||||
            <button type="submit" class="btn btn-primary btn-block">Save Slack Settings</button>
 | 
			
		||||
                        <div class="form-group row">
 | 
			
		||||
                            <div class="col-sm-6">
 | 
			
		||||
                          <span class="switch">
 | 
			
		||||
                            <input type="checkbox" name="enable_{{ $c.Method }}" class="switch" id="switch-{{ $c.Method }}" {{if .Enabled}}checked{{end}}>
 | 
			
		||||
                            <label for="switch-{{ $c.Method }}">Enable Slack</label>
 | 
			
		||||
                          </span>
 | 
			
		||||
                            </div>
 | 
			
		||||
 | 
			
		||||
        </form>
 | 
			
		||||
                            <div class="col-sm-6">
 | 
			
		||||
                                <button type="submit" class="btn btn-primary btn-block">Save Slack Settings</button>
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </div>
 | 
			
		||||
 | 
			
		||||
    </div>
 | 
			
		||||
{{ end }}
 | 
			
		||||
 | 
			
		||||
                    </form>
 | 
			
		||||
 | 
			
		||||
                </div>
 | 
			
		||||
            {{ end }}
 | 
			
		||||
 | 
			
		||||
                <div class="tab-pane fade" id="v-pills-browse" role="tabpanel" aria-labelledby="v-pills-browse-tab">
 | 
			
		||||
                {{ range .Repos }}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -44,31 +44,37 @@
 | 
			
		|||
                <form action="/users" method="POST">
 | 
			
		||||
                    <div class="form-group row">
 | 
			
		||||
                        <label for="username" class="col-sm-4 col-form-label">Username</label>
 | 
			
		||||
                        <div class="col-sm-8">
 | 
			
		||||
                            <input type="text" name="username" class="form-control" id="username" placeholder="Username">
 | 
			
		||||
                        <div class="col-sm-4">
 | 
			
		||||
                            <input type="text" name="username" class="form-control" id="username" placeholder="Username" required>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <div class="col-sm-4">
 | 
			
		||||
                          <span class="switch">
 | 
			
		||||
                            <input type="checkbox" name="admin" class="switch" id="switch-normal">
 | 
			
		||||
                            <label for="switch-normal">Administrator</label>
 | 
			
		||||
                          </span>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="form-group row">
 | 
			
		||||
                        <label for="email" class="col-sm-4 col-form-label">Email Address</label>
 | 
			
		||||
                        <div class="col-sm-8">
 | 
			
		||||
                            <input type="text" name="email" class="form-control" id="email" placeholder="user@domain.com">
 | 
			
		||||
                            <input type="email" name="email" class="form-control" id="email" placeholder="user@domain.com" required>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="form-group row">
 | 
			
		||||
                        <label for="password" class="col-sm-4 col-form-label">Password</label>
 | 
			
		||||
                        <div class="col-sm-8">
 | 
			
		||||
                            <input type="password" name="password" class="form-control" id="password" placeholder="Password">
 | 
			
		||||
                            <input type="password" name="password" class="form-control" id="password" placeholder="Password" required>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="form-group row">
 | 
			
		||||
                        <label for="password_confirm" class="col-sm-4 col-form-label">Confirm Password</label>
 | 
			
		||||
                        <div class="col-sm-8">
 | 
			
		||||
                            <input type="password" name="password_confirm" class="form-control" id="password_confirm" placeholder="Confirm Password">
 | 
			
		||||
                            <input type="password" name="password_confirm" class="form-control" id="password_confirm" placeholder="Confirm Password" required>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="form-group row">
 | 
			
		||||
                        <div class="col-sm-8">
 | 
			
		||||
                            <button type="submit" class="btn btn-primary">Create User</button>
 | 
			
		||||
                        <div class="col-sm-12">
 | 
			
		||||
                            <button type="submit" class="btn btn-primary btn-block">Create User</button>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </form>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -61,7 +61,10 @@ type Email struct {
 | 
			
		|||
	To       string
 | 
			
		||||
	Subject  string
 | 
			
		||||
	Template string
 | 
			
		||||
	From     string
 | 
			
		||||
	Data     interface{}
 | 
			
		||||
	Source   string
 | 
			
		||||
	Sent     bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Config struct {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue