mirror of https://github.com/statping/statping
parent
b741e55e35
commit
70150c5318
|
@ -18,7 +18,7 @@ services:
|
||||||
|
|
||||||
env:
|
env:
|
||||||
global:
|
global:
|
||||||
- VERSION=0.28.5
|
- VERSION=0.28.6
|
||||||
- DB_HOST=localhost
|
- DB_HOST=localhost
|
||||||
- DB_USER=travis
|
- DB_USER=travis
|
||||||
- DB_PASS=
|
- 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
|
FROM alpine:latest
|
||||||
|
|
||||||
ENV VERSION=v0.28.5
|
ENV VERSION=v0.28.6
|
||||||
|
|
||||||
RUN apk --no-cache add libstdc++ ca-certificates
|
RUN apk --no-cache add libstdc++ ca-certificates
|
||||||
RUN wget -q https://github.com/hunterlong/statup/releases/download/$VERSION/statup-linux-alpine.tar.gz && \
|
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..")
|
utils.Log(1, "Inserting scss, css, emails, and javascript files into assets..")
|
||||||
CopyToPublic(ScssBox, "scss", "base.scss")
|
CopyToPublic(ScssBox, "scss", "base.scss")
|
||||||
CopyToPublic(ScssBox, "scss", "variables.scss")
|
CopyToPublic(ScssBox, "scss", "variables.scss")
|
||||||
CopyToPublic(EmailBox, "emails", "error.html")
|
CopyToPublic(EmailBox, "emails", "message.html")
|
||||||
CopyToPublic(EmailBox, "emails", "failure.html")
|
CopyToPublic(EmailBox, "emails", "failure.html")
|
||||||
CopyToPublic(CssBox, "css", "bootstrap.min.css")
|
CopyToPublic(CssBox, "css", "bootstrap.min.css")
|
||||||
CopyToPublic(JsBox, "js", "bootstrap.min.js")
|
CopyToPublic(JsBox, "js", "bootstrap.min.js")
|
||||||
|
@ -106,7 +106,6 @@ func CreateAllAssets() {
|
||||||
CopyToPublic(JsBox, "js", "setup.js")
|
CopyToPublic(JsBox, "js", "setup.js")
|
||||||
CopyToPublic(TmplBox, "", "robots.txt")
|
CopyToPublic(TmplBox, "", "robots.txt")
|
||||||
CopyToPublic(TmplBox, "", "favicon.ico")
|
CopyToPublic(TmplBox, "", "favicon.ico")
|
||||||
|
|
||||||
utils.Log(1, "Compiling CSS from SCSS style...")
|
utils.Log(1, "Compiling CSS from SCSS style...")
|
||||||
err := CompileSASS()
|
err := CompileSASS()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -17,10 +17,10 @@ type FailureData types.FailureData
|
||||||
|
|
||||||
func CheckServices() {
|
func CheckServices() {
|
||||||
CoreApp.Services, _ = SelectAllServices()
|
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 {
|
for _, v := range CoreApp.Services {
|
||||||
obj := v
|
obj := v
|
||||||
go obj.StartCheckins()
|
//go obj.StartCheckins()
|
||||||
go obj.CheckQueue()
|
go obj.CheckQueue()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,21 +1,36 @@
|
||||||
package core
|
package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/hunterlong/statup/notifications"
|
||||||
"github.com/hunterlong/statup/types"
|
"github.com/hunterlong/statup/types"
|
||||||
"github.com/hunterlong/statup/utils"
|
"github.com/hunterlong/statup/utils"
|
||||||
"net/http"
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Communication types.Communication
|
|
||||||
|
|
||||||
func LoadDefaultCommunications() {
|
func LoadDefaultCommunications() {
|
||||||
emailer := SelectCommunication(1)
|
notifications.EmailComm = SelectCommunication(1)
|
||||||
|
emailer := notifications.EmailComm
|
||||||
if emailer.Enabled {
|
if emailer.Enabled {
|
||||||
//LoadMailer(emailer)
|
admin, _ := SelectUser(1)
|
||||||
//go EmailerQueue()
|
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) {
|
func SelectAllCommunications() ([]*types.Communication, error) {
|
||||||
|
var c []*types.Communication
|
||||||
//sample := &Email{
|
|
||||||
// To: "info@socialeck.com",
|
|
||||||
// Subject: "Test Email from Statup",
|
|
||||||
//}
|
|
||||||
|
|
||||||
//AddEmail(sample)
|
|
||||||
}
|
|
||||||
|
|
||||||
func SelectAllCommunications() ([]*Communication, error) {
|
|
||||||
var c []*Communication
|
|
||||||
col := DbSession.Collection("communication").Find()
|
col := DbSession.Collection("communication").Find()
|
||||||
err := col.All(&c)
|
err := col.OrderBy("id").All(&c)
|
||||||
CoreApp.Communications = c
|
CoreApp.Communications = c
|
||||||
return c, err
|
return c, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func Create(c *Communication) (int64, error) {
|
func Create(c *types.Communication) (int64, error) {
|
||||||
c.CreatedAt = time.Now()
|
c.CreatedAt = time.Now()
|
||||||
uuid, err := DbSession.Collection("communication").Insert(c)
|
uuid, err := DbSession.Collection("communication").Insert(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -62,45 +67,30 @@ func Create(c *Communication) (int64, error) {
|
||||||
return uuid.(int64), err
|
return uuid.(int64), err
|
||||||
}
|
}
|
||||||
|
|
||||||
func Disable(c *Communication) {
|
func Disable(c *types.Communication) {
|
||||||
c.Enabled = false
|
c.Enabled = false
|
||||||
Update(c)
|
Update(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Enable(c *Communication) {
|
func Enable(c *types.Communication) {
|
||||||
c.Enabled = true
|
c.Enabled = true
|
||||||
Update(c)
|
Update(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Update(c *Communication) *Communication {
|
func Update(c *types.Communication) *types.Communication {
|
||||||
col := DbSession.Collection("communication").Find("id", c.Id)
|
col := DbSession.Collection("communication").Find("id", c.Id)
|
||||||
col.Update(c)
|
col.Update(c)
|
||||||
|
SelectAllCommunications()
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
func SelectCommunication(id int64) *Communication {
|
func SelectCommunication(id int64) *types.Communication {
|
||||||
for _, c := range CoreApp.Communications {
|
var comm *types.Communication
|
||||||
if c.Id == id {
|
col := DbSession.Collection("communication").Find("id", id)
|
||||||
return c
|
err := col.One(&comm)
|
||||||
}
|
if err != nil {
|
||||||
}
|
utils.Log(2, err)
|
||||||
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."))
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
client := http.Client{
|
return comm
|
||||||
Timeout: 15 * time.Second,
|
|
||||||
}
|
|
||||||
_, err := client.Post(slack.Host, "application/json", bytes.NewBuffer([]byte(fullMessage)))
|
|
||||||
if err != nil {
|
|
||||||
utils.Log(3, err)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
12
core/core.go
12
core/core.go
|
@ -23,7 +23,7 @@ type Core struct {
|
||||||
Plugins []plugin.Info
|
Plugins []plugin.Info
|
||||||
Repos []PluginJSON
|
Repos []PluginJSON
|
||||||
//PluginFields []PluginSelect
|
//PluginFields []PluginSelect
|
||||||
Communications []*Communication
|
Communications []*types.Communication
|
||||||
OfflineAssets bool
|
OfflineAssets bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,6 +45,16 @@ func init() {
|
||||||
CoreApp = new(Core)
|
CoreApp = new(Core)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func InitApp() {
|
||||||
|
SelectCore()
|
||||||
|
SelectAllCommunications()
|
||||||
|
InsertDefaultComms()
|
||||||
|
LoadDefaultCommunications()
|
||||||
|
SelectAllServices()
|
||||||
|
CheckServices()
|
||||||
|
go DatabaseMaintence()
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Core) Update() (*Core, error) {
|
func (c *Core) Update() (*Core, error) {
|
||||||
res := DbSession.Collection("core").Find().Limit(1)
|
res := DbSession.Collection("core").Find().Limit(1)
|
||||||
res.Update(c)
|
res.Update(c)
|
||||||
|
|
|
@ -3,7 +3,9 @@ package core
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/fatih/structs"
|
"github.com/fatih/structs"
|
||||||
|
"github.com/hunterlong/statup/notifications"
|
||||||
"github.com/hunterlong/statup/plugin"
|
"github.com/hunterlong/statup/plugin"
|
||||||
|
"github.com/hunterlong/statup/types"
|
||||||
"upper.io/db.v3/lib/sqlbuilder"
|
"upper.io/db.v3/lib/sqlbuilder"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -23,13 +25,38 @@ func OnFailure(s *Service, f FailureData) {
|
||||||
for _, p := range AllPlugins {
|
for _, p := range AllPlugins {
|
||||||
p.OnFailure(structs.Map(s))
|
p.OnFailure(structs.Map(s))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onFailureEmail(s, f)
|
||||||
|
onFailureSlack(s, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func onFailureSlack(s *Service, f FailureData) {
|
||||||
slack := SelectCommunication(2)
|
slack := SelectCommunication(2)
|
||||||
if slack == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if slack.Enabled {
|
if slack.Enabled {
|
||||||
msg := fmt.Sprintf("Service %v is currently offline! Issue: %v", s.Name, f.Issue)
|
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
|
package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/hunterlong/statup/types"
|
||||||
"github.com/hunterlong/statup/utils"
|
"github.com/hunterlong/statup/utils"
|
||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
func InsertDefaultComms() {
|
func InsertDefaultComms() {
|
||||||
emailer := &Communication{
|
emailer := SelectCommunication(1)
|
||||||
Method: "email",
|
if emailer == nil {
|
||||||
Removable: false,
|
emailer := &types.Communication{
|
||||||
Enabled: false,
|
Method: "email",
|
||||||
|
Removable: false,
|
||||||
|
Enabled: false,
|
||||||
|
}
|
||||||
|
Create(emailer)
|
||||||
}
|
}
|
||||||
Create(emailer)
|
slack := SelectCommunication(2)
|
||||||
slack := &Communication{
|
if slack == nil {
|
||||||
Method: "slack",
|
slack := &types.Communication{
|
||||||
Removable: false,
|
Method: "slack",
|
||||||
Enabled: false,
|
Removable: false,
|
||||||
|
Enabled: false,
|
||||||
|
}
|
||||||
|
Create(slack)
|
||||||
}
|
}
|
||||||
Create(slack)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func DeleteConfig() {
|
func DeleteConfig() {
|
||||||
|
@ -82,12 +89,14 @@ func LoadSampleData() error {
|
||||||
}
|
}
|
||||||
checkin.Create()
|
checkin.Create()
|
||||||
|
|
||||||
for i := 0; i < 20; i++ {
|
//for i := 0; i < 3; i++ {
|
||||||
s1.Check()
|
// s1.Check()
|
||||||
s2.Check()
|
// s2.Check()
|
||||||
s3.Check()
|
// s3.Check()
|
||||||
s4.Check()
|
// s4.Check()
|
||||||
}
|
//}
|
||||||
|
|
||||||
|
utils.Log(1, "Sample data has finished importing")
|
||||||
|
|
||||||
return nil
|
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) {
|
func Error404Handler(w http.ResponseWriter, r *http.Request) {
|
||||||
w.WriteHeader(http.StatusNotFound)
|
w.WriteHeader(http.StatusNotFound)
|
||||||
ExecuteResponse(w, r, "error_404.html", nil)
|
ExecuteResponse(w, r, "error_404.html", nil)
|
||||||
}
|
}
|
|
@ -3,17 +3,17 @@ package handlers
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/hunterlong/statup/core"
|
"github.com/hunterlong/statup/core"
|
||||||
|
"github.com/hunterlong/statup/notifications"
|
||||||
|
"github.com/hunterlong/statup/types"
|
||||||
"github.com/hunterlong/statup/utils"
|
"github.com/hunterlong/statup/utils"
|
||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
func PluginsHandler(w http.ResponseWriter, r *http.Request) {
|
func PluginsHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
auth := IsAuthenticated(r)
|
if !IsAuthenticated(r) {
|
||||||
if !auth {
|
|
||||||
http.Redirect(w, r, "/", http.StatusSeeOther)
|
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
//CoreApp.FetchPluginRepo()
|
//CoreApp.FetchPluginRepo()
|
||||||
|
|
||||||
//var pluginFields []PluginSelect
|
//var pluginFields []PluginSelect
|
||||||
|
@ -31,8 +31,7 @@ func PluginsHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func SaveSettingsHandler(w http.ResponseWriter, r *http.Request) {
|
func SaveSettingsHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
auth := IsAuthenticated(r)
|
if !IsAuthenticated(r) {
|
||||||
if !auth {
|
|
||||||
http.Redirect(w, r, "/", http.StatusSeeOther)
|
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -63,8 +62,7 @@ func SaveSettingsHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func SaveSASSHandler(w http.ResponseWriter, r *http.Request) {
|
func SaveSASSHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
auth := IsAuthenticated(r)
|
if !IsAuthenticated(r) {
|
||||||
if !auth {
|
|
||||||
http.Redirect(w, r, "/", http.StatusSeeOther)
|
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -78,8 +76,7 @@ func SaveSASSHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func SaveAssetsHandler(w http.ResponseWriter, r *http.Request) {
|
func SaveAssetsHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
auth := IsAuthenticated(r)
|
if !IsAuthenticated(r) {
|
||||||
if !auth {
|
|
||||||
http.Redirect(w, r, "/", http.StatusSeeOther)
|
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -89,45 +86,64 @@ func SaveAssetsHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func SaveEmailSettingsHandler(w http.ResponseWriter, r *http.Request) {
|
func SaveEmailSettingsHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
auth := IsAuthenticated(r)
|
if !IsAuthenticated(r) {
|
||||||
if !auth {
|
|
||||||
http.Redirect(w, r, "/", http.StatusSeeOther)
|
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
emailer := core.SelectCommunication(1)
|
emailer := core.SelectCommunication(1)
|
||||||
|
|
||||||
r.ParseForm()
|
r.ParseForm()
|
||||||
emailer.Host = r.PostForm.Get("host")
|
smtpHost := r.PostForm.Get("host")
|
||||||
emailer.Username = r.PostForm.Get("username")
|
smtpUser := r.PostForm.Get("username")
|
||||||
emailer.Password = r.PostForm.Get("password")
|
smtpPass := r.PostForm.Get("password")
|
||||||
emailer.Port = int(utils.StringInt(r.PostForm.Get("port")))
|
smtpPort := int(utils.StringInt(r.PostForm.Get("port")))
|
||||||
emailer.Var1 = r.PostForm.Get("address")
|
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)
|
core.Update(emailer)
|
||||||
|
|
||||||
//sample := &Email{
|
sample := &types.Email{
|
||||||
// To: SessionUser(r).Email,
|
To: SessionUser(r).Email,
|
||||||
// Subject: "Sample Email",
|
Subject: "Test Email",
|
||||||
// Template: "error.html",
|
Template: "message.html",
|
||||||
//}
|
From: emailer.Var1,
|
||||||
//AddEmail(sample)
|
}
|
||||||
|
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)
|
http.Redirect(w, r, "/settings", http.StatusSeeOther)
|
||||||
}
|
}
|
||||||
|
|
||||||
func SaveSlackSettingsHandler(w http.ResponseWriter, r *http.Request) {
|
func SaveSlackSettingsHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
auth := IsAuthenticated(r)
|
if !IsAuthenticated(r) {
|
||||||
if !auth {
|
|
||||||
http.Redirect(w, r, "/", http.StatusSeeOther)
|
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
slack := core.SelectCommunication(2)
|
slack := core.SelectCommunication(2)
|
||||||
r.ParseForm()
|
r.ParseForm()
|
||||||
slack.Host = r.PostForm.Get("host")
|
slackWebhook := r.PostForm.Get("slack_url")
|
||||||
slack.Enabled = true
|
enable := r.PostForm.Get("enable_slack")
|
||||||
if slack.Host == "" {
|
slack.Enabled = false
|
||||||
slack.Enabled = false
|
if enable == "on" && slackWebhook != "" {
|
||||||
|
slack.Enabled = true
|
||||||
|
go notifications.SlackRoutine()
|
||||||
}
|
}
|
||||||
|
slack.Host = slackWebhook
|
||||||
core.Update(slack)
|
core.Update(slack)
|
||||||
core.SendSlackMessage("This is a test from Statup!")
|
|
||||||
http.Redirect(w, r, "/settings", http.StatusSeeOther)
|
http.Redirect(w, r, "/settings", http.StatusSeeOther)
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func SetupHandler(w http.ResponseWriter, r *http.Request) {
|
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)
|
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
port := 5432
|
port := 5432
|
||||||
if os.Getenv("DB_CONN") == "mysql" {
|
if os.Getenv("DB_CONN") == "mysql" {
|
||||||
port = 3306
|
port = 3306
|
||||||
|
@ -109,15 +111,13 @@ func ProcessSetupHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
admin.Create()
|
admin.Create()
|
||||||
|
|
||||||
core.InsertDefaultComms()
|
|
||||||
|
|
||||||
if sample == "on" {
|
if sample == "on" {
|
||||||
go core.LoadSampleData()
|
core.LoadSampleData()
|
||||||
}
|
}
|
||||||
|
|
||||||
core.SelectCore()
|
core.InitApp()
|
||||||
|
time.Sleep(2 * time.Second)
|
||||||
http.Redirect(w, r, "/", http.StatusSeeOther)
|
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||||
//mainProcess()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetupResponseError(w http.ResponseWriter, r *http.Request, a interface{}) {
|
func SetupResponseError(w http.ResponseWriter, r *http.Request, a interface{}) {
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package handlers
|
package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/hunterlong/statup/core"
|
"github.com/hunterlong/statup/core"
|
||||||
"github.com/hunterlong/statup/types"
|
"github.com/hunterlong/statup/types"
|
||||||
|
@ -39,15 +38,17 @@ func CreateUserHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
http.Redirect(w, r, "/", http.StatusSeeOther)
|
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fmt.Println("creating user")
|
|
||||||
r.ParseForm()
|
r.ParseForm()
|
||||||
username := r.PostForm.Get("username")
|
username := r.PostForm.Get("username")
|
||||||
password := r.PostForm.Get("password")
|
password := r.PostForm.Get("password")
|
||||||
email := r.PostForm.Get("email")
|
email := r.PostForm.Get("email")
|
||||||
|
admin := r.PostForm.Get("admin")
|
||||||
|
|
||||||
user := &core.User{
|
user := &core.User{
|
||||||
Username: username,
|
Username: username,
|
||||||
Password: password,
|
Password: password,
|
||||||
Email: email,
|
Email: email,
|
||||||
|
Admin: (admin == "on"),
|
||||||
}
|
}
|
||||||
_, err := user.Create()
|
_, err := user.Create()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
15
main.go
15
main.go
|
@ -65,20 +65,7 @@ func mainProcess() {
|
||||||
utils.Log(3, err)
|
utils.Log(3, err)
|
||||||
}
|
}
|
||||||
core.RunDatabaseUpgrades()
|
core.RunDatabaseUpgrades()
|
||||||
core.CoreApp, err = core.SelectCore()
|
core.InitApp()
|
||||||
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()
|
|
||||||
|
|
||||||
if !core.SetupMode {
|
if !core.SetupMode {
|
||||||
LoadPlugins()
|
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);
|
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; }
|
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 {
|
@keyframes pulse_animation {
|
||||||
0% {
|
0% {
|
||||||
transform: scale(1); }
|
transform: scale(1); }
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -36,19 +36,24 @@
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<td class="content-cell" style="box-sizing: border-box; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; padding: 35px; word-break: break-word;">
|
<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">
|
<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>
|
</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>
|
<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">
|
<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>
|
</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;">
|
<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;">
|
<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>
|
</td>
|
||||||
</table>
|
</table>
|
||||||
</td>
|
</td>
|
||||||
|
|
|
@ -36,7 +36,7 @@
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<td class="content-cell" style="box-sizing: border-box; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; padding: 35px; word-break: break-word;">
|
<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">
|
<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.
|
Since you got this email, it confirms that your Statup Status Page email system is working correctly.
|
||||||
</p>
|
</p>
|
|
@ -242,6 +242,119 @@ HTML,BODY {
|
||||||
transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;
|
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 {
|
@keyframes pulse_animation {
|
||||||
0% { transform: scale(1); }
|
0% { transform: scale(1); }
|
||||||
30% { transform: scale(1); }
|
30% { transform: scale(1); }
|
||||||
|
|
|
@ -79,22 +79,22 @@
|
||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
<label for="service_name" class="col-sm-4 col-form-label">Service Name</label>
|
<label for="service_name" class="col-sm-4 col-form-label">Service Name</label>
|
||||||
<div class="col-sm-8">
|
<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>
|
</div>
|
||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
<label for="service_type" class="col-sm-4 col-form-label">Service Check Type</label>
|
<label for="service_type" class="col-sm-4 col-form-label">Service Check Type</label>
|
||||||
<div class="col-sm-8">
|
<div class="col-sm-8">
|
||||||
<select name="check_type" class="form-control" id="service_type" value="{{.Type}}">
|
<select name="check_type" class="form-control" id="service_type" value="{{.Type}}">
|
||||||
<option value="http" selected>HTTP Service</option>
|
<option value="http" {{if eq .Type "http"}}selected{{end}}>HTTP Service</option>
|
||||||
<option value="tcp">TCP Service</option>
|
<option value="tcp" {{if eq .Type "tcp"}}selected{{end}}>TCP Service</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
<label for="service_url" class="col-sm-4 col-form-label">Application Endpoint (URL)</label>
|
<label for="service_url" class="col-sm-4 col-form-label">Application Endpoint (URL)</label>
|
||||||
<div class="col-sm-8">
|
<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>
|
</div>
|
||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
|
@ -121,7 +121,7 @@
|
||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
<label for="service_response_code" class="col-sm-4 col-form-label">Expected Status Code</label>
|
<label for="service_response_code" class="col-sm-4 col-form-label">Expected Status Code</label>
|
||||||
<div class="col-sm-8">
|
<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>
|
</div>
|
||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
|
@ -133,7 +133,7 @@
|
||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
<label for="service_interval" class="col-sm-4 col-form-label">Check Interval (Seconds)</label>
|
<label for="service_interval" class="col-sm-4 col-form-label">Check Interval (Seconds)</label>
|
||||||
<div class="col-sm-8">
|
<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>
|
</div>
|
||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
|
|
|
@ -50,7 +50,7 @@
|
||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
<label for="service_name" class="col-sm-4 col-form-label">Service Name</label>
|
<label for="service_name" class="col-sm-4 col-form-label">Service Name</label>
|
||||||
<div class="col-sm-8">
|
<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>
|
</div>
|
||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
|
@ -65,7 +65,7 @@
|
||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
<label for="service_url" class="col-sm-4 col-form-label">Application Endpoint (URL)</label>
|
<label for="service_url" class="col-sm-4 col-form-label">Application Endpoint (URL)</label>
|
||||||
<div class="col-sm-8">
|
<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>
|
</div>
|
||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
|
@ -104,7 +104,7 @@
|
||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
<label for="service_interval" class="col-sm-4 col-form-label">Check Interval (Seconds)</label>
|
<label for="service_interval" class="col-sm-4 col-form-label">Check Interval (Seconds)</label>
|
||||||
<div class="col-sm-8">
|
<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>
|
</div>
|
||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
|
|
|
@ -105,7 +105,7 @@
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" class="btn btn-primary btn-block mt-2">Save Style</button>
|
<button type="submit" class="btn btn-primary btn-block mt-2">Save Style</button>
|
||||||
</form>
|
</form>
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{ with $c := index .Communications 0 }}
|
{{ with $c := index .Communications 0 }}
|
||||||
|
@ -125,7 +125,7 @@
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="password">SMTP Password</label>
|
<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>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
@ -143,7 +143,19 @@
|
||||||
<input type="number" name="limit" class="form-control" value="30" id="limit" placeholder="noreply@domain.com">
|
<input type="number" name="limit" class="form-control" value="30" id="limit" placeholder="noreply@domain.com">
|
||||||
</div>
|
</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>
|
</form>
|
||||||
|
|
||||||
|
@ -151,22 +163,34 @@
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
|
|
||||||
{{ with $c := index .Communications 1 }}
|
{{ with $c := index .Communications 1 }}
|
||||||
<div class="tab-pane fade" id="v-pills-{{ $c.Method }}" role="tabpanel" aria-labelledby="v-pills-{{ $c.Method }}-tab">
|
<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">
|
<div class="form-group">
|
||||||
<label for="host">Slack Webhook URL</label>
|
<label for="slack_url">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">
|
<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>
|
</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">
|
<div class="tab-pane fade" id="v-pills-browse" role="tabpanel" aria-labelledby="v-pills-browse-tab">
|
||||||
{{ range .Repos }}
|
{{ range .Repos }}
|
||||||
|
|
|
@ -44,31 +44,37 @@
|
||||||
<form action="/users" method="POST">
|
<form action="/users" method="POST">
|
||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
<label for="username" class="col-sm-4 col-form-label">Username</label>
|
<label for="username" class="col-sm-4 col-form-label">Username</label>
|
||||||
<div class="col-sm-8">
|
<div class="col-sm-4">
|
||||||
<input type="text" name="username" class="form-control" id="username" placeholder="Username">
|
<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>
|
</div>
|
||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
<label for="email" class="col-sm-4 col-form-label">Email Address</label>
|
<label for="email" class="col-sm-4 col-form-label">Email Address</label>
|
||||||
<div class="col-sm-8">
|
<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>
|
</div>
|
||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
<label for="password" class="col-sm-4 col-form-label">Password</label>
|
<label for="password" class="col-sm-4 col-form-label">Password</label>
|
||||||
<div class="col-sm-8">
|
<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>
|
</div>
|
||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
<label for="password_confirm" class="col-sm-4 col-form-label">Confirm Password</label>
|
<label for="password_confirm" class="col-sm-4 col-form-label">Confirm Password</label>
|
||||||
<div class="col-sm-8">
|
<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>
|
</div>
|
||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
<div class="col-sm-8">
|
<div class="col-sm-12">
|
||||||
<button type="submit" class="btn btn-primary">Create User</button>
|
<button type="submit" class="btn btn-primary btn-block">Create User</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -61,7 +61,10 @@ type Email struct {
|
||||||
To string
|
To string
|
||||||
Subject string
|
Subject string
|
||||||
Template string
|
Template string
|
||||||
|
From string
|
||||||
Data interface{}
|
Data interface{}
|
||||||
|
Source string
|
||||||
|
Sent bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
|
|
Loading…
Reference in New Issue